diff --git a/main.py b/main.py index 417f35f..5ea5477 100644 --- a/main.py +++ b/main.py @@ -29,7 +29,7 @@ class AppConfig: output_folder: str = "output" landmark_model: str = "shape_predictor_68_face_landmarks.dat" resize_size: int = 512 - face_resolution_threshold: int = 128 + face_resolution_threshold: int = 80 pose_threshold: float = 25.0 left_eye_pos: Tuple[float, float] = (0.35, 0.4) date_from: str = None @@ -71,6 +71,7 @@ def check_output_folder() -> Tuple[bool, int]: return len(files) == 0, len(files) def background_process( + max_workers: int = 1, progress_callback: Callable = None, cancel_flag: Callable = None ) -> List[str]: @@ -86,7 +87,7 @@ def background_process( try: return process_faces( config=config, - max_workers=1, + max_workers=max_workers, progress_callback=progress_callback, cancel_flag=cancel_flag ) @@ -110,13 +111,10 @@ def check_connection() -> Dict[str, any]: def cancel() -> Dict[str, any]: """Cancel the current processing job.""" global processing_thread, cancel_requested - cancel_requested = True - if processing_thread and processing_thread.is_alive(): progress_info["status"] = "cancelled" return jsonify({"success": True, "message": "Processing cancelled."}) - cancel_requested = False return jsonify({"success": False, "message": "No active processing to cancel."}) @@ -152,8 +150,7 @@ def index() -> str: config.pose_threshold = float(request.form.get("pose_threshold")) config.date_from = request.form.get("date_from") config.date_to = request.form.get("date_to") - - # Video compilation options + max_workers = int(request.form.get("max_workers")) compile_video = request.form.get("compile_video") == "on" framerate = int(request.form.get("framerate", 15)) @@ -168,7 +165,7 @@ def index() -> str: # Start processing processing_thread = threading.Thread( target=background_process, - args=(update_progress, lambda: cancel_requested) + args=(max_workers, update_progress, lambda: cancel_requested) ) processing_thread.start() result = "Processing started. Please wait and watch the progress bar below." diff --git a/timelapse.py b/timelapse.py index 09a3872..676ffa6 100644 --- a/timelapse.py +++ b/timelapse.py @@ -247,6 +247,18 @@ def get_head_pose(landmarks, image): proj_matrix = np.hstack((rotation_matrix, translation_vector)) _, _, _, _, _, _, euler_angles = cv2.decomposeProjectionMatrix(proj_matrix) pitch, yaw, roll = [float(angle) for angle in euler_angles] + + # Normalize angles to be between -180 and 180 degrees + pitch = (pitch + 180) % 360 - 180 + yaw = (yaw + 180) % 360 - 180 + roll = (roll + 180) % 360 - 180 + + # Adjust pitch to be between -90 and 90 degrees + if pitch > 90: + pitch = 180 - pitch + elif pitch < -90: + pitch = -180 - pitch + return pitch, yaw, roll @@ -493,7 +505,7 @@ def crop_and_align_face(image, face_data, resize_size, face_resolution_threshold # Check if both eyes are visible if not check_eye_visibility(landmarks['left_eye'], landmarks['right_eye']): logger.info("Eyes are too closed") - draw_landmarks(img_np, landmarks['all_landmarks'], (x1, y1, x2, y2), "discarded/eyes_closed.jpg") + # draw_landmarks(img_np, landmarks['all_landmarks'], (x1, y1, x2, y2), f"discarded/eyes_closed_{face_data.get('id')}.jpg") return None # Get head pose @@ -505,10 +517,10 @@ def crop_and_align_face(image, face_data, resize_size, face_resolution_threshold # Check if head pose is within acceptable range pitch, yaw, roll = pose - if abs(pitch) > pose_threshold or abs(yaw) > pose_threshold or abs(roll) > pose_threshold: + if abs(yaw) > pose_threshold: logger.info(f"Head pose exceeds threshold: pitch={pitch:.1f}°, yaw={yaw:.1f}°, roll={roll:.1f}°") - draw_landmarks(img_np, landmarks['all_landmarks'], (x1, y1, x2, y2), f"discarded/pose_pitch_{pitch:.1f}_yaw_{yaw:.1f}_roll_{roll:.1f}.jpg") - # return None + draw_landmarks(img_np, landmarks['all_landmarks'], (x1, y1, x2, y2), f"discarded/pose_yaw_{yaw:.1f}.jpg") + return None # Get eye positions left_eye_center = np.mean(landmarks['left_eye'], axis=0)