feat(datasets): YOLO 风格标注数据集与几何/像素增强
- yolo: images/ 与同层 labels/ 的路径约定、`class_id cx cy w h` 归一化行解析 (兼容分割多边形取外接矩形)、data.yaml 读写;缺失或空 txt 即无缺陷样本 - film_defect: 抛出图像、框、图像级标签;label_mode 决定多热向量还是互斥下标 - transforms: 图框同步的 Resize / Letterbox / CenterCrop / RandomResizedCrop / 翻转 / ColorJitter / GaussianBlur / PerImageStandardize - target_encoder: 高斯半径与 heatmap / wh / offset / mask 监督信号编码 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3a36aad6c3
commit
af22dc639c
@@ -0,0 +1,111 @@
|
||||
"""把框标注编码成 CenterHead 需要的监督信号。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
def gaussian_radius(height: float, width: float, min_overlap: float = 0.7) -> float:
|
||||
"""CenterNet 的半径公式:保证半径内的框与 GT 的 IoU >= min_overlap。"""
|
||||
a1 = 1.0
|
||||
b1 = height + width
|
||||
c1 = width * height * (1 - min_overlap) / (1 + min_overlap)
|
||||
r1 = (b1 - np.sqrt(max(b1**2 - 4 * a1 * c1, 0.0))) / (2 * a1)
|
||||
|
||||
a2 = 4.0
|
||||
b2 = 2 * (height + width)
|
||||
c2 = (1 - min_overlap) * width * height
|
||||
r2 = (b2 - np.sqrt(max(b2**2 - 4 * a2 * c2, 0.0))) / (2 * a2)
|
||||
|
||||
a3 = 4 * min_overlap
|
||||
b3 = -2 * min_overlap * (height + width)
|
||||
c3 = (min_overlap - 1) * width * height
|
||||
r3 = (-b3 + np.sqrt(max(b3**2 - 4 * a3 * c3, 0.0))) / (2 * a3)
|
||||
|
||||
return max(0.0, min(r1, r2, r3))
|
||||
|
||||
|
||||
def draw_gaussian(heatmap: np.ndarray, center: tuple[int, int], radius: int) -> None:
|
||||
"""在 heatmap 上叠加二维高斯(取 max,避免相邻目标互相削弱)。"""
|
||||
diameter = 2 * radius + 1
|
||||
sigma = diameter / 6.0
|
||||
grid = np.arange(-radius, radius + 1, dtype=np.float32)
|
||||
gaussian = np.exp(-(grid[None, :] ** 2 + grid[:, None] ** 2) / (2 * sigma**2))
|
||||
|
||||
cx, cy = center
|
||||
height, width = heatmap.shape
|
||||
left, right = min(cx, radius), min(width - cx, radius + 1)
|
||||
top, bottom = min(cy, radius), min(height - cy, radius + 1)
|
||||
if right <= -left or bottom <= -top:
|
||||
return
|
||||
|
||||
masked_heatmap = heatmap[cy - top : cy + bottom, cx - left : cx + right]
|
||||
masked_gaussian = gaussian[radius - top : radius + bottom, radius - left : radius + right]
|
||||
np.maximum(masked_heatmap, masked_gaussian, out=masked_heatmap)
|
||||
|
||||
|
||||
class CenterTargetEncoder:
|
||||
"""生成 heatmap / wh / offset / mask 四个监督张量。
|
||||
|
||||
Args:
|
||||
num_classes: 类别数。
|
||||
stride: 模型输出相对输入的下采样倍数。
|
||||
max_objects: 单图最多编码的目标数。
|
||||
min_overlap: 高斯半径的 IoU 阈值。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_classes: int,
|
||||
stride: int,
|
||||
max_objects: int = 64,
|
||||
min_overlap: float = 0.7,
|
||||
) -> None:
|
||||
self.num_classes = num_classes
|
||||
self.stride = stride
|
||||
self.max_objects = max_objects
|
||||
self.min_overlap = min_overlap
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
boxes: np.ndarray,
|
||||
labels: np.ndarray,
|
||||
image_size: tuple[int, int],
|
||||
) -> dict[str, torch.Tensor]:
|
||||
img_h, img_w = image_size
|
||||
out_h, out_w = img_h // self.stride, img_w // self.stride
|
||||
|
||||
heatmap = np.zeros((self.num_classes, out_h, out_w), dtype=np.float32)
|
||||
wh = np.zeros((2, out_h, out_w), dtype=np.float32)
|
||||
offset = np.zeros((2, out_h, out_w), dtype=np.float32)
|
||||
mask = np.zeros((out_h, out_w), dtype=np.float32)
|
||||
|
||||
count = 0
|
||||
for box, label in zip(np.asarray(boxes, dtype=np.float32), np.asarray(labels), strict=True):
|
||||
if count >= self.max_objects:
|
||||
break
|
||||
x1, y1, x2, y2 = box / self.stride
|
||||
box_w, box_h = x2 - x1, y2 - y1
|
||||
if box_w <= 0 or box_h <= 0:
|
||||
continue
|
||||
|
||||
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
|
||||
cx_int, cy_int = int(cx), int(cy)
|
||||
if not (0 <= cx_int < out_w and 0 <= cy_int < out_h):
|
||||
continue
|
||||
|
||||
radius = max(1, int(gaussian_radius(box_h, box_w, self.min_overlap)))
|
||||
draw_gaussian(heatmap[int(label)], (cx_int, cy_int), radius)
|
||||
|
||||
wh[:, cy_int, cx_int] = (box_w, box_h)
|
||||
offset[:, cy_int, cx_int] = (cx - cx_int, cy - cy_int)
|
||||
mask[cy_int, cx_int] = 1.0
|
||||
count += 1
|
||||
|
||||
return {
|
||||
"heatmap": torch.from_numpy(heatmap),
|
||||
"wh": torch.from_numpy(wh),
|
||||
"offset": torch.from_numpy(offset),
|
||||
"mask": torch.from_numpy(mask),
|
||||
}
|
||||
Reference in New Issue
Block a user