ARTICLE DETAIL

资讯详情

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

YOLOv7缺陷检测全流程实战:从数据标注到RK3588部署

YOLOv7缺陷检测全流程实战:从数据标注到RK3588部署 上个月我帮朋友把一个YOLOv7缺陷检测项目从头到尾过了一遍从一堆没整理过的手机照片到最终在RK3588板子上以接近30fps的速度跑实时推理整个过程大概花了两周。今天把这条“数据标注→模型训练→模型优化→部署落地”的完整链路拆开聊聊包括我在每个环节做的决策、踩过的坑、以及最后验证过的参数配置。这篇内容适合三类人看一是刚接触目标检测、想系统走一遍全流程的学生二是已经在用YOLO做项目但部署环节总出问题的一线工程师三是准备把检测模型搬到RK3588、Jetson Orin这类边缘设备上的朋友。我会尽量少讲空话多给可以直接抄作业的步骤和配置。1. 项目开工前我做的三个决策模型选型、流程拆分和评价标准1.1 为什么在2025年还选YOLOv7说实话YOLOv8、YOLOv9甚至YOLO11都已经比较成熟了我依然在这个项目里选了YOLOv7并不是因为情怀而是综合考量了三个很现实的因素。第一精度与速度的平衡。YOLOv7发布时主打的就是在不显著增加推理成本的前提下提升精度它提出的E-ELAN结构Extended Efficient Layer Aggregation Network通过扩展、洗牌、合并基数来控制梯度路径让网络在参数量相近时能学到更丰富的特征。我的实际感受是在相同输入尺寸下YOLOv7的mAP通常比YOLOv5同量级模型高2到3个点推理速度又比YOLOv8的大模型快不少。对于工业检测这种既要漏检率低、又要实时响应的场景这个平衡点很关键。第二部署生态的成熟度。目标检测项目真正花费时间的往往不是训练一个模型而是把它部署到实际硬件上。YOLOv7已经在TensorRT、RKNN、OpenVINO等推理平台上被反复验证过网上能找到大量算子兼容性和性能调优的案例。相比之下更新版本的YOLO在某些边缘设备上的算子支持还没那么完善遇到不支持的算子时排错成本会很高。第三社区积累的优化方案多。无论是知识蒸馏、通道剪枝还是量化感知训练YOLOv7都有相对成熟的脚本和教程。这对后面做模型优化非常重要。所以我的意见是如果项目不是为了学术先进性而是为了稳定落地YOLOv7仍然是一个非常可靠的选择。1.2 把全流程拆成四个里程碑全流程听起来很长但管理起来并不复杂。我习惯按照产出物把它拆成四个阶段每个阶段有明确的完成标志和质检标准。数据阶段从原始图片到可训练的标准数据集。产出物是标注好的txt文件、类别文件、train/val划分列表。这个阶段的完成标志是抽检标注准确率超过95%且每个类别样本数分布合理。训练阶段从数据集到一组最优权重。产出物是训练日志、验证集指标、best.pt。完成标志是mAP0.5达到项目基线且混淆矩阵中没有明显的类别相互误判。优化阶段从权重到轻量化模型。产出物是剪枝或蒸馏后的weights、量化校准后的模型。完成标志是模型大小和推理速度满足目标平台要求精度损失可控。部署阶段从模型文件到可运行的推理接口。产出物是ONNX/TensorRT/RKNN格式的模型、前后处理代码、性能测试报告。完成标志是端到端延迟达标多路视频流稳定运行。我这次项目的时间分配是数据阶段6天、训练阶段3天、优化阶段2天、部署阶段3天。数据阶段占比最高这点后面会谈为什么。1.3 先定评价指标再谈技术方案很多朋友一上来就只盯着mAP这个习惯在真实项目里会吃亏。我接手这个项目时先和技术负责人对齐了一个结论mAP是训练阶段的指标部署阶段更关心的是端到端延迟、漏检率和误检率。以我这次做的工业零件缺陷检测为例客户明确要了两个指标漏检率低于1%单帧处理时间低于35ms。mAP只是作为训练阶段的中间监控指标真正决定项目是否验收的是这两个实际运行指标。所以我从头到尾都保持了一个习惯每训练一个模型不管mAP多高都先跑一遍完整的前处理推理后处理验证流程确认端到端效果。这个习惯在后面对齐ONNX输出时帮了大忙。2. 数据标注这关从原始照片到标准数据集的全过程2.1 数据清洗比想象中重要很多教程会默认你手里的数据已经是“干净”的但实际项目中基本不可能。我的数据是从现场产线收集的包括三种型号的零件每种型号约1200张图总共3600多张。拿到手的第一件事不是标注而是清洗。我清洗掉的图片主要有几类严重过曝或欠曝导致无法辨认的、运动模糊导致目标轮廓不清的、前后帧几乎重复的、以及完全拍错对象比如拍到工人手指而不是零件。清洗前后对比可用图片从3600多张降到了3200张左右。可别小看这一步模糊和错误的图片进入训练集会让模型在学习阶段产生很大噪音后期排查精度问题时根本不知道是标注错了还是训练参数的问题。顺便提一句数据标注实训里经常讲的“3D点云标注”是另一个完全不同的技术路线主要面向自动驾驶、机器人抓取这种需要深度信息的场景。如果你做的是2D图像检测任务只要先把2D标注的质量做扎实就行不用被那些高级概念带偏节奏。2.2 标注工具选型我从LabelImg切到了Label Studio市面上标注工具不少我整理了一下主要选项按项目规模来选。工具适合场景优点缺点LabelImg个人实验、少量图片轻量、无需部署、导出VOC/COCO方便无协作、标签管理弱、不能直接预标注Label Studio需多人协作的中等规模项目网页版、支持AI预标注、可配置QA流程服务器部署和维护有学习成本X-AnyLabeling需要半自动标注的场景支持加载YOLO模型预标注速度快对复杂遮挡场景预标注结果要仔细检查这次项目我最终用的是Label Studio。我们的标注是先由三名标注员分别做初标再由我抽检第二轮。Label Studio的多用户管理和标注任务分配功能帮了大忙。如果你只是自己一个人做几百张图的测试验证LabelImg完全够用别忘了两种工具导出的格式最终都要转成YOLO训练格式。2.3 标注规范怎么框才能让模型学得好标注规范是数据阶段最容易出问题的地方。我定义了几条硬性规则要求所有标注员无脑执行外接矩形必须紧贴目标可见轮廓上下左右尽量不要留超过目标宽高5%的背景。留白太多模型会学到“目标周围有背景”的错误模式。目标被部分遮挡时只要可见部分超过整体面积的70%就按完整目标的理想外接框标注可见部分在30%到70%之间按可见部分的外接框标注低于30%直接不标。这样做的目的是避免模型产生“半个人影但其实是完整目标”的语义混乱。边界模糊的目标比如反光很强的零件边缘以标注员一致认为的可见边缘为准不允许凭想象补齐轮廓。每张图由两名标注员独立标注第一遍如果用Label Studio的重复标注功能对比两个框的IoU低于0.85就认定为疑似问题框需要三人复核。这些规则看似简单但执行好之后训练时的损失曲线会明显更稳定。我对比过规范标注的数据配置训练到第100轮时的mAP比“自由发挥”标注的数据高将近5个点后期怎么调参都补不回来。2.4 标注格式转换与数据集划分Label Studio默认导出为JSON格式而YOLOv7训练需要的是每张图片对应一个同名txt文件内容格式是“class_id x_center y_center width height”坐标全部归一化到0到1。我写了一个Python脚本来完成转换。import json import os # 读取Label Studio导出的JSON with open(label_studio_export.json, r, encodingutf-8) as f: data json.load(f) # 类别映射表按项目实际类别修改 class_map {defect_type_a: 0, defect_type_b: 1, defect_type_c: 2} os.makedirs(yolo_labels, exist_okTrue) for item in data: # item包含图片路径与标注结果 image_name os.path.basename(item[image_path]) base_name os.path.splitext(image_name)[0] img_w item[width] img_h item[height] txt_path os.path.join(yolo_labels, base_name .txt) with open(txt_path, w) as out_f: for annotation in item.get(annotations, []): for box in annotation.get(result, []): if box.get(type) ! rectanglelabels: continue value box[value] class_label value[rectanglelabels][0] # 注意Label Studio的坐标是左上角xy与宽高 x value[x] / 100.0 * img_w y value[y] / 100.0 * img_h w value[width] / 100.0 * img_w h value[height] / 100.0 * img_h # 转换为YOLO的cx, cy, w, h归一化格式 cx (x w / 2) / img_w cy (y h / 2) / img_h w_norm w / img_w h_norm h / img_h out_f.write(f{class_map[class_label]} {cx:.6f} {cy:.6f} {w_norm:.6f} {h_norm:.6f}\n)转换完之后还要做数据集划分。我习惯按7:2:1划分训练集、验证集、测试集并且用固定的随机种子保证每次实验的可复现性。还有一个容易忽略的点划分时最好按“文件夹或视频片段”为单位隔离而不是把单帧随机散开。否则同一个场景的连续帧会同时出现在训练集和验证集里评估出来的指标会虚高部署到新场景后精度立刻掉下来。3. 训练与调优被忽略的细节决定了模型最终上限3.1 环境搭建与官方仓库的坑YOLOv7官方仓库的依赖不算复杂PyTorch 1.12以上基本都能跑。我的建议是直接用Anaconda建一个干净环境Python版本3.9左右PyTorch选择与你显卡驱动匹配的版本。我用的是RTX 3090环境配置如下conda create -n yolov7 python3.9 conda activate yolov7 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118然后从GitHub拉取YOLOv7源码安装依赖git clone https://github.com/WongKinYiu/yolov7.git cd yolov7 pip install -r requirements.txt官方仓库里有个比较隐蔽的坑train.py默认读取的类别数来自你传入的数据配置文件如果你忘了修改data/custom.yaml里的nc类别数量和names类别名称训练会在加载数据集时报错或者更严重——不报错但类别名和权重文件对不上导致训练结果完全混乱。我的建议是任何项目都先建一个独立的数据配置文件而不是在别人的yaml上随手改。# data/custom.yaml train: /path/to/train.txt val: /path/to/val.txt nc: 3 names: [defect_type_a, defect_type_b, defect_type_c]3.2 训练超参数设置照着抄也要理解为什么YOLOv7的训练入口是train.py我第一次跑用了一套相对保守但有效的参数。这里直接给出我的配置和理由。python train.py \ --workers 8 \ --device 0 \ --batch-size 16 \ --img 640 640 \ --data data/custom.yaml \ --cfg cfg/training/yolov7.yaml \ --weights yolov7.pt \ --name defect_detection \ --epochs 150 \ --hyp data/hyp.scratch.pbatch-size设为16是因为3090的24G显存跑YOLOv7 640分辨率批量16是稳定不会OOM的值。显存不够就降到8不要为了强行撑大批次降低图片分辨率这会直接影响小目标的检出能力。--hyp data/hyp.scratch.p 是YOLOv7官方推荐的一套超参数。学习率初始值0.01配合它的cosine退火策略前期收敛快、后期稳定。我不建议新手一开始就自定义超参先跑通再调。--weights yolov7.pt 是官方的COCO预训练权重。从预训练权重开始训练通常能少花两倍时间而且在目标检测这种域迁移任务里预训练权重学到的基础特征边缘、纹理、形状绝大部分可以复用到你的自定义任务中。3.3 训练过程中的关键信号怎么判断模型在变好训练过程中我习惯每20轮记录一次指标重点关注三条曲线box_loss、obj_loss、cls_loss以及验证集上的mAP0.5和mAP0.5:0.95。我的经验判断是前20轮loss下降幅度大很正常如果30轮后box_loss还在震荡不降优先怀疑学习率太大或者标注数据里有错误框。mAP0.5达到0.95以上时mAP0.5:0.95通常还比较低只有0.6到0.7左右。这时模型对大目标检测已经不错但对小目标或边界框精度还欠火候。如果训练集loss一直降但验证集loss不降甚至回升说明过拟合了。处理办法不是盲目加数据增强而是先检查验证集是否和训练集有同场景重叠。我之前遇到过一次最终发现是数据集划分没按场景隔离导致验证集指标虚高。训练中如果爆显存OOM最快的方式是把batch-size减半同时把workers调低。如果再爆再检查一下你是否有其他程序占据显存。3.4 精度不够时的两条体验过的优化路线如果模型在验证集上mAP达标但在实际场景中漏检率偏高我不会立刻调网络结构而是先走两条优化路线。第一条是知识蒸馏。我用自己的数据集训练一个YOLOv7-e6e作为teacher模型然后用原YOLOv7作为student模型在训练时让student同时学习真实标签和teacher的预测输出。蒸馏的好处是student模型可以在保持较小体积的同时逼近大模型的检测精度。具体做法是在train.py里额外加载teacher权重并用蒸馏损失替换原损失。需要注意的是teacher模型的输入尺寸和student必须一致否则特征图大小对不上蒸馏效果会大打折扣。第二条是通道剪枝。YOLOv7的剪枝不像YOLOv5社区那么成熟官方没有直接给出一个通用的剪枝脚本。我自己用torch_pruning库做结构化剪枝实验后发现一个问题剪枝率超过30%后mAP掉得很快尤其是小目标检测能力几乎腰斩。所以我最后的做法是“适度剪枝量化补偿”在FGVC蒸馏之后再做20%的通道剪枝然后再用原始数据微调20个epoch精度基本能回落到原来95%以上模型体积则减少了将近35%。4. 部署环节模型从“能检测”到“能上线”的距离4.1 ONNX导出与输出对齐验证部署的第一步是把PyTorch权重转成ONNX。YOLOv7官方仓库提供了export.py但使用前要先明确一件事你想导出带NMS的端到端模型还是只导出检测头的原始输出。我这次在RK3588上部署后处理中的NMS是自己用C实现的所以我导出了不包含NMS的纯检测头模型。这样更灵活也方便以后切换不同的NMS策略。python export.py \ --weights runs/train/defect_detection/weights/best.pt \ --img-size 640 640 \ --batch-size 1 \ --simplify \ --include onnx导出后立刻做一个“输出对齐验证”用同一张测试图分别跑PyTorch模型和ONNX Runtime比较两边的输出向量差异。如果差异超过1e-3量级多半是算子转换出了问题比如某些版本PyTorch导出的SiLU激活函数在ONNX里会多出几条恒等分支需要用onnx-simplifier清洗一遍。我的验证脚本很简单import onnxruntime as ort import torch import numpy as np # pytorch推理 img_tensor torch.randn(1, 3, 640, 640).cuda() model torch.load(best.pt, map_locationcuda)[model].float().eval().cuda() with torch.no_grad(): torch_output model(img_tensor) # onnx推理 sess ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider]) onnx_input_name sess.get_inputs()[0].name onnx_output sess.run(None, {onnx_input_name: img_tensor.cpu().numpy()}) # 对比主输出 for i, (t_out, o_out) in enumerate(zip(torch_output, onnx_output)): diff np.max(np.abs(t_out.cpu().numpy() - o_out)) print(f输出{i}最大差异: {diff:.8f})如果差异在可接受范围内再继续做后续转换。这一步我强烈建议不要跳直接转TensorRT或RKNN之后再回头排查问题会被埋得很深。4.2 在PC上用TensorRT做一份性能基线虽然最终部署平台是RK3588但我习惯先在PC上用TensorRT刷一个性能基线这样能对比出边缘设备的算力损耗。转换命令很简单trtexec --onnxbest.onnx \ --saveEnginebest_fp16.engine \ --fp16 \ --workspace2048如果显存有限可以把workspace调小。这里有一点经验YOLOv7在TensorRT上建议至少测一下FP16和INT8两种模式。用FP16时精度几乎无损推理速度相比FP32大概提升60%到80%。用INT8时速度更快但需要准备校准数据集。trtexec --onnxbest.onnx \ --saveEnginebest_int8.engine \ --int8 \ --calib/path/to/calib_images校准集不要直接用训练集最好从验证集里随机抽样300到500张覆盖各类目标的图片。我之前贪省事直接用了200张训练集图片做校准结果发现推理在小目标上的置信度普遍下降因为校准数据分布和真实场景不一致。4.3 边缘设备适配RK3588与Jetson Orin两条路线的实测对比这次项目的主要部署目标是RK3588所以重点说RKNN路线。RK3588上跑YOLO检测模型官方推荐的流程是PyTorch权重 → ONNX → RKNN。我用的是rknn-toolkit2转换命令大致如下from rknn.api import RKNN rknn RKNN() # 配置量化等信息 rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588) # 加载ONNX模型 rknn.load_onnx(modelbest.onnx) # 模型转换量化类型可选 fp16 或 int8 rknn.build(do_quantizationTrue, datasetcalib_dataset.txt) # 导出RKNN模型 rknn.export_rknn(best.rknn)这里有个常见的坑RKNN对某些算子支持不够比如DynamicShape模型在RKNN上可能直接报错。解决方法是尽量导出静态shape的ONNX模型这也是我前面坚持用--batch-size 1和在export.py中固定img-size的原因。我有条件的同时在Jetson Orin上也测了同一个模型两种方案的对比如下平台转换路线推理精度单帧耗时备注RK3588ONNX→RKNN(INT8)有一定精度损失小目标置信度下降约32ms功耗低适合产线嵌入Jetson OrinONNX→TensorRT(FP16)基本无损约11ms算力更强但整机成本高我的结论是如果产品要做嵌入式批量出货RK3588是合理的性价比选择如果项目对实时性要求极高并且预算不是第一敏感Jetson Orin更省心。4.4 工程集成前处理、后处理与推理服务的那些坑模型转换只是部署的一半另一半是工程集成。我第一次部署时在这块跌了不少跟头总结下来主要是三个环节。第一前处理必须与训练时完全一致。我在训练时用了letterbox等比缩放加灰边部署时如果忘了给图片加同样的灰边模型精度会断崖式下降。我的C代码里专门写了一个letterbox函数并在注释里标明padding颜色必须是(114, 114, 114)和训练时保持一致。第二后处理解码和NMS不能太随意。原YOLOv7的检测头输出包括三个尺度的预测每个尺度有[box_coords, objectness, class_scores]。在C里我直接手写了一个解码函数先把输出张量转成候选框再过NMS。实测下来如果后处理里NMS的IoU阈值设为0.45和PyTorch版本的结果最接近。第三推理服务要考虑多线程并发。如果在PC上跑可以用FastAPI配合ONNX Runtime或TensorRT将模型封装成一个HTTP服务。在部署到边缘设备上时更推荐直接写一个C或Python的多线程推流程序保证每路视频流独占一个推理上下文避免并发时显存冲突。关于整个项目的一些体会回到最开始为什么这类项目真正耗时最多的不是模型训练而是数据标注和部署对齐现在答案已经很清楚了模型训练是一个标准流水线只要环境配好、参数合理剩下的就是等待收敛。但数据标注的质量决定了模型精度的天花板而部署对齐的细节决定了这个精度能不能在真实环境里兑现。我个人的建议是如果你准备做类似项目先从数据标注规范入手宁可多花两天把标注流程定细致也好过训练完之后再回头翻工。部署阶段则要尽早确认目标平台最好在做模型优化之前就明确要跑在TensorRT还是RKNN这类后端上因为它直接决定你要不要做INT8量化、要不要固定静态batch。最后无论用什么部署方案都保留一份标准化的ONNX模型然后所有后处理代码都在同一份标准输出上做一致性测试——这是我觉得性价比最高的省坑技巧。
返回列表