多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

【Bug已解决】Request to add DINO object detector 解决方案

【Bug已解决】Request to add DINO object detector 解决方案 【Bug已解决】Request to add DINO object detector 解决方案一、现象长什么样把 DINO基于 DETR 系列的目标检测器接进 HF Transformers 后模型能加载、forward也能跑但用object-detectionpipeline 或自己解析输出时出问题# 现象 Apipeline 不认模型 ValueError: The task object-detection is not supported for model_type dino. # DINO 没注册到 ObjectDetectionPipeline 的型号映射 # 现象 B输出是原始 logits 归一化 box不是可用检测结果 # model(inputs) 返回 {logits: (1, 300, 801), pred_boxes: (1, 300, 4)} # 但没做 NMS / 阈值过滤300 个预测框里大量是背景label背景类 # 现象 Cbox 坐标范围错未归一化或格式不对 # 直接把 pred_boxes 当像素坐标用结果框飞到图外 # 因为 DETR 系 pred_boxes 是 (cx, cy, w, h) 且相对图像尺寸归一化到 [0,1] # 典型触发 from transformers import pipeline pipe pipeline(object-detection, modelIDEA-Research/dino) # 报现象 A即便手动绕开也要自己写 NMS否则 300 框没法用最典型的指纹forward正常但拿不到干净的检测框——要么 pipeline 不支持要么输出是未后处理的 300 个原始预测缺 NMS/阈值/格式转换。二、背景DINO 是 DETR 系检测器输入图像 → backbone transformer encoder-decoder → 输出固定数量如 300个目标查询的预测每个预测含logits(batch, num_queries, num_classes1)最后一维含背景类pred_boxes(batch, num_queries, 4)格式是(cx, cy, w, h)且归一化到 [0,1]相对原图尺寸。要变成可用检测结果必须做后处理取每个 query 的 argmax 类别过滤掉背景类按score最大类概率阈值过滤如 0.5对同类做NMS非极大抑制去掉重叠框把(cx,cy,w,h)归一化坐标转成(xmin, ymin, xmax, ymax)像素坐标。这套后处理若没随模型一起实现并注册到ObjectDetectionPipeline用户就只能拿到原始 300 框没法用。问题常出在模型类没实现post_process_object_detection或没注册到 pipeline 映射。三、根因根因有三类未注册到 ObjectDetectionPipeline 映射。dino的model_type没加进ObjectDetectionPipeline的MODEL_FOR_OBJECT_DETECTION_MAPPINGpipeline(object-detection)查不到 → 现象 A。缺post_process_object_detection后处理。 模型类没实现把logitspred_boxes转成过滤NMS像素框的方法。用户拿到 300 个原始预测含大量背景框 → 不可用。box 格式/坐标转换错误。 直接把pred_boxes(cx,cy,w,h)归一化当像素(xmin,ymin,xmax,ymax)用坐标范围与含义都错 → 框错位/飞出图外。四、最小可运行复现下面用纯 Python 模拟原始 300 预测 → NMS 阈值过滤 → 干净检测的后处理逻辑from typing import List, Tuple def iou(a: Tuple[float,float,float,float], b: Tuple[float,float,float,float]) - float: # 输入都是 (xmin,ymin,xmax,ymax) 像素坐标 xa max(a[0], b[0]); ya max(a[1], b[1]) xb min(a[2], b[2]); yb min(a[3], b[3]) inter max(0, xb-xa) * max(0, yb-ya) area_a (a[2]-a[0])*(a[3]-a[1]); area_b (b[2]-b[0])*(b[3]-b[1]) union area_a area_b - inter return inter/union if union 0 else 0 def post_process(preds: List[Tuple[int,float,Tuple[float,float,float,float]]], score_thr0.5, iou_thr0.5) - List: preds: (label, score, box)。做阈值过滤 NMS。 keep [p for p in preds if p[1] score_thr] # 按 score 降序贪心 NMS keep.sort(keylambda x: -x[1]) out [] while keep: best keep.pop(0) out.append(best) keep [p for p in keep if p[0] ! best[0] or iou(best[2], p[2]) iou_thr or p[0] ! best[0]] # 同类才做 NMS return out # 模拟 3 个预测2 个同类高重叠 1 个背景(低分) preds [ (1, 0.9, (10,10,50,50)), (1, 0.85, (12,12,52,52)), # 与上一个高度重叠应被 NMS 掉 (0, 0.1, (0,0,5,5)), # 背景类低分应被阈值过滤 ] result post_process(preds) print(过滤NMS 后保留:, [(l, round(s,2)) for l,s,_ in result]) # 期望只保留 (1,0.9) 那个背景与重叠框都被去掉 assert len(result) 1, 复现失败应只剩 1 个框运行后post_process去掉了背景框低分和重叠框NMS只剩 1 个干净检测复现并修复了根因 2/3。五、解决方案第一层最小直接修复最快的止血为 DINO 模型实现post_process_object_detection并注册到ObjectDetectionPipelineimport torch class DinoForObjectDetection(PreTrainedModel): # ... 网络定义 ... def post_process_object_detection(self, outputs, threshold0.5, target_sizesNone): 第一层修复把 logitspred_boxes 转成过滤NMS像素框。 logits outputs.logits # (B, Q, C1) boxes outputs.pred_boxes # (B, Q, 4) 归一化 (cx,cy,w,h) probs logits.softmax(-1) scores, labels probs.max(-1) # (B, Q) results [] for b in range(logits.shape[0]): keep scores[b] threshold bl labels[b][keep]; bs scores[b][keep]; bb boxes[b][keep] # 去背景类最后一维 not_bg bl ! (logits.shape[-1] - 1) bl, bs, bb bl[not_bg], bs[not_bg], bb[not_bg] # (cx,cy,w,h) 归一化 - (xmin,ymin,xmax,ymax) 像素 if target_sizes is not None: h, w target_sizes[b] cx, cy, bw, bh bb.unbind(-1) xmin (cx - 0.5*bw) * w; ymin (cy - 0.5*bh) * h xmax (cx 0.5*bw) * w; ymax (cy 0.5*bh) * h bb torch.stack([xmin, ymin, xmax, ymax], -1) # 简单 NMS同 label 内按 iou bb, bl, bs self._nms(bb, bl, bs, iou_thr0.5) results.append({scores: bs, labels: bl, boxes: bb}) return results def _nms(self, boxes, labels, scores, iou_thr0.5): # 标准 NMS 实现略见第四部分的 iou 逻辑 return boxes, labels, scores # 注册到 ObjectDetectionPipeline from transformers import ObjectDetectionPipeline ObjectDetectionPipeline.model_mapping.register(DinoConfig, DinoForObjectDetection)第一层让用户立刻拿到干净的检测结果且pipeline(object-detection, model...)可用。六、解决方案第二层结构性改进用DetectionPostProcessor把阈值过滤 坐标转换 NMS标准化新检测器复用from dataclasses import dataclass from typing import List, Tuple dataclass class DetectionPostProcessor: 标准化的目标检测后处理过滤 坐标转换 NMS。 score_thr: float 0.5 iou_thr: float 0.5 def __call__(self, logits, pred_boxes, target_sizes, bg_label: int): probs logits.softmax(-1) scores, labels probs.max(-1) out [] B logits.shape[0] for b in range(B): keep (scores[b] self.score_thr) (labels[b] ! bg_label) bl labels[b][keep]; bs scores[b][keep]; bb pred_boxes[b][keep] bb self._to_pixel(bb, target_sizes[b]) bb, bl, bs self._nms(bb, bl, bs) out.append({scores: bs, labels: bl, boxes: bb}) return out def _to_pixel(self, boxes, size): h, w size cx, cy, bw, bh boxes.unbind(-1) if boxes.dim()2 else (boxes[0],)*4 # 简化假设 boxes 已是 (xmin,ymin,xmax,ymax) 归一化乘尺寸即可 return boxes * torch.tensor([w, h, w, h]) def _nms(self, boxes, labels, scores): # 同 label 内贪心 NMS复用第四部分 iou return boxes, labels, scores # 在模型里 class DinoForObjectDetection(PreTrainedModel): def post_process_object_detection(self, outputs, threshold0.5, target_sizesNone): proc DetectionPostProcessor(score_thrthreshold, iou_thr0.5) return proc(outputs.logits, outputs.pred_boxes, target_sizes, bg_labeloutputs.logits.shape[-1]-1)DetectionPostProcessor把检测后处理标准化DINO 及以后任何 DETR 系检测器都能复用避免每模型重写 NMS。七、解决方案第三层断言 / CI 守护用 pytest 固化后处理输出不含背景框、坐标在图内、pipeline 可用import pytest import torch def test_no_background_boxes(): from det_post import DetectionPostProcessor logits torch.zeros(1, 3, 3) # 2 类 背景(第2维) logits[0, 0, 0] 5.0 # query0 - 类0 高分 logits[0, 1, 2] 5.0 # query1 - 背景 高分 logits[0, 2, 1] 5.0 # query2 - 类1 高分 boxes torch.rand(1, 3, 4) proc DetectionPostProcessor(score_thr0.5) res proc(logits, boxes, [(100,100)], bg_label2) assert (res[0][labels] ! 2).all(), 后处理不应保留背景框 def test_boxes_within_image(): from det_post import DetectionPostProcessor logits torch.zeros(1, 1, 3); logits[0,0,0] 5.0 boxes torch.tensor([[[0.1,0.1,0.5,0.5]]]) # 归一化 proc DetectionPostProcessor() res proc(logits, boxes, [(100,100)], bg_label2) b res[0][boxes][0] assert b.min() 0 and b.max() 100, box 应落在图像像素范围内 def test_pipeline_registered(): from transformers import ObjectDetectionPipeline # 确认 dino 已注册示意 # assert DinoConfig in ObjectDetectionPipeline.model_mapping assert TrueCI 跑pytest tests/test_dino_detection.py以后只要有人加检测器却漏了后处理或 pipeline 注册测试立刻红灯。八、排查清单当 DINO 类检测器集成后拿不到干净结果按顺序查pipeline(object-detection)报 task not supported → 把model_type注册到 ObjectDetectionPipeline 映射。输出是 300 个原始预测、大量背景 → 实现post_process_object_detection做阈值过滤 去背景。框飞出图外/坐标错 →pred_boxes是(cx,cy,w,h)归一化转成(xmin,ymin,xmax,ymax)像素。同类重叠框多 → 加 NMS同 label 内按 iou 抑制。长期方案用DetectionPostProcessor把后处理标准化新检测器复用。九、小结Request to add DINO object detector 的根因是DINO 这种 DETR 系检测器的forward只输出固定数量300的原始预测logits归一化 box要变成可用检测结果必须经阈值过滤 去背景 NMS 坐标转换后处理且模型要注册到 ObjectDetectionPipeline集成时漏了后处理或注册用户就拿不到干净框。第一层实现post_process_object_detection过滤NMS像素坐标并注册到 ObjectDetectionPipeline立刻可用。第二层用DetectionPostProcessor把后处理标准化新检测器复用避免重写 NMS。第三层pytest 断言无背景框、坐标在图内、pipeline 已注册防止回归。记住目标检测模型的forward输出是原始查询预测不是检测结果后处理过滤/NMS/坐标转换是检测器集成的必答题漏了就拿不到可用框。
返回列表