从零开始详解OpenCV硬币检测:原理、实现与优化全攻略

从零开始详解OpenCV硬币检测:原理、实现与优化全攻略

一、引言:硬币检测的应用场景与技术挑战

硬币检测是计算机视觉领域的经典入门案例,广泛应用于自动售货机、货币分拣机、教育实验等场景。其核心挑战在于:硬币尺寸小、表面反光、边缘模糊,且可能存在重叠或遮挡。OpenCV作为开源计算机视觉库,提供了丰富的工具链,可高效解决这类问题。本文将从零开始,逐步解析硬币检测的全流程,帮助读者掌握图像处理的核心方法。

二、环境准备与基础工具

1. OpenCV安装与配置

OpenCV支持Python/C++,推荐使用Python环境(Python 3.6+ + OpenCV 4.x)。安装命令如下:

  1. pip install opencv-python opencv-contrib-python numpy matplotlib

2. 图像处理基础概念

  • 像素:图像的最小单元,由RGB三通道值表示。
  • 灰度化:将彩色图像转换为单通道,减少计算量。
  • 二值化:通过阈值分割将图像转为黑白,突出目标区域。
  • 形态学操作:如膨胀、腐蚀,用于修复边缘或去除噪声。

三、硬币检测的核心流程

1. 图像预处理:从噪声到清晰

(1)灰度化与降噪

原始图像可能存在光照不均或传感器噪声,需先转换为灰度图并降噪:

  1. import cv2
  2. import numpy as np
  3. # 读取图像并转为灰度图
  4. img = cv2.imread('coins.jpg')
  5. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  6. # 高斯模糊降噪
  7. blurred = cv2.GaussianBlur(gray, (5, 5), 0)

关键点:高斯核大小(如5×5)需根据图像分辨率调整,过大可能模糊边缘。

(2)边缘检测:Canny算法的应用

Canny边缘检测通过梯度计算和双阈值分割提取边缘:

  1. edges = cv2.Canny(blurred, 50, 150) # 低阈值50,高阈值150

参数调优:阈值比例通常为1:2或1:3,需通过实验确定最佳值。

2. 轮廓检测与筛选

(1)查找轮廓

OpenCV的findContours函数可提取所有闭合轮廓:

  1. contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

参数说明

  • RETR_EXTERNAL:仅检测外部轮廓。
  • CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角方向的冗余点。

(2)轮廓筛选:基于面积与圆度

硬币轮廓通常为圆形,可通过面积和圆度筛选:

  1. min_area = 100 # 最小面积阈值
  2. max_area = 5000 # 最大面积阈值
  3. coin_contours = []
  4. for cnt in contours:
  5. area = cv2.contourArea(cnt)
  6. if min_area < area < max_area:
  7. # 计算轮廓的圆度(周长平方/面积,接近圆时值较小)
  8. perimeter = cv2.arcLength(cnt, True)
  9. circularity = (perimeter ** 2) / (4 * np.pi * area) if area > 0 else float('inf')
  10. if circularity < 1.5: # 圆度阈值
  11. coin_contours.append(cnt)

优化建议:动态调整面积阈值以适应不同尺寸的硬币。

3. 硬币定位与标记

(1)最小外接圆拟合

对筛选后的轮廓拟合最小外接圆,获取圆心和半径:

  1. for cnt in coin_contours:
  2. (x, y), radius = cv2.minEnclosingCircle(cnt)
  3. center = (int(x), int(y))
  4. radius = int(radius)
  5. cv2.circle(img, center, radius, (0, 255, 0), 2) # 绘制绿色圆
  6. cv2.putText(img, f'R:{radius}', (center[0]-20, center[1]-20),
  7. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)

(2)非极大值抑制(NMS)

若存在重叠轮廓,需通过NMS去除冗余检测:

  1. def nms_coins(contours, overlap_thresh=0.3):
  2. boxes = []
  3. for cnt in contours:
  4. (x, y), radius = cv2.minEnclosingCircle(cnt)
  5. boxes.append([x-radius, y-radius, x+radius, y+radius])
  6. # 使用OpenCV的NMS函数(需转换为矩形框)
  7. indices = cv2.dnn.NMSBoxes(boxes, [1.0]*len(boxes), overlap_thresh)
  8. if len(indices) > 0:
  9. indices = indices.flatten()
  10. return [contours[i] for i in indices]

四、进阶优化与实战技巧

1. 自适应阈值分割

针对光照不均的图像,可使用自适应阈值替代全局阈值:

  1. adaptive_thresh = cv2.adaptiveThreshold(
  2. gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
  3. cv2.THRESH_BINARY_INV, 11, 2
  4. )

2. Hough圆变换的替代方案

Hough圆变换对噪声敏感,可结合边缘检测和轮廓分析:

  1. circles = cv2.HoughCircles(
  2. blurred, cv2.HOUGH_GRADIENT, dp=1, minDist=20,
  3. param1=50, param2=30, minRadius=10, maxRadius=100
  4. )

参数说明

  • dp:累加器分辨率与图像分辨率的反比。
  • minDist:检测到的圆心之间的最小距离。
  • param1:Canny边缘检测的高阈值。
  • param2:圆心检测的阈值(越小检测越多假圆)。

3. 多硬币分类与计数

通过半径或面积统计不同面值的硬币:

  1. radius_counts = {}
  2. for cnt in coin_contours:
  3. (x, y), radius = cv2.minEnclosingCircle(cnt)
  4. radius = int(radius)
  5. # 假设半径范围对应不同面值
  6. if 10 <= radius <= 15:
  7. radius_counts['1元'] = radius_counts.get('1元', 0) + 1
  8. elif 16 <= radius <= 20:
  9. radius_counts['5角'] = radius_counts.get('5角', 0) + 1
  10. print(f"检测结果:{radius_counts}")

五、完整代码示例

  1. import cv2
  2. import numpy as np
  3. def detect_coins(image_path):
  4. # 1. 读取图像并预处理
  5. img = cv2.imread(image_path)
  6. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  7. blurred = cv2.GaussianBlur(gray, (5, 5), 0)
  8. # 2. 边缘检测
  9. edges = cv2.Canny(blurred, 50, 150)
  10. # 3. 查找并筛选轮廓
  11. contours, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  12. coin_contours = []
  13. for cnt in contours:
  14. area = cv2.contourArea(cnt)
  15. if 100 < area < 5000:
  16. perimeter = cv2.arcLength(cnt, True)
  17. circularity = (perimeter ** 2) / (4 * np.pi * area) if area > 0 else float('inf')
  18. if circularity < 1.5:
  19. coin_contours.append(cnt)
  20. # 4. 标记硬币
  21. for cnt in coin_contours:
  22. (x, y), radius = cv2.minEnclosingCircle(cnt)
  23. center = (int(x), int(y))
  24. radius = int(radius)
  25. cv2.circle(img, center, radius, (0, 255, 0), 2)
  26. cv2.putText(img, f'R:{radius}', (center[0]-20, center[1]-20),
  27. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
  28. # 5. 显示结果
  29. cv2.imshow('Detected Coins', img)
  30. cv2.waitKey(0)
  31. cv2.destroyAllWindows()
  32. # 调用函数
  33. detect_coins('coins.jpg')

六、总结与展望

本文从OpenCV基础出发,系统讲解了硬币检测的完整流程,包括图像预处理、边缘检测、轮廓分析和结果标记。通过代码实现和参数调优建议,读者可快速上手并优化检测效果。未来可进一步探索深度学习方案(如YOLO)以提升复杂场景下的鲁棒性。掌握这一案例后,读者可迁移至其他圆形物体检测任务,如零件质检、生物细胞分析等。