supervision/examples/time_in_zone/scripts/draw_zones.py

174 lines
4.9 KiB
Python

import json
import os
import cv2
import numpy as np
from jsonargparse import auto_cli
import supervision as sv
KEY_ENTER = {"Return", "KP_Enter"}
KEY_ESCAPE = "Escape"
KEY_QUIT = "q"
KEY_SAVE = "s"
THICKNESS = 2
COLORS = sv.ColorPalette.DEFAULT
WINDOW_NAME = "Draw Zones"
POLYGONS = [[]]
current_mouse_position: tuple[int, int] | None = None
def resolve_source(source_path: str) -> np.ndarray | None:
if not os.path.exists(source_path):
return None
image = cv2.imread(source_path)
if image is not None:
return image
frame_generator = sv.get_video_frames_generator(source_path=source_path)
frame = next(frame_generator)
return frame
def mouse_event(x: int, y: int, event_type: str) -> None:
global current_mouse_position
if event_type == "move":
current_mouse_position = (x, y)
elif event_type == "down":
POLYGONS[-1].append((x, y))
def redraw(
image: np.ndarray, original_image: np.ndarray, window: sv.ImageWindow
) -> None:
global POLYGONS, current_mouse_position
image[:] = original_image.copy()
for idx, polygon in enumerate(POLYGONS):
color = (
COLORS.by_idx(idx).as_bgr()
if idx < len(POLYGONS) - 1
else sv.Color.WHITE.as_bgr()
)
if len(polygon) > 1:
for i in range(1, len(polygon)):
cv2.line(
img=image,
pt1=polygon[i - 1],
pt2=polygon[i],
color=color,
thickness=THICKNESS,
)
if idx < len(POLYGONS) - 1:
cv2.line(
img=image,
pt1=polygon[-1],
pt2=polygon[0],
color=color,
thickness=THICKNESS,
)
if idx == len(POLYGONS) - 1 and current_mouse_position is not None and polygon:
cv2.line(
img=image,
pt1=polygon[-1],
pt2=current_mouse_position,
color=color,
thickness=THICKNESS,
)
window.show(image)
def close_and_finalize_polygon(
image: np.ndarray, original_image: np.ndarray, window: sv.ImageWindow
) -> None:
if len(POLYGONS[-1]) > 2:
cv2.line(
img=image,
pt1=POLYGONS[-1][-1],
pt2=POLYGONS[-1][0],
color=COLORS.by_idx(0).as_bgr(),
thickness=THICKNESS,
)
POLYGONS.append([])
image[:] = original_image.copy()
redraw_polygons(image)
window.show(image)
def redraw_polygons(image: np.ndarray) -> None:
for idx, polygon in enumerate(POLYGONS[:-1]):
if len(polygon) > 1:
color = COLORS.by_idx(idx).as_bgr()
for i in range(len(polygon) - 1):
cv2.line(
img=image,
pt1=polygon[i],
pt2=polygon[i + 1],
color=color,
thickness=THICKNESS,
)
cv2.line(
img=image,
pt1=polygon[-1],
pt2=polygon[0],
color=color,
thickness=THICKNESS,
)
def save_polygons_to_json(
polygons: list[list[tuple[int, int]]], target_path: str | os.PathLike[str]
) -> None:
data_to_save = polygons if polygons[-1] else polygons[:-1]
with open(target_path, "w") as f:
json.dump(data_to_save, f)
def main(source_path: str, zone_configuration_path: str) -> None:
"""
Interactively draw polygons on images or video frames and save the annotations.
Args:
source_path: Path to the source image or video file for drawing polygons.
zone_configuration_path: Path where the polygon annotations saved as JSON file.
"""
global current_mouse_position
original_image = resolve_source(source_path=source_path)
if original_image is None:
print("Failed to load source image.")
return
image = original_image.copy()
window = sv.ImageWindow(WINDOW_NAME)
window.set_mouse_callback(mouse_event)
window.show(image)
while True:
key = window.wait_key(1)
if not window.is_open:
break
if key in KEY_ENTER:
close_and_finalize_polygon(image, original_image, window)
elif key == KEY_ESCAPE:
POLYGONS[-1] = []
current_mouse_position = None
elif key == KEY_SAVE:
save_polygons_to_json(POLYGONS, zone_configuration_path)
print(f"Polygons saved to {zone_configuration_path}")
break
redraw(image, original_image, window)
if key == KEY_QUIT:
break
window.close()
if __name__ == "__main__":
from jsonargparse import auto_cli, set_parsing_settings
set_parsing_settings(parse_optionals_as_positionals=True)
auto_cli(main, as_positional=False)