调试的过程太麻烦了,因此打算详细解释一下每步的含义,很多地方懂了之后发现其实很简单,但是学起来却发现很多地方无从下手,因为资料太少了,真的都是不断踩坑一点一点摸索出来的,写以此文以便后人乘凉

此处将展示一个完全独立的节点的编写过程,如果读者打算移植算法到ROS平台可以稍作阅读,首先是在仿真环境下要产生可以订阅的激光雷达数据和地图数据,最开始尝试了fake系列包的定位,但是尝试了几天之后发现,fake定位是真的fake,使用的是fake_localization,没有输出任何可用的传感器数据,完全是使用的是里程计数据Odometry转化为tf数据,因此仿真时机器人会疯狂撞墙,因此需要换一个仿真

然后找到了gmapping的仿真,使用的是古月居的Package,链接文末给出了,不要下载rbx的,那个是面向ubuntu1404-ros-indigo的包,每个ubuntu版本有对应的ros包,不能互通,使用前建议先查看一下环境

这个是用gazebo仿真一个带障碍物和机器人的环境
roslaunch mrobot_gazebo mrobot_laser_nav_gazebo.launch
这个是用启动gmapping仿真(应该是arbotix)
roslaunch mrobot_navigation gmapping_demo.launch
这个是输出cmd_vel的控制台,用于控制小车移动的
roslaunch mrobot_teleop mrobot_teleop.launch

前两个仿真的界面可以关闭,只要不关控制台,话题是一直存在的,调试程序阶段可以关掉,以节省CPU算力,后面不建议关闭,因为需要查看小车实时位置

文件存储格式参考其他的数据包,也可以在文末的Git链接里面下载到源码,编译IDE使用的是roboware-studio,一个基于vscode的编辑器,有不少ROS的特性

然后自己建立新的结点订阅数据,数据类型使用“rostopic info /xxx”来查看类型,也可以在rviz下点add查看发布的数据类型,设置订阅队列长度为1,这样只订阅最新的消息

int main(int argc, char **argv) {
//初始化节点
ros::init(argc, argv, "laser_listener");
ros::NodeHandle nh; //订阅gmapping在gazebo仿真下的数据
ros::Subscriber subMapParam = nh.subscribe("/map_metadata", , mapParamCallback);
ros::Subscriber subMap = nh.subscribe("/map", , mapCallback);
ros::Subscriber subScan = nh.subscribe("/scan", , laserCallback); //循环读取
ros::spin();
return ;
}

然后获取地图参数,原数据名字为“MapMetaData”,但笔者选择了之前的命名方式mapParam,然后读取数据,这里只能用 “->” 来读取,不能使用 “.” 来获取子元素,可以使用ROS_INFO来输出数据到控制台

void mapParamCallback(const nav_msgs::MapMetaData::ConstPtr &msg) {
mapParam.oriMapCol = msg->width;
mapParam.oriMapRow = msg->height;
mapParam.mapResol = msg->resolution;
mapParam.mapOriX = msg->origin.position.x;
mapParam.mapOriY = msg->origin.position.y;
//ROS_INFO("%d %d %lf %lf %lf\n", oriMapRow, oriMapCol, mapOriX, mapOriY, mapResol);
}

然后获取地图Map数据,这里用了两个保险,分别是检测是否有数据输入,没有则等待输入,另一个是是否处理完Map数据,如果没有处理完则特生匹配会报错,数据储存在msg下的data里面,虽然能自动补全出其他子元素,但是很多并不能查看,具体格式可以参考文末官方文档,读取方式用数组的方式读取,没有使用c++的迭代器,然后就是数据格式,因为ROS使用的是栅格地图,一种基于概率的地图表示方法,因此用0表示空闲(探测过但是没有障碍物),100表示占据(探测过并确认有障碍物),用1~99表示存在障碍物的概率(探测过),用255(在unsigned int 8下也是-1)表示未知(未探测过),这和笔者前面写的算法的表示方法不同,笔者的表示方式中0是未知,1是占据(有障碍物),-1(255)是空闲(无障碍物),所以做了一个映射,(暂时没有考虑概率方式的计算),然后是生成贝叶斯概率距离地图和用LSD算法提取直线信息

void mapCallback(const nav_msgs::OccupancyGrid::ConstPtr &msg) {
if (mapParam.oriMapCol <= || mapParam.oriMapRow <= )
return;
isMapReady = false;
mapValue = cv::Mat::zeros(mapParam.oriMapRow, mapParam.oriMapCol, CV_8UC1);
//int max = 0;
int cnt_col, cnt_row;
//ROS_INFO("size%d %d %lf\n", msg->info.height, msg->info.width, msg->info.resolution); //取消注释FILE相关的可以输出地图到文件,在Windows下运行main_on_windows使用
//FILE *fp = fopen("mapValue.txt", "w+");
for (cnt_row = ; cnt_row < mapParam.oriMapRow; cnt_row++) {
for (cnt_col = ; cnt_col < mapParam.oriMapCol; cnt_col++) {
uint8_t value = msg->data[cnt_row * mapParam.oriMapCol + cnt_col];
if (value == ) {
//fprintf(fp, "%d ", 0);
mapValue.ptr<uint8_t>(cnt_row)[cnt_col] = ;
}
else if (value == ) {
//fprintf(fp, "%d ", 255);
mapValue.ptr<uint8_t>(cnt_row)[cnt_col] = ;
}
else {
//fprintf(fp, "%d ", 1);
mapValue.ptr<uint8_t>(cnt_row)[cnt_col] = ;
}
}
}
//fclose(fp);
// cv::imshow("mapValue", mapValue);
// cv::waitKey(1);
//计算mapCache,用于特征匹配的先验概率
double z_occ_max_dis = ;
mapCache = mylsd::createMapCache(mapValue, mapParam.mapResol, z_occ_max_dis);
//LineSegmentDetector 提取地图边界直线信息
LSD = mylsd::myLineSegmentDetector(mapValue, mapParam.oriMapCol, mapParam.oriMapRow, 0.3, 0.6, 22.5, 0.7, );
isMapReady = true;
}

最后是持续输入的激光雷达数据,因为地图数据只有更新才会传入,因此必须等待地图数据发布并且处理完才能处理激光雷达数据并做特征匹配,所以加了一个保险,这里储存的方式是距离和增量角度,因此做一个简单的转换,格式是float,如果用double类型会报错,然后做特征匹配并用OpenCV输出(目前情况在每次地图更新之后会清空坐标点,可以自行增加缓存机制来储存位置信息),当然,读者也可以发布tf坐标然后用rviz显示

void laserCallback(const sensor_msgs::LaserScan::ConstPtr &msg) {
//等待地图处理结束
if (isMapReady == false)
return;
//读取激光雷达数据
std::vector<float> ranges = msg->ranges;
int len_lp = ;
for (int i = ; i < ranges.size(); i++) {
if (ranges[i] != INFINITY) {
lidarPointPolar[i].range = ranges[i];
//角度是用增量式方式来储存的
lidarPointPolar[i].angle = msg->angle_min + i * msg->angle_increment;
lidarPointPolar[i].split = false;
len_lp++;
}
//ROS_INFO("No.%d RPLIDAR:%lf %lf", i, lidarPointPolar[i].range, lidarPointPolar[i].angle);
} if (len_lp != ) {
//匹配雷达特征到地图特征 返回像素坐标和真实坐标
myrdp::structFeatureScan FS = FeatureScan(mapParam, lidarPointPolar, len_lp, , 0.04, 0.5);
imshow("RPLidar", FS.lineIm);
waitKey(); double estimatePose_realworld[];
double estimatePose[];
Mat poseAll;
//数据接口转换
structFA FA = trans2FA(FS, LSD, mapParam, lidarPointPolar, len_lp);
//特征匹配
myfa::FeatureAssociation(FS.lineIm, FA.scanLinesInfo, FA.mapLinesInfo, mapParam, FA.lidarPos, LSD.lineIm, \
mapCache, mapValue, FA.ScanRanges, FA.ScanAngles, estimatePose_realworld, estimatePose, poseAll); //将图像坐标加入地图中
LSD.lineIm.ptr<uint8_t>((int)estimatePose[])[(int)estimatePose[]] = ;
imshow("lineIm", LSD.lineIm);
waitKey();
}
}

目前数据仅仅是做了最基本的特征匹配,还没有增加优化算法,因此在特征不明显的地方漂移比较大,但是在特征点明显的地方,比如拐角处定位效果还是不错的

然后是编译需要的CMakeLists,分别是生成文件夹的名字“project(lsd)”,c11特性,包含库的路径find_package及需要包含的数据类型(如topic类型),然后在catkin_package里面也添加对应的数据类型,然后在包含路径里面添加catkin,因为笔者用到了opencv,所以添加了opencv路径,之后是生成可执行文件和添加链接库

cmake_minimum_required(VERSION 2.8.)
project(lsd) add_compile_options(-std=c++) find_package(catkin REQUIRED COMPONENTS
roscpp
sensor_msgs
geometry_msgs
)
find_package(OpenCV) catkin_package(
CATKIN_DEPENDS
roscpp
sensor_msgs
geometry_msgs
INCLUDE_DIRS include
) include_directories(
include
${catkin_INCLUDE_DIRS}
${OpenCV_INCLUDE_DIRS}
) add_executable(lsd
src/main_on_linux.cpp
src/baseFunc.cpp
src/FeatureAssociation.cpp
src/myLSD.cpp
src/myRDP.cpp)
target_link_libraries(lsd ${catkin_LIBRARIES})
target_link_libraries(lsd ${OpenCV_LIBS})

然后是package.xml,name里面是生成文件夹的名字,然后添加需要的数据类型(同上),需要同时添加build_depend和run_depend(可能部分库可以只添加一项,为了不出错,建议都添加)

<?xml version="1.0"?>
<package>
<name>lsd</name>
<version>0.0.0</version>
<description>The test package</description> <maintainer email="pyrokinecist@aliyun.com">pyrokine</maintainer> <license>TODO</license> <buildtool_depend>catkin</buildtool_depend>
<build_depend>geometry_msgs</build_depend>
<build_depend>sensor_msgs</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>tf</build_depend>
<run_depend>geometry_msgs</run_depend>
<run_depend>sensor_msgs</run_depend>
<run_depend>roscpp</run_depend>
<run_depend>tf</run_depend> <!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here --> </export>
</package>

然后编译完就可以执行了,理论上,此节点可以先于也可以晚于其他节点包括master节点开启,如果运行时报错提示特征匹配文件内出错,目前只能重新开启,正在修复该问题,如果编译的时候提示一下错误,可以忽略

Github源码:(在ROS文件夹里面)

https://github.com/Pyrokine/LineSegmentDetector17

感谢以下Geeks:

《ROS机器人开发实践》源码
https://github.com/huchunxu/ros_exploring
ros订阅激光雷达/scan
https://blog.csdn.net/weixin_33910137/article/details/86873846
sensor_msgs/LaserScan Message
http://docs.ros.org/api/sensor_msgs/html/msg/LaserScan.html
nav_msgs/OccupancyGrid Message
http://docs.ros.org/kinetic/api/nav_msgs/html/msg/OccupancyGrid.html

基于Ubuntu1604+ROS-kinetic+roscpp的激光雷达定位算法从零开始移植的更多相关文章

  1. ROS入门学习(基于Ubuntu16.04+kinetic)

    本文主要部分全部来源于ROS官网的Tutorials. Setup roscore # making sure that we have roscore running rosrun turtlesi ...

  2. 在ROS Kinetic和Gazebo 8中使用智能汽车仿真演示

    在ROS Kinetic和Gazebo 8中使用智能汽车仿真演示 智能车无人驾驶技术是目前人工智能和机器人技术的研究热点,有许多开源平台可以使我们零基础零成本入门无人驾驶技术.本文分享一下目前ROS官 ...

  3. ROS_Kinetic_02 ROS Kinetic 迁移指南及中文wiki指南(Migration guide)

    ROS_Kinetic_02 ROS Kinetic 迁移指南(Migration guide) 对于ROS Kinetic Kame有些功能包已经更新改变,提供关于这些包的迁移注意或教程.主要针对于 ...

  4. SLAM+语音机器人DIY系列:(二)ROS入门——3.在ubuntu16.04中安装ROS kinetic

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...

  5. ros kinetic安装rbx1

    1.首先安装一些依赖包 sudo apt-get install ros-kinetic-turtlebot-bringup \ ros-kinetic-turtlebot-create ros-ki ...

  6. Ubuntu 16.04 + ROS Kinetic 机器人操作系统学习镜像分享与使用安装说明

    Ubuntu 16.04 + ROS Kinetic 镜像分享与使用安装说明 内容概要:1 网盘文件介绍  2 镜像制作  3 系统使用与安装 ---- 祝ROS爱好者和开发者新年快乐:-) ---- ...

  7. ROS_Kinetic_01 在ubuntu 16.04安装ROS Kinetic 2017.01更新

    ROS_Kinetic系列学习(一),在ubuntu 16.04安装ROS Kinetic. Celebrating 9 Years of ROS! ubuntu16.04已经发布半年多了,ROS的K ...

  8. Ros Kinetic 配置 OpenCV2和CV_bridge (Python, C++)

    本篇介绍如何在Ros-kinetic环境下运用opencv2进行开发的配置,系统平台为64位Ubuntu16.04. 需要系统环境: 1.Ros kinetic版本,一般自带cv_bridge, 若没 ...

  9. Ubuntu 16.04 使用docker资料汇总与应用docker安装caffe并使用Classifier(ros kinetic+usb_cam+caffe)

    Docker是开源的应用容器引擎.若想简单了解一下,可以参考百度百科词条Docker.好像只支持64位系统. Docker官网:https://www.docker.com/ Docker - 从入门 ...

随机推荐

  1. Html 对象的常用事件列举

    事件名称 触发时间 对象例举 OnBlur 对象失去输入焦点 窗口和所有的表单对象 OnChange 用户改变对象的值 文本框.文本区域.选择列表等 OnClick 用户鼠标点击 链接.按钮.单选钮. ...

  2. mysql连接不释放

    环境: 持久层:JPA 数据库连接池:druid 数据库中间件:Mycat 数据库:Mysql 报错: Unable to acquire JDBC Connection 排查步骤: 方法一: 1.d ...

  3. 关于使用KubeSphere中的docker配置Harbor仓库http访问docker login登陆报错的解决办法

    # 先进入harbor目录中,停止harbor docker-compose stop # 然后修改docker相关文件 # 第一种方式:修改/etc/docker/daemon.json { &qu ...

  4. Hive学习笔记(一)——概述

    1.Hive是个什么玩意? Hive:由Facebook开源用于解决海量结构化日志的数据统计. Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据(有规律的数据)文件映射为一张表,并提供 ...

  5. Quartz时间配置(周期任务)

     序号 说明  是否必填  允许填写的值 允许的通配符  1  秒  是  0-59    , - * /  2  分  是  0-59   , - * /  3 小时  是  0-23   , - ...

  6. oracle 数据库触发器,插入更新时间戳

    1.首先建立一个测试表 CREATE TABLE TestTragger( UserId int Primary Key, Name VARCHAR() Not Null, CreateTime Ti ...

  7. dataGridView读写文本

    constant con = new constant(); private void loadlistbox2() { dataGridView1.ColumnCount = 1; string z ...

  8. flask 与 flask_migrate的使用

    flask 与 flask_migrate的使用 一.安装 pip install Flask-Migrate 二.简单使用 # 文件:manage.py from flask_migrate imp ...

  9. Flask 进阶

    OOP 面向对象反射 # __call__方法 # class Foo(object): # def __call__(self, *args, **kwargs): # return "i ...

  10. security Alternative forms secuerity

    security Alternative forms secuerity (mostly obsolete) English Alternative forms secuerity Pronuncia ...