【从零开始进行高精度手眼标定 eye in hand(小白向)2 Tsai轴角法与四元数法编程实现】

news2024/11/16 13:23:22

从零开始进行高精度手眼标定 eye in hand(小白向)2 Tsai轴角法与四元数法编程实现

  • 前言
  • Tsai标定方法原理推导
  • 轴角方法
    • 原理
    • matlab编程实现
  • 四元数方法
    • 原理
    • matlab编程实现

前言

最近由于组内的相关工作需求,需要进行机器人的高精度标定。原始的标定精度在6mm左右,虽然听起来是非常微小的偏差,但是由于研究方向手术机器人对精度要求极高。且在运动过程中,深度信息误差、畸变误差、机械误差、坐标系转换等一系列误差累积环节,将导致误差放大,因此远不能达到要求。

在经过了为其两周的研究和编程探索,最后成功将标定精度提升到0.5mm,达到了任务要求。

在研究和标定中,查阅了很多博客和相关资料,也踩了很多坑,作为也是小白的自己在研究和学习中发现当前的研究文献和博客最重要的问题是,没有从Tsai方法中AX=XB中,对A矩阵和B矩阵进行详细描述,也没有给出具体的计算代码和过程。默认读者已经搞清楚矩阵AB的物理意义和计算方式,对小白并不友好,笔者在进行手眼标定的过程中也经常由于计算错误A矩阵和B矩阵的左乘或右乘,得到错误的AB矩阵的输入数据,耽误了一些时间,因此在此记录

本系列的重点有三个:

  1. 原理中A,B矩阵的推导和代码计算
  2. Tsai轴角方法和四元数等二步计算方法的实现
  3. 基于非线性优化的高精度手眼标定方法的原理与代码实现

Tsai标定方法原理推导

传送门:

轴角方法

原理

以旋转矩阵进行公式表示可以得到分别关于旋转矩阵和平移矩阵的等式。对等式进行解耦可以分别的得到关于手眼变换矩阵的旋转公式和平移公式。
在这里插入图片描述

通过轴角法对tsai进行求解首先要通过机械手和相机在旋转过程中计算旋转轴向向量和旋转角度,通过两方进行log处理可以使用SVD方法求解旋转矩阵
在这里插入图片描述
最后通过,旋转矩阵与平移矩阵的关系即通过公式(3)进行求解,得到平移矩阵T的值

matlab编程实现

  % clc;
%% 通过四元数方法求解手眼变换矩阵
img_num =44;
%% RobotEffectorPose;      %机器人末端位姿导入
x = load("E:\研究生学习\手眼标定\IMG_20230516_3\pos.txt");  
pos = zeros(4,4,img_num);
% 各个点机械臂位姿变化矩阵
for i=1:1:img_num
    pos(:,:,i) = x(((4*(i-1)+1):4*i),:);
end
% 整合到连续矩阵中
REs = [];
for i=1:1:img_num
    REs = [REs;pos(:,:,i)];
end

%% 标定数据导入
load('calibrationSession0516_3.mat')
TE = calibrationSession.EstimationErrors.ExtrinsicsErrors.TranslationVectorsError;
% TE = estimationErrors.ExtrinsicsErrors.TranslationVectorsError;
Tex = mean(TE(:,1));   Tey = mean(TE(:,2));  Tez = mean(TE(:,3));
errorT = [Tex,Tey,Tez];
%% 求末端和摄像机转轴kl, kr.
kl=[]; kr=[]; theta_r=[]; theta_l=[];
for i = 1:img_num-2
    %% 求摄像机转轴Kr
    Rr1 = calibrationSession.CameraParameters.RotationMatrices(:,:,i);
    Rr2 = calibrationSession.CameraParameters.RotationMatrices(:,:,i+1);
    Rr3 = calibrationSession.CameraParameters.RotationMatrices(:,:,i+2);
%     Tr1 = calibrationSession.CameraParameters.TranslationVectors(i,:)+errorT;
%     Tr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:)+errorT;
%     Tr3 = calibrationSession.CameraParameters.TranslationVectors(i+2,:)+errorT;
    Tr1 = calibrationSession.CameraParameters.TranslationVectors(i,:)+errorT;
    Tr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:)+errorT;
    Tr3 = calibrationSession.CameraParameters.TranslationVectors(i+2,:)+errorT;
%     Rrt1 = [Rr1' (Tr1/1000)';0,0,0,1]/[Rr2' (Tr2/1000)';0,0,0,1];
%     Rrt2 = [Rr2' (Tr2/1000)';0,0,0,1]/[Rr3' (Tr3/1000)';0,0,0,1];
    Rrt1 = [Rr1' (Tr1/1000)';0,0,0,1]/[Rr2' (Tr2/1000)';0,0,0,1];
    Rrt2 = [Rr2' (Tr2/1000)';0,0,0,1]/[Rr3' (Tr3/1000)';0,0,0,1];
    [fr1, thetar1] = InvRot(Rrt1);
    [fr2, thetar2] = InvRot(Rrt2);
    kr = [kr,fr1,fr2,cross(fr1,fr2)];  
    theta_r = [theta_r;thetar1;thetar2];
    %% 求末端转轴Kl
    Re1 = REs(4*i-3:4*i,:);
    Re2 = REs(4*(i+1)-3:4*(i+1),:); 
    Re3 = REs(4*(i+2)-3:4*(i+2),:); 
    Ret1 = Re1\Re2;
    Ret2 = Re2\Re3;
    Rlt1 = Ret1(1:3,1:3);
    Rlt2 = Ret2(1:3,1:3);
    [fl1, thetal1] = InvRot(Rlt1);
    [fl2, thetal2] = InvRot(Rlt2);
    kl = [kl,fl1,fl2,cross(fl1,fl2)];  
    theta_l = [theta_l;thetal1;thetal2];
end
%% 求手眼矩阵旋转关系
Rm = kl*pinv(kr);      %-----------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 求手眼关系平移向量
Prs=[]; Pls=[];
for i = 1:img_num-1
%% (E-Rli)*Pm = Pli-Rm*Pri ----------构造右端    
    %% 末端平移向量
    TRl1 = REs(4*i-3:4*i,:); 
    TRl2 = REs(4*(i+1)-3:4*(i+1),:);   
    Rl = TRl1\TRl2;
    Pli = Rl(1:3,4);            %--------------Pli
    %% 相机外参平移向量
    
    TRr1 = calibrationSession.CameraParameters.RotationMatrices(:,:,i);
    TRr2 = calibrationSession.CameraParameters.RotationMatrices(:,:,i+1);
    TTr1 = calibrationSession.CameraParameters.TranslationVectors(i,:)-errorT;
    TTr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:)-errorT;
%     TTr1 = calibrationSession.CameraParameters.TranslationVectors(i,:);
%     TTr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:);
    TRlt = [TRr1' (TTr1/1000)';0,0,0,1]/[TRr2' (TTr2/1000)';0,0,0,1];
    Pri = TRlt(1:3,4);
    Pr = Pli - Rm*Pri;
    Prs = [Prs;Pr];    %--------------存储
%% (E-Rli)*Pm = Pli-Rm*Pri ----------构造左端   
    E=[1,0,0;0,1,0;0,0,1];
    Rli = Rl(1:3,1:3);
    Pl = E - Rli;
    Pls = [Pls;Pl];   %--------------存储
end
%% 求手眼矩阵平移向量
Pm = pinv(Pls)*Prs;  %-----------------------------------------
H_E = [Rm,Pm;0,0,0,1]
%clearvars -except  cameraParams  estimationErrors  H_E ;

在上述代码中需要注意的两点是数据的输入格式
其中矩阵A的输入为:

x = load("E:\研究生学习\手眼标定\IMG_20230516_3\pos.txt");  

pos.tx文件中储存的是机械臂各个位姿点,由机器人基座标系到末端坐标系的变换矩阵
在这里插入图片描述

矩阵B的输入为:

load('calibrationSession0516_3.mat')

其中calibrationSession0516_3.mat是通过相机标定工具箱保存在代码同一文件夹下的标定参数
在这里插入图片描述

四元数方法

原理

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

matlab编程实现

% clc;
%% 通过四元数方法求解手眼变换矩阵
img_num =46;
%% RobotEffectorPose;      %机器人末端位姿导入
x = load("E:\研究生学习\手眼标定\IMG_20230516_3\pos.txt");  
pos = zeros(4,4,img_num);
% 各个点机械臂位姿变化矩阵
for i=1:1:img_num
    pos(:,:,i) = x(((4*(i-1)+1):4*i),:);
end
% 整合到连续矩阵中
REs = [];
for i=1:1:img_num
    REs = [REs;pos(:,:,i)];
end

%% 标定数据导入
load('calibrationSession0516_3.mat')
TE = calibrationSession.EstimationErrors.ExtrinsicsErrors.TranslationVectorsError;
% TE = estimationErrors.ExtrinsicsErrors.TranslationVectorsError;
Tex = mean(TE(:,1));   Tey = mean(TE(:,2));  Tez = mean(TE(:,3));
errorT = [Tex,Tey,Tez];
%% 求末端和摄像机转轴kl, kr.
kl=[]; kr=[]; theta_r=[]; theta_l=[];
for i = 1:img_num-2
    %% 求摄像机转轴Kr
    Rr1 = calibrationSession.CameraParameters.RotationMatrices(:,:,i);
    Rr2 = calibrationSession.CameraParameters.RotationMatrices(:,:,i+1);
    Rr3 = calibrationSession.CameraParameters.RotationMatrices(:,:,i+2);
%     Tr1 = calibrationSession.CameraParameters.TranslationVectors(i,:)+errorT;
%     Tr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:)+errorT;
%     Tr3 = calibrationSession.CameraParameters.TranslationVectors(i+2,:)+errorT;
    Tr1 = calibrationSession.CameraParameters.TranslationVectors(i,:)+errorT;
    Tr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:)+errorT;
    Tr3 = calibrationSession.CameraParameters.TranslationVectors(i+2,:)+errorT;
%     Rrt1 = [Rr1' (Tr1/1000)';0,0,0,1]/[Rr2' (Tr2/1000)';0,0,0,1];
%     Rrt2 = [Rr2' (Tr2/1000)';0,0,0,1]/[Rr3' (Tr3/1000)';0,0,0,1];
    Rrt1 = [Rr1' (Tr1/1000)';0,0,0,1]/[Rr2' (Tr2/1000)';0,0,0,1];
    Rrt2 = [Rr2' (Tr2/1000)';0,0,0,1]/[Rr3' (Tr3/1000)';0,0,0,1];
    [fr1, thetar1] = InvRot(Rrt1);
    [fr2, thetar2] = InvRot(Rrt2);
    kr = [kr,fr1,fr2,cross(fr1,fr2)];  
    theta_r = [theta_r;thetar1;thetar2];
    %% 求末端转轴Kl
    Re1 = REs(4*i-3:4*i,:);
    Re2 = REs(4*(i+1)-3:4*(i+1),:); 
    Re3 = REs(4*(i+2)-3:4*(i+2),:); 
    Ret1 = Re1\Re2;
    Ret2 = Re2\Re3;
    Rlt1 = Ret1(1:3,1:3);
    Rlt2 = Ret2(1:3,1:3);
    [fl1, thetal1] = InvRot(Rlt1);
    [fl2, thetal2] = InvRot(Rlt2);
    kl = [kl,fl1,fl2,cross(fl1,fl2)];  
    theta_l = [theta_l;thetal1;thetal2];
end

Rm = kl*pinv(kr);      %-----------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 求手眼关系平移向量
Prs=[]; Pls=[];
C=[];D=[];
for i = 1:img_num-1
%% tx = (C'C)^(-1)*C'*D ----------构造C   
    %% 末端平移向量
    TRl1 = REs(4*i-3:4*i,:); 
    TRl2 = REs(4*(i+1)-3:4*(i+1),:);
    Rl = TRl1\TRl2;
    E=[1,0,0;0,1,0;0,0,1];
    C = [C;E-Rl(1:3,1:3)];            %--------------Pli
    %% 相机外参平移向量
    TRr1 = calibrationSession.CameraParameters.RotationMatrices(:,:,i);
    TRr2 = calibrationSession.CameraParameters.RotationMatrices(:,:,i+1);
    TTr1 = calibrationSession.CameraParameters.TranslationVectors(i,:)-errorT;
    TTr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:)-errorT;
%     TTr1 = calibrationSession.CameraParameters.TranslationVectors(i,:);
%     TTr2 = calibrationSession.CameraParameters.TranslationVectors(i+1,:);

%% tx = (C'C)^(-1)*C'*D ----------构造D 
    TRlt = [TRr1' (TTr1/1000)';0,0,0,1]/[TRr2' (TTr2/1000)';0,0,0,1];% 摄像头变换矩阵
    Tb = TRlt(1:3,4);       %摄像头
    Rx = Rl(1:3,1:3);
    Ta = Rl(1:3,4);    %末端
    D = [D;Rm*Tb-Ta];

end
Pm = pinv(C'*C)*C'*D;
%% 求手眼矩阵平移向量
% Pm = pinv(Pls)*Prs;  %-----------------------------------------
H_E = [Rm,Pm;0,0,0,1]
%clearvars -except  cameraParams  estimationErrors  H_E ;

传送门:
1.【从零开始进行高精度手眼标定 eye in hand(小白向)1 原理推导】
2.【从零开始进行高精度手眼标定 eye in hand(小白向)2 Tsai轴角法与四元数法编程实现】
3.【从零开始进行高精度手眼标定 eye in hand(小白向)3 非线性高精度标定法编程实现】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis的常用数据结构之集合类型

集合元素的特点 集合中的元素无序,不可以重复无法通过某一个下标的方式获取元素单个集合最多可以存储2的32次方-1个元素redis支持对集合取交集(共同兴趣点)、并集(两个开发团队擅长什么)、差集 单个集合中的操作指令…

系统架构设计师教程(第2版)

系统架构设计师教程(第2版)作为全国计算机技术与软件专业技术资格(水平)考试指定用书,系统地介绍了系统架构设计师的基本要求,应具备的基础知识和需要掌握的知识。 全书分上、下两篇,共计 20 章…

Qt Quick系列(6)—动画

🚀作者:CAccept 🎂专栏:Qt Quick 文章目录 前言1、简单动画代码示例 2、应用动画代码示例相关知识点 3、缓动曲线代码示例相关知识点 4、动画分组代码示例 5、嵌套动画代码示例 6、状态转换代码示例相关知识点 结语 前言 欢迎来…

安卓进阶(一)App性能优化

文章目录 性能优化的目的及方向流畅性启动速度页面显示速度响应速度 稳定性ANRCrash 资源节省性 布局优化选择耗费性能较少的布局减少布局的层级(嵌套)使用布局标签尽量少用布局属性wrap_contentincludemergeinclude与merge的区别ViewStub 内存泄露常见内…

计算机网络实验:交换机的Telnet远程登录配置

目录 前言实验目的实验内容实验过程画出拓扑图设置IP3,给交换机设IP,实际上相当于给VLAN 1 这个接口设置IP4,连网线5,测试网络是否连通6,通过telnet去管理配置交换机交换机的密码2、console密码3、telnet密码 总结 前言…

【CSS3系列】第五章 · web 字体

写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正&#xff0…

【3DsMAX】从零开始建房(1)

目录 目标 步骤 1. 制作地基 2. 制作台阶 3. 制作地砖 4. 制作第一层主体 5. 挖空第一层门的位置 6. 制作展示厅 目标 要做的房子模型如下: 步骤 1. 制作地基 首先创建一个长方体 可以将其转换为可编辑多边形,然后选中所有顶点,调…

【复变函数笔记】解析函数的定义和性质

文章目录 解析函数的等价定义解析函数的性质 解析函数的等价定义 解析函数的定义: f ( z ) f(z) f(z)在区域内可导则在区域内解析,在一点解析就是在某一邻域内可导。解析函数不可能只在一点解析。柯西-黎曼方程:函数 f ( z ) u ( x , y ) …

【TreeSet集合】比较器排序Comparator的使用

比较器排序Comparator的使用 存储学生对象并遍历,创建TreeSet集合使用带参构造方法 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序 创建学生类: package com.gather.set.treeset; public class Stude…

Nginx网站服务——编译安装与系统服务添加

一、Nginx简介 Nginx特点:(占用内存少,并发能力强) Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器,而且支持热部署,几乎可以做到 7 * 24 小时不间断运行,即使运行几个月也不需要重新启…

makefile编译debug版本调试时无法定位到源文件:No source available for “main() at 0x8544“

问题 之前用makefile编译了一个release版本的可执行文件,但是后面想调试的时候发现无法调试。 觉得-g就能解决问题,后面发现根本不行。 这里虽然加了-g选项,但是调试的时候还是出现找不到源文件的情况。 解决方案 对已有的C/C工程用mak…

Backtrader官方中文文档:第三章Quickstart Guide快速入门

本文档参考backtrader官方文档,是官方文档的完整中文翻译,可作为backtrader中文教程、backtrader中文参考手册、backtrader中文开发手册、backtrader入门资料使用。 快速入门章节目录 快速入门使用平台从0到100:一步一步的演示基本设置设置现…

2023PS beta 爱国版注册安装教程

软件介绍 主要新功能包括: Generative Fill功能(仅在Photoshop Beta桌面应用程序中可用)。这是一个新的工具,可以使用简单的文本提示非破坏性地添加,扩展或删除图像中的内容,以实现令人惊喜,高兴和震惊的真实结果——几秒钟内。要使用此功能,请选择图像中的目标对象或区域,然…

全局流控 or 端到端拥塞控制

同事推荐一篇论文 Bolt: Sub-RTT Congestion Control for Ultra-Low Latency,写点想法。 端到端原则使网络在拥塞控制中始终扮演配角,人们认为拥塞控制是端到端的事。几十年来人们设计的拥塞控制机制始终围绕 “主机在什么情况下要增减 cwnd” 打转。但…

MongoDB 分片集的基本概念

什么是分片集? 副本集(ReplicaSet) 用于解决读请求扩展、高可用等问题。但随着业务场景的进一步增长,可能会出现以下问题: 存储容量超出单机磁盘容量;活跃数据集超出单机内存容量,很多读请求需…

使用Python将《青花瓷》歌词生成词云图

哈喽大家好,因为上次有小伙伴问我,歌曲的歌词和评论怎么生成词云图,想买代码… 当时我就拒绝了,直接免费送给了他。 所以今天来分享给大家 我们以周董的《青花瓷》为例,要对《青花瓷》歌词生成词云图,需…

[Flash][AS3]“懒惰式引用计数回收内存“导致程序崩溃(闪退)

最近在做Flash项目时候发现,Flash CS6 发布的程序在运行超过两个小时后会闪退,在仔细检查脚本代码和资源文件后,排除了这两个方面的原因。又猜测是内存或者GDI泄漏,在任务管理器中观察了一段时间,程序的GDI没有任何变化…

电脑开机总是卡到不能动怎么重装系统?

电脑开机总是卡到不能动怎么重装系统?有用户反馈自己的电脑在开机之后,总是会出现卡死的情况,无法进行任何的操作。遇到这个问题我们可以使用U盘重装系统的方法来进行电脑系统的重装,接下来我们一起来看看以下具体的操作步骤教学吧…

3.7 图像压缩

博主简介:一个爱打游戏的计算机专业学生博主主页: 夏驰和徐策所属专栏:算法设计与分析 1.什么是图像压缩? 在动态规划中,图像压缩是指通过减少图像数据的存储空间,以实现图像文件的压缩和存储优化。动态规…

chatgpt赋能python:从后往前取:Python列表的高效操作

从后往前取:Python列表的高效操作 在Python编程中,列表(List)是最常用的数据类型之一。列表可以保存任意类型的元素,比如数字、字符串、甚至是其他列表等。在这篇文章中,我们将关注Python列表中从后往前取…