最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

【自己動手做一臺SLAM導(dǎo)航機器人】第二章:ROS入門

2023-03-19 01:11 作者:小虎哥哥愛學(xué)習(xí)  | 我要投稿

本專欄目錄:

  • 前言

  • 第一章:Linux基礎(chǔ)

  • 第二章:ROS入門

  • 第三章:感知與大腦

  • 第四章:差分底盤設(shè)計

  • 第五章:樹莓派3開發(fā)環(huán)境搭建

  • 第六章:SLAM建圖與自主避障導(dǎo)航

  • 第七章:語音交互與自然語言處理

  • 附錄A:用于ROS機器人交互的Android手機APP開發(fā)

  • 附錄B:用于ROS機器人管理調(diào)度的后臺服務(wù)器搭建

  • 附錄C:如何選擇ROS機器人平臺進行SLAM導(dǎo)航入門

視頻教程

https://www.bilibili.com/video/BV1jS4y1a7Lz

ROS機器人操作系統(tǒng)在機器人應(yīng)用領(lǐng)域很流行,依托代碼開源和模塊間協(xié)作等特性,給機器人開發(fā)者帶來了很大的方便。我們的機器人“miiboo”中的大部分程序也采用ROS進行開發(fā),所以本文就重點對ROS基礎(chǔ)知識進行詳細(xì)的講解,給不熟悉ROS的朋友起到一個拋磚引玉的作用。本文主要內(nèi)容:

1.ROS是什么

2.ROS系統(tǒng)整體架構(gòu)

3.在ubuntu16.04中安裝ROS kinetic

4.如何編寫ROS的第一個程序hello_world

5.編寫簡單的消息發(fā)布器和訂閱器

6.編寫簡單的service和client

7.理解tf的原理

8.理解roslaunch在大型項目中的作用

9.熟練使用rviz

10.在實際機器人上運行ROS高級功能預(yù)覽

下面這本書是本篇文章的參考文獻,大家有需要可以入手一本:

1.ROS是什么

(圖1)ROS的圖標(biāo)

ROS是一個適用于機器人的開源的元操作系統(tǒng)。其實它并不是一個真正的操作系統(tǒng),其底層的任務(wù)調(diào)度、編譯、尋址等任務(wù)還是由Linux操作系統(tǒng)完成,也就是說ROS實際上是運行在Linux上的次級操作系統(tǒng)。但是ROS提供了操作系統(tǒng)應(yīng)用的各種服務(wù)(如:硬件抽象、底層設(shè)備控制、常用函數(shù)實現(xiàn)、進程間消息傳遞、軟件包管理等),也提供了用于獲取、編譯、跨平臺運行代碼的工具和函數(shù)。ROS主要采用松耦合點對點進程網(wǎng)絡(luò)通信,目前主要還是支持Ubuntu系統(tǒng),windows和Mac OS目前支持的還不好,所以推薦在Ubuntu系統(tǒng)上安裝使用ROS。

1.1.ROS的特性

(圖2)ROS的特性

總結(jié)起來就是,使用ROS能夠方便迅速的搭建機器人原型。ROS使用了BSD許可證,這是一個很寬松的開放許可證,允許在商業(yè)和閉源產(chǎn)品中使用,這一點對開發(fā)產(chǎn)品的創(chuàng)業(yè)公司很重要。ROS當(dāng)前的代碼統(tǒng)計量,總行數(shù)超過1400萬,作者超過2477名。代碼語言以C++為主,63.98%的代碼是用C++編寫的,排名第二的是python,占13.57%,可以說ROS基本上都是使用這兩種語言,來實現(xiàn)大部分的功能。

1.2.ROS的結(jié)構(gòu)

這里主要從四個方面來解讀ROS的結(jié)構(gòu),設(shè)計思想、核心概念、核心模塊、核心工具。

ROS的設(shè)計思路主要是分布式架構(gòu),將機器人的功能和軟件做成一個個節(jié)點,然后每個節(jié)點通過topic進行溝通,這些節(jié)點可以部署在同一臺機器上,也可以部署在不同機器上,還可以部署在互聯(lián)網(wǎng)上。

ROS的核心概念主要是節(jié)點和用于節(jié)點間通信的話題與服務(wù)。管理器Master管理節(jié)點與話題之間通信的過程,并且還提供一個參數(shù)服務(wù)用于全局參數(shù)的配置。ROS通過功能包集stack和功能包package來組織代碼。

ROS的核心模塊包括:通信結(jié)構(gòu)基礎(chǔ)、機器人特性功能、工具集。通信結(jié)構(gòu)基礎(chǔ)主要是消息傳遞、記錄回放消息、遠(yuǎn)程過程調(diào)用、分布式參數(shù)系統(tǒng);機器人特性功能主要是標(biāo)準(zhǔn)機器人消息、機器人幾何庫、機器人描述語言、搶占式遠(yuǎn)程過程調(diào)用、診斷、位置估計、定位導(dǎo)航;工具集主要是命令式工具、可視化工具、圖形化接口。

ROS核心工具很豐富,ROS常用命令工具是rostopic、rosservice、rosnode、rosparam、rosmsg、rossrv、roswtf;ROS常用可視化工具是rqt、rviz;ROS用于存儲與回放數(shù)據(jù)的工具rosbag;ROS的log系統(tǒng)記錄軟件運行的相關(guān)信息;ROS還擁有強大的第三方工具支持:三維仿真環(huán)境Gazebo、計算機視覺庫OpenCV、點云庫PCL、機械臂控制庫MoveIt、工業(yè)應(yīng)用庫Industrial、機器人編程工具箱MRPT、實時控制庫Orocos。

1.3.ROS的學(xué)習(xí)資源

官網(wǎng): ?www.ros.org

源碼: ?github.com

Wiki: ?wiki.ros.org

問答: ?answers.ros.org

2.ROS系統(tǒng)整體架構(gòu)

由于ROS系統(tǒng)的組織架構(gòu)比較復(fù)雜,簡單從一個方面來說明很難說清楚。按照ROS官方的說法,我們可以從3個方面來理解ROS系統(tǒng)整體架構(gòu),這3個方面分別是文件系統(tǒng)級、計算圖級、開源社區(qū)級。

2.1.從文件系統(tǒng)級理解ROS架構(gòu)

如果你是剛剛接手ROS方面的開發(fā)或項目,你肯定會覺得ROS中的各種概念非常奇怪,但是當(dāng)你對ROS的使用熟練之后,你就覺得這些概念很好理解了。與其他操作系統(tǒng)相似,一個ROS程序的不同組件要被放在不同的文件夾下,這些文件夾是根據(jù)不同的功能來對文件進行組織的,如圖3。

(圖3)文件系統(tǒng)級理解ROS架構(gòu)

(1)工作空間

工作空間是一個包含功能包、可編輯源文件和編譯包的文件夾,當(dāng)你想同時編譯不同的功能包時非常有用,并且可以保存本地開發(fā)包。當(dāng)然,用戶可以根據(jù)自己的需要創(chuàng)建多個工作空間,在每個工作空間中開發(fā)不同用途的功能包。不過作為學(xué)習(xí),我們先以一個工作空間為例。如圖3,我們創(chuàng)建了一個名為catkin_ws的工作空間,該工作空間下會有3個文件夾:src、build、devel。

src源文件空間:這個文件夾放置各個功能包和一個用于這些功能包的CMake配置文件CMakeLists.txt。這里做一下說明,由于ROS中的源碼采用catkin工具進行編譯,而catkin工具又是基于cmake技術(shù)的,所以我們會在src源文件空間和各個功能包中都會見到一個文件CMakeLists.txt,這個文件就是起編譯配置的作用。

build編譯空間:這個文件夾放置CMake和catkin編譯功能包時產(chǎn)生的緩存、配置、中間文件等。

devel開發(fā)空間:這個文件夾放置編譯好的可執(zhí)行程序,這些可執(zhí)行程序是不需要安裝就能直接運行的。一旦功能包源碼編譯和測試通過后,可以將這些編譯好的可執(zhí)行文件直接導(dǎo)出與其他開發(fā)人員分享。

(2)功能包

功能包是ROS中軟件組織的基本形式,一個功能包具有用于創(chuàng)建ROS程序的最小結(jié)構(gòu)和最少內(nèi)容,它可以包含ROS運行的進程(節(jié)點)、配置文件等。如圖3,一個功能包中主要包含這幾個文件:

CMakeLists.txt功能包配置文件:用于這個功能包cmake編譯時的配置文件。

package.xml功能包清單文件:用xml的標(biāo)簽格式標(biāo)記這個功能包的各類相關(guān)信息,比如包的名稱、依賴關(guān)系等。主要作用是為了更容易的安裝和分發(fā)功能包。

include/<package_name>功能包頭文件目錄:你可以把你的功能包程序包含的*.h頭文件放在這里,include下之所以還要加一級路徑<package_name>是為了更好的區(qū)分自己定義的頭文件和系統(tǒng)標(biāo)準(zhǔn)頭文件,<package_name>用實際功能包的名稱替代。不過這個文件夾不是必要項,比如有些程序沒有頭文件的情況。

msg非標(biāo)準(zhǔn)消息定義目錄:消息是ROS中一個進程(節(jié)點)發(fā)送到其他進程(節(jié)點)的信息,消息類型是消息的數(shù)據(jù)結(jié)構(gòu),ROS系統(tǒng)提供了很多標(biāo)準(zhǔn)類型的消息可以直接使用,如果你要使用一些非標(biāo)準(zhǔn)類型的消息,就需要自己來定義該類型的消息,并把定義的文件放在這里。不過這個文件夾不是必要項,比如程序中只使用標(biāo)準(zhǔn)類型的消息的情況。

srv服務(wù)類型定義目錄:服務(wù)是ROS中進程(節(jié)點)間的請求/響應(yīng)通信過程,服務(wù)類型是服務(wù)請求/響應(yīng)的數(shù)據(jù)結(jié)構(gòu),服務(wù)類型的定義放在這里。如果要調(diào)用此服務(wù),你需要使用該功能包名稱和服務(wù)名稱。不過這個文件夾不是必要項,比如程序中不使用服務(wù)的情況。

scripts可執(zhí)行腳本文件存放目錄:這里用于存放bash、python或其他腳本的可執(zhí)行文件。不過這個文件夾不是必要項,比如程序中不使用可執(zhí)行腳本的情況。

launch文件目錄:這里用于存放*.launch文件,*.launch文件用于啟動ROS功能包中的一個或多個節(jié)點,在含有多個節(jié)點啟動的大型項目中很有用。不過這個文件夾不是必要項,節(jié)點也可以不通過launch文件啟動。

src功能包中節(jié)點源文件存放目錄:一個功能包中可以有多個進程(節(jié)點)程序來完成不同的功能,每個進程(節(jié)點)程序都是可以單獨運行的,這里用于存放這些進程(節(jié)點)程序的源文件,你可以在這里再創(chuàng)建文件夾和文件來按你的需求組織源文件,源文件可以用c++、python等來書寫。

為了創(chuàng)建、修改、使用功能包,ROS給我們提供了一些實用的工具,常用的有下面這些工具。

rospack:用于獲取信息或在系統(tǒng)中查找工作空間。

catkin_create_pkg:用于在工作空間的src源空間下創(chuàng)建一個新的功能包。

catkin_make:用于編譯工作空間中的功能包。

rosdep:用于安裝功能包的系統(tǒng)依賴項。

rqt_dep:用于查看功能包的依賴關(guān)系圖。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(3)消息

消息是ROS中一個進程(節(jié)點)發(fā)送到其他進程(節(jié)點)的信息,消息類型是消息的數(shù)據(jù)結(jié)構(gòu),ROS系統(tǒng)提供了很多標(biāo)準(zhǔn)類型的消息可以直接使用,如果你要使用一些非標(biāo)準(zhǔn)類型的消息,就需要自己來定義該類型的消息。

ROS使用了一種精簡的消息類型描述語言來描述ROS進程(節(jié)點)發(fā)布的數(shù)據(jù)值。通過這種描述語言對消息類型的定義,ROS可以在不同的編程語言(如c++、python等)書寫的程序中使用此消息。不管是ROS系統(tǒng)提供的標(biāo)準(zhǔn)類型消息,還是用戶自定義的非標(biāo)準(zhǔn)類型消息,定義文件都是以*.msg作為擴展名。消息類型的定義分為兩個主要部分:字段的數(shù)據(jù)類型和字段的名稱,簡單點說就是結(jié)構(gòu)體中的變量類型和變量名稱。比如下面的一個示例消息定義文件example.msg的內(nèi)容,如圖4,int32、float32、string就是字段的數(shù)據(jù)類型,id、vel、name就是字段的名稱。

(圖4)一個示例消息定義文件

在大多數(shù)情況下,我們都可以使用ROS系統(tǒng)提供的標(biāo)準(zhǔn)類型的消息來完成任務(wù),這得益于ROS系統(tǒng)提供了豐富的標(biāo)準(zhǔn)類型的消息。經(jīng)常用到的類型包括:基本類型(std_msgs)、通用類型(sensor_msgs、geometry_msgs、nav_msgs、actionlib_msgs),如圖5。

(圖5)ROS系統(tǒng)提供的常用標(biāo)準(zhǔn)類型的消息

不難發(fā)現(xiàn)std_msgs下面定義的是經(jīng)ROS封裝后的最基本的數(shù)據(jù)類型,比如Bool、Char、Int16等;sensor_msgs下面定義的是跟傳感器數(shù)據(jù)相關(guān)的數(shù)據(jù)類型,比如Image對應(yīng)的就是攝像頭的數(shù)據(jù)類型,Imu對應(yīng)的就是IMU傳感器的數(shù)據(jù)類型,LaserScan對應(yīng)的就是激光雷達(dá)的數(shù)據(jù)類型,PointCloud對應(yīng)的就是點云掃描傳感器(如深度相機)的數(shù)據(jù)類型,Range對應(yīng)的就是距離測量傳感器(如超聲波、紅外測距)的數(shù)據(jù)類型;geometry_msgs下定義的是跟幾何有關(guān)的數(shù)據(jù)類型,比如Pose用來描述機器人在空間的位姿,Quaternion用四元數(shù)描述空間方向,Transform用來描述不同坐標(biāo)系之間的轉(zhuǎn)移關(guān)系,Twist用來描述機器人運動時的位姿、速度等狀態(tài)信息;nav_msgs下定義的是跟機器人導(dǎo)航相關(guān)的數(shù)據(jù)類型,比如OccupancyGrid是柵格地圖的數(shù)據(jù)類型,Odometry是機器人通過輪式碼盤或其他方式融合得到的里程計的數(shù)據(jù)類型,Path是路徑規(guī)劃算法計算得到的導(dǎo)航路勁的數(shù)據(jù)類型;actionlib_msgs下定義的是actionlib控制過程相關(guān)的數(shù)據(jù)類型,比如GoalID描述發(fā)送出去的導(dǎo)航目標(biāo)的ID號,GoalStatus描述執(zhí)行導(dǎo)航目標(biāo)過程的過程狀態(tài)信息。如果想了解更多ROS系統(tǒng)的消息類型的細(xì)節(jié),最好的方式是去ROS wiki看官方的文檔,鏈接如下:

http://wiki.ros.org/std_msgs/

http://wiki.ros.org/common_msgs/

(4)服務(wù)

服務(wù)是ROS中進程(節(jié)點)間的請求/響應(yīng)通信過程,服務(wù)類型是服務(wù)請求/響應(yīng)的數(shù)據(jù)結(jié)構(gòu)。服務(wù)類型的定義借鑒了消息類型的定義方式,所以這里就不在贅述了。區(qū)別在于,消息數(shù)據(jù)是ROS進程(節(jié)點)間多對多廣播式通信過程中傳遞的信息;服務(wù)數(shù)據(jù)是ROS進程(節(jié)點)間點對點的請求/響應(yīng)通信過程傳遞的信息。

2.2.從計算圖級理解ROS架構(gòu)

ROS會創(chuàng)建一個連接所有進程(節(jié)點)的網(wǎng)絡(luò),其中的任何進程(節(jié)點)都可以訪問此網(wǎng)絡(luò),并通過該網(wǎng)絡(luò)與其他進程(節(jié)點)交互,獲取其他進程(節(jié)點)發(fā)布的信息,并將自身數(shù)據(jù)發(fā)布到網(wǎng)絡(luò)上,這個計算圖網(wǎng)絡(luò)中的節(jié)點(node)、主題(topic)、服務(wù)(server)等都要有唯一的名稱做標(biāo)識,如圖6。

(圖6)計算圖級理解ROS架構(gòu)

(1)節(jié)點

節(jié)點是主要的計算執(zhí)行進程,功能包中創(chuàng)建的每個可執(zhí)行程序在被啟動加載到系統(tǒng)進程中后,該進程就是一個ROS節(jié)點,如圖6中的node1、node2、node3等都是節(jié)點(node)。節(jié)點都是各自獨立的可執(zhí)行文件,能夠通過主題(topic)、服務(wù)(server)或參數(shù)服務(wù)器(parameter server)與其他節(jié)點通信。ROS通過使用節(jié)點將代碼和功能解耦,提高了系統(tǒng)的容錯力和可維護性。所以你最好讓每一個節(jié)點都具有特定的單一的功能,而不是創(chuàng)建一個包羅萬象的大節(jié)點。節(jié)點如果用c++進行編寫,需要用到ROS提供的庫roscpp;節(jié)點如果用python進行編寫,需要用到ROS提供的庫rospy。

ROS提供了處理節(jié)點的工具,用于節(jié)點信息、狀態(tài)、可用性等的查詢操作,例如可以用下面的命令對正在運行的節(jié)點進行操作。

rosnode info <node_name>:用于輸出當(dāng)前節(jié)點信息。

rosnode kill <node_name>:用于殺死正在運行節(jié)點進程來結(jié)束節(jié)點的運行。

rosnode list:用于列出當(dāng)前活動的節(jié)點。

rosnode machine <hostname>:用于列出指定計算機上運行的節(jié)點。

rosnode ping <node_name>:用于測試節(jié)點間的網(wǎng)絡(luò)連通性。

rosnode cleanup:用于將無法訪問節(jié)點的注冊信息清除。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(2)消息

節(jié)點通過消息(message)完成彼此的溝通。消息包含一個節(jié)點發(fā)送給其他節(jié)點的信息數(shù)據(jù)。關(guān)于消息類型的知識在前面已經(jīng)講述了,這里就不再展開。

ROS提供了獲取消息相關(guān)信息的命令工具,這里列舉出一些常用的命令,來具體看看吧。

rosmsg show <message_type>:用于顯示一條消息的字段。

rosmsg list:用于列出所有消息。

rosmsg package <package _name>:用于列出功能包的所有消息。

rosmsg packages:用于列出所有具有該消息的功能包。

rosmsg users <message_type>:用于搜索使用該消息類型的代碼文件。

rosmsg md5 <message_type>:用于顯示一條消息的MD5求和結(jié)果。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(3)主題

每個消息都必須發(fā)布到相應(yīng)的主題(topic),通過主題來實現(xiàn)在ROS計算圖網(wǎng)絡(luò)中的路由轉(zhuǎn)發(fā)。當(dāng)一個節(jié)點發(fā)送數(shù)據(jù)時,我們就說該節(jié)點正在向主題發(fā)布消息;節(jié)點可以通過訂閱某個主題,接收來自其他節(jié)點的消息。通過主題進行消息路由不需要節(jié)點之間直接連接,這就意味著發(fā)布者節(jié)點和訂閱者節(jié)點之間不需要知道彼此是否存在,這樣就保證了發(fā)布者節(jié)點與訂閱者節(jié)點之間的解耦合。同一個主題可以有多個訂閱者也可以有多個發(fā)布者,不過要注意必須使用不同的節(jié)點發(fā)布同一個主題。每個主題都是強類型的,不管是發(fā)布消息到主題還是從主題中訂閱消息,發(fā)布者和訂閱者定義的消息類型必須與主題的消息類型相匹配。

ROS提供了操作主題的命令工具,這里列舉出一些常用的命令,來具體看看吧。

rostopic bw </topic_name>:用于顯示主題所使用的帶寬。

rostopic echo </topic_name>:用于將主題中的消息數(shù)據(jù)輸出到屏幕。

rostopic find <message_type>:用于按照消息類型查找主題。

rostopic hz </topic_name>:用于顯示主題的發(fā)布頻率。

rostopic info </topic_name>:用于輸出活動主題、發(fā)布的主題、主題訂閱者和服務(wù)的信息。

rostopic list:用于列出當(dāng)前活動主題的列表。

rostopic pub </topic_name> <message_type> <args>:用于通過命令行將數(shù)據(jù)發(fā)布到主題。

rostopic type </topic_name>:用于輸出主題中發(fā)布的消息類型。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(4)服務(wù)

在一些特殊的場合,節(jié)點間需要點對點的高效率通信并及時獲取應(yīng)答,這個時候就需要用服務(wù)的方式進行交互。提供服務(wù)的節(jié)點叫服務(wù)端,向服務(wù)端發(fā)起請求并等待響應(yīng)的節(jié)點叫客戶端,客戶端發(fā)起一次請求并得到服務(wù)端的一次響應(yīng),這樣就完成了一次服務(wù)通信過程,例如圖6中,node1向node3發(fā)起一次請求,并得到node3返回給node1的響應(yīng)。服務(wù)通信過程中服務(wù)的數(shù)據(jù)類型需要用戶自己定義,與消息不同,節(jié)點并不提供標(biāo)準(zhǔn)服務(wù)類型。服務(wù)類型的定義文件都是以*.srv為擴展名,并且被放在功能包的srv/文件夾下。

ROS提供了操作服務(wù)的命令工具,這里列舉出一些常用的命令,來具體看看吧。

rosservice call </service_name> <args>:用于通過命令行參數(shù)調(diào)用服務(wù)。

rosservice find <service_type>:用于根據(jù)服務(wù)類型查詢服務(wù)。

rosservice info </service_name>:用于輸出服務(wù)信息。

rosservice list:用于列出活動服務(wù)清單。

rosservice type </service_name>:用于輸出服務(wù)類型。

rosservice uri </service_name>:用于輸出服務(wù)的ROSRPC URI。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(5)節(jié)點管理器

節(jié)點管理器(master)用于節(jié)點的名稱注冊和查找等,也負(fù)責(zé)設(shè)置節(jié)點間的通信。如果在你的整個ROS系統(tǒng)中沒有節(jié)點管理器,就不會有節(jié)點、消息、服務(wù)之間的通信。由于ROS本身就是一個分布式的網(wǎng)絡(luò)系統(tǒng),所以你可以在某臺計算機上運行節(jié)點管理器,在這臺計算機和其他臺計算機上運行節(jié)點。

ROS中提供了跟節(jié)點管理器相關(guān)的命令行工具,就是roscre。

roscore:用于啟動節(jié)點管理器,這個命令會加載ROS節(jié)點管理器和其他ROS核心組件。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(6)參數(shù)服務(wù)器

參數(shù)服務(wù)器(parameter server)能夠使數(shù)據(jù)通過關(guān)鍵詞存儲在一個系統(tǒng)的核心位置。通過使用參數(shù),就能夠在節(jié)點運行時動態(tài)配置節(jié)點或改變節(jié)點的工作任務(wù)。參數(shù)服務(wù)器是可通過網(wǎng)絡(luò)訪問的共享的多變量字典,節(jié)點使用此服務(wù)器來存儲和檢索運行時的參數(shù)。

ROS中關(guān)于參數(shù)服務(wù)器的命令行工具,請看下面的常用命令。

rosparam list:用于列出參數(shù)服務(wù)器中的所有參數(shù)。

rosparam get <parameter_name>:用于獲取參數(shù)服務(wù)器中的參數(shù)值。

rosparam set <parameter_name> <value>:用于設(shè)置參數(shù)服務(wù)器中參數(shù)的值。

rosparam delete <parameter_name>:用于將參數(shù)從參數(shù)服務(wù)器中刪除。

rosparam dump <file>:用于將參數(shù)服務(wù)器的參數(shù)保存到一個文件。

rosparam load <file>:用于從文件將參數(shù)加載到參數(shù)服務(wù)器。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

(7)消息記錄包

消息記錄包(bag)是一種用于保存和回放ROS消息數(shù)據(jù)的文件格式。消息記錄包是一種用于存儲數(shù)據(jù)的重要機制,它可以幫助記錄一些難以收集的傳感器數(shù)據(jù),然后通過反復(fù)回放數(shù)據(jù)進行算法的性能開發(fā)和測試。ROS創(chuàng)建的消息記錄包文件以*.bag為擴展名,通過播放、停止、后退操作該文件,可以像實時會話一樣在ROS中再現(xiàn)情景,便于算法的反復(fù)調(diào)試。

ROS提供消息記錄包相關(guān)的命令行工具,請看下面的常用命令。

rosbag <args>:用來錄制、播放和執(zhí)行操作。

關(guān)于這些工具命令的具體使用方法,會在后面的章節(jié)中結(jié)合實例進行具體的講解。這里只是先介紹給大家,讓大家有個概念上的了解,感興趣的朋友也可以自己上網(wǎng)了解這些命令的具體用法。

2.3.從開源社區(qū)級理解ROS架構(gòu)

ROS開源社區(qū)級的概念主要是ROS資源,即通過各個獨立的網(wǎng)絡(luò)社區(qū)分享ROS方面的軟件和知識。

(1)ROS發(fā)行版

ROS發(fā)行版跟Linux發(fā)行版起類似的作用,ROS發(fā)行版是內(nèi)置了一系列常用功能包的ROS系統(tǒng)安裝包,可以被直接安裝到我們的操作系統(tǒng)中。如圖7,是ROS的各個發(fā)行版。

(圖7)ROS的各個發(fā)行版

(2)ROS軟件代碼庫

ROS依賴于開源或共享軟件的源代碼,這些代碼由不同的機構(gòu)共享與發(fā)布,比如github源碼共享,ubuntu軟件倉庫等等。如圖8,是ROS軟件代碼庫的社區(qū)組織形式。

(圖8)ROS軟件代碼庫的社區(qū)組織形式

(3)ROS文檔社區(qū)

ROS wiki是記錄有關(guān)ROS系統(tǒng)各種文檔的主要論壇社區(qū),任何人都可以注冊賬戶、貢獻自己的文件、提供更正或更新、編寫教程及其他行為。感興趣可以進入ROS wiki的主頁面瞧瞧http://wiki.ros.org/。

(4)ROS問答社區(qū)

ROS開發(fā)者可以通過這個資源去提問和尋找ROS相關(guān)的答案,ROS Answer主頁面

https://answers.ros.org/。

3.在ubuntu16.04中安裝ROS kinetic

(圖9)安裝ROS軟硬件配置總結(jié)

使用ROS進行機器人的學(xué)習(xí)和開發(fā),一般需要一個機器人平臺和一個調(diào)試工作平臺。機器人平臺上出于性價比和功耗考慮一般采用ARM嵌入式板作為硬件設(shè)備,比如樹莓派3、RK3399開發(fā)板、nvidia-jetson-tx2等;調(diào)試工作平臺一般采用x86個人電腦,比如筆記本電腦、臺式電腦等。ARM嵌入式板的廠家一般都會提供相應(yīng)定制化的ubuntu系統(tǒng),定制化主要體現(xiàn)在硬件外設(shè)驅(qū)動和一些加快系統(tǒng)速度的優(yōu)化,作為軟件開發(fā)人員可以不必考慮這些問題直接當(dāng)做普通ubuntu使用就行了,比如針對樹莓派3定制化的ubuntu-mate系統(tǒng);x86個人電腦上的話就可以直接安裝ubuntu官方發(fā)布的系統(tǒng)就行了,你可以把ubuntu直接安裝在物理機上,這樣ubuntu運行會更加流暢,你也可以把ubuntu安裝在虛擬機上,這樣可以更方便的切換使用機器上原來的系統(tǒng)(如windows系統(tǒng))和虛擬機上的ubuntu系統(tǒng)。不管是采用何種硬件,在硬件上以何種方式安裝上ubuntu及其定制化ubuntu,一旦我們擁有了一個可用的ubuntu系統(tǒng),就可以在這個ubuntu系統(tǒng)上安裝當(dāng)下流行的ROS發(fā)行版了(本文寫作時最流行的ROS發(fā)行版是ROS-kinetic)。安裝ROS軟硬件配置的總結(jié),如圖9。

機器人平臺上安裝ROS軟硬件配置,推薦大家使用樹莓派3作為硬件,ubuntu-mate-16.04作為操作系統(tǒng),安裝ROS-kinetic這個ROS發(fā)行版。這一部分的具體講解將在后續(xù)的文章中展開。

調(diào)試工作平臺上安裝ROS軟硬件配置,推薦大家使用x86個人電腦(筆記本電腦、臺式電腦都可以)作為硬件,在虛擬機上運行ubuntu16.04系統(tǒng),安裝ROS-kinetic這個ROS發(fā)行版。由于在虛擬機上安裝運行ubuntu16.04系統(tǒng)已經(jīng)在前面的文章《Linux基礎(chǔ)知識》中詳細(xì)講解了,這里就默認(rèn)我們已經(jīng)擁有了一個運行在虛擬機上的ubuntu16.04系統(tǒng)了。接下來就著重講解如何安裝ROS發(fā)行版ROS-kinetic。

其實在ubuntu上安裝ROS,有很詳細(xì)的ROS官方教程,感興趣的朋友可以直接參考官方教程http://wiki.ros.org/kinetic/Installation/Ubuntu。由于官方教程用英文書寫,為了方便大家閱讀,我將官方教程翻譯過來,方便大家學(xué)習(xí),下面正式進入安裝。溫馨提醒,由于不同的編輯器對過長的句子換行的規(guī)則不同,下面的命令被自動換行后可能影響正常的閱讀,請直接參閱官方教程中的命令http://wiki.ros.org/kinetic/Installation/Ubuntu。

(1)配置ubuntu的資源庫

系統(tǒng)設(shè)置->軟件和更新->Ubuntu軟件,可以打開如圖10中的資源庫配置界面,確保“universe”,“restricted”、“multiverse”被勾選了,“下載自”選項中選擇“中國的服務(wù)器”,這樣下載更新軟件速度會更快點。不過一般情況下,以上選項都是默認(rèn)設(shè)置好了的。

(圖10)資源庫配置界面

(2)設(shè)置ubuntu的sources.list

打開命令行終端,輸入如下命令:

(3)設(shè)置keys

打開命令行終端,輸入如下命令:

(4)安裝ros-kinetic-desktop-full完整版

打開命令行終端,分別輸入如下兩條命令:

小技巧,如果安裝過程提示“下載錯誤”,請耐心重試上面的兩條命令,這個錯誤多半是由于網(wǎng)絡(luò)故障造成的。

(5)初始化rosdep

打開命令行終端,分別輸入如下兩條命令:

(6)配置環(huán)境變量

打開命令行終端,分別輸入如下兩條命令:

(7)安裝rosinstall

打開命令行終端,輸入如下命令:

(8)測試ros安裝成功與否

打開命令行終端,輸入如下命令:

如果此時出現(xiàn)以下內(nèi)容

那么恭喜你,ROS已經(jīng)成功的安裝上了!?。?/span>

4.如何編寫ROS的第一個程序hello_world

既然ROS已經(jīng)成功安裝好了,大家一定很想親自動動手編一個ROS的小程序試試手,沒錯,這一節(jié)就隆重請出程序界的起手式例程hello_world。

通過起手式例程hello_world,可以學(xué)到工作空間的創(chuàng)建、功能包的創(chuàng)建、功能包的源代碼編寫、功能包的編譯配置、功能包的編譯、功能包的啟動運行等知識。

(1)工作空間的創(chuàng)建

打開命令行終端,分別輸入如下命令:

這時,便已經(jīng)創(chuàng)建好了一個ROS的工作空間了,接下來就是在catkin_ws工作空間下的src目錄下新建功能包并進行功能包程序編寫了,如果想了解工作空間的詳細(xì)目錄結(jié)構(gòu),請參考本章節(jié)“2.1.從文件系統(tǒng)級理解ROS架構(gòu)”這有對ROS的文件組織結(jié)構(gòu)進行詳解。

(2)功能包的創(chuàng)建

繼續(xù)在命令行終端中,輸入如下命令:

這時候在~/catkin_ws/src/目錄下能看到一個叫hello_world的文件夾,文件夾名稱就是功能包的名稱以及功能包的唯一標(biāo)識符,這個說明功能包創(chuàng)建成功了。

(3)功能包的源代碼編寫

由于我們只是要編寫一個能打印“hello world”的程序,所以就很簡單了,這里先以c++代碼作為示范,后面會講解python的。一些在線教程建議在你的功能包目錄中創(chuàng)建src目錄用來存放c++源文件,這個附加的組織結(jié)構(gòu)是很有益處的,特別是對含有很多種類型文件的大型功能包,不過不是嚴(yán)格必要的。出于編程規(guī)范,我這里也會采用這樣的建議把c++源文件放在功能包中的src目錄下。

所以,首先在hello_world目錄下新建src目錄,再在新建的src目錄下新建一個my_hello_world_node.cpp文件。這里的新建目錄和文件的方法可以在圖形界面下直接操作會更方便一點,如圖11。

(圖11)在功能包中新建文件夾及文件

用文本編輯器gedit打開my_hello_world_node.cpp文件,并輸入如下內(nèi)容。

這里對代碼做一個解析:

#include "ros/ros.h"

這一句是包含頭文件ros/ros.h,這是ROS提供的C++客戶端庫,是必須包含的頭文件,在后面的編譯配置中要添加相應(yīng)的依賴庫roscpp。

?

ros::init(argc,argv,"hello_node");

這一句是初始化ros節(jié)點并指明節(jié)點的名稱,這里給節(jié)點取名為hello_node,一旦程序運行后就可以在ros的計算圖中被注冊為hello_node名稱標(biāo)識的節(jié)點。

?

ros::NodeHandle nh;

這一句是聲明一個ros節(jié)點的句柄,初始化ros節(jié)點必須的。

?

ROS_INFO_STREAM("hello world!!!");

這一句是調(diào)用了roscpp庫提供的方法ROS_INFO_STREAM來打印信息,這里打印字符串"hello world!!!"。

(4)功能包的編譯配置

聲明依賴庫:

對于我們的my_hello_world_node.cpp程序來說,我們包含了<ros/ros.h>這個庫,因此我們需要添加名為roscpp的依賴庫。

首先,用文本編輯器gedit打開功能包目錄下的CMakeLists.txt文件,在find_package(catkin REQUIRED ...)字段中添加roscpp,添加后的字段如下:

同時,找到include_directories(...)字段,去掉${catkin_INCLUDE_DIRS}前面的注釋,如下:

添加好后的效果如圖12所示。

(圖12)在CMakeLists.txt中添加roscpp依賴庫

然后,用文本編輯器gedit打開功能包目錄下的package.xml文件,找到這樣一句話<buildtool_depend>catkin</buildtool_depend>,在這句話的下面添加如下內(nèi)容:

添加好后的效果如圖13所示。

(圖13)在package.xml中添加roscpp依賴庫

聲明可執(zhí)行文件:

就接下來,我們需要在CMakeLists.txt中添加兩句,我的習(xí)慣在文件最后一行添加就好了,來聲明我們需要創(chuàng)建的可執(zhí)行文件。

第一行聲明了我們想要的可執(zhí)行文件的文件名,以及生成此可執(zhí)行文件所需的源文件列表。如果你有多個源文件,把它們列在此處,并用空格將其區(qū)分開。

第二行告訴 Cmake 當(dāng)鏈接此可執(zhí)行文件時需要鏈接哪些庫(在上面的 find_package 中定義)。如果你的包中包括多個可執(zhí)行文件,為每一個可執(zhí)行文件復(fù)制和修改上述兩行代碼。

添加好后的效果如圖14所示。

(圖14)在CMakeLists.txt聲明可執(zhí)行文件

(5)功能包的編譯

功能包的編譯配置好后,就可以開始編譯了,這里有兩種編譯方式,一種是編譯工作空間內(nèi)的所有功能包,另一種是編譯工作空間內(nèi)的指定功能包,兩種編譯方式各有用處,下面分別講解。

第一種,編譯工作空間內(nèi)的所有功能包:

第二種,編譯工作空間內(nèi)的指定功能包:

其實就是加入?yún)?shù) -DCATKIN_WHITELIST_PACKAGES=””,在雙引號中填入需要編譯的功能包名字,用空格分割。

(6)功能包的啟動運行

首先,需要用roscore命令來啟動ROS節(jié)點管理器,ROS節(jié)點管理器是所有節(jié)點運行的基礎(chǔ)。

打開命令行終端,輸入命令:

然后,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的節(jié)點。

再打開一個命令行終端,分別輸入命令:

看到有輸出hello world!!!就說明程序已經(jīng)正常執(zhí)行了,按照我們的設(shè)計程序正常打印后會自動結(jié)束,如圖15。

(圖15)hello_world功能包中my_hello_world_node節(jié)點執(zhí)行結(jié)果

到這里,恭喜你已經(jīng)學(xué)會了ROS的第一個程序hello world!!!

5.編寫簡單的消息發(fā)布器和訂閱器

通過上一節(jié)編寫ROS的第一個程序hello_world,我們對ROS的整個編程開發(fā)過程有了基本的了解,現(xiàn)在我們就來編寫真正意義上的使用ROS進行節(jié)點間通信的程序。由于之前已經(jīng)建好了catkin_ws這樣一個工作空間,以后開發(fā)的功能包都將放在這里面,這里給新建的功能包取名為topic_example,在這個功能包中分別編寫兩個節(jié)點程序publish_node.cpp和subscribe_node.cpp,發(fā)布節(jié)點(publish_node)向話題(chatter)發(fā)布std_msgs::String類型的消息,訂閱節(jié)點(subscribe_node)從話題(chatter)訂閱std_msgs::String類型的消息,這里消息傳遞的具體內(nèi)容是一句問候語“how are you ...”,通信網(wǎng)絡(luò)結(jié)構(gòu)如圖16。

(圖16)消息發(fā)布與訂閱ROS通信網(wǎng)絡(luò)結(jié)構(gòu)圖

(1)功能包的創(chuàng)建

在catkin_ws/src/目錄下新建功能包topic_example,并在創(chuàng)建時顯式的指明依賴roscpp和std_msgs。打開命令行終端,輸入命令:

(2)功能包的源代碼編寫

功能包中需要編寫兩個獨立可執(zhí)行的節(jié)點,一個節(jié)點用來發(fā)布消息,另一個節(jié)點用來訂閱消息,所以需要在新建的功能包topic_example/src/目錄下新建兩個文件publish_node.cpp和subscribe_node.cpp,并將下面的代碼分別填入。

首先,介紹發(fā)布節(jié)點publish_node.cpp,代碼內(nèi)容如下:

對發(fā)布節(jié)點代碼進行解析。

#include "ros/ros.h"

這一句是包含頭文件ros/ros.h,這是ROS提供的C++客戶端庫,是必須包含的頭文件。

?

#include "std_msgs/String.h"

由于代碼中需要使用ROS提供的標(biāo)準(zhǔn)消息類型std_msgs::String,所以需要包含此頭文件。

?

ros::init(argc, argv, "publish_node");

這一句是初始化ros節(jié)點并指明節(jié)點的名稱,這里給節(jié)點取名為publish_node,一旦程序運行后就可以在ros的計算圖中被注冊為publish_node名稱標(biāo)識的節(jié)點。

?

ros::NodeHandle nh;

這一句是聲明一個ros節(jié)點的句柄,初始化ros節(jié)點必須的。

?

ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 1000);

這句話告訴ros節(jié)點管理器我們將會在chatter這個話題上發(fā)布std_msgs::String類型的消息。這里的參數(shù)1000是表示發(fā)布序列的大小,如果消息發(fā)布的太快,緩沖區(qū)中的消息大于1000個的話就會開始丟棄先前發(fā)布的消息。

?

ros::Rate loop_rate(10);

這句話是用來指定自循環(huán)的頻率,這里的參數(shù)10 表示10Hz頻率,需要配合該對象的sleep()方法來使用。

?

while (ros::ok()) {...}

roscpp會默認(rèn)安裝以SIGINT句柄,這句話就是用來處理由ctrl+c鍵盤操作、該節(jié)點被另一同名節(jié)點踢出ROS網(wǎng)絡(luò)、ros::shutdown()被程序在某個地方調(diào)用、所有ros::NodeHandle句柄都被銷毀等觸發(fā)而使ros::ok()返回false值的情況。

?

std_msgs::String msg;

定義了一個std_msgs::String消息類型的對象,該對象有一個數(shù)據(jù)成員data用于存放我們即將發(fā)布的數(shù)據(jù)。要發(fā)布出去的數(shù)據(jù)將被填充到這個對象的data成員中。

?

chatter_pub.publish(msg);

利用定義好的發(fā)布器對象將消息數(shù)據(jù)發(fā)布出去,這一句執(zhí)行后,ROS網(wǎng)絡(luò)中的其他節(jié)點便可以收到此消息中的數(shù)據(jù)。

?

ros::spinOnce();

這一句是讓回調(diào)函數(shù)有機會被執(zhí)行的聲明,這個程序里面并沒有回調(diào)函數(shù),所以這一句可以不要,這里只是為了程序的完整規(guī)范性才放上來的。

?

loop_rate.sleep();

前面講過,這一句是通過休眠來控制自循環(huán)的頻率的。

?

接著,介紹訂閱節(jié)

對訂閱節(jié)點代碼進行解析。

之前解釋過的類似的代碼就不做過多的解釋了,這里重點解釋一下前面沒遇到過的代碼。

void chatterCallback(const std_msgs::String::ConstPtr& msg)

{

??ROS_INFO("I heard: [%s]",msg->data.c_str());

}

這是一個回調(diào)函數(shù),當(dāng)有消息到達(dá)chatter話題時會自動被調(diào)用一次,這個回調(diào)函數(shù)里面就是一句話,用來打印從話題中訂閱的消息數(shù)據(jù)。

?

ros::Subscriber chatter_sub = nh.subscribe("chatter", 1000,chatterCallback);

這句話告訴ros節(jié)點管理器我們將會從chatter這個話題中訂閱消息,當(dāng)有消息到達(dá)時會自動調(diào)用這里指定的chatterCallback回調(diào)函數(shù)。這里的參數(shù)1000是表示訂閱序列的大小,如果消息處理的速度不夠快,緩沖區(qū)中的消息大于1000個的話就會開始丟棄先前接收的消息。

?

ros::spin();

這一句話讓程序進入自循環(huán)的掛起狀態(tài),從而讓程序以最好的效率接收消息并調(diào)用回調(diào)函數(shù)。如果沒有消息到達(dá),這句話不會占用很多CPU資源,所以這句話可以放心使用。一旦ros::ok()被觸發(fā)而返回false,ros::spin()的掛起狀態(tài)將停止并自動跳出。簡單點說,程序執(zhí)行到這一句,就在這里不斷自循環(huán),與此同時檢查是否有消息到達(dá)并決定是否調(diào)用回調(diào)函數(shù)。

(3)功能包的編譯配置及編譯

創(chuàng)建功能包topic_example時,顯式的指明依賴roscpp和std_msgs,依賴會被默認(rèn)寫到功能包的CMakeLists.txt和package.xml中,所以只需要在CMakeLists.txt文件的末尾行加入以下幾句用于聲明可執(zhí)行文件就可以了:

接下來,就可以用下面的命令對功能包進行編譯了:

(4)功能包的啟動運行

首先,需要用roscore命令來啟動ROS節(jié)點管理器,ROS節(jié)點管理器是所有節(jié)點運行的基礎(chǔ)。

打開命令行終端,輸入命令:

然后,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的發(fā)布節(jié)點。

再打開一個命令行終端,分別輸入命令:

看到有輸出“how are you ...”,就說明發(fā)布節(jié)點已經(jīng)正常啟動并開始不斷向chatter話題發(fā)布消息數(shù)據(jù),如圖17。

(圖17)發(fā)布節(jié)點已經(jīng)正常啟動

最后,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的訂閱節(jié)點。

再打開一個命令行終端,分別輸入命令:

看到有輸出“I heard:[how are you ...]”,就說明訂閱節(jié)點已經(jīng)正常啟動并開始不斷從chatter話題接收消息數(shù)據(jù),如圖18。

(圖18)訂閱節(jié)點已經(jīng)正常啟動

到這里,我們編寫的消息發(fā)布器和訂閱器就大功告成了,為了加深對整個程序工作流程的理解,我再把消息發(fā)布與訂閱的ROS通信網(wǎng)絡(luò)結(jié)構(gòu)圖拿出來,加深一下理解。

(圖19)消息發(fā)布與訂閱ROS通信網(wǎng)絡(luò)結(jié)構(gòu)圖

6.編寫簡單的service和client

上一節(jié)介紹了兩個ros節(jié)點通過發(fā)布與訂閱消息的通信方式,現(xiàn)在就介紹ros節(jié)點間通信的另外一種方式服務(wù)。我們將學(xué)到:如何自定義服務(wù)類型、server端節(jié)點編寫、client端節(jié)點編寫等。我就以實現(xiàn)兩個整數(shù)求和為例,client端節(jié)點向server端節(jié)點發(fā)送a、b的請求,server端節(jié)點返回響應(yīng)sum=a+b給client端節(jié)點,通信網(wǎng)絡(luò)結(jié)構(gòu)如圖20。

(圖20)服務(wù)請求與響應(yīng)ROS通信網(wǎng)絡(luò)結(jié)構(gòu)圖

(1)功能包的創(chuàng)建

在catkin_ws/src/目錄下新建功能包service_example,并在創(chuàng)建時顯式的指明依賴roscpp和std_msgs,依賴std_msgs將作為基本數(shù)據(jù)類型用于定義我們的服務(wù)類型。打開命令行終端,輸入命令:

(2)在功能包中創(chuàng)建自定義服務(wù)類型

通過前面的學(xué)習(xí),我們知道服務(wù)通信過程中服務(wù)的數(shù)據(jù)類型需要用戶自己定義,與消息不同,節(jié)點并不提供標(biāo)準(zhǔn)服務(wù)類型。服務(wù)類型的定義文件都是以*.srv為擴展名,并且被放在功能包的srv/文件夾下。

服務(wù)類型定義:

首先,在功能包service_example目錄下新建srv目錄,然后在service_example/srv/目錄中創(chuàng)建AddTwoInts.srv文件,文件內(nèi)容如下:

服務(wù)類型編譯配置:

定義好我們的服務(wù)類型后,要想讓該服務(wù)類型能在c++、python等代碼中被使用,必須要做相應(yīng)的編譯與運行配置。編譯依賴message_generation,運行依賴message_runtime。

打開功能包中的CMakeLists.txt文件,找到下面這段代碼:

將message_generation添加進去,添加后的代碼如下:

繼續(xù),找到這段代碼:

去掉這段代碼的#注釋,將自己的服務(wù)類型定義文件AddTwoInts.srv填入,修改好后的代碼如下:

繼續(xù),找到這段代碼:

去掉這段代碼的#注釋,generate_messages的作用是自動創(chuàng)建我們自定義的消息類型*.msg與服務(wù)類型*.srv相對應(yīng)的*.h,由于我們定義的服務(wù)類型使用了std_msgs中的int64基本類型,所以必須向generate_messages指明該依賴,修改好后的代碼如下:

然后打開功能包中的package.xml文件,填入下面三句依賴:

檢查ROS是否識別新建的服務(wù)類型:

我們通過<功能包名/服務(wù)類型名>找到該服務(wù),打開命令行終端,輸入命令:

看到下面的輸出,如圖21,就說明新建服務(wù)類型能被ROS識別,新建服務(wù)類型成功了。

(圖21)新建服務(wù)類型能被ROS識別

(3)功能包的源代碼編寫

功能包中需要編寫兩個獨立可執(zhí)行的節(jié)點,一個節(jié)點用來作為client端發(fā)起請求,另一個節(jié)點用來作為server端響應(yīng)請求,所以需要在新建的功能包service_example/src/目錄下新建兩個文件server_node.cpp和client_node.cpp,并將下面的代碼分別填入。

首先,介紹server節(jié)點server_node.cpp,代碼內(nèi)容如下:

對server節(jié)點代碼進行解析。

#include “ros/ros.h”

#include “service_example/AddTwoInts.h”

包含頭文件ros/ros.h,這是ROS提供的C++客戶端庫,是必須包含的頭文件,就不多說了。service_example/AddTwoInts.h是由編譯系統(tǒng)自動根據(jù)我們的功能包和在功能包下創(chuàng)建的*.srv文件生成的對應(yīng)的頭文件,包含這個頭文件,程序中就可以使用我們自定義服務(wù)的數(shù)據(jù)類型了。

?

bool add_execute(...)

這個函數(shù)實現(xiàn)兩個int64整數(shù)求和的服務(wù),兩個int64值從request獲取,返回求和結(jié)果裝入response里,request與response的具體數(shù)據(jù)類型都在前面創(chuàng)建的*.srv文件中被定義,這個函數(shù)返回值為bool型。

?

ros::init(argc,argv,”server_node”);

ros::NodeHandle nh;

初始化ros節(jié)點并指明節(jié)點的名稱,聲明一個ros節(jié)點的句柄,,就不多說了。

?

ros::ServiceServer service = nh.advertiseService(“add_two_ints”,add_execute);

這一句是創(chuàng)建服務(wù),并將服務(wù)加入到ROS網(wǎng)絡(luò)中,并且這個服務(wù)在ROS網(wǎng)絡(luò)中以名稱add_two_ints唯一標(biāo)識,以便于其他節(jié)點通過服務(wù)名稱進行請求。

?

ros::spin();

這一句話讓程序進入自循環(huán)的掛起狀態(tài),從而讓程序以最好的效率接收客戶端的請求并調(diào)用回調(diào)函數(shù),就不多說了。

接著,介紹client節(jié)點client_node.cpp,代碼內(nèi)容如下:

對client節(jié)點代碼進行解析。

之前解釋過的類似的代碼就不做過多的解釋了,這里重點解釋一下前面沒遇到過的代碼。

ros::ServiceClient client =

?nh.serviceClient<service_example::AddTwoInts>("add_two_ints");

這一句創(chuàng)建client對象,用來向ROS網(wǎng)絡(luò)中名稱叫add_two_ints的service發(fā)起請求。

?

service_example::AddTwoInts srv;

定義了一個service_example::AddTwoInts服務(wù)類型的對象,該對象中的成員正是我們在*.srv文件中定義的a、b、sum,我們將待請求的數(shù)據(jù)填充到數(shù)據(jù)成員a、b,請求成功后返回結(jié)果會被自動填充到數(shù)據(jù)成員sum中。

?

if(client.call(srv)){...}

這一句便是通過client的方法call來向service發(fā)起請求,請求傳入的參數(shù)srv在上面已經(jīng)介紹過了。

(4)功能包的編譯配置及編譯

創(chuàng)建功能包service_example時,顯式的指明依賴roscpp和std_msgs,依賴會被默認(rèn)寫到功能包的CMakeLists.txt和package.xml中,并且在功能包中創(chuàng)建*.srv服務(wù)類型時已經(jīng)對服務(wù)的編譯與運行做了相關(guān)配置,所以只需要在CMakeLists.txt文件的末尾行加入以下幾句用于聲明可執(zhí)行文件就可以了:

這里面的add_executable用于聲明可執(zhí)行文件。target_link_libraries用于聲明可執(zhí)行文件創(chuàng)建時需要鏈接的庫。add_dependencies用于聲明可執(zhí)行文件的依賴項,由于我們自定義了*.srv,service_example_gencpp的作用是讓編譯系統(tǒng)自動根據(jù)我們的功能包和在功能包下創(chuàng)建的*.srv文件生成的對應(yīng)的頭文件和庫文件,service_example_gencpp這個名稱是由功能包名稱service_example加上_gencpp后綴而來的,后綴很好理解:生成c++文件就是_gencpp,

生成python文件就是_genpy。

接下來,就可以用下面的命令對功能包進行編譯了:

(5)功能包的啟動運行

首先,需要用roscore命令來啟動ROS節(jié)點管理器,ROS節(jié)點管理器是所有節(jié)點運行的基礎(chǔ)。

打開命令行終端,輸入命令:

然后,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的server節(jié)點。

再打開一個命令行終端,分別輸入命令:

看到有輸出“servive is ready!!!”,就說明server節(jié)點已經(jīng)正常啟動并開始等待client節(jié)點向自己發(fā)起請求了,如圖22。

(圖22)server節(jié)點已經(jīng)正常啟動

最后,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的client節(jié)點。

再打開一個命令行終端,分別輸入命令:

看到有輸出提示信息“please input a and b:”后,鍵盤鍵入兩個整數(shù),以空格分割,輸入完畢后回車。如果看到輸出信息“sum=xxx”,就說明client節(jié)點向server端發(fā)起的請求得到了響應(yīng),打印出來的sum就是響應(yīng)結(jié)果,這樣就完成了一次服務(wù)請求的通信過程,如圖23。

(圖23)client節(jié)點已經(jīng)正常啟動

到這里,我們編寫的server和client就大功告成了,為了加深對整個程序工作流程的理解,我再把server與client的ROS通信網(wǎng)絡(luò)結(jié)構(gòu)圖拿出來,加深一下理解。

(圖24)服務(wù)請求與響應(yīng)ROS通信網(wǎng)絡(luò)結(jié)構(gòu)圖

7.理解tf的原理

(1)機器人中的坐標(biāo)系

一個機器人系統(tǒng)中通常會有多個三維參考坐標(biāo)系,而且這些坐標(biāo)系之間的相對關(guān)系隨時間推移會變化。這里舉一個實際的機器人應(yīng)用場景例子,來說明這種關(guān)系和變化:

全局世界坐標(biāo)系:通常為激光slam構(gòu)建出來的柵格地圖的坐標(biāo)系map。

機器人底盤坐標(biāo)系:通常為機器人底盤的坐標(biāo)系base_footprint。

機器人上各部件自己的坐標(biāo)系:比如激光雷達(dá)、imu等傳感器自己的坐標(biāo)系base_laser_link、imu_link。

這些坐標(biāo)系之間的關(guān)系有些是靜態(tài)的、有些是動態(tài)的。比如當(dāng)機器人底盤移動的過程中,機器人底盤與世界的相對關(guān)系map->base_footprint就會隨之變化;而安裝在機器人底盤上的激光雷達(dá)、imu這些傳感器與機器人底盤的相對關(guān)系base_footprint->base_laser_link、base_footprint->imu_link就不會隨之變化。其實,這個很好理解。

如圖25中,map->base_footprint會隨著底盤的移動而變化,即動態(tài)坐標(biāo)系關(guān)系。

(圖25)動態(tài)坐標(biāo)系關(guān)系

如圖26中,base_footprint->base_laser_link、base_footprint->imu_link不會隨著底盤的移動而變化,即靜態(tài)坐標(biāo)系關(guān)系。

(圖26)靜態(tài)坐標(biāo)系關(guān)系

(2)機器人坐標(biāo)關(guān)系工具tf

由于坐標(biāo)及坐標(biāo)轉(zhuǎn)換在機器人系統(tǒng)中非常重要,特別是機器人在環(huán)境地圖中自主定位和導(dǎo)航、機械手臂對物體進行復(fù)雜的抓取任務(wù),都需要精確的知道機器人各部件之間的相對位置及機器人在工作環(huán)境中的相對位置。因此ROS專門提供了tf這個工具用于簡化這些工作。

tf可以讓用戶隨時跟蹤多個坐標(biāo)系的關(guān)系,機器人各個坐標(biāo)系之間的關(guān)系是通過一種樹型數(shù)據(jù)結(jié)構(gòu)來存儲和維護的,即tf tree。借助這個tf tree,用戶可以在任意時間將點、向量等數(shù)據(jù)的坐標(biāo)在兩個坐標(biāo)系中完成坐標(biāo)值變換。

如圖27,為一個自主導(dǎo)航機器人的tf tree結(jié)構(gòu)圖。圓圈中是坐標(biāo)系的名稱,箭頭表示兩個坐標(biāo)系之間的關(guān)系,箭頭上會顯示該坐標(biāo)關(guān)系的發(fā)布者、發(fā)布速率、時間戳等信息。

(圖27)一個自主導(dǎo)航機器人的tf tree結(jié)構(gòu)圖

(3)使用tf

使用tf分為兩個部分,廣播tf變換、監(jiān)聽tf變換。

廣播tf變換:

ROS網(wǎng)絡(luò)中的節(jié)點可以向系統(tǒng)廣播坐標(biāo)系之間的變換關(guān)系。比如負(fù)責(zé)機器人全局定位的amcl節(jié)點會廣播map->odom的變換關(guān)系,負(fù)責(zé)機器人局部定位的輪式里程計計算節(jié)點會廣播odom->base_footprint的變換關(guān)系,機器人底盤上安裝的傳感器與底盤的變換關(guān)系可以通過urdf機器人模型進行廣播(urdf將在后面實際機器人中進行講解)。每個節(jié)點的廣播都可以直接將變換關(guān)系插入tf tree,不需要進行同步。通過多個節(jié)點廣播坐標(biāo)變換的關(guān)系,便可以實現(xiàn)tf tree的動態(tài)維護。

關(guān)于廣播tf變換的具體程序?qū)崿F(xiàn),請直接參考ROS官方教程http://wiki.ros.org/tf/Tutorials

監(jiān)聽tf變換:

ROS網(wǎng)絡(luò)中的節(jié)點可以從系統(tǒng)監(jiān)聽坐標(biāo)系之間的變換關(guān)系,并從中查詢所需要的坐標(biāo)變換。比如要知道機器人底盤當(dāng)前在柵格地圖坐標(biāo)系下的什么地方,就可以通過監(jiān)聽map->base_footprint來實現(xiàn),比如要知道機器人底盤坐標(biāo)系上的某個坐標(biāo)點在世界坐標(biāo)系下的坐標(biāo)是多少,就可以通過監(jiān)聽map->base_footprint,并通過map->base_footprint這個變換查詢出變換后的坐標(biāo)點取值。

關(guān)于監(jiān)聽tf變換的具體程序?qū)崿F(xiàn),請直接參考ROS官方教程http://wiki.ros.org/tf/Tutorials

8.理解roslaunch在大型項目中的作用

(1)roslaunch的作用

在一個大型的機器人項目中,經(jīng)常涉及到多個node協(xié)同工作,并且每個node都有很多可設(shè)置的parameter。比如我們的機器人miiboo_nav導(dǎo)航項目,涉及到地圖服務(wù)節(jié)點、定位算法節(jié)點、運動控制節(jié)點、底盤控制節(jié)點、激光雷達(dá)數(shù)據(jù)獲取節(jié)點等眾多節(jié)點,和幾百個影響著這些node行為模式的parameter。如果全部手動rosrun逐個啟動node并傳入parameter,工程的復(fù)雜程度將難以想象。所以這個時候就需要用roslaunch來解決問題,將需要啟動的節(jié)點和需要設(shè)置的parameter全部寫入一個*.launch文件,然后用roslaunch一次性的啟動*.launch文件,這樣所有的節(jié)點就輕而易舉的啟動了。miiboo_nav導(dǎo)航項目的miiboo_nav.launch文件內(nèi)容如圖28。

(圖28)miiboo_nav導(dǎo)航項目的miiboo_nav.launch文件內(nèi)容

(2)launch標(biāo)簽介紹

launch文件采用xml文本標(biāo)記語言進行編寫,對比較常用的標(biāo)簽進行介紹。

<launch>標(biāo)簽:

這個是頂層標(biāo)簽,所有的描述標(biāo)簽都要寫在<launch></launch>之間。

<node>標(biāo)簽:

這個是最常見的標(biāo)簽,每個node標(biāo)簽里包含了ROS圖中節(jié)點的名稱屬性name、該節(jié)點所在的包名pkg、節(jié)點的類型type(type為可執(zhí)行文件名稱,如果節(jié)點用c++編寫;type為*.py,如果節(jié)點用python編寫)、調(diào)試屬性output(如果output=“screen”,終端輸出信息將被打印到當(dāng)前控制臺,而不是存入ROS日志文件)。

<include>標(biāo)簽:

這個標(biāo)簽是用于導(dǎo)入另一個*.launch文件到當(dāng)前文件。也就是說高層級的launch文件可以通過include的方法調(diào)用其它launch文件,這樣可以使launch文件的組織方式更加模塊化,便于移植與復(fù)用。

<remap>標(biāo)簽:

這個標(biāo)簽是用于將topic的名稱進行重映射, from中填入原來的topic名稱,to中填入新的topic名稱。<remap>標(biāo)簽根據(jù)放置在launch文件的層級不同,在相應(yīng)的層級起作用。

<param>標(biāo)簽:

這個標(biāo)簽用于在參數(shù)服務(wù)器中創(chuàng)建或設(shè)置一個指定名稱的參數(shù)值。

<rosparam>標(biāo)簽:

這個標(biāo)簽用于從yaml文件中一次性導(dǎo)入大量參數(shù)到參數(shù)服務(wù)器中。

<arg>標(biāo)簽:

這個標(biāo)簽用于在launch文件中定義用于存儲的臨時變量,該標(biāo)簽定義的變量只在當(dāng)前l(fā)aunch文件中使用。推薦使用第一種方式賦值,這樣可以方便從命令行中傳入?yún)?shù)。

<group>標(biāo)簽:

這個標(biāo)簽用于將node批量劃分到某個命名空間。便于大項目中節(jié)點的批量管理。

(3)launch的使用方法

首先在相應(yīng)功能包目錄下新建一個launch文件夾。

然后在launch文件夾中新建*.launch文件,并按照上面的launch標(biāo)簽規(guī)則編寫好launch文件的內(nèi)容。

最后在終端中用roslaunch命令啟動launch文件,命令如下:

特別說明,由于roslaunch命令會自動去啟動roscore,所以不需要像之前使用rosrun那樣特意先去手動啟動roscore。

9.熟練使用rviz

(1)rviz整體界面

(圖29)rviz整體界面

rviz是ROS自帶的圖形化工具,可以很方便的讓用戶通過圖形界面開發(fā)調(diào)試ROS。操作界面也十分簡潔,如圖29,界面主要分為上側(cè)菜單區(qū)、左側(cè)顯示內(nèi)容設(shè)置區(qū)、中間顯示區(qū)、右側(cè)顯示視角設(shè)置區(qū)、下側(cè)ROS狀態(tài)區(qū)。

(2)添加顯示內(nèi)容

(圖30)設(shè)置Global Options

如圖30,啟動rviz界面后,首先要對Global Options進行設(shè)置,Global Options里面的參數(shù)是一些全局顯示相關(guān)的參數(shù)。其中的Fixed Frame參數(shù)是全局顯示區(qū)域依托的坐標(biāo)系,我們知道機器人中有很多坐標(biāo)系,坐標(biāo)系之間有各自的轉(zhuǎn)換關(guān)系,有些是靜態(tài)關(guān)系,有些是動態(tài)關(guān)系,不同的Fixed Frame參數(shù)有不同的顯示效果,在導(dǎo)航機器人應(yīng)用中,一般將Fixed Frame參數(shù)設(shè)置為map,也就是以map坐標(biāo)系作為全局坐標(biāo)系。值得注意的是,在機器人的tf tree里面必須要有map坐標(biāo)系,否則該選項欄會包error。至于Global Options里面的其他參數(shù)可以不用管,默認(rèn)就行了。

(圖31)添加地圖顯示

如圖31,在機器人導(dǎo)航應(yīng)用中,我們常常需要用rviz觀察機器人建立的地圖,在機器人發(fā)布了地圖到主題的情況下,我們就可以通過rviz訂閱地圖相應(yīng)主題(一般是/map主題)來顯示地圖。訂閱地圖的/map主題方法很簡單,首先點擊rviz界面左下角[add]按鈕,然后在彈出的對話框中選擇[By topic],最后在列出的topic名字中找到我們要訂閱的主題名字/map,最后點擊[OK]就完成了對/map主題的訂閱。訂閱成功后,會在rviz左側(cè)欄中看到Map項,并且中間顯示區(qū)正常顯示出地圖。

(圖32)添加tf顯示

如圖32,在機器人導(dǎo)航應(yīng)用中,除了觀察地圖外,我們常常還需要觀察機器人在地圖中的位置以及各個坐標(biāo)系的關(guān)系是否工作正常,這個時候就需要通過rviz來顯示tf。和上面添加顯示主題的方法類似,這里添加TF這個類型主題就可以了。說明一下,添加主題可以按主題類型查找,也可以按主題名稱查找。上面添加地圖主題就是按主題名稱查找的,這里添加tf主題是按主題類型查找的。

(圖33)添加里程計顯示

如圖33,我們可以通過rviz訂閱里程計來觀察機器人的運動軌跡(圖中紅色箭頭連接起來的軌跡)。和上面添加顯示主題的方法類似,這里添加/odom這個主題就可以了。這里特別說明一點,我們需要去掉左側(cè)欄中Odometry里面Covariance項后面后面的勾,也就是在Odometry顯示中不啟用Covariance信息。Covariance是描述里程計誤差的協(xié)方差矩陣,如果啟用Covariance來描述Odometry將導(dǎo)致顯示效果很難看,所以建議去掉。

(圖34)添加機器人位置粒子濾波點顯示

如圖34,在機器人導(dǎo)航中,通常采用AMCL粒子濾波來實現(xiàn)機器人的全局定位。通過rviz可以顯示全局定位的例子點。和上面添加顯示主題的方法類似,這里添加/particlecloud這個主題就可以了。

(圖35)添加激光雷達(dá)顯示

如圖35,機器人SLAM和導(dǎo)航中用到的核心傳感器激光雷達(dá)數(shù)據(jù),我們可以通過rviz顯示激光雷達(dá)數(shù)據(jù)(圖中紅色點組成的輪廓)。和上面添加顯示主題的方法類似,這里添加/scan這個主題就可以了。

(圖36)添加攝像頭顯示

如圖36,rviz還可以訂閱攝像頭發(fā)布的主題,這樣在rviz上就可以實現(xiàn)遠(yuǎn)程視頻監(jiān)控了。和上面添加顯示主題的方法類似,這里添加/usb_cam/image_raw這個主題就可以了。

通過上面的實例,我們已經(jīng)知道在rviz中訂閱需要顯示的主題了,被訂閱的主題會在rviz左側(cè)欄中列出,并且主題的顯示與否是相互獨立的,可以通過勾選的方式?jīng)Q定是否顯現(xiàn)該主題,主題項下拉條目中有很多參數(shù)可以設(shè)置,這些參數(shù)決定顯示的風(fēng)格等等,可以根據(jù)需要進行設(shè)置。其他一些不常用的主題訂閱實例沒有給出,有需要可以依葫蘆畫瓢在rviz中進行訂閱顯示就行了。

(3)主界面中常用按鈕

(圖37)rviz顯示配置保存

在上面的添加顯示內(nèi)容的實例中,我們在rviz中添加了很多主題顯示項,并對各顯示項的參數(shù)做了相應(yīng)的設(shè)置。為了下次啟動rviz時,能直接顯示這些內(nèi)容和風(fēng)格,我們需要將當(dāng)前的rviz顯示風(fēng)格以配置文件的方式保存,下次啟動rviz后只需要載入這個配置文件就能進入相應(yīng)的顯示風(fēng)格。很簡單,點擊rviz左上角[file]菜單,在下拉中選擇[Save Config As],在彈出來的對話框中給配置文件取一個名字(我取名為my_cfg1),然后直接Save,my_cfg1.rviz配置文件會被保存到系統(tǒng)中rviz的默認(rèn)目錄。下次啟動rviz后,通過點擊rviz左上角[file]菜單,在下拉中選擇[Open Config],打開相應(yīng)的配置文件就行了。如圖37。

(圖38)機器人初始位置設(shè)定與導(dǎo)航目標(biāo)設(shè)定

在機器人導(dǎo)航中,當(dāng)機器人剛啟動的時候,機器人位置往往需要人為給定一個大概的估計位置,這樣有利于AMCL粒子濾波中粒子點的快速收斂。如圖38,點擊[2D Pose Estimate]按鈕,然后在地圖中找到機器人大致的位置后再次點擊鼠標(biāo)左鍵并保持按下狀態(tài),拖動鼠標(biāo)來指定機器人的朝向,最后松手就完成對機器人初始位置的設(shè)定了。其實就是兩步,先指定機器人的位置,再指定機器人的朝向。我們可以在地圖中指定導(dǎo)航目標(biāo)點,讓機器人自動導(dǎo)航到我們的指定的位置。通過[2D Nav Goal]按鈕就可以完成。操作步驟和機器人初始位置的設(shè)定是類似的,就不累述了。

(圖39)獲取地圖中指定點的坐標(biāo)值

有時候我們需要知道地圖中某個位置的坐標(biāo)值,比如我們獲取地圖中各個位置的坐標(biāo)值并填入巡邏軌跡中,讓機器人按照指定巡邏路線巡邏。通過[Publish Point]按鈕就可以知道地圖中的任意位置的坐標(biāo)值,點擊[Publish Point]按鈕,然后將鼠標(biāo)放置到想要獲取坐標(biāo)值的位置,rviz底部顯示欄中就會出現(xiàn)相應(yīng)的坐標(biāo)值。

(4)rviz啟動方法

首先需要啟動roscore,然后啟動rviz,命令如下:

10.在實際機器人上運行ROS高級功能預(yù)覽

到這里,《ROS入門》這一章節(jié)教程就講完了,由于是為了幫助大家入門ROS,所以教程中的所有內(nèi)容都只是在我們的調(diào)試工作平臺(就是PC電腦)上進行的演示。實際的ROS機器人開發(fā)中,需要有一個調(diào)試工作平臺(比如PC電腦)和一個機器人平臺(比如安裝有ROS的樹莓派3、搭載激光雷達(dá)、IMU、攝像頭,且能移動的機器人底盤)。這樣,我們就可以將SLAM建圖算法、自主導(dǎo)航算法、語音交互算法、圖像算法等放到機器人平臺運行;再利用調(diào)試工作平臺遠(yuǎn)程連接到機器人平臺,就可以非常方便的遠(yuǎn)程調(diào)試SLAM建圖算法、自主導(dǎo)航算法、語音交互算法、圖像算法等算法了。所以在接下來的教程中將在實際的機器人平臺(也就前面提到的我們的miiboo機器人)上展開,這里先提前讓大家預(yù)覽一下機miiboo器人上的ROS編程與算法開發(fā)的高級內(nèi)容:

(1)miiboo機器人上的傳感器ROS驅(qū)動開發(fā)

(2)基于google-cartographer的SLAM建圖算法開發(fā)

(3)基于ros-navigation的機器人自主導(dǎo)航算法開發(fā)

(4)集成語音識別合成和自然語言處理的聊天機器人開發(fā)

(5)機器人與人工智能未來展望

后記

為了防止后續(xù)大家找不到本篇文章,我同步制作了一份文章的pdf和本專欄涉及的例程代碼放在github和gitee方便大家下載,如果下面給出的github下載鏈接打不開,可以嘗試gitee下載鏈接:

  • github下載鏈接:https://github.com/xiihoo/DIY_A_SLAM_Navigation_Robot

  • gitee下載鏈接:https://gitee.com/xiihoo-robot/DIY_A_SLAM_Navigation_Robot

技術(shù)交流

QQ技術(shù)交流群:117698356

參考文獻

[1] 張虎,機器人SLAM導(dǎo)航核心技術(shù)與實戰(zhàn)[M]. 機械工業(yè)出版社,2022.




【自己動手做一臺SLAM導(dǎo)航機器人】第二章:ROS入門的評論 (共 條)

分享到微博請遵守國家法律
兴文县| 东乡县| 竹溪县| 顺昌县| 巴马| 庆安县| 宜兰县| 繁昌县| 米林县| 宿州市| 中超| 怀柔区| 广水市| 盐山县| 峡江县| 于都县| 铁岭县| 宽城| 台江县| 乌拉特中旗| 丰都县| 广水市| 光泽县| 沙湾县| 衡阳县| 仁怀市| 鞍山市| 寿光市| 肇州县| 凤台县| 湟源县| 合作市| 佛坪县| 廊坊市| 子长县| 孝感市| 清水县| 尉犁县| 五莲县| 佳木斯市| 宿迁市|