Source code for cv2ext.video._timeline
# Copyright (c) 2024 Justin Davis (davisjustin302@gmail.com)
#
# MIT License
from __future__ import annotations
from typing import TYPE_CHECKING
import cv2
import numpy as np
from cv2ext.io import IterableVideo
if TYPE_CHECKING:
from pathlib import Path
[docs]
def create_timeline(
videopath: Path,
output: Path | None = None,
bboxes: list[tuple[int, int, int, int]] | None = None,
offset: int = 20,
slices: int = 6,
img_size: tuple[int, int] | None = None,
) -> np.ndarray:
"""
Create a timeline image of a video.
Parameters
----------
videopath : Path
Path to the video file.
output : Path, optional
Path to the output image.
If None, the image will not be saved to disk.
bboxes : list[tuple[int, int, int, int]], optional
List of bounding boxes for each frame.
offset : int, optional
Number of pixels to offset the bounding boxes.
slices : int, optional
Number of frames to include in the timeline.
img_size : tuple[int, int], optional
Size of the images in the timeline.
Returns
-------
np.ndarray
The timeline image.
Raises
------
ValueError
If slices and bboxes are not the same length.
"""
if bboxes is not None and len(bboxes) != slices:
err_msg = (
f"Length of slices ({slices}) and bboxes ({len(bboxes)}) must be equal."
)
raise ValueError(err_msg)
video = IterableVideo(str(videopath), use_thread=True)
frames = []
for i, frame in video:
if i % (len(video) // slices) == 0:
frames.append(frame)
if bboxes is not None:
new_frames = []
for frame, bbox in zip(frames, bboxes):
x1, y1, x2, y2 = bbox
x1 = max(0, x1 - offset)
y1 = max(0, y1 - offset)
x2 = min(frame.shape[1], x2 + offset)
y2 = min(frame.shape[0], y2 + offset)
cropped = frame[y1:y2, x1:x2]
if cropped.shape[0] == 0 or cropped.shape[1] == 0:
new_frames.append(frame)
else:
new_frames.append(cropped)
frames = new_frames
if img_size is not None:
frames = [
cv2.resize(frame, img_size, interpolation=cv2.INTER_CUBIC)
for frame in frames
]
samesize = True
for idx, frame in enumerate(frames):
if idx == len(frames) - 1:
break
if frame.shape != frames[idx + 1].shape:
samesize = False
break
if not samesize:
framesize = frames[0].shape[0::2]
frames = [
cv2.resize(frame, framesize, interpolation=cv2.INTER_CUBIC)
for frame in frames
]
timeline: np.ndarray = np.concatenate(frames, axis=1)
if output is not None:
cv2.imwrite(str(output), timeline)
return timeline