feat: RGBA fix, per-person tracking, dry-run, retry-rejected, reset-person

- Fix RGBA→JPEG error: convert all images to RGB before saving
- Upload tracker: per-person breakdown in JSON, reset_person(), get_person_summary()
- mark_rejected() only fires on face-detection failures (not all HTTP 400s)
- New env vars: DRY_RUN, RETRY_REJECTED, RESET_PERSON
- Tracker summary printed at startup showing uploaded/rejected counts per person
- Document new env vars in compose.yml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 22:39:11 +00:00
co-authored by Claude Sonnet 4.6
parent 11ff3752d3
commit d106d30060
4 changed files with 145 additions and 50 deletions
+11 -5
View File
@@ -14,6 +14,12 @@ logger = logging.getLogger(__name__)
_yolo_model = None
def _save_jpeg(img: Image.Image, path: str) -> None:
if img.mode != "RGB":
img = img.convert("RGB")
img.save(path, format="JPEG")
def get_yolo_model():
"""Singleton for YOLO model."""
global _yolo_model
@@ -110,7 +116,7 @@ def process_face_mode(
scaled_landmarks = [[lm[0] * scale_x, lm[1] * scale_y] for lm in landmarks]
aligned = align_face(img, scaled_landmarks)
if aligned is not None:
aligned.save(os.path.join(output_dir, f"{count}.jpg"), format="JPEG")
_save_jpeg(aligned, os.path.join(output_dir, f"{count}.jpg"))
return True
# Fall back to bounding box crop with configurable margin
@@ -124,7 +130,7 @@ def process_face_mode(
)
face_crop = img.crop(crop_box)
face_crop.save(os.path.join(output_dir, f"{count}.jpg"), format="JPEG")
_save_jpeg(face_crop, os.path.join(output_dir, f"{count}.jpg"))
return True
@@ -149,9 +155,9 @@ def process_object_mode(
conf = float(box.conf[0])
if 0 <= cls_id < len(model.names) and model.names[cls_id] == target_class and conf > 0.5:
x1, y1, x2, y2 = box.xyxy[0].tolist()
img.crop((x1, y1, x2, y2)).save(
_save_jpeg(
img.crop((x1, y1, x2, y2)),
os.path.join(output_dir, f"{count}_{class_idx}.jpg"),
format="JPEG",
)
class_idx += 1
found = True
@@ -164,6 +170,6 @@ def process_object_mode(
def process_full_mode(img: Image.Image, output_dir: str, count: int) -> bool:
"""Save full image."""
img.save(os.path.join(output_dir, f"{count}.jpg"), format="JPEG")
_save_jpeg(img, os.path.join(output_dir, f"{count}.jpg"))
return True