简介这是一套面向计算机视觉初学者与进阶学习者的OpenCV车牌识别实战项目基于Python实现覆盖从图像预处理、车牌定位、字符分割到字符识别的完整流程适合希望理解图像处理与机器学习落地应用、并动手复现车牌识别系统的开发者。压缩包共26个文件约4.44MB包含5个py脚本分别对应车牌图像识别与车牌视频识别两条主线2个mp4素材视频用于测试实时性能1张png示例图片以及12个xml、3个iml等IDE工程配置文件下载后配置好OpenCV、NumPy等依赖即可直接运行。项目已有650人学习代码结构清晰读者可对照脚本逐步调试灰度化、二值化、边缘检测、形态学操作与字符分类等关键环节理解各阶段参数对识别效果的影响并在此基础上改进系统以适应不同场景为后续其他计算机视觉项目打下实践基础。1. 拿到一套 OpenCV 车牌识别项目先别急着点运行很多人下载完一套 OpenCV 车牌识别项目第一反应是双击main.py然后被cv2.error或者ModuleNotFoundError: No module named opencv迎面拍回来。这套项目里通常包含素材视频、测试图片和完整代码理论上解压就能跑但「下载即可运行」有个隐藏前提你的 Python 环境、OpenCV 版本、素材路径三者必须对齐。车牌识别本身是一条经典流水线——灰度化、高斯模糊、Sobel 边缘、形态学闭运算、轮廓筛选、字符分割、模板匹配或 OCR——每一环都有参数在等着你。这篇文章不讲空泛概念而是按「环境怎么配、代码怎么读、参数怎么调、翻车怎么救」的顺序把这套项目从解压到出结果的全过程拆开。适合刚接触 OpenCV 图像处理项目、想拿车牌识别练手或者手里已经有一套代码但跑不通的人。2. 环境与素材让 OpenCV 车牌识别项目真正跑起来2.1 安装 OpenCV 的三种路径与版本选择热词里安装opencv、ubuntu 配置opencv、vs2022安装什么版本的opencv反复出现说明环境这一步卡住的人最多。Python 路线最省事一条命令解决pip install opencv-python opencv-contrib-python numpyopencv-python是主包opencv-contrib-python带额外模块部分字符识别算法会用到numpy是矩阵运算依赖。三者版本要匹配建议锁一个稳定组合比如opencv-python4.8.1.78配numpy2.0避免 numpy 2.x 带来的 ABI 不兼容。如果你在 Ubuntu 上系统源里的python3-opencv往往版本偏旧做形态学参数调试时行为和新版有差异建议还是走 pip。Windows VS2022 的 C 路线则要自己编译或下载预编译包配置include和lib目录新手不建议一上来就啃 C 版本先用 Python 把算法逻辑跑通再考虑移植。提示装完立刻验证别等到跑项目才发现装错。import cv2 import numpy as np print(cv2.__version__) # 确认版本 print(hasattr(cv2, Sobel)) # 确认核心函数存在cv2.__version__打印出来是 4.x 就基本没问题。如果报cv2.error: opencv(4.4.0) ... pip-req-build这类信息通常是 pip 在源码编译时缺依赖直接换预编译 wheel 或升级 pip 即可。2.2 素材目录结构与路径写法项目里的素材一般分两类videos/放测试视频images/放车牌图片。代码里读文件最怕相对路径因为你的工作目录不一定是项目根目录。稳妥做法是用os.path拼绝对路径import os import cv2 BASE_DIR os.path.dirname(os.path.abspath(__file__)) img_path os.path.join(BASE_DIR, images, car1.jpg) img cv2.imread(img_path) if img is None: raise FileNotFoundError(f读不到图片: {img_path}) print(图像尺寸:, img.shape) # (高, 宽, 通道)os.path.abspath(__file__)拿到当前脚本的绝对路径再往上取目录无论从哪里执行都不会找不到文件。cv2.imread返回None是最常见的静默失败——路径错、中文路径、格式不支持都会返回 None 而不报错所以必须显式判断。视频用cv2.VideoCapture(video_path)同样先判断isOpened()。2.3 用一张图跑通最小识别链路在调完整项目前先用单张图验证流水线是否通。下面是最小可运行版本import cv2 import numpy as np def detect_plate(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转灰度 blur cv2.GaussianBlur(gray, (5, 5), 0) # 去噪 sobel cv2.Sobel(blur, cv2.CV_8U, 1, 0, ksize3) # 水平边缘 _, binary cv2.threshold(sobel, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: x, y, w, h cv2.boundingRect(cnt) ratio w / float(h) if 2.5 ratio 5.5 and w 80: # 车牌长宽比经验值 cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) return img img cv2.imread(images/car1.jpg) result detect_plate(img) cv2.imwrite(output.jpg, result)这段代码的逻辑链是灰度化降低计算量高斯模糊压掉噪点Sobel 只取水平方向因为车牌字符是垂直边缘密集Otsu 自动阈值避免手调闭运算把断裂的字符连成块最后用长宽比和宽度筛轮廓。参数里(5,5)是模糊核图越大核可以越大(17,5)是形态学核横向长是为了把一排字符连起来2.5 ratio 5.5是中国蓝牌的经验区间新能源绿牌比例不同要另调。跑通这一步再去看项目里的完整代码你会发现主干就是这个结构。3. 拆解完整代码车牌定位、字符分割与识别三段式3.1 车牌定位阶段的参数怎么调完整项目里定位部分通常比最小版本复杂会加颜色筛选HSV 空间找蓝色或绿色区域和多次形态学操作。核心参数有三个必须理解参数作用调大后果调小后果高斯核 (w,h)去噪强度边缘变糊漏检噪点残留误检形态学核 (w,h)字符连接多个车牌粘连字符断裂成碎块长宽比区间轮廓筛选混入非车牌矩形真车牌被过滤HSV 颜色筛选的写法hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) lower np.array([100, 80, 80]) # 蓝色下限 upper np.array([124, 255, 255]) # 蓝色上限 mask cv2.inRange(hsv, lower, upper)inRange把蓝色区域变成白色掩膜再和边缘结果做与运算能大幅减少误检。蓝色 H 通道大约在 100~124绿色在 40~80 附近具体要按你的素材实测微调。这一步的坑在于光照逆光或夜间图片颜色偏移严重纯颜色法会失效所以项目里一般是颜色和边缘双路并行取交集或并集。3.2 字符分割的投影法与连通域法定位到车牌后要切成单个字符。两种主流做法投影法对车牌区域二值化后统计垂直方向白色像素数波谷就是字符间隙。plate_gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) _, plate_bin cv2.threshold(plate_gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) col_sum np.sum(plate_bin, axis0) // 255 # 每列白点数col_sum里连续为 0 的区间就是间隙据此切分。缺点是车牌有边框或铆钉时投影会乱需要先裁掉边框。连通域法用cv2.connectedComponentsWithStats直接找每个字符的外接矩形再按 x 坐标排序。num, labels, stats, _ cv2.connectedComponentsWithStats( plate_bin, connectivity8) chars [] for i in range(1, num): x, y, w, h, area stats[i] if 10 h plate_bin.shape[0] and area 30: chars.append((x, y, w, h)) chars.sort(keylambda b: b[0]) # 按从左到右排序connectivity8表示八邻域连通area 30过滤噪点小连通域高度上限排除边框。连通域法对断裂字符更鲁棒但汉字笔画可能被拆成多块需要做水平投影合并。3.3 字符识别模板匹配与 OCR 的取舍识别环节项目里常见两种实现。模板匹配是把分割出的字符和预存字符模板逐一比对取相似度最高的def match_char(char_img, templates): char_img cv2.resize(char_img, (20, 40)) best, best_score None, -1 for label, tpl in templates.items(): tpl cv2.resize(tpl, (20, 40)) score cv2.matchTemplate(char_img, tpl, cv2.TM_CCOEFF_NORMED).max() if score best_score: best, best_score label, score return best, best_scorecv2.matchTemplate的TM_CCOEFF_NORMED返回 -1 到 1 的相似度越接近 1 越像。模板匹配的硬伤是字体、倾斜、光照一变就掉分适合固定场景演示。生产环境更常用 OCR比如把分割字符送进 Tesseract 或轻量 CNN。热词里halcon和opencv的区别也常在这被提起——Halcon 的 OCR 和形态学算子封装更成熟OpenCV 胜在免费和可控做车牌这种规则明确的场景OpenCV 完全够用只是识别率要靠自己调。4. 避坑与排查车牌识别项目最常见的 5 个翻车现场4.1 读图返回 None 却没有任何报错现象cv2.imread之后img.shape报AttributeError: NoneType。 原因路径错误、中文路径、文件不存在或格式不支持OpenCV 不抛异常只返回 None。 解决读后立刻判断if img is None用os.path.exists先验证路径中文路径改用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)。4.2 视频能播但一帧都识别不到现象VideoCapture打开成功循环里每帧都没框。 原因视频分辨率高、车牌小固定形态学核尺寸不匹配或首帧是黑屏过渡帧。 解决按帧尺寸动态缩放核大小比如kernel_w max(17, int(frame.shape[1] / 60))跳过前若干帧或对每帧先缩放再处理。4.3 轮廓筛选把车牌和车灯一起框住现象输出图上车灯、格栅也被画了框。 原因长宽比区间太宽或边缘检测把车灯的水平边缘也强化了。 解决收紧长宽比到 3.0~4.5叠加颜色掩膜做与运算再加面积下限过滤小轮廓。4.4 字符分割把「京」字切成两半现象汉字被拆成左右两部分识别成两个字符。 原因连通域法对左右结构的汉字天然会拆开。 解决分割后做一次水平投影合并若相邻两块高度重叠且间距小于阈值就合并或改用整牌 OCR 不做单字分割。4.5 换一批素材识别率断崖下跌现象自带素材跑得好好的换成自己拍的图全乱。 原因参数过拟合到原素材的光照、角度、车牌样式。 解决把阈值、核尺寸、颜色区间抽成配置字典针对新素材重新标定引入自适应阈值cv2.adaptiveThreshold替代固定阈值。5. 从能跑到好用把识别率再往上抬一截项目能跑通只是起点真正拉开差距的是稳定性。我一般会做三件事。第一把整条流水线的中间结果全部可视化落盘——灰度图、边缘图、闭运算图、每个候选轮廓的裁剪图出问题时一眼能看出是哪一环崩的比盲调参数快十倍。第二给参数做网格搜索写个循环遍历形态学核宽度和长宽比区间用一批标注好的图统计命中率选最优组合而不是凭感觉改。best (0, None) for kw in range(13, 25, 2): for ratio_lo in [2.5, 3.0, 3.5]: hit evaluate(kw, ratio_lo, val_images) # 自定义评估函数 if hit best[0]: best (hit, (kw, ratio_lo)) print(最优参数:, best)evaluate里统计的是 IoU 大于 0.5 的命中数val_images至少准备 20 张不同光照的图否则搜出来的参数还是过拟合。第三识别环节如果模板匹配不够就换轻量 OCR把分割字符统一 resize 到固定尺寸送进去识别率通常能从七八成提到九成以上。验证方法上别只看单张效果图。准备一个test/目录按「顺光、逆光、倾斜、夜间、新能源绿牌」分类各放几张每次改完参数跑一遍全量记录每类的通过率。我自己的习惯是维护一个result.csv每行记录图片名、是否检出、识别字符串、置信度改代码前后对比这个文件比盯着输出图靠谱得多。车牌识别这套东西玄学的地方在于光照血泪经验是别信单张图的惊艳效果要信批量统计。把评估脚本写好后面换算法、换参数都有后悔药可吃。希望帮到你。本文还有配套的精品资源点击获取