
在计算机视觉领域,基本形状提取(圆形、椭圆、方形、三角形等)是一项基础且核心的任务,广泛应用于工业质检(如零件尺寸检测)、目标识别(如交通标志检测)、机器人视觉(如障碍物形状判断)等场景。传统方法无需训练数据,依赖图像处理技术即可实现,适合快速落地和入门学习。
本文将聚焦 霍夫变换 和 轮廓检测+形状特征匹配 两种经典算法,用 Python+OpenCV 手把手实现基本形状提取,兼顾原理讲解和实战代码,适合初学者直接上手。
一、核心算法原理
1.1 霍夫变换(Hough Transform):主打圆形/直线检测
霍夫变换的核心思想是 将图像空间的特征(点、线、圆)映射到参数空间,通过投票机制筛选出最可能的特征。
- 霍夫圆检测 :圆的方程为 (x−a)2+(y−b)2=r2(x-a)^2 + (y-b)^2 = r^2(x−a)2+(y−b)2=r2,其中 (a,b)(a,b)(a,b) 是圆心,rrr 是半径。将图像中每个边缘点映射到 (a,b,r)(a,b,r)(a,b,r) 三维参数空间,统计参数空间中投票数最高的点,即为检测到的圆。
- 霍夫直线检测 :直线的极坐标方程为 ρ=xcosθ+ysinθ\rho = x\cos\theta + y\sin\thetaρ=xcosθ+ysinθ,映射到 (ρ,θ)(\rho,\theta)(ρ,θ) 二维参数空间,投票峰值对应直线。(方形可通过直线检测+角点组合推导)
1.2 轮廓检测+形状特征匹配:通用形状识别
轮廓是图像中连续的像素集合(前景与背景的边界)。通过轮廓的几何特征(边数、圆形度、长宽比等),可快速识别形状:
- 轮廓提取 :先对图像预处理(灰度化、模糊、阈值分割),再用
findContours函数提取轮廓; - 轮廓近似 :用
approxPolyDP函数将不规则轮廓近似为多边形(减少冗余点); - 特征判断 :
- 边数=3 → 三角形;
- 边数=4 → 方形(需验证长宽比、内角接近90°);
- 圆形度(轮廓面积/最小外接圆面积)≈1 → 圆形;
- 用
fitEllipse拟合椭圆 → 椭圆。
二、实战准备:环境与依赖
需安装 Python3 和 OpenCV(图像处理核心库),命令如下:
bash
pip install opencv-python matplotlib numpy
三、代码实现:分模块落地
3.1 第一步:图像预处理(关键!决定检测效果)
预处理的目标是 降噪+突出前景轮廓,步骤通常为:
- 读取图像 → 2. 灰度化 → 3. 高斯模糊(降噪) → 4. 阈值分割/边缘检测(突出轮廓)
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像(替换为你的图像路径)
img = cv2.imread("shapes.jpg")
# 复制原图用于绘制结果
img_result = img.copy()
# 1. 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 高斯模糊(核大小为奇数, sigmaX控制模糊程度)
blurred = cv2.GaussianBlur(gray, (5, 5), 1)
# 3. 阈值分割(将图像转为黑白二值图,突出前景)
_, thresh = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV)
# 可选:形态学操作(进一步优化轮廓,如填充小缺口)
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 显示预处理结果
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title("原图")
plt.axis("off")
plt.subplot(1, 3, 2)
plt.imshow(gray, cmap="gray")
plt.title("灰度图")
plt.axis("off")
plt.subplot(1, 3, 3)
plt.imshow(thresh, cmap="gray")
plt.title("预处理后(二值图)")
plt.axis("off")
plt.show()
3.2 第二步:霍夫圆检测(专门针对圆形)
OpenCV 提供 cv2.HoughCircles 函数,直接实现霍夫圆检测,关键参数需根据图像调整:
dp:累加器分辨率(越大检测越慢,精度越高);minDist:两个圆的最小距离(避免重复检测);param1:Canny 边缘检测的高阈值(低阈值为其1/2);param2:投票阈值(越大检测到的圆越可靠);minRadius/maxRadius:圆的半径范围。
python
# 霍夫圆检测
circles = cv2.HoughCircles(
blurred, # 输入图像(建议用模糊后的灰度图)
cv2.HOUGH_GRADIENT, # 检测方法(固定)
dp=1.2, # 累加器分辨率
minDist=50, # 两圆最小距离
param1=50, # Canny边缘高阈值
param2=30, # 投票阈值
minRadius=10, # 最小半径
maxRadius=100 # 最大半径
)
# 绘制检测到的圆
if circles is not None:
circles = np.uint16(np.around(circles)) # 转为整数坐标
for i in circles[0, :]:
# 绘制圆心
cv2.circle(img_result, (i[0], i[1]), 2, (0, 255, 0), -1)
# 绘制圆轮廓
cv2.circle(img_result, (i[0], i[1]), i[2], (255, 0, 0), 2)
# 标注"圆形"
cv2.putText(
img_result, "Circle", (i[0]-i[2], i[1]-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1
)
3.3 第三步:轮廓检测+多形状识别(方形、三角形、椭圆)
通过轮廓的几何特征识别方形、三角形,用 fitEllipse 拟合椭圆:
python
# 提取轮廓(RETR_EXTERNAL:只保留最外层轮廓)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 遍历每个轮廓
for cnt in contours:
# 过滤小轮廓(避免检测噪声)
area = cv2.contourArea(cnt)
if area < 100:
continue
# 轮廓近似(epsilon:近似精度,通常为周长的0.02倍)
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
vertices = len(approx) # 近似多边形的边数
# 计算轮廓的外接矩形(用于标注文字位置)
x, y, w, h = cv2.boundingRect(approx)
center_x = x + w // 2
center_y = y + h // 2
# 1. 三角形(边数=3)
if vertices == 3:
cv2.drawContours(img_result, [approx], 0, (0, 255, 0), 2)
cv2.putText(img_result, "Triangle", (center_x-30, center_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
# 2. 方形(边数=4,且长宽比接近1,内角接近90°)
elif vertices == 4:
# 计算长宽比
aspect_ratio = float(w) / h
# 验证是否为矩形(用轮廓的外接矩形面积与轮廓面积的比值)
rect_area = w * h
area_ratio = area / rect_area
if 0.9 < aspect_ratio < 1.1 and area_ratio > 0.85:
cv2.drawContours(img_result, [approx], 0, (0, 0, 255), 2)
cv2.putText(img_result, "Square", (center_x-25, center_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
else:
# 非正方形(长方形)
cv2.drawContours(img_result, [approx], 0, (128, 0, 128), 2)
cv2.putText(img_result, "Rectangle", (center_x-35, center_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (128, 0, 128), 1)
# 3. 椭圆(轮廓点数足够,且拟合椭圆的长宽比合理)
elif vertices > 8:
# 拟合椭圆(需轮廓点数≥5)
if len(cnt) >= 5:
ellipse = cv2.fitEllipse(cnt)
# 计算椭圆的长宽比(避免将圆识别为椭圆)
major_axis = max(ellipse[1])
minor_axis = min(ellipse[1])
ellipse_ratio = minor_axis / major_axis
if ellipse_ratio < 0.9: # 长宽比差异较大,判定为椭圆
cv2.ellipse(img_result, ellipse, (255, 255, 0), 2)
cv2.putText(img_result, "Ellipse", (center_x-25, center_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1)
3.4 第四步:显示最终结果
python
# 显示结果
plt.figure(figsize=(8, 6))
plt.imshow(cv2.cvtColor(img_result, cv2.COLOR_BGR2RGB))
plt.title("基本形状提取结果")
plt.axis("off")
plt.show()
# 保存结果
cv2.imwrite("shapes_result.jpg", img_result)
print("结果已保存为 shapes_result.jpg")
四、效果展示与参数调优
4.1 测试图像与结果
假设测试图像 shapes.jpg 包含圆形、方形、长方形、三角形、椭圆,运行代码后:
- 圆形:蓝色轮廓+"Circle"标注;
- 方形:红色轮廓+"Square"标注;
- 长方形:紫色轮廓+"Rectangle"标注;
- 三角形:绿色轮廓+"Triangle"标注;
- 椭圆:青色轮廓+"Ellipse"标注。
4.2 常见问题与调优技巧
-
检测不到形状/漏检:
- 降低
HoughCircles的param2(圆检测); - 减小
approxPolyDP的epsilon(轮廓近似更精细); - 降低阈值分割的阈值(
cv2.threshold的第二个参数)。
- 降低
-
误检(检测到噪声):
- 增大
HoughCircles的param2和minRadius; - 提高轮廓面积阈值(
area > 200,过滤小噪声); - 增大高斯模糊的核大小(如
(7,7))。
- 增大
-
圆被识别为椭圆:
- 优化霍夫圆检测参数(确保圆先被检测到);
- 调整椭圆判定的
ellipse_ratio(如改为< 0.8)。
五、总结与扩展
本文通过 霍夫变换 和 轮廓检测+特征匹配 实现了基本形状提取,核心优势是:
- 无需训练数据,代码轻量化;
- 实时性好,适合嵌入式设备;
- 原理直观,适合入门学习。
局限性与扩展方向
- 局限性:对遮挡、变形、复杂背景敏感;
- 扩展方向 :
- 复杂场景:结合深度学习(如 Mask R-CNN)实现端到端形状分割;
- 3D 形状:结合立体视觉(如双目相机)提取 3D 形状;
- 实时处理:用 C++ 重写核心代码,结合 GPU 加速。