1. 项目立意与整体设计思路
1.1 为什么选择YOLOv8-P6而不是标准P5模型
先把这个项目最初的选择逻辑摊开讲。多品牌车型识别这个任务,第一眼看过去和我之前做过的通用目标检测没什么两样:输入一张图,输出若干个框和对应的类别。但真正把需求理清楚之后就会发现,通用检测里很少遇到的问题,在这个场景下全冒出来了。
最头疼的是车辆形态差异。不同品牌的不同款式,车身尺寸差异非常大。一张监控画面里,可能同时出现占了大半个画面的公交车、中等尺寸的轿车、被遮挡得只剩半个尾巴的两厢车。如果只用标准的YOLOv8-P5模型,输入分辨率拉到640,有些远场景里的小车可能只有20到30个像素高,特征图在这一层已经退化得很厉害,检测器根本学不到有判别力的纹理信息。我试过用更大输入尺寸去硬扛,分辨率到1280之后显存直接爆炸,训练速度也慢得没法接受。
P6模型相比P5多了一次下采样层,加深了整个特征金字塔的深度。这让检测器在保留中等尺寸和大尺寸目标召回能力的同时,对小目标的响应明显更敏感。具体点说,P5结构最终的检测头特征图是原始输入的1/32,而P6结构可以输出到1/64,对于小目标的感知跨度更大。但在车型识别场景里,我不需要检测到20像素以下的目标吗?也不是,我还要解决另一个问题——当车辆在画面里被压缩得很小时,它的品牌特征是混沌的,人和模型都难以区分细节。所以P6的真正价值,不是彻底解决小目标问题,而是让中等偏小的目标也能把特征传到更深的检测头里去。
另一个现实原因是部署约束。我手头需要跑推理的设备是一台边缘盒子,算力有限,TensorRT优化前P6模型的单帧推理耗时比P5高了大约百分之三十。如果纯粹追求精度,完全可以上更大规模的模型,但在功耗和实时性约束内,P6是精度和延迟平衡得比较好的选择。
说到底,P6的选择不是因为它“新”,而是因为它恰好覆盖了这个任务里最难的那些场景尺寸分布。
1.2 多品牌车型识别场景的边界与难点
车型识别这个任务,和常见的车辆检测有本质区别。车辆检测只需要把“车”这个框出来,做一个二分类的活儿;车型识别却是同时对“车在哪里”和“这是哪个品牌哪个型号”两个问题做判断。后排的类别空间相比二分类要大得多,而且类别之间不是互斥的:同平台的不同品牌车型,外观上可能只差中网轮廓和大灯线条;不同年份的同款车型,尾灯形状又完全不一样。
做一个现实点判断:在通用公开数据集上能跑出高mAP的模型,放到实际场景里未必好用。公开数据集里的车辆图片大多是购物网站、媒体库里的干净图片,拍摄角度固定,背景干扰少;而我实际遇到的监控画面,有逆光、夜间噪点、雨滴、树影遮挡,还有车辆刚驶过画面边缘只剩一个角的极端情况。这些都是公开数据集很难覆盖的样本分布。
这个项目的目标也明确:对特定区域常见的若干主流品牌(抽象成A、B、C三类品牌,每个品牌下分若干款式)做检测加分类,场景覆盖停车场出入口、高杆监控视角、以及路面侧视角。这三个视角带来的图像形态差异非常大,出入口视角车辆多为正面或背面近景,高杆视角车辆顶部和前后端面占比高,路面侧视角车辆是侧脸。如果只用单一视角的数据训练,换个视角就崩,这是数据层面要先解决的问题。
所以我在立项时就定了三条原则。第一,数据必须以实际场景采集为主,公开数据集只能做辅助预训练。第二,类别的粒度控制在“品牌加款式”级别,不追求到具体年份,因为年份判断需要非常丰富的细节,真实场景数据不足的情况下强行分类只会拉垮整体精度。第三,全流程围绕边缘部署优化,不能在实验室精度上自嗨。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据工作:模型精度的真正分水岭
2.1 数据采集来源与去重清洗
我见过不少项目死在数据上:模型结构换了一版又一版,结果瓶颈根本不在结构,而是训练数据的分布和实际场景对不上。这个项目从开始就把数据抓在手里,花的时间比模型调参还多,但事后证明是完全值得的。
数据来源主要有三块。第一块是停车场出入口设备的历史抓拍图,这些图大多是200万像素的枪机拍的,画面里车辆占比较大,能明显看到车牌和车头细节。第二块是高杆监控的视频抽帧,这个视角车辆面积小、重叠多、角度变化大。第三块是用手持相机模拟路面侧视角拍的照片,尽可能覆盖不同光线时段。
这三块数据加起来,初步筛选后大约有四万多张有效图片。但光有数量还不够,还得做清洗。我遇到的第一个问题是重复数据:同一辆车连续十几帧画面几乎一样,如果不处理,训练集会严重偏向这些重复样本。解决方法很直接,按图像哈希去重加人工抽查,把连续帧的间隔拉大,抽帧间隔设为10到15帧,既保留运动连续性又减少冗余。
第二个问题是标注一致性,这个放到下一节说。
再补一个别处很难看到的细节:数据里的车型类别分布严重不均衡。常见车型可能占了一半以上的样本,冷门车型可能一张图都没有。我用一个很土但有效的方法——计算每类的标注框数量,做了一个可视化图表,发现有个款式的样本量是另一款的三十倍以上。如果不做处理,模型对冷门类别的召回率会非常难看。
处理方式不是简单粗暴地删数据,而是采取分层过采样策略。训练时候对样本量少的类别提高采样权重,同时配合后文的增强策略来扩充样本量。但要注意,过采样太猛会让模型过拟合到这些少数类别的特定场景,所以权重不能拉太高。我最后用的权重范围是1.2到1.8,效果比不打权重提升了大概两个点以上的召回率。
2.2 标注规范与质量审查
标注规范这件事,很多项目都写着“见标注文档”,但实际执行的时候往往一团乱。这个项目的标注规范是我自己先定稿,再培训标注人员,然后我逐批审核,过程比较长,但非常必要。
最核心的一个规范是:什么样的情况允许标,什么样的情况不标。比如车辆出画面只剩不到三分之一的部分,是否标记?目标是场景内有效识别,所以车辆轮廓超过画面边界且可见部分不足二分之一时,默认不标。另一条规范是:被强遮挡的车辆,如果可见部分不足以支撑品牌判断,类标签只标记为“vehicle”而不标记具体品牌。这样的好处是限制了错误标签对模型训练造成的污染。
还有一个细节:所有类别的框统一为整车包围框,不标局部。部分标注人员习惯标车头或者标车尾,这在检测任务里会造成严重的尺度不一致,影响很大。
质量审查的方法,我推荐用“交叉验证标注法”:每个图像都由不同的人标两轮,第一轮初标,第二轮复核。复核时的重点不是看框得对不对,而是看有没有漏框和类标错误。在实际审核中,我发现了几个频发的低级错误:把车尾灯的伪装贴纸阴影当成车身一部分框进去、把远处公交车的“品牌”误标成“vehicle”、把同品牌不同款式的车混淆。这些错误如果混进训练集,模型的边界就会模糊。
审核完成后,还有一个反直觉的操作,我把一部分低置信度标注数据直接扔掉,不要“宽进宽出”。宁可数据少一些,也要保证标签干净。有个阶段我砍掉了百分之八的标注数据,表面看数量减少了,但模型最终精度反而上升了。标签质量优先于数量,这在细粒度分类场景里体现得非常明显。
3. 模型训练配置与优化策略实录
3.1 从预训练权重起步与迁移学习细节
我之前做过对比实验:随机初始化和从COCO预训练权重起步,同样是训练一百个epoch,后者收敛速度快了接近一半,最终精度高了五到七个百分点。原因很好理解——COCO数据里包含大量车辆实例,模型底层的边缘、纹理、颜色特征都已经具备一定泛化能力,迁移到具体品牌分类任务时,只需要在上层特征上做适应,不用从头学基础特征。
在模型具体配置上,我调整了输入分辨率。P6模型默认可以接受多种分辨率输入,结合实际场景的车辆尺寸,我选择了1280作为训练分辨率。原因是输入太小会丢失车型细节,输入太大又影响训练速度。1280相对常用,在显存占用和精度之间是个比较均衡的选择。
训练阶段用到了几种数据增强策略,包括mosaic、旋转、平移、色彩扰动、HSV增强等。mosaic增强对车型场景的效果明显——因为监控画面里的车辆经常互相遮挡,mosaic把多张图拼在一起,等于强迫模型适应这种局部上下文。我特别把mosaic的概率调高到了1.0,前10个epoch用,之后逐步降下来。
不过mosaic也有坑,它会让框的坐标分布发生偏移,尤其是当拼图里有大目标时,目标可能被裁剪到只剩局部。这种局部目标对应标注框里的部分信息不足问题,会导致模型训练初期损失震荡。我的解决方式是前5个epoch关闭mosaic,只做基本增强,让模型先稳定适配目标尺寸分布,再开启mosaic。
EMA(指数移动平均)也值得单独提一下。它在训练过程中对权重做了平滑,避免权重在最优解附近来回震荡。用YOLOv8自带的配置,我设置了衰减系数0.9999,在验证集上获得了稳定的精度提升,大概一到两个点左右。
3.2 超参数选择过程与Loss观测方法
超参数优化这块,我用的是“经验基准加短周期验证”的路子,而不是无脑搜索。因为训练一次P6模型的时间成本偏高,在这个项目里跑一次完整训练大概需要十几个小时,做大规模搜索不现实。
首先是batch size。边缘盒子推理时batch size固定为1,但训练阶段我用batch size 16配合梯度累积模拟更大的batch,把学习率对应提升。初始学习率我设为0.01,并加了warmup策略,前3个epoch从0.0001线性升到0.01。这个warmup很重要,能避免模型开局就学偏。
权重衰减设为0.0005,这个数值是我之前跑检测任务的习惯值,在公开数据集上用起来稳定。测试后发现在这个项目的场景数据上也没有出现严重的过拟合现象,说明数据量和增强策略都还算到位。
关于Loss曲线的观测,我会同时盯着三条曲线:边界框损失、分类损失、分布焦点损失。这三个损失在训练初期都有波动,但到了第30个epoch左右会进入平稳下降阶段。这时候最需要警惕的是分类损失和边界框损失出现背离:分类损失下降缓慢但边界框损失已经收敛,说明模型在“能定位但不能识别”,通常和类别内差异大有关系,需要检查是不是某些类别的样本量太少了。
我在实际训练中还跑过一次完整的消融实验,对比了开启关闭EMA、不同输入分辨率、不同增强策略的组合,大概拆了十几个配置组合。这个实验的价值不在于找出某一个最优配置,而是明白了“同一套超参数在不同数据分布下表现完全可能两极分化”。拿一个比较极端的例子,把mosaic概率提升到1.0在公开数据集上很有效,在我们的出入口数据上反而掉了点,原因是大量图片里车辆占比较大,拼图时经常把车截断,反而引入了不自然的遮挡形态。这类经验用文字记录下来的意义大于所谓的最优参数本身。
4. 推理部署与工程化加速实践
4.1 从PyTorch到TensorRT的模型导出与量化
训练完的模型只是半成品,真正能上生产线的还得是推理引擎优化后的版本。我这里的方案是PyTorch导出ONNX,再转到TensorRT做FP16和INT8量化。
先说导出阶段容易踩的一个坑。YOLOv8模型的输出头格式比老版本更复杂,导出ONNX时如果不对输出做简化,后续在TensorRT里解析会非常费力。我习惯的做法是在导出前把检测头的解码逻辑剥离掉,只保留原始特征图输出,后处理的解码放到TensorRT之外用语言运行时完成。这样导出结构简洁,后续量化适配也更稳。
TensorRT转换这一步比较关键的是动态shape配置。监控视频分辨率虽固定,但为了适配不同摄像头,我设定了最小和最大输入尺寸范围。固定shape当然性能更好,但实际部署时不够灵活,动态shape带来的推理性能损耗大约在百分之五以内,可以接受。
量化校准数据集的选择很讲究。我用的是从训练集里随机挑选的两千张图片,覆盖不同时段、不同光照、不同车型。校准数据集的数量不宜太少,少于五百张的话量化后的精度损失会明显;也不宜太多,太多只是徒增转换时间。
FP16量化在这个项目里表现不错,精度损失几乎可以忽略,大概在百分之零点几的范围,但显存占用直接减半,推理速度提升明显。INT8量化进一步提升速度,但精度损失开始显现,特别是对于一些夜间低照度的图像,模型输出置信度整体会有小幅下降。我最后实际部署的时候用了FP16,在速度和精度之间取了平衡。如果后续有更高帧率需求,再考虑用INT8加针对性的校准数据微调。
另外,TensorRT转换完以后一定要做一次精度对比测试。做法是把同一批图片分别在PyTorch模型和TensorRT引擎上跑,逐图对比输出的类别和置信度。如果发现TensorRT版本大量结果与PyTorch不一致,多半是量化校准数据出了问题,而不是模型本身的问题,这时候优先检查校准集分布是否和实际场景偏差太大。
4.2 视频流处理与业务场景结合
模型只是识别引擎,要实现完整的业务逻辑,还得套一层调度逻辑。我这边部署的流程是:先从视频流抽帧,抽帧频率不是固定的,因为不同场景的车辆驶过速度差异很大。停车场出入口通常车速较慢,五帧每秒的采样率足够;高杆监控视角车辆速度快,十帧每秒才不会漏车。这个参数我是在实际跑了一周之后根据捕获的回放视频统计出来的,而不是拍脑袋定的。
抽帧后进入模型推理,推理结果传给业务层做后续处理。这里有一个容易被忽略的点:模型输出的置信度分数后处理策略也会影响业务效果。我遇到的情况是,单一阈值没法同时适配白天和黑夜两个场景。白天光线好,模型置信度普遍偏高;晚上噪点大,同一个场景的置信度会整体下降。如果用固定阈值,会导致夜间漏检偏多,白天又可能出现误检。
针对这个问题,我换成了“时间段动态阈值”:白天置信度阈值设为0.5,晚上动态降到0.35。实际上,这个设计实现起来非常简单,只是判断一下当前时间,选择不同的阈值参数,但效果提升是实打实的。
还有一个和业务结合的细节是“多帧确认”机制。单帧检测结果容易出现突跳和误报,比如一辆车的尾灯在某一帧被错误识别成另一款车型。我实现的逻辑是:连续三帧内,至少两帧检测到同一个ID的同一个类别,才认为确认结果有效。这个机制用极小的延迟换来了稳定性的大幅提升。
在推理延迟表现上,优化后TensorRT引擎跑1280分辨率单帧耗时约50到60毫秒,边缘盒子CPU负载保持稳定,内存占用也没有异常上涨。算下来单路视频流大约可以跑到十五帧每秒左右,满足出入口监控的实时性要求。
如果说这个项目里最不容易被看见但影响又最大的决定,大概就是“先在数据上砸足够多时间,再进模型训练和部署优化”。很多工程师拿到项目后第一反应是调网络结构,但我这边的经验是,结构的收益微乎其微,真正拉开差距的往往是数据质量、标签一致性、阈值策略这些看似不太“前沿”的工作。
5. 训练中的常见问题与排查技巧实录
5.1 训练早期Loss震荡与梯度爆炸
第一次用P6模型跑这批数据时,我遇到了典型的训练早期异常:Loss曲线在前十个epoch剧烈波动,一度出现陡增到基础值十倍以上的情况。排查这个问题的过程,可以拿出来分享一下。
首先怀疑的是学习率设置。检查之后发现warmup写错了位置,导致模型前几个epoch直接用0.01的学习率起步,激活值变化幅度剧烈,梯度也跟着放大。修正为线性warmup之后,情况有明显改善,但并不是完全消失。
接着怀疑的是数据问题。我抽出了Loss最高那些iteration对应的图像来看,发现这批图里大量包含夜间强光下的车灯眩光画面,像素值分布极端,模型在低光照和高亮区域的激活差异极大。针对这个情况,我在数据增强管线里加强了光照抖动和噪声模拟,给模型提供多样化的照明条件,Loss的波动才真正降下来。
所以排查的思路其实是:先看超参,再看数据,最后才怀疑模型结构。成本最低的检查顺序往往能节约最多时间。
5.2 容易混淆的类别与分类边界优化
我这边最严重的混淆现象出现在两个外形极其接近的车型之间,它们在监控视角下几乎只差中网进气格栅的镀铬条数量。这个类别对即使是人也需要凑近才能分辨,让模型仅从像素层面做区分,难度自然不低。
针对这类混淆,我用了一个比较朴素但有效的策略:对混淆对做了针对性数据增强,把这两类的训练图旋转、裁剪、加噪,强制模型去学习更微小的差异特征。同时,我调整了分类损失里对难例样本的权重,让这些易混淆类别在Loss中的贡献更大一点,模型会投入更多“精力”去学它们的边界。
实际跑下来,这两个类别的Top-1识别准确率提升了大概四个百分点。虽然还不是完美,但在真实场景里配合多帧确认,已经不会出现影响业务的频繁误判了。
再补充一个可视化分析手段。我会把模型的卷积特征图做激活热力图可视化,挑一批分类错误的图看模型到底聚焦在车的哪个部位。很多错误聚焦在车标和中网附近,这说明模型确实在学习有判别力的区域,方向是对的,剩下的是细节分辨能力的问题。
另外还有一个容易被忽略但影响很大的问题:检测框的不一致切割。模型对同一辆车在连续帧里输出的框大小会有轻微抖动,有时候框稍微上移一点,把车顶的行车架也包含了进去;有时候框缩小,把车头灯切掉一半。这种框的不一致性会给分类结果带来噪声。处理思路是:推理阶段做框的时序平滑,以相邻帧的检测框中心点为基准,对框尺寸和位置做加权平均。这个操作不需要回传模型,纯粹在业务层解决,但效果很明显,连续帧里分类结果的稳定性提升了一大截。
6. 从我这个项目的实际经验中,再分享几点零碎但好用的东西
训练P6模型时,我把“YOLOv8-P6”这个关键词在心里反复拆过好几遍。P代表的是pyramid,也就是特征金字塔的层数深度,6是最终特征图的缩放比例。这个模型在尺度感知上的优点,放在多品牌车型识别场景里非常切中要害——因为车型识别的核心难点本来就是“同一辆车在不同距离下的表现差异巨大,但我们要让模型认为它是同一个东西”。
数据增强方面,我强烈建议不要照搬开源库里的默认配置。mosaic这类增强在公开数据上表现好,不代表在自己的场景里就一定好。我自己就在夜间数据上踩了坑:mosaic拼图时,四张图的光照条件完全不一样,模型在同一个特征位置上的输入统计特征剧烈变化,反而导致收敛变慢。做任何增强之前,先在自己的数据上做视觉检查,看看增强后的图像是否符合真实的业务场景逻辑。
关于标签体系,我个人建议用“品牌+款式”的层级结构,而不要平铺成一个大类列表。虽然最终的分类输出还是单个label,但在标注和审核阶段,按层级去组织标注文档,会更容易发现错误。比如“A品牌—X款”如果被误标成“B品牌—Y款”,这种错误在平铺结构里很难一眼看出来,但在层级结构里就暴露得很明显,因为大品牌和款型都对不上。
最后,关于多品牌车型识别这个任务,我的体感是它属于典型的“看起来很标准,做起来全是细节”的领域。模型结构是成熟的,推理框架是现成的,真正的技术含量其实藏在那些不显眼的决定里:数据怎么清洗、标签怎么定义、阈值怎么调、多帧怎么确认。这些细节往大了说,决定了整个系统能不能落地,往小了说,决定了工程团队后面要花多少时间在救火上。把一个项目做稳定,靠的不是某一个灵光一现的优化,而是在每一个环节都反复验证和打磨的耐心。
