bank-thief-detection / src /mask_processor.py
dineth554's picture
Upload src/mask_processor.py with huggingface_hub
895a921 verified
Raw
History Blame Contribute Delete
6 kB
"""
Face Mask Detection and Inpainting Module.
Detects if a person is wearing a face mask and removes it via inpainting
to reveal the underlying face for recognition.
Mask detection uses a heuristic based on lower-face analysis.
Inpainting uses OpenCV's TELEA algorithm on the masked region.
"""
from typing import Tuple, Optional
import numpy as np
import cv2
class MaskProcessor:
"""
Detects face masks and performs inpainting to reveal the face.
Uses color/texture analysis in the lower half of the face to detect masks.
Inpaints using cv2.INPAINT_TELEA for CPU-friendly operation.
"""
def __init__(self, mask_threshold: float = 0.5):
"""
Initialize the mask processor.
Args:
mask_threshold: Confidence threshold for mask detection.
"""
self.mask_threshold = mask_threshold
# Skin color ranges in HSV for lower-face analysis
self.lower_skin = np.array([0, 20, 70], dtype=np.uint8)
self.upper_skin = np.array([20, 150, 255], dtype=np.uint8)
# Mask color ranges (blue/green surgical masks, black cloth masks)
self.mask_colors = {
'blue': (np.array([90, 50, 50], dtype=np.uint8), np.array([120, 255, 255], dtype=np.uint8)),
'green': (np.array([40, 50, 50], dtype=np.uint8), np.array([80, 255, 255], dtype=np.uint8)),
'white': (np.array([0, 0, 180], dtype=np.uint8), np.array([180, 30, 255], dtype=np.uint8)),
'black': (np.array([0, 0, 0], dtype=np.uint8), np.array([180, 255, 60], dtype=np.uint8)),
}
def detect_mask(self, face_crop: np.ndarray) -> Tuple[bool, float]:
"""
Detect if a face crop contains a mask.
Analyzes the lower half of the face for mask-like features.
Uses color segmentation and texture analysis.
Args:
face_crop: Cropped face image (BGR).
Returns:
Tuple of (is_masked: bool, confidence: float).
"""
if face_crop is None or face_crop.size == 0:
return False, 0.0
h, w = face_crop.shape[:2]
if h < 20 or w < 20:
return False, 0.0
# Extract lower half of face (where masks are worn)
lower_half = face_crop[h // 2:, :, :]
# Convert to HSV for color analysis
hsv = cv2.cvtColor(lower_half, cv2.COLOR_BGR2HSV)
# Check for skin pixels in lower half
skin_mask = cv2.inRange(hsv, self.lower_skin, self.upper_skin)
skin_ratio = np.sum(skin_mask > 0) / skin_mask.size
# Check for mask color pixels
mask_pixel_ratio = 0.0
for color_name, (lower, upper) in self.mask_colors.items():
color_mask = cv2.inRange(hsv, lower, upper)
ratio = np.sum(color_mask > 0) / color_mask.size
mask_pixel_ratio = max(mask_pixel_ratio, ratio)
# Edge detection: masks have fewer edges than skin (smooth surface)
gray = cv2.cvtColor(lower_half, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
edge_density = np.sum(edges > 0) / edges.size
# Decision logic:
# Mask is likely if: low skin ratio + high mask color ratio + low edge density
is_masked = False
confidence = 0.0
if skin_ratio < 0.2 and mask_pixel_ratio > 0.15:
# Strong mask color match
is_masked = True
confidence = min(1.0, mask_pixel_ratio * 2.0)
elif skin_ratio < 0.15 and edge_density < 0.05:
# Low skin and low edges -> likely mask
is_masked = True
confidence = 0.7
elif skin_ratio < 0.3 and mask_pixel_ratio > 0.1:
# Moderate evidence
is_masked = True
confidence = 0.6
elif edge_density < 0.02 and mask_pixel_ratio > 0.05:
# Very smooth surface + some mask color
is_masked = True
confidence = 0.55
else:
# Likely no mask - skin visible
confidence = max(0, min(0.5, skin_ratio))
return is_masked, float(confidence)
def remove_mask(
self,
face_crop: np.ndarray,
mask_region: Optional[np.ndarray] = None
) -> np.ndarray:
"""
Remove mask from face via inpainting.
Creates a mask of the lower half of the face and inpaints
using OpenCV's TELEA algorithm for smooth results.
Args:
face_crop: Cropped face image (BGR).
mask_region: Optional custom mask region. If None, uses lower half.
Returns:
Inpainted face crop.
"""
if face_crop is None or face_crop.size == 0:
return face_crop
h, w = face_crop.shape[:2]
if h < 20 or w < 20:
return face_crop
if mask_region is not None:
inpaint_mask = mask_region
else:
# Create mask for lower half of face (below nose area)
inpaint_mask = np.zeros((h, w), dtype=np.uint8)
# Lower 45% of face (chin + mouth area)
mask_start = int(h * 0.45)
inpaint_mask[mask_start:, :] = 255
# Also mask the sides (cheeks) for more natural blending
side_width = int(w * 0.08)
inpaint_mask[mask_start:, :side_width] = 255
inpaint_mask[mask_start:, w - side_width:] = 255
# Apply inpainting
inpainted = cv2.inpaint(
face_crop,
inpaint_mask,
inpaintRadius=3,
flags=cv2.INPAINT_TELEA
)
return inpainted
if __name__ == "__main__":
# Quick test
mp = MaskProcessor()
test_face = np.random.randint(0, 255, (200, 150, 3), dtype=np.uint8)
is_masked, conf = mp.detect_mask(test_face)
print(f"Mask detection test: is_masked={is_masked}, confidence={conf:.3f}")
inpainted = mp.remove_mask(test_face)
print(f"Inpainting test: output shape={inpainted.shape}")
print("MaskProcessor OK!")