来自专辑
没有机器人,如何学习ROS
上一期实验,我们从ROS机器人的头部相机获取了机器人的视觉图像。这一次我们继续深入,使用OpenCV实现机器人视觉中的颜色特征提取和目标定位功能。
对机器人视觉图像进行颜色空间转换,从RGB空间转换到HSV空间,排除光照影响;
对转换后的图像进行二值化处理,将目标物体分割提取出来;
对提取到的目标像素进行计算统计,得出目标物的质心坐标。
ROS Kinetic (Ubuntu 16.04)
cd ~/catkin_ws/src/ |
ROS Melodic (Ubuntu 18.04)
cd ~/catkin_ws/src/ |
注:需将上述指令中的“catkin_ws”替换成实际的工作空间目录。
1.首先,新建一个ROS源码包Package,在Ubuntu里打开一个终端程序,输入如下指令进入ROS工作空间:
cd catkin_ws/src/ |
按下回车之后,即可进入ROS工作空间,然后输入如下指令新建一个ROS源码包:
catkin_create_pkg cv_pkg roscpp cv_bridge |
这条指令的具体含义是:
指令 |
含义 |
catkin_create_pkg |
创建ROS源码包(package)的指令 |
cv_pkg |
新建的ROS源码包命名 |
roscpp |
C++依赖项,本例程使用C++编写,所以需要这个依赖项 |
cv_bridge |
将ROS中的图像数据格式转换成OpenCV数据格式的工具包 |
按下回车键后,可以看到如下信息,表示新的ROS软件包创建成功。
在VSCode中,可以看到工作空间里多了一个cv_pkg文件夹,在其src子文件夹上右键点击鼠标,选择“New File”新建一个代码文件。
新建的代码文件,我们为其命名为“cv_hsv_node.cpp”。
命名完毕后,在IDE的右侧可以开始编写cv_hsv_node.cpp的代码,其内容如下:
#include <ros/ros.h> #include <cv_bridge/cv_bridge.h> #include <sensor_msgs/image_encodings.h> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp>
usingnamespace cv; usingnamespace std;
staticint iLowH = 10; staticint iHighH = 40;
staticint iLowS = 90; staticint iHighS = 255;
staticint iLowV = 1; staticint iHighV = 255;
void Cam_RGB_Callback(const sensor_msgs::ImageConstPtr& msg) { cv_bridge::CvImagePtr cv_ptr; try { cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8); } catch (cv_bridge::Exception& e) { ROS_ERROR("cv_bridge exception: %s", e.what()); return; }
Mat imgOriginal = cv_ptr->image;
//将RGB图片转换成HSV Mat imgHSV; vector hsvSplit; cvtColor(imgOriginal, imgHSV, COLOR_BGR2HSV);
//在HSV空间做直方图均衡化 split(imgHSV, hsvSplit); equalizeHist(hsvSplit[2],hsvSplit[2]); merge(hsvSplit,imgHSV); Mat imgThresholded;
//使用上面的Hue,Saturation和Value的阈值范围对图像进行二值化 inRange(imgHSV, Scalar(iLowH, iLowS, iLowV), Scalar(iHighH, iHighS, iHighV), imgThresholded);
//开操作 (去除一些噪点) Mat element = getStructuringElement(MORPH_RECT, Size(5, 5)); morphologyEx(imgThresholded, imgThresholded, MORPH_OPEN, element);
//闭操作 (连接一些连通域) morphologyEx(imgThresholded, imgThresholded, MORPH_CLOSE, element);
//遍历二值化后的图像数据 int nTargetX = 0; int nTargetY = 0; int nPixCount = 0; int nImgWidth = imgThresholded.cols; int nImgHeight = imgThresholded.rows; int nImgChannels = imgThresholded.channels(); for (int y = 0; y < nImgHeight; y++) { for(int x = 0; x < nImgWidth; x++) { if(imgThresholded.data[y*nImgWidth + x] == 255) { nTargetX += x; nTargetY += y; nPixCount ++; } } } if(nPixCount > 0) { nTargetX /= nPixCount; nTargetY /= nPixCount; printf("颜色质心坐标( %d , %d ) 点数 = %d\n",nTargetX,nTargetY,nPixCount); //画坐标 Point line_begin = Point(nTargetX-10,nTargetY); Point line_end = Point(nTargetX+10,nTargetY); line(imgOriginal,line_begin,line_end,Scalar(255,0,0),2); line_begin.x = nTargetX; line_begin.y = nTargetY-10; line_end.x = nTargetX; line_end.y = nTargetY+10; line(imgOriginal,line_begin,line_end,Scalar(255,0,0),2); } else { printf("目标颜色消失...\n"); }
//显示处理结果 imshow("RGB", imgOriginal); imshow("Result", imgThresholded); cv::waitKey(5); }
int main(intargc, char **argv) { ros::init(argc, argv, "cv_hsv_node");
ros::NodeHandle nh; ros::Subscriber rgb_sub = nh.subscribe("kinect2/qhd/image_color_rect", 1 , Cam_RGB_Callback);
ros::Rate loop_rate(30);
//生成图像显示和参数调节的窗口空见 namedWindow("Threshold", CV_WINDOW_AUTOSIZE);
cvCreateTrackbar("LowH", "Threshold", &iLowH, 179); //Hue (0 - 179) cvCreateTrackbar("HighH", "Threshold", &iHighH, 179);
cvCreateTrackbar("LowS", "Threshold", &iLowS, 255); //Saturation (0 - 255) cvCreateTrackbar("HighS", "Threshold", &iHighS, 255);
cvCreateTrackbar("LowV", "Threshold", &iLowV, 255); //Value (0 - 255) cvCreateTrackbar("HighV", "Threshold", &iHighV, 255);
namedWindow("RGB"); namedWindow("Result"); while( ros::ok()) { ros::spinOnce(); loop_rate.sleep(); } } |
(1)代码的开始部分,先include了五个头文件:
ros.h是ROS的系统头文件;
cv_bridge.h是ROS图像格式和OpenCV图像格式相互转换的函数头文件;
image_encodings.h是图像数据编码格式头文件;
imgproc.hpp是OpenCV的图像处理函数头文件;
highgui.hpp是OpenCV里图像数据存储及显示函数的头文件;
(2)“using namespace cv”表示引入“cv”这个函数空间,这样代码中调用的所有OpenCV函数都不用再带上空间名,直接写函数名即可。
(3)“using namespace std”表示引入“std”标准库的函数空间,后面会用到容器vector,可以不用带上空间名,保持简洁方便阅读。
(4)定义了六个变量,分别是HSV颜色空间的三个向量的阈值范围:
iLowH是色调(Hue)的下限值;
iHighH是色调(Hue)的上限值;
iLowS是颜色饱和度(Saturation)的下限值;
iHighS是颜色饱和度(Saturation)的上限值;
iLowV是颜色亮度(Value)的下限值;
iHighV是颜色亮度(Value)的上限值;
HSV即“色调、饱和度、亮度”,是在颜色检测领域常用的一种颜色空间,将传统的RGB(红绿蓝)图像转换到HSV空间后,可以很大程度上排除光照对颜色检测的影响,从而获得更好的颜色检测结果。HSV的颜色空间如下图:
(6)定义一个回调函数Cam_RGB_Callback,用来处理视频流的单帧图像。其参数msg为ROS里携带图像数据的结构体,机器人每采集到一帧新的图像就会自动调用这个函数。
(7)在Cam_RGB_Callback回调函数内部,我们使用cv_bridge的toCvCopy函数将msg里的图像转换编码成BGR8格式,并保存在cv_ptr指针指向的内存区域。
(8)将cv_ptr指针指向的图像数据复制到imgOriginal,使用cvtColor将其色彩空间转换成HSV,然后进行直方图均衡化,再用咱们设定的阈值进行像素二值化。二值化的目的是将图像中目标物(比如橘色的球)的像素标记出来以便计算位置坐标,二值化结果就是后面咱们会看到的黑白图像,黑的为0,白色为1,其中白色区域就是目标物占据的像素空间。
(9)二值化后使用开操作(腐蚀)去除离散噪点,再闭操作(膨胀)将前一步操作破坏的大连通域再次连接起来。
(10)最后遍历二值化图像的所有像素,计算出橘色像素的坐标平均值,即为该颜色物体的质心。对于圆形这样中心对称的形状物体,可以认为质心坐标就是目标物的中心坐标。这里调用了OpenCV的line()函数,在RGB彩色图像中的目标物质心坐标处,绘制了一个蓝色的十字标记。
(11)在Cam_RGB_Callback回调函数的末尾,使用imshow将原始的彩色图像和最后二值化处理过的图像显示出来。绘制了物体质心坐标的彩色图像显示在一个标题为“RGB”的窗口中,二值化后的黑白图像显示在一个标题为“Result”的窗口中。最后还需要调用一个cv::waitKey(5);延时5毫秒,让上面的两个图像能够刷新显示。
(12)接下来的main(int argc, char** argv)是ROS节点的主体函数,其参数定义和其他C++程序一样。
(13)main函数里,首先调用ros::init(argc, argv, "cv_hsv_node")进行该节点的初始化操作,函数的第三个参数是本实验节点名称。
(14)接下来声明一个ros::NodeHandle对象nh,并用nh生成一个订阅对象rgb_sub,调用的参数里指明了rgb_sub将向主题“/kinect2/qhd/image_color_rect”订阅消息。机器人的摄像头启动后,会将图像数据源源不断的发布到这个主题上,这样我们的程序就能持续激活Cam_RGB_Callback回调函数。
(15)这里使用一个while(ros::ok())循环,以ros::ok()返回值作为循环结束条件可以让循环在程序关闭时正常退出。为了准确控制这个循环的运行周期,生成了一个ros::Rate的频率对象loop_rate,并在构造函数里赋初始值30,表示这个loop_rate对象会让while循环每秒钟循环30次。
(16)在进入while主循环前,先进行一些初始化,使用namedWindow初始化一个“Threshold”窗口。然后用cvCreateTrackbar给这个窗体添加六个滑动条,分别关联到程序开头声明的六个HSV的阈值变量,这样在运行的时候我们就可以用滑动条来实时调节这六个变量的数值大小。
(17)使用namedWindow创建“RGB”和“Result”两个窗体,在前面的回调函数里将会在这两个窗体里显示图像数据。
(18)在while循环中调用ros::spinOnce()函数给其他回调函数得以执行(本例程的回调函数是Cam_RGB_Callback)。
(19)调用loop_rate的sleep函数,确保while循环1秒钟循环30次。
程序编写完后,代码并未马上保存到文件里。这时可以看到VSCode界面右上编辑区的文件名“cv_hsv_node.cpp”右侧有个白色小圆点,标示此文件并未保存。需要按下键盘组合键“Ctrl + S”,界面右上编辑区的文件名标题“cv_hsv_node.cpp”右侧的白色小圆点变成了白色关闭按钮,此时代码文件才算保存成功。
完成保存操作后,还需要将源码文件添加到编译文件里才能进行编译。编译文件在cv_pkg的目录下,文件名为“CMakeLists.txt”。
在VSCode界面左侧工程目录中点击该文件,右侧会显示文件内容。对CMakeLists.txt的修改分为三个步骤:
(1)使用find_package()查找并引入OpenCV依赖包:
find_package(OpenCV REQUIRED) |
(2)添加OpenCV的函数头文件目录路径:
(3)为cv_hsv_node.cpp添加编译规则。内容如下:
add_executable(cv_hsv_node src/cv_hsv_node.cpp ) add_dependencies(cv_hsv_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) target_link_libraries(cv_hsv_node ${catkin_LIBRARIES} ${OpenCV_LIBS} ) |
同样,修改完需要按下键盘快捷键Ctrl+S进行保存,代码上方的文件名右侧的小白点会变成白色关闭按钮,说明保存文件成功。下面开始进行代码文件的编译,启动一个终端程序,键入如下命令进入ROS的工作空间:
cd catkin_ws/ |
然后再执行如下命令开始编译:
catkin_make |
执行这条指令之后,会出现滚动的编译信息,直到出现“[100%] Built targetcv_hsv_node”信息,此时新的cv_hsv_node节点已经编译成功。
2、下面我们需要启动运行这个cv_hsv_node节点的虚拟仿真环境。打开一个终端程序,键入如下命令:
roslaunch wpr_simulation wpb_balls.launch |
回车执行,会启动一个Gazebo窗口,一台ROS机器人面前摆着四个颜色球,机器人的头部相机俯视着这四个球。
3、运行我们编写的cv_hsv_node节点。启动一个新的终端程序,输入以下指令:
rosrun cv_pkg cv_hsv_node |
按下回车键,cv_hsv_node节点就启动起来了。此时会弹出三个窗口:
“RGB”窗体程序,里面显示的是机器人头部相机所看到的四个颜色球的图像。
“Threshold”窗体程序,里面显示的是当前使用的HSV颜色阈值,我们可以使用鼠标直接拖动窗体中的滑杆来改变阈值大小,在其他窗体中会实时显示阈值变化的效果。
“Result”窗体程序,里面显示的是转换颜色空间并二值化后的结果。白色的部分是检测到目标物的像素区域,黑色的部分是被剔除掉的非目标物的像素区域。
这时候我们切换到运行cv_hsv_node节点的终端窗口,可以看到检测到的目标物的中心坐标值:
机器人头部使用的相机是Kinect2,我们采集它的QHD图像分辨率是960×540。颜色质心的坐标是以图像的左上角为(0,0)原点,对照前面“RGB”彩色中绘出的目标位置(蓝色十字标记),可以看到最后计算的目标物质心坐标和图像显示结果大致相同。
4、我们可以借助wpr_simulation附带的程序让中间的桔色球动起来,以观察目标物运动时,咱们的检测算法是否继续奏效。在Ubuntu桌面再开一个新的终端程序,输入如下指令:
rosrun wpr_simulation ball_random_move |
执行之后,可以看到Gazebo里的橘色球开始随机运动。
此时再切换到“RGB”窗口程序,可以看到图像中的桔色球也跟着运动。
回到运行cv_hsv_node节点的终端窗口,可以看到目标中心坐标值也在相应变化。
5、实验中我们编写的cv_hsv_node.cpp在wpr_simulation中有个对应的例程,同学们在遇到编译问题时可以在VSCode中打开这个例程源码文件进行对比参考,文件位置:
~/catkin_ws/src/wpr_simulation/src/demo_cv_hsv.cpp |
其运行指令为:
rosrun wpr_simulation demo_cv_hsv |
6、可以在“Threshold”窗体中改变目标颜色的HSV阈值范围,锁定其他颜色的目标球。然后使用下面这些指令,让其他的颜色球也随机运动,验证这种检测方法的普适性。
红色球 |
rosrun wpr_simulation ball_random_move red |
绿色球 |
rosrun wpr_simulation ball_random_move green |
蓝色球 |
rosrun wpr_simulation ball_random_move blue |
在这个实验中,我们使用OpenCV的颜色空间转换函数,实现了目标物体的位置检测,在这基础上可以扩展出很多的应用。下一次我们会尝试着让ROS机器人完成一个目标追踪的任务,将目标检测和运动控制结合起来,形成一个完整的视觉闭环系统。
下期再见~
更多产品了解
欢迎扫码加入云巴巴企业数字化交流服务群
产品交流、问题咨询、专业测评
都在这里!
2022-08-24 11:07:14
2022-07-28 17:56:56
2022-11-21 10:24:24
2021-12-22 17:54:43
甄选10000+数字化产品 为您免费使用
申请试用
评论列表