diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ea35cb8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +ENTRYPOINT ["python", "SkinDetection.py"] \ No newline at end of file diff --git a/SkinDetection.py b/SkinDetection.py index 47e988f..6cccbd1 100755 --- a/SkinDetection.py +++ b/SkinDetection.py @@ -1,114 +1,142 @@ #!/usr/bin/env python3 -#SkinDetection.py +"""SkinDetection.py -# This program runs all 4 skin detection algorithms one after the other -# Run `python3 SkinDetection.py Path/To/Image` to specify -# inital image, removing this command arguements will result in -# the script attempting hard coded locations so errors may arise -# The +Runs several skin-detection algorithms on a single image and saves results. +""" -from PIL import Image -import math +from __future__ import annotations + +import argparse import colorsys import sys +from pathlib import Path +from typing import Callable -#Explicitly Defined Skin Region Model -def EDSRModel(image_path): - im = Image.open(image_path) - pixels = im.load() - rgb_im = im.convert('RGB') - for i in range(im.size[0]): - for j in range(im.size[1]): - r,g,b = rgb_im.getpixel((i,j)) - if r > 95 and g > 40 and b > 20 and (max(r, g, b) - min(r, g, b)) > 15 and abs(r - g) > 15 and r > g and r > b: - x = j - y = i - else: - pixels[i,j] = (0,0,0) +from PIL import Image - im.save("Results/Explicitly-Defined-Skin-Region.png") -#Colour Segmentation in Normalization rg Color Model -def NormalizationrgModel(image_path): - im = Image.open(image_path) - pixels = im.load() - rgb_im = im.convert('RGB') - for i in range(im.size[0]): - for j in range(im.size[1]): - r,g,b = rgb_im.getpixel((i,j)) - - R = 0.0 - G = 0.0 - B = 0.0 - - if r > 0 or g > 0 or b > 0: - R = r/float(r+g+b) - G = g/float(r+g+b) - B = b/float(r+g+b) - - if 0.465 >= R >= 0.36 and 0.363 >= G >= 0.28: - x = j - y = i - else: - pixels[i,j] = (0,0,0) +def load_image(path: Path) -> Image.Image: + image = Image.open(path) + return image.convert("RGB") - im.save("Results/Normalizationrg.png") -#Color Segmentation in HSV Color Model -def HSVModel(image_path): - im = Image.open(image_path) - pixels = im.load() - rgb_im = im.convert('RGB') - for i in range(im.size[0]): - for j in range(im.size[1]): - r,g,b = rgb_im.getpixel((i,j)) - - r = r/255.0 - g = g/255.0 - b = b/255.0 - - h,s,v = colorsys.rgb_to_hsv(r,g,b) - - h = h*255 - - if 50 >= h >= 0 and 0.68 >= s >= 0.2 and 1 >= v >= 0.35: - x = j - y = i - else: - pixels[i,j] = (0,0,0) +def save_result(image: Image.Image, path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + image.save(path) - im.save("Results/HSV.png") -#Color Segmentation in YCBCR Color Model -def YCBCRModel(image_path): - im = Image.open(image_path) - pixels = im.load() - ycbcr_im = im.convert('YCbCr') - for i in range(im.size[0]): - for j in range(im.size[1]): - y,Cb,Cr = ycbcr_im.getpixel((i,j)) - - if 142.5 >= Cb >= 97.5 and 134 >= Cr >= 17: - pixels[i,j] = (0,0,0) - else: - x = i - y = j +def mask_image( + image: Image.Image, + predicate: Callable[[int, int, int], bool], + invert: bool = False, +) -> Image.Image: + result = image.copy() + pixels = result.load() - im.save("Results/YCBCR.png") + for x in range(result.width): + for y in range(result.height): + r, g, b = image.getpixel((x, y)) + if predicate(r, g, b) ^ invert: + continue + pixels[x, y] = (0, 0, 0) -def main(): - args = sys.argv[1:] + return result - if len(sys.argv) == 1: - image_path = "Images/skin.jpg" - else: - image_path = args[0] - EDSRModel(image_path) - NormalizationrgModel(image_path) - HSVModel(image_path) - YCBCRModel(image_path) - return +def explicitly_defined_skin_region(r: int, g: int, b: int) -> bool: + return ( + r > 95 + and g > 40 + and b > 20 + and (max(r, g, b) - min(r, g, b)) > 15 + and abs(r - g) > 15 + and r > g + and r > b + ) + + +def normalized_rg_skin_region(r: int, g: int, b: int) -> bool: + total = r + g + b + if total == 0: + return False + + R = r / float(total) + G = g / float(total) + return 0.36 <= R <= 0.465 and 0.28 <= G <= 0.363 + + +def hsv_skin_region(r: int, g: int, b: int) -> bool: + r_norm = r / 255.0 + g_norm = g / 255.0 + b_norm = b / 255.0 + h, s, v = colorsys.rgb_to_hsv(r_norm, g_norm, b_norm) + h *= 255 + return 0 <= h <= 50 and 0.2 <= s <= 0.68 and 0.35 <= v <= 1 + + +def ycbcr_skin_region(r: int, g: int, b: int) -> bool: + y = 0.299 * r + 0.587 * g + 0.114 * b + cb = 128 - 0.168736 * r - 0.331264 * g + 0.5 * b + cr = 128 + 0.5 * r - 0.418688 * g - 0.081312 * b + return 97.5 <= cb <= 142.5 and 17 <= cr <= 134 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Run multiple skin detection algorithms on an input image." + ) + parser.add_argument( + "image_path", + nargs="?", + default="Images/skin.jpg", + help="Path to the input image.", + ) + parser.add_argument( + "--output-dir", + default="Results", + help="Directory to write output images.", + ) + parser.add_argument( + "--show", + action="store_true", + help="Open each generated result image after processing.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + image_path = Path(args.image_path) + output_dir = Path(args.output_dir) + show_results = args.show + + if not image_path.exists(): + print(f"Error: image path not found: {image_path}", file=sys.stderr) + return 1 + + try: + image = load_image(image_path) + except OSError as error: + print(f"Error opening image: {error}", file=sys.stderr) + return 1 + + algorithms = [ + ("Explicitly-Defined-Skin-Region.png", explicitly_defined_skin_region, False), + ("Normalizationrg.png", normalized_rg_skin_region, False), + ("HSV.png", hsv_skin_region, False), + ("YCBCR.png", ycbcr_skin_region, True), + ] + + for filename, predicate, invert in algorithms: + output_path = output_dir / filename + result = mask_image(image, predicate, invert=invert) + save_result(result, output_path) + if show_results: + result.show(title=filename) + + print(f"Saved {len(algorithms)} results to {output_dir}") + return 0 + if __name__ == "__main__": - main() + raise SystemExit(main())