tf(二) transformPose
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "ros/ros.h"
#include <tf/transform_listener.h>
#include <tf/tf.h>

int main( int argc, char* argv[] )
{
ros::init(argc, argv, "test_tf");
ros::NodeHandle nh;

tf::TransformListener listener;
geometry_msgs::PoseStamped in_pose;
in_pose.header.frame_id = "parent";
in_pose.header.stamp = ros::Time(0);
in_pose.pose.position.x = 1;
in_pose.pose.position.y = 2;
in_pose.pose.position.z = 0;

in_pose.pose.orientation.x = 0;
in_pose.pose.orientation.y = 0;
in_pose.pose.orientation.z = 0.344215153;
in_pose.pose.orientation.w = 0.93889079678;
ros::Rate r(10);
while(nh.ok() )
{
out_pose.header.frame_id = "child";
// listener->waitForTransform("child", "parent", ros::Time(0), ros::Duration(1) );
listener->transformPose("child", in_pose, out_pose);
r.sleep();
}
}

如果没有while循环,只是一个transformPose,运行就会报错,找不到child,即使tf关系是正常的。如果还是报错,需要加上waitForTransform一行

问题

tf使用transformPose报错: Exception in transformPose: Quaternion malformed, magnitude: 0 should be 1.0

源码在此,原因是四元数平方和偏离1太多

1
2
3
4
5
6
7
8
9
inline void assertQuaternionValid(const geometry_msgs::Quaternion & q)
{
if(std::fabs(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w - 1) > 0.01)
{
std::stringstream ss;
ss << "Quaternion malformed, magnitude: " << q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w << " should be 1.0" <<std::endl;
throw tf::InvalidArgument(ss.str());
}
};

python中使用transformPose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import rospy
import tf
from tf import TransformListener

quaternion = tf.transformations.quaternion_from_euler(0, 0, self.ang, axes='sxyz')
self.temp_pose.pose = Pose(Point(self.goal_x, self.goal_y, 0.000), Quaternion(quaternion[0],quaternion[1],quaternion[2],quaternion[3]))
self.temp_pose.header.frame_id = 'base_link'
# 解决时间戳的问题
self.temp_pose.header.stamp = rospy.Time(0)
try:
self.tf_listener_.waitForTransform("odom", "base_link", rospy.Time(0), rospy.Duration(2.0) )
self.goal = self.tf_listener_.transformPose('odom', self.temp_pose)
except (tf.LookupException, tf.ConnectivityException):
rospywarn('tranform frame failed !')

开始总是出现target_frame does not exist的错误,但是用tf_echo明明可以看到两个坐标系,于是加上了waitForTransform,没有问题了。但是又出现了那个时间戳范围不足的问题,还是Time(0)解决

参考:
tf listener cant look up transform frame


三次样条曲线

单一多项式插值会有两个问题:

  1. 随着多项式的阶数越来越高,计算量也越来越大。
  2. 随着多项式的阶数越来越高,插值精度并不会越来越高,恰恰相反,函数曲线会出现剧烈的振荡,即龙格现象。

既能穿过所有已知点又能避免龙格现象(剧烈的震荡),就要用分段函数插值。就是把所有的已知数据分割成若干段,每段都对应一个插值函数,最终得到一个插值函数序列。

为了保证分段函数之间彼此衔接,每个分段函数都采用高次函数形式来构造(三次样条差值 就是用x的三次方形式构造)这就保证了多个函数之间的衔接光滑。不能用过高阶的函数,否则抖动太剧烈。

三次样条插值就是把已知数据分割成若干段,每段构造一个三次函数,并且保证分段函数的衔接处具有0阶连续,一阶导数连续,二阶导数连续的性质(也就是光滑衔接)。

三次样条曲线不算基于优化的算法,没有目标函数,不过有约束,而且边界约束有多种情况。

一阶连续意味着曲线y=S(x)没有急转弯,没有特别剧烈的跳变。二阶连续意味着每个点的曲率半径有定义。

给定n+1个数据点,共有n个区间,推导如下


自由边界:首尾两端没有受到任何让它们弯曲的力。让端点的斜率自由的在某一位置保持平衡,使得曲线的摇摆最小。
固定边界:首尾两端点的微分值是被指定的。用外力使柔软而有弹性的曲线经过数据点,并在端点处使其具有固定斜率。
非节点边界:指定样条曲线的三次微分匹配

文章QP方法解基于五次多项式形式的cost function minimization问题不是分段的多项式函数,问题相当简单。

参考:
原理
如何推导


gnuplot安装使用

安装依赖库

1
2
sudo apt-get install gnuplot
sudo apt-get install gnuplot-x11

输入 gnuplot,出现命令行,输入 plot sin(x),如果出现正弦曲线说明安装成功。

这样还没完,还需下载gnuplot,解压之后,输入 make 进行编译。如果在自己的项目需要使用gnuplot,将gnuplot_i.hpp复制到对应项目,在程序中加入 #include "gnuplot_i.hpp" 无需链接动态库, 这也是我使用它的主要原因,懒得改CMake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Gnuplot g1;

void plot(const vector<double>& Xs, const vector<double>& Ys, const string& comment, const string& style)
{
g1.set_grid();
g1.set_pointsize(3);
g1.set_style(style).plot_xy(Xs, Ys, comment);
}

void wait_for_key()
{
cout << "Press Enter to continue ..." <<endl;
cin.clear();
cin.ignore(cin.rdbuf()->in_avail() );
cin.get();
return;
}

/*以下为main函数部分*/
std::vector<double> X_t, Y_t;
/*对所有 vector 插入数据*/

g1.set_grid();
g1.set_xlabel("X"); //设置x轴说明
g1.set_ylabel("Y"); //设置y轴说明
// 图例文字
g1.set_style("lines").plot_xy(X_t, Y_t, "left");
plot(X_t, Y_t, "left", "points lc rgb 'red' pt 7");

wait_for_key();

图样(style): gnuplot 描绘数据数据图形是以读入档案中的坐标值后,以图样绘上。gnuplot可提供9种图样,我常用的是:

  1. lines: 将相邻的点以线条连接
  2. points: 将每一点以一符号绘上
  3. linespoints: 同时具有lines 及 points 的功能。

其他还包括

  • linestyle 连线风格(包括linetype,linewidth等)
  • linetype 连线种类
  • linewidth 连线粗细
  • linecolor 连线颜色
  • pointtype 点的种类
  • pointsize 点的大小

参考:C++ 调用Gnuplot实现图形绘制的过程


对纯跟踪算法的改进
Welcome to my blog, enter password to read.
Read more
transform和back_inserter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::string s("Hello");
std::string f;

// 前两个参数是变换的范围,第三个是变换的起始位置
// 第四个参数为lambda表达式,变换的规则
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::toupper(c); });
std::cout <<"after transform, s: "<< s << std::endl; // HELLO

std::transform( s.begin(), s.end(), std::back_inserter(f),
[](unsigned char c) { return std::tolower(c); } );
std::cout << "after transform, f: " << f << std::endl; // hello

std::copy( s.begin(), s.end(), std::back_inserter(f) );
std::cout << "after copy, f: " << f << std::endl; // helloHELLO

back_inserter属于插入迭代器,有三种插入迭代器(back_inserter,inserter,front_inserter),插入迭代器是指被绑定在一个容器上,可用来向容器插入元素的迭代器。 back_inserter是创建一个使用push_back的迭代器

1
2
3
vector<int> v={1,2,3,4,5,6};
vector<int> s;
copy(v.begin(), v.end(), back_inserter(s) ); // s: 123456

追加数据比较适合这种copy+back_inserter的做法。如果把新数据转为string,再用string的+运算符,这就太复杂了。数据很有可能是不以'\0'结尾,这样的话将出现严重bug。


tf2的学习(二)

有次我如此使用:

1
2
3
tf2_ros::Buffer buffer_;
tf2_ros::TransformListener listener(buffer_);
buffer_.transform(in_pose, out_pose, frame, transform_duration_);

结果报错: Exception in transformPose: “map” passed to lookupTransform argument source_frame does not exist

结果发现还是这个问题,我的用法其实相似,最好是把 buffer_listener都作为成员变量(指针),在构造函数里初始化listenner: listener = new tf2_ros::TransformListener(buffer_);, transform函数的使用就比较自由了

问题

使用tf2_ros::Buffer.transform()tf2_ros::Buffer.lookupTransform() 导致 “undefined reference” errors

解决: #include "tf2_geometry_msgs/tf2_geometry_msgs.h"

参考:


文本操作和读写文件

对字符串根据字符进行分段

1
2
3
4
5
6
7
8
9
vector<std::string> params;
std::string file = "123.45.abc.0";
std::istringstream param_stream(file);
std::string param;
while(getline(param_stream, param, '.'))
{
cout << param <<endl;
params.push_back(param);
}

运行结果:

1
2
3
4
123
45
abc
0

读Excel文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
bool readCSVInput(int N, vector<double> &input_points_x, vector<double> &input_points_y, vector<double> &anchor_points_x, vector<double> &anchor_points_y)
{
ifstream readCSV("/home/user/data.csv", ios::in);
int lineNum = 0;
string lineString;
vector<vector<string>> stringArray;
cout << endl;
while (getline(readCSV, lineString) )
{
//cout << lineString << endl;
if (lineNum > 0)
{
stringstream ss(lineString);
string s;
vector<string> oneLineStrings;
while (getline(ss, s, ',') )
oneLineStrings.push_back(s);
if (oneLineStrings.size() != 5)
{
cout << "ERROR:oneLineStrings.size() != 5" << endl;
return false;
}
else
{
input_points_x.push_back(std::stod(oneLineStrings[1]));
input_points_y.push_back(std::stod(oneLineStrings[2]));
anchor_points_x.push_back(std::stod(oneLineStrings[3]));
anchor_points_y.push_back(std::stod(oneLineStrings[4]));
}
}
lineNum++;
}
if (N == input_points_x.size())
{
return true;
}
else
{
input_points_x.clear();
input_points_y.clear();
anchor_points_x.clear();
anchor_points_y.clear();
cout << "ERROR:N == input_points_x.size()" << endl;
return false;
}
}

使用多个相机的记录

双目

代表:图漾, Astro Dabai, ZED

结构光

代表: OAK, 奥比中光, Realsense


Xvisio相机

按照指导书进行配置即可,需要注意的只有noetic的配置有些不同。
启动驱动

在xvisio_viewer查看SLAM的信息
我模仿驱动里的demo-api,写了一个程序获得SLAM位姿,跟xvisio_viewer的效果相同。发布的话题vslam_pose的频率足够满足需求。

xvisio相机启动时,必须面对有特征的环境,否则之后会一直不能获取SLAM信息。在相机移动过程中也要尽量面对有特征环境,如果面对纯色的环境,会失败,和遮挡镜头是一样的。 但即使这样,偶尔也有失败的情况。

反复测试后,感觉这个相机不适合我的需求,只好放弃了。

奥比中光-大白相机

我直接修改了奥比相机的驱动,从源头直通滤波、体素滤波、统计滤波。否则比较远的点云(比如3m以上)乱七八糟还极不稳定,根本不需要。另外两个方向的点云,坐标值比较大的时候,也极不稳定,没有使用的必要。

原始点云256000个点,滤波后,最多20000+个点。

装在机器人上,移动时,偶尔收不到点云数据

realsense D435f

比D435增加一个750nm 近红外滤光片(厚度0.5mm, CLAREX NIR-75N),一个mask。 红外滤光片(IR pass filter)装在相机的前端,两个孔对应红外发射和RGB相机。减少了光反射和阳光引起的深度噪声,它可传播近红外光和吸收可见光。

D435 和 D435f的对比
我做了很多试验,发现D435f确实好很多,不仅在阳光和有光反射情况下,连普通环境也减少了空洞现象。

D435f的射程收到红外发射器的限制,校正可能需要额外的红外补光。

考虑自己买750nm的滤光片装在D435上,淘宝发现很小的一块要80,真是贵。

参考: realsense-d435f


肇观电子的N1(价格 350)和M1(价格 750)相机

M1 主动双目,空洞率少。 N1 被动双目,空洞太多,想使用深度图和点云就不必考虑了。


mipi转USB


雷达的拖尾现象和处理
abstract Welcome to my blog, enter password to read.
Read more
ssh自动登录远程机和expect的使用

从本机连接到远程机,发起SSH连接,执行命令cd ~/Downloads;ls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

# router parameters
ROUTER_USER="xiaoqiang"
ROUTER_HOST="192.168.0.110"
ROUTER_PASSWORD="1"
ROUTER_PORT="22" # default

# start except
expect <<-EOF
set timeout -1
spawn ssh $ROUTER_USER@$ROUTER_HOST -p $ROUTER_PORT -o StrictHostKeyChecking=no -C "cd ~/Downloads;ls"
expect "*password:"
send "$ROUTER_PASSWORD\n\r\n"
expect eof;
EOF

首次远程连接时,会提示公钥确认,远程IP改变时,会提示公钥不一致,这种情况有时非常烦人。对于自动化任务,我们希望跳过公钥确认,修改/etc/ssh/ssh_config,加入:
1
StrictHostKeyChecking no

或者在命令行中使用-o参数: ssh -o StrictHostKeyChecking=no

运行结果:

1
2
3
4
5
6
7
spawn ssh xiaoqiang@192.168.0.110 -p 22 -o StrictHostKeyChecking=no -C cd ~/Downloads;ls
xiaoqiang@192.168.0.110's password:
c-ares-1.17.0.tar.gz
cd1folder.zsh
CursorTheme
darcula.css
g2o_curve_fitting

在password一行之后为命令执行的结果,一直没法实现换行,不太完美