174 lines
4.9 KiB
Python
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)
|