尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于卷积神经网络的车牌识别系统:从CNN原理到MySQL落库

基于卷积神经网络的车牌识别系统:从CNN原理到MySQL落库 简介这是一套基于卷积神经网络CNN实现完整车牌识别功能的毕业设计/课程设计项目源码面向Python学习者、高校计算机专业学生及需要快速搭建识别演示系统的开发者。压缩包共含541个文件除前端HTML/CSS/JS界面与Python后端代码外还附带MySQL数据库脚本及SQL文件、YAML配置、训练权重.pth与图片素材整体约268MB便于直接导入PyCharm运行调试。系统围绕车牌识别与后台管理展开支持上传车牌图片识别车牌号及颜色并提供车牌管理、用户信息查询、用户管理和密码修改等模块前后端完整、数据库结构清晰。目前已有48人学习下载。相比零散教程这份资料可直接对照源码理解CNN模型部署、Python与MySQL交互、Web管理端开发等环节适合用于课程设计、毕业设计答辩或作为车牌识别项目的二次开发基础。1. 基于卷积神经网络的车牌识别仿真软件源代码完整前后端mysql说明文档LW先搞懂交付物再动手下载过“基于卷积神经网络的车牌识别仿真软件源代码完整前后端mysql说明文档LW”这个zip的人多半会面临同一个问题解压出来一堆文件夹不知道先点哪个。这个标题其实已经把交付内容写清楚了它是一套可以演示的软件不是一套只训模型的Python脚本。完整前后端意味着你最后能打开浏览器上传图片看到车牌号输出并写入MySQLmysql说明数据不是假的识别记录能落库说明文档和LW一般对应使用手册和论文用来解释系统设计。适合两类人做毕设/课设的学生以及想学CNN工程化落地的开发者。拿到手的第一件事不是跑模型而是先检查运行环境和大目录结构版本不匹配是这类包最常见的第一道坑。2. 车牌识别为什么用卷积神经网络从字符分类到整牌识别的链路设计2.1 传统模板匹配的误差会叠加CNN把特征工程换成特征学习十年前做车牌识别最主流的方案是模板匹配先定位车牌区域按固定坐标切出七个字符再把每个字符和标准模板算相似度。这套做法在门禁闸机的理想角度下能用但放到仿真软件的任意上传图片里就会连环翻车。问题出在误差是叠加的定位框偏了两个像素切出来的字符就带边框字符带边框模板匹配的相似度就被噪声带偏最后七个字符里如果错两个整个系统看起来就完全不可用。CNN改变了这个链路里最关键的部分它不再依赖人为设计的特征而是用卷积核自动学习字符的笔画、拐角和断笔。你喂给网络的是字符原始灰度图网络第一层卷积学会了边缘第二层卷积学会组合笔画后面的深度特征直接对应“京”和“示”之间的细微差别。这个过程不需要你手工写Sobel算子也不需要你调模板去跟“鲁”字的横撇竖捺对齐。当然CNN不是万能的。它对训练数据的分布非常敏感训练时如果只见过蓝底白字的清晰车牌那测试时遇到倾斜、暗光、绿牌新能源车就会飘。这也是为什么拿到源码包之后不要急着改前端页面先看看它提供的数据集和字符集覆盖面再决定要不要补训练数据。2.2 识别链路选型检测分割单字符识别还是端到端看这种毕设源码的目录结构你会发现大部分包采用的是“车牌定位 字符分割 CNN单字符识别”的路线少数比较新的会用端到端的CRNN直接解码整张车牌。这两种路线各有取舍我建议你根据运行电脑的配置来选择。第一种路线每一步都有清晰的中间产物先用颜色阈值或轮廓检测把车牌区域从整图中抠出来再根据车牌宽高比和字符间距做垂直投影切分最后把每个字符送入CNN分类器。这条路线的训练成本极低31个省简称加24个字母加10个数字总共65类几百张车牌图就能训出一个能用的字符分类器。CPU就能推理毕设演示不会卡在模型加载上。第二种路线是把整张车牌图直接输入网络用循环层或CTC Loss输出一串字符。优点是省略了字符切分这个容易出错环节对倾斜和模糊的容忍度高缺点是数据标注复杂你要把每个字符的坐标都标出来而且最终模型体积大前后端联调时如果还用老电脑识别一张图可能要等好几秒。对“仿真软件”这个定位我一般推荐第一种。原因很现实仿真软件的核心卖点是业务闭环完整而不是算法指标全球第一。你在答辩时能演示“上传图片→返回车牌号→写入MySQL”比只跑一个准确率数字更有说服力。2.3 最小可运行的CNN字符分类器模型代码与三个关键参数字符分类器是整个系统里最容易先跑通的部分。下面这个模型用TensorFlow/Keras实现输入是20×20的灰度字符图输出是字符类别概率。import tensorflow as tf from tensorflow.keras import layers, models # 字符集顺序必须固定例如 # 京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领ABCDEFGHJKLMNPQRSTUVWXYZ0123456789 num_classes 65 def build_char_cnn(input_shape(20, 20, 1)): model models.Sequential([ layers.Conv2D(32, (3, 3), activationrelu, paddingsame, input_shapeinput_shape), layers.MaxPooling2D((2, 2)), # 汇聚层把20x20下采样到10x10 layers.Conv2D(64, (3, 3), activationrelu, paddingsame), layers.MaxPooling2D((2, 2)), # 再次下采样到5x5 layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), # 防止省简称样本不均衡导致过拟合 layers.Dense(num_classes, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) return model model build_char_cnn() model.summary()这段代码里有三个参数值得盯住。第一个是卷积核大小3×3是图像分类任务的常规选择既能覆盖字符笔画局部又不会把特征图撑得太大第二个是MaxPooling窗口两次2×2池化把20×20压到5×5等于告诉模型“稍微平移一两个像素不该改变识别结果”第三个是Dropout比例0.5这是针对车牌字符数据集最常见的毛病——某些汉字样本只有几十张某些字母有几千张不加Dropout模型会对高频字符过拟合。训练时注意类别索引必须和字符表顺序完全一致。如果你训练时“京”是索引0推理时加载的labels文件也必须是同一个顺序。很多源码包换了模型文件后全部识别错位就是训练和推理用的字符表不一致。2.4 数据准备与字符切分决定最终识别率的上游步骤实战里我发现真正让车牌识别翻车的往往不是CNN模型而是字符切分。字符切分常用垂直投影法把二值化后的车牌图片按列统计黑色像素数字符区域会出现明显的峰字符间隙出现谷。下面这段代码是垂直投影的雏形import cv2 import numpy as np def vertical_projection(binary_img): # binary_img: 车牌区域二值图背景为0字符为255 h, w binary_img.shape col_sum np.sum(binary_img 0, axis0) # 返回每个列的字符像素数量峰值对应字符 return col_sum # 使用示例 plate_bin cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(plate_bin, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) proj vertical_projection(binary)这一段代码很基础但你会马上遇到两个坑。第一车牌的铆钉和边框会在投影图里制造假峰所以切分前要用形态学闭运算把边框和字符缝隙的干扰连成整体再根据最大连通域找到真实车牌内部区域。第二汉字结构比字母复杂比如“川”字中间有断开垂直投影会把它切碎。解决办法是按字符固定宽度做二次校正中国车牌七个字符的间距相对固定你在投影峰之间用先验宽度约束把断裂的汉字重新拼起来。数据增强方面不要只做翻转和旋转因为车牌字符翻转后语义就变了。更安全的增强是随机调整亮度、对比度、加高斯噪声以及做很小的透视形变。下面这段代码适合在训练时动态生成增强样本from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range5, width_shift_range0.05, height_shift_range0.05, brightness_range[0.8, 1.2], zoom_range0.1, )参数这样设是有讲究的rotation_range只有5度因为车牌字符不会大幅旋转brightness_range在0.8到1.2之间模拟白天不同光照zoom_range控制在0.1防止缩放破坏字符结构。增强后的字符样本要和原图混在一起训练并且单独留出验证集不要用增强过的数据进验证集否则看到的准确率是虚高的。3. MySQL初始化与前后端环境搭建让源码包从解压变成可运行系统3.1 解压后先认目录前端、后端、数据库脚本、文档各在哪拿到压缩包后先做的事不是双击运行而是打开资源管理器确认目录结构。常见的分层方式是四个部分并列frontend放Vue或React源码backend放Python Flask或Spring Boot接口sql目录里放init.sqldocs放说明文档和LW文档。有些包还会把训练好的模型单独放在models目录里比如cnn_char_model.h5或model.pb。如果解压后没有这些目录而是所有文件平铺在一起不要慌说明文档里通常有“环境说明”章节。建议你按这个顺序核对先看README或说明文档找“运行步骤”“数据库初始化”“默认账号密码”再看models目录里是否有模型权重文件没有权重文件的话模型代码再完整也是黑匣子最后打开sql目录里的建库脚本看它包含哪些表。前后端分离项目的坑通常出现在“前端知道后端地址后端知道数据库地址”这个三角关系上。所以第3章的落地目标就是把三者的连接配置对准。3.2 MySQL建库建表字符集、车牌记录表与用户表车牌识别仿真软件要存的数据很简单用户表和识别记录表就够了。但简单不代表可以随便建字符集选择第一优先。车牌号里有中文所以数据库和表必须用utf8mb4而不是utf8否则“京A12345”会变成乱码。下面是一份基础建表脚本符合常见毕设源码的默认设计CREATE DATABASE IF NOT EXISTS license_plate_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE license_plate_db; -- 用户表用于管理员登录前端 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 车牌识别记录表 CREATE TABLE t_record ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) NOT NULL, image_path VARCHAR(255) NOT NULL, confidence DECIMAL(5,4) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO t_user(username, password) VALUES (admin, 123456);表的字段设计有三个值得注意的地方。plate_no用VARCHAR(20)因为新能源车牌是8位比传统7位长image_path不存二进制图片只存相对路径这样迁移项目时不用把图片和数据库绑定在一起confidence用DECIMAL(5,4)存0到1之间的小数方便后续按置信度筛选质量差的记录。导入脚本的方式在Windows和Linux下略有区别。Windows下打开cmd先cd到sql目录再执行mysql -u root -p init.sqlLinux下一样但要注意MySQL服务必须已经启动。导入成功后用SHOW TABLES;确认两张表都在。3.3 后端连接MySQL配置文件与启动顺序后端连接数据库的配置集中在配置文件里。Python Flask常见的是config.pySpring Boot常见的是application.yml内容上都包含host、port、username、password和database名。下面以Flask为例import pymysql from flask import Flask from flask_cors import CORS app Flask(__name__) CORS(app) # 解决跨域前后端分离必开 app.config[MYSQL_HOST] 127.0.0.1 app.config[MYSQL_PORT] 3306 app.config[MYSQL_USER] root app.config[MYSQL_PASSWORD] root app.config[MYSQL_DB] license_plate_db # 用pymysql实现简单的连接封装 def get_conn(): return pymysql.connect( hostapp.config[MYSQL_HOST], portapp.config[MYSQL_PORT], userapp.config[MYSQL_USER], passwordapp.config[MYSQL_PASSWORD], databaseapp.config[MYSQL_DB], charsetutf8mb4 )这段配置最容易出错的是密码不是root以及端口不是3306。源码包里的默认配置和你的本地环境不一致是高频故障。我拿到一个新包的第一件事就是先把配置改成和我本地一致的参数然后单独测试数据库连通性而不是直接启动整个系统。启动顺序也重要。如果后端依赖MySQL必须先保证MySQL服务已启动再启动后端最后启动前端。反向启动时前端能打开但登录会报503或数据库连接超时你会误以为代码有问题实际是顺序问题。3.4 前端启动与接口地址对接前端启动方式看package.json里的scripts。最常见的是Vue项目用npm安装依赖后启动调试服务cd frontend npm install npm run devnpm install是另一个高发翻车点。不同Node版本下node-sass或node-gyp经常编译失败。解决方案是不要死磕先看项目里有没有package-lock.json有的话删除node_modules后执行npm install --registryhttps://registry.npmmirror.com用国内镜像会稳很多。前端要对接后端接口通常会在项目的src/config.js或vue.config.js里配置proxy。下面是一个典型的代理配置// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true } } } };这个配置的意思是前端页面在8080端口跑所有以/api开头的请求都被转发到后端5000端口。所以你不需要在前端代码里写完整的后端地址只写/api/recognize这样的相对路径就行。如果你发现前端请求404第一步查浏览器的Network面板看请求地址是否带上了8080但没转发成功第二步看后端的Flask或Spring Boot进程是否真的监听在5000端口。4. 用CNN模型打通前后端上传图片、识别结果与MySQL落库4.1 后端识别接口从图片上传到返回车牌号后端是整个仿真软件的枢纽。它接收前端上传的图片调用CNN模型识别车牌把结果写入MySQL再返回JSON给前端。最简单的Flask接口长这样app.route(/api/recognize, methods[POST]) def recognize(): if file not in request.files: return jsonify({code: 400, msg: no file}), 400 file request.files[file] img_bytes file.read() # 用OpenCV解码上传的图片 img cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) if img is None: return jsonify({code: 400, msg: bad image}), 400 # 调用车牌识别流程定位、切分、字符识别 plate_no, confidence recognizer.recognize(img) if plate_no is None: return jsonify({code: 200, msg: no plate, plate_no: }) # 识别结果落库 conn get_conn() cursor conn.cursor() cursor.execute( INSERT INTO t_record(plate_no, image_path, confidence) VALUES (%s, %s, %s), (plate_no, uploads/ file.filename, confidence) ) conn.commit() cursor.close() conn.close() return jsonify({code: 200, plate_no: plate_no, confidence: confidence})接口返回的JSON结构要保持稳定前端才能可靠解析。这里返回了code、msg、plate_no和confidencecode固定200或400plate_no为空表示没识别到车牌。有一个细节图片上传后不要直接存二进制到数据库而是保存到uploads目录数据库只记路径。这样数据库体积小备份也方便。识别器对象recognizer的内部逻辑要单独封装。早期我踩过坑把车牌定位、字符切分、CNN推理全塞在接口函数里结果接口一异常整个Flask进程就一起崩。更好的做法是建一个LicensePlateRecognizer类初始化时加载模型识别时只负责推理这样模型文件缺失时至少接口能返回明确的错误信息而不是进程直接退掉。4.2 前端页面上传框、结果卡片与历史记录前端的核心任务只有一个把图片交给后端把后端返回的车牌号展示出来。Vue页面里最简版本是上传按钮加一个结果区域。template div input typefile changeuploadFile / div v-ifplateNo p识别车牌{{ plateNo }}/p p置信度{{ confidence }}/p /div /div /template script import axios from axios; export default { data() { return { plateNo: , confidence: 0 }; }, methods: { async uploadFile(event) { const file event.target.files[0]; const formData new FormData(); formData.append(file, file); const res await axios.post(/api/recognize, formData, { headers: { Content-Type: multipart/form-data } }); this.plateNo res.data.plate_no; this.confidence res.data.confidence; } } }; /script这段代码要注意三个点。第一FormData的字段名必须和后端request.files[file]一致你写formData.append(image, file)后端就要改成request.files[image]这个不一致导致的前后端联调失败非常常见。第二axios默认的Content-Type是application/json但上传文件必须用multipart/form-data所以headers那里要覆盖。第三页面拿到结果后不要只显示车牌号还要显示置信度这样你才能判断这次识别是可信的高置信度结果还是低置信度瞎猜的。4.3 用curl和浏览器联合验证一条完整链路在打开浏览器之前先用curl验证接口能把“前端问题”和“后端问题”彻底隔离开。命令如下curl -X POST http://127.0.0.1:5000/api/recognize \ -F fileD:/test_plates/京A12345.jpg如果返回的JSON里plate_no是“京A12345”说明后端识别和数据库落库都正常。如果返回no plate问题在车牌识别流程不要动前端。如果请求超时或连接失败问题在服务启动或端口配置。接口通了之后再用浏览器前端走一遍上传流程。此时打开浏览器开发者工具的Network面板看上传请求的状态码和响应。如果接口返回200但前端页面还是空白多半是前端解析字段名写错了比如后端返回confidence前端读成了score。数据库验证是最后一步。登录MySQL后执行SELECT id, plate_no, confidence, create_time FROM t_record ORDER BY id DESC LIMIT 5;如果你看到识别记录按时间一条条增加说明从图片上传到CNN推理到MySQL落库整条链路都是通的。这一步做完你的仿真软件就算真正“能跑了”。5. 车牌识别避坑手册5个高频故障与排查方法5.1 数据库中文乱码现象是“京A12345”变成“???A12345”现象前端界面显示的车牌号没有汉字只有问号或者数据库里plate_no字段存的是乱码字符串。原因这是最典型的字符集问题。最常见的是数据库建库时用了latin1或utf8mb3也可能是数据库连接字符串的charset没有指定utf8mb4。数据表创建后字段字符集同样可以单独设置只改连接串不够。解决把数据库、表、字段的字符集全部改成utf8mb4然后重新导入init.sql。已经存在的乱码数据删掉重来不要试图修复。后端连接串里也要加上charsetutf8mb4。改完之后插入一条“京A12345”测试一下确认显示正常再继续前端。5.2 前端页面白屏接口请求404现象是浏览器F12里请求地址是8080/api/recognize现象前端能打开但上传图片后没有任何反应Network面板显示http://localhost:8080/api/recognize返回404。原因代理没有生效。最常见的是vue.config.js里写了proxy但前端启动脚本没有加载vue.config.js还有一种是后端端口和target端口不一致比如后端跑在5001target仍写5000。解决先确认后端进程监听端口用netstat -ano | findstr 5000在Windows上看Linux用ss -ltnp | grep 5000。确认端口后把target改成实际端口。如果项目里用的是axios封装的baseURL直接写了http://localhost:5000那就不需要proxy但要注意跨域后端必须开启CORS。前端白屏的排查顺序永远是先curl后端再看proxy配置最后才改前端代码。5.3 后端启动报错模型文件不存在或shape不匹配现象Flask或Spring Boot进程能启动但一调用识别接口控制台报“FileNotFoundError: cnn_char_model.h5”或者“Input 0 of layer conv2d is incompatible”。原因模型路径写死为绝对路径项目换目录后路径失效或者是模型训练时输入尺寸是32×32后端预处理代码按20×20缩放。解决模型路径尽量用相对路径或者通过配置文件传入不要写死D:/codes/model.h5。shape不匹配的做法是用model.input_shape打印一下模型期望尺寸然后把预处理代码改成和训练时一致。这属于完全没有排查头绪时最有效的第一步。5.4 汉字识别错得离谱所有车牌号都识别成同一个字现象识别接口正常返回但“京A12345”被识别成“示A12345”“示A12346”汉字永远是同一个错字字母数字反而是对的。原因字符映射表错位。训练模型时字符集顺序是京津沪渝...但推理代码里的labels列表是京津沪渝...或者Python里字符串排序把汉字按Unicode排了导致索引对应的字符整体错位。字母数字恰好对是因为它们排在汉字后面错位只影响汉字。解决打印推理代码里的labels列表和训练记录里的字符表比对逐个校对。我遇到这种情况时会把字符映射表单独存成JSON文件训练和推理共用同一份文件从源头上杜绝错位。这也是我在模型训练项目里最坚持的一个习惯。5.5 上传大图后识别接口超时现象上传手机照片要等好几秒最后前端报超时后端控制台显示识别耗时超过10秒。原因图片像素过大车牌定位阶段对全图做多次缩放和高斯模糊时间全花在预处理上另外如果模型是TensorFlow的未量化float32模型单字符推理在CPU上也要几十毫秒七个字符累加就更慢。解决在后端识别前先做一次图片尺寸限制最长边超过1280就等比例缩小再进入定位流程。推理部分如果源码包用的是Keras先把h5模型转成ONNX再用onnxruntime推理单字符耗时能从几十毫秒降到几毫秒。转换命令是python -m tf2onnx.convert --saved-model ./model --output model.onnx但这个命令依赖具体TensorFlow版本包里的README如果写了模型来源按它来最省事。6. 从仿真到可用置信度拒绝、数据增强与接口压测仿真软件跑通之后如果你还想让它更像一个能交差的系统有三件事值得做。第一件是给识别结果加置信度阈值。前面设计的接口已经返回confidence了前端展示时加一个判断如果置信度低于0.6就显示“疑似识别错误”并把这一条记录标记为低置信度而不写入正常记录表。这个逻辑很简单但能明显提升演示观感因为模型遇到看不见的车牌时会乱猜乱猜的结果会污染历史记录。我在自己的项目里是低于0.6直接返回“未识别”宁可少一次成功也不要一次明显的错字。第二件是补数据增强但不要盲目做随机旋转和翻转。车牌字符不是自然物体翻转后语义就变了。更有效的是对亮度、对比度和透视角度做小范围扰动。用标注工具把几十张真实牌照抠出字符混入原有数据集重新训练比写几百行算法调整代码更有效因为真实场景里的模糊和反光是模拟增强很难完全替代的。第三件是接口压测。演示现场最怕的就是部署机性能不够来了几个人同时上传接口卡死。压测用Apache Bench最方便命令是ab -n 100 -c 10 -T multipart/form-data -p body.txt http://127.0.0.1:5000/api/recognize参数不用一次调满重点看Request per second和Time per request。如果单机只有2核4G建议把并发数控制在10以内同时在后端加上线程池限制避免高并发时MySQL连接被占满。生产环境要上再考虑把模型服务独立部署Flask只做业务转发。我自己的习惯是每次调试完识别率都把当前样本集、标签文件、模型权重和一次评审记录四个文件放在同一个目录下文件名带日期。这样两周后回看某个识别错误的样本能立刻知道它到底是数据问题还是代码问题不用对着黑匣子猜。希望这套从解压到联调再到进阶验证的思路能帮你把这个“基于卷积神经网络的车牌识别仿真软件源代码”真正跑成自己手里的可演示项目。希望帮到你。本文还有配套的精品资源点击获取
返回列表