☰
D-FINE-SEG:给DETR检测器低成本长出实例分割分支
2026/10/21 5:32:35 网站建设 项目流程

D-FINE-SEG这个项目,说起来有点"硬来"。我手头有一批基于DETR范式训练好的检测权重,定位质量一直很稳,但每次落地到具体场景,总被问同一句话:"你能顺便把物体的轮廓也抠出来吗?" 问多了我就开始琢磨,D-FINE这种检测器能不能不做大规模重训,直接把实例分割的能力长出来。

模块拆解下来,这个想法其实没有想象中那么激进。检测和实例分割本来就共享大量底层表征,输入的还是同一张图,backbone和encoder提取出来的语义特征几乎可以直接复用,区别主要在于输出空间多了一条"按像素判实例"的分支。所以我把这个项目命名为D-FINE-SEG,目标很明确:在尽量复用D-FINE检测权重的前提下,加一个mask分支,让模型同时输出"框"和"掩码",并在后续的update版本里持续优化。

这篇文章是D-FINE-SEG的update1记录,重点讲清楚我从"检测模型"改成"检测+实例分割模型"的完整思路、训练策略、实验数据,以及踩过的几个一眼看上去根本想不到的坑。如果你也在折腾DETR系的检测器,想低成本扩成分割模型,这篇应该能帮你少走不少弯路。

1. 为什么要把检测器硬改成实例分割模型

1.1 检测和实例分割,差的不只是一个掩码层

从使用者的角度来看,检测模型输出的矩形框信息密度太低了。工业场景里你要做机械臂抓取,矩形框往往会把背景、相邻物体甚至遮挡物的部分也框进目标区域,单纯靠框做抓取点估计,非常容易翻车。实例分割多给了一层像素级的归属关系,本质上是把"目标在哪"这个问题的答案从四个坐标点变成一张精确的模板图。

但反过来想,这两个任务在信息上的依赖关系非常强。一个框如果都定位不准,那掩码大概率也是乱的;而一个高质量的实例掩码,天然就自带一个高质量的外接矩形。换句话说,实例分割的模型能力是"包含"检测的,那我们让一个检测器长出分割能力,本质上是在已有的特征空间里做一次输出维度扩展,而不是从头训练一个新任务。

D-FINE这类DETR系模型的 decoder 结构,让这种扩展变得尤其自然。它的query token在解码过程中已经逐步迭代出目标的边界位置和语义类别,这些信息本身就是mask分支最好的初始化条件,不需要额外设计复杂的区域提议网络。

1.2 为什么不用现成的实例分割模型,非要自己改

市面上成熟的实例分割模型并不少,效果也确实好。但D-FINE-SEG盯上的场景是"轻量、实时、可复用已有权重"。

很多现成分割模型为了保证mask质量,往往把输入分辨率抬得很高,或者用很大的骨干网络,这在服务器上当然没问题,可一旦换到边缘设备,显存和延迟立刻就崩了。我这边手头很多项目对模型形态的要求是"一套模型同时跑检测和分割",而不是"两个模型分开跑",因为推理链路里多一个模型,意味着多一次前处理、多一批显存占用、多一段pipeline的同步逻辑。

D-FINE的优势恰恰在于它的检测精度高、结构相对轻。我的初衷是把它已经学到的边界感知能力和query迭代能力,直接迁移给mask分支,省掉从头训练的计算成本。这一步如果走通,等于用很小的增量成本,拿到一个"检测不掉点、分割能用"的联合模型。

这才是D-FINE-SEG存在的根本原因。

2. 整体改造思路:检测头旁边长出一个mask分支

2.1 从decoder query到mask embedding的关键路径

D-FINE的decoder输出每个query token时,原本只负责预测类别、回归框和IoU分数。D-FINE-SEG的改动核心,是让这个token再额外投影出一个mask embedding向量,然后用这个向量去"查询"高分辨率像素特征图,生成对应实例的掩码logits。

后者的数学直觉很简单,如果把像素特征记作F(H×W×C),query token映射出的mask embedding记作m(C维向量),那实例掩码就是逐像素做内积:

mask_logits = torch.einsum( # (H*W, C) 与 (C) 点乘 "n,c,hw->n,hw", pixel_embeddings, mask_embeddings )

本质上是让每个query携带一个"语义模式",然后去和高分辨率特征做匹配。这个思路我参考了常见的DETR系分割方案,稳定性和实现成本都很划算。用一句大白话说:检测分支负责回答"这里有没有目标、目标在哪",mask分支负责回答"目标具体覆盖了哪些像素"。

对D-FINE这种已经经过多层query refine的模型,最后一层decoder输出携带的语义信息足够丰富,分出一条mask embedding分支不会对检测性能造成明显挤压。

2.2 pixel decoder:为mask分支补上高分辨率特征

decoder query只有一维向量,要做像素级预测,必须有一张分辨率足够高的特征图来配合它工作。D-FINE的encoder特征主要服务检测,直接拿来做mask会显得分辨率不够,典型的问题是物体边缘糊成一片。

所以我在D-FINE-SEG里增加了一个轻量pixel decoder,思路是把backbone里几个层级的特征重新拉起来。具体来说,我复用了backbone的C3、C4、C5输出,加上encoder的顶层语义特征,通过一个简单的特征金字塔结构把空间分辨率恢复到输入图像的1/4大小。然后统一投影到256维的pixel embedding空间。

这个设计有两点考虑。一是参数量控制得很低,整个pixel decoder只有几层卷积和上采样,不会让模型从"轻量检测器"变成"重量级分割器"。二是特征对齐简单,C3/C4/C5本身就有明确的尺度语义,底层的C3提供边界细节,高层的C5提供类别语义,merge之后正好满足"实例掩码既要边界清晰又要语义准确"的需求。

2.3 为什么不用更复杂的掩码生成方式

其实做一个mask head,市面上有好几条路可以走,比如直接过几个转置卷积上采样到原图大小,或者每个实例单独生成一组动态卷积核,再和特征图做卷积。这些做法在效果上都有人验证过,但D-FINE-SEG的第一版讲求的是"稳定落地"。

我最终选query embedding 与pixel embedding点乘的方案,是因为它天然保持实例和query的一一对应关系。检测分支已经决定了哪些query是有效目标,mask分支只是把同一条query的语义信息"渲染"到像素空间,匹配、NMS、正负样本分配全都可以沿用检测器原有的那套逻辑。

而动态卷积或者多层mask transformer的问题在于引入了额外的序列生成过程,训练的稳定性和收敛节奏都比较难控制。第一版项目我不太想碰这类高风险模块。

3. 关键模块的落地细节

3.1 mask head的结构设计

整个新增分支我拆成三块:mask embedding生成模块、pixel decoder、mask质量评估模块。

mask embedding生成模块接在decoder最后一层之后,输入的是经过refine的query token和对应的参考框信息。参考框信息也会被拼进embedding生成过程,这样模型能知道"当前这个query关注的空间区域在哪",生成的mask embedding会带位置先验。实现上就是两层FC加LayerNorm,输出维度256。

pixel decoder负责把C3/C4/C5的特征对齐到统一维度,再逐级上采样融合。这一步的细节主要在于上采样时不能只做bilinear插值,要加上相邻层特征的残差叠加。我第一版直接做了逐层相加,边缘确实不够干净,后来改成每层先做1x1卷积降维、上采样后再和上一层相加,效果才稳定下来。

mask质量评估模块比较特殊。D-FINE原本就有一个IoU-aware的分类分支,用来预测预测框和真实框的IoU。我在D-FINE-SEG里把它扩展了一下:除了回归box IoU,还加了一个预测mask IoU的头部,也就是把预测掩码和真实掩码之间的IoU作为回归目标,叠加到同一个质量分支上。

这个模块在评估阶段很关键,因为mask的得分和box的得分不完全一致,有一个单独的质量分用来NMS和后处理排序,能明显减少"框很准但掩码缺胳膊少腿"的案例被误顶上去的概率。

3.2 IoU质量分数如何反哺mask训练

训练时如果只给mask分支加focal loss和dice loss,模型往往只学会把掩码"大致糊对",但要精确到什么程度,它没有感知。因为mask方向的损失函数关心的是像素预测对不对,却不告诉模型"你这次预测的掩码整体和真实掩码有多接近"。

我让mask质量分支去回归一个0到1的值,目标就是预测掩码与真实掩码的IoU。这样一来,模型会额外收到一个明确信号:如果你预测的掩码和真实掩码只有0.5的IoU,那这次mask就值0.5分,而不是只要类别对了就算过关。在训练早期,这个分支还能起到稳定训练的作用,因为检测分支原本就有IoU回归任务,两个质量分数的梯度在共享部分可以互相补充。

除开质量分数,推理阶段的用法我后面会单独讲,这也是一个非常容易被忽略的细节。

3.3 匹配策略与损失组合

D-FINE-SEG沿用D-FINE原有的匈牙利匹配机制来分配正负样本。第一阶段我只用类别分数和box IoU作为匹配代价,mask loss放在后面计算。原因在于训练初期mask分支从零开始,预测结果不可靠,如果用mask代价参与匹配,会导致query和目标之间的对应关系剧烈抖动,不利于收敛。

损失函数按权重组合如下:

损失项权重作用
focal分类损失(含IoU aware)1.0保证类别和实例级得分正确
box L1损失5.0保证框回归收敛
GIoU损失2.0进一步约束框的整体位置
box IoU回归损失2.0保留D-FINE原有的IoU预测能力
mask focal损失2.0关注掩码区域的像素级分类
mask dice损失4.0处理前景背景不平衡,稳定掩码形状

mask dice的权重我一开始给到1.0,训练了大概几千步发现掩码还是会碎,后来提到4.0之后明显好很多。

4. 三阶段训练策略:跑通mask分支的关键

4.1 阶段一:mask分支先单独收敛

我不是一上来就把整个网络所有参数全部解冻去跑,而是分了三步走。

第一步,加载D-FINE的检测权重,冻结backbone、encoder、decoder里所有检测相关参数,只允许mask embedding生成模块、pixel decoder和mask质量分支更新。这一步的目的是让新增模块先学会把现有特征映射成可用的掩码,而不是在检测能力还没稳定的情况下同时动两组参数。

这个阶段我设了一个相对高的学习率,比如2e-4,配合基础的图像增强(随机翻转、随机裁剪、颜色抖动),训练大概5个epoch左右。观察指标只看验证集mask AP,不看box AP,因为box这部分是冻结的,理论上不会掉。

跑了几个不同的随机种子,结论是这一步的效果和训练步数的关系比想象中平稳,基本到第2个epoch就已经能看到掩码轮廓成形,后面几个epoch主要是细化边缘和减少碎片。

4.2 阶段二:解冻联合微调

第二步,把解码器整体参数解冻,整个D-FINE-SEG一起联合训练,但学习率降到原来的1/10,也就是2e-5左右。这一步的目的是让decoder里的query在迭代refine时,不光优化框,也顺带优化mask embedding的中间表征。

联合微调的稳定性明显比第一步敏感。我试过直接解冻后还保持2e-4的学习率,结果5个step之内loss就开始飙升,只能回滚。所以学习率一定要降下来,给backbone和encoder相对大的学习率,decoder区域小一点,这种差异化配置会让训练平滑很多。

另外要提醒的是,联合微调阶段不要再去冻结pixel decoder。很多改模型的项目会在第二步遗忘mask分支,结果训练几轮后检测恢复了、mask又回退。pixel decoder必须全程参与反向传播,才能持续从backbone特征里读取到足够的新语义。

4.3 分辨率和增强策略的适配

实例分割对输入分辨率要比检测敏感。D-FINE训练时经常用512或640的短边,但对mask分支来说,小分辨率意味着很多小目标的掩码只有几个像素,根本没法学。我在D-FINE-SEG里把训练短边统一提到800,推理的时候也保持800短边。

这里衍生出一个问题:检测用较小的输入能更快,分割需要较大的输入,那联合部署怎么取舍?我的方案是训练时用800,推理时允许用户根据延迟要求降比例。mask分支在低分辨率下确实会损失边界细节,但在实时性要求高的场景,这是可以接受的。这个"训练高、推理可降"的方案挺适合工程落地的。

增强方面要注意少用强度特别大的裁剪。D-FINE原本的随机裁剪有时候会把目标截掉很大一部分,对检测来说框照样能回归,但对mask来说,目标被截断后掩码形状严重失真,会白白增加训练负担。D-FINE-SEG里我选择比较温和的scale jitter和随机翻转,必要时才挂轻量copy-paste。

5. 实验小考:mask AP与box AP的权衡

5.1 当前数值表现

我在验证集上跑了一轮,当前版本在保持轻量骨干的前提下,mask AP大概到了35.1,box AP相比原生D-FINE从44.8掉到了42.9,大约损失1.9个点,换来的是实例分割能力。

这个box AP的损失有一部分来自分辨率变化(从640提到800)和联合训练干扰,也有一部分来自mask分支对query表征的争夺。整体来说,1.9个点的检测损失换一个可用的mask分支,是符合我预期的,毕竟没有重新设计整个架构,属于一种低成本扩展。

和同龄一些更大规模联合模型相比,这个数值还有差距,不过这块主要差在模型容量上,后续update计划里我会把encoder部分的提效加进去,尽量把这部分补回来。

5.2 踩坑记录:mask分支训练中的三大意外

第一坑,mask embedding的维度和pixel decoder输出对不上。我最初把pixel embedding设计成128维,mask embedding却输出了256维,训练直接报错。这种低级错误其实暴露了一个问题:改模块的时候接口设计没先定清楚。后来我把维数统一成256,并在代码注释里写明接口约定,避免后续再改。

第二坑,mask高分伴随低质量掩码。训练早期经常出现一个现象:某些预测框质量分很高,但对应的掩码明显缺了一大块。排查后发现问题出在质量分支,它虽然额外回归了mask IoU,但NMS排序时如果只按box的质量分来排,那些掩码差的实例就会混进来。这里我特意调整了推理阶段逻辑,mask排序单独用mask质量分,box排序用box质量分,而不是简单把两者相乘。相乘会互相拖累,单独用反而各自干净。

第三坑,padding引起的掩码偏移。D-FINE训练时会在batch内做图片缩放padding,检测任务对绝对位置的敏感度相对低,但mask是逐像素的,padding之后特征图的空间位置就变了。如果你在生成mask logits后再resize回原图,会看到掩码边缘错位。解决方法是把pixel decoder的输出分辨率固定成与输入等比,不上采样到batch统一尺寸,保留每个样本自己的比例。这个问题很隐蔽,不逐张可视化很难发现。

6. update1阶段代码状态与迭代计划

目前D-FINE-SEG的代码分成几个模块:原有的D-FINE检测权重加载逻辑、新增seg_head、mask_loss、pixel_decoder,以及一个评估脚本。训练流程用三阶段config分开管理,每个阶段的损失权重都单独可调。整体代码结构比较清爽,方便后续加功能。

update1版本没有做的事情我也列一下。一是没有给encoder加mask attention的反馈,简单说就是encoder目前还看不到mask分支的信息,我推测加上之后长距离依赖和小物体掩码会更好。二是没有做掩码后处理,比如soft-nms、mask的CRF或者边界细化,这些工程上的加分项都排在后面。三是视频数据的时序稳定性还没有考虑,当前只针对单帧图像。

下一步迭代的重点,是把mask信息定期反馈回encoder,让backbone和encoder在训练时能根据掩码误差动态调整注意力,这也是一些成熟分割模型验证过有效的路线。不过要注意保持D-FINE的实时性,加反馈通路时必须控制计算量。我会在一个专门的小型实验集上先验证收益,再决定要不要全量引入。

另外还打算做一组消融实验,把mask分支在不同backbone规格下的表现记录下来,给后面部署选型用。毕竟工程项目的价值不止是跑出一个分数,还要能说明白"什么配置下可以达到什么效果"。

我个人在调试D-FINE-SEG时最大的体会是:把一个检测器扩成分割器,最大的阻力不在结构设计,而在训练节奏和细节对齐上。结构上的改动其实就那么几处,但训练策略一旦没安排好,就会频繁出现掩码质量与检测质量脱节的问题。这几轮实验下来,我对D-FINE的query表征能力反而更有信心了,也验证了"检测和分割共享一套query"这条路在当时的选择是对的。

后续这个项目我会继续以update的形式更新,每次都会把数值变化和坑记录下来。如果你也在做检测到分割的模型改造,不妨按这个思路试试,有问题可以一起讨论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询