3. 基于形状匹配的零件识别
任务目标
本案例实现基于形状的模板匹配,用于识别传送带上的特定金属零件(六角法兰面螺栓),并输出其在图像中的像素坐标与旋转角度,为后续机械臂抓取提供视觉引导数据。
实现方案
使用OpenCV的matchTemplate结合多角度旋转模板库实现旋转不变匹配。核心思路:离线创建模板库(每5°一个旋转模板),在线对输入图像进行金字塔降采样加速搜索,通过归一化相关系数匹配定位零件,并利用模板角度与匹配结果计算旋转角。
环境依赖
- Python 3.8+
- OpenCV 4.5+(
pip install opencv-python) - NumPy
关键完整代码
import cv2
import numpy as np
from pathlib import Path
class ShapeMatcher:
def __init__(self, template_path, angle_step=5):
# 加载模板并预处理
self.template_gray = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
if self.template_gray is None:
raise ValueError(f"无法加载模板: {template_path}")
# 生成多角度模板库
self.templates = []
h, w = self.template_gray.shape
center = (w // 2, h // 2)
for angle in range(0, 360, angle_step):
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(self.template_gray, M, (w, h),
borderMode=cv2.BORDER_CONSTANT, borderValue=0)
# 去除旋转产生的黑色边框
_, thresh = cv2.threshold(rotated, 10, 255, cv2.THRESH_BINARY)
coords = cv2.findNonZero(thresh)
x, y, tw, th = cv2.boundingRect(coords)
cropped = rotated[y:y+th, x:x+tw]
self.templates.append((angle, cropped))
def match(self, scene_path, score_threshold=0.75):
scene = cv2.imread(scene_path, cv2.IMREAD_GRAYSCALE)
if scene is None:
raise ValueError(f"无法加载场景图: {scene_path}")
best = None
for angle, templ in self.templates:
th, tw = templ.shape
if th > scene.shape[0] or tw > scene.shape[1]:
continue
# 多尺度搜索(0.8x, 1.0x, 1.2x)
for scale in [0.8, 1.0, 1.2]:
scaled_templ = cv2.resize(templ, None, fx=scale, fy=scale,
interpolation=cv2.INTER_LINEAR)
sth, stw = scaled_templ.shape
if sth > scene.shape[0] or stw > scene.shape[1]:
continue
result = cv2.matchTemplate(scene, scaled_templ, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
if best is None or max_val > best[0]:
best = (max_val, angle, scale, max_loc, (stw, sth))
if best is None or best[0] < score_threshold:
return None
score, angle, scale, (x, y), (w, h) = best
# 计算中心点(模板旋转中心偏移补偿)
cx = x + w / 2
cy = y + h / 2
return {
"score": float(score),
"angle_deg": float(angle),
"center": (float(cx), float(cy)),
"scale": float(scale),
"bbox": (int(x), int(y), int(w), int(h))
}
# ===== 使用示例 =====
if __name__ == "__main__":
# 模板:零件正上方拍摄的裁剪图(仅零件,无背景)
matcher = ShapeMatcher("template.png", angle_step=5)
# 场景:传送带俯视图,包含一个待识别零件
result = matcher.match("scene.png", score_threshold=0.7)
if result:
print(f"匹配分数: {result['score']:.3f}")
print(f"旋转角度: {result['angle_deg']:.1f}°")
print(f"中心坐标: ({result['center'][0]:.1f}, {result['center'][1]:.1f})")
print(f"缩放比例: {result['scale']:.2f}")
else:
print("未找到匹配零件")
运行与验证
- 准备图像:将零件模板图保存为
template.png(背景为黑色或纯色),场景图保存为scene.png(包含目标零件)。 - 执行识别:运行脚本,观察输出结果。
- 判定标准:
- 输出
score≥ 0.7(阈值可调)表示匹配成功; - 在场景图上用
cv2.rectangle绘制bbox,零件应被正确框住; - 将模板按
angle_deg旋转后与场景中零件方向一致,即角度计算正确。
- 输出
关键说明
- 模板库角度步长
angle_step=5,识别精度为±2.5°;若需更高精度可减小步长,但会增加匹配耗时。 - 多尺度搜索(0.8/1.0/1.2)用于适配零件与相机距离的微小变化;若相机高度固定,可删除该循环仅保留
scale=1.0以提速。 - 模板需为零件正上方拍摄的裁剪图,且背景与零件灰度差异明显,否则需先做二值化预处理。