简介面向需要完成Python课程设计或毕业设计的开发者这份基于卷积神经网络的车牌识别系统提供了可运行的前后端源码与MySQL数据库脚本。系统覆盖车牌图像上传识别、车牌信息管理、用户登录与权限管理、修改密码等完整功能配套Python 3.6.8、MySQL 5.7、PyCharm等环境说明及技术文档便于快速搭建项目并理解CNN模型的训练与应用流程。压缩包共541个文件以Python源码、JS/CSS/HTML前端文件、PNG/JPG/GIF图像素材、数据库及配置文档为主另含模型权重、字体与说明文档整体体积约268MB目录结构清晰。当前已有48人学习下载适合作为课程设计参考或入门计算机视觉项目的实战样例。其中包含数据字典与接口说明可帮助读者梳理设计思路、快速复现并用于答辩展示。1. 车牌识别仿真软件卡在哪CNN模型只是链路的一环车牌识别在课程设计和毕业设计里是最容易“做到一半卡住”的方向。很多人在 Jupyter 里把卷积神经网络CNN模型训练得不错测试集上准确率能到 95%一旦拼成完整系统就露馅网页上传图片后没反应后端算完结果写不进 MySQL前端白屏一片。这套基于卷积神经网络的车牌识别仿真软件真正的价值不在模型本身而在于把 CNN 训练、Spring Boot/Vue 前后端、MySQL 和说明文档串成一条能演示、能验收的完整链路。适合要交毕设、要打比赛或者想快速搭一套可复现 demo 的从业者。下面按我的落地顺序讲先拆原理再跑通工程最后聊参数和踩坑。2. 卷积神经网络车牌识别的原理与模型结构为什么CNN比模板匹配更可靠2.1 LeNet-5式结构为什么适合车牌字符卷积、权值共享与汇聚层车牌识别本质上是一个图像分类问题把车牌图片中的每个字符切出来然后判断它是哪个汉字、字母或数字。十年前用 MATLAB 做车牌识别常见做法是模板匹配先二值化、算字符的像素分布再用模板库里的字符做相关性匹配。这套逻辑在干净图片上可行但光照变暗、车牌倾斜、字符沾了泥点匹配率掉得飞快。卷积神经网络CNN解决的正是“特征怎么提”的问题。以 LeNet-5 为代表的结构先用卷积核在图片上滑动提取边缘、角点、笔画等局部特征然后通过权值共享把卷积核的参数数量压下来。同一个卷积核在整张图上复用这就是权值共享再加上汇聚层也叫池化层MaxPooling对特征图降采样模型对字符位置的微小偏移就没那么敏感。车牌字符本身是规整笔画CNN 提取到的边缘和纹理特征比人工设计的模板可靠得多这也是现在车牌识别系统几乎不用 MATLAB 模板匹配的原因。实际工程里我不建议一上来就堆 ResNet、EfficientNet 这种大网络。车牌字符是小图分类输入尺寸通常只有几十像素见方LeNet-5 量级的网络已经足够。项目里的模型权重如果没给我一般会先用一个轻量 CNN 结构跑通链路再回头调精度。这个顺序很重要先把整条链路跑通再优化模型不然你会在训练阶段耗掉大量时间最后发现前后端联调才是真正的坑。2.2 完整识别链路怎么拆车牌定位、字符分割与分类头设计一个能用的车牌识别系统前后要经过三个步骤车牌定位、字符分割、字符识别。CNN 在这三步里都能参与但定位和分割的工程量差别很大。常见的做法有两种。第一种是把车牌定位也交给神经网络用目标检测模型比如 YOLO 系列框出车牌区域再做透视校正和字符分割。第二种是仿真软件里的简化版假设输入的图片已经是裁剪好的车牌区域或者前端先手动框选车牌再交给 CNN 做字符分类。这套仿真软件如果面向毕设演示我建议先采用第二种简化方案把主要精力放在“识别入库”链路上因为答辩时老师更关心整条数据流是否贯通而不是定位模型的 mAP。字符分割也有讲究。标准蓝牌是 7 位字符第一位是省份简称汉字第二位是字母后面是字母和数字混合新能源车牌是 8 位第二位和第七位之间有分隔符。如果直接用固定宽度切分新能源车牌会切歪。我习惯的做法是先用连通域分析或者垂直投影找出每个字符的边界再做等宽修正最后统一 Resize 成固定尺寸喂给 CNN 分类器。分类头设计看的是字符集大小。中国大陆车牌字符集大约包含 31 个省份简称、24 个字母I 和 O 不使用、10 个数字加起来 60 多个类别。如果模型是每个字符单独分类最后的全连接层输出维度就是字符集大小后面接 Softmax。项目里的模型权重如果不是你自己训练的一定要先确认它的输出类别顺序否则前端显示的中文字符会全部错位。训练代码里通常会有一份 id_to_char 的映射表推理脚本必须和它严格对应。2.3 训练数据和超参怎么定图像尺寸、数据集划分与类别平衡训练数据这个环节是整套系统里最不能省的部分。公开的车牌数据集比如 CCPD 这类停车场场景数据集包含大量真实车牌图片但它们的文件名、标注格式各不相同。从项目里的说明文档可以反推数据格式如果文档里写了 train/val/test 三个目录图片命名通常是“车牌号_序号.jpg”那么数据集已经按监督学习格式准备好直接训练即可。输入图像尺寸直接影响模型参数量和推理速度。我一般把单字符图片 Resize 到 32x32如果字符带汉字会适当放宽到 40x40因为汉字笔画密集太小了卷积核提取不到细节。颜色空间建议保留 RGB不要转灰度。车牌底色蓝底白字、黄底黑字、绿底新能源本身就是强特征转成灰度后这一信息直接丢失识别率会掉一到两个百分点。数据集划分按照 7:2:1 比较常见。训练时要注意类别平衡省份简称“京、沪、粤”样本多偏远省份简称样本少如果不做处理模型会把所有汉字都往高频类别上偏。下面这段数据加载代码是每个字符类别一个文件夹的方式最省事也最不容易错from torch.utils.data import Dataset from PIL import Image import os class PlateCharDataset(Dataset): def __init__(self, root, transformNone): self.samples [] self.transform transform for label in os.listdir(root): # 每个子目录名是字符标签 for fname in os.listdir(os.path.join(root, label)): if fname.lower().endswith((.jpg, .png)): self.samples.append((os.path.join(root, label, fname), label)) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] img Image.open(path).convert(RGB).resize((32, 32)) if self.transform: img self.transform(img) return img, label逻辑说明getitem里每次读图都做一次 Resize训练阶段没问题推理阶段如果追求速度可以改成预处理完的二进制文件。参数上注意两点一是 resize 尺寸和网络输入一致二是标签处理成整数索引而不是中文字符否则 PyTorch 的 CrossEntropyLoss 不认字符串。建议单独维护一个id2char.json把“0-京、1-沪”这类映射关系固化下来训练和推理共用。3. 把前后端MySQL跑起来从建库到识别结果入库3.1 源码目录最先看哪几个文件前后端、模型与文档的落位拿到这套源码先不要急着点运行花十分钟把目录结构看明白。典型的完整前后端项目会是这样的布局license-plate-system/ ├── frontend/ # Vue前端工程 ├── backend/ # Spring Boot后端工程 ├── model/ # CNN训练代码、权重文件、推理脚本 ├── sql/ # 数据库初始化脚本 └── docs/ # 说明文档和LW设计文档docs 目录下的说明文档和 LW 设计材料通常包含需求分析、数据库设计、接口定义和测试截图先把接口定义看一遍后面联调会省很多事。model 目录如果只有训练代码没有权重就需要先跑训练或下载预训练权重否则后端调用会报“找不到模型文件”。我的习惯是先看三份东西sql 脚本、后端 application.yml、前端接口封装文件。这三份能对上项目大半已经跑得通了。3.2 MySQL建库建表utf8mb4是车牌中文不乱码的前提MySQL 安装配置完成后用 MySQL Workbench 或命令行执行 sql 目录下的脚本。如果脚本不存在自己建两张表完全够用一张存识别记录一张存用户或操作日志。CREATE DATABASE IF NOT EXISTS license_plate DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE license_plate; CREATE TABLE tbl_record ( id INT AUTO_INCREMENT PRIMARY KEY, plate_text VARCHAR(20) NOT NULL COMMENT 识别结果, plate_color VARCHAR(10) DEFAULT blue COMMENT 车牌颜色, confidence DOUBLE COMMENT 识别置信度, image_path VARCHAR(255) COMMENT 原图存放路径, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 识别时间 );车牌识别结果里包含中文所以库、表、连接 URL 三层都必须用 utf8mb4。很多新手只改了数据库字符集连接串没加 characterEncodingutf8结果前端显示中文没问题写进数据库再读出来就是乱码。create_time 用 DATETIME 加默认当前时间后端不需要每次手动塞时间。如果使用 Spring Boot数据源配置里还要注意 MySQL 8 的驱动类名和时区问题。驱动类名是 com.mysql.cj.jdbc.Driver连接串必须带上 serverTimezoneAsia/Shanghai否则会报时区错误。数据库连接池我一般用 HikariCP配置连接数不用太大演示场景 max-pool-size 设 10 就够设太大反而占资源。注意SQL 脚本导入后先单独执行一句 SELECT * FROM tbl_record 确认能查到空表再启动后端避免把“数据库问题”和“应用问题”混在一起。3.3 后端识别接口怎么写Spring Boot调Python推理脚本后端接口是整个系统的中枢。Spring Boot 提供 HTTP 入口接收前端上传的图片然后调用 Python 推理脚本拿到识别结果后写 MySQL。一个最小可用的 ControllerRestController RequestMapping(/api/plate) public class PlateController { PostMapping(/recognize) public Result recognize(RequestParam(file) MultipartFile file) { // 1. 保存上传文件到临时目录 String tempPath /tmp/ System.currentTimeMillis() .jpg; file.transferTo(new File(tempPath)); // 2. 调用Python推理脚本 String cmd python model/predict.py --image tempPath; String output runCmd(cmd); // 3. 解析输出并写入数据库 PlateRecord record saveRecord(output); return Result.success(record); } }runCmd 方法建议用 ProcessBuilder 而不是 Runtime.exec因为 ProcessBuilder 能更方便地控制工作目录和合并错误流。有个关键参数redirectErrorStream(true) 必须设上否则 Python 脚本的报错写到 stderr你只能看到空白输出排错会非常痛苦。更工程化的做法是让 Python 推理模块常驻暴露一个本机 HTTP 服务Spring Boot 通过 HTTP 调用。这种方式避免了每次请求都重新加载模型权重推理速度和资源占用都可控。后台演示项目用 ProcessBuilder 能跑通但不要在产品里这么干每次启 Python 进程的开销在低配机器上能到几百毫秒。保存识别结果时同时写 confidence 和 image_path。image_path 这个字段容易被忽略但答辩演示时评委常会问“识别的这张原图存在哪”有路径字段就能直接展示。3.4 前端上传页面怎么对接Vue axios 与跨域/Tomcat部署前端部分是最容易“看起来没问题却跑不通”的地方。Vue 工程里上传图片后要通过 axios 把文件以 multipart/form-data 格式发给后端。export default { data() { return { file: null, plateText: , confidence: 0 }; }, methods: { async uploadImage(e) { const file e.target.files[0]; if (!file) return; const formData new FormData(); formData.append(file, file); const res await axios.post(/api/plate/recognize, formData); this.plateText res.data.data.plateText; this.confidence res.data.data.confidence; } } }这里最关键的是 baseURL 配置。开发阶段Vue 前端跑在 8080 端口后端跑在 8081跨域请求会被浏览器拦截。常见做法是在 vue.config.js 里配置 devServer 的转发规则把 /api 前缀的请求转发到后端地址后端同时也要放开 CORS。两个都要做只改一个就会踩坑。如果最终要部署到 Tomcat 跑演示前端打包后放入 webapps后端也打成 war 包。Spring Boot 内嵌 Tomcat 时可以直接 java -jar 跑但项目如果要求外置 Tomcat需要把打包方式改成 war。部署路径要注意如果工程名是 license-plate访问 URL 会带上这个上下文路径前端的 axios 请求地址必须一致否则 404。血泪经验改完部署路径后前端资源加载正常接口 404多半是 context-path 没对上先 curl 一下后端接口再动前端。4. 精度、速度与显存怎么取舍识别系统的必调参数4.1 图像预处理参数尺寸、归一化与颜色空间不管模型是自训还是拿现成权重输入图像预处理必须和训练时保持一致。很多项目翻车就翻在这里训练时做了一整套归一化推理时忘了做准确率从 95% 掉到 60%。transform transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor(), # 转成Tensor并把像素值缩放到[0,1] transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ])Resize 尺寸要和训练一致。Normalize 的 mean/std 如果训练时用的是 [0.5,0.5,0.5] 和 [0.5,0.5,0.5]推理时就必须一样。有些源码里训练用了 ImageNet 的均值推理脚本里写的却是另一个这种不一致很难肉眼发现最好的排查办法是看推理脚本里是否 import 了和训练脚本一样的 transform 定义。颜色空间尽量保留 RGB。MATLAB 老教程喜欢转灰度做二值化因为模板匹配对光照敏感二值化可以消除部分干扰但 CNN 方案可以直接从颜色里学到蓝牌、黄牌、绿牌的差异。硬要转灰度整体准确率会掉。如果你用的是新能源绿牌数据集这个问题更明显绿色和黄色在灰度图里几乎区分不开。4.2 训练超参和数据增强batch size、学习率与样本均衡训练超参直接抄经典配置通常就在安全区。batch size 设 32 或 64学习率用 Adam 优化器配 0.001跑 30 到 50 个 epoch。这三个参数在车牌字符分类这个小任务上很好使。数据增强是拉高鲁棒性的重点。车牌图片会遇到光照不均、运动模糊、倾斜、污渍增强策略可以围绕这些真实场景来做亮度扰动在 0.8 到 1.2 倍之间随机调高斯模糊kernel 3x3概率 0.2透视变换模拟车牌拍摄角度偏移角度限制在 5 度以内随机遮挡随机抹掉一小块区域模拟污渍增强比例要控制尤其是透视变换幅度。车牌透视变形过大字符形状不再真实反而会干扰训练。我一般把透视变换的概率设在 0.3角度限制在 5 度以内模糊概率设在 0.2。这些参数说不上有什么严格最优属于“你现场试几组看 val 准确率的变化拍照记录选最稳的一组”。训练过程里如果 val 准确率一直持平不涨优先怀疑增强太强而不是网络太弱。类别不平衡用加权采样解决。采样器按样本数倒数分配权重让“京、沪、粤”这类高频字符不霸占训练低频省份简称每个 epoch 也能被抽到。PyTorch 里用 WeightedRandomSampler 替换 DataLoader 的 sampler 参数即可注意 shuffle 参数要设成 False因为自定义采样器已经承担了乱序的职责两个一起开会导致分布错乱。4.3 推理速度和显存功耗的平衡CPU推理、半精度与量化仿真软件可能跑在没有独立显卡的机器上推理速度是答辩现场体验的关键。CNN 字符分类模型很小单张车牌从切分到识别CPU 推理也能达到几十毫秒级别不需要刻意上 GPU。代码里一定要加 torch.no_grad()否则会构建计算图显存和 CPU 内存白白翻倍。有 GPU 但显存只有 4G 时输入 batch 设为 1不要为了测试跑大 batch。这里有个容易忽略的点Half() 半精度转换在 GPU 上有明显加速但部分 CPU 不支持半精度运算硬转会报错或变慢所以 CPU 环境直接用 float32。如果项目提供 TensorFlow 权重可以用 TFLite 做量化把模型从 float32 压缩到 int8推理速度能提升一倍以上精度损失通常在 1% 以内。量化后的模型符号表会变对字符集的支持要先做验证。做产品化的时候其实有更省事的方案直接对接市面上的车牌识别一体机。这种硬件设备把相机和识别算法打包在一起通过 SDK 或 HTTP 返回识别结果配置工具连上就能用。但仿真软件的价值在于教学和二次开发你得能控制中间每一层所以 CNN 推理、前后端都得自己写。硬件方案可以作为对比参照如果只是想部署一个识别服务买一体机最省心如果想理解车牌识别背后的技术链路那就把代码吃透。5. 车牌识别仿真软件的五个坑与排查清单5.1 识别结果总是“无车牌”定位失效而不是分类问题现象上传整张道路图片接口返回空车牌或“无车牌”但把车牌部分裁剪下来再识别结果又正常。原因这是定位环节失效而不是 CNN 分类器出问题。仿真软件如果假设输入是裁剪好的车牌那问题就出在这个假设被打破前端上传了整图后端直接丢给分类器。字符分类器只能回答“这串字符是什么”回答不了“车牌在哪”。解决在接口层先加一个车牌区域检测。最简单的做法是接 YOLO或者用颜色阈值定位蓝色车牌区域再裁剪区域交给字符分类。如果项目定位功能不支持整图识别就在前端提示用户必须裁剪车牌区域并且后端对传入图片尺寸做校验宽高比明显不是车牌的图直接返回友好错误。我在毕设里见过最隐蔽的版本前端裁剪框实际没生效用户以为裁了发的还是原图。5.2 MySQL连接失败时区、字符集与驱动版本现象后端启动时报错 Cant connect through socket /tmp/mysql.sock或者 Communications link failure。原因第一种是 MySQL 服务没启动或客户端走 socket 文件而数据库在另一台机器。第二种是驱动版本与 MySQL 8 的认证方式不兼容MySQL 8 默认使用 caching_sha2_password老驱动不认识。解决先确认服务状态本机执行 systemctl start mysql 或 service mysql start。连接串里不要用简写明确写 host127.0.0.1 和端口 3306绕过 socket 文件。MySQL 8 配套的驱动要选 mysql-connector-java 8.0 系列同时 URL 加 serverTimezoneAsia/Shanghai 和 characterEncodingutf8。这组参数我用一次记一次少一个都连不上或连上乱码。5.3 前端上传图片后白屏或请求失败跨域与部署路径现象前端页面能打开选择图片后没有任何反应浏览器控制台报 CORS 错误或 404。原因开发环境是跨域问题前端在 8080、后端在 8081浏览器拦截了非同源请求。部署环境是路径问题Tomcat 下 war 包上下文名变了前端请求路径对不上。解决后端写一个 CORS 配置类放行本地地址前端 devServer 配置转发规则。两件事都要做别只做一个。部署环境按顺序排查先访问后端接口确认通再改前端请求路径。记住排查顺序浏览器控制台、后端日志、MySQL 日志不要在代码里瞎猜。5.4 训练loss不降反升数据没归一化与类别不平衡现象训练第一个 epoch loss 在 2 左右之后不降还在往上走或者干脆跳出 NaN。原因常见的有三个。一是输入数据没归一化像素值 0 到 255 直接进网络大输入值让梯度爆炸。二是学习率过大尤其是用 SGD 时 0.01 对这个小模型来说容易发散。三是标签和输出维度对不上类别数配置错误导致梯度反向传播异常。解决先确认数据加载代码里有 ToTensor它会把像素缩放到 [0,1]。把学习率降到 0.001 并换 Adam。再检查全连接层输出维度是否等于字符集大小。如果 loss 还是 NaN在模型 forward 里加一句 x torch.nan_to_num(x) 临时定位看到底是哪层产生的 NaN。loss 曲线偶尔跳一下不一定是坏事但连着三个 epoch 往上走大概率是数据或超参问题别再硬跑。5.5 中文车牌识别成乱码从模型输出到数据库的编码链路现象屏幕上显示“京A12345”正常写进 MySQL 再查出来变成“浜A12345”或问号。原因编码链路断了一环。模型输出的中文是字符串经过 Java 接口、JDBC、MySQL 三层任何一层字符集不对中文都会变乱码。最常见的断点是数据库表字符集不是 utf8mb4或者 JDBC 连接串没加 characterEncodingutf8。解决按顺序排查先看浏览器返回的 JSON 里中文正不正常正常则问题在数据库再查表字符集用 SHOW CREATE TABLE tbl_record 确认最后统一在连接串末尾拼上 characterEncodingutf8。保留字的坑也要注意车牌号里的“O”和“0”容易混淆如果业务上不允许应在入库前做一次替换并记录原始值。6. 用批量脚本验证整套系统准确率怎么算才不算自欺欺人6.1 写一个批量测试脚本统计识别准确率单张图片识别成功说明不了问题。我习惯准备一个 test_images 目录每张图片按“真实车牌号_序号.jpg”命名写脚本批量请求后端接口统计准确率。这一步能把“模型还行”和“系统可用”区分开。import os import requests images os.listdir(test_images) total 0 correct 0 for name in images: path os.path.join(test_images, name) true_label name.split(_)[0] # 文件名约定真实车牌号_序号.jpg with open(path, rb) as f: resp requests.post( http://127.0.0.1:8081/api/plate/recognize, files{file: (name, f, image/jpeg)}, timeout5 ) result resp.json()[data][plateText] total 1 correct (result true_label) print(f{name}: 预测 {result}, 真实 {true_label}) print(f准确率: {correct / total:.2%} ({correct}/{total}))逻辑说明脚本遍历目录用文件名里的前缀当真实标签。requests 的 timeout 参数必须设否则接口卡住时脚本会无限等下去。文件名里不要带中文避免不同操作系统下编码不一致。6.2 接口并发和超时设置演示现场最容易出问题的不是识别精度而是前端“转圈等结果”。给 axios 和 Spring Boot 接口都设置超时前端超过 5 秒直接提示失败而不是一直转圈。后端如果是 ProcessBuilder 调 Python要给子进程加超时销毁逻辑防止 Python 卡死拖垮整个服务。HikariCP 连接池的 max-lifetime 也值得看一眼默认值在 MySQL 8 下偶尔会有连接被服务端断开的情况把 connection-timeout 设到 3000 毫秒比较稳妥。6.3 部署前的检查清单表格里这几项我每次演示前都会过一遍花不了三分钟但能挡掉九成现场翻车检查项预期结果失败时看哪MySQL 服务状态systemctl status 显示 active错误日志 /tmp/mysql.sock 路径后端启动日志无报错端口监听正常时区、字符集、驱动版本curl 调识别接口返回 JSON 且中文正常CORS 配置、Python 路径前端上传一张测试图页面显示车牌号和置信度浏览器 console接口 404/500数据库插入记录MySQL 查询中文不乱码characterEncoding、表字符集我现在的习惯是不管项目多着急先写这个批量脚本再碰代码。它帮我挡掉过无数次“演示现场翻车”也让我能跟人说清楚这套系统的准确率到底是怎么算出来的。希望帮到你。本文还有配套的精品资源点击获取
