
*来源: 第一届3D视觉创新应用竞赛 | 参赛者: 赵晨昊 | 单位:** 深圳大学 *
*硬件平台:** Orbbec Astra Pro 深度相机 + Zora P1 开发板 + 川崎6轴机器人 + 气泵与气动夹具 *
*GitHub:** RobotV_Disorderly-grabbing *
一、项目亮点速览 ✨
想象一下:工厂流水线上,零件乱七八糟地堆在箱子里,机械臂却能像人一样"看一眼"就精准抓起来——不用工人事先摆好,不用担心重叠遮挡。🏭
这不是科幻电影,而是这支团队用 RGB-D 相机 + 6轴机械臂 + 智能算法 实现的真实场景!
六大核心能力一览:
| 🏷️ 核心能力 | 🔧 技术实现 | 💡 亮点 |
|---|---|---|
| 📷 RGB-D 三维感知 | Astra Pro 深度相机实时采集点云 | 毫米级深度精度,直接获取三维信息 |
| 🤖 6轴自由抓取 | 川崎6轴机器人 + 气动夹具 | 任意姿态下稳定抓取,适应复杂工件 |
| 🎯 无序抓取 | 平面拟合 + 法向量求解抓取姿态 | 任意摆放都能抓,告别"排排坐"时代 |
| 📐 4点手眼标定 | SVD分解求解 RT 矩阵 | 快速标定,无需强度信息 |
| 🧠 智能点云处理 | 聚类分割 + 区域生长 + 直通/体素滤波 | 自动识别目标,滤除干扰 |
| 🔗 UDP 实时通信 | Qt 封装网络通信,机器人秒响应 | 低延迟,工业级可靠性 |
二、背景与意义 🌍
2.1 行业痛点:2D视觉的"天花板"
基于 2D 机器视觉的平面抓取技术,在工业界已经非常成熟——但有一个致命前提:
| ⚠️ 2D方案的硬伤 | 💥 实际后果 |
|---|---|
| 零件必须同一平面分开摆放 | 需要人工预先排列,增加工序 |
| 不能有重叠或遮挡 | 杂乱料箱直接"瞎眼" |
| 只定位二维位置,无三维姿态 | 无法适应复杂工件的抓取角度 |
在汽车装配、工件制造等实际生产中,零件往往是杂乱无章地堆放在料箱里的。这时候,传统2D视觉方案直接"躺平"……😅
2.2 我们的思路:上3D!
核心idea: 用 RGB-D 相机获取真实三维点云 → 智能算法分析工件姿态 → 机械臂以正确角度精准抓取
| 🆚 对比维度 | 📸 2D 视觉方案 | 🎯 本方案(RGB-D + 3D) |
|---|---|---|
| 摆放要求 | 必须整齐排列 | ✅ 任意摆放,乱堆也行 |
| 重叠/遮挡 | ❌ 无法处理 | ✅ 通过三维信息区分 |
| 抓取姿态 | 仅2D位置 | ✅ 6维位姿(X, Y, Z, O, A, T) |
| 适用场景 | 简单平面分拣 | ✅ 复杂工业场景 |
三、系统硬件架构 🛠️
3.1 硬件全家福
| 🔩 设备 | 📋 型号/规格 | 🎯 功能 | ⭐ 关键特性 |
|---|---|---|---|
| 💻 开发板 | Zora P1(奥比中光定制) | 边缘计算主控,运行视觉算法 | 适配 Astra 系列相机,支持 Ubuntu/Android |
| 📷 深度相机 | Astra Pro(单目结构光) | RGB + Depth 数据采集 | 0.6m~8m 深度范围,640×480 分辨率 |
| 🦾 工业机器人 | 川崎 6轴机器人 | 执行抓取动作 | 6自由度,高精度重复定位 |
| 🔧 末端执行器 | 气动夹具 | 抓取工件 | 气泵驱动,夹持力可调 |
| 🌬️ 气源系统 | 气泵 + 电控柜 | 为夹爪提供动力 | 稳定气压输出 |
| 🖥️ 上位机 | PC(开发/调试) | 算法开发、编译、监控 | Qt 集成开发环境 |
3.2 系统拓扑

3.3 川崎6轴机器人运动参数
机器人末端执行器的空间位姿由 6个参数 描述:
| 参数 | 含义 | 说明 |
|---|---|---|
| X | 末端 X 坐标 | 沿世界坐标系 X 轴位置(mm) |
| Y | 末端 Y 坐标 | 沿世界坐标系 Y 轴位置(mm) |
| Z | 末端 Z 坐标 | 沿世界坐标系 Z 轴位置(mm) |
| O | 绕 X 轴旋转角 | Roll(翻滚角),度 |
| A | 绕 Y 轴旋转角 | Pitch(俯仰角),度 |
| T | 绕 Z 轴旋转角 | Yaw(偏航角),度 |
💡 关键理解: 传统的2D抓取只需要 (X, Y),而本方案通过 RGB-D 相机获取的深度信息,额外计算出 Z 高度 + 三个旋转角 (O, A, T),实现真正的 6-DOF(六自由度)抓取!
四、系统整体流程 🎬
Image Prompt: Minimalist vector hand-drawn flowchart, 16:9 aspect ratio. Five-stage pipeline diagram showing the complete grasping workflow from left to right: (1) Astra Pro camera icon with RGB+D label → (2) Point cloud preprocessing block (filtering + downsampling) → (3) Region Growing segmentation block → (4) Plane fitting + normal vector calculation → (5) Kawasaki robot arm executing grasp. Each stage is connected by arrows, with brief labels underneath. Color scheme: blue for processing blocks, orange for input/output, clean hand-drawn technical style.
4.1 从"看到"到"抓到"的完整链路

4.2 软件模块划分
| 🧩 模块 | 📦 核心组件 | 🔑 关键技术 |
|---|---|---|
| 视觉感知模块 | Astra SDK + OpenNI | 深度图→点云转换、实时采集 |
| 点云处理模块 | PCL 1.8 | 直通滤波、体素滤波、区域生长分割 |
| 姿态估计算块 | OpenCV 3.2 + Eigen | 平面拟合、法向量、Rodrigues变换 |
| 手眼标定模块 | SVD分解 | 4点标定法、坐标系转换矩阵 |
| 通信控制模块 | Qt + QUdpSocket | UDP协议、机器人指令发送 |
五、核心算法详解 🔬
5.1 点云采集与预处理
5.1.1 从深度图到点云
Astra Pro 通过 OpenNI SDK 输出每帧的 3D 点云数据,核心代码:
// 初始化 Astra 并读取点云
void readPoint(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
{
astra::initialize();
astra::StreamSet streamSet;
astra::StreamReader reader = streamSet.create_reader();
reader.stream<astra::PointStream>().start();
astra::Frame frame = reader.get_latest_frame();
const auto depthFrame = frame.get<astra::PointFrame>();
for (size_t i = 0; i < 640 * 480 - 1; i++)
{
astra::Vector3f pixelValue = depthFrame.data()[i];
pcl::PointXYZ point;
point.x = pixelValue.x;
point.y = pixelValue.y;
point.z = pixelValue.z;
// 深度范围过滤:保留 580mm~630mm 工作距离内的点
if (pixelValue.z < 580 || pixelValue.z > 630 || pixelValue.y < -150)
continue;
cloud->push_back(point);
}
}
📌 关键细节: 代码中通过
z < 580 || z > 630将工作距离锁定在 620mm ± 10mm 范围内,有效剔除背景和前景干扰。
5.1.2 直通滤波(PassThrough)
限定 Z 轴范围,去除超出工作区域的无效点:
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(0.0, 1.0); // 保留 0~1m 范围内的点
pass.filter(*indices);
5.1.3 区域生长分割(Region Growing)
这是整个系统的核心分割算法,利用法向量相似性将散乱点云聚类为独立物体:
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> normal_estimator;
normal_estimator.setSearchMethod(tree);
normal_estimator.setInputCloud(cloud);
normal_estimator.setKSearch(50);
normal_estimator.compute(*normals);
pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> reg;
reg.setMinClusterSize(50); // 最小聚类点数
reg.setMaxClusterSize(3000); // 最大聚类点数
reg.setSearchMethod(tree);
reg.setNumberOfNeighbours(30); // 邻域搜索点数
reg.setInputCloud(cloud);
reg.setInputNormals(normals);
reg.setSmoothnessThreshold(3.0 / 180.0 * M_PI); // 平滑阈值(弧度)
reg.setCurvatureThreshold(1.0); // 曲率阈值
reg.extract(clusters); // 输出:每个 cluster 的点索引集合
区域生长算法原理:

💡 为什么区域生长适合这个场景? 木块由平面构成,同一平面上的点法向量方向一致,不同平面(如相邻木块)法向量差异大,因此区域生长能天然地将接触但朝向不同的木块分开。
5.2 抓取姿态求解(核心算法 ⭐)
这是整个系统最精彩的部分——从一堆点云到机器人抓取指令的完整推导。

5.2.1 平面拟合:找抓取面
从分割出的最大聚类中,拟合出最优平面并提取法向量:
void get2RandomPlaneR(std::vector<cv::Vec4f>& inCloud, cv::Vec4f &Plane, double th)
{
// 使用 RANSAC 或最小二乘法拟合平面
// 输出:Plane = (A, B, C, D),其中 (A,B,C) 为法向量,D 为截距
// ...
}
5.2.2 中心点求解:抓哪里?
将目标聚类中的所有点坐标取平均,得到抓取中心点:
void Calpoint(const std::vector<cv::Vec4f> ptCloud, cv::Vec3f ¢erPt)
{
int ptNum = ptCloud.size();
for (int i = 0; i < ptNum; i++) {
centerPt[0] += ptCloud[i][0]; // X 累加
centerPt[1] += ptCloud[i][1]; // Y 累加
centerPt[2] += ptCloud[i][2]; // Z 累加
}
centerPt = centerPt / ptNum; // 取平均 → 中心点坐标
}
5.2.3 抓取姿态计算:以什么角度抓?
这是算法的精髓——利用平面法向量推导出机器人的三个旋转角 (O, A, T):
// 平面法向量(抓取方向)
cv::Vec3f Vec_N;
Vec_N[0] = planeParm[0]; // 法向量 X 分量
Vec_N[1] = planeParm[1]; // 法向量 Y 分量
Vec_N[2] = planeParm[2]; // 法向量 Z 分量
// 基向量(机器人基座 Z 轴方向)
cv::Vec3f Vec_Base = cv::Vec3f(0, 0, 1);
// 叉乘求旋转轴
cv::Vec3f Vec_new = CrossProduct_3Pt(Vec_N, Vec_Base);
// Rodrigues 公式:旋转轴 → 旋转矩阵
Mat1f M = Mat1f::zeros(3, 3);
Rodrigues(Vec_new, M);
// 从旋转矩阵中提取欧拉角(O, A, T)
float Angle_O, Angle_A, Angle_T;
// Pitch(俯仰角 A):绕 Y 轴旋转
Angle_A = atan2(sqrt(M[0][2]*M[0][2] + M[1][2]*M[1][2]), M[2][2]);
// 特殊处理:A ≈ 0° 或 A ≈ 180° 时的奇异性
if (abs(Angle_A - 0.0) < 1E-2) {
Angle_O = 0;
Angle_T = atan2(-M[0][1], M[0][0]);
} else if (abs(Angle_A - PI) < 1E-2) {
Angle_O = 0;
Angle_T = atan2(M[0][1], -M[0][0]);
} else {
Angle_O = atan2(M[1][2] / sin(Angle_A), M[0][2] / sin(Angle_A));
Angle_T = atan2(M[2][1] / sin(Angle_A), -M[2][0] / sin(Angle_A));
}
// 弧度 → 角度
Angle_O = Angle_O * 180.0 / PI;
Angle_A = 180 - (Angle_A * 180.0 / PI); // 注意:A 取补角
Angle_T = Angle_T * 180.0 / PI;
🎯 一句话概括: 抓哪里 = 点云中心点,以什么角度抓 = 平面法向量 → Rodrigues → 欧拉角
5.2.4 刚体变换:从相机坐标系到机器人坐标系
void RigidTransPtCloud(std::vector<cv::Vec4f> &srcV, Matrix4f trsM)
{
for (unsigned int i = 0; i < srcV.size(); i++)
{
// 对每一个点执行 4×4 变换矩阵乘法
float nX = trsM(0,0)*srcV[i][0] + trsM(0,1)*srcV[i][1]
+ trsM(0,2)*srcV[i][2] + trsM(0,3);
float nY = trsM(1,0)*srcV[i][0] + trsM(1,1)*srcV[i][1]
+ trsM(1,2)*srcV[i][2] + trsM(1,3);
float nZ = trsM(2,0)*srcV[i][0] + trsM(2,1)*srcV[i][1]
+ trsM(2,2)*srcV[i][2] + trsM(2,3);
srcV[i][0] = nX; srcV[i][1] = nY; srcV[i][2] = nZ;
}
}
5.3 手眼标定:4点 SVD 法 🎯
手眼标定的本质: 求解相机坐标系 → 机器人基座坐标系的变换矩阵
团队采用了一种快速4点标定法,无需强度信息:
// 用 4 对已知对应点,通过 SVD 分解求解 RT 矩阵
int ComputeRT_3D(std::vector<Vec3d>& M, std::vector<Vec3d>& D, Mat1d & R, Mat1d & T)
{
// M = 机器人工件坐标系下的 4 个点坐标
// D = 相机点云坐标系下对应的 4 个点坐标
// 通过 SVD 分解求解最优旋转矩阵 R 和平移向量 T
// ...
}
// 构建 4×4 齐次变换矩阵
Mat1d RT = Mat1d::eye(4, 4);
R.copyTo(RT(Rect(0, 0, 3, 3))); // 左上 3×3 填入 R
RT(0, 3) = T[0][0]; // 第4列前3行填入 T
RT(1, 3) = T[1][0];
RT(2, 3) = T[2][0];
// RT = [R | T]
// [0 | 1]
SVD 求解 RT 的数学原理:
给定两组对应点 {M_i} 和 {D_i},i = 1..4
1. 计算质心:
μ_M = (1/4)Σ M_i μ_D = (1/4)Σ D_i
2. 去中心化:
M'_i = M_i - μ_M D'_i = D_i - μ_D
3. 构造协方差矩阵:
H = Σ D'_i · M'_i^T
4. SVD 分解:
H = U · Σ · V^T
5. 最优旋转矩阵:
R = V · U^T
6. 平移向量:
T = μ_M - R · μ_D
7. 齐次变换矩阵:
RT = | R T |
| 0 1 |
📐 标定精度: 受视觉系统重建误差影响,手眼标定匹配误差均值为 1.5mm
5.4 UDP 通信:Qt 封装 📡
// Qt UDP 通信封装
QUdpSocket xx;
xx.bind(QHostAddress("192.168.0.15"), 60000); // 绑定本地端口
// 将抓取指令打包发送
cv::Vec6f buf(centerPt[0], centerPt[1], centerPt[2], Angle_O, Angle_A, Angle_T);
char BUF[100];
sprintf(BUF, "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,4,0",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
// 发送给机器人控制器
xx.writeDatagram(BUF, sizeof(BUF), QHostAddress("192.168.0.2"), 60003);
通信协议设计:
| 字段 | 格式 | 示例 | 说明 |
|---|---|---|---|
| X | float | 123.4 | 抓取点 X 坐标(mm) |
| Y | float | 56.7 | 抓取点 Y 坐标(mm) |
| Z | float | 89.0 | 抓取点 Z 坐标(mm) |
| O | float | 0.0 | Roll 角度(度) |
| A | float | 135.0 | Pitch 角度(度) |
| T | float | 45.0 | Yaw 角度(度) |
| 工具号 | int | 4 | 气动夹具编号 |
| 速度 | int | 0 | 运动速度档位 |
六、研发踩坑全记录 🧩
每个成功的项目背后,都有一部"血泪史"——这支团队也踩了不少坑,记录下来供后来者参考 👇
6.1 已解决的问题 ✅
| 🐛 问题 | 🔍 根因 | ✅ 解决方案 |
|---|---|---|
| 点云精度差 | Astra Pro 在 620mm 处平面标准差达 1.43mm,点云分层 | 多次采集取平均 + 滤波后求中心点/法线来"抹平"误差 |
| 4PCS+ICP 配准失败 | 点云精度差导致匹配结果不鲁棒 | 放弃配准方案,改用平面拟合 + 中心点直接求解 |
| 开发板编译死机 | Zora P1 内存较小,PCL 库加载时内存溢出 | 换用 PC 进行编译和运算 |
| flann 库冲突 | OpenCV 和 PCL 各自携带 flann,版本不兼容 | 手动指定链接顺序,解决编译冲突 |
6.2 已知局限 ⚠️
| ⚠️ 局限 | 📝 说明 | 🚀 改进方向 |
|---|---|---|
| 深度精度有限 | 标准差 1.43mm,Z 方向抓取误差 2mm | 换用条纹结构光相机 |
| 仅支持平面工件 | 当前方案依赖平面拟合 | 引入模板匹配,支持复杂几何体 |
| 无路径规划 | 直接发送抓取指令,无避障 | 增加运动规划模块 |
| 开发板算力不足 | Zora P1 无法流畅运行完整算法 | 升级到 Jetson Orin 级别开发板 |
6.3 点云精度分析
团队对 Astra Pro 的深度精度做了量化测试,结果如下:
| 📊 指标 | 📐 数值 | 📝 说明 |
|---|---|---|
| 工作距离 | 620mm | 优化后的最佳工作距离 |
| 平面标准差 | 1.43mm | 主要误差来源 |
| 正负最大距离 | 9mm | 最坏情况下的偏差 |
| 正负平均距离 | 1.22mm | 平均偏差 |
| 手眼标定误差 | 1.5mm | 4点 SVD 法 |
| X/Y 抓取误差 | 0.5mm | 控制系统补偿后 |
| Z 抓取误差 | 2mm | 受深度精度限制 |
| 角度误差 | 0.8° | 控制系统补偿后 |

七、技术栈总览 🛠️
| 🗂️ 类别 | 🔧 技术选型 | 📋 版本/说明 |
|---|---|---|
| 操作系统 | Ubuntu | 16.04 / 18.04 |
| 编程语言 | C++ | C++11 标准 |
| 深度相机 SDK | OpenNI / Astra SDK | 奥比中光官方 |
| 点云处理 | PCL(Point Cloud Library) | 1.8 |
| 图像处理 | OpenCV | 3.2 |
| 线性代数 | Eigen | 矩阵运算、SVD分解 |
| GUI 框架 | Qt | 5.x,集成界面 + 网络通信 |
| 网络通信 | QUdpSocket | UDP 协议 |
| 开发板 | Zora P1 | 奥比中光定制 ARM 开发板 |
| 机器人 | 川崎 6轴机械臂 | 以太网通信 |
| 末端执行器 | 气动夹具 | 气泵驱动 |
八、技术创新亮点 💡
8.1 四大核心创新
| 🏷️ 创新点 | 📝 说明 | 🆚 传统方案对比 |
|---|---|---|
| 🎯 4点快速手眼标定 | 无需强度信息的 SVD 标定法,操作简单 | 传统方法需专用标定板,步骤繁琐 |
| 📐 平面法向量抓取 | 任意姿态下沿平面法线方向抓取 | 传统方案只能从固定方向接近 |
| 🧠 区域生长智能分割 | 利用法向量一致性自动分离接触物体 | 传统聚类对接触物体效果差 |
| ⚡ 轻量化端到端流程 | 采集→分割→拟合→抓取,无中间配准 | 4PCS+ICP 配准链路长、不鲁棒 |
8.2 方案对比总结
| 🆚 对比维度 | 📸 传统 2D 视觉 | 🔧 4PCS+ICP 配准方案 | 🏆 本方案(平面拟合) |
|---|---|---|---|
| 摆放要求 | 必须整齐 | 任意 | ✅ 任意 |
| 精度 | 高(2D平面) | 低(点云精度差) | ✅ 中等(补偿后可用) |
| 鲁棒性 | 高 | ❌ 对噪声极敏感 | ✅ 取平均后稳定 |
| 计算量 | 低 | 高(配准迭代) | ✅ 低(拟合解析解) |
| 实现难度 | 低 | 高 | ✅ 中等 |
九、总结与展望 🚀
9.1 核心贡献
- 🎯 端到端无序抓取方案:从 RGB-D 采集到机器人执行,形成完整闭环
- 📐 平面拟合 + 法向量求解:巧妙绕过点云配准的坑,用几何方法直接求解抓取姿态
- 🔗 Qt 集成开发:统一界面管理相机、算法、通信,工程化程度高
- 📊 精度量化分析:对系统各环节误差做了详细测量,为后续优化提供数据支撑
9.2 未来方向
| 🚀 方向 | 🎯 目标 | 💡 技术路径 |
|---|---|---|
| 📷 升级深度传感器 | 换用条纹结构光相机,将深度精度提升至 <0.5mm | 条纹投影 + 相位解算 |
| 🧩 模板匹配抓取 | 支持复杂曲面零件(非平面工件) | 预扫描工件库 + 模板匹配 |
| 🗺️ 抓取后路径规划 | 避障 + 最优路径 | RRT / A* 算法 |
| 🤖 第三视角避障 | 利用 Astra Pro 做环境感知 | 实时深度图 + 障碍物检测 |
| 📱 开发板升级 | 在边缘端完整运行算法 | Jetson Orin Nano 8GB |
🌟 写在最后: 这支团队用朴实无华的硬件(Astra Pro + Zora P1 + 川崎机器人),走出了一条"用几何方法解决工程问题"的务实路线。他们没有堆砌花哨的深度学习模型,而是在理解数据特性的基础上,选择了最适合当前硬件条件和精度约束的方案——平面拟合 + 法向量抓取。这种"把手头的牌打好"的工程思维,比任何炫技都更有价值。如果你也在做类似的工业视觉项目,这份实战经验绝对值得参考!🏆
–