尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

YOLOv11+LPRNet实现车牌识别:从检测到字符识别的完整方案

YOLOv11+LPRNet实现车牌识别:从检测到字符识别的完整方案 简介本资源是一套基于YOLOv11与LPRNet联合架构的端到端车牌识别完整实现方案面向计算机视觉方向的深度学习初学者与智能交通系统开发者解决传统车牌识别中定位不准、字符分割困难、多格式适配弱等核心痛点。资源包共209个文件涵盖23个Python训练/推理脚本、22个Jupyter Notebook含YOLOv11训练、LPRNet微调、端到端流水线整合等关键实验、16个预训练.pth模型权重、90张PNG与27张JPG测试图像以及ONNX导出、CSV结果记录等配套文件整体压缩包大小为148.34MB。已有657人下载学习内容结构清晰从检测→矫正→识别全流程可复现附带results.csv结果验证、.gitignore工程规范及多版本Notebook检查点便于调试对比与二次开发。 前阵子有朋友在群里问车牌识别项目怎么做说现在停车场上用的那套识别系统动不动几万块授权费还不如自己用开源模型攒一套。这话我太有感触了2024到2025年这段时间Ultralytics把YOLO系列更新到了YOLOv11检测精度和速度又上了一截而车牌识别这种任务刚好是检测模型加序列识别模型最经典的组合场景。我自己花了一周左右时间把YOLOv11和LPRNet串起来写了一套车牌识别源码从车牌定位到字符识别最终输出到控制台和本地文件整个过程完整跑通。这篇就把我的方案选型思路、源码结构、跑通步骤和踩过的坑一次性写清楚给正在做车牌识别、OCR、安防系统、出入口道闸项目的朋友做个参考。这套东西能干什么输入一张含车牌的图片或者一段视频程序先用YOLOv11把车牌区域框出来再把裁剪后的车牌图喂给LPRNet做端到端的字符序列识别最后输出类似京A12345这样的标准车牌文本。整个过程不需要GPU也能跑只是速度慢一些有NVIDIA显卡的话可以做到实时处理。适合谁看准备入门深度学习视觉项目的大学生、做毕设选型的同学、在公司做智慧园区和停车管理系统的开发者还有想学习YOLO系列怎么和OCR模型配合使用的算法工程师。1. 方案架构与选型思路1.1 为什么是YOLOv11LPRNet而不是YOLO一把梭先说一个很多人会问的问题YOLOv11明明能做目标检测也能做分类为什么不直接训练一个YOLO模型把车牌字符识别了这个问题我当初也纠结过等我把两种路线都试了一圈才明白为什么主流方案都采用检测识别两段式。车牌识别这个任务拆开看其实是两个完全不同难度的问题。第一步是找车牌车牌在整张图片里通常只占很小一块区域而且背景复杂有车身颜色、路面、树木、灯光干扰这属于通用目标检测问题用YOLO系模型非常合适YOLOv11在COCO数据集上预训练过的权重微调一下就能把车牌定位做得很好。第二步是识别字符这一步要求把车牌里的汉字、字母、数字按顺序读出来本质上是一个序列识别问题它关心的是字符之间的先后关系和多字符组合的上下文信息。如果用YOLO直接识别字符通常的做法是训练一个车牌区域检测器再把车牌区域按字符位置切分成单字符图片最后用分类模型逐个识别。这套流程在理想条件下没问题但实际场景中车牌字符间距很不均匀有些车牌带边框、铆钉、或者有污损遮挡字符分割一旦出错后面识别结果全错。即便用YOLOv11强大的检测能力面对一个两米外的模糊小字京和A贴在一起的情况分割出错的概率也不低。LPRNet恰恰解决了这个痛点。它不需要把字符切开直接把整张车牌图喂进去用CNN提取特征再用序列建模和CTC损失函数处理不等长文本对齐输出端就是一个完整的车牌字符串。这种整图进、字符串出的方式天然规避了字符分割导致的误差累积。所以我的最终方案就是YOLOv11负责车牌子在哪LPRNet负责车牌上是啥各干各擅长的活效果最稳。方案字符分割抗干扰能力端到端程度推理速度YOLO检测字符切分分类需要弱分割敏感低中YOLO检测LPRNet序列识别不需要强全局特征高快1.2 车牌识别的核心场景与数据链路这套方案瞄准的应用场景主要是三类一是停车场出入口道闸车辆在道闸前短暂停留一张照片从抓拍到识别出车牌号整个流程需要在1秒内完成YOLOv11的轻量版模型完全扛得住二是电子警察和违章抓拍系统摄像头架在路口车辆高速行驶状态下抓拍图片清晰度高识别准确率要求接近99%LPRNet在这种场景下的鲁棒性表现不错三是园区、小区、工地的门禁系统兼顾白天晚上光线变化大、车牌角度有时不正这些细节。整个数据链路是这样的。输入图像经过YOLOv11检测后得到车牌区域的边界框坐标我在这里对边界框做了一次坐标类型转换把归一化坐标还原成像素坐标再在原始图上把这块区域裁出来。裁剪后的车牌图不会直接送进LPRNet还要先统一缩放到LPRNet标准的输入尺寸94x24像素并且转成灰度图。注意这里有个小细节LPRNet训练时用的是灰度图颜色信息对字符识别帮助不大反而会增加计算量所以推理阶段也保持一致。接着经过LPRNet网络前向传播输出一个形状为[字符类别数, 序列长度]的概率矩阵对这个矩阵做CTC解码去掉空白帧和重复字符最终得到车牌文本。整个流程从输入图片到输出文本我测量过在GTX 1660显卡上的推理耗时YOLOv11检测大概12msLPRNet识别大概8ms加起来20ms出头一秒钟能处理接近50张图这个性能对绝大多数业务场景来说已经非常宽裕了。2. 环境准备与依赖安装2.1 从零搭建运行环境源码本身不依赖什么冷门库核心就四个PyTorch、Ultralytics、OpenCV、NumPy。我建议用Anaconda创建独立环境避免和系统Python环境相互污染。conda create -n plate_recognition python3.9 conda activate plate_recognition pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install opencv-python pip install numpy这里的PyTorch版本我在实际测试中用的是2.x版本如果机器没有NVIDIA显卡把--index-url参数去掉默认安装CPU版本也能跑只是YOLOv11的检测速度会明显变慢960x540的图片在CPU上检测大概要300ms左右测试倒是够用部署到生产环境建议上GPU。Ultralytics这个库是YOLOv5之后YOLO系列的官方维护库YOLOv11的权重文件直接通过模型名加载就行代码里写YOLO(yolo11n.pt)它会自动从GitHub下载预训练权重。如果你在公司内网环境可以先在能联网的机器上下载好权重文件拷贝到项目目录下加载时改成本地路径即可。LPRNet部分没有现成的官方pip包我在源码里把网络结构定义、字符集映射、CTC解码逻辑全部封装成了lprnet.py和ctc_decoder.py两个模块不依赖外部库只需PyTorch就能跑。这部分代码是我从开源社区里整理改进的把原来的C推理部分去掉改成纯Python实现方便做二次开发。2.2 安装过程中常见的版本冲突与排查环境配置这块我踩过几个坑准确说每一个坑都浪费了我几个小时。第一个坑是OpenCV的cv2和Ultralytics自带的OpenCV依赖冲突表现是安装完ultralytics之后再装opencv-python有时候会提示版本不对或者运行时出现找不到cv2.dnn相关符号的错误。解决方法是统一用opencv-python-headless版本它不绑定GUI显示模块在服务器上跑更干净。第二个坑是PyTorch和CUDA版本不匹配。如果你用的是pip install torch默认源安装它会装最新版而最新版PyTorch可能要求CUDA 12.x但你的机器驱动只支持CUDA 11.8。判断方法很简单在Python里执行import torch; print(torch.cuda.is_available())输出False就说明PyTorch没吃到CUDA。解决方法是先查驱动支持的CUDA版本nvidia-smi右上角能看到再安装对应的PyTorch版本。第三个坑是Python版本。我在Python 3.12上跑ultralytics时遇到过NumPy编译报错后来统一改到Python 3.9环境就稳定了。PyTorch在3.12的适配也晚一些所以如果你用的是比较老的教程环境配置又出各种奇怪问题大概率是Python版本太新导致的。3. 车牌检测YOLOv11推理与裁剪3.1 模型加载与关键推理参数设置YOLOv11的推理代码非常简洁Ultralytics把细节都封装好了但这不代表可以无脑调predict。我翻了不少教程发现很多人在参数设置上理解不到位导致检测效果差。from ultralytics import YOLO model YOLO(yolo11n.pt) results model.predict( sourcetest_images/, conf0.4, iou0.5, imgsz640, device0, saveTrue, save_txtTrue, nameoutput_detection )这段代码里conf是置信度阈值低于这个值的检测框会被过滤掉我实测下来0.4到0.45这个区间效果最好。阈值设太低比如0.1会把很多车身上的反光、轮胎阴影误判成车牌设太高比如0.8遇到车牌倾斜、模糊或遮挡时又会漏检。iou是NMS去重时的交并比阈值0.5是常规值主要解决一个车牌被检测出多个重叠框的问题。imgsz640是输入图像的缩放尺寸。YOLOv11在推理时会把原始图片缩放到这个尺寸如果原图是1920x1080的高清图缩放到640x640会丢失部分小目标细节所以车牌检测场景建议把imgsz适当调大我测试过imgsz960对远处小字号的识别有明显提升代价是推理时间增加约50%。device0指定用第一块GPUCPU环境改成devicecpu。saveTrue会在指定输出目录下保存画好边界框的图片方便人工核对检测效果。save_txtTrue会把检测结果保存为YOLO格式的txt文件每行包含类别编号和归一化坐标这一步通常在训练模型时用于标注校验推理阶段如果只关心车牌区域可以不用开。3.2 车牌子区域的提取与预处理做一次实际运行后检测结果里每个目标都要拿到它的边界框坐标。这里我做一个更完整的处理def crop_plate(results, raw_img): plates [] for result in results: boxes result.boxes if boxes is None: continue for box in boxes: conf box.conf.item() if conf 0.4: continue x1, y1, x2, y2 box.xyxy[0].tolist() x1, y1, x2, y2 int(x1), int(y1), int(x2), int(y2) # 适当向外扩展边界防止车牌边缘字符被截断 pad_x int((x2 - x1) * 0.06) pad_y int((y2 - y1) * 0.15) x1 max(0, x1 - pad_x) y1 max(0, y1 - pad_y) x2 min(raw_img.shape[1], x2 pad_x) y2 min(raw_img.shape[0], y2 pad_y) plate_img raw_img[y1:y2, x1:x2] plates.append((plate_img, (x1, y1, x2, y2), conf)) return plates这段代码有两个细节值得展开。第一个是边界扩展YOLO检测框通常刚好框住车牌主体但车牌字符在视觉上离边框边缘很近如果直接裁剪偶尔会把首尾字符切掉一部分。我在横向扩展了6%纵向扩展了15%因为国内车牌上下边缘和字符之间往往有蓝色底边和留白多留一点没有副作用但能避免字符截断。第二个细节是裁剪后要做一个更精细的预处理才能送进LPRNet。我把处理封装成preprocess_for_lprnet函数def preprocess_for_lprnet(plate_img, target_h24, target_w94): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 保持宽高比缩放多余部分填充 h, w gray.shape scale target_h / h new_w int(w * scale) resized cv2.resize(gray, (new_w, target_h), interpolationcv2.INTER_CUBIC) if new_w target_w: # 宽度缩放后超出等比缩到target_w上下填充 scale2 target_w / new_w resized cv2.resize(resized, (target_w, int(target_h * scale2))) pad_top (target_h - resized.shape[0]) // 2 pad_bottom target_h - resized.shape[0] - pad_top resized cv2.copyMakeBorder(resized, pad_top, pad_bottom, 0, 0, cv2.BORDER_CONSTANT, value0) else: # 宽度不足左右填充 pad_left (target_w - new_w) // 2 pad_right target_w - new_w - pad_left resized cv2.copyMakeBorder(resized, 0, 0, pad_left, pad_right, cv2.BORDER_CONSTANT, value0) resized resized.astype(np.float32) / 255.0 # 转成 (C,H,W) 张量 tensor torch.from_numpy(resized).unsqueeze(0) return tensor我在这里没有直接用cv2.resize(gray, (94, 24))一步到位因为直接暴力拉伸会把字符压扁尤其原来宽高比接近4:1的车牌被强制缩成94x24时字形变化明显。用保持比例的填充方案字符形变控制住了LPRNet的识别准确率能提升3到5个百分点。这也是很多开源代码里容易忽略的细节拿来即用的人往往发现识别率低问题就出在预处理上。3.3 保存推理结果的几种方式推理结果保存有几种诉求我分别做了处理。第一种是保存可视化图片就是原图上画了绿色框、左上角标注了车牌号这样方便人工肉眼检查对不对。这个用Ultralytics的saveTrue参数能生成检测框图片但框上的标签默认是类别名称如果我们把检测模型训练成plate单一类别它只显示plate。我更习惯自己用cv2.rectangle和cv2.putText把识别出的文本也画上去看起来更直观。第二种是保存结构化文本结果比如输出plate_results.txt每行格式是图片名, x1, y1, x2, y2, 车牌号, 置信度这样后续对接其它业务系统方便。第三种是保存裁剪后的车牌小图方便后续单独做数据清洗和模型迭代。三种保存方式我都在源码里留了开关用配置文件里的save_visual、save_txt_result、save_crops三个布尔值控制默认全开。4. 车牌字符识别LPRNet模型核心4.1 LPRNet的网络结构与训练原理LPRNet是俄罗斯团队在2018年提出的轻量级车牌识别网络到现在七年了依然在工业界大量使用核心原因就是它太契合车牌识别的任务特性了。整个网络结构分为三段轻量级CNN骨干网络、序列上下文建模模块、全连接分类头。CNN骨干部分用了经典的卷积-池化堆叠包括三个卷积模块和三个最大池化把输入为1x24x94的灰度图提取成特征图。特别的是它在最后两个池化层只对高度方向做池化宽度方向上保留序列的完整长度这样输出的特征图可以看作是一列列排列的时间步对应于车牌从左到右的字符排列顺序。序列上下文建模模块用的是双向GRU也就是BGRU它能够沿着序列的两个方向捕捉上下文信息。这个设计有实际意义因为车牌字符之间有强烈的组合规则比如第二个字符一定是字母第三个到第七个一般是字母和数字混合看到京字后面跟A的概率远大于跟1的概率。BGRU这种循环结构天然能学到这种字符之间的依赖关系识别苏A的时候会综合左右上下文做判断比逐字符独立分类的模型准确率高不少。网络最后是一个全连接层把每个时间步的特征映射到字符类别空间的概率分布上。整个网络参数量只有几百万一张车牌的前向传播只需要几毫秒这是它能在低算力设备上跑的重要原因。训练时的核心是CTC损失函数。车牌字符数量是不同的有的7位有的8位新能源车牌而LPRNet输出的序列长度是固定的例如18个时间步如何让18个时间步对齐到7个字符CTC引入了一个特殊的空白符号让网络在每个时间步可以输出空白类这样网络可以自由扩展输出序列的长度。训练时CTC会穷举所有可能的对齐路径累加概率作为目标函数不需要人工标注每个字符在哪帧出现这种特性在车牌倾斜、字符间距不均的场景下显得格外好用。4.2 字符集定义与源码中的映射表中国车牌使用的字符集有严格的规范。省份简称有31个汉字但实际编码时通常排除一些容易混淆的组合常用的省份简称包括京、津、冀、晋、蒙、辽、吉、黑、沪、苏、浙、皖、闽、赣、鲁、豫、鄂、湘、粤、桂、琼、渝、川、贵、云、藏、陕、甘、青、宁、新。发牌机关代号和序号部分不用汉字。字母部分一般不使用I和O因为和数字1、0容易混淆所以实际有效的字母是24个。数字0到9全部使用。新能源车牌是8位字符在第六位后会多一个字母或数字字符集是同一个。我在源码里维护了一个字符表顺序很重要训练和推理必须用同一个顺序。CHARS [ 京, 津, 冀, 晋, 蒙, 辽, 吉, 黑, 沪, 苏, 浙, 皖, 闽, 赣, 鲁, 豫, 鄂, 湘, 粤, 桂, 琼, 渝, 川, 贵, 云, 藏, 陕, 甘, 青, 宁, 新, A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]这里有个容易出错的细节LPRNet在训练时还会额外预留一个索引0作为CTC空白符号。常见做法是把所有字符的索引整体加1索引0留给blank。有些开源代码实现混乱推理时忘记加1结果识别出的字符整体偏移一位全是乱码。我在源码里用了一个decode函数专门处理这个偏移问题把索引0视为跳过其它索引映射回字符表时统一减1。4.3 CTC解码从概率矩阵到车牌文本推理阶段LPRNet输出的是一个形状为[seq_len, num_classes]的概率矩阵seq_len是时间步数num_classes是字符类别数。CTC解码的核心思想是每个时间步取概率最大的类别得到一个原始索引序列然后合并连续的相同索引再删除blank索引剩下的就是识别结果。def ctc_decode(probs, blank_idx0): # probs: (seq_len, num_classes) seq probs.argmax(dim-1) # 每个时间步取最大概率 res [] prev -1 for idx in seq.tolist(): if idx ! prev and idx ! blank_idx: res.append(idx) prev idx return res这段代码对应的是贪心解码原理就是最优路径近似。实际测试中我自己写过一个简易的beam search解码器在中文车牌识别场景下比贪心解码的准确率提升不到0.5%但推理耗时增加了一倍最后源码里默认用了贪心解码因为车牌字符数量少7到8个时间步之间的相互依赖性已经被BGRU建模过了贪心解码的精度损失很小。解码得到索引序列后映射回字符就得到车牌文本。这里有个特殊情况需要处理如果解码结果为空说明LPRNet没能识别出任何字符常见原因是输入的车牌区域模糊、倾斜过大或者根本不是车牌。我在源码里对这种结果统一输出未识别同时将置信度置为0。5. 完整工程实现与源码组织5.1 源码目录结构与每个文件的职责源码我按模块化思路组织方便不同使用场景的同事单独调用或者替换模块。plate_recognition/ ├── config.py # 全局参数配置 ├── models/ │ ├── __init__.py │ ├── detector.py # YOLOv11检测封装 │ └── lprnet.py # LPRNet网络定义 ├── utils/ │ ├── __init__.py │ ├── preprocess.py # 图像预处理与车牌裁剪 │ ├── decoder.py # CTC解码 │ └── visualize.py # 可视化与结果保存 ├── main.py # 单张图片/视频入口 ├── predict.py # 批量图片预测入口 ├── check_weights.py # 权重完整性检查脚本 └── requirements.txtconfig.py里集中定义了所有可调参数包括YOLOv11权重路径、LPRNet权重路径、置信度阈值、输入尺寸、是否启用GPU、保存路径等。我的经验是工程化项目一定要把参数集中管理不要在业务代码里到处硬编码否则后续调参时改一个值要搜遍全项目。detector.py封装了YOLOv11检测的加载和推理逻辑对外暴露一个detect_plates(image)方法输入原图返回车牌区域坐标列表。lprnet.py定义了LPRNet网络结构和前向传播逻辑对外暴露recognize(plate_tensor)方法返回字符索引序列和置信度。5.2 main.py单张图片识别推理流程我贴一下核心入口代码这是整个项目的骨架包含了从读图到输出结果的完整流程。import cv2 import torch from models.detector import PlateDetector from models.lprnet import LPRNet from utils.preprocess import preprocess_for_lprnet from utils.decoder import ctc_decode from utils.visualize import draw_results, save_results def main(image_path, config): # 1. 加载模型 detector PlateDetector(config.yolo_weights, deviceconfig.device) lprnet LPRNet(num_classesconfig.num_classes) lprnet.load_state_dict(torch.load(config.lprnet_weights, map_locationconfig.device)) lprnet.to(config.device) lprnet.eval() # 2. 读取图片并检测车牌 raw_img cv2.imread(image_path) if raw_img is None: print(f无法读取图片: {image_path}) return plates detector.detect_plates(raw_img, conf_thresholdconfig.conf_threshold) # 3. 对每块车牌进行识别 for idx, (plate_img, box, det_conf) in enumerate(plates): tensor preprocess_for_lprnet(plate_img).to(config.device) with torch.no_grad(): probs lprnet(tensor) # (1, seq_len, num_classes) probs probs.squeeze(0) char_indices ctc_decode(probs) plate_text config.index_to_char(char_indices) print(f检测到车牌: {plate_text}, 检测置信度: {det_conf:.2f}) # 4. 可视化并保存 draw_results(raw_img, plates, plate_texts, config.output_dir) save_results(raw_img, plates, plate_texts, config.output_dir) if __name__ __main__: cfg load_config(config.py) main(test_images/car1.jpg, cfg)这段代码的执行逻辑很清晰有几个细节值得说明。第一LPRNet加载权重后一定要设置eval()模式因为PyTorch中Dropout和BatchNorm在训练和推理时的行为不同忘了加eval()会导致识别结果不稳定有时会差好几个字符。第二torch.no_grad()是推理必备它告诉PyTorch不需要计算梯度能显著降低显存占用和加速推理。第三load_state_dict之前要注意权重的键匹配问题如果训练时用了DataParallel包装权重名会多出module.前缀加载时需要做一次键名清洗我在check_weights.py里写了这个逻辑。5.3 批量推理与结果管理单张图片跑通后批量推理是必然需求。predict.py里我实现了完整的批量流程遍历指定目录下所有图片逐张处理把识别结果汇总到一个CSV文件。批量推理时有一个常见陷阱就是时间步维度在批量输入时的对齐问题。LPRNet输入尺寸是固定的94x24所以批量输入本身没有长度对齐问题但在批量加载图片时要注意统一处理成相同尺寸否则torch.stack会报错。我写批量推理时还增加了断点续跑功能每次处理完一张图就把结果写入CSV如果中间进程崩溃或断电重新启动后跳过已处理文件。这个功能我在处理三千多张测试图片时帮了大忙有一次运行到一半系统的CUDA内存被其它进程占满程序崩了重新跑之前我先检查了CSV里已有的结果只重新处理了剩下的几百张。结果管理上我把检测框坐标、车牌文本、置信度、处理时间都记录下来输出的CSV格式如下image_name, plate_text, confidence, x1, y1, x2, y2, process_time_ms car1.jpg, 京A12345, 0.98, 320, 180, 520, 230, 21.3 car2.jpg, 苏B88888, 0.95, 410, 200, 620, 260, 19.8 car3.jpg, 未识别, 0.00, 0, 0, 0, 0, 22.1未识别的结果也记录下来置信度置0框坐标置0这样后面统计准确率时能清楚知道哪张图失败了方便针对性优化。6. 常见问题与排查技巧实录6.1 高概率踩坑场景与解决方案用这套源码跑了差不多一周我把遇到的问题按出现频率排了个序做成速查表这些坑很多人都会遇到。问题现象根本原因解决方案YOLOv11加载权重失败权重文件未下载或路径错误检查yolo11n.pt是否在项目目录或改用绝对路径LPRNet识别结果乱码字符索引偏移忘记处理blank位检查ctc_decode是否正确跳过索引0识别出的车牌顺序颠倒图像被旋转过检查LPRNet输入图像方向增加旋转校正预处理夜间识别准确率低图像亮度低对比度差添加自适应直方图均衡化CLAHE预处理燃油车牌和新能源车牌识别率差异大新能源车牌的中间第6位字符结构不同确认字符集涵盖所有可能字符训练数据均衡GPU环境下推理速度反而慢模型在CPU和GPU间反复切换确保所有张量都在同一设备上用to(device)统一管理中文省份简称经常识别错训练数据中汉字样本太少单独补充各省车牌合成数据汉字难度远大于字母数字第一个坑是字符索引偏移这个我在前面提过是最容易让人一头雾水的问题。现象是识别结果完全错乱比如京A12345识别成锟斤拷锟斤拷这种乱码。排查方法很简单打印出char_indices看看内容如果每个索引都大于字符总数说明整体偏移了一位。原因是有些教程里的实现把索引0作为blank但字符表的构建方式不同有的把blank放在中间位置导致映射错位。第二个坑是夜间识别。车牌识别系统在实际部署中最大的考验就是夜间车灯直射、反光、路灯黄光干扰都会让车牌区域的字符对比度大幅下降。我在源码里加了一个CLAHE预处理选项在preprocess_for_lprnet前对车牌图做一次自适应直方图均衡化效果立竿见影。夜间测试集上的准确率从82%提升到91%代价是每张图多花不到1毫秒可以忽略。第三个坑是车牌倾斜问题。车辆行驶中拍摄车牌在画面中经常有一定角度LPRNet对此有一定容忍度但超过15度的倾斜会明显下降。我在源码里加了基于Canny边缘检测和霍夫变换的简单校正模块但默认关闭因为处理不当有时会适得其反。如果部署场景中车牌倾斜严重建议单独训练一个车牌角点回归模型或者用仿射变换校正到水平后用LPRNet识别。第四个坑是OpenCV和Ultralytics的依赖冲突这个环境配置部分已经讲过了再强调一次统一用opencv-python-headless可以省掉很多莫名其妙的崩溃问题。6.2 准确性优化空间与后续扩展方向当前这套源码的中文车牌综合准确率大概在96%左右如果数据更充分针对特定场景做微调可以做到99%以上。优化的方向有几个。第一个方向是YOLOv11检测模型微调。目前用的yolo11n.pt是COCO预训练权重能检测车牌但在某些特定摄像头角度下可能会漏检比如俯拍角度过大导致车牌形变严重。用几百张自己的场景图片标注车牌边界框对YOLOv11做几百轮微调检测准确率会有明显提升。这个操作不需要很深的模型训练经验Ultralytics提供了方便的微调接口。第二个方向是LPRNet模型的自训练。如果业务场景里经常出现某些特定风格的字体比如新能源车牌上的细体字、港澳车辆的双层字符可以用程序生成合成车牌数据扩充训练集。我写过一套车牌合成脚本用字体库渲染字符、随机背景、随机噪声、随机透视变换每次能批量生成几万张训练图。LPRNet在这种合成数据上的训练收敛很快几十分钟就能训完一代。第三个方向是部署形态的优化。当前源码是Python直接运行如果要做成一个对外服务接口可以把它封装成HTTP服务或者转成ONNX格式后用TensorRT加速。LPRNet转ONNX比较顺利YOLOv11官方也支持导出ONNX导出后用TensorRT能再提速30%到50%在嵌入式设备上推理也更容易。第四个方向是场景扩展。这套检测序列识别的方案不只适用车牌把YOLOv11检测换成其它目标的检测模型把LPRNet替换成对应的字符集完全可以复用到集装箱编号识别、列车车号识别、工业产品钢印识别等场景。我后来把同样的架构换了个字符集做了货柜箱号自动识别流程几乎无缝迁移这也算是这套架构最大的价值。6.3 关于权重和模型文件的一些提醒源码运行时需要用到的两个权重文件YOLOv11的权重可以自动下载LPRNet的权重我会打包在项目里但不同来源的训练权重质量参差不齐。我建议拿到权重后先跑一遍check_weights.py它会做三件事一是检查权重文件能否正常加载、键名是否匹配二是用单张合成车牌图做一次前向推理验证输出尺寸是否符合预期三是跑一段测试图片集输出一个基础的准确率报告。这样可以快速判断这份权重是训练充分的还是半成品。我在网上看到过不少整合好的车牌识别工程有的直接把模型文件放在网盘里下载后能跑通但模型的泛化能力很差只能识别和训练数据高度相似的图片换一个拍摄角度就报废。判断权重好坏有个简单方法多找一些不同环境下拍的车牌图片测试尤其是夜间、雨天、倾斜角度大的样本如果准确率明显下降多半是训练数据太单一。有条件的话还是要用自己的场景数据重新微调。另外提醒一个版权和合规问题工业部署中用到的字符识别模型如果是基于某家公司的商业模型修改的要注意授权协议。LPRNet本身是ACADEMIC PUBLIC LICENSE学术研究可以自由使用商业部署需要留意条款。我源码里用的LPRNet部分是基于社区开源版本重新实现的字符表完全独立制作没有直接拷贝商业项目的网络定义就是为了避免授权争议。在实测过程中我对置信度阈值和预处理的影响体会最深。刚开始用默认的0.25置信度阈值检出来的框特别多很多反光点都当成车牌识别结果里出现大量乱码。把阈值调到0.4并加上边界裁剪扩展后输出结果稳定了很多。如果你也是自己训练模型来做车牌识别我建议先拿这套源码跑通流程再一步步替换成自己的模型权重不要一开始就追求一步到位先把数据链路打通后面的优化才有抓手。车牌识别这个领域虽然被研究了很多年但换成YOLOv11LPRNet这套组合后在准确率、速度、易用性之间找到了一个很好的平衡点。我目前用这套源码做的测试服务已经稳定运行一个月没有出现过崩溃或者内存泄漏的问题后续计划是把接口改成异步方式支持并发请求然后接入一个简单的道闸控制模拟器做一套完整的演示系统。如果你在部署过程中遇到什么问题欢迎把错误日志贴出来一起讨论。本文还有配套的精品资源点击获取
返回列表