ARTICLE DETAIL

资讯详情

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

OpenCV实战:硬币检测与识别完整流程从预处理到轮廓分类

OpenCV实战:硬币检测与识别完整流程从预处理到轮廓分类 简介一份面向OpenCV入门者的Python实战资源聚焦硬币检测与识别场景。项目以图像处理为基础从照片中定位硬币区域统计个数并判别面值总额适合正在学习轮廓提取、边缘检测、模板匹配或形态学操作的开发者参考。压缩包共13个文件包含1个Python源码和12张JPG图片涵盖模板图像、测试样本与输出结果整体约7.39MB结构简洁便于对照运行。目前已有2242人学习下载。通过源码与配套图片可清晰看到从图像预处理到硬币定位、特征比对及结果输出的完整流程并可根据自备照片替换测试图快速验证算法效果是巩固OpenCV图像处理技能的实用练习。1. 项目背景与目标拆解硬币检测与识别是计算机视觉入门阶段非常适合练手的一个项目因为它能把图像处理的基础操作串起来读取图像、灰度化、去噪、边缘提取、轮廓分析、几何特征筛选最后再按特征做分类。麻雀虽小五脏俱全。等你把这个流程跑通再去看人脸检测、物体识别、OCR这些更高阶的内容思路会清晰很多。这个项目解决的实际问题很简单给定一张包含硬币的图像程序要能告诉你有几枚硬币以及每一枚的币值是多少。更进一步如果图像里有其他圆形物体比如纽扣、瓶盖、棋子还得能把这些“干扰项”过滤掉只保留真正的硬币。围绕这个目标整个实现路径可以拆成四个核心环节图像预处理、硬币候选区域定位、硬币轮廓提取与筛选、硬币币值识别。下面我按这个顺序把每一步的原理和坑都讲清楚。适合看这篇东西的人我建议是有过一点点Python基础至少知道怎么装库、怎么跑脚本的朋友。如果你是纯零基础先把Python环境配好装好OpenCV和NumPy再回来读体验会好很多。全程使用OpenCV的Python接口不需要训练任何模型CPU跑起来毫无压力笔记本也能流畅运行。2. 环境准备与工具选型2.1 Python环境和OpenCV安装我用的是Python 3.9 OpenCV 4.8的组合这套组合在Windows、macOS、Linux上都能跑。安装方式很简单用pip一把梭pip install opencv-python numpy这里有个基本的注意事项opencv-python是社区维护的预编译版本自带常用的图像处理模块日常用足够。如果你想用SIFT这类受专利影响的算法需要安装opencv-contrib-python不过硬币识别用不到别多装。装完之后在Python里验证一下import cv2 import numpy as np print(cv2.__version__) print(np.__version__)能输出版本号就说明环境没问题。如果遇到ModuleNotFoundError: No module named cv2大概率是pip装到了别的Python环境里检查一下你的终端到底用的是哪个解释器。用Anaconda的话先conda activate你的虚拟环境再装。2.2 图像读取与显示的基本姿势项目里的图像I/O虽然基础但有几个细节值得注意。默认用cv2.imread()读进来的图片是BGR三通道彩色图不是RGB这一点初学经常搞混。显示用cv2.imshow()但它会阻塞主线程所以习惯上配合cv2.waitKey(0)用按任意键关闭窗口。import cv2 img cv2.imread(coins.jpg) print(img.shape) # (高, 宽, 通道数)如果读到的img是None先检查文件路径别用中文路径OpenCV对中文路径的支持一直很别扭这种小问题能浪费你半天时间。3. 图像预处理打好地基才是关键预处理是整个硬币识别流程里最吃功夫的一步。图像质量直接决定后面轮廓提取和识别的准确性而预处理的核心目的只有一个把硬币区域从复杂背景中干净地分离出来。3.1 灰度化与去噪原始图像是彩色的但硬币检测阶段我们用不到颜色信息所以先转灰度图减少计算量gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)转灰度之后图像上可能有噪点、纹理干扰直接用会触发很多伪轮廓。我习惯先做一次高斯模糊把细小的噪声磨平blurred cv2.GaussianBlur(gray, (5, 5), 0)高斯模糊的核大小(5, 5)是经验值针对硬币这种边缘比较清晰的物体够用。如果图片噪声特别重还可以加大到(7, 7)。注意别开太大否则硬币边缘会被磨得模糊后续边缘检测找出来的轮廓边界会有偏差。3.2 为什么选择Canny边缘检测OpenCV里找边缘有Sobel、Laplacian、Canny好几种方法硬币识别我首选Canny原因是它对噪声鲁棒而且能找到细且闭合的边缘。Canny内部做了梯度计算、非极大值抑制、双阈值滞后处理效果在工业场景下验证很多遍了。edges cv2.Canny(blurred, 50, 150)两个阈值50和150是什么意思简单理解梯度幅值大于150的一定是边缘小于50的一定不是边缘介于两者之间的再看它是否与确定的边缘相连相连就保留不相连就丢弃。这套机制能让边缘更连续、更干净。阈值怎么调没有绝对标准我的经验是先从(50, 150)试起如果边缘断成一段一段的降低低阈值到(30, 100)如果背景噪声被框出太多拉高两个阈值。调参本身就是图像处理的日常多试几次就有感觉了。3.3 形态学操作补全边缘Canny找出来的边缘偶尔会有小缺口比如硬币表面反光导致某一段梯度不够、没被判定为边缘。这时候可以用形态学操作把断裂的地方接上kernel np.ones((3, 3), np.uint8) edges_closed cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations2)闭运算就是先膨胀再腐蚀能把小空洞填上同时保持整体边缘形状不太走样。硬币边缘如果断口太宽一次闭运算不行就多迭代几次但别贪迭代太多会让边缘膨胀变粗影响后续轮廓面积计算的精度。4. 硬币定位找到候选圆形区域4.1 轮廓提取cv2.findContours的正确用法预处理做完就到了定位环节。OpenCV有两条路一是直接用cv2.findContours找轮廓再用圆的几何特征筛选二是用霍夫圆变换cv2.HoughCircles直接检测圆。两条路我都试过硬币识别场景下轮廓法更直观、可控性更强。cv2.findContours在OpenCV 4.x里只返回两个值很多老教程还写着image, contours, hierarchy那是OpenCV 3之前的老接口直接照抄会报错。正确写法contours, hierarchy cv2.findContours(edges_closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)cv2.RETR_EXTERNAL表示只提取最外层轮廓这样硬币和硬币之间即便互相遮挡了一点点也不会把内部嵌套的轮廓带出来。cv2.CHAIN_APPROX_SIMPLE是压缩轮廓点只保留端点节省内存。4.2 用几何特征筛选硬币区域findContours会把图像里所有连通区域都当成候选轮廓包括圆形硬币也包括边缘噪点连出来的怪形状。所以下一步是写过滤条件把真正像硬币的留下来。硬币是圆形的我的筛选逻辑按下面三个条件做轮廓面积要足够大过滤掉颗粒噪点轮廓的宽高比要接近1过滤掉细长条形状轮廓拟合圆的误差要小过滤掉方形、多边形。第三步是关键用cv2.minEnclosingCircle求轮廓的最小外接圆然后计算轮廓面积占圆面积的比例。如果是完美圆这个比例接近1如果是方形比例约是0.64左右如果是细长条比例会更低。我通常要求这个比例大于0.75才算候选硬币def is_circle(contour): area cv2.contourArea(contour) if area 500: return None (x, y), radius cv2.minEnclosingCircle(contour) circle_area 3.14159 * radius * radius ratio area / circle_area if ratio 0.75: return (x, y, radius) return Nonearea 500这个下限要根据实际图片分辨率调整。如果拍摄距离远硬币在画面里本来就小硬套这个阈值会把真硬币过滤掉灵活一点。4.3 硬币重叠与遮挡的应对思路这里要提前说一个现实问题如果硬币是随意撒在桌面上的很大概率有互相重叠的情况。轮廓法在重叠场景下会比较吃力因为两个圆叠在一起外轮廓就不再是一个标准圆面积比例过滤会把它们整个丢掉。应对方案有两个方向一是尽量让硬币分开摆放再拍照这是最省事的办法实际项目里也推荐这么做所有算法都有限制条件没必要和物理世界硬杠。二是用分水岭算法做粘连分割简单说就是把每个硬币看成一个小山丘通过形态学距离变换找到山丘之间的“分水岭”把连在一起的两枚硬币切开。这个方案实现复杂而且对硬币边缘噪声敏感判断效果好不好需要下不少功夫。5. 硬币识别从形状到币值定位到硬币轮廓之后识别每一枚硬币的币值是最后的临门一脚。这里我基于一个朴素的观察硬币的物理尺寸决定了它成像后的直径像素数同一种币值的硬币直径是固定的。所以用轮廓直径这个特征来分类不依赖训练数据算法透明效果直接。5.1 基于尺寸的币值分类思路假设这个项目识别的是最经典的三枚硬币1元、5角、1角。以人民币为例1元硬币直径约25毫米5角硬币直径约20.5毫米1角硬币直径约19毫米。尺寸差异足够大比例上能轻松区分。但要注意一个关键细节图像里的像素直径和真实物理尺寸之间是等比关系。相机离硬币近成像就大相机离得远成像就小。所以不能用固定的像素直径直接分类需要用一个参考物做标定。我常用的标定方法是在场景里放一枚已知币值的硬币作为“尺子”。假设放的是1元硬币检测出它的像素直径是250那比例系数就是25毫米 / 250像素 0.1毫米/像素。然后测其他硬币的像素直径乘以0.1换成毫米再用真实尺寸去匹配币种。这个方法的假设前提是硬币处于同一个平面、相机正对平面。如果拍照角度太斜成像会有透视形变像素直径不准识别率会下降。5.2 文字和图案特征识别对于图案比较接近的硬币单靠直径区分不开怎么办有一个思路是用模板匹配。先准备每种硬币的正面、反面灰度图作为模板然后用cv2.matchTemplate在待识别区域上做滑动窗口匹配得分最高的模板就是结果。这个方法实现简单但需要大量的模板对光照变化敏感适合固定场景下的小规模识别。更进阶的路子是用卷积神经网络做分类把硬币区域切出来送进一个小型分类网络识别精度能到99%以上。但这就引入了模型训练、标注数据、推理环境依赖等一系列问题。对本文这种入门项目来说尺寸匹配已经足够神经网络等你有具体上线需求再上不迟。6. 完整代码实现与效果验证6.1 清单式可运行的检测代码下面把整个流程串起来给出一个可以直接运行的完整脚本。代码里有注释建议复制到自己项目里一行一行跟着走一遍。import cv2 import numpy as np def preprocess(img): 图像预处理灰度、去噪、边缘提取、闭运算补全。 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5, 5), 0) edges cv2.Canny(blurred, 50, 150) kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) edges cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel, iterations2) return edges def detect_coins(img, edges): 从边缘图中提取轮廓并筛选出圆形硬币区域。 contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) coins [] for contour in contours: area cv2.contourArea(contour) if area 800: continue (x, y), radius cv2.minEnclosingCircle(contour) circle_area np.pi * radius * radius if area / circle_area 0.75: continue coins.append((int(x), int(y), int(radius))) return coins def classify_coin(diameter_px, pixel_per_mm): 根据像素直径换算真实毫米直径再匹配币种。 diameter_mm diameter_px / pixel_per_mm sizes {25.0: 1元, 20.5: 5角, 19.0: 1角} best_match 未知 best_diff 100 for true_size, name in sizes.items(): diff abs(diameter_mm - true_size) if diff best_diff: best_diff diff best_match name return best_match def main(img_path, ref_diameter_pxNone, pixel_per_mmNone): img cv2.imread(img_path) if img is None: print(图片读取失败检查路径) return edges preprocess(img) coins detect_coins(img, edges) # 标定逻辑如果传入了已知参考硬币的像素直径则计算比例尺 if ref_diameter_px and pixel_per_mm is None: # 这里假设参考硬币是1元直径25mm pixel_per_mm ref_diameter_px / 25.0 for (x, y, r) in coins: diameter_px 2 * r if pixel_per_mm: label classify_coin(diameter_px, pixel_per_mm) else: label f{diameter_px}px cv2.circle(img, (x, y), r, (0, 255, 0), 2) cv2.putText(img, label, (x - 30, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(Result, img) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ __main__: # 运行示例main(coins.jpg, ref_diameter_px250) main(coins.jpg)这段代码在我的测试图集上能稳定跑通。注意main函数的标定参数没给的话只会在图上标出直径像素值不会输出币种这是刻意留的说明逻辑方便你先验证检测环节再接入分类逻辑。6.2 测试结果从理想图到现实图我用三张测试图做了效果验证结论值得分享第一张是纯色背景、光源均匀、硬币完全分开的图片。预处理之后边缘非常干净检测出5枚硬币全部正确分类准确率100%。第二张是木纹桌面背景硬币周围有木纹线条干扰。Canny把木纹的一些纹路也检测成了边缘但面积过滤和圆形比例过滤把它们全部挡掉了最后依然全部正确。第三张是硬币部分重叠、加上阴影的场景。这里就开始出问题了重叠的两枚硬币被当成一个非圆形轮廓面积比例阈值判定不过直接被过滤掉。这也是我为什么特意在4.3强调重叠场景的应对方案。7. 调参过程与各类坑的排查7.1 Canny阈值怎么调才算合适很多刚接触OpenCV的朋友最头疼的就是Canny的阈值。我给一个适用范围很广的调法先用(50, 150)跑一遍看边缘图如果边缘断裂严重说明低阈值太高或高阈值太高整体往下调比如(30, 100)如果边缘图全是细碎纹理说明阈值太低整体往上调比如(80, 200)如果硬币边缘完整但背景也多先提高面积过滤阈值再看Canny要不要调整。调参的本质是在“边缘完整度”和“噪声引入”之间找平衡没有一组参数能应对所有场景但多试几次你对图像的感觉会很快建立起来。7.2 findContours返回值的版本差异这是网上报错的高频陷阱。OpenCV 3.2以后findContours只返回contours, hierarchy但大量存量教程还写着三返回值。解决办法很简单用我代码里的两返回值写法或者加个兼容判断但没必要直接按新版写法就行OpenCV 4.x用户占比已经压倒性多数。顺带一提不要用cv2.drawContours在二值图上反复调试搞久了会晕。我的习惯是画在原始彩色图上直接从视觉上对比定位结果和真实硬币。7.3 光照不均带来的误检与漏检硬币表面是金属材质反光严重。侧光拍出来的硬币边缘高光和阴影对比强烈Canny有时会把硬币内部的图案纹路也当边缘提取出来。更麻烦的是强反光区域可能让边缘出现断带导致闭运算补不上。我的经验是两个对策一是拍摄时用漫反射光源尽量消除正反射光斑这是物理层面的解法效果最好。二是在预处理时先用cv2.createCLAHE做一次自适应直方图均衡化增强局部对比度让光照不均的影响变小clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) gray clahe.apply(gray)加上这一步之后我的测试集漏检率明显下降。如果环境光照一直不理想强烈建议把CLAHE写进预处理流程。7.4 已知问题排查速查表现象可能原因解决方式检测到很多小圆圈噪点面积阈值太低提高cv2.contourArea过滤下限硬币边缘中间断了一块Canny高阈值太高或闭运算迭代不够降低高阈值增加闭运算迭代次数检测出的圆偏大或偏小边缘膨胀导致轮廓外扩减小形态学核尺寸别iteration太多有些硬币完全没检测到重叠或轮廓比例不满足圆形条件分开摆拍或尝试分水岭分割findContours报错参数数量不对OpenCV版本接口不一致用当前版本的两返回值写法币值识别错误比例尺标定不准用已知硬币重新标定像素/毫米比例8. 总结与实际项目延展方向不做总结性套话了说说实际操作中最深的一点体会硬币检测这个项目看起来代码量不大但如果你把它完整跑通、调过参数、处理过光照不均和重叠遮挡你会发现自己对OpenCV整套图像处理流程的理解完全不一样了。它逼你把每个环节的原理搞明白因为任何一个环节出了问题结果就直接摆在那里骗不了人。这个项目做完之后后续还有好几个可以顺着扩展的方向。一是硬币币值识别可以加入纹理特征比如用HOG特征加SVM分类器替换掉简单直径匹配提升相似尺寸硬币的区分度二是可以做成实时版本用摄像头读取视频流在每一帧上跑检测这会涉及帧率优化和缓存复用三是为项目加一个UI界面用PyQt或Streamlit把图片拖进去直接出结果方便给别人演示。如果手边正好有硬币不妨现在就拍一张试试把预处理、轮廓筛选、标定识别完整跑一遍。等到你亲手把一枚硬币从图片里圈出来程序准确喊出“1元”的那一刻这个项目带来的成就感会告诉你花这些时间是值得的。本文还有配套的精品资源点击获取
返回列表