Files
HVideoTool/hvideotool/core/detection/yolo.py
T

110 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Ultralytics YOLO detector.
Wraps an Ultralytics YOLO model (detection or segmentation) behind the
:class:`Detector` interface. Designed for the LADA mosaic-detection weights
(https://huggingface.co/ladaapp/lada), which are YOLO *segmentation* models with
a single ``mosaic`` class — but it works with any Ultralytics ``.pt`` whose class
names map onto :class:`CensorType`.
Heavy imports (``ultralytics``/``torch``) happen lazily in ``__init__`` so the
rest of the app never pulls them in until detection actually runs.
Licensing: Ultralytics YOLO and the LADA weights are AGPL-3.0. See README.
"""
from __future__ import annotations
import os
import numpy as np
from ...config import DetectionConfig
from ..video.frame import Frame
from .base import Detector
from .types import CensorType, Detection
def _name_to_type(name: str) -> CensorType:
n = name.lower()
if "mosaic" in n or "pixel" in n:
return CensorType.MOSAIC
if "blur" in n:
return CensorType.BLUR
if "bar" in n or "black" in n:
return CensorType.BLACK_BAR
return CensorType.UNKNOWN
class YoloDetector(Detector):
def __init__(self, model_path: str, config: DetectionConfig | None = None) -> None:
self.cfg = config or DetectionConfig()
if not os.path.isfile(model_path):
raise FileNotFoundError(
f"Файл весов не найден: {model_path}\n"
"Скачайте модель детекции мозаики LADA, например:\n"
" curl.exe -L -o models\\lada_mosaic_detection_model_v4_accurate.pt "
'"https://huggingface.co/ladaapp/lada/resolve/main/'
'lada_mosaic_detection_model_v4_accurate.pt?download=true"'
)
try:
from ultralytics import YOLO
except ImportError as exc: # pragma: no cover - environment dependent
raise ImportError(
"Не установлен ultralytics. Установите: pip install -e \".[yolo]\" "
"(и PyTorch с CUDA отдельно — см. README)."
) from exc
# Resolve the device: CUDA only when actually available, else CPU. A CPU-only
# torch build raises "Torch not compiled with CUDA enabled" if asked for cuda,
# so we never request it without a working GPU (even on an explicit override).
try:
import torch
cuda_ok = torch.cuda.is_available()
except Exception: # noqa: BLE001
cuda_ok = False
device = self.cfg.yolo_device
if device is None:
device = "cuda" if cuda_ok else "cpu"
elif "cuda" in str(device) and not cuda_ok:
device = "cpu"
self._device = device
self._model = YOLO(model_path)
@property
def name(self) -> str:
return f"YoloDetector(device={self._device})"
def detect(self, frame: Frame) -> list[Detection]:
results = self._model.predict(
source=frame.image, # BGR ndarray; ultralytics handles it
conf=self.cfg.yolo_conf,
imgsz=self.cfg.yolo_imgsz,
device=self._device,
verbose=False,
)
if not results:
return []
res = results[0]
boxes = getattr(res, "boxes", None)
if boxes is None or len(boxes) == 0:
return []
names = res.names # {class_index: class_name}
xyxy = boxes.xyxy.cpu().numpy()
confs = boxes.conf.cpu().numpy()
classes = boxes.cls.cpu().numpy().astype(int)
# Segmentation polygons in source-pixel coords, one per detection (if any).
polygons = res.masks.xy if getattr(res, "masks", None) is not None else None
out: list[Detection] = []
for i in range(len(xyxy)):
x1, y1, x2, y2 = xyxy[i]
bbox = (int(x1), int(y1), int(x2 - x1), int(y2 - y1))
poly: list[tuple[int, int]] = []
if polygons is not None and i < len(polygons):
poly = [(int(px), int(py)) for px, py in polygons[i]]
ctype = _name_to_type(names.get(int(classes[i]), ""))
out.append(Detection(type=ctype, score=float(confs[i]), bbox=bbox, polygon=poly))
return out