ARTICLE DETAIL

资讯详情

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

基于CNN的车牌识别仿真系统:从模型训练到前后端MySQL联调

基于CNN的车牌识别仿真系统:从模型训练到前后端MySQL联调 简介一套基于卷积神经网络的车牌识别仿真软件源代码提供了完整的前后端系统、MySQL 数据库及配套说明文档适合正在准备毕业设计或课程设计的 Python 学习者。系统实现了车牌图片上传识别、车牌号及颜色识别、车牌信息管理、登录认证、修改密码、用户信息与用户管理等功能覆盖了信息增删改查和登录安全校验等典型后台管理需求。压缩包内共 541 个文件、总大小约 268MB包括 py/pyc 后端核心代码、js/css/html 前端页面、png/jpg/gif 图标和演示素材、sql 数据库初始化脚本、docx/doc 设计文档、pth 模型权重文件以及 mp4 演示视频和 pptx 答辩课件环境说明给出了 Python 3.6.8、MySQL 5.7、Navicat11 和 PyCharm 的工具组合便于快速搭建开发环境。已有 48 人学习。整套代码的价值在于将车牌识别从算法到业务管理完整串通代码结构清晰配套文档齐全既可用于学习 CNN 模型调用与数据库交互也能直接支撑毕业设计、课程设计的系统演示、答辩展示与二次开发。1. 车牌识别仿真系统落地CNN识别前后端与MySQL要一起跑通把一张车牌照片扔进网页系统自动返回车牌号和颜色同时把识别结果写进MySQL、在后台列表里可增删改查——这就是这份基于卷积神经网络的车牌识别仿真软件要解决的完整链路。它不是一个孤立的算法demo而是把CNN识别、Python后端、layui前端和MySQL管理端拼成一个能跑起来的Web系统非常适合毕业设计和课程设计复现。你在PyCharm里打开项目配好Python 3.6.8和MySQL 5.7先起后端再开前端注册登录、上传车图、查管理记录整条线都能看到效果。它不需要你从零搭框架但如果你想改参数、换数据集、调模型结构就得知道每一环的接缝在哪里。下面我从目录和数据流入手把这份资源真正拆开来讲。2. 系统架构与数据准备识别之外的增删改查链路2.1 前后端分层与MySQL存储设计拿到项目先看目录结构。这套系统不是孤立的算法脚本而是带管理后台的Web应用。前端资源里能看到layui.css、layer.css、fullcalendar.css、laydate.css、font-awesome.min.css、style.css这一串文件说明界面是标准的管理后台风格——layui提供按钮、表单、弹窗组件layer承担消息提示laydate管日期控件fullcalendar负责日历展示。后端是Python写的服务数据落在MySQL 5.7里。我拿到这类资源的第一件事不是急着跑起来而是先把数据流画清楚。一次完整的操作链路是这样的用户从layui页面输入用户名密码登录→后端校验身份→进入车牌管理页面上传车牌图片→后端把图片预处理后喂给CNN模型→模型返回车牌号与颜色→识别结果连同图片路径写入MySQL→后台列表里可以对记录做查询、修改、删除用户管理里可以增删用户。理解这条链路你才知道改哪里会影响什么换模型看识别接口改页面找layui模板查数据看MySQL表。数据库层面至少有两类核心表用户表和车牌识别记录表。用户表存用户名、密码、角色、创建时间密码字段建议存MD5或SHA256摘要而不是明文识别记录表存图片路径、车牌号、颜色、识别时间。资源里带的sql脚本通常已经建好库和表你在Navicat11里执行一遍就能把结构拉起来。fullcalendar和laydate出现在资源里意味着后台可能在识别时间统计上做了可视化的方向你在车牌管理列表里可以按日期范围筛选记录。2.2 数据集文件怎么用chars2.7z与charsChinese.7z的分工资源里有两个压缩包chars2.7z和charsChinese.7z。这是CNN训练与识别真正要吃的素材我拆过的车牌识别项目里字符集一般是按「汉字 字母数字」分开存储的。chars2.7z放的是字母和数字字符图片charsChinese.7z放的是汉字字符图片对应车牌上的省份简称比如京、津、冀、鲁、苏这些。字符图片按字符名组织目录训练时靠文件夹名确定标签不需要额外的标注文件这个设计对课程设计来说非常省事。解压时我建议显式指定输出目录避免文件散落在项目根目录mkdir -p data/chars mkdir -p data/charsChinese 7z x chars2.7z -odata/chars 7z x charsChinese.7z -odata/charsChinese参数说明7z x是解压命令-o后面直接跟目标路径且不加空格这是7-Zip命令行常见易错点。如果你不习惯命令行在Windows下用7-Zip图形界面右键解压到指定文件夹效果一样但注意别选「解压到 文件名\」那个选项——那样会多套一层目录代码里的路径就要跟着改。解压完确认层级data/chars下应该是0、1、2这样的数字文件夹和A、B、C这样的字母文件夹data/charsChinese下是省份简称命名的文件夹。每一类字符图片都放在以该字符命名的目录下这就是CNN分类的天然标签来源。样本数量在这个环节值得留意。字符图片通常每类几十到上百张做毕业设计足够起步。汉字字符类别比字母数字少但某些生僻省份简称的样本可能特别少这会在训练阶段直接表现为识别率偏低。2.3 训练前一定要做的一次字符图片检查解压完数据集别急着开训。先用一段小脚本过一遍数据确认图片尺寸、通道数统一这是我复现每个字符识别项目的保留动作import os import cv2 img_dir data/chars # 两个数据集目录各检查一遍 for root, dirs, files in os.walk(img_dir): for f in files: if not f.endswith((.jpg, .png, .bmp)): continue path os.path.join(root, f) img cv2.imread(path) if img is None: print(读取失败:, path) # 文件损坏或压缩时编码异常 elif img.shape[:2] ! (20, 20): # 具体尺寸以模型输入为准 print(尺寸异常:, path, img.shape[:2])这段逻辑说明os.walk递归遍历目录筛选出jpg、png、bmp三种常见图片格式cv2.imread读不到有效数据时返回None说明文件损坏或者格式不是OpenCV能解析的img.shape[:2]取高和宽和模型预期的输入尺寸对比。车牌字符这类小图常见尺寸是20x20或32x32具体以训练代码里input_shape为准我这里只是示范检查思路。提示chars2.7z里的字母数字样本通常多于汉字字符所以训练后汉字识别率偏低不一定是模型问题先去看charsChinese里的类别样本量。3. 卷积神经网络模型CNN结构与关键参数设置3.1 卷积层、汇聚层与全连接层的配置逻辑这份资源的核心识别引擎是卷积神经网络。车牌识别场景里输入是灰度或二值化的字符图片输出是字符类别概率分布。CNN比全连接网络强在卷积核能自动提取边缘、笔画、纹理特征不需要人工设计特征工程。网络上说的「汇聚层」就是池化层它在卷积层之后对特征图做下采样保留主要特征的同时压缩计算量。以下是这类车牌字符识别项目最常见的网络结构资源里的模型代码基本是这个思路的变体from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model Sequential([ Conv2D(filters32, kernel_size(3, 3), activationrelu, input_shape(20, 20, 1)), MaxPooling2D(pool_size(2, 2)), Conv2D(filters64, kernel_size(3, 3), activationrelu), MaxPooling2D(pool_size(2, 2)), Conv2D(filters128, kernel_size(3, 3), activationrelu), Flatten(), Dense(units512, activationrelu), Dropout(rate0.5), Dense(unitsnum_classes, activationsoftmax), # num_classes为字符类别总数 ]) model.compile( optimizeradam, losscategorical_crossentropy, metrics[accuracy], )逐层过一遍第一个Conv2D的input_shape必须和预处理后的图片尺寸一致图片resize成20x20灰度图就是(20, 20, 1)最后的1表示单通道。前两层用3x3小卷积核两个3x3堆叠的感受野等价于一个5x5但参数量更少、非线性更强这是现代CNN的通用设计。MaxPooling2D的pool_size(2, 2)把特征图宽高各减半下一层的计算量直接降为四分之一。Flatten把三维特征图拉成一维向量送给后面的全连接层。最后的Dense层units要等于字符类别总数训练前数一下数据集文件夹个数写错这里的维度fit阶段就会报shape不匹配。中间加了Dropout(0.5)训练时随机丢弃一半神经元这是抑制过拟合的常用手段。3.2 训练参数batch_size、epoch与学习率的选择资源里的训练脚本有默认的epoch和batch_size但那个值不一定适合你复现时的机器。我的建议是先用默认值跑通再根据验证曲线调整history model.fit( x_train, y_train, batch_size64, # 每批64张内存紧张可以降到32 epochs20, # 小数据集20轮足够观察收敛 validation_split0.2, # 留20%做验证集 verbose1, )参数含义batch_size是每次梯度更新用多少张图64在字符小图上表现稳定机器学习性能瓶颈时降到32收敛会更平滑但训练时间变长。epoch设20是因为字符图片集通常不大跑多了容易过拟合——验证集准确率不再上升而训练集却逼近100%就是过拟合的典型信号。validation_split0.2从训练数据里切20%出来做验证这部分数据不参与权重更新只用于观察每个epoch后的泛化表现。verbose1表示每个epoch打印一行进度。有个参数经常被忽略学习率。adam优化器的默认初始学习率是0.001对大多数场景够用。如果你的loss在头几个epoch完全不降先别调学习率去检查图像是否做了归一化——像素值从0到255除以255变成0到1的浮点区间而不是把整数直接喂给网络。这个细节对CNN收敛速度影响很大很多课程设计翻车就翻在这里。3.3 识别流程车牌定位、字符切割与逐字符分类这一步要理解车牌识别不是「一张大图直接输出字符串」而是先定位、再切割、最后逐字符分类的三段式流程def recognize_plate(image_path): # 1. 定位车牌区域返回裁剪后的车牌图 plate_img locate_plate(image_path) # 2. 按字符间距做连通域切割返回字符图列表 char_images split_chars(plate_img) # 3. 逐字符送入CNN分类并拼接结果 result for img in char_images: img cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img cv2.resize(img, (20, 20)) img img.reshape(1, 20, 20, 1).astype(float32) / 255.0 pred model.predict(img, verbose0) char label_map[np.argmax(pred[0])] result char return result定位函数常见实现是用颜色阈值锁定蓝色或黄色车牌区域或者用边缘检测加轮廓筛选。切割函数利用字符间的固定空隙和连通域分析切出单个字符。预测阶段每个字符都要走一遍相同的预处理转灰度、resize、reshape加批次维度、归一化。model.predict返回每个类别的概率数组np.argmax取最大概率的索引label_map把索引映射回字符。切割这一步最容易出问题如果切出的字符块宽度不一致强制resize到20x20后字符比例变形识别率会明显往下掉。4. 前后端联调与识别接口从PyCharm跑通到浏览器传图4.1 服务端识别接口图片上传与JSON返回后端在这类Python资源里最常见的实现是Flask依赖少、路由直观PyCharm里直接运行入口脚本就能起服务。核心接口就两个方向用户认证和图片识别。识别接口的关键逻辑是接收前端上传的图片文件保存到临时路径调用CNN模型识别后返回结构化JSONfrom flask import Flask, request, jsonify import os import uuid app Flask(__name__) UPLOAD_FOLDER uploads os.makedirs(UPLOAD_FOLDER, exist_okTrue) app.route(/api/recognize, methods[POST]) def recognize_api(): file request.files.get(file) # 字段名要和前端一致 if file is None: return jsonify({code: 1, msg: 未收到图片}) suffix os.path.splitext(file.filename)[1] save_path os.path.join(UPLOAD_FOLDER, uuid.uuid4().hex suffix) file.save(save_path) plate_no, color recognize_plate(save_path) return jsonify({code: 0, plate_no: plate_no, color: color})request.files.get取的是前端上传的file字段字段名不一致会拿到None这是联调最常见的低级错误。uuid.uuid4().hex生成随机十六进制串做文件名避免同图多次上传互相覆盖os.path.splitext取出原文件的扩展名后缀保证保存后的图片格式能被OpenCV正常读取。识别函数入参是本地路径返回车牌号和颜色Flask自动把字典序列化成JSON前端拿到后直接渲染。4.2 MySQL接入连接配置与识别记录落库数据库连接配置集中在单独的db模块里PyMySQL是Python连MySQL的常见选择。资源里的SQL脚本会建好库和表你要改的只是连接参数import pymysql def get_conn(): return pymysql.connect( hostlocalhost, # 本机连接用localhost port3306, # MySQL默认端口 userroot, password123456, # 改成你本机MySQL密码 databaseplate_db, charsetutf8mb4, # 用utf8mb4避免中文乱码 cursorclasspymysql.cursors.DictCursor, )cursorclass设为DictCursor后查询结果返回字典列表字段名直接可用Flask序列化JSON时不用再手动组装。charset用utf8mb4而不是utf8是因为utf8mb4能完整存储中文和特殊符号车牌管理里存省份汉字用它最稳妥。识别成功后要把结果写库这是「识别」和「管理」两个功能衔接的地方def save_record(plate_no, color, image_path): conn get_conn() try: with conn.cursor() as cursor: sql ( INSERT INTO plate_record (plate_no, color, image_path, create_time) VALUES (%s, %s, %s, NOW()) ) cursor.execute(sql, (plate_no, color, image_path)) conn.commit() # 不commit的话数据不会真正落库 finally: conn.close()execute用%s占位符参数通过第二个参数传入而不是拼进SQL字符串这是防SQL注入的基本做法。conn.commit()容易漏漏掉的结果是insert执行了但表里查不到数据排查半天发现是没提交。finally里的conn.close()确保连接释放不然连接池会被占满。4.3 数据库表结构用户表与车牌记录表资源里的SQL脚本通常长这样手动画个表结构能帮你理解前后端字段对接CREATE TABLE user_info ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT admin, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE plate_record ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) NOT NULL, color VARCHAR(10), image_path VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );user_info的username加UNIQUE约束防止重复账号password字段存摘要值而不是明文。plate_record记录识别结果和图片路径create_time由MySQL自动填充当前时间列表页按时间倒序就能看到最新识别记录。4.4 前端页面与接口对接layui上传组件前端用layui渲染管理界面车牌识别页最核心的是上传组件。layui的upload模块负责文件选择、上传和回调渲染button typebutton classlayui-btn iduploadBtn上传车牌图片/button div idresultBox 车牌号span idplateNo/spanbr 颜色span idplateColor/span /divlayui.use([upload, layer], function () { var upload layui.upload; var layer layui.layer; upload.render({ elem: #uploadBtn, url: /api/recognize, field: file, done: function (res) { if (res.code 0) { document.getElementById(plateNo).innerText res.plate_no; document.getElementById(plateColor).innerText res.color; } else { layer.msg(res.msg); } } }); });elem指定触发上传的按钮选择器url指向后端识别接口field值必须和request.files.get(file)里的名字一致写错的话后端取到的file就是None。done是上传完成后的回调res是后端返回的JSON对象code为0表示成功把车牌号和颜色渲染到页面对应的span里code非0时用layer.msg弹出错误信息这是layui自带的提示组件不需要额外引弹窗库。5. 避坑从环境到模型识别率的关键排查清单5.1 Python 3.6.8与TensorFlow版本不兼容现象按资源里的依赖清单装完包import tensorflow直接报错或者运行训练脚本提示找不到tensorflow.contrib模块。原因资源明确标注Python 3.6.8对应TensorFlow 1.x到2.x的过渡期如果资源代码是TF 1.x风格contrib模块在TF 2.x里已经被移除直接装新版TensorFlow必然报错。解决新建Python 3.6.8的虚拟环境安装资源锁定的TF版本通常是pip install tensorflow1.15.0 keras2.2.4。不要用系统全局环境装避免影响其它项目。5.2 MySQL连接报错1045或1130现象SQL脚本在Navicat11里导入正常后端启动后连接数据库报错错误码1045是访问被拒绝1130是连接被拒绝。原因root密码和配置文件里写的不一致或者root账号只允许localhost访问而后端请求走的地址和权限判定规则不匹配。解决用Navicat连上本机MySQL确认root密码同步修改Python配置文件里的password字段如果用专用账号执行授权CREATE USER plate_userlocalhost IDENTIFIED BY plate123; GRANT ALL PRIVILEGES ON plate_db.* TO plate_userlocalhost; FLUSH PRIVILEGES;5.3 车牌识别率低先查灰度化与归一化是否一致现象模型能跑通但识别结果总把6识别成8省份汉字互相混。原因训练和预测的预处理不一致。最常见的是predict阶段忘了除以255归一化或者训练时做了灰度处理而预测时直接喂了BGR彩色图。解决把预处理抽成公共函数训练和预测调同一份代码。固定顺序读图→转灰度→resize到模型输入尺寸→astype(float32)→除以255。这个改动通常能把识别率拉回不少。5.4 前端上传一直转圈后端没有收到请求现象layui上传组件点击后一直loading后端控制台没有任何请求日志。原因前后端分离运行时跨端口前端页面在8080端口Flask跑在5000端口浏览器拦截了跨域请求。解决给Flask加跨域支持安装flask-cors后注册from flask_cors import CORS CORS(app)5.5 高清大图定位不到车牌小截图反而能识别现象输入实际拍摄的高清车图定位失败输入截图里的小图却能识别。原因定位函数里的颜色阈值或轮廓面积过滤是按像素绝对值设计的大图里的车牌轮廓面积占比相对变小光照变化也会让HSV颜色值偏离阈值区间。解决在定位前把图片统一缩放或裁剪到合理尺寸比如最长边缩到800像素同时检查HSV颜色区间是否覆盖蓝、黄两类车牌底色。如果不确定阈值范围写一段脚本把定位失败的图打印出来看HSV直方图再调整区间。6. 进阶用自己的数据集重训模型验证真实泛化效果把模型真正变成自己的关键一步是替换数据集重训。你可以在chars2.7z和charsChinese.7z的基础上用手机拍真实车牌把字符切出来补进对应文件夹也可以直接在现有字符图片上做数据增强——小幅旋转、平移、加噪声让模型对形变和光线变化更稳。重训前先用脚本统计样本分布低于50张的类别是短板优先补数据import os from collections import Counter def count_samples(root_dir): cnt Counter() for cls_name in os.listdir(root_dir): cls_path os.path.join(root_dir, cls_name) if os.path.isdir(cls_path): cnt[cls_name] len(os.listdir(cls_path)) return cnt for d in [data/chars, data/charsChinese]: print(d, count_samples(d))这段代码的作用是把每个字符文件夹的样本数统计出来Counter是Python内置的计数器遍历每一类字符目录统计文件个数。输出结果里样本明显少的字符就是要重点补充的类别。训练完成后找几张项目里没见过的真实车牌图做端到端验证不要拿训练集里的图自测——训练集上的高准确率说明不了泛化能力。验证时记录两个指标字符级准确率和整牌级准确率。整牌全对的难度远大于单字符对因为拼接环节只要有一步切割偏差整串结果就错了。我在自己的复现里养成一个习惯每次重训完强制在10张真实车牌的固定测试集上跑一遍把误识别的图单独归档下次调参直接对比。从那以后我每次改数据集或调CNN结构都强制走一遍「统计样本→重训→真实图预测→归档误例」这条路。样本均衡、预处理一致、切割稳定这三件事比反复调epoch值更影响车牌识别落地效果。希望帮到你。本文还有配套的精品资源点击获取
返回列表