RacingDemo / src /utils.py
Vlad Bastina
merge
11a8362
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)