#!/usr/bin/env python3 """SkinDetection.py Runs several skin-detection algorithms on a single image and saves results. """ from __future__ import annotations import argparse import colorsys import sys from pathlib import Path from typing import Callable from PIL import Image def load_image(path: Path) -> Image.Image: image = Image.open(path) return image.convert("RGB") def save_result(image: Image.Image, path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) image.save(path) def mask_image( image: Image.Image, predicate: Callable[[int, int, int], bool], invert: bool = False, ) -> Image.Image: result = image.copy() pixels = result.load() 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) return result 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__": raise SystemExit(main())