
简介基于Python开发的车牌识别参考项目源码包面向智能交通、车辆监控及自动化管理等应用领域的开发者和研究者是一套从图像采集、车牌定位、字符分割到结果输出的完整实现框架。项目融合PyQt5图形界面与OpenCV图像处理能力支持Python 3.6及3.7环境并兼容OpenCV 3.4.3与4.2.0两个版本方便在不同依赖配置下直接运行和二次调试。整个压缩包共两千个文件其中包含一千九百八十七张车辆及车牌样本图片、七个核心脚本、一个用户界面文件、三个参数配置和两份说明文档整体大小约二十五点五三兆目录结构清晰便于按模块查阅和复用。社区已有三百一十人浏览学习适合作为入门学习和二次开发的基础。通过阅读源码可以深入理解车牌识别中图像预处理、边缘检测、颜色特征提取、字符分割和分类识别等关键技术同时项目自带界面与配置文件支持可视化调试与参数调整能有效降低学习门槛帮助开发者快速构建实际应用或开展算法实验。1. 车牌识别参考项目这份 Python 源码能让你少走三个月弯路做车牌识别网上一搜一半是深度学习炼丹教程另一半是只给效果图不给源码的演示视频。真正能下载下来、双击跑起来、每行代码都看得懂的 Python 车牌识别参考项目不多。这份源码走 OpenCV 传统视觉路线用 PyQt5 搭图形界面把预处理、车牌定位、字符分割、字符识别整条链路都摆在明面上测试环境覆盖 Python 3.6 opencv-python 3.4.3 和 Python 3.7 opencv-python 4.2.0 两套组合。适合三类人课程设计要交源码和界面的学生第一次接触图像处理想找案例入门的初学者需要在本地 CPU 环境快速验证识别效果的工程师。它不依赖 GPU、不涉及深度学习框架调试时能看到每个环节输出的中间小图比把识别过程当黑匣子的模型方案友好得多。下文把链路、环境、参数和坑逐个拆开照着跑一遍比看十篇教程都顶用。2. 识别链路拆解从 debug_char_auxRoi 到 gt_ 标注源码把每个中间环节都留了证据压缩包里有 debug_char_auxRoi_427.jpg、319_sun_0_15.jpg、gt_15_6.jpg 这类文件命名乍看很乱其实是开发调试时留下的中间产物auxRoi 是字符级辅助 ROI 快照gt 是 ground truth 的人工标注结果sun 代表阳光光照场景。这些文件不是冗余垃圾而是排查识别问题的地图——哪一步出错翻对应环节的图就能定位。文件命名含义调试用途debug_char_auxRoi_序号.jpg字符级辅助 ROI 快照判断字符分割是否完整、有无粘连gt_车牌_序号.jpgground truth 正确标注与识别结果比对统计准确率序号_sun_序号.jpg阳光场景测试样本验证光照变化下的鲁棒性2.1 预处理与车牌定位HSV 颜色筛选加形态学闭运算国内车牌以蓝底白字为主黄底黑字和绿底黑字新能源次之。传统视觉定位最稳的思路是先按颜色把候选区域筛出来而不是一上来就做边缘检测。边缘检测对背景里的树木、建筑轮廓太敏感颜色筛选的鲁棒性明显更好。import cv2 import numpy as np # BGR 转 HSV按蓝牌范围生成掩码具体范围要按实际样本标定 hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, np.array([100, 80, 80]), np.array([124, 255, 255])) # 闭运算先膨胀后腐蚀把字符间的空隙填平 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)这两个参数是定位环节的核心。颜色范围决定能不能筛出车牌H 通道对色相敏感S 和 V 受光照影响最大阳光直射场景下像素值会被整体抬升lower 的 V 值不宜设太高。核大小决定筛出之后能不能连成一块车牌是宽扁矩形核宽大于核高专门填平字符之间的竖缝核选成正方形容易把上下边缘的干扰区域也融进来。轮廓筛选接着做# OpenCV 4.x 返回两个值3.x 早期版本可能返回三个解包方式要统一 contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for c in contours: x, y, w, h cv2.boundingRect(c) ratio w / h if h ! 0 else 0 # 标准车牌长宽比约 3.14:1放宽到 2.5~5.5 过滤干扰 if 2.5 ratio 5.5 and w 60 and h 15: plate_candidate frame[y:y h, x:x w]长宽比过滤很有效但要注意相机安装角度偏斜时车牌在画面里是斜的boundingRect 得到的 w/h 会被拉伸可能超出 5.5 的上限。斜车牌后面要做透视矫正转正否则字符分割阶段会整体错位。常见做法是用最小外接矩形的四个角点做单应变换把车牌映射成正视的矩形再做后续处理。2.2 字符分割垂直投影切割debug_char_auxRoi 就是这一步的证据定位到车牌后灰度化、二值化、去掉上下边框和铆钉再按列做垂直投影把字符一个个切开# 车牌区转灰度 自适应二值化字符为白、背景为黑 gray cv2.cvtColor(plate_candidate, cv2.COLOR_BGR2GRAY) binary cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 5) # 按列统计白色像素数连续非零区间视为字符边界 col_sum np.sum(binary, axis0) char_slices [] in_char False for i, v in enumerate(col_sum): if v 0 and not in_char: start i in_char True elif v 0 and in_char: end i in_char False if end - start 10: # 字符最小宽度过滤噪声列 char_slices.append((start, end))垂直投影的原理很朴素字符笔画竖着分布有字符的列白色像素多字符间隙的列接近零。连续非零区间就是字符的左右边界。adaptiveThreshold 比固定阈值抗光照变化blockSize 15、C 值 5 是常见起点字符宽度不同要跟着调。最容易翻车的是阈值不会自适应阳光直射下字符反光固定阈值会把笔画断成碎块背光时又容易把背景一起判成白色。debug_char_auxRoi_427.jpg 就是每个字符被切成独立 ROI 后保存的快照427 是切片序号。打开看一张能直接判断这个字切完整没有、有没有粘到邻字比盯着识别结果猜原因快得多。2.3 字符识别模板匹配为主gt_ 图是验证精度的标尺字符切好后进入识别环节。这份项目走模板匹配路线切出来的字符和预置模板库逐张比对取匹配度最高的字符作为结果# 模板字典键是字符值是统一尺寸的模板图 best_char, best_score None, -1.0 for char_name, tpl in template_dict.items(): resized cv2.resize(char_img, (tpl.shape[1], tpl.shape[0])) res cv2.matchTemplate(resized, tpl, cv2.TM_CCOEFF_NORMED) score res.max() if score best_score: best_score, best_char score, char_name # 低于阈值返回未知字符不硬给结果 result.append(best_char if best_score 0.7 else ?)TM_CCOEFF_NORMED 对亮度做了归一化同一字符在不同光照下的匹配结果更稳定。0.7 这个阈值要按模板库质量调模板全、字符清晰可以抬到 0.8 减少误判切割有残缺或模板偏少要降下来避免把正确字符拒之门外。gt_15_6.jpg、gt_242_5.jpg 里的 gt 是 ground truth即人工确认过的正确车牌。参考项目跑完识别后拿结果和 gt 逐一比对一致才记为通过。这套自测方法朴素但有效一组测试图对应一张标注识别一致才算通过正好拿来做精度统计。2.4 选型逻辑为什么走 OpenCV 传统链路不用深度学习项目选了颜色定位加投影分割加模板匹配的传统路线不是偷懒是参考项目的定位决定的。传统链路的好处很实在不依赖 GPUCPU 单帧处理几十到一百毫秒普通笔记本跑得动第三方依赖只有 opencv-python 和 PyQt5环境干净每个环节都能输出中间图写课程设计报告、做答辩演示都很顺。深度学习方案在复杂场景上限更高但要训练数据、要标注、要 GPU 部署对参考项目来说负担过重初学者也不好看懂。先把传统链路吃透再接触深度方案是比较合理的进阶顺序。3. 环境搭建与复现Python 3.6/3.7 双版本下的依赖安装与启动源码给的测试平台有两组Python 3.6 PyQt5 5.11.3 opencv-python 3.4.3以及 Python 3.7 PyQt5 5.11.3 opencv-python 4.2.0。实际复现时我用 3.7 那组更省事3.6 要处理老版本包的兼容告警。下面按 3.7 走完整流程。组合PythonPyQt5opencv-python说明方案A3.65.11.33.4.3老版本组合注意返回值差异方案B3.75.11.34.2.0推荐新 API 更稳定3.1 用虚拟环境隔离依赖避免污染系统 Python车牌识别项目最忌讳把包直接装进系统 Python。opencv-python 版本间 API 有差异PyQt5 又容易和系统 Qt 库冲突强烈建议建一个独立虚拟环境# 创建 Python 3.7 虚拟环境 python -m venv venv_lpr # Windows 激活 venv_lpr\Scripts\activate # Linux/macOS 激活 source venv_lpr/bin/activate pip install opencv-python4.2.0.34 PyQt55.11.3 numpy用 venv 而不是 conda是因为这份项目依赖简单不需要 conda 的包管理复杂度。opencv-python 4.2.0 的完整小版本是 4.2.0.34指定完整版本号能避免 pip 解析出意外版本。numpy 是 opencv 的隐式依赖但显式写上更保险防止安装顺序导致版本被覆盖。提示pip 下载慢或超时可以加-i https://pypi.tuna.tsinghua.edu.cn/simple换用国内镜像源速度差别非常大。机器上同时存在多个 Python 时先跑 python --version 确认当前解释器确实是 3.7 或 3.6再创建虚拟环境。否则建出来的环境可能不是你想要的版本后面装包和运行会产生连锁问题。3.2 目录结构与启动入口解压后是 License_plate_recognition-master 这种主目录里面是项目本体。常见结构是主程序文件、识别核心模块、模板库目录、测试图片目录。启动前先确认模板库路径存在不少参考项目用相对路径加载模板从别的目录启动会直接报找不到模板。# 在项目根目录执行不要跳进子目录 python main.py启动后出现 PyQt5 窗口界面上一般有选择图片、开始识别这类按钮。选一张测试图比如样本里的 319_sun_0_15.jpg点识别结果显示在界面下方同时可能在当前目录输出中间过程图。如果启动报 No module named cv2先跑 pip list 看 opencv-python 是否真的装进了当前虚拟环境——环境串台是这类项目最常见的启动失败原因全局环境和 venv 混用pip 装到全局python 却指向 venv。3.3 OpenCV 3.4.3 和 4.2.0 的两处 API 差异两代 OpenCV 都验证过但切换时有两个差异要特别注意。第一个是 findContours 的返回值4.x 统一返回 (contours, hierarchy) 两个值3.x 某些版本返回 (image, contours, hierarchy) 三个值。同一份代码直接解包成三个变量在 4.2.0 下会直接报错。# 兼容两代 API 的统一解包方式 result cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours result[0] if len(result) 2 else result[1]第二个差异在 cv2.resize 的默认插值算法字符统一尺寸环节偶发出现表现为模板匹配分数整体偏低。排查时把 interpolation 参数显式指定为 cv2.INTER_AREA 或 cv2.INTER_LINEAR就能消除版本默认值不同带来的影响。这两个问题在项目源码里很可能已经处理过但你自己扩展代码时仍会踩到。3.4 用样本图做冒烟测试并建立基线环境配好、程序能启动后用自带测试图跑一次冒烟测试。319_sun_0_15.jpg 这类 sun 样本是特意选的阳光直射场景能顺利走完定位、分割、识别说明环境基本正常识别失败也别急着改参数先打开中间过程图看断在哪一步。冒烟测试的另一个作用是建立基线记录当前参数下这批测试图的识别正确率后面调参才有对比标准。这一步很多人跳过结果调了半天也不知道是变好还是变坏。4. 参数调优实战车牌定位、字符分割与识别置信度的关键参数参考项目能跑通只是第一步实际到手的图片往往更复杂——角度偏、光线乱、背景花。这一章把三个环节里最影响结果的参数列出来按动哪个、怎么动、动了看什么来写。参数所属环节典型值调参方向HSV 颜色范围定位blue: (100,80,80)~(124,255,255)光照偏移时放宽 S/V形态学核定位(17,5)按车牌宽高比调整自适应阈值 blockSize分割15字符笔画粗则增大最小字符宽度分割10px按实际字宽取中值模板匹配阈值识别0.7按分数分布取分隔值4.1 颜色阈值蓝牌黄牌绿牌分开标定HSV 颜色范围是定位环节的第一道闸门。项目默认配置通常只覆盖蓝牌要扩展黄牌或绿牌得加分支# 蓝牌与黄牌的 HSV 区间示例数值需用取色工具标定 color_ranges { blue: ((100, 80, 80), (124, 255, 255)), yellow: ((15, 120, 120), (35, 255, 255)), } for name, (lower, upper) in color_ranges.items(): mask cv2.inRange(hsv, np.array(lower), np.array(upper))调参别信网上抄来的固定值直接对测试图取色打印车牌区域像素的 H、S、V 值再取能包住所有采样点的最小范围。同一个颜色的 H 值在不同光照下会偏移阳光场景和阴天场景最好各取一组运行时按场景切换。注意黄牌的 H 区间和蓝牌离得远可以先分别做闭运算再把结果合并避免跨颜色粘连导致定位区域变大。4.2 形态学核与轮廓过滤宁缺毋滥闭运算核大小直接决定车牌区域能否连成整体。核太窄、闭运算强度不够字符缝隙填不平车牌被切碎成多块核太宽会把进气格栅、车灯融进来候选轮廓数量暴增。# 调试顺序核宽从 11 到 23 步进 2观察候选轮廓数量变化 for kw in range(11, 24, 2): kernel cv2.getStructuringElement(cv2.MORPH_RECT, (kw, 5)) closed cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) print(kw, len(contours))实用技巧不只看轮廓总数要看通过长宽比过滤后的候选数量。核宽太小候选多但全是碎片核宽太大候选少但可能融进干扰区域。找到让候选数稳定在 1~3 个的核宽就是比较合适的值。面积过滤也要加上车牌在画面里占的面积有合理范围过小的轮廓直接扔掉。这个阈值和相机安装距离强相关没有通用值按实际画面标定。4.3 二值化与投影分割字符宽度阈值是命门投影分割的参数集中在二值化和最小字符宽度。adaptiveThreshold 的 blockSize 决定局部阈值的考察范围字符笔画细用小 blockSize笔画粗要增大。C 值控制判定偏移C 越大前景越难被判定为白色适合抑制阴影背景。字符最小宽度设错了会出两类典型问题设太大窄字符数字 1、字母 I被当噪声丢掉识别结果少一位设太小字符间隙里的孤立噪点被当成字符结果多出一截。调试方法打印每段投影区间的宽度和真实字符宽度对比取中间值。倾斜车牌投影前必须转正。不矫正就做垂直投影字符边界全是斜的分割结果惨不忍睹。矫正用最小外接矩形的角度做仿射变换角度超过正负 5 度就必须处理。这个环节不做后面识别分数再高也白搭。4.4 匹配阈值置信度低于阈值宁可返回未知模板匹配的阈值不建议拍脑袋定。我的习惯是先跑一批测试图统计正确识别和错误识别对应的分数分布再在两类分数之间取分隔值比直接定 0.7 靠谱得多。# 统计一批样本的匹配分数观察分布再定阈值 scores [run_match(sample) for sample in test_samples] scores.sort() print(np.percentile(scores, [10, 25, 50, 75, 90]))如果发现错误识别的分数普遍高于 0.75问题不在阈值而在模板库本身——模板太少或质量差该去扩充模板而不是调阈值。样本量少的时候宁可返回未知字符也比硬给一个错字强。后续对接计费或统计系统时错字的处理成本比空值高得多。5. 避坑指南五个翻车现场和排查思路跑这类参考项目问题多半不在算法本身而在环境、路径、参数三处。下面五条是我复现和调试时真实踩过的坑按现象、原因、解决三部分写。前两条是环境问题中两条是代码兼容和路径问题最后一条是 GUI 工程问题按顺序排查基本能覆盖九成启动失败场景。5.1 Python 环境串台包装了找不到现象pip install 成功但 python main.py 启动报 No module named cv2。原因机器上装了多个 Pythonpip 属于 A 环境python 命令指向 B 环境包装到了 A 里B 自然找不到。参考项目场景里conda、系统 Python、venv 三套环境并存的概率很高串台是常态。解决统一用虚拟环境进入环境后先跑 python -m pip list 确认 cv2、PyQt5 都在列表里。再不行在项目入口打印 sys.executable把当前解释器路径和 pip 安装路径比对立刻能看出是不是同一套环境。5.2 findContours 解包报错返回值数量对不上现象报 ValueError: not enough values to unpack (expected 3, got 2)或者反过来 expected 2 got 3。原因OpenCV 3.x 和 4.x 的 findContours 返回值数量不一致。4.x 返回两个值3.x 某些版本返回三个值。项目在 3.4.3 环境下调试过同一段代码切到 4.2.0 行为就变了。解决不硬改环境版本用统一写法兼容两代 API先取 len(result) 判断再解包。这样同一份源码在两组测试平台都能跑后面升级 OpenCV 版本也不用回头改代码。5.3 模板库路径失效换启动方式就找不到模板现象在项目根目录启动正常换目录或用 IDE 直接运行报找不到模板文件识别结果全是乱码。原因项目用相对路径加载模板库而相对路径是相对当前工作目录解析的。IDE 的运行配置、命令行所在目录不一样工作目录就变了模板自然找不到。解决在入口文件开头把工作目录切到项目根目录加一行 os.chdir(os.path.dirname(os.path.abspath(file)))。或者把模板路径改成基于项目根目录的绝对路径拼接。这个动作做完换任何启动方式都不怕。5.4 字符粘连投影分割把两个字符切成一个现象识别结果字符数变少7 位车牌只出 6 位。打开 debug_char_auxRoi 图能看到某一张里有粘连的两个字符。原因字符间距小、二值化后笔画粘连或者闭运算核太宽把字符缝隙填上了。用固定阈值二值化时这个问题反复出现阳光图片尤其明显。解决换 adaptiveThresholdblockSize 从 11 到 21 逐档试同时收窄形态学核宽。先看 auxRoi 中间图确认粘连位置再决定调二值化还是调核不要凭感觉同时乱动多个参数。5.5 界面卡死识别逻辑占住 GUI 主线程现象点开始识别后PyQt5 窗口无响应系统提示程序未响应。原因车牌识别是纯计算任务直接在按钮的槽函数里跑CPU 忙起来就占住了主线程的消息循环。图片分辨率高、循环多时几秒内就会卡死。解决把识别逻辑放进 QThread主线程只负责更新结果。这是 PyQt5 做图像处理 GUI 的通用套路改动量不大但对体验提升非常明显。至少也要在识别开始前禁用按钮、结束后恢复避免重复点击叠加计算任务。调参之前先复制一份原始代码或参数配置改坏了能还原。很多翻车不是算法不行是被自己改乱的这个习惯能帮你省掉大半悔棋时间。6. 进阶验证用调试中间图搭一套回归测试参考项目自带的测试图数量有限真要交付或做研究得把它扩成能反复跑的回归流程。中间图机制是这套流程的地基没有它每次调参都像盲人摸象。6.1 固定调试输出让失败可复现项目能输出 debug_char_auxRoi、gt_ 这类中间图说明作者开发时就习惯留证据。建议把这个机制固定下来每次跑完测试原始图、定位结果图、分割结果图、最终识别结果按固定目录保存文件名带原图和序号。这样任何一次失败都能回溯到具体环节不用重新跑一遍完整流程。# 每张图的识别结果写进 CSV自动和 gt 比对 import csv with open(result_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([img_name, .join(result), gt_text, OK if .join(result) gt_text else FAIL])gt_ 标注文件正好能当 CSV 的比对来源。把 gt 图的文件名解析成标注文本跑完自动对比人工只看失败样本效率会高很多。6.2 分场景统计准确率别只看总正确率我自己踩过的一个教训所有测试图混在一起统计总正确率 92%看起来不错按场景分开看阳光样本只有 60%。光照一强颜色筛选偏色、字符投影断裂短板被总正确率盖住了。所以回归测试一定要带场景标签sun、night、tilt 各统计一份。哪个场景弱就针对性调哪组参数而不是盲目动全局配置。6.3 参数版本化改坏了能回滚调参最大的风险是改了一轮之后整体变差却想不起原来参数是什么。我现在的习惯每组参数存一个配置文件文件名带日期和准确率比如 params_20240612_acc092.ini。调参前先看基准准确率调完跑全量测试准确率提升才保留否则还原。从那以后我每次跑车牌识别项目都强制走一遍基准、调参、回归、归档的流程中间图、结果 CSV、参数文件三样对齐才收工。这份源码包连同测试图和模板库一起下载按这套流程跑一遍收获比只看教程大得多。希望这套流程能帮到你。本文还有配套的精品资源点击获取