☰
YOLOv10小目标车辆检测实战:从预处理到TensorRT部署的关键细节
2026/10/22 14:34:09 网站建设 项目流程

先把话说在前面:小目标车辆检测这事儿,做起来远没有标题看上去那么轻松。同一个场景里,大车轻松检出、小车集体隐身,是常态。把YOLOv10搬过来解决小目标问题,方向没问题,真正拉开差距的是预处理、训练细节、推理策略这三层有没有抠到位。这篇就把整个系统从思路到落地完整拆开,看完你至少能少踩三次坑。

1. 项目整体设计与选型思路

1.1 小目标为什么“小”——难点藏在哪

小目标在多数目标检测框架里的判定标准很粗暴:小于32x32像素,或者占整张图面积不到0.566%。放到车辆场景里,车大概50到150像素,放在手机上看清清楚楚,但喂给模型时经过缩放、下采样,特征图上一个目标就只剩几个像素点。拿我自己之前的实测数据说,30层开外的小轿车,在原图里大概是40x20个像素,经过YOLO的Backbone直接缩小到原来的八分之一甚至十六分之一,留给检测头的有效信息就剩3x2个格子,全靠那十几二十个像素做分类和回归,难度直线上升。

还有个物理层面的坑:小目标在图像里天然缺乏上下文信息。大卡车能看到车厢、车头、轮子,小轿车远看就是一个深色小方块,特征极其有限。模型更倾向于把这种孤立的像素块误判成地面标记、阴影或者噪声。这也是为什么很多人用YOLOv5/v8跑通了大目标检测,一换到小目标场景,mAP直接掉了二十多个点。

小目标检测难,本质上是三个矛盾叠加:特征信息与感受野的矛盾、样本数量与样本质量的矛盾、定位精度与计算开销的矛盾。做小目标车辆检测必须同时应付这三者,缺一个方案都很难落地。

1.2 为什么是YOLOv10,而不是继续用V5/V8

选择YOLOv10,不是因为它版本号最新,而是它在解决小目标问题上做了几件实际有用的事。

最关键的改动是去掉了NMS(非极大值抑制)。以前用YOLOv5跑车辆检测,最大的痛点是密集场景下小目标互相重叠,NMS阈值调高调低都难受:阈值调高了,同一辆车出多个框;调低了,相邻的小车被合并成一个框,直接漏检。YOLOv10用了一致的双标签分配策略,在训练阶段同时做一对一和一对多匹配,推理时只保留一对一匹配的预测头,模型天然学会了筛选重复检测框,不需要事后做NMS。密集停车的俯拍场景下,这个改动对小目标的漏检率改善非常明显。

另外YOLOv10在Backbone里用了更轻量的C2fUIB模块,计算开销比V8的C2f低,Feature Pyramid里还加入了PSA注意力机制,让小目标区域的特征权重更高。实践下来,同等算力下,YOLOv10s的小目标召回率比我之前用YOLOv8s调了几个星期还高两三个点。

当然,去掉NMS也带来了一个副作用:个别极小目标的置信度普遍偏低,容易从0.25的默认阈值下滑过去。这个需要在推理阶段单独做置信度补偿,后面我详细说。

1.3 系统模块怎么划,交付形态怎么定

做这套系统的时候,我把它拆成了三个完全独立的模块:训练管线、推理服务、UI交互层。三者之间只通过模型文件和检测结果JSON通信,互不依赖。

  • 训练管线:负责数据预处理、模型训练、验证、导出,使用公开的Ultralytics训练仓库为基础做定制修改。
  • 推理服务:封装了模型加载、图像预处理、推理、后处理的全流程,对外暴露统一的predict接口,支持单张图片、视频帧流和文件夹批量模式。
  • UI交互层:基于PySide2开发,负责文件选择、参数调节、结果可视化、检测结果导出。

为什么这么拆?因为实际项目里大概率会遇到“训练在A机器,推理部署在B机器,演示在C机器”的情况。如果所有逻辑都耦合在一个脚本里,换机器就全盘崩溃。模块化拆开之后,推理服务只依赖模型文件和PyTorch推理,UI层只依赖推理服务的输出,后面即使要换检测模型,UI完全不用动。

还有一个很重要的决策:UI层没有用Web技术,直接上了桌面框架。单人使用的检测工具,桌面端开发效率更高,文件路径处理也更直接——PySide2的QFileDialog拿到的就是本地绝对路径,不用考虑浏览器沙箱、文件上传之类的限制。后期如果要多人协作,再把推理服务单独部署成HTTP接口也行,架构上随时可以演进。

2. 小目标车辆检测的关键实现细节

2.1 预处理:切图策略和letterbox到底怎么调

小目标检测里,预处理比模型本身更能决定上限。做车辆小目标检测,我强烈建议把输入分辨率从默认的640拉高到1280,甚至1920。

原理很简单:640分辨率下,一个40x20像素的小车,在模型输入里还是40x20吗?不是。YOLO的处理流程是先把原图等比缩放到长边640,这意味着如果原图是1920x1080的大图,车辆在输入图里就只剩13x6个像素,基本不可识别。而拉到1280,同样的车膨胀到27x13,特征就丰富许多,检测头有足够的信息去产生置信度。

单纯提高分辨率会带来计算量翻倍的问题,所以实际项目中我更常用的是滑窗切图策略,思路参考SAHI的做法。把大图切成若干个1024x1024的块,块与块之间保留20%的重叠率,每个块独立跑检测,最后把检测框映射回原图坐标,用置信度做跨块去重。这种方案看起来处理流程变长了,但在显存小于16G的消费级显卡上,是质量和性能的最佳平衡点。

letterbox也有讲究。Ultralytics默认的letterbox会把原图等比缩放到目标尺寸,剩下来的区域用114这个固定灰度值填充。对小目标来说,填充区域越多,目标被压缩得越小,所以训练和推理时都要尽量让输入宽高比接近数据集的真实分布。

我一般在训练前先做一次数据集的宽高比统计,看看绝大多数图片是16:9还是4:3,然后把imgsz设置成对应的1920x1080或1280x960,而不是盲目用正方形。这个细节看着小,却在好几个场景里直接让mAP涨了1.5个点。

2.2 数据增强:不只是开Mosaic那么简单

YOLO自带的数据增强管线很丰富,但对小目标车辆检测,默认配置远不够,有些地方甚至适得其反。

训练初期开Mosaic,效果是好的。四张图拼在一起,相当于人为制造了更多小目标样本,同时打乱了背景上下文,模型被迫去学目标本身而不是场景记忆。但训练最后10到20个epoch必须关掉Mosaic,原因在于Mosaic的随机缩放会把小目标压得更小,甚至缩到1x1像素,模型在增强后分布上反复迭代,反而学歪了。官方训练脚本里其实有这个开关,但很多人默认跑全程,导致小目标检测结果虚高而不实用。

针对小目标的专项增强,我强烈推荐“复制粘贴”策略——从训练集里把小目标车辆的像素块随机裁剪出来,粘贴到背景图的随机位置。这个手段在公开航拍数据集上特别有效,等于把小目标样本数量直接翻了几倍。我实测在某个公开车辆检测数据集上,用了这个增强后,小目标类别的AP从68跳到76以上。

需要注意两点:复制的目标周围要保留一定边缘像素作为上下文,不能裁太“干净”,否则模型学到的就是纯色块。粘贴的位置要避开原图已有的目标区域,防止同一位置出现完全重叠的注释框。

2.3 推理端的三件小事:多尺度、置信度补偿、加速

训练完之后,推理策略还能再榨出几个百分点的效果。

第一件是多尺度推理。同一张图分别用0.8、1.0、1.25三个缩放系数跑三遍,然后基于置信度做加权融合。多尺度推理对小目标效果很好,因为不同缩放尺度下,同一个目标的信息量不同,1.0尺度下可能只有4x4像素,但1.25尺度下就变成5x5,特征更充分一些。代价是推理时间乘以三,所以一般只在离线批量检测时用,实时场景我基本放弃。

第二件是置信度补偿。前面提过YOLOv10去掉NMS之后,小目标置信度普遍偏低。我在推理代码里做了一个分段补偿函数:小于150像素的目标,置信度阈值从0.25下调到0.12,同时要求连续三帧都出现才判定为有效目标。这个策略针对视频检测特别有用——一个真实车辆会连续出现在多帧里,而噪声点几乎不可能稳定跨帧出现。代价是增加了一帧延迟,但漏检率大幅下降。

第三件是推理加速。如果部署机器是NVIDIA显卡,优先用TensorRT的FP16推理,推理速度是PyTorch的3到5倍。CPU机器只能用ONNX Runtime的FP32或者INT8量化。这里的经验是,INT8量化小目标误检会增加不少,尤其是把栏杆、井盖、道路裂缝误判成车辆。要上量化,至少先准备好超过1000张的验证集来重新评估阈值。

3. 从环境准备到模型训练和部署的完整实操过程

3.1 环境配置清单与避坑

项目环境核心里就四样东西:Python、PyTorch、Ultralytics、PySide2。有的机器预装了很新的Python 3.12,但PySide2的版本只支持到Python 3.9,装到一半直接报找不到QT相关模块。稳妥的组合是Python 3.9.7 + PyTorch 2.1.0 + CUDA 11.8。

用conda建环境比较省心:

conda create -n yolov10 python=3.9 conda activate yolov10 pip install ultralytics pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install pyside2

如果只是推理,不训练,可以直接装onnxruntime,连PyTorch都可以省:

pip install onnxruntime ultralytics pyside2

还有个小坑值得说道——Windows机器上如果PySide2界面一旦切到视频实时推理就卡死,通常不是代码问题,而是把视频解码和推理放到了UI主线程。解决方案后面单独讲。

3.2 训练参数设置与显存计算

训练小目标车辆检测,我对参数档位的建议:先用8张以上带标注的公开数据集图片做一轮10个epoch的测试,确认loss能降下去,再上完整数据集正式训练。这样能快速发现数据标注格式错误、类别映射错位这类低级问题,不至于浪费十几个小时。

拿我常用的配置举例,数据集包含3万张图片,标注类别只保留四个:小轿车、SUV、卡车、公交车。默认的COCO 80类标注需要做一次类别筛选和映射,这一步最好在训练前的数据检查脚本里就完成,别指望训练时自动忽略,实测中有些类别的错误标签会直接拖累相邻类别的mAP。

训练指令参考:

yolo detect train model=yolov10x.pt data=my_dataset.yaml imgsz=1280 epochs=100 batch=8 lr0=0.003 close_mosaic=10 project=my_project name=exp

几个关键参数说明:

  • imgsz=1280:分辨率直接决定小目标上限,追求极端效果的可以上1920,但显存占用是四倍。
  • batch=8:单卡默认推理内存占用约10G,如果卡是24G的可以开到16,显存不够就开梯度累积。
  • lr0=0.003:比默认的0.01低,因为小目标数据集样本量不大,学习率太高容易在前期把特征学飞。
  • close_mosaic=10:最后10个epoch关掉Mosaic,让模型适配真实分布。

显存估算有个粗略公式:对于YOLOv10x,输入分辨率1280时,一张图大概占用2.1G显存(含梯度),batch=8就是17G左右;YOLOv10s在这个分辨率下单张约0.8G,batch=16约13G。如果你的卡是8G,建议直接上YOLOv10s + imgsz=640,或者用切图策略配合小batch。

3.3 模型导出与TensorRT加速

训练完的best.pt只是中间产品,真正部署时建议导出成ONNX或TensorRT引擎。YOLOv10官方仓库里自带导出脚本和命令行方式:

yolo export model=best.pt format=onnx dynamic=True imgsz=1280 yolo export model=best.pt format=engine half=True imgsz=1280 workspace=8

导出engine时workspace大小要跟推理显存匹配,默认设置有时候会直接OOM。注意导出的imgsz必须与训练一致,否则尺寸不匹配也会导致效果异常。

TensorRT部署时,我建议在检测输出后加一层自定义的后处理,把原始浮点输出转成有效的目标框和置信度。这个看起来麻烦,但可以避免在每次推理时重新跑一遍letterbox操作,速度能再快10%。这几个优化做完,1080p视频流在RTX 3060上可以跑到接近38FPS,差不多能支撑准实时监控。

3.4 UI界面:从命令行工具到可双击运行的小软件

命令行版本的说实话很多同行也能写,真正让人愿意用的还得是有界面的。我的UI界面用PySide2,功能模块按三块设计:左侧配置面板,中间显示区域,右侧结果列表。

配置面板里有几个控件要注意设计细节。置信度滑杆的范围设在0.05到0.60,步长0.01,能应对不同场景下的灵敏度调整。NMS滑杆的默认值是0.35,这个值在小目标场景不宜超过0.6,不然密集区域的框会被错误合并。类别过滤用下拉多选框,方便只关注某种车型。

中间显示区域核心是QLabel,直接显示QPixmap。视频推理部分则需要用QThread把耗时操作放到子线程,避免UI主线程因推理卡顿导致界面无响应。

UI里的一个隐藏功能是检测结果存CSV。每一帧或每张图片的检测结果,都会附带车辆类别、坐标框、置信度三个字段追加到CSV里。实际项目里客户要的往往不是检测框,而是车流量统计表,这个导出功能帮了大忙。

关键代码逻辑可以看这一段,展示了如何调用推理模块并更新UI:

# 推理子线程中调用 class Worker(QThread): frame_done = pyqtSignal(int, list) def __init__(self, det, source): super().__init__() self.det = det self.source = source def run(self): cap = cv2.VideoCapture(self.source) idx = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break boxes = self.det.predict(frame) self.frame_done.emit(idx, boxes) idx += 1 cap.release() # 主线程中接收检测结果并绘制 def on_frame_result(self, idx, boxes): frame = self.frames[idx] for cls, conf, xyxy in boxes: cv2.rectangle(frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0, 255, 0), 2) label_map = {0: "car", 1: "suv", 2: "truck", 3: "bus"} label = label_map.get(cls, "unknown") cv2.putText(frame, f"{label} {conf:.2f}", (int(xyxy[0]), int(xyxy[1] - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) self.update_display(frame)

4. 踩坑记录:小目标车辆检测的典型问题与解决过程

4.1 小目标漏检率高到离谱

最常遇到的情况是,经过训练的模型对近处车辆检测效果非常好,对画面远端只有几十像素的车完全无动于衷。排查方向几乎可以锁定三类原因。

第一类是输入分辨率不够。先看推理时实际输入到网络里的图,如果小目标只有个位数像素,直接在预处理阶段就消失了,再怎么调后处理都没用。解决方法是统一把推理尺寸调整到1280或切图。

第二类是训练配置错误。有些人为了追求训练速度把imgsz设成640,导致在训练阶段模型就“看不到”小目标,后面推理时换1280反而让模型困惑。训练和推理的观测尺寸必须一致,这是底线。

第三类是类别样本极度不平衡。小汽车样本几百个框,但SUV样本有几十万个框,模型自然会偏向学大目标。解决办法是把数量多的类别复制一部分用于后期,或者用小目标数据做重采样。我建议先画出各个类别在不同尺寸下的框数量分布图——搞一套可视化统计脚本非常值得,能快速定位数据层面的问题。

4.2 误检居高不下:道路标线、井盖、阴影都在报“车”

小目标模型常见的误检来源是深色路面上的井盖、白线箭头、树木阴影。它们和远距离小轿车的视觉特征高度相似,都是深色斑块。

我处理这个问题的办法是“负样本挖掘”:把实时视频里的背景帧抽出来,标注成空背景类别,加进训练集。每个背景样本标注成“无目标”,模型学完这一批数据后,背景特化的误检会被压制。这个方法立竿见影,缺点是会增加训练集规模,大约多20%的数据量。

还可以用时间域后处理:视频场景中车辆必然在运动,检测框若在连续多帧中位置完全静止,大概率是井盖或阴影。在推理代码里加一个“运动一致性”检查,判断连续帧的检测框中心位置变化小于阈值,就剔除。这对静态误检特别有效,而且不需要重新训练。

4.3 训练Loss不下降或直接输出NaN

遇到loss不下降,先排查学习率。小目标数据往往样本数量少,学习率过大很常见。我自己的经验是,lrf(最终学习率因子)要设置得很小,比如0.01,否则后期loss在底部反复震荡。

NaN问题十有八九跟数据预处理有关。如果数据集中有全黑或全白的图片,像素方差为0,归一化之后就变成NaN,模型喂了一张NaN图片进来直接崩。在数据检查脚本里加一条统计逻辑:像素标准差小于阈值的图片直接排除,这个零成本排查能省下大量时间。

4.4 UI界面卡死与推理速度瓶颈

视频检测时界面不响应、进度条不走,基本可以断定是推理放在了主线程。PySide2的while循环跑着同步推理,事件循环堵死,窗口只能画一个“无响应”的状态。正确的处理方式是UI线程只负责信号交互和绘制,把每一帧的检测放到QThread里去。

推理速度方面,推荐几个顺序:先用ONNX Runtime替换PyTorch推理,能快一倍;再上TensorRT FP16,又是三倍提升;最后如果目标机上显卡太弱,建议把输入降到960,并用切图+多线程并行。最关键的还是要明确目标,实时场景严禁做多尺度推理,那是离线批量检测才用得上的策略。

基于实操的几点个人体会

聊到最后,分享几点我反复在不同项目里验证过的体会。不要迷信最新模型,YOLOv10的NMS-free设计确实强,但训练数据质量仍然是最重要的影响因素,我用一批标注质量高的数据在YOLOv5上跑出的结果,把同数据在YOLOv10上的效果都能逼到相当接近的水平。小目标检测里,预处理和推理策略的收益往往比换模型架构提升更明显,至少对我常用的场景而言,切图和增强带来的收益是实打实的。

如果手里的项目也在做小目标检测,先把imgsz确认好,没有1280的原因,就别碰任何后续的调参。后续想继续扩展的话,切图策略和TensorRT加速这两块可以一起用上,批处理场景的吞吐量还能再上一个台阶。

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

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

立即咨询