1. 基于颜色分割的简单物体抓取

基于颜色分割的简单物体抓取 学习卡片

任务目标

构建一个最小可复现的视觉抓取系统:通过摄像头(或静态图像)识别红色方块,计算其中心在图像中的像素坐标,并转换为机器人基座坐标系下的抓取位置,最后输出该位置供机器人执行抓取。本案例聚焦视觉识别与坐标转换核心链路,不涉及具体机器人运动学求解。

环境与依赖

  • Python 3.8+
  • OpenCV(pip install opencv-python)
  • NumPy(pip install numpy)
  • 若使用真实相机:任意USB摄像头;若使用静态图:任意包含红色方块的图片

实现思路

系统分为三个步骤:颜色分割(将RGB转HSV,用阈值提取红色区域)、轮廓提取与中心计算(找到最大红色轮廓并求质心)、坐标转换(利用相机内参和已知高度的单应性矩阵,将像素坐标映射到机器人平面坐标)。为简化且保证可复现,本案例使用静态图像输入,并假设相机光轴垂直于抓取平面(即平面单应关系)。

关键完整代码

import cv2
import numpy as np

def detect_red_block(image_path):
    """读取图像,返回红色方块中心像素坐标及掩膜"""
    img = cv2.imread(image_path)
    if img is None:
        raise FileNotFoundError(f"无法读取图像: {image_path}")
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 红色在HSV中有两个区间(0°附近),分别提取后合并
    lower_red1 = np.array([0, 120, 70])
    upper_red1 = np.array([10, 255, 255])
    lower_red2 = np.array([170, 120, 70])
    upper_red2 = np.array([180, 255, 255])

    mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
    mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
    mask = cv2.bitwise_or(mask1, mask2)

    # 形态学去噪
    kernel = np.ones((5, 5), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        return None, None, mask

    # 取最大轮廓(假设目标为最大红色区域)
    largest = max(contours, key=cv2.contourArea)
    M = cv2.moments(largest)
    if M["m00"] == 0:
        return None, None, mask
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])
    return (cx, cy), largest, mask

def pixel_to_robot(pixel_point, homography):
    """将像素坐标通过单应矩阵转换为机器人平面坐标(单位:mm)"""
    pt = np.array([pixel_point[0], pixel_point[1], 1.0])
    robot = homography @ pt
    robot /= robot[2]
    return robot[0], robot[1]

if __name__ == "__main__":
    # ===== 配置 =====
    IMAGE_PATH = "red_block.jpg"   # 输入图像
    # 单应矩阵:通过4组像素-机器人坐标对标定获得(此处为示例值)
    # 假设图像640x480,工作区域300mm x 200mm,原点在图像中心
    H = np.array([
        [0.5, 0, -160],
        [0, -0.5, 120],
        [0, 0, 1]
    ], dtype=float)

    # ===== 执行 =====
    center, contour, mask = detect_red_block(IMAGE_PATH)
    if center is None:
        print("未检测到红色方块")
    else:
        robot_x, robot_y = pixel_to_robot(center, H)
        print(f"像素中心: ({center[0]}, {center[1]})")
        print(f"机器人抓取位置: ({robot_x:.1f} mm, {robot_y:.1f} mm)")

        # 可视化(可选)
        img = cv2.imread(IMAGE_PATH)
        cv2.drawContours(img, [contour], -1, (0, 255, 0), 2)
        cv2.circle(img, center, 5, (255, 0, 0), -1)
        cv2.imshow("Detection", img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

运行与验证

  1. 准备一张包含红色方块的图像(如red_block.jpg),背景尽量简单,红色区域为画面中最大红色连通域。
  2. 运行脚本:python grasp_detection.py
  3. 通过条件:终端输出像素中心坐标和机器人坐标;可视化窗口中红色方块被绿色轮廓框住,中心有蓝色圆点标记。
  4. 坐标验证:将图像中心(320,240)代入单应矩阵,应得到(0,0)——即机器人原点对应图像中心。若红色方块位于图像中心偏右,则robot_x应为正值,偏上则robot_y为正值(因y轴翻转)。

常见问题

  • 检测不到红色:调整HSV阈值,特别是光照变化时需重新标定;可先输出mask图观察阈值是否合适。
  • 单应矩阵如何获得:实际使用时需用棋盘格等标定板,取4个以上角点的像素坐标与机器人实际坐标,用cv2.findHomography计算。本案例的H为演示值,仅用于验证流程。
  • 多目标场景:当前实现取最大红色区域,若需抓取指定目标,需增加尺寸或位置筛选逻辑。
图像平面 (像素) (cx, cy) u v 单应矩阵 H 机器人平面 (mm) (X, Y) X Y 像素→毫米 (需标定) 核心链路:HSV阈值分割 → 最大轮廓质心 → 单应变换 → 抓取坐标 红色双区间阈值(0°/180°) + 形态学开闭运算去除噪点