import cv2 import math import numpy as np import matplotlib.pyplot as plt from utils.detections_utils import run_detection import os def get_box_center(box): """Calculate the center point of a bounding box""" x = (box[0] + box[2]) / 2 y = (box[1] + box[3]) / 2 return (x, y) def calculate_angle(center, point, reference_point): """Calculate angle between two points relative to 12 o'clock position""" # Calculate vectors ref_vector = (reference_point[0] - center[0], reference_point[1] - center[1]) point_vector = (point[0] - center[0], point[1] - center[1]) # Calculate angles from vectors ref_angle = math.atan2(ref_vector[1], ref_vector[0]) point_angle = math.atan2(point_vector[1], point_vector[0]) # Calculate relative angle in degrees angle = math.degrees(point_angle - ref_angle) # Normalize angle to 0-360 range angle = (angle + 360) % 360 return angle def process_clock_time(detections_data, image_path): """Process clock time from detections""" # Organize detections by class_name and select the one with highest confidence for each class detections_by_class = {} for detection in detections_data[0]: class_name = detection['class_name'] if class_name not in detections_by_class or detection['confidence'] > detections_by_class[class_name]['confidence']: detections_by_class[class_name] = detection # Validate required keys required_keys = ['hours', 'minutes', '12', 'circle'] for key in required_keys: if key not in detections_by_class: print(f"Error: Missing required key '{key}' in detection data.") return None # Calculate circle center circle_box_point = get_box_center(detections_by_class['circle']['box']) # Determine center point: use 'center' if exists, otherwise use circle center if 'center' in detections_by_class: center_point = get_box_center(detections_by_class['center']['box']) else: center_point = circle_box_point hours_point = get_box_center(detections_by_class['hours']['box']) number_12_point = get_box_center(detections_by_class['12']['box']) # Try to get seconds point with highest confidence seconds_point = None seconds_angle = None calculated_seconds = 0 if 'minutes' in detections_by_class: minutes_point = get_box_center(detections_by_class['minutes']['box']) if 'seconds' in detections_by_class: seconds_point = get_box_center(detections_by_class['seconds']['box']) # Calculate raw angles relative to 12 o'clock position hour_angle = calculate_angle(center_point, hours_point, number_12_point) # Calculate minute angle if minutes_point: minute_angle = calculate_angle(center_point, minutes_point, number_12_point) # Calculate seconds angle if seconds point exists if seconds_point: seconds_angle = calculate_angle(center_point, seconds_point, number_12_point) # Convert angles to time hours = (hour_angle / 30) # Each hour is 30 degrees # Round to nearest hour and minute hours = math.floor(hours) % 12 if hours == 0: hours = 12 if minute_angle is not None: minutes = (minute_angle / 6) minutes = round(minutes) % 60 calculated_minutes = minutes # Calculate seconds if angle exists if seconds_angle is not None: seconds = (seconds_angle / 6) # Each second is 6 degrees seconds = round(seconds) % 60 calculated_seconds = seconds return { 'hours': hours, 'minutes': calculated_minutes if minute_angle is not None else None, 'seconds': calculated_seconds if seconds_angle is not None else None } def draw_clock(image_path, center_point, hours_point, minutes_point, seconds_point, number_12_point, hour_angle, minute_angle, seconds_angle, calculated_hours, calculated_minutes, calculated_seconds, image_name): """Draw clock and reference points on the image""" img = cv2.imread(image_path) # To int center = (int(center_point[0]), int(center_point[1])) hours = (int(hours_point[0]), int(hours_point[1])) minutes = (int(minutes_point[0]), int(minutes_point[1])) if minutes_point else None seconds = (int(seconds_point[0]), int(seconds_point[1])) if seconds_point else None twelve = (int(number_12_point[0]), int(number_12_point[1])) # Draw the reference points cv2.circle(img, center, 3, (0, 0, 255), -1) # Centro em vermelho cv2.circle(img, twelve, 3, (255, 0, 0), -1) # Ponto 12 em azul # Draw the lines with thicker strokes cv2.line(img, center, hours, (0, 0, 255), 5) # Ponteiro das horas em vermelho if minutes: cv2.line(img, center, minutes, (255, 0, 0), 4) # Ponteiro dos minutos em azul if seconds: cv2.line(img, center, seconds, (255, 165, 0), 2) # Ponteiro dos segundos em laranja cv2.line(img, center, twelve, (0, 255, 0), 1) # Linha de referĂȘncia (12h) em verde # Draw the text cv2.putText(img, f"Hour angle: {hour_angle:.1f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) if minute_angle is not None: cv2.putText(img, f"Minute angle: {minute_angle:.1f}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) time_text = f"Time: {int(calculated_hours):02d}:{int(calculated_minutes):02d}" else: time_text = f"Time: {int(calculated_hours):02d}" if seconds_angle is not None: cv2.putText(img, f"Seconds angle: {seconds_angle:.1f}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) time_text = f"Time: {int(calculated_hours):02d}:{int(calculated_minutes):02d}:{int(calculated_seconds):02d}" else: time_text = f"Time: {int(calculated_hours):02d}:{int(calculated_minutes):02d}" cv2.putText(img, time_text, (10, 120 if seconds else 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) output_path = f'results/images/{image_name}' cv2.imwrite(output_path, img) print(f"Annotated image saved to {output_path}") def zoom_into_clock_circle(image_path, confidence=0.01): """ Attempt to find the clock circle and zoom into it for more precise detection. Args: image_path (str): Path to the input image confidence (float): Confidence threshold for detection Returns: str: Path to the zoomed-in image, or None if no suitable circle found """ # Read the image image = cv2.imread(image_path) # Run detection to find clock circle detections = run_detection(image_path, confidence=confidence) # Find the circle detection with highest confidence circle_detection = None for detection in detections[0]: if detection['class_name'] == 'circle' and detection['confidence'] >= confidence: if not circle_detection or detection['confidence'] > circle_detection['confidence']: circle_detection = detection if not circle_detection: return None # Extract bounding box x1, y1, x2, y2 = circle_detection['box'] # Add some padding (20% on each side) height, width = image.shape[:2] pad_x = int((x2 - x1) * 0.2) pad_y = int((y2 - y1) * 0.2) # Calculate padded coordinates with boundary checks x1_pad = max(0, x1 - pad_x) y1_pad = max(0, y1 - pad_y) x2_pad = min(width, x2 + pad_x) y2_pad = min(height, y2 + pad_y) # Crop the image zoomed_image = image[int(y1_pad):int(y2_pad), int(x1_pad):int(x2_pad)] # Save the zoomed image zoomed_image_path = f'results/zoomed_images/{os.path.splitext(os.path.basename(image_path))[0]}_zoomed.jpg' os.makedirs('results/zoomed_images', exist_ok=True) cv2.imwrite(zoomed_image_path, zoomed_image) return zoomed_image_path def process_clock_with_fallback(image_path, confidence=0.01): """ Attempt to process clock time with fallback to zoomed detection. Args: image_path (str): Path to the input image confidence (float): Confidence threshold for detection Returns: dict or None: Processed clock time result """ # First attempt with original image #original_result = process_clock_time(run_detection(image_path, confidence=confidence), image_path) # If original detection succeeds, return the result #if original_result: # return original_result # Try zooming into clock circle zoomed_image_path = zoom_into_clock_circle(image_path, confidence) # If no zoom possible, return None if not zoomed_image_path: return None detections = run_detection(zoomed_image_path, confidence=confidence, zoom = True) # Attempt detection on zoomed image zoomed_result = process_clock_time(detections, zoomed_image_path) return detections, zoomed_result