ARTICLE DETAIL

资讯详情

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

Python+OpenCV车牌识别系统源码解析:U-Net定位与CNN识别实战

Python+OpenCV车牌识别系统源码解析:U-Net定位与CNN识别实战 简介基于Python与OpenCV实现的车牌识别系统源码包主要面向高校毕业设计、课程设计和期末大作业等场景适合深度学习与图像识别方向的学习者进行项目实战与算法研究。压缩包共含16个文件包括9个Python源程序、2个H5模型权重、1个TXT使用说明、3个GIF演示动画和1个MP4效果视频整体大小约26.7MB。Python代码覆盖图像预处理、车牌定位、字符分割与识别等核心模块H5模型为训练好的权重文件可直接加载调用GIF和MP4直观展示GUI操作过程与识别效果TXT说明辅助快速启动。运行自带的主程序即可进入基于tkinter的图形界面从读取图片到输出车牌结果实现完整闭环。读者可通过阅读源码弄清传统视觉算法与深度学习模型如何协作也可参考其界面布局、事件处理与模型封装方式快速迁移到类似识别任务。目前已有1084人学习下载项目兼具完整性与可扩展性既能作为毕业设计直接交付也可在此基础上补充夜间识别、倾斜校正等功能进一步提升工程能力。1. 国内车牌识别系统源码怎么挑pythonopencv 解压就能跑才算合格做毕设找车牌识别源码的人最怕三种情况代码不完整、模型没权重、GUI 是摆设。这套基于 pythonopencv 的国内车牌识别系统源码把 U-Net 定位、CNN 字符识别和 Tkinter 界面绑在了一起解压后运行 GUI.py 就能看到完整识别流程。它面向的是要交毕设、课程设计或期末大作业的深度学习与图像识别方向学生不用自己收集车牌数据集、不用从零训练cnn.h5 和 unet.h5 两个权重文件随包附带。你真正要做的是先搞清楚两条推理管线怎么协作再把 GUI 稳稳跑起来最后根据自己的场景调参数。下面我从文件结构讲起按“启动 → 原理 → 避坑 → 进阶”的顺序把整个项目过一遍。2. 解开压缩包文件清单、模型权重与第一次 GUI 启动2.1 先花五分钟把文件分类拿到压缩包解压之后不要急着运行先对照文件列表过一遍搞清楚哪些是入口、哪些是模型、哪些是工具。这个包里的内容大致分四类GUI 入口、模型定义与训练、推理工具、演示素材。下面这张表是完整的文件职责对照。文件职责类型GUI.pyTkinter 图形界面主入口入口imgGUI.py单张图片识别的 GUI 版本入口vidGUI.py视频识别的 GUI 版本入口CNN.pyCNN 字符识别模型定义模型Unet.pyU-Net 车牌分割模型定义模型train.py模型训练脚本工具core.py预处理、后处理等公共函数工具unet.h5U-Net 训练好的权重权重cnn.h5CNN 训练好的权重权重videospilt.py视频拆成帧序列工具imgtovid.py帧序列合成视频工具demo_car.mp4自带演示视频素材1.gif、2.gif、3.gif运行效果演示图素材使用说明.txt环境与运行说明文档这张清单的价值在于你不用把每个文件都打开读一遍就能知道该动哪里。GUI.py、imgGUI.py、vidGUI.py 三个入口对应三种用法多数人只需要跑通 GUI.pytrain.py 只有在你想重训模型时才需要碰videospilt.py 和 imgtovid.py 是视频识别链路里的辅助工具用于把视频拆帧、再把标注结果合回视频。我习惯在解压后先把 使用说明.txt 从头到尾读一遍虽然它通常只有几行字但里面一般会写清 Python 版本要求和缺失依赖的安装顺序这能省掉后面不少排查时间。2.2 两个 h5 决定识别效果谁先谁后别搞错这个项目最核心的推理链路是“先定位、再识别”。unet.h5 负责回答“车牌在哪”cnn.h5 负责回答“车牌上是什么字符”。两个模型串行工作先让 U-Net 从整张图中分割出车牌区域再把车牌区域切成单个字符逐个交给 CNN 分类。这种两阶段设计与端到端识别最大的区别在于可解释性——哪一步出问题你能直接看到是定位失败还是识别失败调参时不用黑匣子全猜。具体到运行顺序上GUI.py 启动后先加载 unet.h5再加载 cnn.h5。如果 unet 没加载成功程序会在定位阶段就终止如果 cnn 没加载成功会出现“框已经画出来了但车牌号码显示不出来”的诡异状态。所以拿到源码后建议先单独写两行代码分别验证两个权重文件能正常加载再进入 GUI 整体调试。验证方式很简单用 Python 交互环境执行from tensorflow.keras.models import load_model; load_model(unet.h5, compileFalse)不报错就说明模型文件本身没问题。2.3 启动 GUI.py最小依赖与第一次运行过程理论上说解压后直接运行 GUI.py 即可但我建议用命令行启动而不是双击因为能看到报错输出cd C:\Users\yourname\plate_system python GUI.py如果提示ModuleNotFoundError: No module named cv2就补 OpenCV提示No module named tensorflow就装 TensorFlow。常见依赖版本组合是 Python 3.7-3.10、opencv-python 4.x、numpy 1.x、TensorFlow 2.x、Pillowpip install opencv-python numpy pillow pip install tensorflow2.10.0注意这里有个细节GUI 模式不要装opencv-python-headless。headless 版本不含 GUI 相关的高层功能虽然对大部分图像运算没影响但和 Tkinter 混用时容易出一些莫名其妙的显示问题。装好之后运行窗口中会弹出图片选择按钮和结果展示区域选择一张车牌图片程序先画一个框标出车牌位置再把识别出的车牌号码显示在文本框里。整个界面交互逻辑很直接没有复杂的多级菜单这也是它适合课程设计展示的原因——演示时你只需要点两下鼠标就能把“图片输入→定位→识别→结果显示”的完整流程讲给评委听。3. 定位与识别拆开讲U-Net 抠车牌、CNN 认字符3.1 U-Net 语义分割先回答“车牌在哪”U-Net 在这套系统里做的是像素级分割。输入一张任意尺寸的图片先缩放到模型约定尺寸常见是 224×224 或 256×256归一化到 0-1 区间后前向传播输出一张与输入同尺寸的 mask。mask 上数值接近 1 的像素代表模型认为它属于车牌前景。U-Net 名字里的“U”来自网络结构的形状左边是逐步下采样的编码器右边是逐步上采样的解码器中间用跳跃连接把浅层细节和深层语义拼在一起所以它对小目标和边缘模糊区域的分割效果明显优于普通卷积分类网络。import cv2 import numpy as np from tensorflow.keras.models import load_model unet load_model(unet.h5, compileFalse) img cv2.imread(test.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized cv2.resize(img_rgb, (224, 224)) input_tensor np.expand_dims(resized / 255.0, axis0) mask unet.predict(input_tensor)[0] mask cv2.resize(mask, (img.shape[1], img.shape[0]))这段代码里np.expand_dims(..., axis0)是把三维图片补成四维 batch因为 Keras 模型默认接受(batch, height, width, channels)形状的输入/255.0是归一化到 0-1unet.predict返回的结果里[0]取回第一张图的 mask。原始图片尺寸不一定是 224 的整数倍所以最后把 mask 重新 resize 回原图尺寸这样后面画框时坐标才对得上。如果项目代码里已经封装好了类似函数调用方式可能略有差异但核心逻辑就是这一套。3.2 从 mask 到车牌矩形轮廓与外接框有了 mask最直接的做法是对它做二值化然后用cv2.findContours找连通区域。车牌通常只占画面一小块所以取面积最大的那个轮廓再用cv2.boundingRect得到外接矩形最后从原始图里把这个区域切出来。这个步骤的鲁棒性直接决定后续识别上限——框偏了、框大了字符切分就跟着错。mask_bin (mask 0.5).astype(np.uint8) contours, _ cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: print(no plate found) exit() box max(contours, keycv2.contourArea) x, y, w, h cv2.boundingRect(box) plate img[y:y h, x:x w] cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2)0.5是二值化阈值实际场景里如果车牌反光或距离远可以降到 0.3 试试。RETR_EXTERNAL只取最外层轮廓避免把车牌里的字符边缘当成独立区域CHAIN_APPROX_SIMPLE压缩轮廓点只保留端点计算更快。这里还有一个容易被忽略的点cv2.findContours在 OpenCV 不同版本里返回值数量不一样4.x 返回两个值3.x 返回三个值如果你的环境是 OpenCV 3.x上面代码要改成_, contours, _ cv2.findContours(...)。3.3 字符切分把车牌切成一排单独字符车牌区域切出来后要让它变成 CNN 能处理的单字符图。国内蓝牌一般是 7 位含一个省份汉字、一个字母、五个字母或数字中间有个小圆点分隔。切分最常用的方法是垂直投影先把车牌转灰度、二值化再统计每一列上的白色像素数投影值从 0 变正的那一列就是字符的起始边界。gray cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) col_sum np.sum(binary 255, axis0) char_regions [] in_char False start 0 for i, v in enumerate(col_sum): if v 0 and not in_char: start i in_char True elif v 0 and in_char: char_regions.append((start, i)) in_char FalseOTSU 方式会自动选择二值化阈值对比度正常的车牌图不需要手动调。切分结果里宽度明显小于平均值的区间一般是噪声或圆点代码里通常加一个过滤条件只保留宽度在平均宽度 0.5 倍以上的区间。这里最典型的失败场景是汉字“京”的内部结构复杂如果二值化后笔画断裂垂直投影会把一个字切成两段导致后续识别结果变成 8 位甚至 9 位。遇到这种情况可以在切分前先做一次形态学闭运算把断裂的笔画连起来。3.4 CNN 单字符识别逐字预测与字符集约束切分出的每个字符块缩放成 CNN 输入尺寸后逐个预测。CNN.py 里定义的模型结构通常是卷积 池化 全连接输出层用 softmax 给出每个类别的概率类别集合覆盖了国内车牌常见的省份汉字、24 个大写字母I 和 O 通常去掉以避免与数字 1、0 混淆和 10 个数字。省份汉字一般按全国车牌代码表收集大约 30 多个类别加上字母和数字总共 65 类左右。from tensorflow.keras.models import load_model import numpy as np cnn load_model(cnn.h5, compileFalse) labels [京, 津, 沪, 渝, 冀, 豫, 云, 辽, 黑, 湘, 皖, 鲁, 新, 苏, 浙, 赣, 鄂, 桂, 甘, 晋, 蒙, 陕, 吉, 闽, 贵, 粤, 青, 藏, 川, 宁, 琼] \ [chr(i) for i in range(65, 91) if chr(i) not in [I, O]] \ [str(i) for i in range(10)] def predict_char(img): img cv2.resize(img, (32, 32)) / 255.0 img np.expand_dims(img, axis0) probs cnn.predict(img)[0] return labels[int(np.argmax(probs))]这里 labels 列表的顺序必须和训练时的类别顺序一致否则预测结果会错位。逐字识别的好处是每个字符都有独立的置信度哪个字没认出来可以单独定位坏处是字符切分的错误会直接传导到识别结果——切多了、切少了后面全跟着错。实际调试时我一般会在predict_char里把probs的 top-3 打出来看如果正确字符排在第二或第三位说明模型前几层特征提取没问题多半是输入图片的归一化或尺寸与训练时不匹配。3.5 视频链路vidGUI.py 如何复用同一套流程视频识别的逻辑更直白cv2.VideoCapture读取视频帧每一帧重复上面的定位、切分、识别流程然后用cv2.putText把结果绘制到当前帧上。项目里 videospilt.py 负责把视频拆成帧序列imgtovid.py 负责把处理后的帧合回视频这两个工具本质上是批量脚本方便你脱离 GUI 单独调试某一帧。import cv2 import numpy as np cap cv2.VideoCapture(demo_car.mp4) writer None while True: ret, frame cap.read() if not ret: break mask get_mask(frame) # 走 U-Net box locate_plate(mask) # 轮廓定位 result recognize_plate(frame) # 走 CNN cv2.putText(frame, result, (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 2) if writer is None: h, w frame.shape[:2] writer cv2.VideoWriter(output.avi, cv2.VideoWriter_fourcc(*MJPG), 20, (w, h)) writer.write(frame)这套复用关系的重点在get_mask、locate_plate、recognize_plate三句话的结构把整条链路的每一步都封装成函数图像 GUI 和视频 GUI 之间只是调用方式不同核心逻辑完全一样。这也是项目能把 imgGUI.py 和 vidGUI.py 分开维护而没有重复造轮子的原因。视频输入的帧率一般按 20-30fps 处理但模型推理速度如果到不了这个水平就需要在代码里加跳帧逻辑这个放到下一章详细说。4. 避坑清单模型加载、中文路径与视频卡顿的高频问题4.1 现象一unet.h5 加载直接报错现象load_model(unet.h5)抛KeyError: weight_1或者提示某层名称未知程序在启动阶段就崩了。原因这个 h5 很可能是用旧版本 Keras 训练的当前 TensorFlow 2.10 对旧权重文件的恢复逻辑改过部分自定义层无法自动重建。解决加载时关闭编译选项损失训练时的 optimizer 状态但推理完全不受影响。model load_model(unet.h5, compileFalse)如果还是报错就检查一下 Python 和 TensorFlow 版本TensorFlow 2.4 到 2.10 之间对旧 Keras 权重兼容性最好。实在不行可以用pip install tensorflow2.6.0降版本毕设项目的 使用说明.txt 里如果写了推荐版本以它为准。4.2 现象二cv2.imread 或视频路径返回 None现象点开 GUI 选择图片后没有任何反应控制台打印None或者视频窗口黑屏。原因OpenCV 的imread对中文路径支持很差压缩包若放在含中文的目录下路径传进去会乱码。解决把整个项目文件夹移到纯英文路径下例如D:\plate_system输入图片也改名为纯英文文件名。这是最省事的处理方式别折腾编码转换。同理demo_car.mp4 如果读取失败先确认路径里没有中文和空格视频文件最好放在与 GUI.py 同一目录下避免相对路径写错。4.3 现象三视频推理卡成一帧一帧现象demo_car.mp4 跑起来像幻灯片CPU 占用拉满界面严重卡顿。原因每一帧都做一次 U-Net 分割再加上字符切分和 CNN 逐个预测CPU 推理单帧耗时可能到几百毫秒视频 20fps 根本追不上。解决跳帧处理每 3-5 帧处理一帧或者把输入图缩到 640 宽以内再送进模型。frame_idx 0 while True: ret, frame cap.read() if not ret: break frame_idx 1 if frame_idx % 5 ! 0: continue frame cv2.resize(frame, (640, int(frame.shape[0] * 640 / frame.shape[1]))) result recognize_plate(frame)跳帧属于标准的工程妥协演示场景完全够用。如果你用的是 NVIDIA 显卡可以把 TensorFlow 换成 GPU 版本推理速度能提升 5-10 倍但安装 GPU 版本身也是个坑——CUDA、cuDNN、TensorFlow 三者版本必须匹配不建议在赶毕设的时候折腾这个。4.4 现象四识别结果混入杂字符现象车牌明明干净输出却是京A·123#4这种带#的字符串多一位或者多一个符号。原因字符切分时的垂直投影把车牌边缘的铆钉、反光点当成了字符列。解决切分后按连通域的宽度和长宽比过滤识别阶段再用白名单约束输出凡是不在汉字 字母 数字集合里的结果直接丢弃。具体过滤条件参考下面这段chars [] for start, end in char_regions: w_char end - start if w_char avg_width * 0.5: continue chars.append(plate[:, start:end])avg_width 是所有切分区间宽度的平均值用这个相对值做过滤比写死绝对像素值更通用。如果你发现过滤后字符数量仍然大于 7大概率是“京”这类汉字被切开回到 3.3 节说的形态学闭运算去处理。4.5 现象五train.py 一跑就报数据路径错现象执行python train.py直接FileNotFoundError提示某个目录不存在。原因源码包通常只附权重文件不带训练集。train.py 默认的数据目录结构一般是“每个字符一个文件夹文件夹名就是标签”但压缩包里不会替你准备好这些数据。解决先用公开的字符集图片或自己人工切好的字符图按data/京/xxx.jpg、data/A/xxx.jpg这种结构组织数据再修改 train.py 里的数据路径参数。如果你只是想用这个系统做演示和毕设不需要碰 train.py直接用现成的两个 h5 就够了。5. 进阶拿自己的数据跑一遍系统验证再决定要不要微调5.1 准备一张有真实场景的测试集跑通 demo 只是第一步毕设答辩时老师大概率会问“你的模型在别的场景下表现如何”。我建议从训练集中收尾处开始自己拍或者从行车记录仪截 30-50 张不同角度、光照、背景的车牌图放到test_imgs目录文件名统一按“车牌号_序号.jpg”命名例如京A12345_01.jpg。命名规则里带上真实车牌号后面统计时可以直接从文件名里提取真值不需要另建标注文件。5.2 写个脚本批量统计识别率import os import cv2 from core import recognize_plate total 0 correct 0 for name in os.listdir(test_imgs): if not name.endswith(.jpg): continue img cv2.imread(os.path.join(test_imgs, name)) pred recognize_plate(img) true_label name.split(_)[0] total 1 if pred.replace(·, ) true_label: correct 1 print(f{name}: pred{pred}, true{true_label}) print(faccuracy: {correct / total:.2%} ({correct}/{total}))如果正确率在 90% 以上说明这套系统在你的场景下够用如果掉到 70% 以下优先看定位是否稳定——把 mask 可视化出来确认车牌区域是否被完整框住。定位没问题再看字符切分最后才考虑重新训练 CNN。这个排查顺序很重要定位、切分、识别是三个独立的故障域不分开验证出了问题只能瞎猜。5.3 一条值得保留的习惯从那以后我每次拿到这类源码都会强制走一遍“拆帧观察定位框 → 单独抽字符看图 → 再统计识别率”的流程。这套 pythonopencv 车牌识别源码最大的价值不是开箱即用而是让你在改代码的过程中把整套“分割 分类”的套路练熟。希望帮到你。本文还有配套的精品资源点击获取
返回列表