import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont def extract_frame(video_path: str, timestamp: str, output_image_path: str) -> None: """ Extracts a frame from a video at the specified timestamp and saves it as an image. Parameters: - video_path (str): Path to the input video file. - timestamp (str): Timestamp in the format "HH:MM:SS.sss". - output_image_path (str): Path to save the extracted frame image. """ # Convert timestamp to milliseconds h, m, s = map(float, timestamp.split(':')) target_msec = int((h * 3600 + m * 60 + s) * 1000) # Open the video cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise IOError(f"Cannot open video file: {video_path}") # Set position in milliseconds cap.set(cv2.CAP_PROP_POS_MSEC, target_msec) # Read the frame ret, frame = cap.read() if not ret: raise RuntimeError(f"Failed to retrieve frame at {timestamp} ({target_msec} ms)") # Save frame as PNG cv2.imwrite(output_image_path, frame) print(f"Frame saved as {output_image_path}") # Clean up cap.release() def center_bbox_in_circle( input_image_path: str, bbox: tuple, output_path: str, background_path = "src/assets/BACKGROUND.png", mask_path = "src/assets/circle_mask.png", circle_center=(296, 126), circle_radius=77, blend_alpha: float = 0.3 ) -> None: """ Centers a bounding box from an input image into a circular area on a background image. Parameters: - input_image_path (str): Path to the input image. - background_path (str): Path to the background image with a circle. - mask_path (str): Path to the circular mask image (grayscale). - bbox (tuple): Bounding box in normalized format (y_min, x_min, y_max, x_max), range 0–1000. - circle_center (tuple): (x, y) center of the target circle on the background. - circle_radius (int): Radius of the target circle. - output_path (str): Path to save the resulting image. - blend_alpha (float): Blending factor for the background (default: 0.3). """ background = cv2.imread(background_path) input_image = cv2.imread(input_image_path) img_height, img_width = input_image.shape[:2] y_min = int(bbox[0] / 1000.0 * img_height) x_min = int(bbox[1] / 1000.0 * img_width) y_max = int(bbox[2] / 1000.0 * img_height) x_max = int(bbox[3] / 1000.0 * img_width) bbox_w = x_max - x_min bbox_h = y_max - y_min bbox_center_x = x_min + bbox_w // 2 bbox_center_y = y_min + bbox_h // 2 circle_diameter = 2 * circle_radius scale = min(circle_diameter / bbox_w, circle_diameter / bbox_h) new_width = int(img_width * scale) new_height = int(img_height * scale) resized_image = cv2.resize(input_image, (new_width, new_height)) new_bbox_center_x = int(bbox_center_x * scale) new_bbox_center_y = int(bbox_center_y * scale) offset_x = circle_center[0] - new_bbox_center_x offset_y = circle_center[1] - new_bbox_center_y canvas = np.zeros_like(background) start_x = max(offset_x, 0) start_y = max(offset_y, 0) end_x = min(start_x + new_width, background.shape[1]) end_y = min(start_y + new_height, background.shape[0]) src_start_x = max(-offset_x, 0) src_start_y = max(-offset_y, 0) target_h = min(end_y - start_y, resized_image.shape[0] - src_start_y) target_w = min(end_x - start_x, resized_image.shape[1] - src_start_x) canvas[start_y:start_y+target_h, start_x:start_x+target_w] = resized_image[ src_start_y:src_start_y + target_h, src_start_x:src_start_x + target_w ] mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) mask = cv2.resize(mask, (background.shape[1], background.shape[0])) blended = cv2.addWeighted(canvas, blend_alpha, background, 1.0 - blend_alpha, 0) mask_3c = cv2.merge([mask, mask, mask]) inv_mask = cv2.bitwise_not(mask_3c) outside = cv2.bitwise_and(blended, mask_3c) inside = cv2.bitwise_and(canvas, inv_mask) combined = cv2.add(outside, inside) cv2.imwrite(output_path, combined) print(f"Saved result to {output_path}") def overlay_image_bottom_right( background_path: str, overlay_path: str, output_path: str ) -> None: """ Overlays an image (optionally with transparency) onto the bottom-right corner of another image. Parameters: - background_path (str): Path to the background image. - overlay_path (str): Path to the overlay image (can have alpha channel). - output_path (str): Path to save the combined image. """ # Load images (support alpha if available) background = cv2.imread(background_path, cv2.IMREAD_UNCHANGED) overlay = cv2.imread(overlay_path, cv2.IMREAD_UNCHANGED) h_bg, w_bg = background.shape[:2] h_ov, w_ov = overlay.shape[:2] x_offset = w_bg - w_ov y_offset = h_bg - h_ov if overlay.shape[2] == 4: # Split channels overlay_rgb = overlay[:, :, :3] alpha = overlay[:, :, 3] / 255.0 # Handle 3-channel background (add alpha if missing) if background.shape[2] == 3: background = cv2.cvtColor(background, cv2.COLOR_BGR2BGRA) for c in range(3): # Only blend RGB channels background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c] = ( alpha * overlay_rgb[:, :, c] + (1 - alpha) * background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c] ) else: # No alpha channel: paste directly background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov] = overlay # Ensure saving as 3-channel PNG if background.shape[2] == 4: background = cv2.cvtColor(background, cv2.COLOR_BGRA2BGR) cv2.imwrite(output_path, background) print(f"Combined image saved to {output_path}") def overlay_image_top_left( background_path: str, overlay_path: str, output_path: str, padding_x: int = 20, padding_y: int = 20 ) -> None: """ Overlays an image (optionally with transparency) onto the top-left corner of another image with padding. Parameters: - background_path (str): Path to the background image. - overlay_path (str): Path to the overlay image (can have alpha channel). - output_path (str): Path to save the resulting image. - padding_x (int): Horizontal padding from the left. - padding_y (int): Vertical padding from the top. """ # Load images (support alpha if available) background = cv2.imread(background_path, cv2.IMREAD_UNCHANGED) overlay = cv2.imread(overlay_path, cv2.IMREAD_UNCHANGED) h_bg, w_bg = background.shape[:2] h_ov, w_ov = overlay.shape[:2] x_offset = padding_x y_offset = padding_y # Ensure overlay fits in background with padding if x_offset + w_ov > w_bg or y_offset + h_ov > h_bg: raise ValueError("Overlay image does not fit within background with given padding.") if overlay.shape[2] == 4: overlay_rgb = overlay[:, :, :3] alpha = overlay[:, :, 3] / 255.0 # Add alpha channel to background if needed if background.shape[2] == 3: background = cv2.cvtColor(background, cv2.COLOR_BGR2BGRA) for c in range(3): # Blend RGB channels background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c] = ( alpha * overlay_rgb[:, :, c] + (1 - alpha) * background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c] ) else: # No alpha: paste directly background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov] = overlay # Convert back to BGR before saving (if needed) if background.shape[2] == 4: background = cv2.cvtColor(background, cv2.COLOR_BGRA2BGR) cv2.imwrite(output_path, background) print(f"Image with overlay saved to {output_path}") def annotate_image_with_phrase_and_label(image_path, output_path, phrase, label): font_path = "src/assets/Herokid-BoldUltraCondensed.otf" # Path to your custom font file img_cv2 = cv2.imread(image_path) if img_cv2 is None: raise ValueError("Could not load the image.") # Convert BGR (OpenCV) to RGB (Pillow) img_rgb = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB) img_pil = Image.fromarray(img_rgb) draw = ImageDraw.Draw(img_pil) # Load custom font font_size = 50 font = ImageFont.truetype(font_path, font_size) # Image dimensions width, height = img_pil.size margin = 10 # Phrase (bottom-left in white) phrase_bbox = draw.textbbox((0, 0), phrase, font=font) phrase_width = phrase_bbox[2] - phrase_bbox[0] phrase_height = phrase_bbox[3] - phrase_bbox[1] phrase_pos = (margin, height - phrase_height - margin) draw.text(phrase_pos, phrase, font=font, fill="white") # Label (bottom-right in black) label_bbox = draw.textbbox((0, 0), label, font=font) label_width = label_bbox[2] - label_bbox[0] label_height = label_bbox[3] - label_bbox[1] label_pos = (width - label_width - margin, height - label_height - margin) draw.text(label_pos, label, font=font, fill="black") # Convert back to OpenCV and save img_output = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) cv2.imwrite(output_path, img_output)