
简介一套基于Python与OpenCV的数字图像处理课程作业车牌识别完整项目资源面向希望入门图像处理、完成课程设计或毕业设计的学习者。项目使用Python3结合OpenCV实现车牌定位与字符识别通过tkinter搭建桌面交互界面整体结构清晰适合对照源码理解图像灰度化、边缘检测、形态学处理等核心环节。资源包共19个文件压缩包约4.74MB包含5个Python源文件、多张车牌样本与处理过程图片、SVM识别数据、答辩PPT以及说明文档覆盖源码、数据、演示与答辩展示等完整交付内容。其中tmp目录按阶段保存灰度图、边缘图等中间结果chepai目录提供不同场景测试车牌图片方便读者逐步调试、对比效果并替换验证。已有219人学习下载可作为课程设计或初期项目立项参考在自行调试和功能扩展中提升实战能力。1. 车牌识别为什么成了数字图像处理课程的“试金石”从 Python 和 OpenCV 说起车牌识别这个课程作业题目看起来是人工智能骨子里全是数字图像处理的基础操作灰度化、边缘检测、形态学、投影分割、模板匹配。用 Python 配合 OpenCV 实现代码量不大但每一步都有肉眼可见的中间结果非常适合用来讲原理、撑答辩。反直觉的是这套流程真正的难点不在“识别”而在“定位”——一旦车牌在画面里没被框准后面所有字符分割和模板匹配都会跟着翻车。我见过太多课设现场不是被算法难住而是被环境卡死python 安装没问题却 import 不到 cv2或者直接弹出 cv2.error: OpenCV(4.4.0)... 的老报错。这篇文章就按“定位→分割→识别→答辩”的落地路径写从预处理参数讲到答辩 PPT 的讲法适合正在赶数字图像处理课程设计、想快速跑通一套车牌识别流程并且能在答辩时把“为什么这么设参数”说清楚的人。代码基于 OpenCV 4.x 和 Python 3面向蓝底白字的国内车牌场景。2. 车牌定位把预处理管线拆成五步参数从“玄学”变成可解释2.1 为什么先做灰度、滤波和边缘预处理顺序不是拍脑袋车牌定位的前提是把车牌从复杂背景里“框”出来。蓝底白字在 RGB 空间里颜色特征明显但夜间、反光、树荫会让颜色漂移所以不少课程作业先做边缘检测再用形态学把边缘连成连通域。常见做法是读图 → 灰度化 → 高斯模糊 → Sobel 边缘 → 二值化 → 闭运算 → 轮廓筛选。这个顺序不是拍脑袋定的。灰度化把三个通道压成一个通道计算量直接降到三分之一高斯模糊去除传感器噪声避免把路面纹理当成强边缘Sobel 算子在 x 方向上对车牌字符的竖边响应最强因为字符密集会产生大量竖直边缘二值化把边缘响应变成黑白图才能喂给形态学操作闭运算先膨胀后腐蚀能把断开的字符边缘“焊”成一大块最后用轮廓的面积、宽高比和倾斜角度锁定车牌。每一步都在为下一步减少干扰答辩时要是被问“为什么用 Sobel 不用 Canny”回答就是Sobel 对竖直边缘更敏感参数只有一个核大小后续闭运算更容易把字符边缘连成整体。import cv2 import numpy as np def preprocess_for_plate(img): # 1. 灰度化保留亮度丢掉颜色所有后续计算都在单通道图上完成 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 高斯模糊核越大越平滑但也会抹掉小字边缘5x5 对普通街拍够用 blur cv2.GaussianBlur(gray, (5, 5), 0) # 3. 只算 x 方向的 Sobel 边缘车牌字符竖边多dx1,dy0 更聚焦 sobelx cv2.Sobel(blur, cv2.CV_64F, 1, 0, ksize3) # 4. 取绝对值再转 uint8否则负梯度会被直接截断成黑色 sobel_abs np.uint8(np.absolute(sobelx)) # 5. 自适应阈值局部窗口单独算阈值比全局固定 127 更扛光照不均 binary cv2.adaptiveThreshold(sobel_abs, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, -10) # 6. 闭运算先膨胀后腐蚀把邻近字符边缘的缝隙填平 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return closed代码逻辑说明Sobel 算子的输出是带正负的梯度所以第 4 步必须取绝对值否则负梯度会被当成黑像素丢掉第 5 步自适应阈值里 blockSize15 表示每个 15x15 局部窗口单独算平均灰度C-10 表示用“均值减 10”当阈值C 的绝对值越大保留的边缘越少背景越干净。第 6 步闭运算核设为宽 17、高 5是因为字符边缘之间的水平间隙通常小于 17 像素核太高则会把上下两排字符也焊在一起导致后续轮廓变成一个巨大的整块。这套参数在 640x480 到 1920x1080 的常用图片里都能撑住但换摄像头就必须重调这就是图像处理课设里最自然试错的地方。2.2 轮廓筛选用面积、宽高比和倾斜角把候选框逼进“车牌比例”闭运算之后的图里不只有车牌还有保险杠、车灯、路面标线留下的连通域。不能把所有轮廓都当成车牌必须加几何约束。标准车牌的宽高比约 3.14拍摄时存在透视畸变所以筛选范围放宽到 2.5 到 4.5。角度上车牌在画面里通常接近水平或小角度倾斜超过 30 度的候选框大概率是误检。def find_plate_candidates(closed, original): # 提取外部轮廓OpenCV 4.x 只返回 contours 和 hierarchy 两个值 contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: # 最小外接矩形能同时给出中心、宽高和旋转角贴合倾斜车牌 rect cv2.minAreaRect(cnt) (x, y), (w, h), angle rect # 防御性处理minAreaRect 的宽高顺序不固定先统一成 宽 高 if w h: w, h h, w angle - 90 area w * h ratio w / h # 车牌在 640x480 图中通常占 20000 到 100000 像素 if 10000 area 150000 and ratio 2.5 and ratio 4.5: # 长边和短边范围同时约束排除远处小目标和近处大广告牌 if 80 w 500 and 30 h 150: candidates.append((rect, cnt)) candidates.sort(keylambda r: r[0][1][0] * r[0][1][1], reverseTrue) return candidates这段代码里最容易被忽视的是 minAreaRect 返回的宽高顺序和角度范围。同一个矩形OpenCV 可能返回 (width150,height50, angle0)也可能返回 (width50,height150, angle89)所以必须做“如果 wh 就交换宽高”的处理否则比例筛选全乱。面积阈值 10000 到 150000 是按 720p 画面中车牌占 1/10 到 1/30 估算的换成 2K 分辨率时上限要同步放大。筛选后按面积降序排列取第一个作为最可能的车牌区域。如果始终框不中先看 closed 图里车牌边缘有没有连成整块——没连上就是闭运算核太小连成一坨就是核太大这是定位阶段最有效的排查思路。2.3 从候选框到车牌图透视矫正和裁剪的参数边界拿到 minAreaRect 之后不能直接按矩形边界裁剪。如果车牌在画面里是倾斜的直接裁出来的图里字符也是歪的后面的投影分割会全错。常见做法是把旋转矩形的四个角点取出来按固定尺寸做透视变换把车牌摆正。这里也要提一句有人想用 OpenCV 的 solvePnP 做三维姿态矫正对课程作业完全没必要车牌是平面目标单应变换就够solvePnP 还需要棋盘格标定纯属给自己加戏。def crop_plate(original, rect): # 取旋转矩形的四个角点boxPoints 保证逆时针顺序 box cv2.boxPoints(rect).astype(np.float32) # 目标尺寸 240x76宽高比约 3.15比标准车牌略大防止边缘掉像素 dst np.array([[0, 0], [240, 0], [240, 76], [0, 76]], dtypenp.float32) # 计算单应矩阵把 box 映射到 dst M cv2.getPerspectiveTransform(box, dst) warped cv2.warpPerspective(original, M, (240, 76)) return warped透视变换之后车牌区域被摆成水平且尺寸统一这给后面的字符分割省了很大麻烦。目标尺寸 240x76 不是随便定的240/76 约等于 3.15略大于 3.14 的标称比例是怕变换后边缘少了几列像素。实际使用中先把 box 转成 float32再传给 getPerspectiveTransformboxPoints 的顺序一旦错位图像就会被扭成对角的所以建议打印 box 验证四个点分别对应左上、右上、右下、左下。到这里预处理和定位的链路已经完整下一步就是吃这块“被掰正”的车牌图。2.4 不同分辨率和场景下的参数速查先调哪一个有顺序很多同学拿到参数就问“为什么是 17 不是 15”其实这个值只对特定分辨率有效。根据我的经验可以按输入宽度先给一组起点参数输入宽度高斯核Sobel ksize闭运算核面积阈值范围640(5,5)3(17,5)10000 - 1200001280(7,7)3(23,7)50000 - 4000001920(9,9)5(31,9)120000 - 1000000调参顺序有讲究先从闭运算核开始让车牌边缘在 closed 图里连成一块再调轮廓筛选的面积阈值让候选框数量降到 1 到 3 个最后才动高斯核和 Sobel 核。这三个层面互相关联如果一次改三个变量出了问题根本不知道是哪一步引起的。我一般会在代码里加一个滑杆窗口用 cv2.createTrackbar 实时看 closed 图遇到新场景先把参数拖到能看到完整车牌轮廓再回去跑轮廓筛选。3. 字符分割与识别从垂直投影到模板匹配每一步都要看中间结果3.1 垂直投影分割为什么先“填坑”再“切”车牌区域摆正后字符是水平排列的每一列只要存在字符像素垂直投影值就会大于 0字符间空隙的投影值接近 0投影曲线会形成一波一波的峰。分割就变成“找波谷”的问题。但实际二值化后的车牌图字符内部会有空洞、边框铆钉会干扰、反光会让笔画断裂直接投影会切出残缺字符。所以先对裁剪后的车牌图做一次“竖直方向膨胀”把同一个字符内断开的笔画粘起来再投影。def segment_chars(plate_gray): # 自适应阈值反色白字变白底黑这里用 BINARY_INV让字符为白背景为黑 binary cv2.adaptiveThreshold(plate_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 15, -5) # 竖直膨胀核高 7 像素同字符上下断裂被接上字符间水平间隙不受影响 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (1, 7)) dilate cv2.dilate(binary, kernel, iterations1) # 垂直投影统计每一列白色像素总数 h, w dilate.shape col_sum np.sum(dilate 255, axis0) # 遍历每一列把连续非零段记录为一个字符候选区 in_char False chars [] for i in range(w): if col_sum[i] 2 and not in_char: start i in_char True elif col_sum[i] 2 and in_char: end i # 宽度小于 5 的段大概率是噪声比如边框或铆钉 if end - start 5: chars.append((start, end)) in_char False return binary, chars这里的两个关键参数是膨胀核 (1,7) 和投影阈值“2”。核高 7 允许字符内上下 7 像素的断裂被接上汉字比如“京”“沪”笔画密集太小接不上太大会把相邻字符也接到一块投影阈值 2 表示该列至少有 2 个白色像素才认为属于字符用来过滤单点椒盐噪声。分割完务必立刻把 chars 画到图上看每一段是否正好对应一个字符。最常见的错误是“京”被切成两半因为汉字左右结构内部空隙大如果相邻段间距特别小就把它们合并成一个段。这一步的结果好坏直接决定模板匹配的输入是否干净。3.2 模板匹配识别别急着上神经网络先看匹配方法怎么选字符分割得到 7 个区域后每个区域都要归一化成统一尺寸再和模板匹配。OpenCV 的 matchTemplate 提供多种匹配方式课程作业里我推荐用 TM_CCOEFF_NORMED——归一化相关系数输出范围在 -1 到 1 之间1 表示完全一致受光照和对比度影响比平方差法小。模板库可以自己生成也可以从真实车牌裁剪每类字符多存几个样例。def recognize_char(char_binary, templates): # 把分割得到的字符区域缩放到模板同尺寸 40x80 char_resized cv2.resize(char_binary, (40, 80), interpolationcv2.INTER_LINEAR) # 归一化到 0-255让灰度分布一致避免相关系数被整体亮度带偏 char_norm cv2.normalize(char_resized, None, 0, 255, cv2.NORM_MINMAX) best_score -1 best_label for label, temp in templates.items(): # 模板和待匹配图必须同尺寸否则结果完全不可信 if temp.shape ! char_norm.shape: temp cv2.resize(temp, (40, 80)) # 相关系数法数值越大越相似 res cv2.matchTemplate(char_norm, temp, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(res) if max_val best_score: best_score max_val best_label label return best_label, best_score逻辑说明matchTemplate 在等尺寸图像上得到的是一个 1x1 矩阵minMaxLoc 取到唯一位置的最大值作为该模板的得分。经验阈值是得分大于 0.6 才接受低于 0.4 标记为“未识别”不要硬猜。模板匹配天然对字体敏感车牌专用字体和系统自带字体差异很大如果你拿宋体生成模板“京”字的骨架完全不同得分会很低。解决办法是收集同一字体下的真实字符做模板每个字符 3 到 5 个样例就够再多也只是重复。3.3 识别率为什么上不去数据预处理比算法更重要很多同学把识别结果不好归咎于分割或匹配算法实际上问题多半出在字符图像本身。比如模板是白底黑字待识别图是黑底白字极性反了相关系数会变成负的再比如字符没有居中匹配时根本对不齐。与其纠结换模板匹配算法不如花时间把每个字符区域的上下边界精确裁剪在字符段内部再做一次水平投影去掉上下残留的白边让字符主体占满归一化图像。这一步对识别率的影响比换成 ddddocr 或 Tesseract 还明显。如果时间允许可以跑一组对比数据同一套测试图用裸模板匹配和自己做字符切割后再匹配识别率可能从 50% 涨到 70%这个数据放进答辩 PPT 很有说服力。3.4 汉字识别为什么最难笔画密度与连通域特征的补充车牌里的汉字“京”“沪”“粤”和数字字母在结构上完全不同。数字 0-9 和字母 A-Z 大多笔画简单、连通域少投影分割容易切准汉字笔画密集且常有左右或上下结构内部空隙会在投影里形成第二个峰导致切碎。我一般会加一道“字符宽度校验”分割完成后计算七个字符段的平均宽度如果某个段的宽度小于平均宽度的一半就检查它左右两侧的间隙若间隙小于 3 像素则与相邻段合并。这道规则能救回一半的“京”字。另一个可选特征是字符内部孔洞个数“0”和“A”都有孔汉字“京”没有这个特征可以作为模板匹配之外的辅助投票让最终结果更稳。4. 课程作业避坑OpenCV 环境、findContours 与投影粘连的三类典型翻车4.1 python 安装后 import 不到 cv2环境路径与版本不一致现象python 装了好几次pip show opencv-python 也能看到包但运行 import cv2 直接报 ModuleNotFoundError: No module named cv2或者报 cv2.error: OpenCV(4.4.0) C:\Users... 这类来自预编译包的底层错误。原因绝大多数情况不是包没装而是电脑里有多个 python 环境。有的同学用 Anaconda又装官网 python还开了虚拟环境pip 命令指向的环境和运行脚本的环境不是同一个。少部分是 OpenCV 和 numpy 版本冲突老预编译包依赖旧 numpy后来手动升级 numpy 导致二进制接口不匹配。解决不要用系统全局 python 反复装包。先执行 which python 和 which pip 确认当前环境在同一个环境里运行 pip install opencv-python opencv-contrib-python numpy最后用 python -c import cv2; print(cv2.version) 验证。还报错就走卸载重装pip uninstall opencv-python -y 后重装。课程作业不用追最新版OpenCV 4.5 或 4.6 完全够用。4.2 findContours 返回值在 OpenCV 3/4 里不一致解包直接翻车现象同样的代码在同学电脑上跑通在自己电脑上报 ValueError: not enough values to unpack (expected 3, got 2)。原因OpenCV 3 的 findContours 返回 (image, contours, hierarchy) 三个值OpenCV 4 返回 (contours, hierarchy) 两个值。网上很多老教程写的是三个值放到 OpenCV 4 就会解包失败。解决统一按 OpenCV 4 的写法接收两个值也别依赖返回值里的 image。如果后面需要原图在读图时就先 copy。兼容写法contours, hierarchy cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)同时注意输入必须是 8 位单通道二值图不能直接喂彩色图否则即使不报错轮廓也会退化成外框一圈。这个坑我帮学弟调代码时见得太多了十有八九是把 findContours 放在了二值化之前。4.3 闭运算把字符粘连成一块核大小是定位和分割的分水岭现象定位阶段闭运算后车牌“京A12345”七个字符被连成一个大白块面积和宽高比都超出阈值候选框直接丢掉或者反过来定位成功但字符分割时同一个字符断成两截。原因定位时的闭运算核水平方向太宽比如 (25,5)把字符间空隙也填上字符间隔线和文字全焊成一个矩形而分割阶段膨胀核如果垂直方向太小笔画断裂又接不上。解决定位时核的宽度按字符间距估计。车牌在 240 宽的目标图中字符宽度约 45 像素间距约 12 像素所以闭运算核宽取 15 到 20 安全高度 3 到 5太高会把上下两排结构连起来。分割时用 (1,7) 竖长核只连接同字符内的纵向断裂。调参顺序先让轮廓筛选中目标矩形稳定出现再回头微调核宽不要同时动好几个参数。4.4 模板匹配得分普遍偏低字体、尺寸和二值方向都要对齐现象程序不报错但识别结果只有“京”是对的其他全错或者所有字符得分都低于 0.3。原因其一模板尺寸和归一化尺寸不一致matchTemplate 能运行但结果无意义其二模板字符和真实车牌字体差异太大其三待识别图和模板的黑白极性相反。解决做一套模板对齐清单——模板统一转成 40x80 灰度图再走一遍自适应二值化和待识别字符用完全相同的预处理对模板库每张图做归一化先拿单个真实字符挨个匹配打印 top3 分数如果都小于 0.4就检查二值极性把模板和待识别图都统一成“白字黑底”或“黑字白底”。模板匹配在课程作业规模下天花板就在七成左右答辩时讲清楚它为什么只能作为课设方案比硬吹更强。4.5 车灯和反光也被定位成车牌增加颜色与位置双重校验现象定位算法框出来的是车头镀铬装饰条或圆形车灯不是车牌。原因车灯和装饰条同样有强边缘闭运算后也容易形成长条连通域仅靠面积和宽高比分不开。解决候选框出来后用 HSV 颜色空间做一次车牌颜色投票。蓝底车牌 H 通道约在 100 到 124S 大于 80V 大于 80统计候选区域内满足蓝色条件的像素比例超过 10% 才保留。车灯因为 H 不在蓝色范围或形状接近圆形可以在轮廓筛选里加“矩形度”约束——最小外接矩形面积与轮廓实际面积之比大于 0.6 才继续。双重校验之后误检率明显下降答辩时也多了一个可以展开讲的点。5. 答辩 PPT 怎么讲才不心虚把图像处理流程画成一张闭环数据流5.1 首页到算法总览一张图讲清输入、处理与输出答辩时间通常只有五分钟PPT 第一页放题目第二页就要出现完整的处理流程图。这张图不要画成软件架构图要画成数据流原始图像 → 灰度化 → 高斯模糊 → Sobel 边缘 → 自适应阈值 → 闭运算 → 轮廓筛选 → 透视变换 → 字符分割 → 模板匹配 → 输出结果。每个框里放一张对应的中间结果小图比放文字更直观。评委想确认的是“你理解每一步在做什么”所以每帧中间结果都要能现场生成而不是贴一张静态效果图。我一般建议准备一个 show_results.py 脚本把所有中间结果用 plt.subplot 拼成网格图。答辩时有人问某个环节你就调出这张图指出哪张是膨胀后的、哪张是投影曲线。这样做的好处是不用背台词指着图说就行还能证明代码确实是自己的。PPT 里不贴大段代码只贴关键函数名和参数表比如高斯核 5x5、Sobel ksize3、闭运算核 17x5。评委通常会追问这些参数为什么是这个值你得能答上来否则参数表反而暴露短板。5.2 最容易被打断的三个技术点Sobel、闭运算与模板匹配的缺陷答辩时老师最爱打断的位置往往就是你提到“阈值”“算子”“形态学”的时候。第一个高频问题是“为什么用 Sobel 而不用 Canny”。回答思路车牌字符是密集竖边Sobel 的 x 方向梯度能直接突出这种结构Canny 要先做非极大值抑制再做双阈值产生的边缘更细也更碎之后闭运算要花更大的核才能连起来反而容易连错。最好现场把两行代码的结果图对比一下。第二个高频问题是“闭运算在做什么膨胀和腐蚀的先后能不能换”。答先膨胀后腐蚀是闭运算先腐蚀后膨胀是开运算。这里目标是填补字符边缘的间隙所以先膨胀后腐蚀能保住整体轮廓顺序反了就变成开运算会把断开的边缘清理得更干净定位时连通域反而更少。第三个高频问题是“模板匹配的缺点你知道吗”。这是一个展示诚意的机会直接承认模板匹配对字体、噪声和光照敏感课设规模小样本上只能做到七成再补一句“工业上现在用深度学习但本课程重点是把图像处理基础链条跑通”比硬吹效果好得多。5.3 答辩话术先讲结果后讲流程再讲失败案例最容易被评委扣分的讲法是“我拿到一张图先做了这个又做了那个”。听三分钟就犯困。更好的顺序是先放 10 张测试图的识别结果表格讲“这套流程在 10 张图中正确识别 8 张两种失败分别是夜间低照度和强反光”再讲“为什么能做到八张是靠定位、分割、识别三个环节相互配合”。这样评委心里的预期是“这人知道自己的结果边界”提问也会更友好。PPT 里我建议加一页“失败案例分析”选两张失败的图标出失败原因比如车牌被遮挡导致轮廓断裂或者蓝色车漆导致颜色投票误判。主动暴露问题比被问到哑口无言好很多。最后一页不要写“谢谢观看”就结束可以放一个“待改进点”列表二值化阈值改为自适应版本、模板数量扩充、引入字符级连通域过滤。这叫留口子评委顺着这个问你就能接着讲你已经做过的改进形成闭环。5.4 按 10 页左右的课设答辩模板组织内容基于我批改过不少课设 PPT 的经验给你一个可以直接套的页数结构第 1 页题目和姓名第 2 页整体流程图第 3 到 5 页分别讲预处理、定位、分割识别的每一段流程每页只讲一个核心操作和两个关键参数第 6 页效果展示用 8 到 10 行的识别结果表格带正确/错误标记第 7 页失败案例第 8 页关键技术说明把 Sobel 和闭运算放大讲第 9 页课程体会与改进方向第 10 页致谢。总共十页五分钟刚好。页数不贪多关键在信息密度。很多人把整个代码 dump 进 PPT反而让评委觉得你没消化。如果担心被问“为什么不用 YOLO 直接框车牌”可以加一句“课程要求聚焦图像处理基础深度学习方案依赖标注数据两者不冲突”。这类回应自然、真诚也不会引发过度延伸。6. 进阶调优给车牌加一重 HSV 颜色保险识别率再上一个台阶前面定位用的全是边缘和形状特征在夜晚、车灯干扰、车身贴纸乱的情况下误检率容易升高。我最后会用一招颜色先验先用 HSV 范围把蓝色像素做成掩膜再在掩膜上做闭运算和轮廓筛选。这样即使边缘断裂也能靠颜色找回来实现成本很低。# 读彩色图转到 HSV 后取蓝色范围作为车牌区域掩膜 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, (100, 80, 80), (124, 255, 255)) # 颜色区域自然连片闭运算核可以比边缘法稍小 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5)) mask_close cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)我实践中最稳的做法是把边缘法和颜色法两个结果做“或”逻辑只要一个方法给出候选框就送去透视矫正然后在矫正后的彩色图里再校验一次蓝色像素比例低于 5% 就丢弃。这个阈值不用调太紧因为白天强光下蓝色会泛白H 通道变化不大但 S 和 V 会波动宁可漏检也不误检。最后一件事模板集的扩充要按“新增模板 → 重跑测试集 → 记录识别率”的循环来。我是吃过亏的图省事从网上找了一套非车牌的字体当模板识别率只有 40%换成一批真实车牌字符后直接升到 70%。所以时间有限时优先做好字符分割和模板对齐再去碰更复杂的识别模型。希望这些踩过的坑能帮你少走几步弯路让你的课设顺利过关也真正弄懂每一步图像处理操作为什么在那里。本文还有配套的精品资源点击获取