ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

轮胎字符识别实战:从数据标注到YOLOv5与CNN两阶段模型训练

轮胎字符识别实战:从数据标注到YOLOv5与CNN两阶段模型训练 简介这份资源面向计算机、电子信息工程、数学等专业的大学生用于课程设计、期末大作业与毕业设计场景核心任务是轮胎字符识别。包内提供完整源代码、文档说明与配套数据覆盖从原始数据提取高度数据、转化为高度图、裁切与修复图像到展平、规格化、去噪、直方图均衡化与直方图裁切等完整预处理链路并包含模型推理相关文件便于理解机器学习项目从数据到识别的实现思路。资源共157个文件以png、jpg图像样本为主辅以19个py脚本、txt说明文档及模型参数文件压缩包约333.11MB目录结构清晰方便按模块查阅与复现。代码采用参数化编程参数可灵活更改注释明细且内含运行结果均经测试运行成功。目前已有268人学习适合需要完整赛题方案、可运行代码与排错参考的读者参考使用。1. 轮胎字符识别到底在识别什么从一条产线质检需求说起轮胎侧壁那圈凸起的字符包含规格、生产日期、DOT 码、模具号是追溯和质量管控的唯一身份信息。硫化成型后字符是凸起的、黑底黑字、还带弧度人眼在强反光下都容易看错更别说让普通 OCR 直接读。2023 机器学习作业里“轮胎字符识别”这个题目本质是让你用一套完整的机器学习流程把工业场景下的低对比度字符检测与识别跑通而不是调个现成 API 就完事。它解决的是给定一张轮胎侧壁图先定位字符区域再逐字识别出内容。适合正在做课程设计、想入门工业视觉、或者需要一套可复现字符识别 pipeline 的人。数据、源代码、文档说明三件套齐全意味着你能从数据标注格式一路看到推理脚本这对新手尤其友好——不用自己从零攒数据集直接改参数就能观察模型行为。下面按“数据怎么准备 → 模型怎么选 → 怎么训练 → 怎么避坑 → 怎么验证”的顺序拆开讲。2. 数据准备与标注格式轮胎字符数据集怎么读、怎么切、怎么增强2.1 先看清数据集长什么样拿到“数据”这一项第一件事不是急着写模型而是把目录结构和标注格式摸清楚。轮胎字符数据集常见两种组织方式一种是检测框标注每张图对应一个 txt 或 xml记录字符框坐标和类别另一种是整行文本标注只给整串字符靠 CTC 或注意力解码。课程作业里多数是第一种因为字符类别有限数字 0-9、字母若干、斜杠、点检测加分类两步走更稳。我一般先跑一段统计脚本确认图片数量、分辨率分布、字符类别分布。这一步能提前发现类别不均衡——比如数字“1”和“7”在轮胎上出现频率远高于其他字符如果不处理模型会偏向多数类。import os import cv2 import collections img_dir data/images label_dir data/labels counter collections.Counter() sizes [] for name in os.listdir(img_dir): if not name.lower().endswith((.jpg, .png, .bmp)): continue img cv2.imread(os.path.join(img_dir, name)) h, w img.shape[:2] sizes.append((w, h)) label_path os.path.join(label_dir, os.path.splitext(name)[0] .txt) if os.path.exists(label_path): with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 5: counter[parts[0]] 1 # 第一列是类别 id print(图片总数:, len(sizes)) print(分辨率样本:, sizes[:5]) print(类别分布:, counter.most_common())这段脚本做三件事遍历图片目录、读取每张图尺寸、统计标注文件里每个类别出现次数。counter的 key 是类别 idvalue 是出现次数。如果发现某个类别只有个位数样本后面训练时就要考虑过采样或加权损失。分辨率样本用来判断是否需要统一缩放——轮胎图常见 1280×1024 或 2448×2048直接缩到 640 会丢小字符细节建议保持长边 1024 以上再送入检测网络。2.2 标注格式转换从 VOC 到 YOLO 的四个边界坑很多作业给的是 VOC 格式 xml而训练检测模型常用 YOLO 格式 txt。转换本身不难难在边界处理。我踩过的坑包括坐标越界、宽高为负、类别名映射错、图片和标注文件名不对应。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, img_w, img_h, class_map, out_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in class_map: continue cls_id class_map[cls_name] bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 边界裁剪防止越界 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue # 跳过无效框 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))关键参数class_map是类别名到 id 的字典必须和后续训练配置一致img_w、img_h从对应图片读取不能硬编码。四个边界坑分别是坐标超出图片范围裁剪解决、宽高为零或负跳过、类别名大小写不一致统一 strip 后映射、xml 和图片文件名不匹配转换前先做文件名对齐检查。转换完建议随机抽 20 张可视化验证别等训练 loss 不降才回头查。2.3 数据增强针对轮胎字符的三种有效手段轮胎字符识别不能照搬通用增强。随机裁剪容易把字符切掉颜色抖动对黑底黑字意义不大。我一般用三种小角度旋转±5°、对比度受限自适应直方图均衡化CLAHE、轻微高斯模糊。CLAHE 对凸起字符的阴影边缘提升明显高斯模糊模拟产线轻微失焦。import cv2 import numpy as np def augment_tyre(img): # 小角度旋转 h, w img.shape[:2] angle np.random.uniform(-5, 5) M cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0) img cv2.warpAffine(img, M, (w, h), borderModecv2.BORDER_REPLICATE) # CLAHE 提升局部对比度 lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) lab[:, :, 0] clahe.apply(lab[:, :, 0]) img cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) # 轻微模糊 if np.random.rand() 0.3: img cv2.GaussianBlur(img, (3, 3), 0) return imgclipLimit2.0控制对比度增强上限太大反而放大噪声tileGridSize(8,8)决定局部区域大小轮胎字符区域通常占图 1/10 左右8×8 网格比较合适。旋转角度超过 10° 会让字符框标注失效所以控制在 ±5°。增强只对训练集做验证集保持原图否则评估指标会虚高。3. 模型选型与训练检测加识别两阶段怎么搭3.1 为什么轮胎字符识别更适合“检测 分类”而不是端到端端到端 OCR如 CRNN CTC在文档场景很强但轮胎字符有两个特殊性字符是离散凸起、间距不固定、还有弧形排列。端到端模型容易把相邻字符合并或漏掉。两阶段做法是先检测每个字符框再对每个框分类好处是每个字符独立判断漏检和误检可分开排查。检测阶段常用 YOLOv5/v8 或轻量 SSD分类阶段用一个小 CNN如 ResNet18 或自定义 4 层卷积。课程作业算力有限YOLOv5n 加 ResNet18 在 1080Ti 上 batch 16 能跑起来。如果数据量少于 2000 张检测模型建议冻结 backbone 前几层只训 head。3.2 检测模型训练三个必调参数与一个验证习惯以 YOLOv5 为例配置文件里img-size、batch-size、lr0是三个最影响结果的参数。轮胎图字符小img-size设 1024 比 640 的 mAP 通常高 5 到 10 个点但显存翻倍。batch-size根据显存调8 或 16。lr0初始学习率设 0.01如果 loss 震荡就降到 0.001。python train.py --img 1024 --batch 8 --epochs 100 \ --data tyre_chars.yaml --weights yolov5s.pt \ --cfg models/yolov5s.yaml --lr0 0.01 --patience 20--patience 20表示 20 轮验证指标不提升就早停省时间。tyre_chars.yaml里要写对train、val路径和nc类别数、names类别名列表。训练时每轮结束看val/box_loss和mAP0.5如果 box_loss 降但 mAP 不升多半是过拟合加增强或减模型容量。3.3 字符分类网络输入尺寸与类别不平衡处理检测框裁出来后统一缩放到 32×32 或 48×48 送入分类网络。轮胎字符笔画粗细差异大32×32 对细笔画可能不够我一般用 48×48。类别不平衡用加权交叉熵权重取类别频率倒数。import torch import torch.nn as nn class TyreCharCNN(nn.Module): def __init__(self, num_classes): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier nn.Linear(128 * 6 * 6, num_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x) # 加权损失 class_counts torch.tensor([...]) # 从统计脚本得到 weights 1.0 / class_counts.float() weights weights / weights.sum() criterion nn.CrossEntropyLoss(weightweights)输入 48×48 经过三次池化变成 6×6128*6*6是全连接输入维度。权重归一化后总和为 1避免 loss 尺度变化太大。训练分类网络时学习率设 0.001用 Adam 优化器20 轮左右收敛。验证时看每类准确率别只看总体——总体 95% 但某个字符 60% 的情况很常见。4. 避坑与排查轮胎字符识别里最容易翻车的五件事4.1 现象训练 loss 正常下降但验证 mAP 始终为 0原因标注文件路径写错或类别 id 从 1 开始而模型从 0 开始。YOLO 格式要求类别 id 从 0 连续编号如果标注里是 1 到 10模型会认为有 11 类且第 0 类无样本。解决用统计脚本打印标注里所有类别 id 的最小值和最大值确认从 0 开始且连续。同时检查data.yaml里nc是否等于实际类别数。4.2 现象检测框大量重叠同一个字符被检出多次原因NMS非极大值抑制的 IoU 阈值设太高或者锚框尺寸和字符实际尺寸不匹配。轮胎字符框通常很扁或很窄默认锚框偏方正。解决把 NMS IoU 从 0.45 降到 0.3并用 k-means 重新聚类锚框。YOLOv5 自带--anchor-size可以按数据集统计。4.3 现象分类准确率在验证集上波动超过 10%原因验证集太小或划分时没有按轮胎图片分组。同一张轮胎图裁出的多个字符如果分散在训练和验证集会造成信息泄漏。解决按原始图片划分训练/验证同一张图的所有字符框只出现在一个集合里。验证集至少占 20%且每类至少 10 个样本。4.4 现象推理时单张图耗时超过 500ms原因检测模型输入尺寸太大或者分类网络逐字符串行推理没有 batch。解决检测输入从 1024 降到 768 先看精度损失分类时把同一张图的所有字符框拼成一个 batch 一次前向。另外用torch.no_grad()和model.eval()别忘关梯度。4.5 现象模型在训练集上完美换一批新轮胎图就崩原因过拟合到特定轮胎品牌或光照条件。数据集里如果只有一种轮胎侧壁模型学到的是背景纹理而不是字符形状。解决增强里加随机亮度调整和局部遮挡Cutout训练时用早停。如果条件允许收集至少两个品牌的轮胎图哪怕每个品牌只有几十张。5. 验证与进阶怎么确认你的轮胎字符识别真的能用5.1 用混淆矩阵定位“哪个字符最容易被认错”总体准确率会掩盖问题。跑完验证集后画一个混淆矩阵看非对角线上的热点。轮胎字符里常见混淆对0 和 O、1 和 I、8 和 B、5 和 S。如果某一对混淆严重先检查标注是否本身就有歧义——有些轮胎字体确实把 0 和 O 做得几乎一样。如果是标注问题合并类别或重新定义如果是模型问题针对该对字符补充样本。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt cm confusion_matrix(y_true, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(预测) plt.ylabel(真实) plt.savefig(confusion_matrix.png)y_true和y_pred是验证集所有字符的分类标签。矩阵对角线越深越好非对角线如果有明显数字就针对性地找那些样本看。5.2 端到端推理脚本从一张图到字符串输出把检测和分类串起来输出完整字符串。注意字符排序轮胎字符可能是弧形排列按检测框中心点 x 坐标排序在多数情况下够用但如果弧形弯曲严重需要按极角排序。def recognize_tyre(img_path, detector, classifier, class_names): img cv2.imread(img_path) boxes detector(img) # 返回 [x1,y1,x2,y2,conf,cls] chars [] for box in boxes: x1, y1, x2, y2 map(int, box[:4]) crop img[y1:y2, x1:x2] crop cv2.resize(crop, (48, 48)) crop crop.astype(float32) / 255.0 crop torch.from_numpy(crop).permute(2, 0, 1).unsqueeze(0) with torch.no_grad(): logits classifier(crop) pred logits.argmax(1).item() chars.append((x1, class_names[pred])) chars.sort(keylambda x: x[0]) # 按 x 坐标排序 return .join(c for _, c in chars)detector和classifier都是 eval 模式。排序用x1而不是中心点因为字符宽度差异大时中心点排序可能错位。如果轮胎字符是上下两行需要先按 y 坐标分行再分别排序。5.3 一个我常用来快速判断“值不值得继续调”的习惯每次改完参数我只跑 10 张验证图人眼看输出字符串和真实值差几个字符。如果 10 张里错超过 3 张先别调模型回去查数据和标注。血泪经验是轮胎字符识别里 80% 的精度问题出在标注不一致和验证集泄漏模型本身反而没那么玄学。把数据清理干净YOLOv5n 加一个小 CNN 就能到可用水平。希望帮到你。本文还有配套的精品资源点击获取
返回列表