简介这是基于Python与卷积神经网络的车牌识别仿真软件毕业设计项目面向计算机相关专业学生或需要快速搭建完整课设方案的人群。资源从登录页面、后台首页到车牌照片上传与识别形成完整闭环并包含系统测试方法与步骤适合用于课程设计、毕业设计或CNN入门实践。资源包共700个文件包含49个Python脚本、PyTorch权重、SQL数据库脚本、HTML/CSS/JS前端页面、大量GIF/PNG图片素材、Word/PDF设计说明等压缩包约275.72MB。这些文件覆盖开发源码、界面样式、数据初始化、演示视频和论文支撑材料结构较完整。已有154人学习下载配合演示视频可快速跑通项目结合详细设计文档、源码和数据库脚本能帮助理解车牌识别中的图像上传、模型调用与结果展示逻辑前端样式和素材也便于直接替换改造适合答辩演示和二次开发。1. 基于卷积神经网络的车牌识别仿真软件毕业设计到底在做什么如果你拿到这个标题大概率会以为它只是“训练好的模型套个界面”。但真正的仿真软件要跑通一条完整链路读入车牌图像、CNN定位与识别、结果写进数据库、界面展示识别记录。这个zip里的源码、数据库和演示视频就是让你在答辩现场能直接演示而不是只讲PPT。适合计算机、电子、自动化专业做毕设或课设也适合想低成本上手卷积神经网络落地的新手。下面按我实际做过的方案把架构、代码、参数和踩坑一次说清楚。2. 车牌识别仿真软件的整体架构与CNN选型先拆链路再写代码设计之前先别急着装框架第一步是把数据流画出来一张带车牌的图片进软件经过车牌定位、字符分割、字符识别三个环节输出“鲁A·B1234”这样的字符串同时把识别记录写入数据库。整个仿真软件拆成四层界面层、算法层、数据层和演示层。界面层用PyQt或Tkinter算法层是CNN模型和图像处理函数数据层用SQLite演示层是答辩存档用的视频和截图。这个分层看起来老套但好处是每一层都能单独测试和替换后面换模型、换数据库都不至于推倒重来。2.1 四层结构怎么分工界面层、算法层、数据层、演示层各管什么界面层只做三件事选图片、显示图片、显示识别结果。别往界面里塞业务逻辑否则后期换模型时改起来很痛苦。算法层是核心包含两个独立方法车牌定位和字符识别。定位负责从背景里框出车牌区域识别负责把区域里的字符变成文本。数据层建议使用SQLite因为毕设场景不需要像MySQL那样单独装服务文件型数据库拷到哪都能跑论文里也能解释“采用轻量级嵌入式数据库”。演示层是交付的一部分录一段3分钟左右的视频就够了别录太长答辩老师只会快进看。每一层都要可以独立验证界面层点按钮不崩算法层单测准确率数据层看SQLite文件里的记录是否完整。我见过太多毕设把所有代码写在一个main.py里结果定位参数调一次要重启整个界面效率极低。把函数拆成detect.py、recognize.py、db.py、ui.py四个模块后面你调参时就知道这个拆分的价值了。2.2 为什么选CNN而不是传统OCR鲁棒性、可解释性和工作量传统车牌识别用颜色分割加形态学加模板匹配在固定场景下效果不差但一遇到倾斜、模糊、光照变化就崩。CNN卷积神经网络的优势在于自动提取特征对轻微畸变和噪声的容忍度更高。更重要的是题目里写着“基于卷积神经网络”论文正文里整章写CNN结构、训练过程和实验对比答辩主线非常顺。常见的做法有两种一是用轻量CNN做字符分类定位靠OpenCV二是用YOLO做端到端检测再用CRNN做序列识别。我建议毕设选第一种原因很现实字符分类模型数据好造、训练快、出了问题好排查端到端CRNN虽然更“高级”但需要大量整图标注短时间很难做干净。YOLO方案还要额外处理检测框的NMS和类别映射对你而言是重复造轮子。仿真软件更看重“可控”——你能解释每一块逻辑而“调包”在答辩时是最容易被追问的。字符分类CNN是你手写的OpenCV定位流程也是你手写的这两点足够构成工作量。下面这张表是我在选型时常用的对比方案定位方式识别方式标注成本训练时长答辩友好度方案AOpenCV颜色轮廓CNN字符分类低短高方案BYOLO检测CNN字符分类中中高方案CYOLO检测CRNN序列识别高长低方案C的识别准确率上限最高但底层的CTC Loss、Sequence解码要解释清楚并不容易。如果你只做十几天直接选方案A。如果你论文时间充裕可以选方案B定位鲁棒性更好但要注意YOLO的备份和演示视频要在CPU上流畅跑否则答辩现场会很难看。2.3 网络结构怎么定字符分类CNN的参数骨架字符分类模型输入是单字符图典型尺寸40×100灰度图。输出类别数取决于你的数据集汉字31类、数字10类、字母24类有些省份简称共用一个模型实际类别数是67个左右。一个足够支撑毕设的轻量网络不需要多深两层卷积加两层全连接就够。具体结构输入40×100×1Conv116个3×3卷积核ReLU2×2 MaxPool输出20×50×16Conv232个3×3卷积核ReLU2×2 MaxPool输出10×25×32Flatten10×25×32 8000FC1128神经元ReLUFC267神经元Softmax这个结构的参数量不到20万CPU上单张推理不到10毫秒。它的价值不在于刷榜而在于让论文里的“网络结构图”和“参数量计算”有东西可写。如果你只有一个GPU或干脆用CPU训练这个小模型几分钟就能收敛到95%以上完全够用。注意输入尺寸不要随意改。很多同学把单字符图resize成64×64结果全连接层的特征数量变了模型跑不起来。我在下一章会给可运行的PyTorch代码这里的尺寸参数会直接对应到代码里的Linear输入。2.4 Python版本与依赖环境第一次跑别用最新版Python环境是不少同学第一个翻车点。我见过在Python 3.12上装TensorFlow失败后直接放弃的人。做这个项目建议Python 3.9或3.10然后装opencv-python、PyTorchCPU版即可、PyQt5。开发机不一定要有NVIDIA显卡CPU能跑推理训字符分类网络也就几十个epoch不会太慢。环境装好后立刻执行pip freeze requirements.txt换机器时一条命令恢复环境不会把时间耗在“为什么我这跑不起来”上。提示如果你用Anaconda创建独立环境时指定python3.9后面所有依赖都装在这个环境里别污染base环境。2.5 为什么仿真软件要带数据库和演示视频答辩材料的完整性标题里明确写了“源码数据库演示视频”说明这个项目不是要求你只提供一个训练好的模型而是要求交付一个“软件作品”。答辩时老师会关注三点第一系统能不能现场跑第二识别结果有没有被保存第三你实验过程有没有影像记录。SQLite文件里能查到每次识别的历史记录这就是“数据库”的价值演示视频则是用来防止现场设备出问题、以及提交给学院存档的备用材料。录视频时最好把界面操作和数据库变化录在同一屏里让老师看到点击识别后records.db里真的多了一行。3. 用Python把车牌识别跑通数据集准备、训练与导出的可复现步骤仿真软件里最容易被轻视但最影响效果的是数据。视觉领域有句话模型是玄学数据是科学。这里说的数据不只是数量还包括组织方式、预处理和训练参数。下面按我常用的做法从数据集构建开始一步步来。3.1 数据集组织与标注目录结构、CSV标注和训练集划分常见数据来源是CCPD中国城市停车数据集它直接给出标注好的车牌图但标注的是四个角点需要自己转换。另一种是自建收集实拍车牌图或者用程序合成车牌。对于毕设合成数据配合少量真实数据就够。目录结构我习惯这样放data/ synth/ images/train/ labels/train.csv real/ images/val/ labels/val.csv标注CSV每行包含image_path和7个字符列。因为中国大陆车牌是省份简称加字母加6位字符一共7个字符。注意汉字不能用ASCII直接编码需要构建自己的字符表。读取和构建字符表的代码import csv char_map {} # 字符 - id id_map {} # id - 字符 with open(data/synth/labels/train.csv, r, encodingutf-8) as f: reader csv.reader(f) header next(reader) # 跳过表头 for row in reader: if not row: continue chars row[1:] # 从第2列起是7个字符 for c in chars: if c not in char_map: idx len(char_map) char_map[c] idx id_map[idx] c print(字符类别数:, len(char_map))逻辑说明先跳过表头循环每一行取出字符遇到新字符就分配一个递增id同时维护id_map用于推理时把预测id还原回文字。参数说明CSV的编码在Windows下可能是gbk如果你的文件报编码错误把encodingutf-8改成gbk字符类别数应该稳定在65到70之间如果只有20多说明你的数据集字符覆盖太窄需要补充。接下来把CSV转换成训练用的张量。这一步要自己做DataSet类但毕设里最简单的做法是先预读所有图片路径到内存再用torch.utils.data.Dataset包装。注意训练集和验证集要按“车牌ID”划分而不是单纯随机划分否则同一个车牌的不同裁剪会同时出现在两边造成数据泄漏。3.2 训练一个字符分类CNNPyTorch代码与关键参数下面以PyTorch为例。首先定义网络与2.3节的参数骨架完全对应import torch.nn as nn class CharCNN(nn.Module): def __init__(self, num_classes): super().__init__() # 输入尺寸(batch, 1, 100, 40)100是高40是宽 self.features nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(32 * 10 * 25, 128), nn.ReLU(), nn.Linear(128, num_classes), ) def forward(self, x): return self.classifier(self.features(x))逻辑说明输入是形状(batch, 1, 100, 40)的灰度图经过两次卷积和池化后空间尺寸变成25×10通道数32展平后8000维输入全连接。如果你把输入尺寸改成80×160这个8000要做对应修改具体算法是高度除以4、宽度除以4再乘以32。参数说明padding1保持尺寸不因卷积缩小MaxPool2d(2)把长宽各缩小一半。训练循环的代码import torch model CharCNN(num_classeslen(char_map)) optimizer torch.optim.Adam(model.parameters(), lr1e-3) loss_fn nn.CrossEntropyLoss() for epoch in range(10): for imgs, labels in train_loader: optimizer.zero_grad() out model(imgs) loss loss_fn(out, labels) loss.backward() optimizer.step() print(epoch, epoch, loss, loss.item())逻辑说明每个batch先清空梯度做前向传播计算交叉熵损失反向传播更新权重。参数说明lr1e-3是默认值如果loss在epoch 3之后还在上升改成1e-4batch_size取64或128看内存epochs取10通常足够字符分类不是难任务再多反而会过拟合。训练完成后保存权重torch.save(model.state_dict(), models/char_cnn.pth)注意保存的是state_dict而不是整个model。这样以后换环境时只要用同样的CharCNN类加载就不会因为Python版本或PyTorch版本不同而反序列化失败。加载代码model CharCNN(num_classeslen(char_map)) model.load_state_dict(torch.load(models/char_cnn.pth)) model.eval()3.3 车牌定位与字符分割OpenCV颜色加轮廓的经典组合定位是传统图像处理的重头戏。以蓝底白字车牌为例用HSV颜色过滤蓝色区域再找外接矩形。代码import cv2 def locate_plate(img): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, (100, 80, 80), (124, 255, 255)) mask cv2.GaussianBlur(mask, (5, 5), 0) contours, _ cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: x, y, w, h cv2.boundingRect(cnt) if 2.0 w / h 5.0 and w 100: return x, y, w, h return None逻辑说明先将BGR转成HSV因为H通道对颜色更稳定inRange生成蓝色区域的二值图GaussianBlur去掉小噪点findContours拿到轮廓后用最小外接矩形筛选。参数说明H范围100~124是蓝色做黄色车牌就改成15~34w100的阈值要按图片分辨率调1920宽时100合理640宽时降到40宽高比2.0~5.0覆盖了标准车牌和轻微透视如果车牌照变形严重上限放到6。拿到车牌区域后在灰度图上做字符分割。这里给出垂直投影分割的简化实现def segment_chars(gray_plate): gray cv2.resize(gray_plate, (400, 100)) _, binary cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) col_sum binary.sum(axis0) / 255 chars [] start None for i, s in enumerate(col_sum): if s 0 and start is None: start i elif s 0 and start is not None: chars.append((start, i)) start None if start is not None: chars.append((start, len(col_sum))) return chars逻辑说明先把车牌统一resize到400×100这样投影坐标是可比的二值化让字符变白背景变黑然后统计每一列白色像素数连续非零段就是一个候选字符的左右边界。参数说明threshold的127按灰度图自动调整如果车牌上有强光阴影改用cv2.adaptiveThreshold更稳resize会把长宽比破坏但对字符分类来说统一尺寸本来就是训练时的输入要求。这个分割方法对蓝牌效果好对绿牌容易出问题避坑章会讲。3.4 模型导出与封装把权重变成可复用的推理函数训练好模型后写一个统一的推理模块供界面和批量脚本调用。下面是单字符识别函数def recognize_char_img(char_img, model, id_map): gray cv2.cvtColor(char_img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, (40, 100)) x torch.from_numpy(gray).float().unsqueeze(0).unsqueeze(0) / 255.0 with torch.no_grad(): out model(x) prob torch.softmax(out, dim1) conf prob.max().item() pred_id out.argmax(1).item() return id_map[pred_id], conf逻辑说明把字符图转成模型需要的形状(batch, 1, 40, 100)除以255归一化用softmax拿到概率最大概率就是置信度。参数说明这里没有做减均值是因为训练时也没减保持输入分布一致即可torch.no_grad()必须在推理时加否则计算图会积聚大量内存。整牌识别就是把字符分割结果逐个喂给上面函数然后拼接def recognize_plate(chars, plate_gray, model, id_map): plate_gray cv2.resize(plate_gray, (400, 100)) # 和segment_chars内部保持一致 full_text confs [] for x0, x1 in chars: char_img plate_gray[:, x0:x1] ch, conf recognize_char_img(char_img, model, id_map) full_text ch confs.append(conf) return full_text, sum(confs) / len(confs)这里的plate_gray来自定位后的彩色图像的灰度版recognize_plate内部先resize成400×100保证和segment_chars返回的坐标对应。注意车牌第一个字符可能是汉字投影分割后汉字可能会被拆成两段需要在避坑章里进行合并处理。到这一步核心识别链路已经能跑起来接下来把结果落库并串上界面。4. 数据库与仿真界面把识别结果存下来才算一个“软件”如果没有数据库这个项目就是模型演示脚本不是“仿真软件”。数据库的作用是持久化识别记录方便事后查询、统计、导出这也是答辩老师常问的点。下面从选型讲到增删改查。4.1 SQLite还是MySQL毕设场景的选型依据毕设场景我强烈建议SQLite。理由有三个不用安装数据库服务学校机房电脑不一定有MySQL单个文件拷走就能备份Python标准库自带sqlite3零依赖。如果你论文里写“MySQL”也不是不行但需要在答辩前在机房电脑上装好并配好服务PPT上还得写“采用C/S架构”会多很多不必要的架构负担。数据库连接池这类东西在单机软件里完全用不到不要为了显得专业而引入。4.2 建表与写入识别记录车牌号、时间、置信度与图片路径表结构不用复杂一张记录表就够CREATE TABLE IF NOT EXISTS recognition ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate TEXT NOT NULL, confidence REAL, image_path TEXT, created_at TEXT DEFAULT (datetime(now, localtime)) );plate存识别出的完整车牌confidence存本次识别的平均置信度image_path存输入图片的相对路径created_at存本地时间。如果你想在论文里展示字符级的错误可以再加一列conf_json用JSON字符串保存每个字符对应的置信度。Python写入代码import sqlite3 conn sqlite3.connect(records.db) conn.execute(CREATE TABLE IF NOT EXISTS recognition ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate TEXT NOT NULL, confidence REAL, image_path TEXT, created_at TEXT DEFAULT (datetime(now, localtime)) )) def save_record(plate, conf, img_path): conn.execute( INSERT INTO recognition(plate, confidence, image_path) VALUES (?, ?, ?), (plate, conf, img_path) ) conn.commit()逻辑说明程序启动时连接一次records.db并建表save_record用参数化SQL插入避免拼接字符串带来的注入问题每次写入后commit否则重启程序数据丢失。参数说明这里用的conn是全局连接毕设这种单线程串行写入完全够用。如果后面界面识别放到子线程不要多个线程共用同一个连接写SQLite会报“database is locked”。4.3 查询与展示增删改查里的“查”是答辩必现答辩时要演示的不只是识别出来还要展示数据库里存了记录所以至少写一个查询方法def query_records(limit10): cur conn.execute( SELECT id, plate, confidence, image_path, created_at FROM recognition ORDER BY id DESC LIMIT ?, (limit,) ) return cur.fetchall()ORDER BY id DESC让最新记录排在最前LIMIT限制返回条数。论文里可以写“系统支持识别记录的持久化存储与查询”。如果你想加“删除历史记录”功能加一行DELETE即可如果想加“统计识别总次数”用SELECT COUNT(*)。这些操作本质上都是SQLite的标准增删改查但要注意写完之后要commit。4.4 PyQt界面怎么串起来按钮触发、图片展示、结果回写界面推荐PyQt5比Tkinter好看也比Tkinter好扩展。下面是最小可运行的窗口import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout class PlateApp(QWidget): def __init__(self): super().__init__() self.setWindowTitle(车牌识别仿真软件) self.label QLabel(请选择图片) self.btn QPushButton(选择图片并识别) self.result QLabel(识别结果) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn) layout.addWidget(self.result) self.setLayout(layout) self.btn.clicked.connect(self.on_click) def on_click(self): # 这里调用识别函数和保存记录 pass if __name__ __main__: app QApplication(sys.argv) win PlateApp() win.show() sys.exit(app.exec_())逻辑说明QVBoxLayout把三个控件垂直排列btn.clicked信号连接到on_click槽函数。现在on_click是空实现需要补上图像选择、识别和落库逻辑import cv2 from PyQt5.QtWidgets import QFileDialog from PyQt5.QtGui import QImage, QPixmap def on_click(self): fname, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.jpg *.png)) if not fname: return img cv2.imread(fname) region locate_plate(img) if region is None: self.result.setText(未定位到车牌) return x, y, w, h region plate_img img[y:yh, x:xw] gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) chars segment_chars(gray) text, conf recognize_plate(chars, gray, model, id_map) save_record(text, conf, fname) # 把图像显示到界面上BGR转RGB rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) qt_img QImage(rgb.data, rgb.shape[1], rgb.shape[0], rgb.strides[0], QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qt_img)) self.result.setText(f识别结果{text} 置信度{conf:.2f})逻辑说明用QFileDialog选择图片读写图像用OpenCV定位失败时要给出明确提示不能让界面卡死识别成功后写入数据库再把图像转换后显示。参数说明rgb.strides[0]是图像每一行的字节数PyQt5要求这个参数和图像宽度匹配否则图像会扭曲如果你不显示原图而只显示识别裁剪代码可以更短。关键点model和id_map需要在程序启动时加载一次不要在on_click里加载否则每次点击都要读几百KB的权重文件界面会卡。5. 车牌识别仿真软件避坑指南数据泄漏、绿牌误判、分割失败的5个现场这一章全是血泪经验每一条都来自实际调试现场。我按“现象、原因、解决”的顺序写你可以直接对照自己的问题排查。5.1 训练准确率99%实拍识别率却不到六成数据分布与泄漏现象模型在测试集上acc很高一换实拍图就乱。原因训练集和测试集来自同一批CCPD截图或者合成数据太干净背景光、噪声、模糊都没有更严重的是同一个车牌的多个裁剪图同时出现在训练和验证集里这叫数据泄漏。解决按车牌ID划分数据集而不是按图片随机划分合成数据里加入随机亮度、高斯噪声和轻微模糊增强时不要做水平翻转因为字符翻转后语义变了。数据增强代码用OpenCV的random起就够了重点是把增强严格放在训练集验证集保持原样。5.2 绿色新能源车牌总是被当成蓝牌HSV颜色先验写死的后果现象定位函数里用蓝色mask一过滤绿色区域直接空掉新能源车牌根本找不到。原因颜色先验只考虑了传统蓝牌。解决HSV的绿色范围H在35~77再做一次inRange把蓝色和绿色mask用bitwise_or合并再走同一个轮廓筛选逻辑。如果你的训练集里有蓝牌也有绿牌字符分割后CNN照样能识别因为字符形状差异不大所以定位是主要问题。我一般把黄牌也一起加进去三种颜色mask合成一张用面积和宽高比过滤这样应对场景广一些。5.3 汉字“鲁”经常被拆成“田”和“日”或者张冠李戴现象字符分割后出现多一个字符或少一个字符汉字笔画断裂导致投影出现空隙。原因垂直投影在汉字内部遇到空隙就当成新字符车牌上铆钉、边缘污渍也会产生伪轮廓。解决先对二值图做一次形态学闭运算把相近笔画连上再做垂直投影或者对投影段做合并——如果两个相邻段的间隔小于平均字符宽度的0.3倍就合并成一个段。我做的是投影段合并加固定数量修正先合并再检查段数如果超过7段把最窄的段并到相邻段如果少于7段在最大间隙处切开。这个规则不优雅但非常有效。5.4 界面点击识别时程序卡死或数据库报“database is locked”现象点击按钮后窗口未响应过一会儿SQLite提示locked。原因在UI线程里做了图像处理加模型推理阻塞了事件循环另一个原因是每次点击都新建数据库连接并同时在多个线程写入。解决把识别任务放到QThread线程池界面线程只负责按钮回调和显示结果线程里调用识别函数后将信号发回界面数据库连接只保留一个且所有写操作都在同一个线程执行。如果你不想引入信号槽最简单的做法是识别函数开销不大10ms内可以暂时不优化但数据库连接来一个用一个确实会锁。建议至少把数据库连接改为全局单例。5.5 演示视频很流畅现场跑却很慢CPU推理与模型缓存现象录演示视频时用了带GPU的电脑到答辩教室换成没有独立显卡的机器识别一帧要2秒。原因模型在GPU上训练但推理时没有做CPU优化且每次点击都加载了一次权重。解决模型初始化放到程序启动时不要放在识别回调里把输入图片统一resize到训练时的40×100避免动态尺寸带来的额外计算如果你愿意用torch.jit.script或ONNX导出模型CPU推理速度能提升20%到50%。我在答辩前会专门到机房没有显卡的电脑上跑一遍并把识别函数里打印帧耗时确保单张图控制在200ms以内否则现场演示就是灾难。6. 让仿真软件更像“作品”的三个细节置信度标注、批量识别与一键导出到了这一步识别链路和界面已经能跑通但距离“作品”还差三个细节。第一个是置信度可视化。识别界面里如果平均置信度低于0.6就在结果后面标“疑似错误”并用红色显示0.6到0.8标黄色大于0.8标绿色。这个设计成本很低但答辩时能直接看出你对模型不确定性是有意识的。代码就是在on_click里根据conf设置QLabel的样式表if conf 0.6: self.result.setStyleSheet(color: red;) elif conf 0.8: self.result.setStyleSheet(color: orange;) else: self.result.setStyleSheet(color: green;)第二个是批量识别。写一个脚本遍历测试文件夹里的所有图片把识别结果累积成列表最后写入Excel或CSV。这一功能直接服务于论文里的“实验对比表”。代码很简单import glob results [] for path in glob.glob(test_images/*.jpg): img cv2.imread(path) region locate_plate(img) if region is None: results.append((path, , 0.0)) continue x, y, w, h region plate_img img[y:yh, x:xw] gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) chars segment_chars(gray) text, conf recognize_plate(chars, gray, model, id_map) results.append((path, text, conf))第三个是一键导出报告。用pandas把results变成DataFrame一行代码导出Excelimport pandas as pd pd.DataFrame(results, columns[path, plate, conf]).to_excel(results.xlsx, indexFalse)有这三件套你的仿真软件就不再是“能跑的脚本”而是一个“能交差的软件”。我吃过一个教训答辩前一周才想起要录演示视频结果定位阈值没调好现场翻车。后来我把所有参数HSV范围、宽高比、投影合并阈值、置信度阈值集中放到config.py里标好注释调参时改一个文件就好再也不用到代码里乱翻。希望帮到你。本文还有配套的精品资源点击获取