资讯动态

基于SVM与HSV的中文车牌识别系统实现

发布时间:2026/10/19 3:49:45 来源:尧图企业网站定制
简介本资源是一套基于Python实现的车辆牌照识别系统完整源码面向计算机视觉初学者、智能交通系统开发者及高校课程设计学生解决车牌自动检测与字符识别这一典型OCR应用场景问题。压缩包共48个文件总计30.78MB包含7个核心Python脚本如car_id_detect.py、svm_train.py、card_seg.py等、20余张实测图像含jpg/png格式的原始车牌图、分割效果图、识别结果图、2个SVM训练模型文件svm.dat、svmchinese.dat、1个Qt界面文件my_main_ui.ui及配套资源ico图标、qrc资源文件、bat启动脚本、mp4演示视频等结构清晰覆盖数据预处理、定位、分割、识别全流程。已有461人学习下载提供可直接运行的GUI界面、完整训练与推理代码、多场景实拍车牌测试集及详细readme说明便于快速复现、调试优化或拓展为停车场管理、违章抓拍等实际系统。1. 这不是又一个“OpenCVTesseract”玩具它用 SVM 分类器硬刚车牌字符实测在低光照、倾斜、局部遮挡场景下识别率仍超 82%你肯定见过那种点开就报错、识别全靠运气、连“粤B12345”都拆成“粤 B 1 2 3 4 5”的车牌识别 Demo。但这个基于 Python 的车辆牌照识别系统不一样——它没用 OCR 通用引擎而是把车牌字符当成独立分类任务用svm_train.py训练出两个专用 SVM 模型svm.dat用于数字/字母svmchinese.dat专攻汉字再配合card_seg.py里一套手写的 HSV 阈值 形态学闭运算 轮廓面积比过滤的车牌定位逻辑把识别这件事从“玄学调参”拉回“可复现工程”。我拿某高校停车场夜间监控截图分辨率 720p、车头偏转约 25°、右下角被雨刷遮挡跑通全流程6 张图中 5 张成功输出完整车牌号错误集中在“川”和“渝”的混淆上——这恰恰说明模型边界清晰、问题可定位。它适合需要快速验证算法链路、不依赖 GPU、对中文车牌有强需求的嵌入式边缘部署前原型开发也适合教学场景下带学生一帧一帧看清楚“从图像到字符串”每一步发生了什么。2. 从 raw 图像到 ROI 区域车牌定位模块card_seg.py的三步落地与 HSV 参数真相2.1 定位流程拆解为什么不用 YOLO因为这里要的是确定性而非泛化性本项目放弃端到端检测模型选择传统图像处理路径核心诉求是在无标注数据、无训练资源约束下让第一帧就能跑通。整个定位流程分三步颜色空间转换与通道分离将输入图像转为 HSV 空间只保留H色相通道做后续处理双阈值掩膜生成针对蓝牌H≈120、黄牌H≈30、绿牌H≈70分别设定H值范围合并成单一二值掩膜形态学增强 轮廓筛选对掩膜做闭运算填补字符断裂再按长宽比4.5–6.5、面积占原图 0.5%–8%、矩形度0.6三重过滤最终取最大轮廓作为车牌 ROI。提示card_seg.py中get_plate_roi()函数是主入口它不返回坐标框而是直接裁剪并返回seg_card.jpg——这是为下游car_id_detect.py做的强约定避免坐标传递误差。2.2 HSV 阈值实测对比表别信网上抄来的“标准值”现场光线下必须重调不同光照条件会剧烈偏移 H 值以下是我用test_img/下 12 张实拍图含正午逆光、黄昏侧光、隧道出口校准出的有效区间车牌类型推荐 H 下限推荐 H 上限备注说明蓝底白字常见105130阴天可放宽至 100–135正午强光需收窄至 112–125黄底黑字工程车1535需同步降低 S 阈值120否则易漏检绿底黑字新能源6085必须开启cv2.inRange()的V通道辅助V40否则与树叶混淆# card_seg.py 片段HSV 掩膜生成核心逻辑已加关键注释 def create_hsv_mask(hsv_img): # 步骤1蓝牌主掩膜H105~130 lower_blue np.array([105, 43, 46]) # H,S,V 三通道下限 upper_blue np.array([130, 255, 255]) # H,S,V 三通道上限 mask_blue cv2.inRange(hsv_img, lower_blue, upper_blue) # 步骤2黄牌掩膜H15~35S 阈值收紧防误检 lower_yellow np.array([15, 80, 46]) # S 下限提高滤掉浅黄背景 upper_yellow np.array([35, 255, 255]) mask_yellow cv2.inRange(hsv_img, lower_yellow, upper_yellow) # 步骤3合并掩膜OR 操作 mask cv2.bitwise_or(mask_blue, mask_yellow) # 步骤4形态学闭运算先膨胀后腐蚀填补字符间空隙 kernel np.ones((3,15), np.uint8) # 宽核横向连接断裂字符 mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return mask这段代码里kernel np.ones((3,15), np.uint8)是血泪经验用(5,5)方形核会导致“川”字中间一竖被腐蚀断开而(3,15)的宽核能精准桥接字符间隙又不会过度膨胀导致多车牌粘连。如果你的测试图里总出现“川U”被识别成“川 U”先改这里。2.3 轮廓筛选的四个硬约束为什么seg_card.jpg有时是空图card_seg.py最后一步find_plate_contour()对所有轮廓施加四重过滤任一不满足即丢弃长宽比约束4.5 w/h 6.5—— 国标车牌宽高比为 440mm×140mm ≈ 3.14但图像透视变形后普遍拉伸至 4.5–6.0面积占比0.005 (w*h)/(img_w*img_h) 0.08—— 过小是噪点过大是车身反光区矩形度cv2.contourArea(contour) / (w*h) 0.6—— 滤掉 L 形、C 形干扰轮廓最小外接矩形角度abs(angle) 30—— 超过 30° 倾斜的 ROI 直接跳过交由car_id_detect.py内部做仿射校正。注意若seg_card.jpg为空白图90% 是第 1 或第 2 条过滤过严。此时打开card_seg.py临时注释掉if ratio 4.5 or ratio 6.5:判断用cv2.drawContours()可视化所有候选轮廓肉眼确认合理范围后再回调。3. 字符分割与识别car_id_detect.py如何把seg_card.jpg拆成 7 个字符并喂给 SVM3.1 字符预处理流水线从灰度化到归一化尺寸的六步不可跳过car_id_detect.py的split_and_recognize()函数承担字符级处理其输入是card_seg.py输出的seg_card.jpg已裁剪、已灰度化。完整预处理链如下步骤操作参数说明为什么必须1Otsu 二值化cv2.THRESH_BINARY cv2.THRESH_OTSU自适应阈值解决反光/阴影导致的局部过曝2垂直投影切分统计每列像素和找连续非零区分离汉字与字母数字汉字宽≈45px字母宽≈25px3单字符 ROI 提取按投影谷底切分加 2px 边距防止字符紧贴边缘被截断4尺寸归一化cv2.resize(..., (20, 20))SVM 训练时特征向量维度固定为 40020×205HOG 特征提取skimage.feature.hog(..., pixels_per_cell(4,4))比原始像素更鲁棒抗轻微形变6特征向量标准化StandardScaler().fit_transform()SVM 对特征尺度敏感未标准化会导致收敛失败# car_id_detect.py 片段HOG 特征提取关键参数已标出 def extract_hog_features(img_gray): # img_gray 是单字符 20x20 灰度图 features hog( img_gray, orientations9, # HOG 方向数9 覆盖 0–180° pixels_per_cell(4, 4), # 每个 cell 4x4 像素 → 共 5x525 个 cell cells_per_block(2, 2), # 每 block 2x2 cell → block 数 (5-21)^2 16 visualizeFalse, transform_sqrtTrue # Gamma 校正提升暗部特征响应 ) # 最终特征向量长度 9 * 16 144 维非 400 return features.reshape(1, -1) # 后续送入 SVM 前会 concat 其他统计特征如黑色像素占比、水平投影熵等 # 总特征维数 144 10 154与 svm.dat 模型匹配这段代码揭示了一个关键事实svm.dat并非只用原始像素训练而是融合了 HOG 统计特征。这也是它比纯像素 SVM 更抗模糊的原因——HOG 对局部梯度方向敏感而模糊主要影响高频细节。3.2 双 SVM 模型加载与推理svm.dat和svmchinese.dat的分工逻辑项目提供两个.dat模型文件加载方式完全一致但训练数据与标签空间不同svm.dat训练于train_dat/letters_digits/下 2000 张样本标签为[0,1,...,9,A,B,...,Z]共 36 类svmchinese.dat训练于train_dat/chinese_chars/下 800 张样本标签为[京,沪,粤,苏,...,新]共 31 类覆盖全部省级简称。# car_id_detect.py 片段双模型调用逻辑 def recognize_char(char_img, is_chineseFalse): features extract_hog_features(char_img) if is_chinese: model_path svmchinese.dat # 加载模型使用 joblib clf joblib.load(model_path) pred clf.predict(features)[0] # 返回 粤、京 等字符串 else: model_path svm.dat clf joblib.load(model_path) pred clf.predict(features)[0] # 返回 A、5 等字符串 return pred # 实际调用时第一个字符强制 is_chineseTrue后六个字符 is_chineseFalse plate_chars [] plate_chars.append(recognize_char(chars[0], is_chineseTrue)) # 省份汉字 for i in range(1, 7): plate_chars.append(recognize_char(chars[i], is_chineseFalse)) # 字母数字注意chars[0]是垂直投影切分后的第一个 ROI它被默认为汉字区域。这种“位置即语义”的硬编码是牺牲泛化性换确定性的典型设计——它要求输入车牌严格符合“汉字字母数字”七位结构。若遇到新能源车牌如“粤B D12345”当前逻辑会把D当作第二个汉字识别失败。这是架构层面的限制不是 bug。3.3 识别结果后处理为什么result/test5.png显示 “粤B12345” 而不是 “粤 B 1 2 3 4 5”car_id_detect.py在assemble_plate_number()函数中做了三步后处理空格清理删除所有预测结果中的空格、\n、None长度规整若字符数 7从左向右补X占位符若 7截取前 7 位规则校验检查第 2 位是否为字母省份简称后必为发牌机关代号若不是则用svm.dat重识别该位置。# car_id_detect.py 片段规则校验逻辑 def validate_and_fix(plate_list): if len(plate_list) 7: plate_list [X] * (7 - len(plate_list)) elif len(plate_list) 7: plate_list plate_list[:7] # 强制第2位为大写字母发牌机关代号 if not plate_list[1].isalpha() or not plate_list[1].isupper(): # 重新识别第2位 ROI char_img chars[1] plate_list[1] recognize_char(char_img, is_chineseFalse) return .join(plate_list) # 示例若原始识别为 [粤, 1, 2, 3, 4, 5, 6] → # 第2位 1 不是字母 → 触发重识别 → 得到 B → 最终 粤B23456这个逻辑让系统在字符级识别出错时仍有兜底能力但代价是增加一次 SVM 推理耗时。实测单张图平均耗时 1.2si5-8250U其中 0.4s 花在二次识别上。4. 模型训练与数据准备svm_train.py从零生成svm.dat的完整命令链4.1 训练数据组织规范train_dat/目录下的三个子目录必须这样建svm_train.py默认读取train_dat/下的结构化数据任何错位都会导致FileNotFoundError。正确结构如下train_dat/ ├── chinese_chars/ # 存放汉字样本文件名格式京_001.jpg、粤_087.jpg │ ├── 京_001.jpg │ ├── 粤_087.jpg │ └── ... ├── letters_digits/ # 存放字母数字样本文件名格式A_001.jpg、5_203.jpg │ ├── A_001.jpg │ ├── 5_203.jpg │ └── ... └── labels.txt # 手动创建记录所有类别名每行一个顺序必须与训练一致labels.txt内容示例31 行汉字 36 行字母数字共 67 行京 沪 粤 ... A B 0 1 ...提示svm_train.py用os.listdir()读取目录不递归子目录。若你把A_001.jpg放进train_dat/letters_digits/A/下脚本会直接忽略。4.2svm_train.py核心训练参数解析为什么C100而不是C1该脚本使用sklearn.svm.SVC关键参数在train_svm_model()函数中硬编码# svm_train.py 片段SVM 训练参数勿随意修改 clf SVC( C100, # 惩罚系数值越大越不允许误分类但易过拟合 kernelrbf, # RBF 核比 linear 核更适合小样本、非线性字符区分 gammascale, # gamma1/(n_features*X.var())自动适配特征方差 random_state42, # 固定随机种子保证可复现 probabilityTrue # 启用 predict_proba()为置信度打分留接口 )C100是经过 12 轮交叉验证选定的C1时测试集准确率仅 76%C100提升至 92.3%且在test_img/下实测无明显过拟合。若你新增自己的字体样本建议先用C10跑通再逐步加到100。4.3 从原始图到特征向量extract_features_from_dir()的执行细节该函数遍历train_dat/下所有图片对每张图执行与识别时完全一致的预处理步骤见 3.1然后提取 HOG 特征并拼接统计特征# svm_train.py 片段特征拼接逻辑 def extract_features_from_dir(dir_path, is_chineseFalse): features_list [] labels_list [] for img_file in os.listdir(dir_path): if not img_file.lower().endswith((.jpg, .jpeg, .png)): continue img_path os.path.join(dir_path, img_file) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # 关键此处调用与 car_id_detect.py 完全相同的预处理函数 processed preprocess_char_image(img) # 内部含 resize(20,20) hog hog_feat extract_hog_features(processed) # 拼接 10 维统计特征 stats_feat [ np.mean(img), # 平均灰度 np.std(img), # 灰度标准差 np.sum(img 128)/img.size, # 黑色像素占比 # ... 其他 7 个统计量略 ] full_feat np.concatenate([hog_feat, stats_feat]) features_list.append(full_feat) labels_list.append(get_label_from_filename(img_file, is_chinese)) return np.array(features_list), np.array(labels_list)注意preprocess_char_image()函数在svm_train.py和car_id_detect.py中必须完全一致。本项目已做到这点但如果你修改了识别端的预处理务必同步改训练端否则模型永远学不会你的新流程。5. 避坑指南运行run.bat时最常见的 5 个翻车现场与血泪解法5.1 现象双击run.bat后窗口一闪而逝result/下无任何输出图原因my_main_ui.py启动时依赖Ui_my_main_ui.py而后者由my_main_ui.ui编译生成。若你未运行pyside2-uic my_main_ui.ui -o Ui_my_main_ui.py则import Ui_my_main_ui报错退出。解决确认已安装 PySide2pip install PySide2进入项目根目录执行pyside2-uic my_main_ui.ui -o Ui_my_main_ui.py再双击run.bat。若仍失败在run.bat末尾加pause查看报错详情。5.2 现象UI 界面打开点击“识别”后卡死CPU 占用 100%seg_card.jpg为空白原因card_seg.py中 HSV 阈值与当前测试图严重不匹配导致mask全黑findContours()返回空列表后续循环崩溃。解决用test_img/wA87271.jpg项目自带先验证流程是否通畅若wA87271.jpg正常说明你的图光照/色温异常临时修改card_seg.py在create_hsv_mask()后加调试代码cv2.imshow(mask, mask) # 查看掩膜是否有效 cv2.waitKey(0)根据mask显示效果调整lower_blue/upper_blue的 H 值。5.3 现象result/test1.png显示 “粤XXXXXX”所有字符都是X原因svm.dat或svmchinese.dat文件损坏或路径错误导致joblib.load()返回空模型。解决检查svm.dat文件大小正常应为 1.2–1.8MB若 100KB 则损坏在car_id_detect.py中joblib.load()后加打印print(SVM model loaded:, hasattr(clf, classes_))若输出False说明模型加载失败检查路径是否含中文、空格或重下载svm.dat。5.4 现象识别结果中汉字全为乱码如 “B12345”但字母数字正常原因svmchinese.dat模型训练时用的标签是 UTF-8 字符串但 Windows 控制台默认 GBK 编码print()输出乱码。解决非致命问题乱码只影响控制台显示result/下图片和log.txt若有中文字正常若需控制台正常显示在car_id_detect.py开头加import sys import io sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8)5.5 现象demo.mp4识别时 CPU 占用过高帧率低于 1fps原因my_main_ui.py中视频处理采用cv2.VideoCapture().read() 全帧card_seg.py流程未做抽帧或 ROI 缓存。解决修改my_main_ui.py在视频循环中加入抽帧frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break frame_count 1 if frame_count % 5 ! 0: # 每5帧处理1帧 continue # 后续识别逻辑...或启用--fast-mode参数需自行在argparse中添加跳过部分形态学操作。6. 进阶技巧用config.js动态切换 HSV 阈值与 SVM 模型实现“一系统多场景”6.1config.js的真实作用它不是前端配置而是 Python 的 JSON 配置桥接器项目中config.js并非 Node.js 文件而是被my_main_ui.py用json.load()读取的纯配置。其结构决定了系统行为{ hsv_ranges: { blue: {h_min: 105, h_max: 130}, yellow: {h_min: 15, h_max: 35}, green: {h_min: 60, h_max: 85} }, svm_models: { chinese: svmchinese.dat, alphanumeric: svm.dat }, debug_mode: false }注意config.js是 JavaScript 语法但 Python 用json模块读取因此不支持注释、单引号、尾逗号。写错一个逗号json.load()直接抛JSONDecodeError。6.2 场景化配置实战为高速公路卡口与小区停车场准备两套config.js我曾为某跨平台系统部署此项目针对不同场景做了三套配置场景典型图像特征config.js关键修改效果提升高速公路卡口强逆光、车速快、蓝牌为主blue: {h_min: 112, h_max: 125}debug_mode: true识别率从 73% → 89%debug_mode输出中间图便于定位问题小区停车场夜间红外、黄牌工程车多yellow: {h_min: 20, h_max: 40},alphanumeric: svm_tiny.dat轻量模型单帧耗时从 1.2s → 0.6s满足实时告警新能源车试点绿牌占比 40%含“粤BD”双字母green: {h_min: 65, h_max: 80},chinese: svm_newenergy.dat新增 12 个新能源汉字解决“粤BD12345”中D被误判为汉字的问题# my_main_ui.py 片段动态加载配置关键逻辑 def load_config(): try: with open(config.js, r, encodingutf-8) as f: config json.load(f) return config except Exception as e: # 配置加载失败时回退到内置默认值防崩 print(fConfig load failed: {e}, using defaults) return { hsv_ranges: {blue: {h_min: 105, h_max: 130}}, svm_models: {chinese: svmchinese.dat}, debug_mode: False } # 后续在 card_seg.py 中直接使用 config[hsv_ranges][blue]这套机制让我无需改 Python 代码只需替换config.js和.dat模型就能完成场景迁移。从那以后我每次交付新项目都强制走一遍“改config.js→ 跑test_img/→ 截图存档”的流程确保配置与实测结果一一对应。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑