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("未找到匹配零件")

运行与验证

  1. 准备图像:将零件模板图保存为template.png(背景为黑色或纯色),场景图保存为scene.png(包含目标零件)。
  2. 执行识别:运行脚本,观察输出结果。
  3. 判定标准:
    • 输出score ≥ 0.7(阈值可调)表示匹配成功;
    • 在场景图上用cv2.rectangle绘制bbox,零件应被正确框住;
    • 将模板按angle_deg旋转后与场景中零件方向一致,即角度计算正确。

关键说明

  • 模板库角度步长angle_step=5,识别精度为±2.5°;若需更高精度可减小步长,但会增加匹配耗时。
  • 多尺度搜索(0.8/1.0/1.2)用于适配零件与相机距离的微小变化;若相机高度固定,可删除该循环仅保留scale=1.0以提速。
  • 模板需为零件正上方拍摄的裁剪图,且背景与零件灰度差异明显,否则需先做二值化预处理。