视觉机器人抓取实战:从物体定位到抓取估计的完整流程解析(附Python代码示例)
前阵子有个做自动化设备的朋友跟我吐槽,说他们工厂上了一套视觉抓取方案,调试了快两个月,识别精度上去了,可机械臂一抓还是经常掉东西。我问他检查过抓取点没有,他愣了一下——原来他一直在优化“看见物体”的部分,压根没认真对待“抓哪儿、怎么抓”这个环节。这正是视觉机器人抓取里最常见的误区:很多人以为装个相机、训练个检测模型就算“会抓取了”,实际上从物体定位到抓取估计是一条完整的技术链路,任何一环掉链子,整个系统就废。这篇文章我想把这套流程从头捋一遍,从物体怎么定位、抓取姿态怎么估计,到用Python写一个最小可用的抓取管道,把我实际调试中踩过的坑和经验一并交代清楚。不管你是刚入门机器人视觉的开发者,还是已经在做相关项目但被精度和成功率折磨的工程师,这篇应该都能给你一些直接的参考。
1. 先从“人去拿杯子”说起:视觉抓取系统的任务拆解
1.1 人类的举手投足背后,隐藏着三个连续的计算过程
你从桌面上拿起一杯水,这个动作看似毫不费力,但你如果把“拿杯子”拆解成计算过程,会发现大脑其实完成了三件极其复杂的事:第一,眼睛通过双目视差或者单目经验判断出杯子的位置——这是定位;第二,大脑根据杯子的形状、大小和当前朝向,决定手从哪个方向伸过去、虎口放在哪里、手指怎么合拢——这是抓取估计;第三,手在接近杯子的过程中,触觉和视觉反馈不断修正原始计划——这是闭环控制。视觉机器人抓取要复现的,正是这三件事的工程化版本。
有意思的是,这三件事的难度并不是平均分布的。物体定位经过深度学习几年的轰炸,现在拿起任何一个主流检测框架,都能在常见物体上取得不错的框检测效果。但抓取估计这件事,直到今天依然是学术和工业界的攻坚重点。你可以把人手理解成一只拥有二十多个自由度的灵巧末端,而工业夹爪往往只有一两个自由度——它的“抓取策略”极其受限,所以必须在抓取前就把姿态算得足够准。
1.2 一个完整的抓取系统由哪些模块组成
从工程落地来看,一套典型的视觉抓取系统包含以下模块,它们之间的数据流是严格单向的:
| 模块 | 核心职责 | 常见实现方式 | 输出数据 |
|---|---|---|---|
| 图像采集 | 获取场景的视觉信息 | RGB相机、深度相机(ToF/结构光/双目) | 彩色图+深度图/点云 |
| 物体定位 | 找到目标在图像/空间中的位置 | YOLO系列、Mask R-CNN、PointNet++等 | 2D边界框/分割掩码/3D位姿 |
| 抓取估计 | 生成末端执行器的抓取配置 | 采样评分法、GG-CNN、GraspNet等 | 抓取矩形/6-DoF抓取姿态 |
| 坐标变换 | 把图像坐标转换到机器人坐标 | Eye-to-Hand / Eye-in-Hand标定 | 机器人基座坐标系下的位姿 |
| 运动规划与控制 | 让机械臂按计划接近并抓取 | MoveIt、ROS、厂商SDK | 关节轨迹/末端轨迹 |
| 反馈修正 | 根据执行结果调整下一次尝试 | 视觉伺服、力觉反馈 | 修正量 |
这套管道里,最容易出问题的地方往往不在某一个单独的模块,而在模块之间的接口。典型例子:检测模型给出的边界框精度很高,但坐标变换用的外参矩阵标定已有几个月没更新,相机磕碰过一次,结果抓取位置偏出2厘米,照样失败。所以我个人的习惯是,做视觉抓取项目时先打通整条链路,再回头优化单个模块——先用比较粗糙但完整的流程跑起来,逐步替换瓶颈组件。
1.3 你实际需要的最小硬件配置
很多读者可能担心这套东西需要很贵的设备。其实做一个最小可行的视觉抓取原型,你只需要三样东西:一个深度相机或者单目相机(哪怕是普通USB摄像头也行,后面我讲怎么用平面约束弥补深度缺失)、一台装了Python和OpenCV的电脑、以及一台支持外部通信的机械臂(如果你暂时没有机械臂,可以用仿真环境替代,比如CoppeliaSim或者PyBullet)。这篇文章的代码例子我尽量做成不依赖具体机械臂品牌的形式,把核心算法部分拆出来,你换到自己的设备上时只需要修改坐标变换和控制接口两处。
2. 物体定位不是“认出它”就完事:从像素框到抓取点
2.1 三种定位技术路线:2D检测、实例分割与6D位姿估计
物体定位这个问题,根据你要抓取的物体和工况不同,有三种技术路线可以选,这里先做个对比,让你心里有个谱:
第一种是2D目标检测,用YOLO、Faster R-CNN这类模型输出目标物体的边界框。它的优点是速度快、标注成本低、技术非常成熟;缺点是只知道物体大致在图像里的方位,不知道物体具体朝向和深度,直接用于抓取需要额外补充信息。适合那些形状简单、放在固定平面上、姿态变化不大的物体,比如传送带上的盒子、固定托盘里的工件。
第二种是实例分割,典型代表是Mask R-CNN。它比边界框多输出一个像素级掩码,这样你可以计算出物体轮廓的精细信息——质心位置、主轴方向、轮廓面积等,这些信息对后续抓取点的选择非常有价值。缺点是推理慢一些,标注成本更高(要逐像素标注),但考虑到现在有很多预训练模型和标注工具,这个成本已经可以接受了。
第三种是6D位姿估计,直接输出物体在相机坐标系下的完整位置和姿态,包括旋转。代表方法有基于对应点的PVN3D、基于模板的配准方法,以及工业里常见的CAD模型匹配。这是三种路线里信息量最大、也最适合直接驱动机械臂的,但前提是你要有物体的CAD模型或足够的真实/合成训练数据。如果你的场景是“每次抓取同一种已知型号的工件”,直接上6D位姿估计是最省心的。
2.2 二值化不是土办法:在没有标注数据时的起点
很多人在真实项目里遇到的第一关不是模型效果差,而是根本没有标注数据。这时候我会先用OpenCV做基于颜色或基于深度的分割,先让整条链路跑起来,再决定要不要上深度学习。
比如在一个深色传送带上抓浅色工件的场景,HSV颜色阈值分割往往就能达到惊人的稳定性。我做过一个原型,工厂的工件是白色塑料壳,传送带是深蓝色,一个简单的inRange操作加轮廓提取,分割准确率就超过了95%,完全够用来验证后面的抓取算法。这时候你需要的代码很少,大致流程是:转HSV空间、设置颜色阈值生成掩码、形态学开闭运算去噪、找轮廓并过滤面积、计算每个轮廓的质心和方向角。
这种做法听起来“土”,但在工业场景里非常实用。因为很多自动化工位的光照是可控的、物体颜色和背景是有意设计的对比色,人为制造分割条件本来就是工程手段之一。等你验证完抓取流程以后,如果确实需要应对复杂多变场景,再换成基于深度学习的实例分割模型,到那时你已经有整条管道可以接,只需要替换一个函数,风险小得多。
2.3 坐标变换链路:从“像素坐标”到“机器人坐标”到底发生了几次变换
这是视觉抓取里最劝退新人的一部分,但也是我最想讲明白的一部分。很多项目失败就是死在这里——检测结果明明很准,但机械臂抓偏了,原因就是坐标没有对齐。
要把图像里的一个像素点变成机械臂末端的目标位置,你需要经过至少三次坐标变换:
第一次变换:像素坐标 → 相机坐标系。像素坐标(u,v)只是图像上的行列号,要恢复出相机坐标系下的三维点(Xc,Yc,Zc),需要用到相机内参矩阵K(焦距fx、fy,光心cx、cy)和深度值Zc。公式是:
# 像素坐标转相机坐标 fx, fy = camera_intrinsics["fx"], camera_intrinsics["fy"] cx, cy = camera_intrinsics["cx"], camera_intrinsics["cy"] def pixel_to_camera(u, v, depth): """u, v为像素坐标, depth为该像素处的深度值""" xc = (u - cx) * depth / fx yc = (v - cy) * depth / fy zc = depth return xc, yc, zc如果你用的是单目相机没有深度,那就需要事先知道物体所在平面的高度(比如桌面在工作台的高度是固定的),这样就能把Zc设为固定值,这招在实际中非常常用,相当于用平面约束替代深度传感器。
第二次变换:相机坐标系 → 机器人基座坐标系。这里需要外参矩阵(旋转矩阵R和平移向量t),也就是相机在机器人基座坐标系下的位姿。这个就是所谓的“手眼标定”结果。视觉抓取里有两种经典布局:相机固定在机械臂外的Eye-to-Hand,以及相机装在机械臂末端的Eye-in-Hand。前者外参标定一次基本不变;后者随着机械臂运动,外参一直在变,所以需要在线计算,通常会用机械臂当前末端位姿乘上相机相对末端的固定变换。
第三次变换:机器人基座坐标系 → 抓取路径规划。经过第二次变换,你已经拿到了一个机器人基座坐标系下的目标点,剩下的事情就是让机械臂运动规划器去生成一条无碰撞路径。这一步通常由MoveIt或者厂商自带的运动库完成。
我强烈建议你在写业务代码之前,先写一个坐标变换的单元测试:人为构造一个已知位置的点,从机器人坐标反算到像素坐标,再正算回来,验证整个变换链路没有遗漏。绝大多数坐标问题都能通过这种简单测试提前暴露。
3. 抓取估计:最容易被低估的核心难点
3.1 什么是“一个抓取”?——抓取表示的四种主流方式
先问一个基础问题:你要让机械臂夹住一个物体,最少需要哪些信息?答案是需要末端执行器的位置、接近方向、以及开口宽度。学术上对抓取有很多种表示方式,我在实战中主要接触四种:
1. 七维抓取表示:三维位置(x,y,z)+ 四元数姿态(qx,qy,qz,qw),外加一个指间开度。这是最通用的表示,直接对接机器人控制接口。你的算法最终输出的就是这东西。
2. 抓取矩形表示(GG-CNN那套思路):在2D图像平面里定义一个旋转矩形,矩形的中心对应抓取位置,矩形的方向对应夹爪的旋转角度,矩形的宽度对应夹爪开口大小。它计算简单、可视化直观,特别适合平面抓取任务。你自己写快速Demo时建议用这种,调试起来非常视觉化。
3. 6-DoF抓取姿态:在三维空间中定义完整的末端位姿,多用于从散乱堆叠的物体中抓取。GraspNet-1Billion就是这类数据的代表。
4. 机器人动作编码:直接预测机器人的关节角度或末端轨迹——端到端方案的思路。这个目前更多还是学术探索,工业里较少直接使用,因为可解释性和安全性不好把控。
我在实际落地时通常把抓取估计拆成两步:先用分割/检测结果生成一批候选抓取点,再用评分机制选出最优抓取。这样每一层结果都能可视化、能人工审查,不是一块黑盒。
3.2 基于几何分析的抓取点生成:质量矩、最小包围盒、主轴方向
如果你的物体是放在平面上、形状比较规则的(比如圆柱罐、矩形盒子、薄板件),完全不需要上复杂的网络,用几何分析就能得到不错的抓取点。这三种典型形状的抓取策略分别是:
- 圆柱罐:夹爪沿径向夹住罐体中部,抓取点位于投影轮廓的质心处,夹爪指向垂直于圆柱轴线。
- 矩形盒子:抓取点落在长边或短边的中心连线上,夹爪方向平行于盒子长边,这样接触面积大、摩擦力足。
- 不规则薄板:用PCA主成分分析求出轮廓的主轴,沿主轴方向在轮廓两侧对称取点作为抓取点。
下面这段代码演示了如何从分割掩码里提取轮廓、计算质心和方向角,然后生成一个旋转矩形抓取候选:
import cv2 import numpy as np def compute_contour_features(mask): """从二值掩码中提取轮廓、质心与方向""" contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(contours) == 0: return None # 取面积最大的轮廓作为目标物体 largest = max(contours, key=cv2.contourArea) # 计算质心 M = cv2.moments(largest) if M["m00"] < 1e-6: return None cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) # 用PCA求主轴方向 pts = np.squeeze(largest).astype(np.float32) if pts.ndim != 2 or pts.shape[0] < 2: return None mean = np.mean(pts, axis=0) centered = pts - mean cov = np.cov(centered.T) eigvals, eigvecs = np.linalg.eig(cov) main_dir = eigvecs[:, np.argmax(eigvals)] # 主轴方向向量 angle = np.degrees(np.arctan2(main_dir[1], main_dir[0])) # 用最小旋转矩形作为抓取矩形 rect = cv2.minAreaRect(largest) return { "centroid": (cx, cy), "angle": rect[2] if rect[2] is not None else angle, "rect": rect, "contour": largest }这里有一个细节:cv2.minAreaRect返回的角度范围是[-90°, 0°],但机械臂控制往往需要[0°, 180°]的绝对角度,所以拿到角度后要根据夹爪开口方向做一次归一化。这个坑我踩过一次,当时调试半天发现夹爪总是转错方向,最后发现是角度范围定义的不一致。
3.3 抓取质量评估:不是“能夹住”就行,还要夹得稳
生成一堆候选抓取点之后,面临的第二个问题是:哪个抓取最稳?这里我常用两套评估思路:
力学分析派:根据物体的摩擦锥、接触点位置、重心投影来判断抓取是否满足力闭合条件。大致逻辑是:如果夹爪两个接触点的连线不经过物体重心投影,那么物体被夹起来以后会倾斜甚至滑落。你把物体的重心投影与夹爪中心的位置差作为惩罚项,越小越好。
数据驱动派:训练一个抓取质量评分网络,输入物体点云或图像块,输出这个抓取姿态的成功率或质量分数。GraspNet的Graspness、GG-CNN的Q值都是这个思路。做法是:先对场景生成海量候选抓取,网络对每个抓取打一个0到1的质量分,最后取最高分的那个作为执行结果。
工业落地中,我倾向于两者结合:用几何规则快速过滤掉明显不合理的候选(比如夹爪落在物体外面、开口超过物体尺寸、接触点太靠近物体边缘),再用网络或经验评分对剩余候选排序。这样既保证了计算速度,又避免了纯数据方法的偶然失误。
这里给一个简单可用的过滤规则列表:
- 候选抓取矩形中心必须在分割掩码内部;
- 抓取矩形宽度(夹爪开口)必须在物体尺寸的30%到80%之间;
- 物体的重心投影到抓取中心距离不能超过物体半径的20%;
- 抓取方向与桌面法线的夹角不能超过45度,否则容易侧滑。
3.4 平面抓取与散乱堆叠抓取:复杂度完全不是一个量级
最后必须提醒一句:抓取估计的难度和场景形态强相关。平面抓取(把物体整齐放在桌面或托盘上)是一个相对可解的问题,上面提到的几何方法、2D抓取矩形都够用。但散乱堆叠抓取(物体杂乱地堆在料筐里)就是另一个世界了——你需要处理遮挡、物体互相支撑、分离操作,这时必须上6D位姿估计和更高级的抓取规划,成功率能做到80%以上在业界就已经算不错的系统了。
如果你刚开始做,我强烈建议先把平面抓取做扎实、做到成功率稳定在95%以上,再考虑挑战堆叠场景。一上来就做最难的需求,容易劝退。
4. 手写一个最小可用的视觉抓取管道(Python + OpenCV实现)
4.1 环境准备:OpenCV和numpy就够了
在正式写代码之前,先把环境配好。这个项目最小依赖只有OpenCV和NumPy,如果你要做深度学习检测,再按需加上PyTorch或者YOLO的环境。安装命令很简单:
# 建议使用Python 3.8以上版本 pip install opencv-python numpy验证安装是否成功,可以直接在终端跑:
import cv2 import numpy as np print(cv2.__version__) print(np.__version__)如果你的环境里没有Python,或者没装过pip,网络上搜索“python安装教程”、“vscode配置python环境”能找到非常详细的手把手教程,这里不展开了。我提一个新手常犯的错:只装了opencv-python,但在代码里import的时候用了import cv2没问题,可一旦涉及视频读取又安装了opencv-contrib-python,两个包冲突了,版本对不上各种报错。我的建议是只用opencv-contrib-python一个包,它包含的算法更全,和opencv-python不会冲突于同一环境。
4.2 第一步:读取图像并分割目标物体
我们先用一个假设的场景:蓝色背景的工作台上,放置一个黄色的马克杯,我们要定位它并估计抓取点。相机固定在工作台上方(Eye-to-Hand)。第一步是把黄色物体从背景中分割出来。
import cv2 import numpy as np cap = cv2.VideoCapture(0) # 或读取本地图像 def grab_frame(): ret, frame = cap.read() if not ret: raise IOError("无法读取相机图像") return frame def segment_yellow(frame): """通过HSV颜色阈值分割黄色物体""" hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # 黄色在HSV中的大致范围,需要根据实际光照微调 lower_yellow = np.array([20, 80, 80]) upper_yellow = np.array([35, 255, 255]) mask = cv2.inRange(hsv, lower_yellow, upper_yellow) # 形态学操作:先腐蚀后膨胀,去除噪点 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) return mask这段代码里最需要花时间调的就是HSV的阈值范围。我的建议是不用靠猜,写一个带滑动条的小工具,实时调试阈值,看掩码效果。你可以用OpenCV的createTrackbar快速做一个调参面板,把H_min、H_max、S_min等暴露出来,一边拖一边观察掩码,几分钟就能找到合适的范围。
光照对HSV分割的影响非常大。如果现场光照有频闪或变化,你可以先在采集端做白平衡修正,或者改用深度分割的方式。总之不要指望一两个固定阈值吃遍所有场景。
4.3 第二步:计算候选抓取点
在拿到分割掩码后,我们调用前面定义的compute_contour_features函数,提取轮廓的质心、角度和最小旋转矩形,然后生成抓取矩形候选。这一步是“从分割结果到抓取指令”的桥梁。
def estimate_grasp_from_mask(mask): """从掩码中估计单点抓取""" result = compute_contour_features(mask) if result is None: return None (cx, cy), angle, rect = result["centroid"], result["angle"], result["rect"] center, (w, h), ang = rect # 保证夹爪开口宽度不等于物体轮廓宽度,这里取轮廓短边的60% grasp_width = min(w, h) * 0.6 # 抓取中心仍然是物体质心 grasp_center = (int(cx), int(cy)) # 夹爪方向垂直于主轴方向(对于圆柱形物体,径向夹持更稳) grasp_angle = angle + 90 return { "grasp_center": grasp_center, "grasp_angle": grasp_angle, "grasp_width": grasp_width, "object_rect": rect }这段逻辑是:对平面放置的杯子,最优抓取方向是沿着杯子轮廓的短轴方向夹持,比如马克杯抓把手两侧的杯身比抓杯沿更容易夹稳。抓取宽度设为轮廓短边的60%,是为了保证夹爪能包住物体但又不至于太宽而夹空。这些参数后续可以根据实验效果再调节。
4.4 第三步:把抓取点从像素坐标映射到机器人坐标
现在我们已经有了目标在图像上的抓取中心、角度、开口宽度,要把它们发送给机械臂,必须转换到机器人基座坐标系。假设我们已经完成了手眼标定,得到了外参矩阵extrinsic(4x4)和相机内参K。用第2.3节里的坐标变换函数,先求抓取中心在相机坐标系下的坐标,再转到机器人坐标系。
def pixel_to_robot(u, v, depth, K, extrinsic, plane_z=0.0): """像素坐标 -> 相机坐标(利用固定平面高度) -> 机器人基座坐标""" # 像素坐标转相机坐标,假设物体在固定高度平面 plane_z fx, fy = K[0,0], K[1,1] cx, cy = K[0,2], K[1,2] # 深度值来自相机测量或平面假设 zc = depth xc = (u - cx) * zc / fx yc = (v - cy) * zc / fy # 相机坐标 -> 机器人基座坐标 cam_point = np.array([xc, yc, zc, 1.0]) robot_point = extrinsic @ cam_point return robot_point[:3] # 使用示例 K = np.array([[600.0, 0, 320.0], [0, 600.0, 240.0], [0, 0, 1.0]]) extrinsic = np.eye(4) # 这里替换为实际标定结果 # depth可以用深度相机读取,也可以用已知平面高度替代 depth_value = 0.5 # 假设物体在相机前方0.5米处 robot_pos = pixel_to_robot(cx, cy, depth_value, K, extrinsic)这里关于深度值要补充一下:如果你用的是RGB-D相机,可以直接从深度图读取对应像素的深度值;如果你是单目相机,那就要保证物体总是落在同一个固定高度的平面上,这个假设在传送带抓取、桌面抓取里都成立,很实用。真要处理随意堆叠的物体,就别省深度相机的钱了。
4.5 第四步:把抓取指令发送给机械臂并执行抓取
最后就是和机械臂的通信了。不同品牌的机械臂API差异比较大,但基本都遵循同样的模式:建立TCP连接(或者调用ROS服务),把目标位姿发送给运动规划器,规划器生成轨迹后执行。这里给出一个用TCP Socket通信的伪代码示例,你换成自己用的机械臂SDK就行:
import socket import json def send_grasp_command(robot_ip, port, pose, grip_width): """pose: (x, y, z, roll, pitch, yaw),grip_width: 夹爪开口宽度""" command = { "type": "grasp", "pose": list(pose), "grip_cmd": grip_width } with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((robot_ip, port)) s.sendall(json.dumps(command).encode("utf-8")) response = s.recv(1024) return response.decode("utf-8")工业机器人比如UR、ABB、发那科都有自己的网络接口或者ROS驱动,UR的滑动接口可以直接通过socket发URScript代码;国产的一些协作臂也有Python SDK。不管用什么,核心都是把你的抓取位姿转成目标的TCP位姿,然后确保夹爪信号和机械臂运动之间有一个到达同步的机制——夹爪不能在运动过程中提前闭合,否则会撞到物体。
4.6 把整个流程串起来:主循环代码
最后把上面所有模块在main循环里串起来。注意加一个简单的状态机:检测、接近、抓取、复位,每一步都要有明确的成功判据。
def main(): while True: frame = grab_frame() mask = segment_yellow(frame) grasp = estimate_grasp_from_mask(mask) if grasp is None: print("未检测到目标物体") continue # 将抓取点转换到机器人坐标 u, v = grasp["grasp_center"] depth = get_depth_from_camera(u, v) # 或用平面假设 robot_pos = pixel_to_robot(u, v, depth, K, extrinsic) # 转换角度并组成6D位姿 angle_rad = np.deg2rad(grasp["grasp_angle"]) pose = (*robot_pos, 0.0, 0.0, angle_rad) # 假设roll/pitch固定为0 # 发送前可视化 show_result(frame, mask, grasp) key = cv2.waitKey(1) & 0xFF if key == ord('g'): # 按g键触发一次抓取 send_grasp_command(ROBOT_IP, PORT, pose, grasp["grasp_width"]) time.sleep(2.0) elif key == ord('q'): break我在这个循环里加入手动按键触发而不是自动连续抓取,是为了在调试阶段避免机械臂失控。建议你在真实抓取之前,始终保留一个中断开关。等到系统运行稳定了,再改成全自动模式,并且加上抓取失败的视觉重试策略——第一次没抓到,机械臂回到原点,相机重新定位,调整抓取点再试一次,比一次失败就报警停机要实用得多。
5. 这套流程在真实工位上踩过的坑
5.1 标定精度:一天一检,别等抓偏了才想起来
手眼标定的精度直接决定整个系统的上限。你算法再牛,外参矩阵偏个0.5度,到了工作距离1米处就是接近1厘米的位置偏差,夹爪很可能直接撞到物体而不是包住它。我的习惯是每次开机后先跑一个标定验证程序:放一个已知位置的标定板,用视觉算出它当前的位姿,和理论位姿比一比,误差超过阈值就提醒重新标定。另外相机任何一次拆卸重装、哪怕只是拧松了固定螺丝,外参都要重标。别信“装回去还是原样”这种话,毫米级定位不允许这种事发生。
5.2 夹爪速度和位姿的配合:接近方向要沿着抓取方向
我发现很多新手犯的一个错误是:机械臂移动到一个目标点后,就直接让夹爪沿某个固定方向(比如竖直向下)去夹取,完全没有考虑抓取方向要和夹爪的接近方向一致。比如你生成的抓取角度是30度,那机械臂的末端姿态在接近过程中就应该已经转到30度,并且沿着这个方向慢慢靠近,这样才能保证夹爪和物体正确对位。如果先直接下移到位再旋转,物体可能已经被撞歪了。这里有一个实用技巧:把接近运动拆成两段——先在安全高度快速移到抓取点的正上方或正前方,然后以低速沿抓取方向直线接近,这样既安全又高效。
5.3 光照是分割的头号敌人:用深度信息补足、用数据增强预防
前面提到用颜色分割启动项目,但在真实车间里,光照变化是很头疼的。日光灯频闪、设备阴影移动、工件表面反光,都可能让同一个阈值时而漏检时而误检。我试过的几个有效手段包括:在硬件层面加遮挡板避免直射光;在软件层面做自适应阈值或者高斯滤波后再分割;更可靠的是把颜色分割换成基于深度/高度阈值的分割——深度相机不受光照影响,放一个平面上的物体高度必然是连续的一块,直接用深度范围切割就能拿到轮廓。
如果你决定用深度学习分割模型,别忘了做颜色数据增强,模拟各种光照条件;真实环境数据也尽量多采几个时间段,别只在调试时那个固定光照下采。
5.4 失败重试策略:抓取系统不是一次成功的游戏
再好的抓取估计也会遇到失败——物体重心估计偏了、夹爪打滑、物体表面纹理导致视觉误判。一个成熟的视觉抓取系统必须有失败重试机制。我的做法是:当夹爪闭合时,如果力传感器或电流检测发现闭合距离明显小于预期(说明夹空了),或者夹起后视觉检测发现物体还在原处,就触发重抓流程。重抓时不要完全复现上一次的抓取点,而是改变接近角度或者把抓取点往重心方向偏移一些,有时候换一个方位就成功了。这套逻辑对于提高整线直行率帮助巨大。
还有一个小经验:在抓取执行完成后,不要着急进入下一个循环,花0.5秒用相机确认一下物体是否真的被移走了。这一步能过滤掉大量“我以为抓起来了”的情况,这在无人值守运行时尤其重要。
5.5 从仿真到实机:先在仿真里验证,再到实物上微调
最后一个建议,也是我自己的习惯:任何新的抓取算法,先在PyBullet或CoppeliaSim这类仿真环境里跑。PyBullet的好处是它有完整的URDF机器人模型,有视觉传感器仿真,还能模拟夹爪和物体之间的物理接触,调试抓取策略非常方便。仿真里跑得稳定了,再上真机,主要工作就是调相机内外参、校准坐标系,算法逻辑基本不用大改。当然,仿真和现实的差距也是存在的——尤其是摩擦力、表面变形这些东西,所以仿真通过了不代表真机一次成功,但至少能帮你筛掉相当多的低级bug,省下大量在真机上反复试错的时间。
拿我最近做的一个密封圈抓取项目举例:仿真里抓取成功率98%,上真机第一次只有70%,后来发现是相机安装的支架有点软,机械臂动作时支架轻微晃动导致标定失效。换了刚性支架以后,真机成功率稳定在了93%。这个故事说明两件事:一是仿真验证能帮你把算法本身的坑排掉;二是真机的物理安装和环境细节,往往才是最终决定成败的因素。