资讯动态

小样本轮胎检测实战:YOLOv8从数据标注到部署全流程

发布时间:2026/9/26 4:27:26 来源:尧图企业网站定制
简介汽车轮胎识别数据集面向目标检测与计算机视觉开发者包含837张训练图像标注类别聚焦于轮胎识别率99.5%适合用于YOLOv8模型训练、算法验证及车辆周边感知相关项目。压缩包为zip格式共1915个文件由957张JPG图片、957个TXT标注文件和1个YAML配置文件组成整体大小24.83MBJPG与TXT一一对应标注采用YOLOv8格式YAML可提供训练所需的类别配置数据组织清晰能够快速接入现有训练流程。目前已有638人学习。使用这份数据可省去大量人工标注工作直接用于模型训练和性能测试对于入门者可以学习目标检测数据集的构建与标注规范对于进阶者则可借助该基础数据开展迁移学习、超参数调优或在真实车辆场景中验证轮胎识别效果。无论是课程设计、毕业课题还是工业应用验证这套数据都能提供有效支撑。1. 837张轮胎图识别率99.5%小样本目标检测的真实处境一个典型的落地场景停车场入口要判断车位状态最可靠的视觉信号不是车牌而是轮胎——有轮胎就是有车。另一头洗车机、轮胎养护门店想按轮胎型号做自动分流也需要先稳定检出轮胎。这类需求最大的瓶颈不是模型是数据。轮胎在公开数据集里永远是背景板真到自己训的时候才发现带标注的轮胎图比模型还贵。837张训练图的汽车轮胎识别数据集就是冲这个来的图不多但标注直接给成YOLOv8格式的txt省掉你最头疼的转格式环节。99.5%的识别率看着喜人可小样本高分背后往往藏着验证集划分、标注质量和阈值设定的猫腻。这篇文章按我踩过的坑拆一遍拿到这种数据集先做什么、训练参数怎么定、99.5%怎么验真伪、部署在哪翻车。适合正卡在数据少、又不想从零标图的车辆视觉项目的人。2. 轮胎数据集与YOLOv8标注格式拿到手先做三件事2.1 先盘目录YOLOv8标准布局和data.yaml凡是自称YOLOv8格式的数据集第一件事不是看图片内容而是盘目录。YOLOv8的标准布局长这样images和labels两个目录各自再分train和val图片和txt标签一一对应再加一个data.yaml说明路径和类别数。常见的组织方式是datasets/ └── tires/ ├── images/ │ ├── train/ # 训练图 │ └── val/ # 验证图 ├── labels/ │ ├── train/ # 与训练图同名的txt │ └── val/ └── data.yaml拿到数据集先把这棵树的完整性过一遍。我见过两种典型的翻车结构一种是标签全部堆在labels目录下没有train和val之分需要手工切另一种是图片在images/train标签却散落在别的路径yaml里写了一个不存在的相对路径。数据集的目录组织决定了后面所有命令能不能跑通这里省时间就是在给训练阶段埋雷。data.yaml里两个字段最容易出错。path定义数据集根目录常见做法是写成相对路径然后训练时cd到datasets目录的父级再执行命令这样无论项目搬到哪个机器都不用改绝对路径。nc是类别数轮胎识别如果只分一类就写1names写成{0: tire}如果数据集把胎面、胎侧分成了多个类别nc就要相应改names列表顺序必须和标签txt里的类别id一致顺序错了模型训出来也是乱的。提示检查目录时顺手验证一遍图片和txt的文件名一一对应。用脚本比对一下两个目录的文件名集合缺标签的图直接挑出来避免训练时某张图被静默跳过。2.2 标签文件里每个数字的含义从txt到可视化YOLOv8标签txt每一行代表一个目标格式是class x_center y_center width height全部是相对于图片宽高的归一化值不是像素坐标。拿一张1280x960的图举例某个轮胎框的左上角像素坐标是(320, 240)右下角是(800, 720)那这一行就是0 0.4375 0.5 0.375 0.5换算逻辑是x中心点(320800)/(2*1280)宽度(800-320)/1280以此类推。这个换算关系是基本功但真有人栽在上面——把归一化值当像素值直接喂给Loss结果训练loss曲线在下降mAP却一直趴在0完全白跑。拿到标签我先抽图可视化比直接看数字直观得多。用OpenCV几行脚本就能刷一批检查图import cv2 img_path datasets/tires/images/train/000001.jpg label_path datasets/tires/labels/train/000001.txt img cv2.imread(img_path) h, w img.shape[:2] for line in open(label_path): cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imwrite(check.jpg, img)这段脚本的逻辑是把归一化的框坐标还原成像素坐标然后画框写类别id输出成一张检查图。我一般随机抽三五十张跑一遍重点看两类问题一是框是不是明显偏大或偏小比如一个框把整个车门包进去二是类别id是不是张冠李戴胎面标成了胎侧。这两类问题在837张的小数据集里哪怕只有十几张也足够把训练曲线搅得乱七八糟。2.3 数据划分与seed固定别让验证集混进训练图837张图怎么切训练和验证直接决定99.5%那个数字可不可信。很多数据集默认按8:2随机切分但小样本切分有个隐蔽问题如果同一辆车从多个角度拍了很多张随机切分会让同一辆车的不同角度同时出现在训练集和验证集里。模型其实在背这辆车而不是在学轮胎。严格的做法是按车ID或拍摄组划分让验证集里出现的车训练时一次都没见过。仓库里有车ID信息最好没有的话退而求其次把连拍的序列帧归到同一组再切。划分脚本要固定随机种子不然每次重跑结果都不同import os import random import shutil random.seed(42) base datasets/tires imgs sorted(os.listdir(f{base}/images/train)) random.shuffle(imgs) val_count max(1, len(imgs) // 5) val_imgs imgs[:val_count] os.makedirs(f{base}/images/val, exist_okTrue) os.makedirs(f{base}/labels/val, exist_okTrue) for name in val_imgs: stem os.path.splitext(name)[0] shutil.move(f{base}/images/train/{name}, f{base}/images/val/{name}) shutil.move(f{base}/labels/train/{stem}.txt, f{base}/labels/val/{stem}.txt)这里的重点是random.seed(42)先固定再shuffle保证每次划分结果一致训练结果可复现。val_count取五分之一837张里分到约167张做验证对单类检测来说规模足够算出波动可接受的指标。如果原数据集已经带好了val目录那这步跳过但要额外做一件事检查val和train里有没有重名文件。两张同名图内容可能完全一样这种数据泄漏会让验证mAP虚高到失去参考价值。2.4 标注质量的批量校验labelimg与cvat怎么选标签抽检发现问题后就要决定用哪个工具修。个人快速修正用labelimg最顺手环境干净、启动快直接打开txt对应的图改框就行适合几十张图的小修。但如果发现的问题是大范围的比如整套数据的框都偏大或者类别标反了手工逐张改不现实我一般把数据导进cvat做一次批量复审。cvat支持多人协作和任务分配可以按帧抽检也能导出成YOLOv8格式处理几百张图比labelimg高效得多。关键是改完一定要再从cvat导出成同样的YOLOv8 txt格式导出后重新跑一遍2.2的可视化脚本确认坐标没有被二次转换弄丢。标注格式这种事改一次就必须验证一次不然训练时才发现坐标系错位等于前功尽弃。3. 用YOLOv8跑通轮胎训练最小命令与九个关键参数3.1 训练前环境与文件确认先把环境说清楚。只要是能装PyTorch的机器YOLOv8在CPU上也能训Ubuntu 20.04装CPU版本跑单张640x640图大约要两三秒837张一轮就是半个多小时裸跑几百轮不现实。有GPU最省心GTX 1660Ti这种6GB显存的卡跑YOLOv8sbatch开到16、输入640x640完全没有压力。安装就一条命令pip install ultralytics训练前确认三件事data.yaml里path的路径能正确解析到数据集根目录标签txt没有空文件或异常字符yaml里的类别数和标签第一列的最大值匹配。ultralytics在训练启动时会扫描每张图并打印框数量统计如果看到大量warning提示corrupt image或者统计里全是0个目标多半是标签路径配错了。另外首次训练如果指定modelyolov8s.pt框架会自动下载预训练权重内网环境需要提前下载好pt文件放到当前目录命令改成model./yolov8s.pt避免卡在下载这一步。3.2 最小可用训练命令yolo detect train \ datatires.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ projectruns/tires \ nameexp1这条命令做了三件事加载COCO预训练的yolov8s权重作为起点读取tires.yaml指向的数据集然后在runs/tires/exp1目录下输出训练日志和权重。COCO数据集里没有tire这个类别那预训练权重有什么用关键在于模型已经在千万张图上学会了纹理、边缘、形状这些通用特征特征提取器可以直接迁移收敛速度和最终精度远好于从随机初始化开始这也是837张小样本能跑到高mAP的关键前提。epochs设120轮只是一个起点配合早停机制大多数情况下60轮左右就能收敛。3.3 训练效果的关键参数batch、imgsz、lr0、freeze参数决定训练走向这里列几个实际项目里必调的按影响排序batch6GB显存跑yolov8s建议8到16。显存不够优先降imgsz到480不要硬把batch压到2BatchNorm在小batch下统计量不稳定收敛会变得很玄学。imgsz640是默认值。轮胎在画面里属于大目标640足够。如果后续要部署到1080p视频流做实时推理训练用640推理时直接把输入拉升到1280工程上是可行的精度损失在可接受范围。epochs837张小数据120轮足够超过200轮基本在背训练集。配合patience早停实际训练往往跑不到设定轮数。lr0默认0.01。如果头几个epoch的loss直接炸到NaN或者验证loss剧烈震荡降到0.005重开。optimizer默认auto框架会根据模型自动选。小数据集我倾向显式指定SGD加momentum泛化通常比AdamW稳一点。freeze冻结前几层不参与训练。小样本迁移学习常用freeze10先让分类头和检测头收敛再解冻低层微调能明显压住过拟合。weight_decay0.0005这是小数据集防止过拟合的主力不要关。cos_lr打开余弦退火训练后期loss下降更平滑小样本特别推荐。augment默认的翻转缩放对轮胎够用但mosaic合成图在轮胎这种大面积单一目标上会切掉目标主体容易引入错误标签建议关掉mosaic或用更弱的增强组合。3.4 训练曲线怎么看哪张图说明模型在正常学习训练结束后进runs/tires/exp1目录重点看results.png和confusion_matrix.png。results.png第一行是box_loss训练loss和验证loss两条线持续下降且间距不大说明在学习且没过拟合如果训练loss一路走低、验证loss在第40轮开始掉头向上这就是典型的过拟合信号——模型开始背训练图了早停该触发的地方就在这里。再看混淆矩阵。单类检测时矩阵里是tire和background两行两列。如果background那一列有相当比例的响应说明模型把大量背景误判成轮胎部署时就会表现为满屏乱框。这种误检光调阈值救不回来得回训练集补负样本拍一些空车位、纯路面、半截轮胎的图标注文件留空让模型见过足够的背景。这个操作对压误检非常有效。4. 避坑99.5%是如何炼成的以及它为什么不可全信4.1 坑一验证集泄漏让99.5%成了自嗨现象训练时val mAP一路涨到0.995不回头训练loss和验证loss都低得离谱你以为模型神了拿到现场视频一跑漏检和误检一起来。原因837张对随机切分来说太少。如果原始采集来自同一个停车场、同一个机位甚至同一辆车连续帧随机切分会让训练集和验证集高度相似。模型背住的是停车场的砖缝和光线不是轮胎这个概念验证指标自然虚高。99.5%就是这么炼成的——它测的是模型对训练场景的记忆不是泛化能力。解决重新按场景切分。理想情况是从另一个停车场或另一时段补拍几十张做测试集没条件就退一步把同一辆车的不同角度归同一组按组分而不是按图分。分组切分的val就算mAP只有0.92可信度也远高于混入同源数据的0.995。以后凡是看到小数据集上夸张的识别率第一个问题永远是验证集里有没有训练集的影子。4.2 坑二标注框偏大导致Loss降不下来现象box_loss卡在0.8左右不动mAP50有0.9但mAP50-95只有0.5像被一层天花板压住。原因抽查可视化时没看清框的边界。轮胎是圆形很多标注习惯直接拉一个矩形把轮毂内侧也包进去等于模型每张图都在学「轮胎加一块背景」特征被稀释收敛上限自然被压低。这种问题在837张的小数据集里影响被成倍放大。解决回labelimg把边缘明显的框收紧框要贴合胎面的外侧边缘不要把轮毂内圈包进去。837张全部重标工作量太大我只修loss异常的那批图实际约200张修完box_loss降到了0.6附近mAP50-95明显上升。记住一个规律验证集mAP50高但mAP50-95上不去优先怀疑标注精度而不是模型容量。4.3 坑三推理时置信度阈值设太低现象训练指标全绿部署到摄像头后每帧冒出一堆误检框逻辑处理直接被无效框淹没。原因训练时框架默认用conf0.25做验证验证集分布和训练集同源所以表现正常。现场光照变了、角度变了模型输出置信度整体偏低真目标在0.25阈值以下被漏掉假目标里那些0.2以上置信度的噪音全被当成轮胎框了出来。阈值是训练指标和线上表现之间最大的落差所在。解决导出模型前先拿现场拍摄的几十张图跑一遍推理统计所有框的置信度分布把阈值设在真目标和误检框的分布低谷处。一般我会写个十几行的脚本扫一遍取「真目标最低0.7、误检最高0.3」的阈值。这里还有一个参数别乱动NMS的IoU阈值保持默认0.7改小了会把同一辆车上两个重叠的轮胎框合并掉改大了又会出现同一目标重复框。4.4 坑四单类数据集没有负样本现象recall很高precision崩了模型对圆形的路障、井盖、轮胎形状的水渍全都要框一下。原因训练集837张全是「有轮胎的图」模型没见过空停车场和水泥路面推理时没有参照系判断边界。目标检测本质上是分类器的扩展训练时完全没有负样本模型天然分不清轮胎和圆形背景。解决去现场拍50张无轮胎的背景图每张配一个空的txt标签文件混进训练集重新训练。这招对99.5%这类虚高数字是有效的降温剂验证集里也放几张无轮胎图此时precision和recall才会真正拉开差距暴露模型真实的误报水平。5. 部署与推理从best.pt导出ONNX到跑通RTSP5.1 导出ONNX固定尺寸还是动态轴训练产出的是best.ptPyTorch格式适合验证和进一步调参但生产环境一般导出ONNX体积小、不依赖torch运行库RTSP接入或嵌入式部署都靠它。导出命令yolo export modelruns/tires/exp1/weights/best.pt formatonnx imgsz640 opset12imgsz640同时把导出模型的输入固定成[1,3,640,640]。opset固定在12端侧兼容性最好转RKNN或TensorRT都不容易卡算子。如果想让模型支持多分辨率输入可以加dynamicTrue让宽高维度变成动态但这会让TensorRT和NPU工具链无法做固定shape优化推理性能明显下降我一般不建议开。用ONNXRuntime验证导出的模型是否可用这一小段代码必须跑通再进下一步import onnxruntime as ort import cv2 import numpy as np sess ort.InferenceSession(best.onnx) img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 outs sess.run(None, {sess.get_inputs()[0].name: img}) preds outs[0] print(preds.shape) # 确认输出形状outs[0]的形状会因ultralytics版本不同而变化旧版是[1, 84, 8400]新版可能是[1, 8400, 84]其中84来自4个框坐标加80个类别得分。如果训练时没有限制类别数导出后的分类头默认是COCO的80类空间只识别轮胎的话需要在后处理里过滤索引或者干脆在训练时加classes1参数让分类头只剩1路输出。后者推理逻辑最省事输出维度变成5前4个是坐标第5个直接是轮胎置信度。5.2 用ONNXRuntime接RTSP视频流的最小推理循环停车场场景最常用的是RTSP摄像头输入。最小可跑的推理循环长这样import cv2 import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx) cap cv2.VideoCapture(rtsp://192.168.1.64:554/stream1) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while True: ret, frame cap.read() if not ret: break h, w frame.shape[:2] inp cv2.resize(frame, (640, 640)) inp inp[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 outs sess.run(None, {sess.get_inputs()[0].name: inp})[0] # 以输出形状[1, 8400, 5]为例做阈值过滤和NMS boxes outs[0][outs[0][:, 4] 0.35] # 先按置信度过滤 for b in boxes: x1 int((b[0] - b[2] / 2) * w) y1 int((b[1] - b[3] / 2) * h) x2 int((b[0] b[2] / 2) * w) y2 int((b[1] b[3] / 2) * h) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(tire, frame) if cv2.waitKey(1) 0xFF 27: break这段代码里有一个点需要说明outs[0]的坐标是归一化到640x640输入图的缩放回原图分辨率时要用原图的宽高w和h换算不是直接拿640去乘。NMS在多个框重叠时还是需要做的上面为了展示做了简化实际建议直接复用ultralytics仓库ops模块里的non_max_suppression实现。这个循环的问题在于用OpenCV的VideoCapture拉RTSP流在弱网下容易丢帧卡顿进阶可以换FFmpeg硬解通道但作为模型验证已经足够——它能告诉你模型在真实视频帧上到底能检出什么、漏掉什么。5.3 边缘设备部署RK3588量化怎么不翻车部署到边缘设备时RK3588是一个常见选择。RK3588的NPU不能直接跑ONNX需要用rknn-toolkit2转成RKNN格式。转换前先做两件事把opset固定成12用onnx-simplifier把图结构里的冗余节点抹掉。最容易卡算子的是模型里的上采样方式和某些激活函数遇到这类报错先降imgsz到640以下再试。量化是RK3588部署最值得花时间的环节。我一般用混合量化前几层卷积保持FP16中间层用INT8最后输出层再保留FP16。轮胎检测对边缘纹理敏感全INT8量化后mAP会掉1到2个点混合量化能把损失控制在0.5个点以内。这时候837张训练集还有最后一项用途从训练集里抽100张覆盖不同光照和角度的图作为量化校准集比随机抽图的量化效果显著更好。记住校准集的分布要贴近现场真实输入不是贴近训练集分布。6. 把837张做成2000张的效果增强与迁移学习的实战技巧数据增强不是无脑全开。YOLOv8默认开的Mosaic对轮胎这种大面积单一目标反而有害四张图拼在一起后轮胎经常被切掉三分之一等于给模型喂了错误标注。我一般关掉Mosaic改用HSV增强hue设0.02、sat设0.5、val设0.4让模型对停车场不同灯光下的色偏鲁棒。垂直翻转flipud别开——轮胎虽然对称但翻转后模型学的是倒着的车部署时又不会出现倒车场景纯属浪费拟合能力。迁移学习有个小技巧冻结前10层先训30轮再解冻全量微调50轮。COCO预训练权重的前几层是通用边缘纹理特征直接在837张上全量微调低层特征会被小数据集带偏。两阶段训练能把这个风险压住yolo detect train datatires.yaml modelyolov8s.pt \ epochs30 freeze10 lr00.005冻结阶段学习率要调低0.005比默认0.01更稳解冻后可以把学习率恢复到0.01继续跑。这套两阶段方案对几百张级别的小样本效果明显收敛更稳最终精度也高一些。做多了小样本检测项目最大的教训是先花半天抽检标注和划分再花半天训练永远比先跑了再说快。虚高的验证指标一旦出现后面调什么参数都是白调。每次看到「识别率99.5%」这种数字先问三个问题验证集怎么切的测试图哪来的置信度阈值多少问完心里才有底。如果你严格按「目录检查、标签抽检、分组划分、冻结训练、现场阈值校正」这套流程走837张数据出个能稳定上线的轮胎检测模型不是问题。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑