tf的疑难问题

ROS版本不兼容产生的TransformListenerWrapper问题

AmclNode类中有这样一段:

1
2
3
4
5
// Use a child class to get access to tf2::Buffer class inside of tf_
struct TransformListenerWrapper : public tf::TransformListener
{
inline tf2_ros::Buffer &getBuffer() {return tf2_buffer_;}
};

这是Kinetic的版本,但是在Melodic里没有这一段了,这是ROS版本兼容问题,在Kinetic里用不了这个结构体了。搜索发现getBuffer只有在AmclNode::runFromBag里调用,因为平时不用bag仿真时测试AMCL,所以这句话可以注释掉。

“map” passed to lookupTransform argument target_frame does not exist

解决方法: 先加上listener.waitForTransform,再lookupTransform

The listener’s buffer, which carries all information about recent transformation, is literally empty. Therefore, any transform which looks-up the buffer does not find the frames it needs. It is good practice to wait for some time after the listener has been created so that the buffer can fill up. But instead of just sleeping, tf use waitForTransform

参考:


坐标系空间转换

机器人SLAM问题中涉及到平移和旋转,平移是在欧氏空间中,连续的平移变换只需要向量相加。 可是旋转是在非欧空间了,连续的旋转变换就是矩阵连续右乘

右手坐标系:x向前(roll),y向左(pitch),z向上(yaw)

用矩阵表示绕某轴旋转

旋转矩阵

坐标系A变换到B,从坐标系B到坐标系A的旋转矩阵的每一列,都是{B}的坐标轴单位向量在{A}中的表示

旋转矩阵R是一个行列式为1的正交矩阵,而且行向量和列向量的长度都为1,所以它的转置就是它的逆,而逆矩阵就可以将坐标系旋转回来。
R的每一行就是{A}的坐标轴单位向量在{B}中的表示。
SO(n)是特殊正交群,这个集合由n维空间的旋转矩阵组成。 SO(3)指三维空间的旋转。

变换矩阵

左上角为旋转矩阵,右侧为平移向量,这种矩阵又称为特殊欧氏群

逆矩阵的形式

如果一个点的坐标是 ,那么点作T变换后的坐标就写作 ,按齐次矩阵和坐标形式计算就可得到。

Censi 公式

我习惯用Censi论文的公式,还可以用Grisetti派系的
坐标系示意图.jpg
圆加和圆减.jpg

$\quad\quad\quad\quad\quad\quad\quad\Downarrow$

其实这里就是 ,只是在二维平面,R就是绕Z轴旋转的旋转矩阵形式。

左乘和右乘

左乘是行变换,右乘是列变换。这是因为习惯上来说,空间中的向量用列向量表示,用矩阵左乘列向量,就是把它在空间当中变换。

若绕静坐标系(世界坐标系)旋转,则左乘,也是变换矩阵 x 坐标矩阵。 在固定坐标系下,一个点 经过一个旋转矩阵,再经过一个旋转 ,总共旋转 ,这是左乘。 向量旋转中的右乘本没必要,甚至会引起人误解。

若是绕动态坐标系旋转(自身建立一个坐标系),则右乘,也就是坐标矩阵 x 变换矩阵

即左乘是相对于坐标值所在的坐标系(世界坐标系)下的三个坐标轴进行旋转变换,右乘则是以当前点为旋转中心,进行旋转变换。

车在全局坐标系中运动,这可以看做绕固定轴旋转的过程,是左乘矩阵

具体去看文章: 欧拉角,旋转矩阵,旋转向量,四元数

图优化里的相对位姿是由右乘获得的,即节点 $X_i$ 和 $X_j$ 之间的相对位姿是

常用的公式 是把点P从坐标系转换到 坐标系,由于旋转时,两个坐标系原点还是重合的,我们说经过了平移后,平移向量 是从坐标系 坐标系指向

参考:
cartographer中公式的推导


发布者的latch机制

这个就是最常用的advertise函数的最后一个参数,原型: advertise (const std::string &topic, uint32_t queue_size, bool latch=false)

是否锁存。某些话题并不是会以某个频率发布,比如/map这个topic,只有在初次订阅或者地图更新这两种情况下,才会向/map发布消息,这里就用到了锁存。

锁存的作用就是,将发布者最后一次发布的消息保存下来,然后把它自动发送给后来的订阅者。这种方式非常适合变动较慢、相对固定的数据(例如地图),然后只发布一次,相比于同样的消息不定的发布,锁存的方式既可以减少通信中对带宽的占用,也可以减少消息资源维护的开销。

查看gmapping中的函数SlamGMapping::startLiveSlam(),发现三个话题都是 latched:

1
2
3
entropy_publisher_ = private_nh_.advertise<std_msgs::Float64>("entropy", 1, true);
sst_ = node_.advertise<nav_msgs::OccupancyGrid>("map", 1, true);
sstm_ = node_.advertise<nav_msgs::MapMetaData>("map_metadata", 1, true);


latch=true,发布消息一次或几次,然后设法阻塞程序(常常用spin(),这里只是阻塞,跟订阅无关),订阅者仍然能收到阻塞之前发布的消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ros::init(argc,argv, "Pub");
ros::NodeHandle nh;
ros::Publisher pub_latch_true = nh.advertise<std_msgs::Int8>("topic_latch_true", 50, true);
ros::Publisher pub_latch_false = nh.advertise<std_msgs::Int8>("topic_latch_false", 50, false);
ros::Rate rate(1);

std_msgs::Int8 msg;
msg.data = 2;
pub_latch_true.publish(msg);
pub_latch_false.publish(msg);
while(ros::ok())
{
ros::spinOnce();
rate.sleep();
}
return 0;

运行程序后等一会,运行rostopic echo topic_latch_false,什么也没有。运行rostopic echo topic_latch_true,会收到data=2的消息。 即使先运行两个echo,再运行发布者,结果也是如此

bool Publisher::isLatched () const 用于判断是否latch


协方差矩阵

协方差可以表示两个变量的协同关系, 变化趋势是否一致。 相关性就是用X、Y的协方差除以X的标准差和Y的标准差。

我们机器人用到的向量是(x,y,z, roll,pitch,yaw),协方差就是一个6x6的矩阵,对角线的元素为各个随机变量的方差,非对角线的元素就是两两随机变量之间的协方差。AMCL中所用的协方差矩阵,由于z,pitch,roll都是0,矩阵的第1,8和最后的元素,分别是x,y,yaw的方差。另外还有x和y的协方差

如果协方差矩阵是单位矩阵,那么就是标准的多元高斯分布。由于协方差矩阵是对称矩阵,所以可以做特征分解

其实可以变成

因此任意一个协方差矩阵可以看做三维变换的结果,概率密度函数是由协方差矩阵的特征向量控制旋转,特征值控制尺度。 协方差矩阵就是根据这两项对标准高斯分布进行变换。

AMCL中的pf_ran_gaussian函数是以对角线元素为标准差,生成一个高斯分布。


虚拟墙/自定义的障碍

要想在地图中添加障碍,用virtual_layercostmap_prohibition_layer都可以,效果一样,区别就是前者的yaml里需要加上朝向,其实是没必要的。下面以virtual_layer为例向代价地图加入新的障碍。

进入costmap_2d这个包,查看costmap_2d\costmap_plugins.xml文件,可以看到目前所拥有的层,基类都是costmap_2d::Layer

配置代价地图

launch里加入

1
2
<rosparam file="$(find navigation2d)/param/virtual_layer.yaml" command="load" ns="global_costmap" />
<rosparam file="$(find navigation2d)/param/virtual_layer.yaml" command="load" ns="local_costmap" />

global_costmap_params.yamllocal_costmap_params.yaml里加入 - {name: virtual_layer, type: "virtual_costmap_layer::VirtualLayer"}

最后是设置禁止区域坐标参数,首先需要在参数配置文件夹(global_costmap_params.yaml所在的文件夹)中创建新的文档,命名为prohibition_areas.yaml,然后输入:

1
2
3
4
5
6
7
costmap_prohibition_layer:
prohibition_areas:
- [[-1.3, -2.98],[-1.3, -16.77]]
- [[13.31, -26.77],[2.88, -3.45]]
- [[-4.96, -37.78],[-32.67, -37.90]]
- [[-23.39, -33.43],[-11.89, -33.20]]
- [[0.0, 6.89],[0.0, 38.05]]

如果是virtual_layer.yaml,设置类似如下:
1
2
3
4
5
6
virtual_layer:
zone_topics: ["/zone"]
obstacle_topics: ["/obstacle"]
static_forms: [[[-18.46,-12.18,0.0],[-13.21,-12.18,0.0],[-13.21,-13.4,0.0],[-18.46,-13.4,0.0]],
[[-13.99,-0.17,0.0],[-11.87,-0.14,0.0]],
[[-13.18,-11.15,0.0],[-11.81,-11.15,0.0]]]

此时加载virtual_layer成功,但是障碍没有被膨胀,只有静态障碍和代价值

查来查去,怎么也找不到原因,最后是在两个代价地图的yaml里,把- {name: virtual_layer, type: "virtual_costmap_layer::VirtualLayer"} 放到膨胀层之前 ,问题解决,看来膨胀层的源码不完善。

注意事项:

  1. 一定要严格按照上述格式来设置坐标,格式错误导致不能识别禁止区域坐标情形有: (1)坐标前的短横线没对齐 (2) 定义禁止区域或者禁止线,两坐标之间缺少了逗号
  2. 可以同时定义多个点障碍/线障碍/障碍区域。对于3个及以上的点,形成的是封闭的区域障碍。
  3. 禁止区域的坐标是map坐标系的
  4. 由于Bresenham算法,形成的线可能看起来不够直
    18_1119.png

costmap_prohibition_layer

源码在costmap_prohibition_layer

CMakeLists里可以看到add_library(costmap_prohibition_layer src/costmap_prohibition_layer.cpp), 结果在lib/libcostmap_prohibition_layer.so

看文件package.xml,最后有一句:

1
2
3
<export>
<costmap_2d plugin="${prefix}/costmap_plugins.xml"/>
</export>

costmap_plugins.xml如下:

1
2
3
4
5
<library path="lib/libcostmap_prohibition_layer">
<class type="costmap_prohibition_layer_namespace::CostmapProhibitionLayer" base_class_type="costmap_2d::Layer">
<description>ROS-Package that implements a costmap layer to add prohibited areas to the costmap-2D by a user configuration.</description>
</class>
</library>

以上几项缺一不可,放到工作空间编译后,在终端中输入rospack plugins —attrib=plugin costmap_2d ,这个命令是显示当前costmap_2d能接受的所有插件,如果出现

1
costmap_prohibition_layer /home/user/plan_ws/src/costmap_prohibition_layer/costmap_plugins.xml

说明prohibition_layer已经是一个可供使用的地图插件了。


虚拟墙的坐标.png

参考:
在Costmap_2d里面插入Prohibition_layer


ROS中的Lazy发布和订阅

之前看depthimage_to_laserscan包,发现它的订阅发布采用的是所谓 lazy模式。今天研究了一下,发现并不复杂。先看advertise函数的一个原型:

1
2
3
4
5
6
7
Publisher ros::NodeHandle::advertise(const std::string& topic,
uint32_t queue_size,
const SubscriberStatusCallback & connect_cb,
const SubscriberStatusCallback & disconnect_cb = SubscriberStatusCallback(),
const VoidConstPtr & tracked_object = VoidConstPtr(),
bool latch = false
)

原来有三个原型,我们常用的是第一个,这个是第二个,第三个就极其罕见了,形参是个AdvertiseOptions类型。

话题有订阅者时,发布者会触发connect_cb函数,停止订阅时又触发disconnect_cb函数。如果让它们做类成员函数,就使用Boost::bind形式,tracked_object暂时不用。所以程序可以这样写:

1
2
3
4
5
6
7
8
9
10
void connectCb(const ros::SingleSubscriberPublisher& singlePub)
{
ROS_INFO("Subscriber Name: %s", singlePub.getSubscriberName().c_str());
}
void disconnectCb(const ros::SingleSubscriberPublisher& singlePub)
{
ROS_INFO("disconnectCb");
}
......
ros::Publisher pub = nh.advertise<std_msgs::Int8>("topic", 50, connectCb,disconnectCb);

可以用rostopic echo topic测试


再来看depthimage_to_laserscan的程序,构造函数里是这样的:

1
2
3
4
pub_ = n.advertise<sensor_msgs::LaserScan>("scan", 10, 
boost::bind(&DepthImageToLaserScanROS::connectCb, this, _1),
boost::bind(&DepthImageToLaserScanROS::disconnectCb, this, _1)
);

它是把两个函数用成员函数的形式,connectCb是这样的:
1
2
3
4
5
6
if (!sub_ && pub_.getNumSubscribers() > 0)
{
ROS_DEBUG("Connecting to depth topic.");
image_transport::TransportHints hints("raw", ros::TransportHints(), pnh_);
sub_ = it_.subscribeCamera("/camera/depth/image_raw", 10, &DepthImageToLaserScanROS::depthCb, this, hints);
}

depthCb是:

1
2
sensor_msgs::LaserScanPtr scan_msg = dtl_.convert_msg(depth_msg, info_msg);
pub_.publish(scan_msg);

显然逻辑是这样的:有其他节点订阅scan话题时,发布者里才订阅image_raw话题,然后才会发布scan话题的实际消息,所谓的lazy就是scan话题的消息发布

disconnectCb里,如果另一个节点不再订阅scan话题,本节点就会sub_.shutdown();;如果再有订阅scan,又可以通过connectCb使用sub_,设计确实很巧妙

参考:
Publisher advertise
SingleSubscriberPublisher


USB和串口的设备号绑定

参考:这篇博客,意外的是我配置rplidar时发现标志号码和博客中是一样的,还以为像产品序列号那样。我设置了底盘的USB和思岚雷达的USB

realsense的安装包里面包含了udev的文件,即realsense udev的deb。所以使用过realsense的系统,自己没编辑,也会有一个99-realsense-libusb.rules,不必自己设置

70-snap.core.rules是系统自带的


ROS运行和调试利器
  • rosed: 可以直接编辑某个package当中的文件,格式是rosed package file,还可以用tab补全名称,如果文件名在package里不是唯一的,那么会呈现出一个列表,让你选择编辑哪一个文件。

  • roswtf: 检查ROS各节点联系是否有错;检查环节变量等系统设置

rostopic

  • rostopic echo -b BAGFILE -a, 显示bag文件的所有消息。但是所有消息一次显示,没什么意义。

  • rostopic echo -n1, 只echo一段消息

  • rostopic echo -p --nostr --noarr /topic_name, 不显示话题中的string和array部分

  • rostopic echo /odom/pose/pose/position, 直接显示odom话题中的position部分

  • rostopic bw topic_name: 用于显示主题所使用的带宽。

  • rostopic delay topic_name : 显示ROS节点通信延迟

以下命令可以组合使用:

  • rostopic list -p, 只显示发布的话题

  • rostopic list -s, 只显示订阅的话题

  • rostopic list -v, 显示话题细节,有几个发布者和订阅者

  • rostopic list --host, 根据host名分组

有一次使用rostopic pub一个std_msgs/Int8的消息,结果报错 Inbound TCP/IP connection failed: field data must be an integer type 。 确实echo没有结果,另开一个终端,source 工作空间后成功。

rosmsg rosnode

  • rosmsg package [package_name], 显示所有在这个包里面的消息

  • rosmsg md5 [message], 显示某个消息的md5sum

  • rosnode cleanup, 清除僵尸节点, 也就是清除那些实际关闭但能用rosnode list查看到的节点

  • rosnode machine <hostname>: 用于列出指定计算机上运行的节点

  • rospack find turtlesim, 输出指定package的路径, 输出/opt/ros/kinetic/share/turtlesim

  • rospack depends turtlesim, 查看package的依赖项

roslaunch

  • --wait, 延迟launch直到检测到roscore

roslaunch启动的时候,会尝试获得已有的rosmaster的id,如果没有获得,将会创建一个自己的。假如同时也有一个roslaunch和roscore启动,可能会导致run_id冲突。比如roslaunch启动太快.png

可以使用roslaunch --wait rplidar_ros a2.launch,它会输出这样的信息:

1
2
roscore/master is not yet running, will wait for it to start
master has started, initiating launch

  • --screen, 输出所有节点的日志,用于debugging.

  • --dump-params, 以YAML格式输出launch文件中的所有参数

参考:roslaunch Commandline Tools


tf2的学习(一)

现在的ROS提倡使用tf2

tf2经过重新设计,只提供tf的关键功能,不涉及转换等函数。

tf_conversions.transformations.quaternion_from_euler(roll, pitch, yaw)

每个listener都有一个buffer储存所有tf广播发出的transforms,当广播发出transform时,需要花点时间(毫秒级)才会进入buffer,所以请求now的transform时,会有一小段时间差。

使用tf2_ros::BufferlookupTransform()函数可以获得tf树的指定时间的转换。常用的指定时间是ros::Time(0)ros::Time::now,前者是缓冲区中最新可用的转换,后者就是当前的时间。对于now,由于时间差,可能出现报警

有时会出现报警: Lookup would require extrapolation into the past

对于这个报警,我们有四种解决方法:

  1. 完善lookupTransform的参数,增加ros::Duration(sec),sec大于报警中的时间差即可
  2. 使用tf2_ros::Buffer的canTransform函数,有可用的变换了再获得
  3. 使用tf message filter
  4. 忽视这个报警,未成功获取的新transform会放弃

一个比较优雅的程序是这样的:

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
// #include <ros/ros.h>
// #include <tf2_ros/transform_listener.h>
// #include <geometry_msgs/TransformStamped.h>

ros::init(argc, argv, "tf_node");
ros::NodeHandle nh;
tf2_ros::Buffer buff;
tf2_ros::TransformListener listener(buff);
ros::Publisher pub = nh.advertise<geometry_msgs::TransformStamped>("transform",10);
ros::Rate rate(400);
while(nh.ok())
{
geometry_msgs::TransformStamped trans;
try {
ros::Time now = ros::Time::now();
// if(buff.canTransform("base_footprint","imu",now,
// ros::Duration(0.03),NULL)
// 30毫秒应该足够了
trans = buff.lookupTransform("base_footprint",
"imu", now,
ros::Duration(0.03) );
// else
// ROS_WARN("no transform in buffer");
} catch (tf2::TransformException & ex) {
ROS_WARN("%s", ex.what());
ros::Duration(1.0).sleep();
continue;
}
pub.publish(trans);
rate.sleep();
}

创建listener后,会接受tf2 transformations,对其缓存10秒。TransformListener对象应当是persist,否则cache不能填充。常见方法是让TransformListener对象作为类的成员变量。

lookupTransform获取的是TransformStamped消息,最后发布出来。因为加了Duration,try catch可以去掉。

由于实际上加了Duration,所以得到的不是当前的,而是上一个“当前”。换句话说,想得到当前最新的transform其实是没有意义的,一般都用ros::Time(0),Wiki上也是这么说的

怎么才知道我们修改后的程序有效了?一个是看程序运行后是否报警,还有就是echo transform话题,结果可以看到头部

只看seq和时间,如果增长一直很流畅,那就是修改生效了。如果修改还有问题,比如Duration时间太短,seq在增长一会后会有停顿,然后继续增长,这就是buffer里面空了,tf数据还没有插入到里面

已知点在子坐标系中的坐标和父子坐标系的变换,求点在父坐标系的坐标

用tf::transformPose更方便
1
2
3
4
5
6
7
8
9
10
geometry_msgs::PointStamped point_in, point_out;
point_in.header.frame_id = "child";
point_in.header.seq = 0;
point_in.header.stamp = ros::Time(0);
point_in.point.x = 2;
point_in.point.y = 1;
point_in.point.z = 4;

// buff跟上面的使用一样, 返回的point_out是point_in在 parent坐标系 的坐标
point_out = buff.transform(point_in, point_out, "parent", ros::Duration(1));

使用这段程序前,必须在find_package里添加 tf2-geometry-msgs,否则编译不成功。如果没有,需要先安装这两个包:

1
2
3
# 依赖库,  即使目前有,也要安装,可能需要更新
sudo apt-get install ros-kinetic-orocos-kdl
sudo apt-get install ros-kinetic-tf2-geometry-msgs

手动实现 tf2_ros::Buffer::transform函数

也就是用程序实现上面的转换

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
47
48
49
50
51
52
53
54
55
56
57
58
ros::init(argc, argv, "test_tf");
ros::NodeHandle nh;

tf2_ros::Buffer buff;
tf2_ros::TransformListener listener(buff);
geometry_msgs::TransformStamped transform =
buff.lookupTransform("parent","child",ros::Time(0),ros::Duration(1));

ROS_INFO("transform from parent to child x: %f",transform.transform.translation.x);
ROS_INFO("transform from parent to child y: %f",transform.transform.translation.y);
ROS_INFO("transform from parent to child z: %f\n",transform.transform.translation.z);

geometry_msgs::PointStamped point_in, point_out;
point_in.header.frame_id = "child";
point_in.header.seq = 0;
point_in.header.stamp = ros::Time(0);
// 在 child 坐标系中的点
point_in.point.x = 2;
point_in.point.y = 1;
point_in.point.z = 4;

ROS_INFO("point in child: 2, 1, 4\n");
point_out = buff.transform(point_in, point_out, "parent", ros::Duration(1));

ROS_INFO("point int parent, x: %f",point_out.point.x);
ROS_INFO("point int parent, y: %f",point_out.point.y);
ROS_INFO("point int parent, z: %f\n",point_out.point.z);

cout<<fixed<<setprecision(2);

Eigen::Quaterniond quaternion(transform.transform.rotation.w,
transform.transform.rotation.x,
transform.transform.rotation.y,
transform.transform.rotation.z
);
Eigen::Matrix3d rotation = quaternion.matrix();
cout << "rotation matrix: " <<endl<< rotation <<endl<<endl;

Eigen::Matrix4d m;
m.block(0,0,3,3) = rotation;

Eigen::Vector4d Vcol, Vrow;
Vcol << transform.transform.translation.x,
transform.transform.translation.y,
transform.transform.translation.z, 1;
m.col(3) = Vcol;

Vrow << 0,0,0,1;
m.row(3) = Vrow.transpose();
cout << "transform matrix: "<<endl<< m<<endl<<endl;

Eigen::Vector4d point;
point << point_in.point.x,
point_in.point.y,
point_in.point.z, 1;

Eigen::Vector4d transformed_point = m * point;
cout << "transformed point: "<<endl<<transformed_point.head(3) << endl;

我们已知parent-child的变换,首先需要从位移和欧拉角获得齐次变换矩阵,把point_in变换为齐次坐标,然后左乘齐次矩阵,再取结果的前三个元素。 tf变换的本质就是左乘变换矩阵

常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
void tf2::Transform::setIdentity()
{
// Matrix3x3 m_basis;
m_basis.setIdentity(); // 单位矩阵
// Vector3 m_origin;
m_origin.setValue(tf2Scalar(0.0), tf2Scalar(0.0), tf2Scalar(0.0));
}

Transform::operator*(const Transform& t) const
{
return Transform(m_basis * t.m_basis,
(*this)(t.m_origin) );
}

参考:
tf2_ros::Buffer Class
tf2_ros::MessageFilter
tf2_ros::BufferInterface
using “tf2” to transform coordinates

古月学院:位置角度平移旋转,“乱七八糟”的坐标变换


配置 vnc server

SSH是不带界面的协议,之前在机器人远程机配置vnc4server,从本地连接到远程机,希望能在远程机直接运行rviz以进行观测,不知道失败了多少次,今天又试了试,这次得到如下结果:
vnc4server环境加载rviz失败.png
这是因为rviz是基于Qt和OpenGL的,但是vnc4server对OpenGL的支持有问题,所以报错

目前最好的就是x11vnc。这个程序不仅不收费,是开源的,而且还支持opengl程序,rviz之类的程序也可以正常打开了。

VNC经典BUG: 能连接成功,不能操作界面,SSH可以操作
  1. 安装x11vnc
1
sudo apt-get install -y x11vnc net-tools
  1. 设置访问密码
1
sudo x11vnc -storepasswd

按提示设置密码,密码一般放在/home/user/.vnc/passwd

  1. 创建服务文件

sudo vim /lib/systemd/system/x11vnc.service,文件内容如下:

1
2
3
4
5
6
7
8
9
10
[Unit] 
Description=Start x11vnc at startup.
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/USERNAME/.vnc/passwd -rfbport 5900 -shared

[Install]
WantedBy=multi-user.target

  1. 启动服务:
    1
    2
    3
    sudo systemctl daemon-reload
    sudo systemctl enable x11vnc.service
    sudo systemctl start x11vnc.service

一般来说,经过上面步骤就成功了。但是可能出现下面错误
status running.png
failed for display.png

其实上面一大堆配置都是为了开机启动,在4之前可以先手动运行,直接x11vnc即可,看输出的文本是否正常,端口有可能是5901,可以用netstat命令检查

手动启动x11vnc的结果
Linux安装x11vnc server的结果
ubuntu上的x11vnc界面,可直接打开GUI程序

成功运行VNC前后的 netstat

  1. 客户端

下载vnc viewer,输入目标IP,端口5900,之后就可以正常连接了,比如192.168.0.103:5900

如果在不插显示器使用rviz时还是报错, 插上hdmi转vga的转接头(不接显示器,只是转接头) 就可以打开正常使用了。如果此时再外接一个显示器,实际就成了双屏配置,在VNC里会出现长屏幕,对于大显示器,用着更舒服了。

但是如果只插一个HDMI线,可以显示,但拖拽终端会出现重影

可以在设置里面调整分辨率和比例,如果感觉VNC速度慢,可以禁用Compiz

有的网络下,会出现经常掉线重连的情况,此时只需把画面质量改为Medium即可,当然Low更可以。

参考:
ubuntu18.04安装x11vnc


配置vino

  1. 安装
1
2
3
4
5
6
7
8
9
sudo apt update

sudo apt install vino

sudo ln -s ../vino-server.service /usr/lib/systemd/user/graphical-session.target.wants

# 配置VNC server:
gsettings set org.gnome.Vino prompt-enabled false
gsettings set org.gnome.Vino require-encryption false

使用命令 /usr/lib/vino/vino-server开启

  1. 每连接到一个新的wifi ,都需要在设置的页面把共享的wifi打开

  2. 将网卡加入VINO服务命令

1
2
3
4
5
6
7
8
# 用于显示 NetworkManager(网络管理器)中当前配置的网络连接列表及其详细信息.
nmcli connection show

# 将指定的 UUID(Universally Unique Identifier)添加到 Vino 服务器启用的连接列表中,指定允许远程访问和控制你的计算机的特定连接.
dconf write /org/gnome/settings-daemon/plugins/sharing/vino-server/enabled-connections "['your UUID']"

# export DISPLAY作用是指定 X Window 系统的显示器,通过设置 DISPLAY 环境变量,它们可以知道要将图形显示在哪个显示器上,默认是0.
export DISPLAY=:0
  1. 设置开机自启动
1
2
3
4
5
6
7
8
9
10
11
gsettings set org.gnome.Vino enabled true
mkdir -p ~/.config/autostart
vim ~/.config/autostart/vino-server.desktop


# 将下面的内容添加到该文件中,保存并退出。
[Desktop Entry]
Type=Application
Name=Vino VNC server
Exec=/usr/lib/vino/vino-server
NoDisplay=true
  1. 安装虚拟显示器

经过以上设置,连接VNC后可能是splash screen或者说花屏,还是不正常。用虚拟显示器解决

1
sudo apt-get install  -y  xserver-xorg-core-hwe-18.04  xserver-xorg-video-dummy-hwe-18.04

sudo vim /usr/share/X11/xorg.conf.d/xorg.conf加入下面内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Section "Device"
Identifier "Configured Video Device"
Driver "dummy"
EndSection

Section "Monitor"
Identifier "Configured Monitor"
HorizSync 31.5-48.5
VertRefresh 50-70
EndSection

Section "Screen"
Identifier "Default Screen"
Monitor "Configured Monitor"
Device "Configured Video Device"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1920x1080"
EndSubSection
EndSection

重启后应该就正常了。如果连接到显示器,反而会不能正常显示,那么就把配置文件删了。

参考: vino的配置