Skip to content

Commit

Permalink
1. New: Backup the resulting file if it already exists;
Browse files Browse the repository at this point in the history
2. New: Add `threshold` parameter to control the proportion of face area;
3. Fix: `image_type` did not work;
4. Fix: Empty `log` folder would be created when run `stone -h`;
  • Loading branch information
ChenglongMa committed Aug 29, 2023
1 parent ea4465c commit ae51ba7
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 12 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup
from setuptools import find_packages

VERSION = '0.2.2'
VERSION = '1.0.0'

with open('README.md') as f:
LONG_DESCRIPTION = f.read()
Expand Down
15 changes: 9 additions & 6 deletions src/stone/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def process_image(filename, image_type_setting,
specified_palette, default_palette,
specified_tone_labels, default_tone_labels,
to_bw, new_width, n_dominant_colors,
scale, min_nbrs, min_size, verbose):
scale, min_nbrs, min_size, threshold, verbose):
basename, extension = filename.stem, filename.suffix

image: np.ndarray = cv2.imread(str(filename.resolve()), cv2.IMREAD_COLOR)
Expand All @@ -45,6 +45,8 @@ def process_image(filename, image_type_setting,
image_type = image_type_setting
if image_type == 'auto':
image_type = 'bw' if is_bw else 'color'
else:
is_bw = image_type == 'bw'
if len(specified_palette) == 0:
skin_tone_palette = default_palette['bw' if to_bw or is_bw else 'color']
else:
Expand All @@ -57,7 +59,8 @@ def process_image(filename, image_type_setting,
try:
records, report_images = process(image, is_bw, to_bw, skin_tone_palette, tone_labels,
new_width=new_width, n_dominant_colors=n_dominant_colors,
scaleFactor=scale, minNeighbors=min_nbrs, minSize=min_size, verbose=verbose)
scaleFactor=scale, minNeighbors=min_nbrs, minSize=min_size, threshold=threshold,
verbose=verbose)
return {
'basename': basename,
'extension': extension,
Expand All @@ -76,6 +79,7 @@ def process_image(filename, image_type_setting,


def main():
args = build_arguments()
# Setup logger
now = datetime.now()
os.makedirs('./log', exist_ok=True)
Expand All @@ -87,8 +91,6 @@ def main():
datefmt='%H:%M:%S'
)

args = build_arguments()

filenames = build_filenames(args.images)
is_single_file = len(filenames) == 1

Expand Down Expand Up @@ -127,6 +129,7 @@ def main():
os.makedirs(output_dir, exist_ok=True)
result_filename = os.path.join(output_dir, './result.csv')
image_type_setting = args.image_type
threshold = args.threshold

def write_to_csv(row: list):
with lock:
Expand All @@ -139,7 +142,7 @@ def write_to_csv(row: list):

# Backup result.csv if exists
if os.path.exists(result_filename):
renamed_file = os.path.join(output_dir, './result_bak.csv')
renamed_file = os.path.join(output_dir, now.strftime('./result_bak_%y%m%d%H%M.csv'))
shutil.move(result_filename, renamed_file)
header = 'file,image type,face id,' + ','.join(
[f'dominant {i + 1},props {i + 1}' for i in range(n_dominant_colors)]) + ',skin tone,PERLA,accuracy(0-100)'
Expand All @@ -150,7 +153,7 @@ def write_to_csv(row: list):
specified_palette=specified_palette, default_palette=default_tone_palette,
specified_tone_labels=specified_tone_labels, default_tone_labels=default_tone_labels,
to_bw=to_bw, new_width=new_width, n_dominant_colors=n_dominant_colors,
scale=scale, min_nbrs=min_nbrs, min_size=min_size, verbose=debug)
scale=scale, min_nbrs=min_nbrs, min_size=min_size, threshold=threshold, verbose=debug)

with logging_redirect_tqdm():
with tqdm(filenames, desc='Processing images', unit='images') as pbar:
Expand Down
20 changes: 15 additions & 5 deletions src/stone/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def resize(image, width: int = -1, height: int = -1):
return cv2.resize(image, (width, height))


def detect_faces(image, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), biggest_only=True):
def detect_faces(image, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), biggest_only=True, is_bw=False, threshold=0.3):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray)

Expand All @@ -74,7 +74,18 @@ def detect_faces(image, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), bigge
return []
# Change the format of faces from (x, y, w, h) to (x, y, x+w, y+h)
faces[:, 2:] += faces[:, :2]
return faces
return [face for face in faces if is_face(face, image, is_bw, threshold)]


def is_face(face_coord, image, is_bw, threshold=0.3):
x1, y1, x2, y2 = face_coord
face_image = image[y1:y2, x1:x2]
detect_skin_fn = detect_skin_in_bw if is_bw else detect_skin_in_color
_, skin_mask = detect_skin_fn(face_image)
skin_pixels = cv2.countNonZero(skin_mask)
total_pixels = face_image.shape[0] * face_image.shape[1]
skin_ratio = skin_pixels / total_pixels
return skin_ratio >= threshold


def mask_face(image, face):
Expand Down Expand Up @@ -287,12 +298,11 @@ def create_message_bar(dmnt_colors, dmnt_props, tone_hex, distance, bar_width):


def process(image: np.ndarray, is_bw: bool, to_bw: bool, skin_tone_palette: list, tone_labels: list = None, new_width=-1, n_dominant_colors=2,
scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), biggest_only=True,
verbose=False):
scaleFactor=1.1, minNeighbors=5, minSize=(30, 30), biggest_only=True, threshold=0.3, verbose=False):
image = resize(image, new_width)

records, report_images = {}, {}
face_coords = detect_faces(image, scaleFactor, minNeighbors, minSize, biggest_only)
face_coords = detect_faces(image, scaleFactor, minNeighbors, minSize, biggest_only, is_bw, threshold)
n_faces = len(face_coords)

if n_faces == 0:
Expand Down
2 changes: 2 additions & 0 deletions src/stone/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,7 @@ def build_arguments():
'Higher value results in less detections but with higher quality, defaults to 5', default=5)
parser.add_argument('--min_size', type=int, nargs='+', metavar=('WIDTH', 'HEIGHT'),
help='CONFIG: minimum possible face size. Faces smaller than that are ignored, defaults to "90 90".', default=(90, 90))
parser.add_argument('--threshold', type=float, metavar='THRESHOLD', help='CONFIG: what proportion of the skin area is required to identify the '
'face, defaults to 0.3', default=0.3)

return parser.parse_args()

0 comments on commit ae51ba7

Please sign in to comment.