refactor: update bbox format from (x,y,w,h) to (x1,y1,x2,y2)
This commit is contained in:
parent
1f74e621f8
commit
e2d19cfbf0
1 changed files with 109 additions and 0 deletions
109
src/faceblur/output.py
Normal file
109
src/faceblur/output.py
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
"""Output generation module."""
|
||||||
|
|
||||||
|
import colorsys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from .video import Frame
|
||||||
|
from .detect import FaceData
|
||||||
|
from .cluster import Cluster
|
||||||
|
|
||||||
|
|
||||||
|
def generate_output(
|
||||||
|
frames: List[Frame], faces: List[FaceData], clusters: List[Cluster], output_dir: str
|
||||||
|
) -> None:
|
||||||
|
"""Generate output with bounding boxes and face crops.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frames: List of extracted frames
|
||||||
|
faces: All detected faces
|
||||||
|
clusters: Clustered faces
|
||||||
|
output_dir: Output directory
|
||||||
|
"""
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
|
||||||
|
face_to_cluster: Dict[int, int] = {}
|
||||||
|
for cluster in clusters:
|
||||||
|
for face in cluster.faces:
|
||||||
|
face_to_cluster[face.id] = cluster.id
|
||||||
|
|
||||||
|
faces_by_frame: Dict[int, List[FaceData]] = {}
|
||||||
|
for face in faces:
|
||||||
|
if face.frame_index not in faces_by_frame:
|
||||||
|
faces_by_frame[face.frame_index] = []
|
||||||
|
faces_by_frame[face.frame_index].append(face)
|
||||||
|
|
||||||
|
frames_dir = output_dir / "frames"
|
||||||
|
frames_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for frame in frames:
|
||||||
|
frame_faces = faces_by_frame.get(frame.index, [])
|
||||||
|
_draw_frame_with_boxes(frame.path, frame_faces, face_to_cluster, frames_dir)
|
||||||
|
|
||||||
|
for i, cluster in enumerate(clusters):
|
||||||
|
if cluster.id == -1:
|
||||||
|
cluster_dir = output_dir / "unclustered"
|
||||||
|
else:
|
||||||
|
cluster_dir = output_dir / f"cluster_{i:02d}"
|
||||||
|
cluster_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for face in cluster.faces:
|
||||||
|
_extract_face_crop(face, cluster_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_cluster_color(cluster_id: int) -> Tuple[int, int, int]:
|
||||||
|
"""Generate a consistent color for a cluster."""
|
||||||
|
if cluster_id < 0:
|
||||||
|
return (128, 128, 128)
|
||||||
|
|
||||||
|
hue = (cluster_id * 0.618033988749895) % 1.0
|
||||||
|
rgb = colorsys.hsv_to_rgb(hue, 0.8, 0.9)
|
||||||
|
return (int(rgb[2] * 255), int(rgb[1] * 255), int(rgb[0] * 255))
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_frame_with_boxes(
|
||||||
|
frame_path: Path,
|
||||||
|
faces: List[FaceData],
|
||||||
|
face_to_cluster: Dict[int, int],
|
||||||
|
output_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Draw bounding boxes on a frame."""
|
||||||
|
image = cv2.imread(str(frame_path))
|
||||||
|
if image is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
x1, y1, x2, y2 = face.bbox
|
||||||
|
cluster_id = face_to_cluster.get(face.id, -1)
|
||||||
|
color = _get_cluster_color(cluster_id)
|
||||||
|
|
||||||
|
cv2.rectangle(image, (x1, y1), (x2, y2), color, 2)
|
||||||
|
|
||||||
|
if cluster_id >= 0:
|
||||||
|
label = f"C{cluster_id}"
|
||||||
|
cv2.putText(
|
||||||
|
image, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
output_path = output_dir / f"frame_{frame_path.stem.split('_')[1]}.jpg"
|
||||||
|
cv2.imwrite(str(output_path), image)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_face_crop(face: FaceData, output_dir: Path) -> None:
|
||||||
|
"""Extract and save a face crop."""
|
||||||
|
image = cv2.imread(str(face.frame_path))
|
||||||
|
if image is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
x1, y1, x2, y2 = face.bbox
|
||||||
|
face_img = image[y1:y2, x1:x2]
|
||||||
|
|
||||||
|
if face_img.size == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = f"face_{face.frame_index:04d}_{face.id % 100:02d}.jpg"
|
||||||
|
output_path = output_dir / filename
|
||||||
|
cv2.imwrite(str(output_path), face_img)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue