【机械臂】Gluon_2L6 + realsense D435i标定 手动+自动

准备工作

系统:双系统 Ubuntu 1804
ROS: Melodic(默认已经安装好)
OpenCV:

step1.0、使用moveit_setup_assistant 配置机械臂

  • step1.0.1 修改urdf文件
    因为我拿到手的机械臂与URDF中的零点位置不同,所以更改一下配置文件~/innfos_ros/src/gluon/urdf/gluon.urdf
    大约在113行左右,axis_joint_1的关节角度限制改一下
    改成
    <limit
      lower="-4.34"
      upper="1.2"
      effort="30"
      velocity="3.14" />

然后启动机械臂配置助手

 roslaunch moveit_setup_assistant setup_assistant.launch
  • step1.1 新建一个Moveit config包
  • step1.2 选择刚刚修改好的urdf文件
  • step1.3 点击Load Files
    在这里插入图片描述

加载完成如图所示
在这里插入图片描述

  • step1.4 点击Generate Collision Matrix 生成自碰撞矩阵
    在这里插入图片描述

  • step1.5 添加虚拟关节,world
    在这里插入图片描述
    在这里插入图片描述

  • step1.6 添加规划组,子关节
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • step1.7 添加预设位姿,我就添加了一个,可以多添加几个
    在这里插入图片描述
    在这里插入图片描述

  • step1.8 End Effectors 添加末端执行器,因为我这里只有机械臂,还没安装夹爪,所以这里先不设置

  • step1.9 Passive Joints 被动关节,这里没有,如果安装了夹爪那么夹爪的爪子就是被动关节

  • step1.10 ROS Control 选择自动生成
    在这里插入图片描述

  • step1.11 Simulation与3D Perception 跳过,标定完后再设置

  • step1.12 填入作者信息
    在这里插入图片描述

  • step1.13 生成包,注意选择路径
    在这里插入图片描述

  • setp1.14 退出配置助手,刷新环境变量

source ~/.bashrc
  • step1.15 修改生成的demo.launch
<launch>

  <!-- specify the planning pipeline -->
  <arg name="pipeline" default="ompl" />

  <!-- By default, we do not start a database (it can be large) -->
  <arg name="db" default="false" />
  <!-- Allow user to specify database location -->
  <arg name="db_path" default="$(find GL2_moveit_config)/default_warehouse_mongo_db" />

  <!-- By default, we are not in debug mode -->
  <arg name="debug" default="false" />

  <arg name="use_gui" default="false" />
  <!-- <arg name="use_rviz" default="true" /> -->
  <!-- Load the URDF, SRDF and other .yaml configuration files on the param server -->
  <include file="$(find GL2_moveit_config)/launch/planning_context.launch">
    <arg name="load_robot_description" value="true"/>
  </include>
  <!-- If needed, broadcast static tf for robot root -->
  <!-- Real robot connected -->
  <include file="$(find gluon_control)/launch/gluon_control.launch">
  </include>

  <!-- Given the published joint states, publish tf for the robot links -->
  <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" respawn="true" output="screen" />

  <!-- Run the main MoveIt! executable without trajectory execution (we do not have controllers configured by default) -->
  <include file="$(find GL2_moveit_config)/launch/move_group.launch">
    <arg name="allow_trajectory_execution" value="true"/>
    <arg name="fake_execution" value="false"/>
    <!-- <arg name="fake_execution_type" value="$(arg fake_execution_type)" /> -->
    <arg name="info" value="true"/>
    <arg name="debug" value="$(arg debug)"/>
    <arg name="pipeline" value="$(arg pipeline)"/>
    <!-- <arg name="load_robot_description" value="$(arg load_robot_description)"/> -->
  </include>

  <!-- Run Rviz and load the default config to see the state of the move_group node -->
  <include file="$(find GL2_moveit_config)/launch/moveit_rviz.launch" >
    <arg name="rviz_config" value="$(find GL2_moveit_config)/launch/moveit.rviz"/>
    <arg name="debug" value="$(arg debug)"/>
  </include>

  <!-- If database loading was enabled, start mongodb as well -->
  <include file="$(find GL2_moveit_config)/launch/default_warehouse_db.launch" if="$(arg db)">
    <arg name="moveit_warehouse_database_path" value="$(arg db_path)"/>
  </include>

</launch>

step1.16 验证是否可用 GL2_moveit_config是我使用配置后的包名字,不建议使用大写字母开头,会报错
在这里插入图片描述
机械臂上电并使用该命令,同时用手扶机械臂,防止机械臂初始化过程中未初始化的电机关节由于重力作用下落损坏设备。

roslaunch GL2_moveit_config demo.launch

此时机械臂会进入零点位置
在rviz中随意拖动设备,plan并execute
检查是否会遇到执行失败的问题,如图所示。
如果有许多角度都失败了那么重启一下demo.launch
不然下面标定过程中会卡在 Cannot calibrate from current position 上
在这里插入图片描述
在这里插入图片描述

机械臂使用完成后进入innfos_arm/innfos-gluon-controller/关闭电源使能

cd ~/innfos_arm/innfos-gluon-controller/
. environment
./setrobot t GL_2L6_4L3 # GL_2L6_4L3需要执行这个,不执行问题也不大还没有遇到
./robotserver mode0 # 关闭电源使能

step2.0 编译标定程序

mkdir -p ~/test_aruco_easy_hand/src && cd ~/test_aruco_easy_hand/src
git clone https://github.com/pal-robotics/aruco_ros.git
git clone https://github.com/IFL-CAMP/easy_handeye.git

修改参数

cd ~/test_aruco_easy_hand/src/aruco_ros/launch
vim single_realsense.launch

粘贴入下面内容

<launch>
    <arg name="markerId"        default="582"/>
    <arg name="markerSize"      default="0.1"/>    <!-- in m -->
    <arg name="eye"             default="left"/>
    <arg name="marker_frame"    default="aruco_marker_frame"/>
    <arg name="ref_frame"       default="camera_color_frame"/>  <!-- leave empty and the pose will be published wrt param parent_name -->
    <arg name="corner_refinement" default="LINES" /> <!-- NONE, HARRIS, LINES, SUBPIX -->
    <node pkg="aruco_ros" type="single" name="aruco_single">
        <remap from="/camera_info" to="/camera/color/camera_info" />
        <remap from="/image" to="/camera/color/image_raw" />
        <param name="image_is_rectified" value="True"/>
        <param name="marker_size"        value="$(arg markerSize)"/>
        <param name="marker_id"          value="$(arg markerId)"/>
        <param name="reference_frame"    value="$(arg ref_frame)"/>   <!-- frame in which the marker pose will be refered -->
        <param name="camera_frame"       value="camera_color_frame"/>
        <param name="marker_frame"       value="$(arg marker_frame)" />
        <param name="corner_refinement"  value="$(arg corner_refinement)" />
    </node>
</launch>

参数说明

  • markerId:使用的Marker标签的ID,这里在准备时打印的Marker标签ID为582

  • markerSize:Marker标签的实际大小,单位为m,这里在准备时打印的Marker标签实际大小为10厘米即0.1m

  • ref_frame:参考坐标系名称,这里选择Realsense的camera_color_frame作为参考坐标系

  • <remap from=“/camera_info” to=“/camera/color/camera_info” />:将/camera_info重映射为对应Realsense实际发布的相应的Topic即/camera/color/camera_info

  • <remap from=“/image” to=“/camera/color/image_raw” />:将/image重映射为对应Realsense实际发布的相应的Topic即/camera/color/image_raw

  • camera_frame:相机坐标系,修改为实际的相机坐标系camera_color_frame

  • step2.3 配置easy_handeye

cd ~/test_aruco_easy_hand/src/easy_handeye/easy_handeye/launch/
vim eye_in_hand_calibrate.launch

粘贴如以下内容

<?xml version="1.0"?>
<launch>
    <!-- 生成标定文件的名称 -->
    <arg name="namespace_prefix" default="gluon_rs_d435i" />
    <!-- gluon机械臂MoveIt!配置的move_group为gluon,所以修改为gluon -->
    <arg name="move_group" default="gluon" />
    <!-- start easy_handeye -->
    <include file="$(find easy_handeye)/launch/calibrate.launch" >
        <arg name="namespace_prefix" value="$(arg namespace_prefix)" />
<arg name="move_group" value="$(arg move_group)" />
        <!-- 这里使用眼在手上的方式进行标定,所以此处改成true -->
        <arg name="eye_on_hand" value="true" />
    <!--tracking_base_frame为realsense的相机坐标系-->
        <arg name="tracking_base_frame" value="camera_color_frame" />
    <!--tracking_marker_frame对应aruco_ros包中single_realsense.launch中的marker_frame的值-->
        <arg name="tracking_marker_frame" value="aruco_marker_frame" />
    <!--robot_base_frame为机器人基座坐标系-->
        <arg name="robot_base_frame" value="dummy" />
    <!--robot_effector_frame为工具坐标系,如夹爪,吸盘等,但实际的机器人模型未添加夹爪,所以这里设置为末端关节5_Link-->
        <arg name="robot_effector_frame" value="5_Link" />
        <arg name="freehand_robot_movement" value="false" />
        <arg name="robot_velocity_scaling" value="0.5" />
        <arg name="robot_acceleration_scaling" value="0.2" />
    </include>
</launch>

PS:这里的机械臂末端是实际链接深度相机的关节,我的
在这里插入图片描述
在这里插入图片描述
可以看到我的深度相机实际上是连接在第五个电机上的,即其坐标是相对于5_Link固定的,所以我这里写的是5_Link
链接机械臂与深度相机的模型来自于从零开始的机械臂yolov5抓取gazebo仿真(一)Lord_ZYX的博客-CSDN博客
修改完成后编译

cd ~/test_aruco_easy_hand/
catkin_make
echo "source ~/test_aruco_easy_hand/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc

step3.0 自动标定

  • step3.1 启动Realsense、aruco、机械臂、easy_handeye 节点
# 启动深度相机
roslaunch realsense2_camera rs_camera.launch
# 启动aruco
roslaunch aruco_ros single_realsense.launch
# 打开image_view 查看aruco发布的图像话题
rosrun image_view image_view image:=/aruco_single/result
# 启动机械臂moveit控制程序
roslaunch GL2_moveit_config demo.launch
# 然后调整机械臂位姿,直到aruco发布的图像话题中可以看到标定板在视野中央
# 启动easy_handeye 
roslaunch easy_handeye eye_in_hand_calibrate.launch

如图
在这里插入图片描述
在这里插入图片描述

  • step3.2 重复1、2、3、4。只要画面中能看到标定板的坐标信息就点击4采样
    在这里插入图片描述

  • step3.3 直到十七个点标定完,点击计算
    在这里插入图片描述

  • step3.4 可以点击保存,标定信息保存在~/.ros/easy_handeye/下面
    PS :遇到在这里插入图片描述

Cannot calibrate from current position
的问题

  • 要么就调整一下机械臂位姿,重启一下GL2_moveit_config demo.launch
  • 或者干脆使用手动标定的办法

附上两次标定的结果

# 第一次
translation: 
  x: 0.00186935102131
  y: 0.0193147763718
  z: 0.0783182224774
rotation: 
  x: -0.481285930881
  y: 0.303509486976
  z: -0.454033133706
  w: 0.685638211849

# 第二次
translation: 
  x: 0.0157418883308
  y: 0.0423640147247
  z: 0.152850956679
rotation: 
  x: -0.530311271262
  y: 0.463840825509
  z: -0.473198006338
  w: 0.528871715033

step4.0 手动标定

由于在 Cannot calibrate from current position 这个问题上卡了两天。
于是打算使用手动标定的办法
具体步骤

  • step4.1 手动控制机械臂,使aruco发布的图像话题中可以看到标定板在视野中,然后每调整一个动作订阅一下/aruco_single/poserosto
rostopic echo /aruco_single/poserosto
  • step4.2 记录一下相机与标定板的position与orientation
header:
  seq: 12526
  stamp:
    secs: 1697013561
    nsecs: 250904322
  frame_id: "camera_color_frame"
pose:
  position:
    x: -0.0191426966339
    y: 0.00880137924105
    z: 0.211938127875
  orientation:
    x: -0.016056670143
    y: 0.998521559028
    z: 0.0419401089597
    w: -0.0306252634272
  • step4.3 然后使用moveit_commander_cmdline.py记录一下机械臂末端位姿
rosrun moveit_commander moveit_commander_cmdline.py
use gluon
current # 查看当前机械臂末端相对于基坐标的position与orientation

(这里记得需要在moveit中gluon规划组的末端改为5_Link)(忽略我下面的Link6 这里只讲方法)

joints = [0.0231194779469 0.225352582548 -1.86336170163 -2.08869864822 -1.5825253568 6.61477915356e-05]
6_Link pose = [
header:
  seq: 0
  stamp:
    secs: 1697013505
    nsecs: 791723966
  frame_id: "dummy"
pose:
  position:
    x: 0.080115429479
    y: 0.188752756384
    z: 0.268797094909
  orientation:
    x: -2.89505769747e-05
    y: -1.76295785108e-05
    z: 0.703068203569
    w: 0.71112242264 ]
6_Link RPY = [-6.596440111243088e-05, 1.5634883131054623e-05, 1.5594058788530805]
  • step4.4 累计记录十六对位姿后
    新建文件夹
mkdir -p ~/eyehand_cul/src && cd eyehand_cul/src
vim eyehand_cul.cpp

将位姿按照x、y、z、w、x、y、z填入下面代码,然后复制进eyehand_cul.cpp里面

#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <iterator> 
#include <algorithm>  
using namespace cv;
using namespace std;

template <typename T>
std::ostream& operator<< (std::ostream& out, const std::vector<T>& v) {
	if (!v.empty()) {
		out << '[';
		std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
		out << "\b\b]";
	}
	return out;
}
int num = 16;
//相机中组标定板的位姿,x,y,z,w,x,y,z
Mat_<double> CalPose = (cv::Mat_<double>(num, 7) <<
-0.019142697,0.008801379 ,0.211938128, -0.030625263 ,-0.01605667, 0.998521559, 0.041940109,//1
-0.018183947 ,-0.01305026 ,0.266784608 ,-0.0305709842782, -0.01733744 ,0.990492948 ,0.1329983,//2
0.052254666, 0.003839179 ,0.229174137, -0.0124332456699 ,0.097019154, 0.987631484, 0.122542847,//3
-0.00218128, 0.040340081 ,0.280634135 ,-0.0173363 ,-0.006932675, 0.999253326 ,-0.033825736,//4
0.00097711 ,0.044277284 ,0.268945873, 0.027248854 ,0.008675707 ,0.998468688, -0.047355149,//5
0.070512287 ,-0.029839678 ,0.245356679 ,0.00587619991395, 0.009445105,0.999025508 ,0.042711756,//6
0.01260369 ,-0.029761016, 0.244636059, 0.112973851 ,-0.054361917, 0.991293555, 0.040234058,//7
-0.019118361, -0.017231764 ,0.233383402 ,-0.106525247 ,0.410003692 ,0.896736898 ,0.128110422,//10
0.099970877 ,0.019913295, 0.27814135 ,-0.171188348, -0.026357483, 0.977001287 ,0.124371695,//11
0.066922761 ,0.033534456 ,0.246380225 ,-0.035780141, 0.095604627, 0.99024207 ,0.094869276,//12
0.008322123, 0.002318738, 0.325623274, -0.023902414, -0.110716933, 0.972771369 ,0.20220361,//13
0.024424298 ,0.021282304 ,0.297789633 ,-0.028250203, -0.018387057 ,0.997930176 ,0.054765001,//14
0.038527615 ,0.011775037 ,0.287405342 ,0.086648789 ,-0.008709179, 0.995841732, 0.026746618,//15
0.003589922,0.00701269,0.258110791, 0.0226302680186 ,-0.030048007,0.998662633,0.03546735,//16
0.063243598,0.017215444,0.242752165,-0.172558339,0.016110283,0.983767444,0.046537029,//17
0.103742972,0.031597719,0.322632223,0.138930386, -0.017062396,0.989923597,0.021412465);
//机械臂末端的位姿,x,y,z,w,x,y,z
Mat_<double> ToolPose = (cv::Mat_<double>(num, 7) <<
0.080115429, 0.188752756, 0.268797095,	0.711122423 ,-2.89506E-05, -1.76296E-05 ,0.703068204,
0.079509091, 0.135887183 ,0.307017162 ,0.708490925 ,-0.060471851, 0.061237559, 0.700452515,
0.049454675, 0.136262666, 0.306985693, 0.725050330725, -0.061949952, 0.059867487, 0.683286254,
0.049421024, 0.128409149 ,0.251500415 ,0.725792606, 0.041424009 ,-0.049814442, 0.684855945,
0.02010073,  0.130137971,0.25125298,  0.753329021, 0.043344859 ,-0.048035788 ,0.654453338,
0.0199721, 0.129670255 ,0.22324334 ,0.754805574 ,-0.004757904 ,0.007351554 ,0.655890129,
-0.024412855 ,0.135925483 ,0.223350168, 0.813153915, -0.005418092 ,0.006905753 ,0.581982531,
// -0.040225057, 0.141633432, 0.240963427, 0.853272398, -0.005880946, 0.006437633, 0.521392545
// 0.073773508, 0.123675173 ,0.240700209, 0.718363659 ,-0.004314918, 0.007587391, 0.695613015
0.112837801 ,0.122723284 ,0.259375359, 0.615747852 ,-0.048330788 ,0.043396404, 0.785261402,
0.052193137, 0.117061429, 0.32842347, 0.614743163 ,-0.060138307, 0.028169535, 0.785926654,
0.052202149, 0.117077284, 0.32842138, 0.715579296, -0.063443094 ,0.019756814 ,0.695363872,
0.046541136, 0.078699691 ,0.402184185 ,0.708776168 ,-0.141292184 ,0.100289471 ,0.683823723,
0.052306526, 0.0556187, 0.302499524, 0.715848464, -0.039502601 ,-0.004856761, 0.697120458,
0.012201325 ,0.099124405, 0.261528262, 0.79822923, 2.60876E-05, -1.68821E-05, 0.602353795,
0.022673847, 0.13587605, 0.261526637 ,0.752695169 ,2.43867E-06 ,3.71625E-05 ,0.658369183,
0.122156664, 0.122508, 0.26153452, 0.614337728 ,-2.13706E-05 ,-3.35018E-05, 0.789043189,
-0.088553092 ,0.128898855, 0.274293529, 0.831408557, -1.5297E-05 ,-4.24533E-05 ,0.555661596);
// -0.085495239 0.136304549 0.363611561 0.848087503 -0.061449431 0.084416486 0.519466469
// 0.021827131 0.094066761 0.334487546 0.779167445 -0.050555801 0.090946723 0.618118838

//R和T转RT矩阵
Mat R_T2RT(Mat& R, Mat& T)
{
	Mat RT;
	Mat_<double> R1 = (cv::Mat_<double>(4, 3) << R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2),
		R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2),
		R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2),
		0.0, 0.0, 0.0);
	cv::Mat_<double> T1 = (cv::Mat_<double>(4, 1) << T.at<double>(0, 0), T.at<double>(1, 0), T.at<double>(2, 0), 1.0);

	cv::hconcat(R1, T1, RT);//C=A+B左右拼接
	return RT;
}

//RT转R和T矩阵
void RT2R_T(Mat& RT, Mat& R, Mat& T)
{
	cv::Rect R_rect(0, 0, 3, 3);
	cv::Rect T_rect(3, 0, 1, 3);
	R = RT(R_rect);
	T = RT(T_rect);
}

//判断是否为旋转矩阵
bool isRotationMatrix(const cv::Mat& R)
{
	cv::Mat tmp33 = R({ 0,0,3,3 });
	cv::Mat shouldBeIdentity;

	shouldBeIdentity = tmp33.t() * tmp33;

	cv::Mat I = cv::Mat::eye(3, 3, shouldBeIdentity.type());

	return  cv::norm(I, shouldBeIdentity) < 1e-6;
}

/** @brief 欧拉角 -> 3*3 的R
*	@param 	eulerAngle		角度值
*	@param 	seq				指定欧拉角xyz的排列顺序如:"xyz" "zyx"
*/
cv::Mat eulerAngleToRotatedMatrix(const cv::Mat& eulerAngle, const std::string& seq)
{
	CV_Assert(eulerAngle.rows == 1 && eulerAngle.cols == 3);

	eulerAngle /= 180 / CV_PI;
	cv::Matx13d m(eulerAngle);
	auto rx = m(0, 0), ry = m(0, 1), rz = m(0, 2);
	auto xs = std::sin(rx), xc = std::cos(rx);
	auto ys = std::sin(ry), yc = std::cos(ry);
	auto zs = std::sin(rz), zc = std::cos(rz);

	cv::Mat rotX = (cv::Mat_<double>(3, 3) << 1, 0, 0, 0, xc, -xs, 0, xs, xc);
	cv::Mat rotY = (cv::Mat_<double>(3, 3) << yc, 0, ys, 0, 1, 0, -ys, 0, yc);
	cv::Mat rotZ = (cv::Mat_<double>(3, 3) << zc, -zs, 0, zs, zc, 0, 0, 0, 1);

	cv::Mat rotMat;

	if (seq == "zyx")		rotMat = rotX * rotY * rotZ;
	else if (seq == "yzx")	rotMat = rotX * rotZ * rotY;
	else if (seq == "zxy")	rotMat = rotY * rotX * rotZ;
	else if (seq == "xzy")	rotMat = rotY * rotZ * rotX;
	else if (seq == "yxz")	rotMat = rotZ * rotX * rotY;
	else if (seq == "xyz")	rotMat = rotZ * rotY * rotX;
	else {
		cv::error(cv::Error::StsAssert, "Euler angle sequence string is wrong.",
			__FUNCTION__, __FILE__, __LINE__);
	}

	if (!isRotationMatrix(rotMat)) {
		cv::error(cv::Error::StsAssert, "Euler angle can not convert to rotated matrix",
			__FUNCTION__, __FILE__, __LINE__);
	}

	return rotMat;
	//cout << isRotationMatrix(rotMat) << endl;
}

/** @brief 四元数转旋转矩阵
*	@note  数据类型double; 四元数定义 q = w + x*i + y*j + z*k
*	@param q 四元数输入{w,x,y,z}向量
*	@return 返回旋转矩阵3*3
*/
cv::Mat quaternionToRotatedMatrix(const cv::Vec4d& q)
{
	double w = q[0], x = q[1], y = q[2], z = q[3];

	double x2 = x * x, y2 = y * y, z2 = z * z;
	double xy = x * y, xz = x * z, yz = y * z;
	double wx = w * x, wy = w * y, wz = w * z;

	cv::Matx33d res{
		1 - 2 * (y2 + z2),	2 * (xy - wz),		2 * (xz + wy),
		2 * (xy + wz),		1 - 2 * (x2 + z2),	2 * (yz - wx),
		2 * (xz - wy),		2 * (yz + wx),		1 - 2 * (x2 + y2),
	};
	return cv::Mat(res);
}

/** @brief ((四元数||欧拉角||旋转向量) && 转移向量) -> 4*4 的Rt
*	@param 	m				1*6 || 1*7的矩阵  -> 6  {x,y,z, rx,ry,rz}   7 {x,y,z, qw,qx,qy,qz}
*	@param 	useQuaternion	如果是1*7的矩阵,判断是否使用四元数计算旋转矩阵
*	@param 	seq				如果通过欧拉角计算旋转矩阵,需要指定欧拉角xyz的排列顺序如:"xyz" "zyx" 为空表示旋转向量
*/
cv::Mat attitudeVectorToMatrix(cv::Mat m, bool useQuaternion, const std::string& seq)
{
	CV_Assert(m.total() == 6 || m.total() == 7);
	if (m.cols == 1)
		m = m.t();
	cv::Mat tmp = cv::Mat::eye(4, 4, CV_64FC1);
	//如果使用四元数转换成旋转矩阵则读取m矩阵的第第四个成员,读4个数据
	if (useQuaternion)	// normalized vector, its norm should be 1.
	{
		cv::Vec4d quaternionVec = m({ 3, 0, 4, 1 });
		quaternionToRotatedMatrix(quaternionVec).copyTo(tmp({ 0, 0, 3, 3 }));
		// cout << norm(quaternionVec) << endl; 
	}
	else
	{
		cv::Mat rotVec;
		if (m.total() == 6)
			rotVec = m({ 3, 0, 3, 1 });		//6
		else
			rotVec = m({ 7, 0, 3, 1 });		//10

		//如果seq为空表示传入的是旋转向量,否则"xyz"的组合表示欧拉角
		if (0 == seq.compare(""))
			cv::Rodrigues(rotVec, tmp({ 0, 0, 3, 3 }));
		else
			eulerAngleToRotatedMatrix(rotVec, seq).copyTo(tmp({ 0, 0, 3, 3 }));
	}
	tmp({ 3, 0, 1, 3 }) = m({ 0, 0, 3, 1 }).t();
	//std::swap(m,tmp);
	return tmp;
}
int main()
{
	//定义手眼标定矩阵
	std::vector<Mat> R_gripper2base;
	std::vector<Mat> t_gripper2base;
	std::vector<Mat> R_target2cam;
	std::vector<Mat> t_target2cam;
	Mat R_cam2gripper = (Mat_<double>(3, 3));
	Mat t_cam2gripper = (Mat_<double>(3, 1));

	vector<Mat> images;
	size_t num_images = num;

	// 读取末端,标定板的姿态矩阵 4*4
	std::vector<cv::Mat> vecHb, vecHc;
	cv::Mat Hcb;//定义相机camera到末端grab的位姿矩阵
	Mat tempR, tempT;

	for (size_t i = 0; i < num_images; i++)//计算标定板位姿
	{
		cv::Mat tmp = attitudeVectorToMatrix(CalPose.row(i), true, ""); //转移向量转旋转矩阵
		vecHc.push_back(tmp);
		RT2R_T(tmp, tempR, tempT);

		R_target2cam.push_back(tempR);
		t_target2cam.push_back(tempT);
	}
	//cout << R_target2cam << endl;
	for (size_t i = 0; i < num_images; i++)//计算机械臂位姿
	{
		//cv::Mat tmp = attitudeVectorToMatrix(ToolPose.row(i), true, "zyx");
		cv::Mat tmp = attitudeVectorToMatrix(ToolPose.row(i), true, ""); //机械臂位姿为欧拉角-旋转矩阵
		//tmp = tmp.inv();//机械臂基座位姿为欧拉角-旋转矩阵
		vecHb.push_back(tmp);
		RT2R_T(tmp, tempR, tempT);

		R_gripper2base.push_back(tempR);
		t_gripper2base.push_back(tempT);

	}
	//cout << t_gripper2base[0] << " " << t_gripper2base[1] << " " << t_gripper2base[2] << " " << endl;
	//cout << t_gripper2base << endl;
	//手眼标定,CALIB_HAND_EYE_TSAI法为11ms,最快
	calibrateHandEye(R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, R_cam2gripper, t_cam2gripper, CALIB_HAND_EYE_TSAI);

	Hcb = R_T2RT(R_cam2gripper, t_cam2gripper);//矩阵合并

	std::cout << "Hcb 矩阵为: " << std::endl;
	std::cout << Hcb << std::endl;
	cout << "是否为旋转矩阵:" << isRotationMatrix(Hcb) << std::endl << std::endl;//判断是否为旋转矩阵

	//Tool_In_Base*Hcg*Cal_In_Cam,用第一组和第二组进行对比验证
	cout << "第一组和第二组手眼数据验证:" << endl;
	cout << vecHb[0] * Hcb * vecHc[0] << endl << vecHb[1] * Hcb * vecHc[1] << endl << endl;//.inv()

	cout << "标定板在相机中的位姿:" << endl;
	cout << vecHc[1] << endl;
	cout << "手眼系统反演的位姿为:" << endl;
	//用手眼系统预测第一组数据中标定板相对相机的位姿,是否与vecHc[1]相同
	cout << Hcb.inv() * vecHb[1].inv() * vecHb[0] * Hcb * vecHc[0] << endl << endl;

	cout << "----手眼系统测试----" << endl;
	cout << "机械臂下标定板XYZ为:" << endl;
	for (int i = 0; i < vecHc.size(); ++i)
	{
		cv::Mat cheesePos{ 0.0,0.0,0.0,1.0 };//4*1矩阵,单独求机械臂下,标定板的xyz
		cv::Mat worldPos = vecHb[i] * Hcb * vecHc[i] * cheesePos;
		cout << i << ": " << worldPos.t() << endl;
	}
	getchar();
}

然后新建CMakeLists.txt文件

vim CMakeLists.txt
# cmake needs this line
cmake_minimum_required(VERSION 3.1)

# Define project name
project(opencv_example_project)

# Find OpenCV, you may need to set OpenCV_DIR variable
# to the absolute path to the directory containing OpenCVConfig.cmake file
# via the command line or GUI
find_package(OpenCV REQUIRED)

# If the package has been found, several variables will
# be set, you can find the full list with descriptions
# in the OpenCVConfig.cmake file.
# Print some message showing some of them
message(STATUS "OpenCV library status:")
message(STATUS "    config: ${OpenCV_DIR}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

# Declare the executable target built from your sources
add_executable(opencv_example eyehand_cul.cpp)

# Link your application with OpenCV libraries
target_link_libraries(opencv_example PRIVATE ${OpenCV_LIBS})

然后编译

mkdir build && cd build
cmake ..
make
./opencv_example

输出如下

Hcb 矩阵为:
[0.2529948966039119, 0.653276241650712, 0.7135991412460475, 0.03017181911674481;
 -0.9616527451985686, 0.2505542525409881, 0.1115641707076408, -0.001168880560819843;
 -0.1059130773060283, -0.7144597389834835, 0.6916138383716883, -0.07547482289580558;
 0, 0, 0, 1]
是否为旋转矩阵:1

第一组和第二组手眼数据验证:
[-0.9599436488593028, -0.2792873236999738, 0.02251185762079012, 0.03910078804958135;
 -0.2214110370594403, 0.7068656080347647, -0.6718021783806323, 0.3715502367630538;
 0.171712974154593, -0.6498766069605237, -0.7403884453808385, 0.3356351522593363;
 0, 0, 0, 1]
[-0.9571525726204432, -0.2881555801842336, -0.02872826729728267, 0.03927764326193531;
 -0.195760970020495, 0.7169541282410056, -0.6690698169418562, 0.361339517428391;
 0.2133930511483864, -0.634778023840171, -0.7426441043317652, 0.3899559117828131;
 0, 0, 0, 1]

标定板在相机中的位姿:
[-0.9975296556812412, -0.02621344623609158, -0.06517238877465595, -0.018183947;
 -0.04247700198940089, 0.9640217307427128, 0.2624077112806484, -0.01305026;
 0.05594898858924794, 0.2645278017033053, -0.9627537337269685, 0.266784608;
 0, 0, 0, 1]
手眼系统反演的位姿为:
[-0.9997047217560895, -0.01765094882544278, -0.01670075395733612, -0.008077176050000887;
 -0.02122639774834765, 0.9688851305323734, 0.2465989550282803, 0.03666166641663988;
 0.0118284064209383, 0.2468806374594315, -0.9689737049477073, 0.2448389356423604;
 0, 0, 0, 1]

----手眼系统测试----
机械臂下标定板XYZ为:
0: [0.03910078804958135, 0.3715502367630538, 0.3356351522593363, 1]
1: [0.03927764326193531, 0.361339517428391, 0.3899559117828131, 1]
2: [0.08683425441662714, 0.3536004451954924, 0.344910525909183, 1]
3: [0.02034896224340062, 0.3730021028587656, 0.3734935601188322, 1]
4: [0.01512751433860177, 0.3721913333345857, 0.3619222720564731, 1]
5: [0.09750146300366933, 0.3262202586990512, 0.3276439530655735, 1]
6: [0.03073768486914086, 0.3183407005095492, 0.3336689835478626, 1]
7: [0.02995544030800457, 0.3001712206250036, 0.3357754570798158, 1]
8: [0.04285151011411379, 0.3998638437274073, 0.387024726387165, 1]
9: [0.08581249543011772, 0.3669269088202225, 0.3650878968232826, 1]
10: [0.02323560666223969, 0.3795163686181222, 0.4495181615632068, 1]
11: [0.03880720012554556, 0.3239243677987539, 0.401306202842833, 1]
12: [0.08461018689300819, 0.3412465124182371, 0.3723482669356442, 1]
13: [0.02624041046694395, 0.3572248296625789, 0.3591640752105018, 1]
14: [0.0952593972169479, 0.3536129148496938, 0.334956523944783, 1]
15: [0.08165612719991988, 0.3910115315510737, 0.3884131893555852, 1]

参考网站

RM机械臂与Realsense D435手眼标定教程 - 知乎 (zhihu.com)
(已修正精度 1mm左右)Realsense d435i深度相机+Aruco+棋盘格+OpenCV手眼标定全过程记录_realsense相机标定_Fr0mdeepsea的博客-CSDN博客
机械臂urdf模型修改及功能包配置_stl转urdf-CSDN博客
sunday_description · master · mirrors / lord-z / sunday · GitCode
从零开始的机械臂yolov5抓取gazebo仿真(一)_Lord_ZYX的博客-CSDN博客

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Intel RealSense D435i是一款深度摄像头,具有双目摄像头和红外传感器,可以实时获取环境的深度信息,并提供用于机器视觉和计算机图形的可编程接口。六自由度机械臂是一种能够在三维空间中实现六个自由度运动的机械臂,可以进行复杂的物体抓取和放置操作。 在进行Intel RealSense D435i与六自由度机械臂标定时,主要是为了将深度摄像头的坐标系与机械臂的坐标系进行匹配,使得机械臂能够准确地通过摄像头获取到目标物体的位置和姿态信息,从而实现精确的抓取和放置操作。 标定过程通常包括以下几个步骤: 1. 放置目标物体:首先,将机械臂放置在一个已知的位置,并将目标物体放置在机械臂的工作区域内。 2. 采集数据:使用深度摄像头对目标物体及其周围环境进行拍摄,获取深度图像和RGB图像。 3. 提取特征点:通过对深度图像进行处理,提取目标物体上的特征点,例如角点或边缘。 4. 匹配特征点:使用机械臂控制软件,通过移动机械臂使得相机能够观察到目标物体,然后将特征点与机械臂上的坐标进行匹配。 5. 计算标定参数:根据匹配的特征点和已知的机械臂坐标,使用标定算法来计算出相机与机械臂之间的关系,确定坐标转换矩阵。 6. 验证标定结果:通过将机械臂移动到不同位置,并使用深度摄像头观察目标物体来验证标定的准确性和稳定性。 通过以上标定过程,可以将Intel RealSense D435i与六自由度机械臂进行精确匹配,实现准确的物体抓取和放置操作,提高机器人的操作精度和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值