资讯动态

OpenCV车牌识别源码:定位、分割、识别全流程解析

发布时间:2026/9/28 22:54:56 来源:尧图企业网站定制
简介这是一套面向计算机视觉初学者与开发者的车牌号识别与提取实战源码基于OpenCV与Python实现可应用于智能交通、停车场自动收费、车辆管控等场景。项目围绕车牌定位、字符分割与字符识别等核心环节展开帮助读者理解从图像预处理到号码输出的完整流程。压缩包共43个文件约2.47MB其中37个JPG图片作为车牌样本用于算法学习与测试2个Python源文件承载核心识别逻辑另有Markdown文档说明项目结构与用法以及编译文件与说明文本便于直接运行。目前已有434人学习下载。通过这套源码读者可获得可运行的车牌识别基础工具并借助样本图片与文档快速复现实验、调试参数为后续优化光照、脏污、破损等复杂场景下的识别效果提供参考也可作为学习计算机视觉与机器学习的实践案例。1. 从一张车牌照片到号码文本这套 OpenCV 车牌识别源码能省掉哪些重复劳动手里有一批车辆照片想批量把车牌号抠出来存进表格第一反应往往是找现成的识别接口。可真到落地时才发现接口按次计费、网络延迟不可控、内网环境根本调不通而自己从零写一套又不知道从哪下手。这套基于 OpenCV 和 Python 的车牌号识别与提取设计源码解决的正是这个中间地带的问题它把车牌定位、字符分割、字符识别这条链路用纯 Python 加 OpenCV 串了起来不依赖深度学习框架也不需要 GPU一台装了 Python 的普通机器就能跑。项目里带了 37 张 JPG 车牌样本图覆盖不同角度和光照还有 car.py、spider.py 两个核心源文件以及一份 readme.txt 说明。适合谁做课程设计的学生、想快速验证车牌识别流程的开发者、需要在内网离线环境跑识别的工程人员。它不追求工业级精度但胜在链路完整、代码可读、改起来方便拿来当二次开发的起点比从零造轮子划算得多。2. 拆开 car.py车牌定位、字符分割、字符识别三段链路怎么串2.1 先看清整个识别流程的骨架车牌识别听起来是一个任务实际拆开是三个独立又串联的阶段。第一阶段是车牌定位从整张车辆照片里找到车牌所在的矩形区域把无关背景裁掉第二阶段是字符分割在定位到的车牌小图里把每个字符单独切出来因为后面识别是逐字符进行的第三阶段是字符识别对每个切出来的字符图判断它到底是哪个数字或字母。这三段任何一段出问题最终结果都会错而且错误会往后传递——定位偏了分割就切不准识别自然全乱。car.py 的组织方式基本就是按这三段来的。常见做法是先用颜色特征做粗定位因为国内蓝底白字车牌的颜色饱和度很高在 HSV 空间里用 inRange 卡一个蓝色区间就能把候选区域筛出来再配合形态学操作把车牌连成一块完整矩形。定位到之后转灰度、做自适应二值化让白色字符从蓝色底上凸显出来然后用轮廓查找或垂直投影把字符一个个切开。最后识别环节这套源码走的是模板匹配路线把切出来的字符和预置的字符模板逐一比对取相似度最高的那个作为结果。为什么用模板匹配而不是深度学习因为这套源码的定位是轻量、可读、可改。模板匹配不需要训练、不需要标注数据、不需要额外依赖几十行代码就能跑通对课程设计和流程验证来说足够。代价是它对字体变化、模糊、倾斜的鲁棒性有限遇到非常规字体或严重污损的车牌容易翻车。这一点后面避坑章节会细说。2.2 环境准备与依赖安装跑这套代码之前环境要先配好。Python 版本建议 3.8 以上项目里的编译文件是 cpython-311说明作者用的是 3.11用 3.11 或 3.10 都比较稳。OpenCV 用 opencv-python 包即可不需要编译源码直接 pip 装。# 创建独立虚拟环境避免和系统里的包冲突 python -m venv car_env # 激活虚拟环境Windows car_env\Scripts\activate # 激活虚拟环境macOS / Linux source car_env/bin/activate # 安装核心依赖 pip install opencv-python numpy # 验证 OpenCV 是否装好能打印版本号就说明没问题 python -c import cv2; print(cv2.__version__)这里几个参数值得说明。用虚拟环境是为了隔离因为 OpenCV 不同版本之间 API 有差异系统里如果已经装了别的版本直接全局安装容易打架。opencv-python 是预编译好的 wheel 包装起来快适合直接调用如果你需要用到 SIFT、SURF 这类专利算法或者要自己编译带 CUDA 的版本才需要走源码编译那条路这套车牌识别用不到。numpy 是 OpenCV 的底层依赖一般会跟着自动装上但显式写出来更保险。装完之后如果 import cv2 报 ModuleNotFoundError: No module named opencv九成是虚拟环境没激活或者 pip 装到了另一个 Python 解释器下面。先 which pythonWindows 用 where python确认当前解释器路径再用这个解释器对应的 pip 重装一次。2.3 车牌定位HSV 颜色筛选加形态学处理定位是整条链路里最影响成败的一步。下面这段代码演示了从读图到框出车牌候选区域的核心逻辑思路是先转 HSV 卡蓝色再用形态学把碎块连成整块。import cv2 import numpy as np def locate_plate(image_path): # 读取原图 img cv2.imread(image_path) if img is None: raise FileNotFoundError(f读不到图片: {image_path}) # 转 HSV 空间颜色筛选在 HSV 下比 RGB 稳定 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 蓝色车牌的 HSV 区间H 在 100-124 之间覆盖大部分蓝底 lower_blue np.array([100, 80, 80]) upper_blue np.array([124, 255, 255]) # 生成蓝色区域的二值掩膜 mask cv2.inRange(hsv, lower_blue, upper_blue) # 开运算去掉小噪点闭运算把车牌区域连成一块 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 找轮廓按面积排序取最大的几个作为候选 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours sorted(contours, keycv2.contourArea, reverseTrue) for cnt in contours[:5]: x, y, w, h cv2.boundingRect(cnt) # 车牌宽高比大致在 2.5 到 5 之间据此过滤 ratio w / float(h) if 2.5 ratio 5.0 and w 80: return img, (x, y, w, h) return img, None逻辑说明inRange 把蓝色像素标成 255、其余标成 0得到一张黑白掩膜。形态学的闭运算先膨胀后腐蚀负责把车牌上被字符打断的蓝色区域重新连起来开运算先腐蚀后膨胀负责把零散的小蓝点清掉。kernel 的尺寸 (17, 5) 是横向长条因为车牌是扁的横向连接比纵向连接更符合实际。参数说明lower_blue 和 upper_blue 的 H 区间是这套代码最需要按实际数据调的地方。国内蓝牌 H 大致落在 100 到 124但如果你的图片偏色严重或者光照很暗S 和 V 的下限这里都是 80要适当降低否则蓝色区域会被漏掉。宽高比 2.5 到 5.0 是经验区间单层蓝牌接近 3.14新能源绿牌比例不同如果你的场景混了多种车牌这个过滤条件要放宽或分路处理。2.4 字符分割二值化加垂直投影切字符定位到车牌矩形后把它裁出来单独处理。字符分割的常见做法是转灰度、二值化然后做垂直投影——统计每一列的白点数量字符所在列白点多字符之间的间隙列白点少据此找到切分点。def split_characters(plate_img): # 裁出的车牌转灰度 gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 自适应二值化应对光照不均比全局阈值稳 binary cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 19, 5) # 垂直投影按列统计白点数量 projection np.sum(binary 255, axis0) # 设定阈值低于阈值认为是字符间隙 threshold binary.shape[0] * 0.05 chars [] start None for i, val in enumerate(projection): if val threshold and start is None: start i elif val threshold and start is not None: # 宽度太窄的当噪点丢掉 if i - start 5: chars.append(binary[:, start:i]) start None return chars逻辑说明adaptiveThreshold 对每个小区域单独算阈值比全局阈值更能扛光照不均车牌一边亮一边暗时优势明显。垂直投影把二维问题降成一维找间隙就变成找投影值低于阈值的连续区间。threshold 取图像高度的 5%意思是某一列白点少于总高度的 5% 就认为是间隙。参数说明adaptiveThreshold 的 blockSize 必须取奇数19 表示每个像素参考周围 19×19 的区域C 是常数从均值里减掉值越大二值化越激进、白区越少5 是个温和的起点。最小字符宽度 5 是过滤噪点的下限实际车牌字符宽度通常在 15 像素以上如果你的图分辨率高这个值要跟着放大。分割出来的字符顺序就是从左到右直接对应车牌号的顺序。3. 拿 37 张样本图跑通全流程从单张测试到批量提取3.1 单张图片走通识别链路环境配好、函数写好先用项目里的一张样本图把整条链路跑通确认每一步的输出都符合预期。这一步不要急着批量单张调通了再放大。import cv2 import numpy as np # 复用前面的定位和分割函数 img, box locate_plate(img/84.jpg) if box is None: print(没定位到车牌检查颜色区间和宽高比过滤) else: x, y, w, h box # 裁出车牌区域 plate img[y:yh, x:xw] # 保存中间结果方便肉眼核对定位准不准 cv2.imwrite(debug_plate.jpg, plate) chars split_characters(plate) print(f切出 {len(chars)} 个字符) # 把每个字符单独存图检查分割有没有切歪 for idx, ch in enumerate(chars): cv2.imwrite(fdebug_char_{idx}.jpg, ch)逻辑说明这段代码的价值在于把中间结果落盘。定位准不准看 debug_plate.jpg 就知道分割切得对不对看 debug_char_*.jpg 一目了然。很多人一上来就批量跑结果识别全错却不知道错在哪一段把中间图存下来是最省时间的排查手段。参数说明裁图用的是 img[y:yh, x:xw]注意 OpenCV 的坐标顺序是先 y 后 x写反了会裁出乱七八糟的区域。字符图命名带序号方便按顺序核对。如果切出来的字符数明显不对正常蓝牌 7 个字符先回去看二值化效果多半是阈值或 blockSize 不合适。3.2 批量处理与结果落表单张跑通后把 img 目录下的样本图批量过一遍结果写进 CSV方便后续核对和统计。import os import csv import cv2 def recognize_batch(img_dir, output_csv): results [] # 只处理 jpg 文件跳过其他格式 files [f for f in os.listdir(img_dir) if f.lower().endswith(.jpg)] for fname in sorted(files): path os.path.join(img_dir, fname) try: img, box locate_plate(path) if box is None: results.append((fname, , 定位失败)) continue x, y, w, h box plate img[y:yh, x:xw] chars split_characters(plate) # 这里接字符识别得到号码文本 plate_text recognize_chars(chars) results.append((fname, plate_text, 成功)) except Exception as e: # 单张失败不影响整批 results.append((fname, , f异常: {e})) # 写 CSVutf-8-sig 保证 Excel 打开不乱码 with open(output_csv, w, newline, encodingutf-8-sig) as fp: writer csv.writer(fp) writer.writerow([文件名, 识别结果, 状态]) writer.writerows(results) return results recognize_batch(img, result.csv)逻辑说明批量处理的关键是异常隔离单张图报错不能让整批中断所以每张图包在 try 里失败就记一条状态继续往下走。结果写 CSV 用 utf-8-sig 编码是因为普通 utf-8 在 Excel 里打开中文会乱码加 BOM 头能解决。参数说明sorted(files) 保证处理顺序稳定方便复现。状态列区分「定位失败」和「异常」前者是算法没找到车牌后者是代码报错排查方向完全不同。recognize_chars 是字符识别函数需要你自己接上模板匹配或 OCR这套源码里对应的是模板比对那段逻辑。3.3 识别环节的模板匹配实现字符识别这一步模板匹配的思路是把每个切出来的字符缩放到统一尺寸和预置的字符模板逐一算相似度取最高的。下面是一个简化实现。def recognize_chars(chars, template_dirtemplates): # 预加载所有字符模板键是字符值是模板图 templates {} for fname in os.listdir(template_dir): label os.path.splitext(fname)[0] # 文件名即字符 tpl cv2.imread(os.path.join(template_dir, fname), 0) templates[label] cv2.resize(tpl, (20, 40)) result for ch in chars: # 统一尺寸和模板对齐 ch_resized cv2.resize(ch, (20, 40)) best_label, best_score , -1 for label, tpl in templates.items(): # 用归一化相关系数做相似度 score cv2.matchTemplate(ch_resized, tpl, cv2.TM_CCOEFF_NORMED)[0][0] if score best_score: best_score, best_label score, label result best_label return result逻辑说明matchTemplate 在模板和原图尺寸完全一致时返回的是 1×1 的矩阵取 [0][0] 就是相似度分数。TM_CCOEFF_NORMED 是归一化相关系数结果在 -1 到 1 之间越接近 1 越像对亮度变化有一定容忍度。参数说明统一尺寸 (20, 40) 是宽 20 高 40字符是竖长的这个比例要贴合实际字符形状比例失真会拉低匹配分数。模板目录里每个文件以字符命名比如 0.jpg、A.jpg加载时用文件名当标签。这套模板匹配对字体一致性要求高如果测试图和模板字体差异大分数普遍偏低识别就会乱。4. 避坑与排查车牌识别跑不通时先看这几处4.1 定位框偏大或偏小现象debug_plate.jpg 里框出来的区域包含了大量背景或者只框到车牌的一部分。原因HSV 颜色区间没贴合实际图片或者形态学 kernel 尺寸不合适。解决先把原图转 HSV用取色工具看车牌区域的实际 H、S、V 值据此调整 lower_blue 和 upper_bluekernel 太大容易把相邻的蓝色物体连进来太小则车牌连不成整块从 (17, 5) 开始上下试。4.2 字符切多了或切少了现象正常 7 个字符的车牌切出 5 个或 9 个。原因二值化后字符之间有粘连或者字符内部断裂被当成间隙。解决调 adaptiveThreshold 的 blockSize 和 CblockSize 调大对光照更宽容但细节会糊C 调小让白区更完整也可以在垂直投影前先做一次横向膨胀把断裂的字符连起来。4.3 识别结果张冠李戴现象字符切对了但识别出来的号码不对比如 8 认成 B、0 认成 O。原因模板匹配对形近字符区分能力弱模板尺寸或字体和实际不符。解决把形近字符的模板都放进去让匹配去选统一尺寸时保持字符原始宽高比不要硬拉伸如果还是分不清考虑对字符图做细化或骨架提取突出笔画结构差异。4.4 批量跑到某张图就崩现象批量处理时程序在某张图上抛异常退出。原因图片路径含中文、图片损坏、或者某张图尺寸异常导致后续操作越界。解决读图后先判断 img is None路径统一用 os.path.join 拼接避免分隔符问题批量循环里包 try 做异常隔离单张失败记录状态继续不要让整批挂掉。4.5 换一批图效果断崖式下降现象在项目自带的 37 张样本上跑得还行换成自己的图就大面积失败。原因颜色区间、宽高比、二值化参数都是针对样本调过的换数据分布就失配。解决把新图当新项目对待重新统计颜色分布和车牌尺寸参数从头调一遍如果场景里车牌类型多蓝牌、绿牌、黄牌按颜色分路处理不要指望一套参数通吃。5. 把识别结果做稳的几个进阶技巧模板匹配跑通之后想让它更稳有几个方向可以试。第一个是预处理增强在定位前先做一次直方图均衡化或者 CLAHE把暗图和过曝图的对比度拉回来颜色筛选的命中率会明显提升。第二个是定位结果校验拿到候选框后不要直接用先检查框内蓝色像素占比占比过低说明框错了直接丢弃换下一个候选。第三个是识别环节加置信度阈值matchTemplate 的最高分如果低于某个值比如 0.5说明这个字符谁都不像与其硬猜不如标记为不确定人工复核。下面这个表格对比了几种常见改进手段的投入和收益按自己的场景选。改进手段实现难度主要收益适用场景CLAHE 预处理低暗光、过曝图定位更准光照条件差的图定位框蓝色占比校验低减少误定位背景有蓝色干扰物识别置信度阈值低减少错认暴露问题对准确率要求高字符骨架提取中形近字符区分更好字体差异大的图多颜色分路处理中支持多种车牌类型混牌场景还有一个容易被忽略的点是验证方法。不要只看几张图的识别结果对不对要建一个小测试集每张图人工标好正确号码跑完算准确率。准确率分两层看定位成功率框对了没有和识别准确率号码对了没有。定位成功率低就先调颜色和形态学识别准确率低就调二值化和模板。分开统计才能定位到具体是哪一段拖后腿。我自己的习惯是每次改完参数先把中间结果图存下来扫一眼确认定位和分割这两段没退化再看最终号码。因为最终结果错的时候你没法从号码本身判断是定位错了还是识别错了只有中间图能告诉你真相。从那以后我每次调这套车牌识别的参数都强制走一遍「存中间图 → 看定位 → 看分割 → 看识别」这个流程省了大量瞎猜的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑