ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从定位到识别:OpenCV+SVM+百度AI构建车牌识别系统

从定位到识别:OpenCV+SVM+百度AI构建车牌识别系统 简介毕业设计《Python基于Opencv和SVM机器学习算法和百度AI平台车牌识别系统》完整项目包面向计算机相关专业学生及开发者可用于毕业设计、课程设计或项目初期演示。系统基于PycharmPython3.6与MySQL采用图像边缘和车牌颜色定位结合Opencv自带SVM模型训练字符识别并内置百度AI平台API作为兜底识别方案兼顾算法实现与工程落地。压缩包共171个文件约94.55MB包含Python源码、MySQL建库SQL、训练数据文件、图片素材、XML配置及演示视频其中大量jpg/png/jpeg图片用于样本与测试py文件为算法核心文档说明与使用指南便于快速上手。目前已有77人学习下载适合需要完整参考方案或希望在此基础上扩展功能的读者。1. 毕业设计里的车牌识别OpenCV、SVM、百度AI平台三样怎么分工毕设里最受欢迎的车牌识别项目十有八九是这个组合Python OpenCV SVM 百度AI平台。OpenCV负责从图片里把车牌找出来SVM负责认字符百度AI平台用云端OCR接口做兜底。跑通之后输入一张车辆照片输出一串车牌号本地能跑、演示视频能录答辩也方便讲清“自己动手做”的部分。很多人第一次看到这个标题以为必须上深度学习。其实车牌字符是印刷体字体单一、样本量大SVM加特征工程完全扛得住。真正让项目翻车的往往不是SVM而是前面的车牌定位和后面的接口对接。我自己带过的毕业设计里有超过一半的人卡在“定位不准”和“OpenCV版本报错”上。这篇按“选型→定位→训练→避坑→验证”的顺序把可复现代码和参数直接给出来。你按步骤走能把一个能跑的车牌识别系统落到本地你想调优也能从参数说明里找到方向。2. 技术选型与工作流SVM扛住本地识别百度AI平台只做兜底2.1 为什么用SVM而不是直接上CNN最近两年很多人一听说“识别”就想到深度学习实际做车牌识别项目时SVM反而更适合毕业设计。原因在于车牌字符是印刷体字体规整类别范围固定31个省简称、24个字母、10个数字没有手写体那种自由变形。这种情况下SVM在几百个样本上就能训练出很稳的模型而CNN往往需要上千张样本才敢说收敛还要考虑是否用GPU训练时间可能从几分钟膨胀到几小时。另一个实际好处是OpenCV内置了SVM模块不需要额外引入TensorFlow或PyTorch。毕设环境换到实验室电脑、带到演示现场只要装了Python环境和OpenCV就能跑。如果你用过sklearn就会发现OpenCV的SVM接口虽然老一点但训练流程基本一致。SVM还能直接输出样本到超平面的距离这个距离可以用来判断置信度。我一般在距离低于阈值时才把图片交给百度AI平台做二次识别而不是每张图都调云接口。这样既省API调用费用又能在本地环境里展示“自己的模型也能干活”。对比一下几个常见方案方案样本需求硬件依赖可解释性部署成本SVM几百个字符样本纯CPU可用高能画出决策边界低CNN几千到几万GPU可选但影响训练速度低黑匣子高百度AI零训练样本必须联网中等结果透明但模型不可见按次计费所以我的建议是本地用SVM做主识别云端用百度AI兜底而不是反过来。2.2 从车辆图片到车牌字符串系统只做六件事整个车牌识别系统的流程可以拆成六个固定步骤每一步都有明确的输入输出。先把流程立起来后面填代码才不会乱。读取图片从磁盘或摄像头帧中拿到BGR图像。图像预处理灰度化、去噪、边缘检测目的是突显车牌区域。车牌定位用边缘信息和形态学操作找出候选区域再按宽高比筛掉非车牌。字符分割在候选区域内做二值化用投影法把每个字符切出来。SVM字符识别把切出的字符归一化送进SVM模型得到标签序列。百度AI兜底当SVM置信度不足或结果格式非法时调用云端车牌识别接口输出最终结果。在代码层面第2到第4步是OpenCV的主场第5步是SVM的主场第6步是HTTP请求。很多人在第3步和第4步翻车因为OpenCV的预处理参数不是通用的光照一变同一个参数可能把所有候选区域全滤掉或者全保留。后面第3章会专门讲参数怎么调。下面是流程和对应依赖的一个小结方便你签任务时对照。步骤核心工具常见输入输出图片读取OpenCV车辆图片或视频帧BGR ndarray预处理OpenCVBGR图边缘图、二值图车牌定位OpenCV边缘图车牌矩形坐标字符分割OpenCV车牌区域图字符图片列表字符识别OpenCV SVM字符特征字符串云端兜底百度AI接口原始车牌图车牌字符串2.3 双识别策略SVM置信度不够时再请百度AI平台百度AI平台的接入方式并不复杂但用法很讲究。如果直接把所有图片都发给云端毕设演示时会遇到两个问题第一断网或接口限流会导致整个系统不可用第二评委可能会追问“你自己做的模型在哪”到时候只能指着几个调用代码场面会很难看。所以我把识别设计成级联SVM先跑给每个字符返回一个距离值距离越小说明样本越靠近分类边界越不可靠。如果整张牌的字符平均距离低于阈值或者识别结果里出现了车牌中不该出现的字母“I”和“O”就触发百度AI。这样做还有一个好处就是本地识别率能单独统计云接口只负责少数疑难样本费用也控制在很低的水平。百度AI的接入有两种常见方式一种是直接用baidu-aip这个SDK包另一种是用requests请求REST接口。毕业设计我更喜欢用requests因为依赖少评委看代码也更直观。核心流程是先拿API Key和Secret Key换取access_token然后POST图片数据到车牌识别接口。import requests import base64 def get_access_token(api_key, secret_key): url https://aip.baidubce.com/oauth/2.0/token?grant_typeclient_credentialsclient_id{}client_secret{} resp requests.post(url.format(api_key, secret_key)) return resp.json().get(access_token) def baidu_plate_ocr(image_path, access_token): with open(image_path, rb) as f: img_base64 base64.b64encode(f.read()) url https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate?access_token{} resp requests.post(url.format(access_token), data{image: img_base64}) return resp.json()这里的access_token默认有效期是30天你不需要每次识别都重新获取只要在系统启动时获取一次并缓存到内存里即可。等第5章讲避坑时我会再提一个常见的token失效问题。3. 车牌定位与字符分割OpenCV预处理关键参数和可复现代码3.1 从灰度图到边缘图高斯模糊与Canny的低阈值高阈值怎么设车牌定位的前提是把图像里的“强边缘”找出来。车牌字符和底色之间有明显的灰度跳变车身边的线条同样有所以我们要用边缘检测把候选区域全部圈出来再用轮廓几何特征去筛掉非车牌区域。常见做法是先把BGR图转成灰度图再做高斯模糊最后用Canny边缘检测。高斯模糊里最常踩的坑是ksize设置太大比如(7,7)会把车牌字符的细节直接抹掉导致边缘图里车牌区域变成一片空白。我自己一般用(3,3)既去了噪点又不伤边缘。Canny的阈值也是经验活低阈值设太低会出现一堆纹理噪点高阈值设太高会把车牌的边界线也过滤掉。一个比较稳的起点是低阈值50、高阈值200。如果检测不到车牌把高阈值降到150试试如果整个画面全是噪点把低阈值提到80。下面这段代码就是一个能直接运行的预处理骨架。import cv2 import numpy as np def preprocess(image_path): # 注意OpenCV的imread不支持中文路径后面第5章会专门讲 img cv2.imread(image_path) if img is None: raise ValueError(图片读取失败检查路径或文件名是否含中文) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯滤波ksize必须为正奇数(3,3)是兼顾去噪和保边的选择 blurred cv2.GaussianBlur(gray, (3, 3), 0) # Canny边缘检测低阈值50高阈值200 edged cv2.Canny(blurred, 50, 200) # 形态学闭运算把车牌字符的离散边缘连成一个整体块 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) closed cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel) return img, gray, edged, closed这里闭运算的kernel也很有讲究。核太大会把车头大灯、保险杠跟车牌连成同一个连通域导致后面轮廓筛选时整个车头都被框进来核太小又起不到连接字符边缘的作用。我一般先从(5,5)起步如果定位结果里总是出现大块非车牌区域就换成(3,3)。3.2 轮廓筛选宽高比、面积、矩形度三个条件卡掉大部分干扰拿到闭运算后的边缘图下一步是找轮廓。OpenCV 4.x的findContours返回两个值旧教程里那种三个返回值的写法是OpenCV 2.x时代的直接照着抄会报错。轮廓找到后我用三个条件筛选候选车牌区域。第一个条件是面积。车牌在整张图中的占比不会太小如果轮廓面积小于某个阈值比如2000像素直接丢掉。当然这个阈值跟图片分辨率有关如果是1920x1080的图2000像素很小基本只能滤掉噪点。第二个条件是宽高比普通蓝牌长宽比大约是3.14新能源牌略扁一点但也在2.2到4.5之间。第三个条件是矩形度也就是轮廓面积和它最小外接矩形面积的比值车牌是规则矩形这个值通常在0.7以上而车身边缘往往是弯弯曲曲的矩形度较低。def find_plate_candidates(closed): # OpenCV 4.x的返回值contours, hierarchy contours, hierarchy cv2.findContours( closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) candidates [] for contour in contours: x, y, w, h cv2.boundingRect(contour) area w * h # 面积过滤太小不可能是车牌 if area 2000: continue # 宽高比过滤普通蓝牌约440:140新能源牌更扁 aspect w / h if not (2.0 aspect 4.8): continue # 矩形度过滤轮廓面积 / 外接矩形面积 rect_area area contour_area cv2.contourArea(contour) extent contour_area / rect_area if extent 0.5: continue candidates.append((x, y, w, h)) # 如果候选区域不止一个优先选面积最大的那个 if not candidates: return None return max(candidates, keylambda rect: rect[2] * rect[3])这段代码的筛选逻辑并不复杂但很多人会漏掉最后的“面积最大优先”这一步。车辆图片里可能同时出现车灯、标志、车牌多个候选面积最大的往往就是车牌因为车牌字符密集且闭运算效果好。如果当前环境经常出现道闸杆、广告牌等干扰可以把矩形度阈值提高到0.7。3.3 字符分割OTSU二值化加垂直投影间隔符要单独处理候选框出来后下一步是把这个矩形区域里的字符一个个切出来。首先把车牌区域转成灰度图然后用OTSU找阈值做二值化。为什么不用固定阈值因为不同照片的曝光差异很大固定阈值要么让字符断掉要么让背景全变成白点。OTSU会根据当前灰度分布自动选阈值在大多数光照下表现稳定。车牌字符是白字底色是蓝色或黄色所以二值化时要取反让字符变成白色前景。然后对二值图做垂直投影统计每一列有多少个白色像素点。连续有白色像素的列区间就是一个候选字符。这里有个典型的坑车牌中间的间隔符“·”也会被投影成一个窄区间宽度明显小于字符直接按宽度过滤掉。def segment_chars(plate_img): # plate_img是裁剪出的车牌BGR图 gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # OTSU自动阈值 反二值化字符为白色背景为黑色 _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) # 垂直投影统计每列白色像素数量 col_sum np.sum(thresh, axis0) chars [] in_char False start 0 for i, value in enumerate(col_sum): if value 0 and not in_char: in_char True start i elif value 0 and in_char: in_char False end i width end - start # 过滤掉太窄的噪点和间隔符 if width 5: chars.append((start, end)) # 按宽度排序或直接按从左到右顺序保留 # 这里用过滤后的平均宽度来剔除间隔符 if len(chars) 7: # 如果切出来的字符数少于7多半是间隔符没除掉 widths [end - start for start, end in chars] med_w np.median(widths) chars [(s, e) for s, e in chars if med_w * 0.3 (e - s) med_w * 1.5] results [] for start, end in chars: # 每一列区间再用行投影裁剪上下边缘 col_region thresh[:, start:end] row_sum np.sum(col_region, axis1) row_indices np.where(row_sum 0)[0] if len(row_indices) 0: continue top, bottom row_indices[0], row_indices[-1] char_img thresh[top:bottom 1, start:end] # 统一缩放到SVM输入尺寸这里用32x40 char_img cv2.resize(char_img, (32, 40), interpolationcv2.INTER_AREA) results.append(char_img) return results字符分割完成后你最好把每一张切出的字符图保存到本地看一眼。我见过最多的情况是汉字被切成两半或者“0”和“Q”的投影形状高度相似。如果汉字被切开可以把闭运算kernel在水平方向拉长比如(3,3)改成(5,3)让笔画重新连起来如果多个字符粘在一起则说明二值化后字符间有粘连需要检查车牌区域是否倾斜倾斜时可以先做透视校正。4. 训练SVM车牌字符模型样本、核函数、C与gamma的调参经验4.1 样本组织与特征提取二值像素特征还是HOG特征SVM训练的第一步是解决样本和特征。常见做法是手工从车牌图上切字符每个字符保存成一个文件夹文件夹名就是标签。我一般每个字符至少准备50张正样本覆盖不同光照、不同倾斜、不同清晰度。如果样本太少后面验证时某一类字符的识别率会明显拉低整体成绩。特征提取有两种常用方案。第一种是把字符图resize到固定尺寸直接展平成像素向量。优点是实现简单代码少缺点是对笔画粗细敏感同一个“京”字在不同车牌上如果粗细不一样像素向量差异会很大。第二种是用HOG特征OpenCV的HOGDescriptor可以提取梯度方向直方图对边缘形状更鲁棒。毕设里我推荐直接用像素特征因为车牌字符本身是印刷体像素级信息已经够用而且HOG特征维度高训练反而慢。下面用sklearn做特征矩阵构建因为sklearn的交叉验证接口比OpenCV方便。import os import cv2 import numpy as np from sklearn.svm import SVC from sklearn.model_selection import cross_val_score def load_dataset(dataset_dir): X, y [], [] for label in os.listdir(dataset_dir): label_dir os.path.join(dataset_dir, label) if not os.path.isdir(label_dir): continue for fname in os.listdir(label_dir): path os.path.join(label_dir, fname) img cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: continue img cv2.resize(img, (32, 40)) # 展平成1024维像素向量 X.append(img.flatten() / 255.0) y.append(label) return np.array(X), np.array(y) X_train, y_train load_dataset(dataset/train)这里有一点必须注意sklearn的SVC要求标签是数值所以汉字、字母都要先做映射。比如“京”→0“A”→1“0”→10。映射表在训练时保存下来预测时要用同一个映射否则全乱。4.2 核函数、C和gamma怎么调先记住一组稳定值最近做optdigits手写数字分类中SVM核函数与参数的影响研究的人很多车牌字符识别跟它是同一个套路只是车牌字符比手写数字规整得多。SVM默认的RBF核在大多数字符样本上表现都很好但C和gamma要一起调。C是错误惩罚项C越大模型对训练样本的错分惩罚越重容易过拟合C越小模型越平滑可能欠拟合。gamma控制RBF核的半径gamma越大决策边界越复杂gamma越小边界越平滑。对车牌字符这种规整数据常见的稳定范围是C在1到100之间gamma在0.01到1之间。我的起点是C10gamma0.1然后在这个起点附近做网格搜索。你可以用小范围的交叉验证先跑一遍别一上来就全量数据。from sklearn.model_selection import GridSearchCV param_grid { C: [1, 10, 100], gamma: [0.01, 0.1, 1], } svc SVC(kernelrbf) # verboseFalse因为毕设演示时会有大量日志容易吓到评委 grid GridSearchCV(svc, param_grid, cv5, scoringaccuracy) grid.fit(X_train, y_train) print(best params:, grid.best_params_) print(best score:, grid.best_score_)这里有个细节sklearn里的gamma默认是“scale”自动按特征维度计算但OpenCV里的gamma是显式传入的。所以你在sklearn里搜到的最优gamma值在OpenCV里要原样写不能省略。网格搜索跑完如果最佳分数在97%以下先别急着调参数回去检查样本有没有标错。4.3 用OpenCV训练SVM并接入识别主流程sklearn只负责离线调参真正部署时要用OpenCV的SVM加载因为识别主流程里不想依赖sklearn减少环境问题。OpenCV的SVM训练需要准备float32类型的输入矩阵标签要是整数数组类别从0开始。思路是用sklearn网格搜索拿到最优C和gamma然后用同样的参数创建OpenCV SVM用完整训练集再训练一次保存成.dat文件。这里不直接转换sklearn模型是因为两者内部实现细节有差异重新训练反而更稳。def train_opencv_svm(X_train, y_train, C10, gamma0.1): # OpenCV要求float32输入 X np.array(X_train, dtypenp.float32) y np.array(y_train, dtypenp.int32) svm cv2.ml.SVM_create() svm.setType(cv2.ml.SVM_C_SVC) svm.setKernel(cv2.ml.SVM_RBF) svm.setC(C) svm.setGamma(gamma) # ROW_SAMPLE表示每行是一个样本 svm.train(X, cv2.ml.ROW_SAMPLE, y) svm.save(svm_chars.dat) return svm svm_model train_opencv_svm(X_train, y_train, C10, gamma0.1)在预测环节字符图切出来后先做和训练时一样的预处理灰度图、二值化、resize到32x40、展平、除以255。然后调用predict。def svm_predict_char(svm_model, char_img): feature char_img.reshape(1, -1).astype(np.float32) / 255.0 _, result svm_model.predict(feature) label_id int(result[0][0]) # 通过映射表把id还原成字符 return id_to_char[label_id]这里最容易翻车的是数据类型。如果feature不是float32OpenCV会直接报错type mismatch。另一个坑是predict的结果是二维数组一定要先取result[0][0]再转int。训练集和测试集要严格分开。很多毕设代码里训练时把全部样本都用上评估时也跑同一批样本准确率当然好看但到了演示现场换一张没见过的图就露馅。我一般从每个字符的样本里随机留出20%作为测试集在sklearn里跑交叉验证最后才用全部样本训练OpenCV的模型。5. 避坑与排查OpenCV版本、SVM误判、百度AI接口常见的四个坑5.1 import cv2直接报ModuleNotFoundError但OpenCV明明装过很多同学在Python里执行import cv2时终端直接返回ModuleNotFoundError: No module named cv2。第一反应是重新pip install opencv-python结果装完还是报错。这个坑十有八九是IDE的解释器和你命令行里的pip不在同一个环境。还有一个经典现象是Pycharm里装过opencv-python但打开终端运行时又提示找不到模块。原因是你Pycharm用的虚拟环境不是当前终端默认环境pip install安装的位置和代码运行位置不一致。解决这个问题的步骤很直接。先在命令行执行where python或which python确认当前Python路径。再用这个Python执行python -m pip install opencv-python。装完之后在同一个命令行里输入python然后import cv2验证。不要在Pycharm的终端里反复试因为它会自动激活项目的虚拟环境很容易绕晕。如果是安装时从豆瓣源或阿里源下载注意源必须是最新的有些镜像站同步滞后会把老版本的包标记成最新导致和你代码不兼容。5.2 车牌定位把整个车头框出来轮廓筛选条件失效我见过最惨的结果是车牌定位的绿色矩形框圈住了大半个车头宽高比明明是3:1但面积巨大。原因通常是闭运算是用(7,7)的大核把车灯、中网和车牌全部合并成一个连通域导致最后筛选出的轮廓是车头整体。解决方法是做两级处理。第一级把闭运算的kernel从(7,7)缩小到(3,3)或(5,3)减少横向粘连第二级在轮廓筛选时把矩形度阈值从0.5提高到0.7。车牌是规则矩形矩形度非常高而车头轮廓通常有凹陷和凸起矩形度会低于0.6。如果还是框大区域再加一个限制条件车牌候选的宽高比精确范围可以缩到2.5到4.5排除掉保险杠那种长条形。另外很多车辆照片里车牌区域和车身颜色接近比如白车配白牌Canny边缘检测出来的车牌边界可能会断。这时可以先用HSV颜色空间找一个固定颜色范围比如蓝色牌照的蓝色区域做一次颜色掩膜再和边缘图做与运算。颜色辅助定位是最能救命的一招。5.3 SVM把“0”认成“O”把“粤”认成“川”字符识别结果错得离谱不是模型不准而是类别定义的问题。车牌字符里根本没有字母I和O但在训练时如果样本文件夹里包含了别的字体生成的“O”SVM就学出了一个O类别预测时自然会把0归到O那边。解决办法很简单训练时直接剔除I和O两个类别让模型只在合法类别里做决策。如果已经训练完了可以在预测结果后加一个合法性过滤出现O和I等于非法直接触发百度AI兜底。这个处理在毕设演示时特别有用能明显减少无脑错误。还有一类误判是省份汉字之间搞混比如“粤”和“川”轮廓相似。原因是样本数太少或者字符分割时把左边偏旁切掉了。如果“粤”只剩右边的一半SVM看到的就是一个不完整的字符。我一般遇到这种情况都会回去看字符分割结果把汉字单独用更宽的轮廓保留区间不要用统一宽度切全部字符。汉字宽度通常比数字大一些可以单独设置宽度阈值。5.4 百度AI接口报“Access token invalid”但明明刚申请成功接入百度AI最常出现的接口错误是access_token无效。很多人会在每次识别时都重新请求token但系统时间如果和服务器时间偏差大或者你本地缓存了过期token就会出现这个错误。更隐蔽的是requests返回JSON里没有走success流程而是直接抛了HTTP异常。我习惯的做法是在系统启动时获取token放到全局变量里然后用一个函数判断是否过期。token有效期是30天不需要每次刷新。如果确实拿到了token还是报错先检查一下是不是把token拼进URL时带了多余空格。requests会自动处理URL编码但不会帮你去掉末尾换行符复制粘贴时容易带进去。import time class BaiduPlateClient: def __init__(self, api_key, secret_key): self.api_key api_key self.secret_key secret_key self.access_token None self.expire_time 0 def ensure_token(self): # token有效期30天提前一天刷新即可 if self.access_token and self.expire_time time.time() 86400: return self.access_token url https://aip.baidubce.com/oauth/2.0/token?grant_typeclient_credentialsclient_id{}client_secret{} resp requests.post(url.format(self.api_key, self.secret_key)) data resp.json() self.access_token data[access_token] self.expire_time time.time() data[expires_in] return self.access_token这段代码把token缓存和过期判断封装好了避免每次识别都重新请求。还有个细节百度AI的返回结果里可能不直接包含车牌字符串而是嵌套在result数组里你用resp.json()之后要多打印一层看看结构不要想当然去data[words_result][0][words]里取不同接口字段名不一样。6. 验证与进阶把识别准确率测到90%以上的参数固化技巧6.1 用带标签的测试集量化识别率别靠肉眼数很多人演示时觉得“看到一辆车就能识别出来”就够了但到答辩阶段评委一定会问准确率。你提前准备一个测试集才是正路把车辆图片放进一个文件夹每张图片的文件名直接写成真实车牌号比如“京A12345.jpg”。识别脚本读取文件后自动把预测结果和文件名对比统计字符级准确率。import cv2 import os def evaluate(test_dir): total_chars 0 correct_chars 0 correct_plates 0 total_plates 0 for fname in os.listdir(test_dir): if not fname.lower().endswith(.jpg): continue # 文件名里去掉扩展名就是真实车牌 true_plate os.path.splitext(fname)[0] # 你的主识别流程定位-分割-SVM-百度兜底 pred_plate predict_plate(os.path.join(test_dir, fname)) total_plates 1 if pred_plate true_plate: correct_plates 1 # 字符级对比更严格 for c1, c2 in zip(pred_plate, true_plate): total_chars 1 if c1 c2: correct_chars 1 print(f整牌准确率: {correct_plates / total_plates:.2%}) print(f字符准确率: {correct_chars / total_chars:.2%})这个脚本跑完后如果整牌准确率不到80%我建议先看哪些字符错得最多把错误分布打出来。常见情况是“冀”字样本不够或者数字“0”和字母“D”混淆。补样本比调SVM参数更有效。6.2 让参数固化到配置文件下次直接复现SVM参数、轮廓筛选参数、百度AI的API Key都不能写死在代码里。我吃过一次亏答辩前临时改代码不小心把一个路径写错整个识别流程崩了。从那以后所有关键参数都放到一个JSON文件里代码运行时从配置读取换环境改配置比改代码安全得多。{ preprocess: { gaussian_kernel: 3, canny_low: 50, canny_high: 200, close_kernel: [3, 3] }, svm: { model_path: models/svm_chars.dat, C: 10, gamma: 0.1, kernel: RBF, char_size: [32, 40] }, baidu: { api_key: your_api_key, secret_key: your_secret_key, fallback: true } }参数固化之后每次跑实验都留一份当时的配置文件副本记录测试集和准确率。这招能让你在复盘时知道哪个参数组合得到了最好结果而不是靠“我记得当时随便改了改”。我也是被网格搜索覆盖过的参数搞到抓狂后才养成这个习惯。整个系统跑通后我一般还会加一步把识别结果画到原始图上保存成视频用于录制演示视频。OpenCV的VideoWriter可以直接把一帧帧识别结果写进去。视频里能看到车牌定位框和识别字符串比静态图更有说服力。录视频时注意帧率别太高15帧就够文件也不会太大方便放进毕设文档的展示素材里。这套组合的实际效果在正常光照下整牌识别率能做到90%以上遇到极端反光或者车牌严重倾斜时百度AI兜底还能救回来。如果你正在做这个方向先把第3章的定位代码跑通再谈参数调优每一步的坑都比想象中多但也都在可控范围内。希望帮到你。本文还有配套的精品资源点击获取
返回列表