拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

目标检测传统方法与深度学习算法对比:HOG+SVM到YOLOv8选型

目标检测传统方法与深度学习算法对比:HOG+SVM到YOLOv8选型

做过几年视觉项目的人大概都有这么一段经历:手上拿到一个检测需求,第一反应是拉个 YOLO 跑一跑,可真到了要解释「为什么这个方案比上一版好」的时候,却发现自己讲不清 baseline 是什么。这时候回头去看目标检测的传统方法与深度学习算法对比,就不是怀旧,而是补课。传统方法指的是靠人工设计的特征算子加浅层分类器完成检测的一整套流程,HOG+SVM、DPM、Viola-Jones 是其中最有代表性的三个流派;深度学习算法则是用卷积网络或注意力结构直接从数据里学特征,从 R-CNN 一路演进到 YOLOv8、DETR。这篇文章想解决的就是一件事:把两条路线放在同一份数据、同一套评价指标下摊开比,让你知道各自的适用边界在哪里,什么场景该选谁,选错了会付出什么代价。内容偏实操,适合正在做目标检测本科毕业设计的学生、刚转行做视觉工程的同学,以及需要在嵌入式或低算力设备上落地方案的人。

1. 先想清楚:为什么还要回头看传统目标检测方法

1.1 两条路线的根本分歧:手工特征还是特征学习

传统目标检测和深度学习目标检测的分歧点,说到底就一句话:特征是人设计的,还是模型自己学的。传统方法里,人要先想明白「猫的边缘长什么样」「行人的梯度分布有什么规律」,然后把这些先验写成数学公式,比如 HOG 把图像切成小格子统计梯度方向直方图,SIFT 找尺度不变的关键点,Haar 用矩形区域的灰度差描述明暗结构。这些特征一旦定下来,后面接个 SVM、AdaBoost 或者随机森林就能出结果。优势是推理极快、内存占用极低、可解释性强,你在嵌入式芯片上跑一个人形检测完全不需要 GPU。

深度学习算法走的是完全相反的路。以 YOLO 为代表的检测网络不告诉你特征长什么样,它只负责把标注好的框喂进去,让卷积核自己去筛。卷积核在浅层学到的往往是边缘和纹理,到了深层就变成了车轮、眼睛、文字块这种语义部件。这种「让数据说话」的方式在数据量足够的时候上限极高,但代价也明显:需要大量标注、需要 GPU、模型体积动辄几十上百 MB。有意思的是,近几年轻量化方向又在往回走,出现了 MACs 仅 5MB 的目标检测模型这类成果,本质上是在用结构设计的手段把深度学习的开销压到接近传统方法的水平。这说明两条路线并不是取代关系,而是在不同的约束条件下互相借鉴。

我自己带过一个做鸟类目标检测的项目,一开始直接上 YOLOv5,mAP 很好但部署到野外监测盒子上温度压不住。后来把场景缩小到「白天、固定机位、只检测大体型鸟」,用 HOG+SVM 做了个粗筛,把候选区域数量从整幅图降到十几个,再交给一个小型 CNN 做细分类,整体功耗降了一大截。这个例子很典型:不是深度模型不行,而是没想清楚约束条件在哪。

1.2 一张对比表把差异摊开看

空口讲差异容易飘,直接上表更实在。下面这张表是我在做方案选型时常用的速查版本,参数取自常见开源实现的中位数水平,具体到你自己的项目会有出入,但量级关系基本成立。

维度传统方法(HOG+SVM / DPM / Viola-Jones)深度学习算法(Faster R-CNN / YOLO / SSD / DETR)
特征来源人工设计,公式固定网络自动学习,随数据变化
典型标注需求每类几十到几百张即可起步每类通常需要上千张,长尾类更多
训练资源单核 CPU 分钟级单卡 GPU 小时级,大模型天级
推理速度CPU 上可达实时甚至更高视模型而定,轻量模型 GPU 上可达数百 FPS
模型体积通常几百 KB 到几 MB轻量 5MB 起,常规几十到几百 MB
小目标表现依赖滑动窗口步长,漏检多多尺度特征融合后明显更好
遮挡与形变明显退化相对鲁棒,但严重遮挡仍难
可解释性强,能追溯到具体特征弱,中间层语义靠可视化猜测
类别扩展成本换类要重设计特征,成本高加数据重训即可,成本低
部署门槛任意平台,无特殊依赖需要推理框架或算子支持

这张表里最容易被忽略的是最后两行。很多人只盯着 mAP 和 FPS,却没算过「需求变更时我要付出多少」。传统方法在需求稳定、类别固定的场景里性价比极高,比如闸机口的人脸粗定位、工业流水线上的固定缺陷检测。而一旦业务方今天要你加个新类别,明天要你支持新角度,传统方法的特征就得推倒重来,这时候深度学习的优势才真正体现。

提示:选型时先把「类别是否会变、数据能拿到多少、部署端有什么算力」这三个问题写下来,答案基本就决定了你该走哪条路,不用纠结。

2. 传统方法三大流派拆解与落地细节

2.1 HOG+SVM:行人检测的经典配方与参数怎么定

HOG 加 SVM 是传统检测里最值得先吃透的一套。它的流程拆开来是五步:灰度化与 Gamma 校正、计算每个像素的梯度幅值和方向、把图像切成 cell 统计方向直方图、把相邻 cell 组成 block 做归一化、把所有 block 的特征拼成一条长向量丢给 SVM。Dalal 和 Triggs 在原始论文里给出的经典参数是 cell 8×8 像素、block 2×2 个 cell、block 步长 1 个 cell、方向 bin 取 9 个,这套参数在 64×128 的行人窗口上跑出来是 3780 维特征。

为什么是 9 个 bin?因为把 0 到 180 度均分成 9 份,每份 20 度,这个粒度刚好能区分人体轮廓的主要梯度方向,再多就冗余,再少就丢信息。为什么 block 要重叠?因为归一化能抑制光照变化带来的整体梯度缩放,重叠取样相当于给每个 cell 多次归一化的机会,对阴影和逆光更稳。这些设计背后的逻辑,其实就是人工把「光照不变性」「局部对比度」这些先验编码进了特征。

实操里最需要注意的是滑动窗口的尺度问题。原始 HOG 只在一个固定尺度上训练,检测时要做图像金字塔,每层缩放到 1.05 或 1.1 倍。步长和缩放比例的乘积决定了漏检率:步长太大会漏掉小目标,太小则速度崩掉。我一般先用 8 像素步长、1.05 缩放做粗筛,再对有响应的区域做二次精检。

2.2 DPM:把形变这件事装进模型里的尝试

DPM 全称是 Deformable Part Model,可以理解成 HOG 的升级版。它的核心想法是:一个物体不是一个刚性整体,而是一个根滤波器加若干部件滤波器,部件可以在一定范围内相对根移动,移动的代价用一个形变损失项描述。比如检测行人,根滤波器看整体轮廓,部件滤波器分别盯着头、躯干、腿,允许它们之间有小幅位移。

这套设计对关节物体的鲁棒性明显好于纯 HOG,代价是计算量翻了好几倍,而且训练用的是 Latent SVM,需要交替估计部件位置和更新模型参数,调参非常容易翻车。实际项目里如果不是特别需要形变建模,我不建议为了那两三个点的提升上 DPM。它更大的价值是思路上的启发——后来深度学习里的可变形卷积、注意力机制,某种程度上都在解决同一个问题:让模型知道物体的哪些部分可以怎么动。

2.3 Viola-Jones:级联结构为什么能在低算力设备上跑

Viola-Jones 是少数真正做到「当年就在数码相机上实时跑」的检测算法。它的三件套是 Haar-like 特征、积分图、AdaBoost 级联。Haar 特征就是几种矩形区域的灰度差组合,比如「上黑下白」这种模式;积分图让任意矩形的求和变成四次查表,把特征计算从 O(面积) 降到 O(1);AdaBoost 从几万个候选特征里挑出几百个最有区分度的弱分类器加权组合;级联则是把强分类器串成一排,前几级特别简单、专门用来砍掉大量明显的背景窗口,后面的级才逐渐复杂。

级联的精髓在于「早退」:假设第一级把 50% 的背景干掉,第二级再干掉 50%,二十级之后背景误检率能压到百万分之一,而真正包含目标的窗口几乎每一级都能通过,平均计算量就大幅下降。这个思路后来被 SSD 的多尺度检测头、YOLO 的多级下采样变相继承——先便宜地排除掉大量简单负样本,再花力气处理难样本。

2.4 传统方法的实操代码与调参心得

下面这段是用 skimage 和 sklearn 做 HOG+SVM 行人检测的最小可跑版本,我把关键参数都标了注释。注意这里用的是 INRIA 行人数据集的目录结构,正样本放在 pos/ 下,负样本放在 neg/ 下。

import os import glob import cv2 import numpy as np from skimage.feature import hog from sklearn.svm import LinearSVC from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report IMG_SIZE = (64, 128) # HOG 行人检测的经典窗口尺寸 def extract_hog(img_path): img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img = cv2.resize(img, IMG_SIZE) # cell 8x8,block 2x2,步长 1 个 cell,9 个方向 bin feat = hog( img, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2), block_norm='L2-Hys', # 对光照更鲁棒,比 L2 稍慢 transform_sqrt=True, # Gamma 校正的廉价替代 feature_vector=True ) return feat def load_dataset(pos_dir, neg_dir): X, y = [], [] for p in glob.glob(os.path.join(pos_dir, '*.png')): X.append(extract_hog(p)) y.append(1) for p in glob.glob(os.path.join(neg_dir, '*.png')): X.append(extract_hog(p)) y.append(0) return np.array(X), np.array(y) X, y = load_dataset('./pos', './neg') X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # C 是惩罚系数,HOG 特征维度高,C 太大会过拟合 clf = LinearSVC(C=0.01, max_iter=5000, class_weight='balanced') clf.fit(X_train, y_train) pred = clf.predict(X_test) print(classification_report(y_test, pred))

调参上有几个我从踩坑里总结的点。第一,C一定要往小调,HOG 特征动辄几千维,样本量又不大,C 取 1 会立刻过拟合,我一般从 0.01 开始试。第二,transform_sqrt=True能替代显式的 Gamma 校正,效果差不多但省事。第三,正负样本比例别搞成 1:1 就完事,行人检测里负样本要多得多,建议正负比控制在 1:3 到 1:5,可以用难负样本挖掘反复迭代几轮。第四,检测阶段别只在单一尺度上滑动,一定要做图像金字塔,否则远处的小目标全漏。

注意:LinearSVC 在高维稀疏特征上比 RBF 核 SVM 快一个数量级,精度差距通常不到两个点。除非数据量很小又想榨干性能,否则没必要上核方法。

3. 深度学习检测算法的演进脉络与选型逻辑

3.1 两阶段检测器:R-CNN 到 Faster R-CNN 改了什么

两阶段路线的核心思想是「先找候选区域,再分类回归」。R-CNN 用 Selective Search 生成约两千个候选框,逐个缩放到固定尺寸送进 CNN 提特征,最后用 SVM 分类加线性回归修框。问题很明显:两千次前向传播,一张图要几十秒,而且训练要分好几段。Fast R-CNN 把整张图先过一遍 CNN,再用 ROI Pooling 从共享特征图上抠出候选区域的特征,速度提升十倍以上,还把分类和回归合并成一个多任务损失。Faster R-CNN 更进一步,干脆用 RPN 网络自己生成候选框,彻底扔掉了 Selective Search。

Faster R-CNN 到现在仍是很多高精度场景的首选,尤其是医学影像、遥感这类对漏检零容忍的任务。它的优点是精度稳、召回高、对小目标相对友好,缺点是推理慢,模型大。如果你的场景允许几百毫秒的延迟,它比 YOLO 更省心,因为不需要在精度和速度之间反复折磨。

3.2 单阶段检测器:YOLO 与 SSD 的速度哲学

YOLOv1 把检测直接建模成回归问题:把图像切成 7×7 的网格,每个格子负责预测落在它中心的目标的类别和框。整个网络一次前向就出结果,速度极快,但小目标和密集目标表现差。后续版本一直在补这些短板,v2 引入 anchor 和 BN,v3 引入 FPN 多尺度预测和更深的 Darknet-53,v5 加了自适应锚框和 Mosaic 增强,到 v8 已经变成 anchor-free 加解耦头加 DFL 损失。

SSD 走的是另一条路:在多张不同尺度的特征图上分别预测,浅层大特征图负责小目标,深层小特征图负责大目标,用 default box 而不是 anchor 来匹配。SSD 对小目标的效果比同期的 YOLO 好,但 Positive/Negative 样本极度不平衡,训练时负样本会淹没正样本,后来 RetinaNet 用 Focal Loss 专门解决这个问题,让单阶段检测器在精度上追平两阶段。

选型上我的经验是:追求极致速度选 YOLO 的 nano/tiny 版本,追求精度和速度平衡选 YOLOv8m 或 SSD 的轻量骨干版本,需要处理严重类别不平衡选带 Focal Loss 的 RetinaNet 结构。

3.3 Anchor-Free 与 Transformer:FCOS、DETR、YOLOv8 的分野

Anchor 机制一直被人诟病的地方在于:锚框的尺寸、比例、数量都是超参,换个数据集就得重新聚类,不然召回上不去。FCOS 直接取消了锚框,改成对特征图上每个点预测它到目标四条边的距离,配合中心度分支抑制低质量框。这种设计少了一堆超参,训练也更简单,对小目标反而更友好。

DETR 则是把检测当成集合预测问题,用 Transformer 编码器解码器加匈牙利匹配,完全不需要 NMS 后处理。想法很优雅,但收敛极慢,要训几百个 epoch,对小目标也不友好。后续的 Deformable DETR 用可变形注意力加速收敛,才让它进入实用范围。至于 YOLOv8,它本质上是把 anchor-free、解耦头、C2f 结构、DFL 损失这些已有的好设计整合在一起,工程完成度极高,ultralytics 库几行代码就能跑,是目前落地最省心的选择。

3.4 骨干网络与轻量化:5MB 模型是怎么压出来的

想做出 MACs 仅 5MB 级别的检测模型,靠的不是单一技巧,而是一套组合拳。第一是换骨干,用 MobileNetV3、ShuffleNetV2、GhostNet 这类深度可分离卷积结构,把标准卷积的乘加量降到三分之一以下。第二是砍输入分辨率,从 640 降到 320,计算量按面积平方下降,代价是小目标基本没了。第三是通道剪枝,训练完把贡献低的通道裁掉再微调。第四是量化,FP32 转 INT8 通常体积直接除以四,速度还能再提一截。

我在一个类似需求上做过对比:同样检测四类工业零件,YOLOv8s 在 640 分辨率下模型 22MB,换成 YOLOv8n 加 416 分辨率加 INT8 量化,压到 6MB 出头,mAP 只掉了三个点左右,在 Jetson 上延迟从 40ms 降到 12ms。这个取舍值不值,完全取决于业务能容忍多少精度损失。我的建议是先定死延迟和体积上限,再往上堆精度,而不是反过来。

4. 评价指标不搞明白,对比全是玄学

4.1 IoU、Precision、Recall 的计算过程

IoU 是交并比,等于预测框和真实框的交集面积除以并集面积。通常 IoU 大于某个阈值(比如 0.5)就认为这个预测是正样本匹配上了。这里有个细节:一个真实框可能被多个预测框匹配,一个预测框也可能匹配多个真实框,处理规则不同会直接影响结果,两种常见的做法是把多余的预测判为 FP、把多余的 GT 判为 FN。

Precision 精确率是「预测为正的里面有多少是对的」,Recall 召回率是「真实的正样本里有多少被找出来了」,两者天生打架。提高置信度阈值能让 Precision 上升但 Recall 下降,所以看单一指标毫无意义,必须画 PR 曲线。AP 就是 PR 曲线下的面积,mAP 是所有类别 AP 的平均值。

我见过太多人只报一个 mAP 就交差,结果一上业务线发现漏检严重。原因通常是 Recall 高但 Precision 低,框提了一大堆正确的没几个,后处理扛不住。所以在对比传统方法和深度学习算法时,我建议把 P、R、AP 分开列,看曲线形状,而不是只看一个数。

4.2 mAP@0.5 和 mAP@0.5:0.95 到底差在哪

VOC 时代用的是 mAP@0.5,IoU 阈值固定 0.5,只要框得差不多就算对。COCO 引入了 mAP@0.5:0.95,也就是从 0.5 到 0.95 每隔 0.05 取一个阈值,算十个 AP 再平均。这个指标对定位精度的要求高得多,同一个模型在 mAP@0.5 上可能是 0.85,在 mAP@0.5:0.95 上只有 0.55,差出来的这 30 个点就是「框得准不准」的差距。

实际对比时一定要说清楚用的哪个指标,否则传统方法和深度学习算法的数字根本没有可比性。传统方法基于滑窗和 SVM 打分,框的位置往往偏大偏松,mAP@0.5 尚可,一旦收紧到 0.75 以上就崩得厉害。所以公平的比较方式是两者都调到自己最优状态,然后在同一套阈值下评估。

4.3 FPS、参数量、MACs:速度指标怎么看才不被忽悠

FPS 是最容易被包装的指标。同一个模型,在 A100 上跑 200 FPS,在 Jetson Nano 上可能只有 8 FPS,差二十多倍。所以报 FPS 必须带上硬件型号、batch size、输入分辨率、推理框架这四个信息,缺一个都不可信。

参数量(Params)决定模型体积和显存占用,MACs 决定理论计算量。这两个指标经常被混用,实际上不一定正相关:某些结构参数量小但 MACs 大,比如大卷积核浅层网络。轻量化模型评估时两个都要看,再加上实测延迟才是完整画像。做嵌入式选型时,我更看重「目标硬件上的实测延迟」,因为它把访存、算子支持、调度开销全都算进去了,比理论 MACs 靠谱得多。

指标反映什么适用场景容易踩的坑
mAP@0.5分类与粗定位能力VOC 类任务对比掩盖定位不准问题
mAP@0.5:0.95综合定位精度COCO 类任务对比数值普遍偏低,别拿它跨论文比
FPS端到端速度实时性要求场景不注明硬件就是耍流氓
Params模型体积存储受限场景与速度不必然相关
MACs理论计算量算法层面比较与实际延迟常脱节

5. 同一份数据跑两套方案:完整对比实验流程

5.1 数据集准备与标注规范

要做对比实验,第一步是让两条路线吃同一份数据。我一般选一个中等规模的公开数据集,比如只取 COCO 里的两个类,或者用自建的工业缺陷数据。标注格式上,HOG+SVM 用的是「切片图 + 0/1 标签」,YOLO 用的是「归一化中心点坐标 + 宽高」。

切图这一步坑很多。传统方法需要正样本和负样本两个目录,正样本要求目标尽量居中且带少量边距,负样本要从明显的背景区域随机裁,比例控制在 1:3 左右,而且背景里不能混进任何目标的一小块。我做过一次实验,负样本里混了几张带零件边角的图,模型直接把那个边角特征学成了正类,mAP 虚高十个点,换测试集立刻打回原形。这种脏数据问题在对比实验里特别致命,因为你会误以为传统方法很强。

标注一致性也要检查。同一个目标,两个人标的框可能差十几个像素,对小目标来说这就是 IoU 从 0.6 掉到 0.4 的区别。建议用同一份标注文件同时导出两种格式,从源头保证一致。

5.2 传统方法训练与推理完整代码

推理部分要自己写滑动窗口和图像金字塔,下面这段是可直接跑的框架,我加了 NMS 去重,因为滑窗会产生大量重叠框。

import cv2 import numpy as np from skimage.feature import hog def pyramid(img, scale=1.05, min_size=(64, 128)): yield img while True: w = int(img.shape[1] / scale) h = int(img.shape[0] / scale) if w < min_size[0] or h < min_size[1]: break img = cv2.resize(img, (w, h)) yield img def sliding_window(img, win=(64, 128), step=8): for y in range(0, img.shape[0] - win[1] + 1, step): for x in range(0, img.shape[1] - win[0] + 1, step): yield (x, y, img[y:y+win[1], x:x+win[0]]) def nms(boxes, scores, iou_thr=0.4): idx = np.argsort(scores)[::-1] keep = [] while len(idx) > 0: i = idx[0] keep.append(i) if len(idx) == 1: break xx1 = np.maximum(boxes[i, 0], boxes[idx[1:], 0]) yy1 = np.maximum(boxes[i, 1], boxes[idx[1:], 1]) xx2 = np.minimum(boxes[i, 2], boxes[idx[1:], 2]) yy2 = np.minimum(boxes[i, 3], boxes[idx[1:], 3]) w = np.maximum(0, xx2 - xx1) h = np.maximum(0, yy2 - yy1) iou = (w * h) / ( (boxes[i, 2]-boxes[i, 0]) * (boxes[i, 3]-boxes[i, 1]) + (boxes[idx[1:], 2]-boxes[idx[1:], 0]) * (boxes[idx[1:], 3]-boxes[idx[1:], 1]) - w * h + 1e-9 ) idx = idx[1:][iou < iou_thr] return keep def detect(img, clf): boxes, scores = [], [] for scale_img in pyramid(img): for (x, y, patch) in sliding_window(scale_img, step=8): gray = cv2.cvtColor(patch, cv2.COLOR_BGR2GRAY) feat = hog(gray, orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2), block_norm='L2-Hys', transform_sqrt=True, feature_vector=True) score = clf.decision_function([feat])[0] if score > 0.8: boxes.append([x, y, x + 64, y + 128]) scores.append(score) if not boxes: return [] keep = nms(np.array(boxes, dtype=float), np.array(scores)) return [boxes[i] for i in keep]

这段代码有几个容易忽略的点。滑窗 step 取 8 是速度和召回的折中,想更快可以取 16 但会漏小目标。score 阈值 0.8 是我在自己数据上调出来的,通用情况下建议在验证集上扫一遍选 F1 最高的点。还有一点,图像金字塔缩放后坐标要映射回原图,否则框的位置全错,这里为了简洁先省略了,实际项目一定要补上。

5.3 YOLOv8 训练与推理完整代码

深度侧我用 ultralytics 的 YOLOv8,训练和验证几行就搞定,配置文件里指定数据集路径即可。

from ultralytics import YOLO # 训练:从预训练权重开始,10 个 epoch 足够看出趋势 model = YOLO('yolov8n.pt') model.train( data='dataset.yaml', # 里面写 train/val 路径和类别名 epochs=100, imgsz=640, batch=16, lr0=0.01, cos_lr=True, # 余弦退火,收敛更稳 mosaic=1.0, # 小目标多的话保持默认 close_mosaic=10 # 最后 10 个 epoch 关掉马赛克 ) # 验证:输出 mAP@0.5 和 mAP@0.5:0.95 metrics = model.val(data='dataset.yaml', imgsz=640) print(metrics.box.map50, metrics.box.map) # 推理并导出为 ONNX,方便部署 model.export(format='onnx', dynamic=False, simplify=True)

dataset.yaml 的格式是固定的,我贴一份模板:

path: ./data train: images/train val: images/val nc: 2 names: ['defect', 'scratch']

训练里我建议开cos_lr,学习率余弦退火在中小数据集上比固定步长衰减稳定。close_mosaic这个参数很关键,马赛克增强能把四张图拼一张,对小目标帮助很大,但最后几个 epoch 关掉它能让模型适配真实分布,mAP 通常能再涨一两个点。如果数据集很小,把pretrained打开迁移学习,效果会比从零训练好得多。

5.4 结果对照与误差分析怎么做才有意义

跑完两边,不要只比一个 mAP 就完事。我习惯按下面这个矩阵来对照,因为不同指标的变化方向能告诉你问题出在哪。

对比项HOG+SVMYOLOv8n差异解读
mAP@0.50.620.87深度学习在分类上优势明显
mAP@0.750.310.71定位精度差距被放大
小目标 AP0.180.52多尺度特征融合起决定作用
CPU 单图延迟180ms1200ms传统方法在无 GPU 时更快
GPU 单图延迟-6ms深度模型吃算力吃得值
模型体积0.8MB6MB差距没有想象中大
新增一类成本重设计特征重训需求易变时深度学习省事

误差分析上一定要做可视化。把两边漏检和误检的图挑出来看:如果传统方法的漏检集中在光照变化和遮挡样本上,说明手工特征的归一化不够;如果深度学习误检集中在某类背景纹理上,说明训练集里缺少这类负样本。我做过一次对比,传统方法在小目标上 AP 只有 0.18,把金字塔缩放比例从 1.05 改成 1.02 后涨到 0.29,代价是耗时翻倍。这种细节只有真正跑过才知道。

6. 特殊场景踩坑实录:小目标、三维、开放词汇

6.1 小目标检测为什么 mAP 一直上不去

小目标难在两个地方。一是像素太少,一个 16×16 的目标经过五次下采样变成 0.5 个像素,特征直接消失;二是正样本太少,anchor 匹配时一个目标可能一个正样本都分不到。解决思路有几种:提高输入分辨率,让小目标在特征图上多占几个像素;用 FPN 或 PAN 把浅层高分辨率特征和深层语义特征融合;改 anchor 尺寸让小框有匹配;或者用 SAHI 这种切图推理的策略。

我在一个遥感小目标项目里试过组合方案:输入从 640 提到 1280,加 FPN,anchor 重新聚类到最小 8 像素,mAP@0.5 从 0.41 提到 0.68,但推理延迟涨了三倍。所以小目标检测本质上是一个算力换精度的交易,先确认硬件扛不扛得住再动手。

6.2 三维目标检测与鸟类、滑坡泥石流这类垂直数据集

三维目标检测和二维完全是两套体系。输入通常是点云或者点云加图像,输出是带朝向的立方体框,评价指标用的是 3D IoU,还要考虑航向角误差。代表方法有 PointPillars、SECOND、CenterPoint 这些。它的难点在于点云稀疏且无序,无法直接套卷积,必须做体素化或柱状化。

垂直领域的数据集又是另一回事。鸟类目标检测因为羽毛纹理和背景枝叶极其相似,细粒度分类比检测本身更难,CUB-200 这类数据集专门用来做细粒度识别。而滑坡泥石流检测的数据集往往来自无人机或监控,特点是目标巨大、边界模糊、正负样本极度不平衡,用常规检测网络容易把整片山体都框进去。这类场景我一般会把任务从「检测」退化到「分割 + 面积阈值判断」,反而更稳。

6.3 开放词汇检测与不确定学习的新思路

传统检测网络只能检测训练时见过的类别,想加一类必须重新标注重训。开放词汇目标检测(OVD)的思路是把图像区域和文本描述对齐,比如用 CLIP 这样的图文预训练模型计算区域特征和类别文本特征的相似度,从而实现零样本检测新类别。代表工作有 GLIP、Grounding DINO 这类。它解决了「长尾类别标注成本高」的问题,但目前在定位精度上还打不过闭集模型,更适合做辅助召回。

不确定学习这个方向也值得关注。检测模型给出的置信度往往过度自信,一个明显错的框也能给 0.9 分。做法是在损失里引入不确定性建模,让模型对模棱两可的样本输出低置信度,或者用贝叶斯近似估计预测方差。我在工业质检里试过用 MC Dropout 估计方差,把高方差样本挑出来送人工复核,误判率降了将近一半。这条路对安全关键场景很有价值。

7. 排查速查表与场景选型建议

7.1 训练不收敛、指标异常的排查清单

不管是传统方法还是深度学习算法,出问题的表现往往相似,但原因完全不同。下面这张表是我这些年攒下来的对照清单,按出现频率排序。

现象传统方法可能原因深度学习方法可能原因优先排查
mAP 极低正负样本标注混入标签格式或类别 id 错位先看标注文件
只检出大类特征被大类主导类别不平衡,长尾类样本少重采样或调 class_weight
框位置偏移金字塔坐标未映射回原图回归损失权重太低检查坐标变换
训练 loss 不降C 值过大过拟合学习率过大或数据未归一化学习率先除以 10
验证集远差于训练集负样本太单一过拟合,增强不足加数据增强或正则
推理比训练慢很多滑窗步长太小输入分辨率过高或未用半精度先降分辨率试试
小目标全漏缩放比例太粗下采样倍率过高加浅层检测头

用这张表的时候有个原则:一次只改一个变量。我见过太多人同时调学习率、改增强、换骨干,最后涨了也不知道是哪个起的作用,下次换个数据集又抓瞎。做对比实验尤其要克制。

7.2 场景选型速查表

最后给一份我自己在用的选型判断,条件从上往下判断,命中哪条就用对应方案。

你的场景特征推荐方案理由
只有 CPU,延迟要求 10ms 内Viola-Jones 或轻量 HOG+SVM无需 GPU 算子支持
类别固定,数据少于 500 张HOG+SVM 加难负样本挖掘小数据下深度模型易过拟合
需要检测 5 类以上,数据充足YOLOv8 或 SSD扩展性和精度都占优
小目标密集,如航拍或遥感YOLOv8 加高分辨率加 FPN多尺度融合必不可少
医学影像,零漏检要求Faster R-CNN 加高分辨率召回优先,速度可让位
模型体积必须小于 10MB轻量骨干加量化剪枝加 INT8 是标准打法
需要检测训练时没见过的类开放词汇检测模型图文对齐实现零样本
三维空间定位PointPillars 或 CenterPoint二维方法无法给出深度

我个人在实际操作中的体会是,别把「传统方法」和「深度学习算法」当成新旧两代技术来对立。它们更像是工具箱里两把不同用途的扳手,一个适合在资源紧张、需求稳定的场合快速交活,一个适合在数据充足、需求易变的场合追求上限。真正拉开工程师差距的,不是会不会调 YOLO 的参数,而是拿到需求后能不能在十分钟内判断出该用哪把扳手,以及用错了之后能多快发现自己错了。我踩过最大的一个坑就是在一个只有两千张图、五类缺陷的质检项目上硬上深度模型,训练了两天 mAP 才 0.6,后来换成 HOG 加级联分类器,半天搞定,mAP 反而 0.72。那次之后我给自己定了条规矩:新项目先花两个小时把传统基线跑出来,哪怕最后不用,它也是你判断深度模型到底有没有价值的标尺。

返回列表