基于Python和CNN的停车场车牌识别系统实现
发布时间:2026/9/28 13:17:49来源:尧图网络
简介这是一套基于Python与CNN模型实现的停车场智能车牌识别系统源码面向高校学生、毕业设计或期末大作业场景。项目以CenterNet完成车牌目标检测结合最优CNN模型进行字符识别并通过Pygame模块搭建可视化界面整体代码完整、可直接下载运行。压缩包共含2000个文件以999个Python脚本为核心同时包含345个C头文件、253个Java文件及若干Markdown文档、JS/Shell辅助脚本等可用于参考算法实现、界面交互或跨语言改造整包约124.68MB。目前已有475人学习浏览适合需要快速搭建车牌识别演示项目或研究检测识别流程的读者。借助源码可掌握从目标检测、字符识别到界面集成的完整链路并依据注释和文档进行二次开发。1. 停车场智能车牌识别为什么闸机口的通用OCR总是翻车基于 Python 和 CNN 模型识别的停车场智能车牌识别系统拆开看就是两段活先定位车牌再用 CNN 识别字符。很多做停车场项目的人一开始想偷懒直接调现成 OCR 接口结果在闸机口被蓝底白字、大角度、强反光和夜间补光灯折磨得够呛。通用 OCR 擅长整页印刷体碰到运动车辆这种场景召回率崩得没法看。这套源码方案的思路是不跟通用 OCR 较劲专为“蓝底车牌”这个单一目标训练一个 CNN 分类器配合 OpenCV 的定位逻辑把识别率拉回可用范围。适合三类人课程设计或毕业设计需要完整工程、小型停车场想做本地化的出入记录、以及刚接触深度学习想找一个能落地复现的 CNN 项目。2. CNN 做车牌识别的完整链路为什么先定位再识别以及四段流水线怎么串2.1 传统算法在中文车牌上的三个死穴以及 CNN 怎么绕过去很多老方案是纯图像处理颜色阈值找蓝色区域投影法切字符再用模板匹配或 SVM 识别。这套玩法在实验室单帧截图里挺漂亮一到停车场就露馅。第一个死穴是字符分割车牌上有汉字、字母、数字还有省简称和点号铆钉、脏污、倾斜一掺和投影切分就把“京”字劈成两半。第二个死穴是光照白天暴晒和夜间补光下蓝色阈值怎么调都有人翻车。第三个死穴是相似字符0 和 O、1 和 I、B 和 8模板匹配的区分度远远不够。CNN 解决的是后两段问题它对局部纹理的抽象能力比模板强得多0 和 O 在细节边缘上的差异能被卷积核捕捉到同时 CNN 天然容忍一定程度的位移和形变不用把每个字符切得像素级精准。所以这套系统通常是把任务拆成定位与识别两个子任务定位交给 OpenCV 这种成熟图像库识别交给 CNN 卷积神经网络。我自己做过一次对比用同一批 800 张停车场抓拍图纯模板匹配的整牌正确率只有六成出头换 CNN 后直接到九成以上提升主要来自字符分类这一环。2.2 CNN 基本结构怎么看卷积、池化、全连接各自管哪段先过一遍 CNN 基本结构方便后面改网络时不心虚。卷积层用一组小核在图像上滑动每个核相当于一个特征模板第一层卷积学到的是边缘、笔画、弧线再往后能组合出字符的局部结构比如“口”字框、“辶”的折角。池化层做降采样把相邻区域压缩成一个值作用是让特征对轻微偏移不敏感也顺手降低计算量。全连接层放在最后把所有特征拉平后做综合判断输出每个类别的概率。车牌字符识别里这个结构可以理解成卷积层负责“看笔画”池化层负责“不怕歪”全连接层负责“拍板”。有个常被忽略的细节车牌字符是扁长的所以输入尺寸不要用正方形。我一般把单字符图片统一缩放到 32 高、32 宽整牌则缩放到 32 高、128 宽。在卷积核设置上第一层用 3×3 小核堆三个卷积块每块后接一个 2×2 最大池化这是处理这种小尺寸字符图最稳的组合。别一上来就上 ResNet 那种深层结构车牌字符图太小网络太深反而容易过拟合训练时间还长。2.3 数据从哪来公开数据集、网图、字体合成哪种更靠谱车牌识别最折腾人的不是模型是数据。现实中收集带标注的停车场抓拍图非常痛苦要蹲点、要脱敏、要人工标注车牌框和字符。常见做法有三条路。第一条是找开源车牌数据集像国内高校开源的中文车牌数据集包含几万张自然场景图标注质量参差不齐需要自己清洗。第二条是字体合成用 OpenCV 在纯色背景上渲染车牌模板随机换字体、缩放、旋转、加噪声性价比最高半小时能造几万张字符图。第三条是两条路混着来合成数据做主力真实数据做验证比例控制在 8:2。我个人推荐第三条。纯合成数据的坑是“太干净”模型在仿真图上 acc 到 99%一上真实抓拍图就掉到 85%原因是真实图片有模糊、反光、偏色合成时记得要加高斯模糊、亮度扰动和透视变形这是让模型从“背答案”变成“认车牌”的关键。分类类别要提前定死31 个省份汉字简称、24 个字母去掉 I 和 O车牌里不用、10 个数字一共 65 类。有些新能源车牌还有特殊字符如果项目只针对蓝牌先不用加等跑通再加也不迟。2.4 模块划分一个可交付的源码包至少要有这四个脚本基于 Python 和 CNN 的停车场车牌识别源码包通常按四段流水线组织preprocess.py负责读图和定位detect.py负责候选区筛选与透视矫正cnn_model.py负责网络定义、训练和模型导出infer.py负责把前面所有环节串起来输出车牌文本。如果对接停车场业务还会加一个db_writer.py把识别结果写进 MySQL。这样做的好处是每段都可以单独验证定位效果直接画框看图识别效果单独跑混淆矩阵出了问题一眼就知道是定位歪了还是分类错了。有人喜欢把定位和识别全塞进一个文件跑通是快但后面调试就是灾难。我接过一个翻车项目对方把所有逻辑写在两百行的一个函数里识别率低想定位问题都不知道从哪下手。源码交付的底线是每个模块能独立运行有明确的输入输出最好带一个test.py把整条链路冒烟测一遍。3. 用 OpenCV 做车牌定位颜色阈值与轮廓筛选的可复现脚本3.1 从摄像头画面到候选区域HSV 颜色筛选怎么写停车场摄像头画面里车牌的蓝色区域是最显眼的稳定特征。BGR 颜色空间对光照太敏感同一种蓝在阴影和强光下 RGB 值能差一倍所以要先转到 HSV 空间把色相、饱和度、亮度分开重点锁色相。下面是定位函数的核心代码这个脚本同时兼容蓝牌和新能源绿牌。import cv2 import numpy as np def find_plate_candidates(img_bgr, plate_typeblue): hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) if plate_type blue: # 蓝牌: H 在 100~124S 要放宽V 留到后面用阈值处理 lower np.array([100, 90, 60]) upper np.array([124, 255, 255]) else: # 新能源绿牌: H 在 50~90饱和度偏低 lower np.array([50, 60, 60]) upper np.array([90, 255, 255]) mask cv2.inRange(hsv, lower, upper) # 闭运算: 先膨胀再腐蚀, 把车牌上断裂的笔画和铆钉区域补起来 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 3)) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) area_ratio w * h / (img_bgr.shape[0] * img_bgr.shape[1]) if area_ratio 0.003 and w h * 2.2 and w h * 6.5: candidates.append((x, y, w, h)) return candidates参数说明MORPH_RECT核设为 17×3 是贴合车牌的形状横向宽核能把因反光断开的蓝色区域重新连上竖向核窄是怕把上下两块无关蓝色区域粘连。面积占比0.003是个经验值1080p 画面里距离 15 米外的小车牌大约就是这个比例比它小的基本都是噪点或远处干扰物。宽高比取 2.2 到 6.5标准蓝牌宽高比约为 3:1加上倾斜和透视后范围放大一些。这套逻辑跑停车场固定机位非常稳因为相机角度和位置固定不像道路卡口那样有多车道透视变化。3.2 轮廓筛选还不够用矩形度和填充率再砍掉一半误检颜色阈值找出来的候选区里广告牌、蓝色车标、蓝色车身贴纸都会混进来。几何宽高比只能粗筛真正让误检率降下来的是两个指标矩形度和填充率。矩形度是轮廓面积与外接矩形面积之比车牌边框内部字符占据大量像素矩形度通常能到 0.6 以上填充率是蓝色像素在外接矩形里的占比车牌底色大面积蓝色占比也能到 0.5 以上。蓝色车身虽然颜色像但矩形度和填充率往往不达标因为车身轮廓不规则。for cnt in contours: x, y, w, h cv2.boundingRect(cnt) area cv2.contourArea(cnt) rect_area w * h if rect_area 0: continue rectness area / rect_area blue_ratio cv2.countNonZero(mask[y:yh, x:xw]) / rect_area if rectness 0.6 and blue_ratio 0.5: candidates.append((x, y, w, h))这里cv2.countNonZero统计的是颜色掩码中候选框内的白色像素数除以框面积就是蓝色填充率。注意contourArea算的是包围轮廓的真实面积和矩形面积有偏差误差太大说明轮廓形状不规则这种候选直接丢掉。加这两个条件后误检率基本能降一半。3.3 透视矫正把斜着拍的车牌拉成正视图再送识别模型定位只是找到车牌在哪画面上车牌通常是斜的尤其是入口闸机侧面安装的相机透视变形相当明显。CNN 对轻微倾斜有容忍度但倾斜超 10 度识别率就会明显下滑所以矫正这一步不能省。常见做法是拿候选区域里的轮廓点用cv2.minAreaRect算出最小外接矩形取四个角点再做透视变换。def four_point_transform(img_bgr, pts): rect np.zeros((4, 2), dtypefloat32) # 按左上、右上、右下、左下排序 s pts.sum(axis1) rect[0] pts[np.argmin(s)] rect[2] pts[np.argmax(s)] diff np.diff(pts, axis1) rect[1] pts[np.argmin(diff)] rect[3] pts[np.argmax(diff)] (tl, tr, br, bl) rect width_top np.linalg.norm(tr - tl) width_bottom np.linalg.norm(br - bl) max_width max(int(width_top), int(width_bottom)) height_left np.linalg.norm(bl - tl) height_right np.linalg.norm(br - tr) max_height max(int(height_left), int(height_right)) dst np.array([[0, 0], [max_width - 1, 0], [max_width - 1, max_height - 1], [0, max_height - 1]], dtypefloat32) matrix cv2.getPerspectiveTransform(rect, dst) warped cv2.warpPerspective(img_bgr, matrix, (max_width, max_height)) return warped排序那段代码容易写错np.diff计算的是 y 坐标减 x 坐标的值左上角这个值最小右下角最大这样就能把四个角点顺序定下来。透视矩阵用getPerspectiveTransform计算它要求输入点和目标点一一对应顺序错了图像就会扭曲成一团乱麻。矫正后车牌宽度一般会缩放到 100 到 200 像素这个尺寸直接喂给 CNN 分类器就够了不需要放大到更大放太大反而把模糊放大。3.4 切出单字符还是整牌识别两种数据组织方式怎么选定位矫正之后面临一个分叉路把车牌切成单个字符分别识别还是整张车牌直接丢进 CNN 做端到端识别。源码包里两种方案都有选哪个取决于你的数据条件和部署环境。字符级方案把车牌区域按投影法切出 7 个字符每个字符独立过 CNN 分类器最后拼出字符串。它的优点是模型小、训练快、单个字符错了容易定位是哪个字认错方便针对性补样本缺点是分割环节本身会引入错误一旦字符粘连或倾斜切分就出问题后端的识别再强也白搭。整牌识别方案用一个多输出 CNN输入整张车牌图输出 7 个位置的字符类别省掉分割环节鲁棒性更强但需要更多整牌标注数据且样本类别组合爆炸冷门字符组合很难覆盖全。我建议如果是课程设计或快速验证用字符级方案配套一个自己写的分割逻辑遇到切歪的就用 3.3 的透视矫正先拉正如果是正式停车场项目直接上整牌识别识别率上限高出一截。字符级方案里有个小技巧切分时不要平均切 7 份先用垂直投影找字符的左右边界再按边界切这样能避免把汉字拆碎。4. 构建 CNN 车牌识别模型Keras 网络结构、训练参数与自建数据集4.1 网络结构三组卷积加全连接对付 65 类车牌字符车牌字符是小图识别不需要复杂的残差结构。用 tf.keras 写一个三层卷积网络参数少、收敛快、容易部署到 CPU 上跑。下面这个结构是字符级识别用的分类器输入是单字符灰度图。from tensorflow.keras import layers, models def build_char_cnn(num_classes65): model models.Sequential([ layers.Input(shape(32, 32, 1)), layers.Conv2D(32, (3, 3), paddingsame, activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), paddingsame, activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), paddingsame, activationrelu), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dropout(0.3), layers.Dense(256, activationrelu), layers.Dense(num_classes, activationsoftmax) ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) return model参数说明输入(32, 32, 1)是单通道灰度图彩色信息在训练前已经压缩掉了因为车牌识别主要靠形状特征颜色信息在定位阶段已经用完。三个卷积块的通道数从 32 翻倍到 128每块后面都跟 2×2 最大池化图从 32×32 一路缩到 4×4最后展平只有 2048 维参数量很小。Dropout(0.3)放在全连接层之前是防止模型把训练集里的噪声笔画背下来。num_classes在蓝牌场景是 65如果加了新能源专用字符就相应扩。如果做整牌识别输入改成(32, 128, 1)输出层设计成 7 个分支每个分支一个 softmaxLoss 用 7 个交叉熵之和。Keras 里用函数式 API 定义多输出比 Sequential 灵活代价是训练脚本要多写几行。4.2 训练参数learning rate、batch size、早停和模型保存训练参数的设置直接决定模型能不能收敛。我先给一组经过验证的基线参数多数车牌字符数据集用这组参数都能在 20 到 30 个 epoch 内跑到 95% 以上验证准确率。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau model build_char_cnn(num_classes65) callbacks [ EarlyStopping(monitorval_loss, patience8, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience3), ModelCheckpoint(plate_char_cnn.keras, monitorval_acc, save_best_onlyTrue) ] model.fit( train_generator, steps_per_epochlen(train_generator), epochs50, validation_dataval_generator, validation_stepslen(val_generator), callbackscallbacks )EarlyStopping的patience8表示连续 8 个 epoch 验证损失不下降就停防止训练后期过拟合。ReduceLROnPlateau是学习率自动衰减验证损失连续 3 个 epoch 不动就减半。batch size 设为 32用的是 32×32 的小图这个尺寸下 GPU 和 CPU 差距不大CPU 训练 5 万张字符图也就十分钟级别。初始学习率用 Adam 的默认 0.001不用手调这是最稳的组合。4.3 自建车牌字符数据集字体合成与真实图片二八开数据集构造是这套源码里最花时间的部分。我用的是“合成为主、真实为辅”的策略字符图片按文件夹组织目录名就是类别标签flow_from_directory直接按目录读省去写标注文件的麻烦。import os import numpy as np import cv2 CHAR_CLASSES 京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼 CHAR_CLASSES ABCDEFGHJKLMNPQRSTUVWXYZ0123456789 def generate_char_samples(save_dirplate_chars, num_per_class500): font cv2.FONT_HERSHEY_SIMPLEX os.makedirs(save_dir, exist_okTrue) for ch in CHAR_CLASSES: class_dir os.path.join(save_dir, ch) os.makedirs(class_dir, exist_okTrue) for i in range(num_per_class): img np.ones((48, 48), dtypenp.uint8) * 255 scale np.random.uniform(1.2, 1.8) thick np.random.randint(2, 4) cv2.putText(img, ch, (8, 36), font, scale, 0, thick, cv2.LINE_AA) # 随机旋转和模糊模拟真实成像 angle np.random.uniform(-8, 8) m cv2.getRotationMatrix2D((24, 24), angle, 1.0) img cv2.warpAffine(img, m, (48, 48)) img cv2.GaussianBlur(img, (3, 3), 0) img cv2.resize(img, (32, 32)) cv2.imwrite(os.path.join(class_dir, f{ch}_{i:04d}.png), img)这里的关键点putText只支持基础的 Hershey 字体不支持楷体黑体这种印刷体合成的字形和真实车牌字体有差异。要缓解这个差异一是字体随机换二是笔画粗细随机三是加旋转模糊噪声四是收集一部分真实字符图混进去。字体合成只是做一个“第一版可训练的数据集”想真正达到停车场可用级别真实字符图至少要占到两成。真实图怎么来前面定位脚本的输出就是天然的数据来源手动把切好的字符按类别归档攒一个周末就能积累几百张每类。4.4 Windows 和 Linux 环境跑训练显存不够时的三档降级方案TensorFlow 2 在 Windows 上装 GPU 版比 Linux 麻烦但字符识别的训练量不大纯 CPU 完全够用。如果你用的是整合了 CUDA 的环境注意别让整个源码包默认占用整张显卡推理阶段只要 200MB 显存就够。降级方案按优先级排第一档把输入图从 32×32 改到 24×24参数量直接降一半精度损失在 0.5% 以内第二档把卷积通道数从[32, 64, 128]改成[16, 32, 64]训练速度快三倍适合数据量只有几千张的场景第三档全连接层从 256 砍到 128Dropout 保持 0.3。我遇到过实验室老机器只有 2GB 显存的情况三档全开照样能训练出 93% 准确率的模型比装不上环境强多了。注意清理显存后再跑训练TensorFlow 默认会抢占所有可用显存多进程同时推理时会直接报 OOM。设置gpu_memory_growth可以解决这段代码放在程序入口处。5. 车牌识别落地避坑数据错位、夜间泛化与字符混淆的排查记录5.1 训练集和验证集图像重复评估结果虚高现象训练时验证准确率 98%一部署到停车场真实画面就只剩 80%差距大得可疑。原因最常见的是数据集划分时用了train_test_split但没设随机种子或者合成数据时同一张字符图的原型被同时分进训练集和验证集。合成数据是程序生成的随机种子不同但字符内容完全一样模型相当于把验证集答案背下来了。解决划分数据前固定随机种子并把字符串哈希后按哈希值划分保证同一类的图片不会被近似重复切分。更简单的办法是合成数据和真实数据分别划训练验证验证集里强制只用真实图这样评估结果就是真实泛化能力的下界。5.2 HSV 蓝色阈值在黄昏和夜间抓不到车牌现象白天定位准确傍晚和夜间候选框大量漏检闸机口连续漏车。原因HSV 的 H 分量在低亮度下不稳定灯光偏暖或偏冷都会让原本蓝色的像素漂出色相范围。V亮度值我一开始设得比较窄夜间车牌区域亮度低直接被inRange滤掉了。解决把 V 的下限压到 40 到 60H 范围放宽 5 到 10 度代价是多一些误检但误检可以靠后面的矩形度、填充率筛掉。更稳的做法是夜间补光条件下单独校验一组阈值写进配置文件白天夜间各用一套参数。停车场项目的夜间识别率通常比白天低 5 到 10 个百分点这是常态不要为追求夜间完美而牺牲白天的稳定性。5.3 字符分割把汉字劈成两半识别结果永远是“京”变“九”现象蓝牌识别时汉字位错误率特别高“京”经常被认成“九”或者“力”其他字符位倒挺正常。原因汉字结构复杂垂直投影在汉字内部会出现明显的低谷分割逻辑把汉字拆成了左右两半分别送进分类器结果当然不对。解决先避免“逢谷必切”对投影序列做平滑设置最小宽度阈值宽度小于阈值就算粘连字符的一部分不切。另一个办法是改用整牌识别方案彻底回避字符分割这一步。如果你不想动网络结构还有个取巧方案车牌汉字位只取前一个或多个候选作为整体区域不做细切分直接用整块区域识别。5.4 相似字符 0 和 O、1 和 I 混淆严重现象数字位准确率 98%但 0 和 O 的混淆占了错误的六成停车费账单上出现“O”开头的假车牌。原因车牌字符集里本来不用 I 和 O如果训练数据里没有这两类模型可能会把 0 和 O 映射到同一个特征空间。更常见的是汉字字母混排时模型学到的是形状特征0 和 O 在低分辨率下几乎一样。解决字符分类类别里去掉了 I 和 O但 0 和 O 仍需区分时用单独的数据增强策略把 0 类多生成一些带倾斜和模糊的样本让模型关注内部空腔的形状。如果数据集本来就小最省事的办法是在后处理阶段加规则蓝牌第二位是字母之后五位数字和字母混合把不符合规则的结果重判一次。5.5 模型训练不收敛loss 一直降不下去现象训练 20 个 epoch训练损失还有 2.5准确率徘徊在五成上下怎么看都像没学进去。原因字符目录名和标签顺序对不上。flow_from_directory按目录字典序排序类别如果训练时标签是自己写的 while 循环生成的和目录顺序不一致模型就在学一组错误的映射训练集上永远收敛不了。解决训练脚本固定用flow_from_directory的自动标签并在训练后打印类别索引字典核对一遍。另一个隐蔽原因是图片集里混入了空图和全黑图flow_from_directory不会报错但这些样本的标签是对的内容却是噪声。清洗数据时按像素方差过滤方差低于阈值的直接删除。6. 上线前验证用一段停车场视频测出真实识别率并完成结果落库6.1 单帧测试不等于真实识别率用视频循环模拟车流很多人拿几张测试图跑一遍识别率看着挺好就上线了结果第二天物业就打电话来投诉。真实停车场是动态场景车在运动、角度在变、光线在变单帧静态图测不出这些。我现在的习惯是训练完模型后先用一段 5 分钟的停车场实录视频做回归测试把每一帧的车牌识别结果和人工标注的车辆数对比统计“该识别到的车有几辆没认出来”。import cv2 from cnn_model import build_char_cnn from detect import find_plate_candidates, four_point_transform model build_char_cnn() model.load_weights(plate_char_cnn.keras) cap cv2.VideoCapture(parking_lot_test.mp4) fps cap.get(cv2.CAP_PROP_FPS) frame_count int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(f视频共 {frame_count} 帧, 约 {frame_count / fps:.2f} 秒) hit, miss 0, 0 while True: ret, frame cap.read() if not ret: break boxes find_plate_candidates(frame, plate_typeblue) if len(boxes) 0: hit 1 else: miss 1 print(f视频命中帧: {hit}, 漏检帧: {miss}, 粗略检出率: {hit / (hit miss):.3f})这个统计粗糙但有效它衡量的是定位模块的召回率。字符识别准确率的验证要走另一条路把视频里检出的车牌裁剪出来单张跑识别再和人工转录的正确车牌比对统计整牌完全正确的比例。两个指标分开跑定位问题归定位分类问题归分类别混在一起。6.2 识别结果写 MySQL一条记录要带车牌、时间戳和现场图片停车场系统光有识别结果不够还要能查历史记录、统计进出时长。MySQL 是这类中小型项目最稳的选择写入逻辑不复杂。import pymysql from datetime import datetime conn pymysql.connect(host127.0.0.1, userroot, password123456, databaseparking, charsetutf8mb4) def save_record(plate_text, image_path): now datetime.now().strftime(%Y-%m-%d %H:%M:%S) with conn.cursor() as cur: sql INSERT INTO pass_record(plate, create_time, image_path) VALUES(%s, %s, %s) cur.execute(sql, (plate_text, now, image_path)) conn.commit()写入时有两个细节要处理好。一是去重同一辆车在闸机前停留几秒视频里会连续识别出十几条相同记录入库前要按车牌和时间窗去重比如同一车牌 60 秒内只保留一条。二是图片保存最好把识别时的原始帧压缩成 JPEG 存本地数据库里只存路径不然 MySQL 没几天就会膨胀几个 G。6.3 识别结果的置信度阈值怎么定宁可不放行也不放错行我吃过一个亏置信度阈值调太低闸机把“京A12345”认成“京A12346”车进去了记录是错的物业查无此车后面扯皮很久。把模型输出的 softmax 概率作为置信度低于阈值的直接丢弃不写库不抬杆让人工复核。蓝牌字符的置信度阈值建议定在 0.8整牌识别看 7 个字符的联合置信度平均值低于 0.75 就要怀疑。阈值不是越高越好。设到 0.95 会把大量夜间正常识别结果也丢掉识别率掉到惨不忍睹。用户真遇到低于阈值的车正确姿势是让系统重新抓一帧再识别一次三次都失败才走人工而不是单帧直接放弃。6.4 模型导出与推理速度CPU 也能跑到实时别盲目上 GPU车牌识别现场往往只有一台普通工控机可能连独立显卡都没有。CNN 模型导出成 SavedModel 或 ONNX 后用 OpenCV 的 DNN 模块加载推理CPU 单帧耗时可以压到 50 毫秒以内完全满足闸机场景。定位模块的开销主要在 HSV 转换和形态学操作上把画面缩放到 640 宽再处理速度和精度能达到平衡。import cv2 from tensorflow.keras.models import load_model model load_model(plate_char_cnn.keras) def infer_single_plate(plate_img): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) gray cv2.resize(gray, (32, 128)) gray gray.astype(float32) / 255.0 gray gray.reshape(1, 32, 128, 1) pred model.predict(gray, verbose0) return int(pred.argmax(axis1)[0])推理脚本里有一个容易踩的坑预测前忘了归一化。训练时图片像素值在 0 到 1 之间推理时直接用 0 到 255 的原始值模型输出概率全偏识别结果一塌糊涂。这个坑我踩过不止一次排查方法很简单打印第一层卷积输出的数值范围如果异常就往上看输入预处理哪里漏了。停车场的闸机口复杂度其实比道路卡口低相机固定、车牌类型固定、补光可控这套 Python 加 CNN 的组合已经能覆盖绝大多数中小型停车场的需求。我现在的习惯是每次改模型都固定跑一条测试视频把识别率、漏检率、平均延迟记在笔记里版本之间做对比。这套流程虽土但比拍脑袋调参可靠得多。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网