93 lines
2.5 KiB
Python
93 lines
2.5 KiB
Python
|
|
"""手部框、撕动作几何与概率(从离线 tear 脚本抽离,不依赖 OpenCV 绘制)。"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from itertools import combinations
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
from ultralytics import YOLO
|
||
|
|
|
||
|
|
|
||
|
|
def union_boxes(boxes: list[list[float]]) -> list[float]:
|
||
|
|
x1 = min(b[0] for b in boxes)
|
||
|
|
y1 = min(b[1] for b in boxes)
|
||
|
|
x2 = max(b[2] for b in boxes)
|
||
|
|
y2 = max(b[3] for b in boxes)
|
||
|
|
return [x1, y1, x2, y2]
|
||
|
|
|
||
|
|
|
||
|
|
def pad_box(
|
||
|
|
xyxy: list[float],
|
||
|
|
img_w: int,
|
||
|
|
img_h: int,
|
||
|
|
pad_ratio: float = 0.30,
|
||
|
|
) -> tuple[int, int, int, int]:
|
||
|
|
x1, y1, x2, y2 = xyxy
|
||
|
|
bw, bh = x2 - x1, y2 - y1
|
||
|
|
px, py = bw * pad_ratio, bh * pad_ratio
|
||
|
|
return (
|
||
|
|
max(0, int(x1 - px)),
|
||
|
|
max(0, int(y1 - py)),
|
||
|
|
min(img_w, int(x2 + px)),
|
||
|
|
min(img_h, int(y2 + py)),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def collect_hand_boxes(det_model: YOLO, boxes) -> list[list[float]]:
|
||
|
|
names = det_model.names
|
||
|
|
out: list[list[float]] = []
|
||
|
|
for box in boxes:
|
||
|
|
cid = int(box.cls[0])
|
||
|
|
label = names.get(cid, "")
|
||
|
|
if label == "hand":
|
||
|
|
out.append(box.xyxy[0].tolist())
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def box_edge_distance(a: list[float], b: list[float]) -> float:
|
||
|
|
dx = max(0, max(a[0], b[0]) - min(a[2], b[2]))
|
||
|
|
dy = max(0, max(a[1], b[1]) - min(a[3], b[3]))
|
||
|
|
return float((dx**2 + dy**2) ** 0.5)
|
||
|
|
|
||
|
|
|
||
|
|
def box_avg_width(boxes: list[list[float]]) -> float:
|
||
|
|
if not boxes:
|
||
|
|
return 0.0
|
||
|
|
return sum(b[2] - b[0] for b in boxes) / len(boxes)
|
||
|
|
|
||
|
|
|
||
|
|
def find_tearing_pair(
|
||
|
|
hand_boxes: list[list[float]],
|
||
|
|
gap_ratio: float = 1.5,
|
||
|
|
) -> tuple[list[float], list[float]] | None:
|
||
|
|
if len(hand_boxes) < 2:
|
||
|
|
return None
|
||
|
|
avg_w = box_avg_width(hand_boxes)
|
||
|
|
threshold = avg_w * gap_ratio
|
||
|
|
best_dist = float("inf")
|
||
|
|
best_pair: tuple[list[float], list[float]] | None = None
|
||
|
|
for a, b in combinations(hand_boxes, 2):
|
||
|
|
d = box_edge_distance(a, b)
|
||
|
|
if d < best_dist:
|
||
|
|
best_dist = d
|
||
|
|
best_pair = (a, b)
|
||
|
|
if best_pair is not None and best_dist <= threshold:
|
||
|
|
return best_pair
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def prob_tearing(tprobs, tear_names: dict[Any, str]) -> float:
|
||
|
|
if tprobs is None:
|
||
|
|
return 0.0
|
||
|
|
data = tprobs.data
|
||
|
|
if data is None:
|
||
|
|
return 0.0
|
||
|
|
d = data.detach().float().cpu().numpy().ravel()
|
||
|
|
for i, name in tear_names.items():
|
||
|
|
if name == "tearing":
|
||
|
|
ii = int(i)
|
||
|
|
if 0 <= ii < len(d):
|
||
|
|
return float(d[ii])
|
||
|
|
return 0.0
|