ARTICLE DETAIL

资讯详情

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

基于UNet+CNN的车牌识别源码:语义分割与字符识别实战

基于UNet+CNN的车牌识别源码:语义分割与字符识别实战 简介基于Python与OpenCV实现的车牌识别系统毕业设计源码包面向计算机、电子信息类专业学生可作为毕业设计、课程设计或期末大作业的完整参考同时兼顾教学演示与实际应用。项目代码采用模块化设计配有训练好的深度学习模型权重部署门槛低即便刚接触图像识别的新手也能较快运行。压缩包共15个文件整包约26.7MB其中9个Python脚本覆盖数据预处理、模型训练、车牌检测识别以及图形界面交互等环节2个模型权重文件提供已训练完成的卷积神经网络与U型网络可直接用于字符识别和语义分割另有3个动态图和1个演示视频分别展示不同真实场景下的运行效果直观呈现从车牌定位、字符分割到最终识别的完整流程。资源已有635人学习下载实用价值得到一定认可。通过本源码包可以系统掌握车牌定位、字符分割、字符识别等核心算法理解传统图像处理与深度学习相融合的实现方案项目自带可视化界面和现成模型支持图片或视频输入方便直接运行调试也便于在此基础上进行二次开发满足毕业设计答辩、课程实践或功能扩展需求。1. 不是边缘检测那套老路一个UNetCNN两阶段车牌识别源码毕业设计撞上车牌识别系统的同学多半会在 OpenCV 的边缘检测和轮廓查找上耗掉两周然后被精度反复按在地上摩擦。这份基于 Python 与 OpenCV 的车牌识别系统源码走的是另一条路用 UNet 做车牌区域语义分割再用 CNN 做字符分类两个预训练好的 h5 权重文件直接加载配合 GUI 和视频处理工具链下载解压就能跑出识别结果。它不教你从零搭建算法而是把一条完整的“分割—识别—可视化”通路摆在面前适合毕设、课程设计和期末大作业拿来直接部署、二次开发。2. 技术路线拆解为什么上一代 OpenCV 方案容易翻车而这套源码用分割打底2.1 为什么是 UNet 而不是传统轮廓检测网上能找到的车牌识别教程十有八九是这套流程灰度化、高斯模糊、Sobel 边缘检测、二值化、形态学闭运算、轮廓查找、按宽高比筛候选区。这套流程在“正面、光照均匀、背景干净、车牌无遮挡”的照片上确实能跑出不错的效果但一旦换成傍晚逆光、车身反光、车牌有污渍或者拍摄角度带点倾斜阈值参数就要重新调一遍。调参调到头大的时候你会发现真正的问题不是参数而是“边缘检测”这个底层假设太脆弱——复杂背景下车牌边缘根本连不成一个干净的矩形。UNet 是语义分割网络输入整张图输出每个像素属于车牌的置信度相当于把“找车牌”从“找边缘围成的矩形”变成了“把车牌像素从背景里抠出来”。两种思路的鲁棒性差距很大分割方式对光照、畸变、倾斜的容忍度高得多。这份源码里的 unet.h5 就是已经训练好的分割权重load_model 之后直接 predict 就能拿到 mask不需要自己重新训练这也是它“下载即用”的核心原因。2.2 文件结构与模块职责映射拿到压缩包先别急着跑先把文件分工搞清楚。这套源码里既有训练脚本又有推理脚本还有两个 GUI 和视频处理工具如果不看结构很容易把 train.py 当成入口然后对着 CPU 干等。以下是我的理解文件模块定位在流程中的角色unet.h5车牌分割模型权重输入整图输出车牌区域 maskcnn.h5字符识别模型权重输入单字符图输出字符类别core.py推理主逻辑串联“分割→裁剪→字符切分→分类”Unet.py分割网络结构定义训练时构建 UNet也可配合 h5 查看结构CNN.py字符分类网络结构定义训练时构建 CNN 分类器train.py训练入口加载数据、训练、保存 h5 权重imgGUI.py单张图片识别界面选图、显示结果、可视化 maskvidGUI.py视频识别界面打开视频或摄像头逐帧识别GUI.py综合界面图片/视频识别功能的整合入口videospilt.py视频拆帧工具把 mp4 按帧拆成图片序列imgtovid.py帧合成视频工具把处理后的帧拼回视频demo_car.mp4演示视频复现效果时用的标准输入1.gif / 2.gif / 3.gif效果演示动图快速预览识别效果这个结构的好处是训练体系和推理体系是分开的。做毕设答辩演示只需要 core.py 加两个 h5外加 imgGUI.py 或 vidGUI.py 做界面展示想二次开发换场景才需要碰 train.py 和两个网络定义文件。这一点先想清楚后面不会迷路。2.3 unet.h5 与 cnn.h5 的配合逻辑两个 h5 的分工简单说就是UNet 回答“车牌在哪”CNN 回答“每个字符是什么”。unet.h5 加载后输入是一张完整图片输出是跟输入同尺寸或经过缩放到原图尺寸的概率图每个像素的值在 0 到 1 之间越接近 1 表示越可能是车牌区域。拿到这个概率图后做一次阈值化再找连通域就能把车牌区域从原图里裁剪出来。cnn.h5 接收的是单个字符的小图输出是各类别的概率分布。注意这里有个关键点cnn.h5 的分类类别顺序是在训练时固定的你的字符集合包含哪些省份汉字、哪些字母和数字就直接决定了输出向量的长度。用的时候要保证项目里 classes 列表的顺序和训练时一致否则会出现“数字字母全对、省份字全错”的诡异现象——这个坑后面避坑章节细说。两个模型配合时最常见的报错是 predict 时提示输入 shape 不匹配。原因基本只有一个输入图片没有缩放到训练时指定的尺寸或者通道顺序没处理好。OpenCV 读进来是 BGR模型训练时用的多半是 RGB转换这一部漏掉颜色语义就反了识别率会明显下降。3. 推理链路代跑从一张图到一串车牌号再到视频逐帧处理3.1 core.py 的典型识别管线我拿到这套源码后第一件事是直接打开 core.py把它的主流程梳理成下面这样。你不一定需要逐行读懂但要看明白数据是怎么流动的# core.py 的核心识别函数结构与常见毕设实现一致 def recognize_plate(img_bgr): # OpenCV 读进来是 BGR模型训练时用的多半是 RGB img_rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # 缩放到 UNet 期望的输入尺寸车牌是宽扁形状所以宽 高 input_tensor cv2.resize(img_rgb, (256, 64)) input_tensor input_tensor.astype(float32) / 255.0 input_tensor np.expand_dims(input_tensor, axis0) # 1. UNet 分割车牌区域输出 [1, H, W, 1] 的概率图 mask unet_model.predict(input_tensor)[0, ..., 0] mask cv2.resize(mask, (img_bgr.shape[1], img_bgr.shape[0])) plate_img crop_plate(img_bgr, mask 0.5) # 2. 字符分割垂直投影切出每个字符 char_list split_chars(plate_img) # 3. CNN 逐个字符识别拼成结果 result for char_img in char_list: char_img cv2.resize(char_img, (32, 32)) char_img char_img.astype(float32) / 255.0 pred cnn_model.predict(char_img.reshape(1, 32, 32, 1))[0] result classes[np.argmax(pred)] return result这段逻辑理清之后整条识别链路就很简单了先分割再裁剪再切字符最后分类。这里面有两个参数值得关注。第一个是 mask 0.5 这个阈值它决定“多像车牌才算车牌”默认 0.5 够用如果发现分割结果偏大或偏小可以往 0.4 或 0.6 调。第二个是字符图 resize 到 32×32这是 CNN 训练时的输入尺寸如果换模型或者重新训练这个尺寸必须跟训练时一致否则 predict 会直接报 shape 不匹配。3.2 从 mask 到单个字符连通域、外接矩形与垂直投影UNet 输出的 mask 只是一张概率图要把车牌区域真正切出来还需要几步后处理。我一般会用 cv2.connectedComponentsWithStats 找连通域然后取面积最大的那个作为车牌区域如果车牌本身有点倾斜用 cv2.minAreaRect 拟合一个旋转外接矩形再做一次仿射变换把车牌矫正成水平正放字符识别率会明显提高。字符分割是另一个容易出问题的地方。常见做法是垂直投影把车牌区域转成灰度图、二值化之后按列统计黑色像素的数量列投影为 0 的空隙就是字符边界。实际操作中要注意两点一是车牌边框会干扰投影严重时会把边框误判成一个字符所以切分前先去掉最外圈几个像素二是铆钉和第二个字符与第三个字符之间的圆点这类小噪声会在投影图上造成伪边界建议先做一次中值滤波再投影。顺序也很关键先切割、再识别。如果字符粘连比较严重切出来的图可能同时包含两个字符CNN 无论如何都会给一个错误分类与其优化模型不如先回头看看分割这一步是否把字符边界切开。这部分没有万能参数我看到的效果是 24×48 或 32×32 的单字符输入都能用关键是训练和推理保持一致。3.3 图片入口与视频入口imgGUI.py 和 vidGUI.py 怎么跑起来跑起来很容易命令行执行 python imgGUI.py 会弹出图片识别窗口选一张图就能看到原图、mask 和最终识别结果python vidGUI.py 则打开视频识别界面可以加载 demo_car.mp4 或者直接调摄像头。这里要提醒一下GUI 里的模型加载是写在初始化部分的第一次打开界面会卡几秒那是 h5 在加载不是死机。视频识别不是直接对着视频流一帧一帧跑而是借助了 videospilt.py 和 imgtovid.py 两个工具。先 videospilt.py 把 demo_car.mp4 按帧拆成一张张图片然后对关键帧做识别最后 imgtovid.py 把带识别结果的帧重新合成视频。之所以拆成两步是因为逐帧跑 UNet 推理很慢拆开后可以指定帧区间比如只处理第 100 到第 200 帧哪一段出问题就重跑哪一段不用从头再来。这也是我比较喜欢这个项目结构的原因中间产物是图片方便调试和可视化。4. 训练脚本二次开发train.py 怎么改、数据怎么组织、权重怎么换4.1 Unet.py 与 CNN.py 网络结构速读如果你只是做毕设演示两个 h5 直接加载就行不需要碰网络定义。但如果你想写进论文里或者想重新训练一版适配自己的数据就得知道这两个文件里是什么。Unet.py 里是标准的 U-Net 结构编码器部分逐层下采样提取特征解码器部分逐层上采样恢复分辨率中间用 skip connection 把同尺度的特征拼接起来这是分割任务的经典结构。CNN.py 里是字符分类网络通常由几层卷积加池化提取特征最后接全连接层和 softmax 输出类别概率。最快的验证方式是加载 h5 后执行 model.summary()它会打印每一层的名称、输出 shape 和参数量。我拿到手就习惯先跑一遍这个确认输入尺寸和类别数是否跟我预期的一致。很多“模型加载失败”的问题其实在这个阶段就能看出来——比如 unet.h5 期望的输入是 256×64你却喂了 128×128那 predict 必然报错。4.2 train.py 跑通的先决条件数据目录与关键超参数train.py 是训练入口但它不像推理那样开箱即用因为训练必须依赖数据。常见的组织方式是 data/ 目录下按类别分子文件夹比如 data/train/粤/、data/train/A/、data/train/1/每个子文件夹放对应字符的图片然后用图像生成器批量喂给模型。下面是一段典型的训练配置逻辑# train.py 中常见的数据加载与训练回调 from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rotation_range5, # 随机旋转 ±5 度 width_shift_range0.05, # 水平平移 5% height_shift_range0.05, # 垂直平移 5% zoom_range0.1, # 随机缩放 10% ) train_gen train_datagen.flow_from_directory( data/train, target_size(32, 32), batch_size32, class_modecategorical ) # 训练时保存验证集上最优的权重epochs 到 patience 次没提升就停 checkpoint ModelCheckpoint(cnn.h5, monitorval_loss, save_best_onlyTrue) early_stop EarlyStopping(patience10, restore_best_weightsTrue) cnn_model.fit( train_gen, epochs100, callbacks[checkpoint, early_stop] )train_datagen 里的旋转、平移、缩放都是数据增强作用是让模型对轻微形变不敏感。对车牌字符来说rotation_range 不建议超过 5因为字符是有方向性的转多了会混淆 6 和 9、O 和 0。batch_size 在 CPU 上训练建议调小到 8 或 16不然内存容易爆epochs 设 100 不是必须跑满EarlyStopping 会在验证集连续 10 个 epoch 没有提升时自动停掉restore_best_weights 保证停掉时加载的是最优权重而不是最后一轮。4.3 换场景时的权重替换策略这套源码自带的两个 h5 是针对通用车牌场景训练的拿来做毕设演示完全够用。如果你想识别自己拍的照片建议先直接用原权重跑几组照片看看哪些失败了。如果只是个别图失败问题多半在后处理参数上如果成片失败再考虑重新训练。重训前务必先备份原始 h5。训练是个玄学过程跑出来的权重不一定比原来的好没有后悔药可吃。另一个常见做法是数据合成——用真实车牌字体生成大量带随机背景、随机噪声、随机角度的字符图用来扩充训练集。这个技巧在车牌识别里非常有效因为合成的字符标签是天然准确的省去了手动标注的痛苦。5. 部署避坑模型加载、中文路径、帧率问题的五个实战记录5.1 Unet 加载失败版本对不上的玄学现象执行 core.py 导入模型时报错常见信息有 Unknown layer、Weight shape mismatch或者直接一大堆堆栈 traceback。原因h5 文件本身是好的但保存这个权重时用的 TensorFlow/Keras 版本和你本地环境不一致。老版本训练的权重里有些层名、参数结构新版本已经不认了。解决先确认环境。我一般用 conda 建一个独立环境然后锁定 TensorFlow 2.6 或 2.10 这个区间去试这两个版本覆盖了大多数毕设项目的保存环境。如果还是不行就换一种加载方式先用 model_from_json 读 Unet.py 里的结构再 load_weights 只加载权重绕开整包加载时对版本的强校验。5.2 OpenCV 读不进中文路径cv2.imread 返回 None现象图片路径里带中文cv2.imread 不报错但返回 None后续代码直接崩。原因OpenCV 的 imread 底层用的是 C 标准库的文件读取不支持非 ASCII 路径。这在 Windows 上特别常见因为桌面或项目路径经常带“新建文件夹”“桌面”这类中文。解决不要用 cv2.imread改用 np.fromfile 读字节流再交给 cv2.imdecode 解码。import cv2 import numpy as np def cv_imread(path): # OpenCV 的 imread 遇到中文路径会静默返回 None # 先用 numpy 按字节读入再解码成图像 data np.fromfile(path, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) return img这段代码把“读文件”和“解码图像”拆成两步绕过了 imread 的路径限制。cv2.IMREAD_COLOR 表示按三通道彩色图读入如果后续要做灰度处理可以解码后再转。这个函数建议直接放到 core.py 或者单独的工具模块里凡是涉及用户选图的地方都统一用它读。5.3 视频识别卡成 PPT现象vidGUI.py 跑 demo_car.mp4画面一卡一顿像幻灯片。原因视频是 25 帧每秒但 UNet 每帧推理一次要几百毫秒CPU 上更慢处理速度跟不上视频播放速度。解决最常见的做法是跳帧识别比如每隔 3 帧识别一次中间 2 帧沿用上一次结果覆盖上去保证画面连贯又不至于卡死。另一个有效手段是把输入尺寸缩小UNet 的输入越小推理越快虽然 mask 精度略降但对视频演示来说完全够用。你甚至可以自己加一个参数控制跳帧幅度跳 1 帧偏流畅跳 5 帧偏跟手按机器性能调。5.4 汉字识别错、数字字母全对现象识别结果里数字和字母都对唯独第一个汉字经常错。原因两个常见可能。一是字符数据不平衡训练集里某些省份汉字样本少模型没学好二是字符分割时汉字和旁边的字母贴得太近切出来的图里混了半边字母。解决先在 GUI 里打开中间可视化确认切出来的第一个字符图是不是干净。如果图干净但还是错就是训练数据问题建议补样本重训如果不干净去调 3.2 节说的垂直投影参数和去边框逻辑。别一上来就折腾模型先分清是分割问题还是分类问题。5.5 环境安装Python、OpenCV、TensorFlow 怎么组队现象pip install opencv-python 顺利装完然后装 TensorFlow 疯狂报错或者反过来。原因Python 版本和 TensorFlow 版本不匹配。TensorFlow 对 Python 版本要求很严格装错版本直接找不到模块。解决我常用的搭配是 Python 3.8 TensorFlow 2.6 opencv-python 4.5/4.8。这个组合经过大量毕设项目验证踩坑最少。安装顺序上先装 TensorFlow 再装 OpenCV因为 OpenCV 依赖的 numpy 版本可能跟 TensorFlow 冲突先装主框架让 pip 自动解析依赖。用 Anaconda 的话直接 conda create -n lpr python3.8 建一个干净环境后面所有问题都在这套环境里排查别跟其他项目混在一起。6. 拿到手先别改代码三招验证这套源码到底靠不靠谱6.1 先用 demo_car.mp4 跑基线解压之后我习惯先跑通官方 demo再做任何修改。demo_car.mp4 是项目自带的演示视频直接 python vidGUI.py 加载它确认能弹窗、能出框、能显示车牌号你的环境就算打通了。同时留意一下帧率和 CPU 占用如果帧率低到没法看按 5.3 的做法设置跳帧如果没报错但识别结果为空优先检查 mask 阈值和字符切分参数。6.2 可视化中间结果分割还是分类一眼定位很多人调来调去不知道问题出在哪一环。我的习惯是在 core.py 里加三行调试代码把 mask 和分割后的字符图分别保存到磁盘上# 调试辅助代码定位错误发生在分割环节还是分类环节 debug_dir debug_imgs os.makedirs(debug_dir, exist_okTrue) # 保存分割出的车牌区域 cv2.imwrite(os.path.join(debug_dir, plate.jpg), plate_img) # 保存切分出的每个字符 for idx, char_img in enumerate(char_list): cv2.imwrite(os.path.join(debug_dir, fchar_{idx}.jpg), char_img)跑完看 debug_imgs 文件夹里的图。如果 plate.jpg 里车牌区域不完整说明 UNet 分割或坐标裁剪有问题如果字符图里混入了边框或粘连字符说明分割环节有问题如果字符图都干净但最终结果错了才是 CNN 分类的问题。这三步能帮你把排查范围缩小到具体模块不用一遍遍跑完整流程。6.3 批量跑一组照片统计识别成功率单张图跑通了再用一批图验证稳定性。写一个批量脚本用 6.2 的识别函数批量处理目标文件夹里的图片然后把预测结果和文件名里的真实车牌号比一比输出正确率import os from core import recognize_plate # 假设 core.py 里导出识别函数 def batch_test(img_dir): for img_name in sorted(os.listdir(img_dir)): img_path os.path.join(img_dir, img_name) img cv_imread(img_path) # 用 5.2 的中文路径兼容读取 pred recognize_plate(img) truth img_name.split(.)[0] # 约定文件名就是真实车牌号 status OK if pred truth else FAIL print(f{status} | {truth} | pred{pred})如果失败图集中在某个类别或某个场景看它们的共性再决定下一步全部是倾斜车牌失败就加强矫正全部是汉字失败就按 5.4 的思路排查分布没规律才考虑补训练数据。从那以后我每次拿到这类毕设源码第一件事就是先跑通 demo、再可视化中间结果、再批量统计这三板斧走完这套源码有几斤几两心里基本有数了。希望帮到你。本文还有配套的精品资源点击获取
返回列表