ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OW-DETR:基于PyTorch的开放世界目标检测原理与实战

2026/9/11 0:15:26 拓冰建站 浏览量
OW-DETR:基于PyTorch的开放世界目标检测原理与实战 简介这是一份基于Pytorch实现OW-DETR开放世界Transformer目标检测算法的完整项目包主要面向具备深度学习与目标检测基础、希望将模型扩展到开放类别场景的算法工程师与研究生。算法利用Transformer自注意力机制在无预设分类目标条件下完成检测适用于自动驾驶、智能监控等动态复杂场景。包内不仅提供流程教程与可直接运行的源码还按工具模块划分出tools训练脚本、OWDETR核心模型、data数据处理、configs实验配置、util通用函数等目录并包含main_open_world.py、engine.py等重要入口借助configs中的实验配置可灵活调整参数便于从数据准备、模型训练到评估全流程复现。资源共74个文件以38个py源码文件为主辅以18个txt配置说明、9个sh运行脚本、README文档及少量C/CUDA扩展整体仅1.51MB轻量且结构清晰。项目已有406人学习工程组织清晰适合深入研究开放世界目标检测原理并在此基础上进行二次开发、实验对比也可作为课程设计或研究基线。1. OW-DETR 如何用 Transformer 在未知类别上直接出框一个反直觉的事实绝大多数目标检测器在训练集里只见过几十个类别时遇到一个从未标注过的新物体会带着很高的置信度把它识别成“背景”。这不是标注漏了而是模型设计里就没有“我不知道这是什么但那里有个东西”这个选项。OW-DETROpen World Detection Transformer要做的就是把这条路打通沿用 DETR 的端到端 Transformer 结构利用解码器交叉注意力图去标注“未知名目标”。它在已知类上正常分类在未知类上只给框和物体度不猜名字。标题里的“基于 PyTorch 实现”点明了落地方式可以用 PyTorch 2.x 配合标准 DETR 权重做增量扩展数据集也不需要大规模人工重标。本文按“原理 → 代码结构 → 训练调参 → 迁移自己数据”的顺序展开中间会给出可复现的命令和代码片段。适合两类人一类是刚接触开放世界检测、想找个能跑通基线的新手另一类是做过 YOLO 或 DETR 系列检测器、想评估开放世界设置下精度与召回边界的老手。2. OW-DETR 的开放世界机制注意力伪标签与三分量物体度2.1 从闭集到开放世界三种检测范式的分界在哪先明确一个容易混淆的概念开放世界检测Open World Detection不等于开放集检测Open Set Detection更不等于传统的闭集检测。三者对“未见过类别”的处理方式完全不同见下表。范式训练时可知类别测试时遇到未见类别典型方法闭集检测 Closed-set全部可见直接归为背景YOLO、Faster R-CNN开放集检测 Open-set全部可见但允许声明未知输出“未知”标签但不给精确位置OpenMax、OLTR开放世界检测 Open-world部分可见检测为未知目标并保留后续增量学习空间ORE、OW-DETRYOLO 这类把检测建模成密集网格回归的流程天然躲不开这个问题每个网格要么是已知类要么是背景没有第三种状态。OW-DETR 走的是另一条路它把“这是不是一个目标”和“这个目标叫什么”拆成两个独立判断未知类只在第一个判断上做文章。这也是它能支撑开放世界场景的根源。2.2 DETR 的集合预测天然具备“先找框、后命名”的条件Transformer 架构及其工作原理在这里体现得很直接。DETR 不使用锚框和后处理 NMS而是把检测看成集合预测问题解码器里固定一组 object query每个 query 经过自注意力和交叉注意力后输出一个目标框和一个类别概率。关键在类别输出和框输出是解耦的。object query 在解码器中和图像特征的交叉注意力决定它最终落在图像哪个区域分类头只是在这个区域上做一个判断。也就是说DETR 本身就允许一个 query“找到了东西但说不出名字”。OW-DETR 抓的正是这个间隙。复现时要注意 DETR 的位置编码使用的是正弦绝对位置编码OW-DETR 基本保留这一设定没有改成可学习的相对位置编码。这个细节影响不大但如果你在迁移到自己的数据集时发现收敛慢可以优先怀疑位置编码的归一化方式而不是模型结构。2.3 OW-DETR 如何让未知类变得可见2.3.1 注意力驱动的伪标签ADPL在做什么训练集里只有已知类标注模型怎么知道哪里可能存在未知目标OW-DETR 给出的做法是用训练过程中编码器产生的注意力图去发现“看起来像物体但没被标注”的区域。具体来说ADPL 模块对编码器最后一层输出计算注意力权重经过一个可学习的门控机制把注意力分数突显出来。然后在注意力图上采样高响应区域聚合成候选框作为伪标签。这些伪标签不携带类别名只携带位置信息用于监督未知类分支。这里的核心区别在于伪标签不来自最终的预测头而是来自注意力图。注意力图从定位信号上学出来不会像分类头那样被“背景”主导。2.3.2 通过对比聚类把未知类彼此分开伪标签有了但这堆未知目标里可能混杂着多个新类别。没有类别名字怎么让它们彼此区分OW-DETR 使用对比聚类让同一张图片内部属于相似区域的未知特征互相靠近属于不同区域的互相推开不同图片之间的特征进行负向对比。这是一种无监督的类别分离方式。它在训练时不生成具体未知类 id而是把特征空间掰成若干个簇。测试时当物体度分支判定某个框是未知目标就把它映射到最近的特征簇从而实现“知道是不同种新东西”但“不知道叫什么名字”的效果。2.3.3 物体度头的三分量结构普通 DETR 的分类头输出维度是known_classes 1这个 1 就是背景。OW-DETR 把它改成三个分量已知类、未知目标、背景。背景只负责纯无目标区域已知类和未知目标互不侵占。三个分量共享特征但分类头分别监督。已知框用真实标注未知目标区域用 ADPL 生成的伪标签其余区域用背景。推理时如果未知分量分数超过阈值就直接输出为 unknown不需要先通过某个已知类别假设。这个三分量结构是 OW-DETR 和 DETR 之间最直接的代码差异。3. 基于 PyTorch 搭建 OW-DETR环境、数据与训练流程3.1 PyTorch 环境搭建版本组合怎么选不踩坑OW-DETR 对 PyTorch 的版本没有硬性依赖常见实现大多基于 DETR 官方代码改造兼容 PyTorch 1.13 到 2.8 都能跑。但建议直接使用新版本省去很多算子兼容麻烦。下面是我在 Ubuntu 上常用的组合conda create -n owdetr python3.10 -y conda activate owdetr pip install torch2.5.1 torchvision0.20.1 --index-url https://download.pytorch.org/whl/cu121 pip install matplotlib pycocotools scipy einops版本对应的逻辑很简单PyTorch 2.5.1 对应 torchvision 0.20.1CUDA 12.1 的预编译包直接指定 cu121。如果显卡驱动支持更高版本也可以把 cu121 换成 cu128但不要单独拆开混装 torch 和 torchvision它们之间有编译期绑定关系。安装完务必跑一下 CUDA 是否可用python -c import torch; print(torch.__version__, torch.cuda.is_available())输出里cuda.is_available()返回 True 再继续。False 的情况优先检查驱动和 PyTorch 的 CUDA 版本是否配套而不是重装 Anaconda。3.2 数据集准备VOC 的已知类/未知类划分与 COCO JSON 格式开放世界检测的标准评测设置是把 PASCAL VOC 的 20 个类划分成 15 个已知类加 5 个未知类。训练时未知类图片照常参与训练但它们的标注会被剔除。模型需要在训练时从注意力图中“自己发现”未标注目标然后再在测试时召回它们。常用做法是先把 VOC 的 XML 标注转成 COCO 格式因为 DETR 系列评估代码依赖 COCO API。转换脚本的关键是 categories 部分要保留完整类别名# voc_to_coco.py import json, xml.etree.ElementTree as ET KNOWN_CLASSES [aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person] ALL_CLASSES KNOWN_CLASSES [train, tvmonitor, sofa, pottedplant] def convert_annotation(xml_path, img_id, images, annotations): tree ET.parse(xml_path) root tree.getroot() img_info {id: img_id, file_name: root.find(filename).text} images.append(img_info) for obj in root.findall(object): name obj.find(name).text if name not in ALL_CLASSES: continue bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) w float(bbox.find(xmax).text) - x1 h float(bbox.find(ymax).text) - y1 annotations.append({ id: len(annotations), image_id: img_id, category_id: ALL_CLASSES.index(name) 1, bbox: [x1, y1, w, h], area: w * h, iscrowd: 0 })训练时切换未知类有两种方式一种是在转换阶段直接丢弃未知类标注另一种是在数据加载器里动态 mask。建议使用第二种这样同一个标注文件可以反复实验不同的已知/未知划分不用为每组划分重新转换一遍数据。3.3 模型代码组织把 DETR 的 forward 扩成三头输出OW-DETR 的常见实现是把 DETR 的DETR类继承下来在原有分类头和框回归头之外新增物体度头和未知类特征头。文件结构可以组织成这样owdetr/ ├── models/ │ ├── detr.py # 基础 DETR 模型 │ ├── owdetr.py # OW-DETR 继承扩展 │ ├── adpl.py # 注意力驱动的伪标签模块 │ └── matcher.py ├── datasets/ │ └── voc_ow.py # 开放世界 VOC Dataset ├── engine.py └── main.py核心的 forward 伪代码如下# models/owdetr.py import torch from torch import nn from models.detr import DETR class OWDetr(DETR): def __init__(self, num_classes15, num_unknown_slots5, **kwargs): super().__init__(**kwargs) # 原分类头已知类 背景 self.class_embed nn.Linear(256, num_classes 1) # 物体度头已知 / 未知 / 背景 三个分量 self.objness_embed nn.Linear(256, 3) # 未知特征对比头 self.unknown_proj nn.Linear(256, 128) def forward(self, samples, targetsNone): outputs super().forward(samples, targets) # features 来自解码器输出的 object query 特征 features outputs[last_hidden_state] outputs[objness_logits] self.objness_embed(features) outputs[unknown_feat] F.normalize(self.unknown_proj(features)) return outputs需要注意objness_embed和class_embed的监督目标不同objness 是一个三分量交叉熵class_embed 只在已知类分量上计算损失。训练时 backgrounds 由 DETR 原匹配器决定unknown 的标签则来自 ADPL 生成的伪框整体构成一个多任务损失。3.4 训练启动命令、参数与日志观察点完成数据准备和模型改造后训练命令基本围绕main.py展开。以 VOC 15/5 划分为例python main.py \ --dataset_file voc_ow \ --data_path ./data/VOCdevkit \ --known_classes 15 \ --unknown_classes 5 \ --batch_size 4 \ --epochs 50 \ --lr 1e-4 \ --lr_drop 40 \ --output_dir ./runs/owdetr_voc15_5参数含义known_classes控制分类头维度unknown_classes只影响伪标签聚类时预留的簇数量batch_size 4是参数量不大但显存有限的保守选择lr 1e-4是 DETR 系列常见的初始学习率Transformer 部分的收敛对学习率比 CNN 更敏感不建议直接调成 1e-3。训练日志里重点观察两个值loss_objness和unknown_recall前者下降说明三分量物体度在起作用后者在验证集上逐步上升说明 ADPL 的伪标签质量在改善。提示前 5 个 epoch 如果loss_class掉得很快而loss_objness几乎不动不用慌因为 DETR 的匹配器先稳定已知类匹配未知类的信号需要一个预热过程。4. OW-DETR 参数与损失伪标签阈值、未知类别数量上限和控制点4.1 伪标签阈值怎么定门控分数、NMS 与训练稳定性的关系ADPL 生成的伪标签不是直接可用的需要过一道筛选。门控注意力分数是一个介于 0 到 1 之间的标量分数越高说明该区域被注意力“关注”得越充分。常见实现里会设定一个阈值 gate_threshold只有得分超过阈值的区域才能生成候选框。阈值设大了漏掉大量未知目标未知类召回率上不去阈值设小了大量背景区域混进来把未知类特征空间搞脏。在 VOC 15/5 设置下建议从 0.3 开始试同时配合 NMS 阈值 0.5 消除重叠框。判断阈值是否合适可以对比开合伪标签时训练集上未知类召回的差异差异过大说明伪标签量过多或噪声过高。具体操作时在 ADPL 加入一个简单的门控控制# adpl.py gate torch.sigmoid(gate_logits) mask (gate 0.3).float() attn_masked attn * mask boxes generate_boxes_from_peaks(attn_masked, nms_threshold0.5)这里gate_logits是门控网络的原始输出generate_boxes_from_peaks把注意力峰值区域转换成伪框。0.3 是经验起始值如果你的数据集物体的尺度偏小可以适当降到 0.25。注意伪标签生成的随机性会影响训练稳定性建议固定随机种子并让 ADPL 模块不参与梯度更新只作为离线信号源使用。4.2 未知类别数量上限 K 的作用与动态更新策略训练集中的未知类实际上只有 5 个但真实场景下模型无法预知。OW-DETR 在对比聚类时需要预先设定未知类特征簇的数量这就是 K。K 设的大小直接影响特征空间的划分粒度。K 太小多个真实新类别被并进同一个簇召回高但精度差。K 太大特征空间过度碎片化一个类被拆成好几个簇物体度头的“未知”分会被分裂。常见实现里 K 是硬编码的有的项目支持动态 K 调整做法是在验证集上计算未知类伪标签的聚簇紧凑度如果平均簇内距离显著高于历史均值说明 K 偏大自动调小。我在复现时更推荐一种简单方法把 K 设成已知类数量的一半左右VOC 场景下 7 到 8 的结果比 5 更平滑因为真实未知类在特征空间里不一定正好是 5 个簇。4.3 损失权重与已知类精度的博弈OW-DETR 的总损失由三部分构成传统检测损失框回归 已知类分类、ADPL 伪标签监督损失、未知物体度损失。默认权重通常都是 1:1:1但这个等权重并不是最优解。损失分量默认权重作用调节方向loss_ce已知类分类1.0维持已知类精度已知类 mAP 掉太狠时增大loss_boxes框回归5.0定位质量基本固定不要频繁动loss_objness物体度1.0区分已知/未知/背景未知召回不足时增加到 1.5-2.0loss_contrast对比聚类0.5未知类特征分离未知类经聚类混淆时增大到 1.0框回归的损失权重保持 DETR 原始设定即可它同时服务已知类和未知类降低它会拖累一切。loss_contrast是最值得调的项它的作用是把未知类特征空间掰开。权重太大时模型会把已知类特征也推散导致已知类 mAP 骤降太小则未知类之间的区分度不够。4.4 训练中如何判断未知分支在收敛只盯着 loss 是不够的因为伪标签本身可能有噪声loss 下降可能只是模型在拟合噪声。必须在训练过程中定期计算验证集上的 unknown recall。可以每 5 个 epoch 跑一次轻量评估重点看两个指标的组合unknown recall 提升但 known mAP 几乎不变健康的收敛。unknown recall 提升但 known mAP 下降超过 2 个点伪标签噪声超标优先调低 gate_threshold其次调低loss_contrast。unknown recall 在后期剧烈震荡大概率是 K 值过大导致聚类簇切换考虑固定 K 并降低学习率。5. 把 OW-DETR 用到自己的数据上迁移检查与未知类验证技巧5.1 从闭集标注快速转成开放世界格式自有数据通常只有已知类标注转成开放世界训练集不需要额外标注未知类。做法是保留全部原始标注作为已知类在数据加载器里维护一个 ignore 列表把想要测试的新类从监督中剔除。# voc_ow.py class OWDataset(Dataset): def __init__(self, ann_file, known_ids, exclude_ids): self.known_ids set(known_ids) self.exclude_ids set(exclude_ids) # 加载标注时只保留 known_ids self.filtered_anns [a for a in all_anns if a[category_id] in self.known_ids]关键在exclude_ids的设计上。这些类别的真实标注在训练时被排除但 ADPL 的注意力图仍然能从图片上感知它们的存在这正好模拟了真实场景里“模型看到了新东西但没人告诉它这是什么”。评估时再用完整标注计算 unknown recall就能看出模型到底有没有学会发现新类。5.2 用已知精度和未知召回率联合选择 checkpoint开放世界检测的最终落地点不在于单个 mAP而在于已知与未知的平衡。评估时同时跑两个指标python eval.py --dataset_file voc_ow --data_path ./data/VOCdevkit \ --resume ./runs/owdetr_voc15_5/checkpoint0020.pth \ --known_classes 15 --unknown_classes 5评估输出会包含 known mAP、unknown recall 和 wildness impact 三项。选择 checkpoint 时优先看 unknown recall 与 known mAP 的比值而不是单看某一边。一个常见的落地策略是取 unknown recall 超过 60% 且 known mAP 不低于封闭集基线 95% 的 checkpoint。实际项目中很少追求最高的 unknown recall因为那通常意味着大量背景被误报为未知。5.3 按未知类别拆开评估定位模型盲区整体 unknown recall 只反映平均水平实际使用里要关注模型漏掉的是哪些类别。逐类别计算 unknown recall 是定位盲区的最直接方法。假设验证集中第 19 类是train但训练时被排除遍历该类的所有标注统计被物体度分支判定为 unknown 的框占比。如果train类召回率明显低于其他未知类通常不是因为物体度分支不好而是因为 ADPL 的伪标签在训练时没有覆盖类似外观的区域比如大尺度目标和低纹理目标。此时针对该类提高门控分数的容差范围或增加数据增强中随机缩放的幅度能显著改善。另外一个容易忽视的细节是评估时 IoU 阈值的选择。开放世界检测场景中未知类框不需要和 GT 完全对齐IoU 0.5 或更宽松的 0.4 都比 COCO 标准的 0.75 更符合“发现新目标”的语义。把所有未知类按召回率从低到高排序后优先处理排在最后的那个类别再重新训练迭代比盲目调整整体权重更高效。本文还有配套的精品资源点击获取