
【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/坐標轉換是檢測器集成的必答題漏了就拿不到可用框。