在我上一篇文章中說到悯搔,要在無人機上跑視覺算法照激。而團隊師兄的方案是程序運行在ROS系統(tǒng)下霎冯,這樣控制和視覺分離铃拇,比較好分工。ROS是什么沈撞?機器人操作系統(tǒng)(Robot Operating System, ROS)是一個應用于機器人上的操作系統(tǒng),它操作方便慷荔、功能強大,特別適用于機器人這種多節(jié)點多任務的復雜場景。 因此自ROS誕生以來,受到了學術(shù)界和工業(yè)界的歡迎,如今已經(jīng)廣泛應用于機械臂缠俺、移動底盤显晶、無人機、無人車等許多種類的機器人上壹士。本文以一個簡單的在ROS下調(diào)用opencv的demo供讀者學習~~
1.運行環(huán)境
- Ubuntu16.04 LTS
- ROS Kinetic
- opencv3.3 & contribe
- Roboware studio (ROS的一個IDE磷雇,strongly recommended)
2.創(chuàng)建工作空間
使用Roboware創(chuàng)建以下層次結(jié)構(gòu),教程可參照網(wǎng)上的其他教程躏救。
vision_pac文件夾表示一個包(package)唯笙,package是ROS源代碼存放的地方,任何ROS的代碼無論是C++還是Python都要放到package中,這樣才能正常的編譯和運行。
src中存放的是C++和Python源代碼盒使,這里每一個.cpp文件都是一個節(jié)點(node)崩掘。從功能角度來說,通常一個node負責者機器人的某一個單獨的功能。由于機器人的功能模塊非常復雜,我們往往不會把所有功能都集中到一個node上,而會采用分布式的方式,把雞蛋放到不同的籃子里少办。自然呢堰,節(jié)點之間會通過各種方法來進行通信,ROS中有兩種凡泣,一種是topic通信枉疼,另一種是service通信。本文中使用的是topic通信鞋拟。
ROS平臺始終的是cmake工具進行編譯骂维,并且ROS對cmake進行擴展,于是便有了Catkin編譯系統(tǒng)贺纲。CMakeLists.txt原本是Cmake編譯系統(tǒng)的規(guī)則文件,而Catkin編譯系統(tǒng)基本沿用了CMake的編譯風格,只是針對ROS工程添加了一些宏定義航闺。所以在寫法上,catkin的 CMakeLists.txt 與CMake的基本一致。這個文件直接規(guī)定了這個package要依賴哪些package,要編譯生成哪些目標,如何編譯等等流程猴誊。Roboware幫我們完成了大部分工作潦刃,不需要關(guān)心這個文件的具體編寫。
pacakge.xml 包含了package的名稱懈叹、版本號乖杠、內(nèi)容描述、維護人員澄成、軟件許可胧洒、編譯構(gòu)建工具畏吓、編譯依賴、運行依賴等信息卫漫。 rospack find菲饼、rosdep 等命令能快速定位和分析出package的依賴項信息,原因就是直接讀取了每一個pacakge中的 package.xml文件列赎。
3.算法原理
這次的demo中宏悦,使用的算法是FAST特征檢測。FAST特征檢測算子基于Harris角點檢測包吝,它簡化了Harris檢測算子的過程饼煞。Harris檢測算子本質(zhì)上是對像素點創(chuàng)建一個窗口,并對周圍各個方向求導漏策。如果垂直的兩個方向上變化很大派哲,就判定為角點。其中用到協(xié)方差矩陣掺喻,特征值分解之類的理論芭届,數(shù)學不好就不誤人子弟了。感耙。褂乍。但可以確定的是,求導是很消耗計算資源的即硼,而且尋找特征點往往只是復雜檢測流程中的一步逃片。于是,就有了FAST算子只酥。
FAST特征檢測算法來源于corner的定義褥实,這個定義基于特征點周圍的圖像灰度值,檢測候選特征點周圍一圈的像素值裂允,如果候選點周圍領(lǐng)域內(nèi)有足夠多的像素點與該候選點的灰度值差別夠大损离,則認為該候選點為一個特征點。
上圖中選擇了周圍的16個像素點僻澎,稱為FAST-16角點檢測器。opencv中的FAST默認為FAST-9十饥,當然你可以通過在構(gòu)建檢測器實例時指定FAST檢測器的類型窟勃。
4.代碼編寫
1)先定義location.msg中的數(shù)據(jù)結(jié)構(gòu)。通信的數(shù)據(jù)是一個二維坐標逗堵。
uint32 x
uint32 y
2)vision.cpp文件調(diào)用FAST特征檢測算子秉氧,并發(fā)送給controller
/*******************************************************************
* Created by 楊幫杰 on 9/21/18
* Right to use this code in any way you want without warranty,
* support or any guarantee of it working
* E-mail: yangbangjie1998@qq.com
* Association: SCAU 華南農(nóng)大空機團
******************************************************************/
#include <ros/ros.h>
#include <vision_pac/location.h> //自定義msg產(chǎn)生的頭文件
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
#define PATH "/home/jacob/下載/bench1.jpg"
void ProcessImage(Mat& input, Mat& output, vector<KeyPoint>& points)
{
points.clear();
//創(chuàng)建FAST特征檢測器,閾值為70
Ptr<FastFeatureDetector> ptrFAST = FastFeatureDetector::create(70);
ptrFAST->detect(input,points);
drawKeypoints(input,points,output,Scalar::all(-1)); //在output上畫出角點位置
imshow("output",output);
waitKey(10);
}
void PublishMsg(vision_pac::location &loc,ros::Publisher &pub,vector<KeyPoint>& points)
{
for(vector<KeyPoint>::iterator it = points.begin(); it != points.end(); it++)
{
loc.x = it->pt.x;
loc.y = it->pt.y;
pub.publish(loc);//發(fā)布
}
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "vision"); //用于解析ROS參數(shù),第三個參數(shù)為本節(jié)點名
ros::NodeHandle nh; //實例化句柄,初始化node
vision_pac::location msg; //自定義location消息并初始化
//創(chuàng)建publisher,往"location_info"話題上發(fā)布消息
ros::Publisher pub = nh.advertise<vision_pac::location>("location_info",100);
ros::Rate loop_rate(1.0); //定義發(fā)布的頻率,1HZ
Mat inputImage = imread(PATH);
Mat outputImage = Mat::zeros(inputImage.size(),inputImage.type());
vector<KeyPoint> keypoints;
while(ros::ok) //循環(huán)發(fā)布msg
{
//計算一幀使用的時間
double time1 = static_cast<double>(getTickCount());
ProcessImage(inputImage,outputImage,keypoints); //對圖像進行處理
time1=((double)getTickCount()-time1)/getTickFrequency();//計算程序運行時間
cout<<"一幀處理時間為"<<time1<<"s"<<endl;//輸出運行時間
PublishMsg(msg,pub,keypoints); //發(fā)布消息
loop_rate.sleep();//根據(jù)前面的定義的loop_rate,設(shè)置1s的暫停
}
return 0;
}
由于定義了location類型的消息砸捏,可以理解為定義了一個結(jié)構(gòu)體谬运,如
struct location
{
int x;
int y;
}
所以隙赁,可以通過這樣的方式定義用于通信的數(shù)據(jù)結(jié)構(gòu)
vision_pac::location msg;
ROS節(jié)點的初始化十分簡潔易懂垦藏,在官方wiki上說明詳細梆暖。值得一提的是有時會發(fā)現(xiàn)通信丟包的情況(節(jié)點之間的通信是基于TCP/IP協(xié)議棧的)。一開始以為是ROS的bug掂骏,后來發(fā)現(xiàn)是因為緩沖區(qū)太小的原因轰驳。。弟灼。级解。所以程序出問題多找找自己的原因,尤其像我這種菜雞- -田绑!
ros::Publisher pub = nh.advertise<vision_pac::location>("location_info",100);
上面這條語句中勤哗,定義了一個Publisher。定義好的Publisher對象通過publish方法就可以把上面定義的msg發(fā)送出去掩驱。其中句柄nh的advertise方法中的泛型接口定義了發(fā)送出去的數(shù)據(jù)結(jié)構(gòu)芒划,第一個參數(shù)定義了topic的名字,第二個則是緩沖區(qū)的大小欧穴。緩沖區(qū)大小要特別注意民逼,當一次發(fā)送多個msg時如果太小就會丟包。
3)controller.cpp中接受消息
/*******************************************************************
* Created by 楊幫杰 on 9/21/18
* Right to use this code in any way you want without warranty,
* support or any guarantee of it working
* E-mail: yangbangjie1998@qq.com
* Association: SCAU 華南農(nóng)大空機團
******************************************************************/
#include <ros/ros.h>
#include <vision_pac/location.h>
void msgCallback(const vision_pac::location::ConstPtr &msg)
{
int pointX,pointY;
pointX = msg->x;
pointY = msg->y;
ROS_INFO("Controller:x = %d, y = %d",pointX,pointY); //輸出
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "controller");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("location_info", 100, msgCallback);//設(shè)置回調(diào)函數(shù)msgCallback
//ros::spin()用于調(diào)用所有可觸發(fā)的回調(diào)函數(shù),將進入循環(huán),不會返回,類似于在循環(huán)里反復調(diào)用spinOnce()
//而ros::spinOnce()只會去觸發(fā)一次
ros::spin();
return 0;
}
相應地涮帘,定義Subcriber對象去訂閱主題拼苍,并指定緩沖區(qū)大小和回調(diào)函數(shù)〉饔В回調(diào)函數(shù)會接收到msg并作為一個引用參數(shù)去讀寫疮鲫。
ROS_INFO("Controller:x = %d, y = %d",pointX,pointY);
上面這條語句相當于c語言中的格式輸出printf,會在終端中輸出相應消息弦叶。當然STL中的iostream也是可以使用的俊犯。
ros::spin();
而這條語句會阻塞線程,并不斷檢查是否有msg接收湾蔓,有則調(diào)用回調(diào)函數(shù)瘫析。如果不執(zhí)行這條語句,程序就直接return了。
5.結(jié)果驗證
這篇文就至此為止了~~ROS還是很具有進步性的,庫和IDE都讓人有不錯的編程體驗梯醒。值得一提的是寿冕,ROS由于實時性欠佳,工業(yè)上很少使用流昏。最近出了ROS-Industrial ,就是用于工業(yè)機器人的ROS。有時間去了解一下~
Reference:
Harris角點檢測原理詳解
OpenCV學習筆記(四十六)——FAST特征點檢測features2D
學習opencv3(中文版) —— Adrian Kaehler & Gary Bradski
opencv計算機視覺編程攻略(第三版) —— Robert
中國大學MOOC---《機器人操作系統(tǒng)入門》課程講義