3. 边缘检测与矩形工件抓取

任务目标

本案例实现从单目相机图像中检测矩形工件(如手机中框、PCB板),输出其四个角点在图像坐标系中的像素坐标,供机械臂抓取定位使用。核心算法为Canny边缘检测 + 霍夫直线检测 + 直线交点计算。

环境依赖

Python 3.8+,OpenCV 4.5+,NumPy。若需实际相机采集,另需OpenCV相机驱动(如USB相机),但本案例提供静态图像作为输入,保证可独立复现。

关键完整代码

import cv2
import numpy as np

def detect_rectangle_corners(image_path):
    """
    检测图像中最大矩形工件的四个角点
    返回: corners (4x2 float array), 按左上、右上、右下、左下排序
    """
    # 读取图像
    img = cv2.imread(image_path)
    if img is None:
        raise ValueError(f"无法读取图像: {image_path}")
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 1. 高斯模糊降噪
    blurred = cv2.GaussianBlur(gray, (5, 5), 1.5)
    
    # 2. Canny边缘检测
    edges = cv2.Canny(blurred, 50, 150)
    
    # 3. 霍夫直线检测
    lines = cv2.HoughLinesP(edges, 1, np.pi/180, 
                            threshold=80, 
                            minLineLength=60, 
                            maxLineGap=10)
    if lines is None:
        return None
    
    # 4. 筛选直线:按角度聚类为水平/垂直两组
    h_lines, v_lines = [], []
    for line in lines:
        x1, y1, x2, y2 = line[0]
        angle = np.degrees(np.arctan2(y2-y1, x2-x1))
        if abs(angle) < 20 or abs(abs(angle)-180) < 20:
            h_lines.append((x1, y1, x2, y2))
        elif abs(abs(angle)-90) < 20:
            v_lines.append((x1, y1, x2, y2))
    
    if len(h_lines) < 2 or len(v_lines) < 2:
        return None
    
    # 5. 对每组直线按截距排序,取最外侧两条
    def line_intercept(line):
        x1, y1, x2, y2 = line
        if abs(x2-x1) < 1e-6:  # 垂直线
            return x1
        return y1 - (y2-y1)/(x2-x1) * x1
    
    h_lines.sort(key=line_intercept)
    v_lines.sort(key=line_intercept)
    
    top, bottom = h_lines[0], h_lines[-1]
    left, right = v_lines[0], v_lines[-1]
    
    # 6. 计算直线交点
    def line_intersection(l1, l2):
        x1, y1, x2, y2 = l1
        x3, y3, x4, y4 = l2
        denom = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
        if abs(denom) < 1e-6:
            return None
        px = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / denom
        py = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / denom
        return (int(px), int(py))
    
    # 左上: top∩left, 右上: top∩right, 右下: bottom∩right, 左下: bottom∩left
    corners = [
        line_intersection(top, left),
        line_intersection(top, right),
        line_intersection(bottom, right),
        line_intersection(bottom, left)
    ]
    
    if any(c is None for c in corners):
        return None
    
    return np.array(corners, dtype=np.float32)

# 最小调用入口
if __name__ == "__main__":
    # 生成测试图像:黑色背景上的白色矩形
    test_img = np.zeros((400, 600), dtype=np.uint8)
    cv2.rectangle(test_img, (150, 100), (450, 300), 255, -1)
    cv2.imwrite("test_rect.png", test_img)
    
    corners = detect_rectangle_corners("test_rect.png")
    if corners is not None:
        print("检测到的角点坐标:")
        for i, pt in enumerate(corners):
            print(f"  角点{i+1}: ({pt[0]}, {pt[1]})")
        # 预期输出(由代码逻辑直接推出):
        # 角点1: (150, 100)  角点2: (450, 100)
        # 角点3: (450, 300)  角点4: (150, 300)
    else:
        print("未检测到矩形")

运行与验证

运行方式:将上述代码保存为rect_detect.py,在终端执行:

python rect_detect.py

验证方法:程序会生成一张600×400像素的测试图,其中白色矩形左上角为(150,100)、右下角为(450,300)。运行后观察控制台输出的四个角点坐标:

  • 若输出与预期坐标一致(允许±1像素误差),说明检测流程正确;
  • 若输出None或坐标偏差过大,请检查OpenCV版本及Canny阈值参数。

实际工件适配:将image_path替换为实际工件图像,需保证工件与背景对比明显、光照均匀。若检测失败,可依次调整Canny阈值(50,150)、霍夫threshold(80)和minLineLength(60)。

常见问题

  • 检测到多余直线:工件表面纹理或反光会产生干扰线,可增大Canny高阈值或霍夫threshold值过滤短线段。
  • 角点排序错乱:本算法按截距取最外侧直线,适用于矩形近似水平/垂直放置的场景。若工件旋转角度较大,需先计算直线角度并旋转校正。
  • 抓取坐标转换:本案例输出像素坐标,实际抓取需通过相机标定矩阵转换为机械臂基坐标系下的三维坐标。