2. 传感器选型与标定:3D相机选型与手眼标定

做无序分拣,第一步就是选一双好「眼睛」。我见过不少项目,算法调得再好,相机选错了,照样抓不准。今天咱们就把3D相机选型和标定这件事聊透。

2.1 三种主流3D相机技术对比

目前工业分拣用得最多的就三种:结构光、ToF、双目。各有各的脾气,我一个个说。

技术类型 原理 典型精度 适用场景 典型代表
结构光 投射编码条纹,三角法解算深度 0.1~1 mm 近距离高精度分拣(<1m) Ensenso、Kinect v1
ToF 发射调制光脉冲,测量飞行时间 1~10 mm 中远距离、快速抓取(0.5~5m) Basler ToF、Kinect v2
双目 双摄像头视差匹配,三角测量 1~5 mm 室外/大视野、纹理丰富场景 ZED、Intel RealSense D435

我的经验:结构光在反光件上容易「翻车」。有一次分拣镀铬金属件,结构光直接丢帧。后来换成双目加主动纹理投影,才算稳住。选型时一定拿实际工件去测,别只看参数表。

2.2 相机内参标定

内参标定,说白了就是搞清楚相机自己的「脾气」——焦距、主点、畸变系数。不标定,你算出来的3D坐标全是歪的。

2.2.1 标定流程

  1. 准备标定板:棋盘格或圆点阵,打印要平整,贴在刚性板上。
  2. 采集图像:至少15~20张,覆盖不同角度、距离、姿态。
  3. 角点检测:用OpenCV的 findChessboardCorners 或 findCirclesGrid。
  4. 求解内参:调用 calibrateCamera,得到相机矩阵K和畸变系数。
  5. 验证重投影误差:误差小于0.5像素才算合格。

避坑指南:我曾经用A4纸打印棋盘格贴在纸箱上标定,结果纸箱微弯,标定误差直接飙到2像素。后来换了3mm铝板贴标定板,误差降到0.2像素。记住——标定板必须平整。

2.2.2 代码示例

import cv2
import numpy as np

# 棋盘格尺寸(内角点)
pattern_size = (9, 6)
objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)

objpoints = []  # 世界坐标系点
imgpoints = []  # 图像坐标系点

for fname in image_files:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
    if ret:
        objpoints.append(objp)
        imgpoints.append(corners)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objpoints, imgpoints, gray.shape[::-1], None, None)

print("相机内参矩阵:\n", mtx)
print("畸变系数:\n", dist)

2.3 手眼标定

相机标好了,还得知道它和机器人手臂的相对位置。这就是手眼标定。分两种:Eye-in-Hand(眼在手上)和 Eye-to-Hand(眼在手外)。

2.3.1 两种方式对比

项目 Eye-in-Hand Eye-to-Hand
相机安装位置 固定在机器人末端 固定在工作台上方
标定目标 求相机→末端(手眼矩阵) 求相机→基座(手眼矩阵)
视野特点 随机械臂移动,视野变化 视野固定,覆盖整个工作区
典型应用 小件抓取、近距离操作 大范围分拣、料筐抓取
标定复杂度 中等(需多次移动末端) 较低(一次标定即可)

注意:Eye-in-Hand标定时,机器人末端姿态变化要足够大,否则解算会退化。我见过有人只平移不旋转,结果标定矩阵全是错的。至少让末端做3个以上不同方向的旋转。

2.3.2 标定原理

核心方程是 AX = XB,其中A是机器人末端位姿变化,B是相机看到的标定板位姿变化,X就是我们要的手眼矩阵。用Tsai两步法或OpenCV的 calibrateHandEye 求解。

import cv2
import numpy as np

# R_gripper2base, t_gripper2base: 机器人末端位姿(从机器人控制器读取)
# R_target2cam, t_target2cam: 标定板在相机坐标系下的位姿(从PnP求解)
R_cam2gripper, t_cam2gripper = cv2.calibrateHandEye(
    R_gripper2base, t_gripper2base,
    R_target2cam, t_target2cam,
    method=cv2.CALIB_HAND_EYE_TSAI
)

print("手眼旋转矩阵:\n", R_cam2gripper)
print("手眼平移向量:\n", t_cam2gripper)

2.3.3 标定步骤(以Eye-in-Hand为例)

  1. 将标定板固定在工作台上,位置不变。
  2. 控制机器人末端移动到不同姿态,记录末端位姿 T_base2gripper。
  3. 在每个姿态下,用相机拍摄标定板,通过PnP求解 T_cam2target。
  4. 收集至少15组数据,调用 calibrateHandEye 求解。
  5. 验证:用标定结果将相机检测到的点转换到机器人基座下,与真实位置对比。

我的习惯:标定完成后,我会在机器人工作空间内随机取5个点,用标定结果反算,再手动示教机器人去碰那个点。误差在2mm以内才算通过。这一步别省,省了后面调试抓取有你哭的。

2.4 本章知识体系

传感器选型与标定知识体系 传感器选型与标定知识体系 3D相机选型与标定 3D相机选型 相机内参标定 手眼标定 结构光 ToF 双目 标定板准备 角点检测 求解内参 Eye-in-Hand Eye-to-Hand AX=XB求解 选型决定精度上限,标定决定精度下限 两者缺一不可,否则抓取规划就是空中楼阁

2.5 选型与标定的常见坑

  • 选型只看分辨率:分辨率高不代表精度高。结构光的精度取决于投射器基线长度,ToF的精度受多路径干扰影响大。我建议你拿实际工件去现场测,别信宣传页。
  • 标定板随便用:标定板不平、打印精度差、反光,都会导致标定失败。花几十块钱买块陶瓷标定板,比打印纸强一百倍。
  • 手眼标定数据太少:少于10组数据,解算结果不稳定。我一般采集20~30组,覆盖工作空间的不同区域。
  • 忽略畸变校正:有些人不做畸变校正直接做手眼标定,结果边缘区域误差大得离谱。先内参标定,再手眼标定,顺序别搞反。

一句话总结:选型看工件,标定看流程。结构光适合小件高精度,ToF适合大范围快速,双目适合纹理丰富场景。内参标定是基础,手眼标定是桥梁。这两步做扎实了,后面的抓取规划才有意义。