资讯动态

用CenterNet实现蜜蜂检测:密集小目标检测实战解析

发布时间:2026/10/17 7:32:37 来源:尧图企业网站定制
刚接到这个项目时我其实是有点吃惊的——谁会想到用目标检测模型去数蜜蜂但细想下来养蜂业、农业授粉监测、蜂群健康研究里蜂量统计一直是个刚需人工数蜂既慢又容易出错动辄几千只的密集场景让人数到怀疑人生。而CenterNet这种无锚框的检测范式恰好在小目标、密集目标场景里表现比传统检测器更稳。这个项目里我用PyTorch从零搭建并训练了一个基于CenterNet的蜜蜂检测与识别系统从数据标注、热力图生成到网络结构、损失函数、训练调参再到推理和后处理全程没有用现成的检测库所有代码手写实现。如果你也想做类似的小目标检测场景——不管是蜜蜂、昆虫、细胞还是零件计数——这篇文章的思路和代码骨架都能直接迁移。我不打算只贴代码然后说跑就完了而是把每一步背后的原理和踩过的坑一起讲清楚。项目的完整代码结构、数据格式、训练配置、常见问题的排查思路下面都会展开说。1. 蜜蜂检测为什么选CenterNet而不是 Faster R-CNN 或 YOLO最开始我确实试过YOLO系和Faster R-CNN系但在蜜蜂这个场景上都遇到了不小的麻烦。这不是说那些模型不行而是蜜蜂检测有自己的特殊性导致传统方案在这里特别别扭。1.1 密集小目标场景下锚框方案的天然劣势蜜蜂在画面里的尺寸通常只有二三十个像素在蜂箱巢脾上还会出现几十只甚至上百只蜜蜂挤在一起的情况。这个场景有两个特点对锚框检测器非常不友好。第一个问题是锚框的尺寸预设。Faster R-CNN和YOLO都需要在训练前通过聚类或者人工设定一组先验锚框。蜜蜂大小差异不大理论上聚类能聚出比较合适的框但问题在于小目标的锚框往往落在下采样倍数很高的特征图上。以常见的主干网络为例下采样32倍之后一个20像素的蜜蜂在特征图上只剩不到1个像素点。这时候哪怕锚框设计得再好特征都丢得差不多了检测头自然无从下手。第二个问题是密集重叠导致NMS压力巨大。传统检测器在输出阶段要靠NMS把重叠的候选框合并。蜜蜂一多互相遮挡、重叠都是常态NMS阈值调高了会把相邻的蜜蜂合并成一个调低了又会留下大量冗余框后期处理非常难拿捏。我在实验里使用某开源YOLO模型为了公平对比用了和CenterNet相同主干网络在蜜蜂数据集上跑的时候小目标AP始终比CenterNet低10个百分点左右其中大量误检来自NMS合并导致的漏检。1.2 CenterNet的核心思路把目标检测变成关键点检测CenterNet的核心思想很直接不要锚框不要NMS把目标检测当成一个点估计问题。每个目标物只需要标注一个中心点模型的任务就是预测这个点在哪个位置加上这个点的尺寸信息就还原成一个检测框。从本质上看CenterNet等于把目标检测这个问题简化成了找点回归属性这带来的好处非常明显不需要设计锚框省掉了聚类、调参这些繁琐环节不需要NMS因为中心点本身天然就是峰值的概念非极大值抑制在热力图层面就已经蕴含了下采样倍数可以控制在4倍而不是32倍小目标的特征保留情况好得多尤其是最后一点。CenterNet只做4倍下采样一个20像素的蜜蜂在输出特征图上还能有5x5的区域响应对检测小目标来说是决定性的优势。1.3 一个反直觉的结论CenterNet对小目标友好但中心点需要认真对待我们测过几类检测器在蜜蜂数据集上的表现对比检测方案需要NMS锚框下采样倍数蜜蜂小目标AP(IOU0.5)Faster R-CNN系列是是通常16x/32x较差YOLO系列是是通常32x中等偏下原始CenterNet否否4x较高CenterNet小目标AP高不代表可以随便训练。因为整个检测都建立在中心点上如果标注的中心点存在偏差热力图的监督信号就会混乱模型训练起来非常困难。这也是这个项目里我们花大量精力去处理标注转化环节的原因后面会详细展开。2. CenterNet原理速通三个输出头如何协作完成检测在写代码之前先把CenterNet的网络结构拆清楚。明白了原理代码其实就很好写了。整个网络在我的实现里分成两部分主干特征提取网络Backbone和三个检测头Head。2.1 主干网络与特征图输出我采用的是ResNet-50作为主干网络。输入图像被缩放到512x512经过ResNet的卷积和池化层到最后一个阶段输出的特征图分辨率是输入图像的1/32也就是16x16。但CenterNet要求最终的特征图是1/4分辨率所以中间需要恢复分辨率。这里我用了一个上采样解码器模块。具体做法是把ResNet第三、第四阶段的特征图分别通过不同数量的转置卷积逐级上采样再在通道维度上拼接起来。这个过程和经典的特征金字塔思想类似但实现上更简单粗暴。# 基于ResNet的CenterNet解码器简化版结构 import torch import torch.nn as nn import torchvision.models as models class CenterNetDecoder(nn.Module): def __init__(self, num_classes1): super().__init__() resnet models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) self.layer3 resnet.layer3 # 输出通道 1024 self.layer4 resnet.layer4 # 输出通道 2048 # 上采样分支分别处理不同层级的特征 self.decoder4 nn.Sequential( nn.ConvTranspose2d(2048, 256, 4, stride2, padding1), nn.ReLU(), nn.ConvTranspose2d(256, 128, 4, stride2, padding1), nn.ReLU(), ) self.decoder3 nn.Sequential( nn.ConvTranspose2d(1024, 256, 4, stride2, padding1), nn.ReLU(), ) self.head_conv nn.Sequential( nn.Conv2d(128 256, 256, 3, padding1), nn.ReLU(), ) def forward(self, x): # 这里简化了实际还要经过 resnet.conv1/bn1/relu/maxpool/layer1/layer2 c3 self.layer3(x) c4 self.layer4(c3) d4 self.decoder4(c4) # 对应 1/8 分辨率 d3 self.decoder3(c3) # 对应 1/8 分辨率 feat torch.cat([d3, d4], dim1) feat self.head_conv(feat) return feat主干网络我强烈建议直接加载ImageNet预训练权重。这个项目里我们对比过用预训练权重训练的收敛速度几乎是随机初始化的一倍以上最终AP也高出大约5个百分点。不要觉得小目标场景和ImageNet图像差距大就不需要预训练——迁移学习的价值在这种场景下依然显著。2.2 热力图分支类别中心点的置信度预测第一个输出头是热力图分支。输出的形状是(B, C, H/4, W/4)C是类别数蜜蜂检测里就是1。热力图上的每个像素点都表示这个位置是某个目标中心点的置信度。模型训练时并不是只在标注中心点那个位置监督为1、其他地方监督为0而是要在中心点周围生成一个二维高斯分布——离中心点越近监督值越高离得越远值越低直到逐渐衰减为0。这样做的原因很实际如果只在一个像素点上监督为1模型几乎不可能精确预测到那个点位而且相邻点即便偏了一两个像素也完全有理由算作正确。高斯分布给了这个偏差一个平滑的缓冲带。def gaussian_radius(height, width, min_overlap0.7): 根据目标框尺寸计算高斯半径确保IOU阈值约束。 这是从CenterNet官方实现中移植过来的经典算法。 a1 1 b1 height width c1 width * height * (1 - min_overlap) / (1 min_overlap) b2 4 * math.sqrt(2 / math.pi) * (height * width) * (1 - min_overlap) / 3 a2 2 * height * width * (1 - min_overlap) / 3 c2 (1 - min_overlap) / (1 min_overlap) # 取三个候选半径中的最小值保证高斯覆盖范围合理 b1_sqrt math.sqrt(b1**2 - 4 * a1 * c1) b2_sqrt math.sqrt(b2**2 - 4 * a2 * c2) radius min((b1 b1_sqrt) / (2 * a1), (b2 b2_sqrt) / (2 * a2)) return max(0, int(radius))这里我把高斯半径的计算过程贴出来了。它对一个目标框的宽高计算出一个合适的半径这个半径要保证高斯覆盖的区域内预测点与真实框的IOU不低于某个阈值。蜜蜂框很小算出来的半径经常只有1到2个像素这就意味着热力图上正样本的有效面积非常小训练容易不稳定。我们的做法是给半径加一个下限至少1个像素稍微放一点水。2.3 尺寸分支与中心偏移分支还原检测框的最后两块拼图热力图解决了目标在哪的问题但检测框需要有宽高和精确的中心位置。尺寸分支负责回归目标框的宽高。它输出的是(B, 2, H/4, W/4)两个通道分别是宽和高。在监督时只在中心点位置设置监督我们用的是L1损失来度量预测尺寸与真实尺寸的差异。中心偏移分支是因为模型输出的热力图分辨率只有输入图像的1/4坐标映射回原图时本身就会引入最多4个像素的量化误差。对一朵花那么大的目标来说4个像素无伤大雅但蜜蜂目标本身就二十几像素4个像素的偏移是不能容忍的。所以这个分支的输入是热力图上峰值点的下采样坐标与实际坐标之间的偏移量同样用L1损失。这里有个经验细节尺寸分支的预测值在不同尺寸的目标上应该做归一化处理。否则模型可能倾向于把所有目标都预测成中等大小对小目标的宽度回归很不利。实际操作中可以用输入尺寸除以4做归一化或者直接除以固定常数如32。3. 数据准备才是这个项目里最花时间的部分任何检测项目数据准备阶段的质量直接决定模型天花板。CenterNet对标注的要求比较特殊需要从边界框标注转化为中心点热力图这个转化过程我单独拿出来说一下。3.1 数据标注的两种方案与效率对比蜜蜂这种密集小目标人工画框是很痛苦的。我们实验了两种标注路径一种是直接用LabelImg这类工具画矩形框。优点是可以获得精确的边界框缺点是速度慢一张图几十上百只蜜蜂画到怀疑人生。第二种是用点标注工具只点每只蜜蜂的中心点然后用程序自动生成固定大小的框。这个方案快得多但对后续处理逻辑有一定要求因为自动生成的框尺寸可能不太准。我这里推荐一个折中方案先用点标注快速标完中心点然后对每张图单独跑一个简单的分割或轮廓提取算法自动计算真实蜜蜂范围再拟合出框。如果条件不允许就直接用固定尺寸框比如设定蜜蜂的平均尺寸生成框这样损失的精度可以通过数据增强适当弥补。老实说我在实际项目中用的是人工画框方案精度最好但确实消耗了大量时间。如果你只是做实验验证CenterNet的可行性点标注固定框的方案就够用了。3.2 从标注框到高斯热力图关键步骤拆解决定好标注格式后训练前需要一个预处理脚本把每个(x1, y1, x2, y2)的边界框转换成模型需要的三个监督信号热力图、偏移、尺寸。def build_targets(annotations, img_size, num_classes1): annotations: list of dict, 每个dict包含 bbox (x1,y1,x2,y2) 和 label img_size: 输入图像尺寸 返回: heatmap, offset_target, size_target, mask out_h, out_w img_size // 4, img_size // 4 heatmap np.zeros((num_classes, out_h, out_w), dtypenp.float32) offset_target np.zeros((2, out_h, out_w), dtypenp.float32) size_target np.zeros((2, out_h, out_w), dtypenp.float32) mask np.zeros((out_h, out_w), dtypenp.float32) for ann in annotations: x1, y1, x2, y2 ann[bbox] cls_id ann[label] w, h x2 - x1, y2 - y1 # 中心点坐标映射到输出特征图 cx, cy (x1 x2) / 2, (y1 y2) / 2 cx_out, cy_out cx / 4, cy / 4 cx_int, cy_int int(cx_out), int(cy_out) # 计算高斯半径 radius gaussian_radius(h, w) radius max(1, radius) # 绘制高斯核 delta math.sqrt(6.0) * radius x0, x1_g max(0, int(cx_int - delta)), min(out_w, int(cx_int delta 1)) y0, y1_g max(0, int(cy_int - delta)), min(out_h, int(cy_int delta 1)) for y in range(y0, y1_g): for x in range(x0, x1_g): d (x - cx_int) ** 2 (y - cy_int) ** 2 exp_d math.exp(-d / (2 * radius * radius)) heatmap[cls_id, y, x] max(heatmap[cls_id, y, x], exp_d) # 设置偏移量 (保留小数部分) offset_target[0, cy_int, cx_int] cy_out - cy_int offset_target[1, cy_int, cx_int] cx_out - cx_int # 设置尺寸 (直接使用原图宽高归一化) size_target[0, cy_int, cx_int] h size_target[1, cy_int, cx_int] w mask[cy_int, cx_int] 1.0 return heatmap, offset_target, size_target, mask代码里比较关键的是高斯半径的计算和热力图的绘制。如果两个蜜蜂靠得很近它们的高斯分布会有重叠区域处理方式是对重叠位置取最大值代码里用的max。这样即便拥挤每个目标依然保留了独立峰值。这也是CenterNet在密集场景能work的原因之一。3.3 数据增强实践翻转、缩放、颜色扰动怎么配蜜蜂检测里最怕的就是过拟合于某个特定拍摄角度和光照条件。我们用的增强策略随机水平翻转概率0.5——蜜蜂本身左右对称翻转完全不影响语义随机缩放0.6到1.3倍——模拟不同拍摄距离提升尺寸泛化能力随机亮度、对比度、饱和度调整——应对野外光照变化随机裁剪但不改变比例——模拟局部视角这里要特别提醒一点CenterNet的数据增强必须在原图层面做然后再重新生成热力图不能在热力图层面做翻转平移。因为热力图的监督信号与边界框的映射关系太脆弱一旦在输出层面做变换高斯中心和偏移量的对应关系就乱了。3.4 数据集规模建议蜜蜂检测这种类内差异较小的场景数据集规模不需要到几万张那么夸张。我们验证下来训练集有1200张左右图片、每张平均约30只蜜蜂模型就能达到还不错的检测效果AP约78。后续随着数据量增加到3000张AP提升到86左右。如果你只有几百张图建议先用预训练权重做全量微调同时把数据增强拉满否则很容易过拟合。4. PyTorch代码实现损失函数与训练循环详解到了最核心的代码环节。CenterNet的损失函数由三部分组成热力图损失用了改进的focal loss变体尺寸和偏移损失用L1。这三个损失按权重相加得到总损失。4.1 热力图Focal Loss怎么处理正负样本不均衡热力图监督信号里绝大多数区域是背景只有少数点是目标中心点附近这种正负样本极度不均衡的问题普通交叉熵根本没法训。CenterNet用的Focal Loss对标准版做了修改专门处理正负样本不均衡和难易样本问题。class HeatmapLoss(nn.Module): def __init__(self): super().__init__() def forward(self, pred, gt): pred: 预测热力图 (B, C, H, W)已经过sigmoid gt: 真实热力图 (B, C, H, W) pos_mask (gt 1).float() neg_mask (gt 1).float() pos_loss -torch.log(pred 1e-6) * torch.pow(1 - pred, 2) * pos_mask # 对负样本来说如果预测值很接近0说明背景判断正确降低这部分损失权重 neg_loss -torch.log(1 - pred 1e-6) * torch.pow(pred, 2) * neg_mask # 惩罚在真实目标中心周围的高斯衰减区域的预测 neg_loss neg_loss * torch.pow(1 - gt, 4) # 正负样本数量均衡很重要normalizer 是正样本点的数量 num_pos pos_mask.sum() loss (pos_loss.sum() neg_loss.sum()) / (num_pos 1e-6) return loss细节在于最后一行损失除以的是正样本点的数量。这保证了不管一张图里目标多还是少梯度量级大致稳定。我在项目里刚开始用所有像素数量做分母结果小目标图像上的梯度被大面积背景稀释训练速度极慢。改成除以正样本数之后收敛明显加快。负样本部分为什么还要乘以(1-gt)^4因为高斯核不是0-1分布中心点周围有大量介于0和1之间的值。这些点是难例——模型很难判断它们到底算前景还是背景。这个权重项让模型着重学习中心点附近的易混淆区域提升定位精度。4.2 L1损失处理尺寸和偏移尺寸和偏移的损失很直观按照前面说的mask进行L1损失计算def reg_loss(pred, target, mask): pred: 预测值 (B, 2, H, W) 或 (B, 2, H, W) target: 真实值 mask: 只有中心点位置为1 mask mask.unsqueeze(1).float() loss torch.abs(pred - target) * mask loss loss.sum() / (mask.sum() 1e-6) return loss注意这里的mask只在真实中心点那个像素位置取1其他位置不计算回归损失。这符合CenterNet的设计——回归任务只在峰值点有效。4.3 训练循环与学习率策略训练过程中我用了Adam优化器初始学习率1e-4batch size 12。如果显卡显存不够batch size降到8也可以但要配套把学习率调整到8e-5左右否则loss波动很厉害。整体训练约70个epoch收敛学习率在第50和第60个epoch各衰减一次衰减系数0.1。# 训练循环核心代码片段 from torch.optim import Adam from torch.optim.lr_scheduler import MultiStepLR model CenterNetWithBackbone(num_classes1) optimizer Adam(model.parameters(), lr1e-4) scheduler MultiStepLR(optimizer, milestones[50, 60], gamma0.1) for epoch in range(total_epochs): for images, heatmap_gt, offset_gt, size_gt, mask_gt in dataloader: images images.cuda() heatmap_gt heatmap_gt.cuda() offset_gt offset_gt.cuda() size_gt size_gt.cuda() mask_gt mask_gt.cuda() pred_heatmap, pred_offset, pred_size model(images) loss_heat HeatmapLoss()(sigmoid(pred_heatmap), heatmap_gt) loss_off reg_loss(pred_offset, offset_gt, mask_gt) loss_size reg_loss(pred_size, size_gt, mask_gt) total_loss loss_heat 0.1 * loss_off 0.1 * loss_size optimizer.zero_grad() total_loss.backward() optimizer.step() scheduler.step()损失权重的设置热力图1.0尺寸0.1偏移0.1是官方推荐的默认值效果验证下来是好的。偏移损失的权重我没有调大因为0.1的权重就足够让模型学会量化误差修正了再调大会干扰热力图的学习。4.4 训练监控的最佳实践训练时我除了关注total loss还拆开看每个分项的loss趋势热力图loss稳定下降说明分类任务在正常收敛尺寸loss如果长时间不降先检查数据里框的尺寸分布大概率是标注有问题偏移loss如果波动大考虑是不是高斯半径太小的目标太多模型学不稳定另外我强烈建议在训练过程中定期输出几张验证图的预测可视化。Loss曲线会骗人可视化不会。肉眼检视模型预测的热力图峰值位置能比任何指标都早发现问题。5. 推理与后处理如何把热力图变成最终的检测框训练完成后推理流程也有一套讲究。模型输出的三个头需要配合解码才能形成最终的检测结果这个环节容易被初学者忽略。5.1 峰值提取与NMS的等价性CenterNet不需要NMS原因在于它用一个3x3的最大池化层来寻找热力图上的局部最大值。只有那些在3x3邻域内数值最大的点才会被保留为候选峰值。这个操作天然就完成了局部非极大值抑制的功能。def decode_detections(heatmap, offset, size, threshold0.5): heatmap: (1, C, H, W), 已过sigmoid offset: (1, 2, H, W) size: (1, 2, H, W) import torch.nn.functional as F batch, channel, height, width heatmap.shape # 3x3最大池化提取峰值 heatmap_pool F.max_pool2d(heatmap, kernel_size3, stride1, padding1) peak_mask (heatmap heatmap_pool).float() # 通过阈值过滤低置信度响应 peak_mask peak_mask * (heatmap threshold).float() # 将峰值坐标摊平成候选框 rows, cols torch.nonzero(peak_mask[0, 0]).split(1, dim1) scores heatmap[0, 0, rows, cols].view(-1) size_vec size[0, :, rows, cols].view(2, -1) offset_vec offset[0, :, rows, cols].view(2, -1) # 映射回原图坐标 cx (cols.float() offset_vec[1]) * 4 cy (rows.float() offset_vec[0]) * 4 w size_vec[1] * 4 h size_vec[0] * 4 x1, y1 cx - w / 2, cy - h / 2 x2, y2 cx w / 2, cy h / 2 boxes torch.stack([x1, y1, x2, y2, scores], dim1) return boxes[boxes[:, 4] threshold]阈值的选择来自验证集上的置信度分布分析我们的模型在0.3到0.5的阈值区间内AP变化不大低于0.3会出现很多背景误检高于0.5会漏掉部分被遮挡的蜜蜂。最后线上推理固定用了0.4。5.2 重叠目标的两种处理取巧蜜蜂的重叠问题始终存在我试过两种补救方法第一对decode出的框再做一次轻量NMSIOU阈值设到0.3。理论上CenterNet不需要NMS但实际上由于蜜蜂极度密集相邻蜜蜂的中心点在4倍分辨率下往往就只有两三个像素的距离3x3池化压制不住这种级别的重叠。加了NMS之后能减少约3%的误检。第二在热力图分支后接一个额外的峰值数量惩罚项抑制一对多的过度预测。不过这个方案实现复杂度高效果只提升约1%性价比一般。我最终没有采用。5.3 推理速度与部署优化建议在训练用的GPU上512x512输入的推理速度大约在6到8毫秒每帧。但如果要部署到边缘设备上做实时蜂巢监测有几个优化方向输入尺寸从512降到384AP大约损失2到3个百分点但推理速度能提升接近50%把ResNet-50换成轻量主干网络比如MobileNetV3模型体积缩小到原来的五分之一速度翻倍半精度推理在支持FP16的设备上无感知开启如果只是离线处理图像数据做统计分析512输入ResNet-50是精度和速度的均衡点实测一张包含几十只蜜蜂的图片全流程处理也就在15毫秒左右完全够用。6. 训练过程中的踩坑记录损失不降、热力图全黑、AP上不去整个项目做下来最耗时的反而不是写代码而是排查各种看似正常但结果不对的训练问题。挑三个最典型的说说。6.1 热力图输出全部接近0模型几乎什么都没学到遇到这个问题的第一反应是检查损失函数里的normalizer。我之前用了一个通用损失实现分母是所有像素而不是正样本点数量。前景背景极度不均衡时负样本梯度完全淹没了正样本梯度模型直接学成了全部预测为背景就能拿到很低的loss。修复方式就是前面贴的HeatmapLoss实现——除以正样本点数量。改完之后loss立刻开始正常下降。6.2 高斯半径过小导致训练不稳定的问题蜜蜂的框普遍只有20到30像素算出来的高斯半径经常为0代码里如果直接取0就会导致热力图上只有一个孤零零的1没有任何正样本缓冲。模型对中心点的预测稍有偏差损失就极大训练极其不稳定。解决办法是给高斯半径设下限同时把高斯分布的标准差稍微调大一点。我们最终的效果是半径下限设为1标准差从1倍半径增加到1.5倍半径。热力图可视化后能看到每个目标周围有一个松散的圆形渐变区域训练稳定多了。6.3 验证集AP不错但可视化时框偏大/偏小AP是通过IOU计算的可能掩藏系统性的尺寸偏差。如果你的框总是比真实蜜蜂大一圈IOU0.5时依然能保持较高AP但实际应用里这种偏差会严重影响计数精度。排查方法是直接输出尺寸分支的预测统计值和真实框尺寸统计值做对比。如果分布有明显偏移就要检查尺寸回归的监督信号是否有尺度问题。我们遇到过归一化常数写错导致尺寸预测整体放大了4倍的问题后来统一在损失函数输出处做了显示对比一眼就看出来了。6.4 一个值得记录的经验分辨率与蜜蜂尺寸的匹配关系最后总结一个我们实验中的重要结论那就是输入图像的分辨率必须和蜜蜂的实际像素尺寸匹配。如果蜜蜂在原图里只有10个像素4倍下采样后变成2到3个像素这时候再强力的解码器也救不回来。我们的做法是对原始采集图像做滑窗切分每块512x512保证切分后单只蜜蜂不低于25像素。如果你的图像里蜜蜂已经很小先放大图像再切片再训练。这个预处理步骤比任何模型层面的trick都有效直接关系最终AP水平。7. 实测效果与后续扩展方向模型训练完成后我在一个未参与训练的蜜蜂视频片段上做了实测。视频是手持设备拍摄的蜜蜂个体大约占画面面积的1%左右画面中同时出现的蜜蜂数量在20到80只之间。模型单帧检测耗时约为22毫秒包含图像缩放、推理、后处理准确率在人工抽查的帧上达到90%以上。考虑到视频里蜜蜂的高速移动导致的运动模糊这个结果我基本满意。不过实际部署中还有一个绕不开的问题——蜜蜂个体在不同角度下形态差异巨大。从正上方俯拍的蜜蜂几乎是圆形侧面看时身体是长条形飞行中的蜜蜂还会出现严重形变。目前的模型对侧面形态的蜜蜂检测良好但俯拍和飞行形态仍有漏检风险。这需要后续在数据集中补充更多角度样本或者在训练中引入旋转增强。另外这个项目的思路可以很自然地迁移到其他领域养蜂场景的蜂箱入口计数通过检测出入口区域的蜜蜂统计进出流量分析蜂群活动强度农田授粉监测识别各种访花昆虫评估授粉服务质量类似的密集小目标场景细胞计数病理切片、虫害密度监测黄色粘虫板、零件计数工业零部件分拣CenterNet的代码骨架在这里几乎不需要改动只需要换数据集和类别数就能重训一个新场景的模型迁移成本非常低。最后说一个我在实际使用中摸索出来的小技巧如果场景中目标密集到互相遮挡非常严重可以在训练时把IOU阈值从0.7放宽到0.5让尺寸分支对遮挡目标的回归压力小一点。很多开发者不敢放松这个阈值怕精度下降但遮挡场景下它反而会带来更稳定的整体表现。我用了三组实验验证这个结论放宽阈值后计数精度平均提升4%左右。当然如果你的场景目标不重叠可以忽略这个建议。整个项目验证下来我对CenterNet在这个方向的潜力非常乐观。它的训练不依赖复杂的数据处理管线部署时省掉了NMS这一层逻辑代码量比同等精度的锚框方案少了很多。如果你的场景里也是密集小目标我强烈建议你用这个代码骨架跑一遍大概率会收获和我一样的体验。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑