【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/坐标转换是检测器集成的必答题漏了就拿不到可用框。