yolov7车牌识别实战:12类中文车牌检测与识别优化

发布时间:2026/10/7 18:52:58
yolov7车牌识别实战:12类中文车牌检测与识别优化
简介这是一套基于YOLOv7的车牌识别算法系统面向人工智能、通信工程、自动化等专业的在校学生、教师及企业开发者可用于毕业设计、课程设计、项目立项演示或算法进阶学习。系统支持单行蓝牌、单行黄牌、新能源车牌、白色警用车牌、教练车牌、武警车牌、使馆车牌、港澳牌车、双层农用车牌、民航车牌等12种中文车牌类型覆盖常见与特殊场景。资源包共100个文件约24.05MB包含38个Python源码、13个YAML配置、5个Shell脚本、4个C与CUDA加速文件、2个PyTorch权重文件以及JPG/PNG示例图、说明文档和字体文件兼顾训练、推理与部署。项目已通过测试运行答辩评审96分代码结构完整便于在此基础上修改扩展或直接用于毕设课设。目前已有123人学习下载适合需要快速上手车牌识别实战的读者参考。1. 从一张歪斜的车牌说起yolov7 车牌识别到底能落地到什么程度地下车库出口闸机摄像头拍到的车牌是斜的还带反光传统 OpenCV 那套颜色分割加轮廓筛选直接罢工。这是我第一次被逼着把检测模型换成正脸检测器也是我第一次认真评估 yolov7 车牌识别算法系统。它要解决的核心问题很具体在一张图或一路视频流里先定位车牌框再识别框里的字符并且要覆盖蓝牌、黄牌、绿牌、白牌、黑牌、民航、使馆、港澳、教练、警用、军牌、双层黄牌等 12 种中文车牌类型。适合谁适合要做停车场、园区出入口、充电桩车位管理、道路卡口这类真实项目的工程师不适合只想跑个 demo 截图发朋友圈的人。因为一旦涉及 12 类车牌类别不均衡、字符集设计、后处理规则这三件事会把你从调参的舒适区里拽出来。源码和文档能给你起点但落地效果取决于你对数据、阈值和后处理的理解。2. 先搞清楚 yolov7 车牌识别系统的三层结构2.1 检测、识别、后处理为什么必须拆开很多人拿到“车牌识别”四个字第一反应是端到端一个模型搞定。实际工程里我一般会拆成三层车牌检测层负责输出车牌四角坐标或水平框字符识别层负责把裁剪出来的车牌图变成字符串后处理层负责按车牌类型做规则校验和纠错。拆开的好处是每一层可以独立替换和调参。检测层用 yolov7是因为它在小目标和中目标上的召回比 yolov5 更稳尤其是车牌这种长宽比极端、像素面积小的目标。识别层常见做法是 CRNN CTC或者轻量级 CNN 全连接分类。后处理层则是纯规则代码不涉及训练但决定了最终准确率的上限。为什么不用一个模型直接输出字符串因为车牌字符位置固定但透视变形大端到端模型在 12 类车牌混训时容易把颜色特征和字符特征耦合在一起蓝牌训练样本多绿牌和黑牌样本少模型会偏向多数类。拆开之后检测层只学“车牌在哪”识别层只学“字符是什么”类别不均衡的影响被分散到两个阶段调参时也更容易定位问题。2.2 12 种中文车牌类型对应的字符集设计12 种车牌类型不是简单加 12 个分类标签。每种车牌的字符长度、字符集、颜色分布都不一样。蓝牌是 7 位黄牌单层 7 位双层黄牌是 14 位分两行绿牌 8 位白牌有 7 位也有 8 位黑牌多为 7 位民航、使馆、港澳、教练、警用、军牌各有各的规则。如果你只用一个统一字符集把汉字、字母、数字全塞进去识别层输出维度会很大小样本类别根本训不动。我一般会这样做先按车牌类型分组每组维护一个字符集映射表。蓝牌和黄牌共用一套“省份简称 字母 数字”的字符集绿牌单独加新能源字母位双层黄牌拆成上下两行分别识别再拼接。字符集设计直接决定识别层的输出维度也决定后处理时怎么做合法性校验。比如蓝牌第二位只能是字母绿牌第三位只能是 D 或 F这些规则写在后处理里能纠正识别层的一部分误判。2.3 从源码包到可运行环境的最小步骤拿到一个 yolov7 车牌识别源码包不要急着python train.py。先确认目录结构常见的是detect/、recognize/、weights/、data/、utils/这几块。下面是我习惯的启动顺序每一步都对应一个可验证的结果。# 1. 建虚拟环境锁 Python 版本yolov7 对 3.8/3.9 最稳 conda create -n plate_yolov7 python3.9 -y conda activate plate_yolov7 # 2. 装 PyTorch注意 CUDA 版本要和驱动匹配不要盲目装最新 pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 3. 装依赖源码包里一般有 requirements.txt pip install -r requirements.txt # 4. 先跑单图推理确认权重能加载、检测框能出来 python detect.py --weights weights/plate_det.pt --source data/samples/test_01.jpg --conf 0.25 --iou 0.45这段命令的逻辑是环境隔离避免和系统 Python 冲突PyTorch 版本和 CUDA 对齐避免CUDA error: no kernel image is available先跑单图推理是为了把“模型加载”和“后处理”两个环节分开验证。参数说明--conf 0.25是检测置信度阈值车牌场景我一般从 0.25 开始试太低会引入大量背景框太高会漏掉远距离小车牌--iou 0.45是 NMS 的 IoU 阈值车牌长宽比大NMS 阈值不宜设太高否则相邻车牌容易互相抑制。提示如果单图推理报KeyError或shape mismatch先检查权重文件和模型结构是否匹配不要直接改代码。3. 训练自己的车牌检测模型数据、参数和评估3.1 车牌数据标注的四个硬性要求yolov7 检测层要的是 YOLO 格式标注每张图对应一个 txt每行class x_center y_center width height坐标归一化到 0 到 1。车牌标注有四个硬性要求少一个都会让训练结果变玄学。第一框要贴紧车牌边缘不要留太多背景车牌本身像素就少框大了等于让模型学背景。第二倾斜车牌用水平框标注时框要覆盖整个车牌区域不要只框字符部分。第三双层车牌按整体框标注不要拆成两个框否则检测层会输出两个重叠框后处理很难合并。第四类别标签按车牌类型分不要所有车牌都标成plate否则识别层拿不到类型先验。我见过最常见的翻车是标注时把车牌框画得比实际大一圈训练 loss 降得很好看但推理时框出来一堆背景识别层裁剪进去全是干扰。血泪经验标注完先随机抽 50 张可视化检查用脚本把框画回原图上看比看 loss 曲线有用得多。3.2 训练命令与关键参数怎么改yolov7 的训练入口一般是train.py核心参数集中在data/plate.yaml和cfg/training/yolov7.yaml。下面是一条我常用的训练命令参数不是默认值是我在车牌场景下调过的。python train.py \ --weights weights/yolov7.pt \ --cfg cfg/training/yolov7.yaml \ --data data/plate.yaml \ --hyp data/hyp.plate.yaml \ --epochs 150 \ --batch-size 16 \ --img-size 640 640 \ --rect \ --workers 8 \ --device 0 \ --name plate_exp01逻辑说明--weights加载 COCO 预训练权重做迁移学习车牌数据量通常不大从头训会欠拟合--rect开启矩形推理车牌长宽比大矩形推理比正方形 letterbox 更省算力也能减少黑边干扰--img-size 640 640是输入分辨率如果小车牌多可以提到 1280但显存要够。参数说明--batch-size 16在 11G 显存上跑 640 分辨率比较稳显存不够就降到 8 并开--accumulate--epochs 150是经验值车牌检测通常 100 到 200 轮收敛看 mAP 曲线早停比死磕轮数重要。hyp.plate.yaml里我一般会改三个值lr0从 0.01 降到 0.005车牌数据少学习率太大会震荡mosaic从 1.0 降到 0.5 mosaic 增强对车牌这种规则目标有时会引入不真实拼接scale从 0.5 降到 0.3车牌尺度变化本身不大过度缩放反而让模型学偏。3.3 评估指标看什么mAP 之外还要看漏检率训练日志里mAP0.5是最常看的指标但车牌场景我还会额外看两个数小目标召回率和误检率。yolov7 的验证脚本会输出每类 AP如果蓝牌 AP 很高但绿牌 AP 很低说明类别不均衡已经影响检测层了需要给绿牌过采样或者调类别权重。漏检率比 mAP 更贴近业务停车场出口漏一辆车就是一次投诉。我一般会在验证集上跑一遍推理统计“有车牌但没检出”的图片比例超过 3% 就要回头查标注和 anchor 匹配。注意不要用训练集上的 mAP 判断模型好坏车牌场景过拟合非常快训练集 mAP 0.98 而验证集 0.85 是常态。4. 字符识别与后处理把检测框变成合法车牌号4.1 CRNN 识别层的输入预处理与 CTC 解码检测层输出的是车牌框坐标识别层要的是固定高度的车牌条图。常见做法是把车牌框裁剪出来做透视变换矫正再缩放到 32x100 或 48x160。透视变换是关键一步倾斜车牌不矫正直接送进 CRNN字符会粘连识别率掉得厉害。矫正需要车牌四角坐标如果检测层只输出水平框可以用框的四个顶点做近似或者单独训一个关键点回归头。import cv2 import numpy as np def crop_plate(img, box): # box: [x1, y1, x2, y2] 水平框 x1, y1, x2, y2 map(int, box) plate img[y1:y2, x1:x2] # 缩放到识别层输入尺寸保持长宽比短边补灰 h, w plate.shape[:2] target_h, target_w 32, 100 scale target_h / h new_w int(w * scale) resized cv2.resize(plate, (new_w, target_h)) if new_w target_w: pad np.full((target_h, target_w - new_w, 3), 114, dtypenp.uint8) resized np.concatenate([resized, pad], axis1) else: resized cv2.resize(resized, (target_w, target_h)) return resized逻辑说明先按高度缩放再补边到固定宽度避免直接拉伸导致字符变形。补边值 114 是常用灰度和训练时预处理保持一致。参数说明target_h32是 CRNN 常见输入高度target_w100对应 7 位车牌如果是双层车牌或 8 位绿牌宽度要相应加大到 120 或 140。CTC 解码时注意blank标签的位置不同源码实现不一样有的把 blank 放在最后有的放在 0解码前先确认。4.2 12 类车牌的后处理规则表识别层输出原始字符串后必须过一遍规则校验。下面这张表是我按 12 类车牌整理的常见校验规则实际项目里会根据业务再收紧。车牌类型长度首位第二位特殊位规则蓝牌7省份简称字母末位可为字母或数字黄牌单层7省份简称字母同蓝牌双层黄牌14省份简称字母上下两行拼接第二行 7 位绿牌8省份简称字母第三位 D/F末位字母或数字白牌7/8省份简称字母末位常为“警”“学”等汉字黑牌7省份简称字母使领馆车牌有固定前缀民航7民航字母固定前缀“民航”使馆7使数字固定前缀“使”港澳7粤字母粤 Z 开头教练7省份简称字母末位为“学”警用7省份简称字母末位为“警”军牌7汉字字母固定军种汉字后处理代码不需要复杂核心是“按类型取规则规则不通过就回退到置信度次高的候选”。我一般会保留识别层 top3 结果逐个过规则第一个通过的就是最终输出。这样比只取 top1 再纠错更稳。4.3 端到端串联脚本与输出格式把检测和识别串起来输出结构化结果方便对接业务系统。import json from detect import run_detection from recognize import run_recognition from postprocess import validate_plate def plate_pipeline(image_path): img cv2.imread(image_path) boxes, scores run_detection(img, conf0.25, iou0.45) results [] for box, score in zip(boxes, scores): plate_img crop_plate(img, box) top3 run_recognition(plate_img, topk3) final None for text, conf in top3: ok, plate_type validate_plate(text) if ok: final {text: text, type: plate_type, conf: conf} break if final is None: final {text: top3[0][0], type: unknown, conf: top3[0][1]} final[box] box.tolist() final[det_score] float(score) results.append(final) return json.dumps(results, ensure_asciiFalse, indent2)逻辑说明检测输出多个框每个框单独走识别和后处理后处理通过就采用不通过就降级到 top1 并标记 unknown。参数说明conf0.25和iou0.45是检测阈值topk3是识别层返回候选数。输出 JSON 里保留det_score和conf方便后续按置信度过滤或人工复核。5. 避坑与排查车牌识别项目里最容易翻车的五件事5.1 现象训练 loss 正常但推理框全歪原因标注文件里的坐标没有归一化或者归一化时除了错误的宽高。YOLO 格式要求x_center和width都除以图片宽度y_center和height除以图片高度很多人统一除以 640 或统一除以宽导致框偏移。解决写一个校验脚本随机抽 20 张图把标注框画出来和原图对比偏了就回去改转换代码。5.2 现象绿牌和黑牌识别率极低原因这两类车牌样本少识别层在 CTC 解码时偏向多数类输出全是蓝牌格式。解决在识别层训练时给绿牌和黑牌过采样或者用类别加权损失。后处理里对绿牌强制校验第三位是否为 D/F不通过就回退能救回一部分。5.3 现象同一辆车连续帧车牌号跳变原因检测框在相邻帧有轻微抖动裁剪出来的车牌图不一样识别层输出不稳定。解决加跟踪用 ByteTrack 或简单 IoU 匹配把同一辆车的检测框关联起来对同一 ID 的识别结果做投票取出现次数最多的字符串。这个改动对视频流场景提升非常明显。5.4 现象双层黄牌只识别出上半行原因检测层把双层车牌当成一个框裁剪时按水平框裁下半行被截断。解决检测层单独加一个“双层”类别或者后处理里对高宽比大于 2 的框做上下拆分分别识别再拼接。更稳的做法是训练一个关键点模型输出双层车牌的两行区域。5.5 现象模型在本地跑得好部署到边缘设备掉点严重原因边缘设备用 FP16 或 INT8 量化yolov7 的某些算子量化后精度损失大尤其是检测头部分。解决量化前先做校准集评估对比 FP32 和 INT8 的 mAP掉超过 2 个点就换量化策略或者只量化 backbone检测头保持 FP16。部署框架选 TensorRT 时注意版本和 CUDA 对齐版本不匹配会直接报错。6. 把 12 类车牌识别压到 30ms 以内一个可复现的优化技巧最后一章不讲大道理讲一个我实际用过的优化路径。目标是在 T4 或 Jetson 这类设备上把“检测 识别 后处理”整条链路压到 30ms 以内满足视频流实时处理。核心思路不是换模型而是把计算图拆开让检测和识别异步跑。第一步检测层用 TensorRT 做 FP16 推理yolov7 的输入分辨率从 640 降到 512车牌本身是小目标512 在多数场景下召回只掉 1 个点左右但速度提升接近 40%。第二步识别层不要每帧都跑用跟踪结果做缓存。同一辆车在连续帧里车牌号不变识别一次缓存 30 帧后续帧直接复用。第三步后处理规则用 C 重写Python 的字符串校验在批量处理时开销不小尤其是 12 类规则分支多的时候。下面是我用来验证优化效果的计时脚本按阶段打点方便定位瓶颈。import time import numpy as np def benchmark(pipeline, image, warmup10, runs50): # 预热避免首次推理的初始化开销干扰 for _ in range(warmup): pipeline(image) times {det: [], rec: [], post: []} for _ in range(runs): t0 time.perf_counter() boxes, scores pipeline.detect(image) t1 time.perf_counter() texts pipeline.recognize(image, boxes) t2 time.perf_counter() results pipeline.postprocess(texts) t3 time.perf_counter() times[det].append((t1 - t0) * 1000) times[rec].append((t2 - t1) * 1000) times[post].append((t3 - t2) * 1000) for k, v in times.items(): print(f{k}: mean{np.mean(v):.2f}ms p99{np.percentile(v, 99):.2f}ms)逻辑说明预热 10 次让 GPU 和缓存进入稳定状态跑 50 次取均值和 p99p99 比均值更能反映卡顿。参数说明warmup10和runs50可以根据设备调整边缘设备建议 runs 不少于 100。打点分三段如果 det 占比超过 60%优先优化检测输入分辨率或换更小的 yolov7 变体如果 rec 占比高检查是不是每帧都在跑识别该加缓存了。我自己的习惯是每次改完优化策略先跑这个脚本看 p99p99 没降下来就不算优化成功。均值好看但 p99 高说明有偶发卡顿视频流场景里这种卡顿比平均慢更致命。这套流程我在三个项目里复用过最稳的收益来自识别缓存和检测分辨率调整量化反而要谨慎翻车概率不低。希望帮到你。本文还有配套的精品资源点击获取