「识别」的核心不是「看形状」,而是「数学参数的最优匹配」与「断点的逻辑缝合」。 本文档将识别原理(五个层次)与对应经典示例、可直接运行的 Python 代码整合为一, 每个示例都配有由脚本生成的演示图。
所有示例图由
code/demo*.py脚本自动生成(合成测试图 → 运行算法 → 出图), 完整可运行代码见code/目录,运行方式见文末 附录:运行演示脚本。
第一层:参数空间投票(霍夫变换)——解决「断裂」问题
当图形因扫描质量差而断成几截时,靠「找连通域」会失效。此时最经典的武器是霍夫变换(Hough Transform)。
原理
- 直线识别:将图像中无数个像素点,映射到「斜率-截距」参数空间进行累加投票。即使一条直线断成 10 段,这些碎片对应的参数值仍然会投射到参数空间的同一个峰值点上,系统通过检测峰值就能「强行」将断线连成一条完整的直线。
- 圆/圆弧识别:升级为霍夫梯度法。算法遍历所有边缘点,计算其梯度方向,将梯度方向映射到圆心坐标空间。只要断点位于同一圆周上,它们衍生出的圆心候选点就会高度聚集,从而识别出完整的圆。
经典示例一:霍夫变换——检测图像中的直线与圆
cv2.HoughLinesP() 用于检测线段,cv2.HoughCircles() 用于检测圆。它们都要求输入是边缘图(如 Canny 的结果)。
import cv2
import numpy as np
# ---------- 1. 直线检测 (HoughLinesP) ----------
def detect_lines(image_path):
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3) # 提取边缘
# 概率霍夫变换检测线段
lines = cv2.HoughLinesP(edges,
rho=1, # 距离精度(像素)
theta=np.pi/180, # 角度精度(弧度)
threshold=100, # 投票阈值
minLineLength=50, # 最小线段长度
maxLineGap=10) # 允许的最大线段间隔
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2) # 画红线
cv2.imshow('Line Detection', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# ---------- 2. 圆检测 (HoughCircles) ----------
def detect_circles(image_path):
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray, 5) # 中值滤波去噪,对圆检测很重要
# 霍夫梯度法检测圆
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
dp=1, # 累加器分辨率与图像分辨率的反比
minDist=50, # 圆心之间的最小距离
param1=100, # Canny高阈值
param2=30, # 圆心检测的累加器阈值
minRadius=10, # 最小半径
maxRadius=100) # 最大半径
if circles is not None:
circles = np.uint16(np.around(circles)) # 转换数据类型
for circle in circles[0, :]:
x, y, r = circle[0], circle[1], circle[2]
cv2.circle(img, (x, y), r, (0, 255, 0), 2) # 画绿色圆
cv2.circle(img, (x, y), 2, (0, 0, 255), 3) # 画红色圆心
cv2.imshow('Circle Detection', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 使用示例
# detect_lines('path_to_your_image.jpg')
# detect_circles('path_to_your_image.jpg')
注意:
threshold、param2等参数对结果影响很大,需要根据具体图像调整。
演示效果图(由 code/demo1_hough_lines.py 生成)

图中左栏为故意打断成多段并加入椒盐噪声的合成直线图像,中栏为 Canny 边缘图, 右栏为霍夫变换检测结果——断裂的线段被重新「缝合」成完整直线。
演示效果图(圆检测,由 code/demo2_hough_circles.py 生成)

图中左栏为圆周被切成多段圆弧的合成图像(含一个内圈干扰圆与噪点), 中栏为 Canny 边缘图,右栏为霍夫圆检测结果——断开的圆弧被还原为完整圆。
第二层:随机采样与一致性检验(RANSAC)——解决「离群杂点」干扰
现实图纸中常有无关的交叉线或噪点(离群点)。识别算法采用 RANSAC(随机抽样一致性) 策略:
- 从杂乱的点集中随机抽取最少点数(直线取 2 点,圆取 3 点)拟合出一个候选图形;
- 遍历所有其他点,统计有多少点落在该图形的容差带(如像素距离 < 2)内;
- 保留「内点」数量最多的那个候选模型。
通过反复迭代,算法能无视大量干扰点,只针对「最主流」的几何特征进行精准识别。
经典示例二:RANSAC——从含噪点中稳健拟合直线
RANSAC 的核心是随机采样和一致性检验。它能有效排除离群点(Outliers)的干扰,找到最能代表主流数据的模型。sklearn 库提供了现成的实现。
import numpy as np
from sklearn import linear_model, datasets
import matplotlib.pyplot as plt
# 生成示例数据:1000个样本,其中50个为离群点
n_samples = 1000
n_outliers = 50
X, y, coef = datasets.make_regression(n_samples=n_samples,
n_features=1,
n_informative=1,
noise=10,
coef=True,
random_state=0)
# 添加离群点(outlier data)
np.random.seed(0)
X[:n_outliers] = 3 + 0.5 * np.random.normal(size=(n_outliers, 1))
y[:n_outliers] = -3 + 10 * np.random.normal(size=n_outliers)
# ---------- 使用RANSAC稳健拟合 ----------
ransac = linear_model.RANSACRegressor()
ransac.fit(X, y)
# 获取内点(Inliers)和外点(Outliers)的掩码
inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)
# ---------- 可视化对比 ----------
line_X = np.arange(X.min(), X.max())[:, np.newaxis]
line_y_ransac = ransac.predict(line_X)
plt.scatter(X[inlier_mask], y[inlier_mask], color='yellowgreen', marker='.', label='Inliers')
plt.scatter(X[outlier_mask], y[outlier_mask], color='gold', marker='.', label='Outliers')
plt.plot(line_X, line_y_ransac, color='navy', linewidth=2, label='RANSAC Regressor')
plt.legend()
plt.xlabel("Input")
plt.ylabel("Response")
plt.show()
print(f"拟合系数: {ransac.estimator_.coef_[0]:.2f}") # 应接近真实系数
说明:
RANSACRegressor会自动将数据分为「内点」和「外点」,并仅用内点进行最终拟合,从而得到稳健的结果。
演示效果图(由 code/demo3_ransac_line.py 生成)

左图为数据分布与拟合对比:黄色点是离群点,绿色点是内点。红色虚线为普通最小二乘对全部点 拟合的结果(被离群点严重带偏),蓝色实线为 RANSAC 拟合结果(正确逼近主流数据)。 右图为仅显示内点与 RANSAC 拟合直线的结果。
经典示例三:RANSAC——从角点中拟合圆
这个例子展示了如何从检测到的角点(Corners)中,使用 RANSAC 算法找到一个圆。
import numpy as np
import matplotlib.pyplot as plt
from skimage.measure import CircleModel # 使用CircleModel作为拟合模型
# 1. 生成一个带噪声的圆
np.random.seed(0)
# 真实圆的参数:圆心(100, 100),半径50
true_center = (100, 100)
true_radius = 50
# 生成圆周上的点,并加入噪声
theta = np.linspace(0, 2*np.pi, 100)
points = np.array([[true_center[0] + true_radius * np.cos(t) + np.random.normal(0, 5),
true_center[1] + true_radius * np.sin(t) + np.random.normal(0, 5)] for t in theta])
# 添加一些随机离群点
outliers = np.random.rand(30, 2) * 200
points = np.vstack([points, outliers])
# 2. 使用RANSAC拟合圆模型
# 定义模型:CircleModel(),需要至少3个点来拟合一个圆
model = CircleModel()
# RANSAC拟合
from skimage.measure import ransac
model_robust, inliers = ransac(points, CircleModel, 3, # 最少3个点拟合一个圆
residual_threshold=10, # 内点阈值
max_trials=1000) # 最大迭代次数
# 3. 结果可视化
inlier_points = points[inliers]
outlier_points = points[~inliers]
# 绘制结果
plt.plot(inlier_points[:, 0], inlier_points[:, 1], '.', color='green', label='Inliers')
plt.plot(outlier_points[:, 0], outlier_points[:, 1], '.', color='red', label='Outliers')
# 绘制拟合出的圆
cy, cx = model_robust.params # CircleModel的参数是 (cy, cx, radius)
r = model_robust.params[2]
circle = plt.Circle((cx, cy), radius=r, color='blue', fill=False, linewidth=2, label='Fitted Circle')
plt.gca().add_patch(circle)
plt.legend()
plt.axis('equal')
plt.show()
print(f"拟合圆心: ({cx:.2f}, {cy:.2f}), 拟合半径: {r:.2f}")
说明:
CircleModel是 scikit-image 库提供的圆形模型。ransac函数会反复随机采样 3 个点来生成候选圆,并选择内点最多的那个作为最终结果。
演示效果图(由 code/demo4_ransac_circle.py 生成)

绿色点为落在圆周附近的内点,红色点为随机离群点。蓝色圆为 RANSAC 拟合结果, 其圆心与半径接近真实值(圆心 (100, 100),半径 50),误差通常在 1 个像素以内。
第三层:拓扑图搜索与子图同构——解决「组合图形」识别
对于复杂的嵌套图形(如法兰盘、回字形零件),单靠基本几何不够。此时将识别转化为「图匹配」问题:
- 算法先提取所有角点(顶点)和线段(边),构建一个属性邻接图(记录每条边的长度、角度、以及顶点间的连接关系)。
- 系统内置一个标准图元库(如「矩形内接圆」、「同心矩形」)。
- 识别时,在待测图中搜索与模板图同构的子图,并校验夹角、长度比例是否一致。一旦匹配上,即判定为特定零件轮廓。
无独立代码示例:该层通常基于
networkx(子图同构networkx.algorithms.isomorphism)配合自建的属性邻接图实现,属于对第一、二层提取结果的二次推理。
第四层:断点缝合与虚拟求交——解决「残缺与遮挡」
这是工业识别中最见功力的部分。当图形缺角或被标注线遮挡时:
- 端点延伸预测:对于断开的线段端点,算法计算其切线方向,若两条断线的延长线距离阈值小于设定值(如 5 个像素),且夹角接近 0° 或 90°,系统会主动「脑补」出虚拟交点,将两条线缝合。
- 投影正交补全:识别矩形时,若只捕捉到三条边,算法会根据平行与垂直约束,自动计算缺失的第四条边所在位置,从而完成全图识别。
无独立代码示例:该层通常以第一层(霍夫)检测出的线段为输入,做线段延长、求交与几何约束补全。
第五层:现代深度学习方法(端到端补充)
尽管传统几何法仍是精度主流,但近年也开始引入深度学习做「粗定位」:
- 使用目标检测网络(如 YOLO)先在整张大图中快速框选出「圆形区域」或「矩形区域」;
- 然后将该局部区域裁切出来,再交由传统的 RANSAC 或霍夫变换进行亚像素级精定位。
这种「粗检测 + 精拟合」的混合模式,大幅提升了在复杂背景下的识别速度。
无独立代码示例:YOLO 部分可参考
ultralytics,裁切后精定位部分复用本文档第一、二层的代码。
总结:识别与分类、拆线的协作关系
| 环节 | 输入 | 核心任务 | 核心算法 |
|---|---|---|---|
| 拆线 | 二值图像像素 | 提取顶点坐标 | Douglas-Peucker(拆分-合并) |
| 分类 | 完整的顶点序列 | 判定几何类型 | 计算边数、夹角、曲率 |
| 识别 | 含噪、断裂的像素/点集 | 从乱局中检出目标并还原完整参数 | 霍夫投票、RANSAC、子图同构、断点缝合 |
识别方法对比
| 方法 | 核心思想 | 优点 | 缺点 | 最佳适用场景 |
|---|---|---|---|---|
| 霍夫变换 | 民主投票:每个像素都为所有可能的图形投票,得票高者胜出。 | 对断裂、轻微变形鲁棒;实现简单。 | 参数空间随图形复杂度指数增长,计算量大;参数调优依赖经验。 | 图像背景干净,需要检测标准几何形状(直线、圆)的场景。 |
| RANSAC | 精英选拔:反复随机抽样,选出能获得最多拥护者(内点)的模型。 | 对大量离群噪声极其鲁棒;可灵活应用于各种数学模型。 | 需要设定较多阈值(如距离阈值、迭代次数);当内点占比过低时可能失效。 | 数据包含大量噪声或离群点,需要稳健拟合的场景。 |
在实际的工业应用中,通常会将两者结合:先用霍夫变换进行粗检测,定位出候选图形区域;再用 RANSAC 对候选区域内的点进行精拟合,以获得亚像素级别的精确参数。
附录:运行演示脚本
所有演示脚本位于 code/ 目录,各自独立可运行(合成测试图 → 运行算法 → 输出 figures/*.png)。
# 进入 code 目录后依次运行(依赖: numpy, matplotlib, opencv-python, scikit-learn, scikit-image)
python demo1_hough_lines.py # 示例一: 霍夫变换检测断裂直线
python demo2_hough_circles.py # 示例一(续): 霍夫梯度法检测断裂圆
python demo3_ransac_line.py # 示例二: RANSAC 稳健拟合直线
python demo4_ransac_circle.py # 示例三: RANSAC 稳健拟合圆
| 脚本 | 对应章节 | 输出图 |
|---|---|---|
code/demo1_hough_lines.py | 第一层 示例一 | figures/fig_hough_lines.png |
code/demo2_hough_circles.py | 第一层 示例一 | figures/fig_hough_circles.png |
code/demo3_ransac_line.py | 第二层 示例二 | figures/fig_ransac_line.png |
code/demo4_ransac_circle.py | 第二层 示例三 | figures/fig_ransac_circle.png |
code/figure_style.py | — | 中文字体与绘图公共配置(被各脚本引用) |