-
Notifications
You must be signed in to change notification settings - Fork 0
Example : Using image and Controlling ROBOTIS OP2 LED
This tutorial describes how to interface ROS and OpenCV by converting ROS images into OpenCV images and how to control OP LED. Included is a sample node that can be used as a template for your own node.
This package subscribes ROS image(sensor_msgs/Image) and calculates the RGB average value of center of image. And It controls the LED of ROBOTIS OP to same color. (It works with example_read_write of robotis_example package and robotis_controller package)
###1.1 Installation Get source code
$ cd %YOUR_CATKIN_WORKSPACE_HOME%/src
$ git clone https://github.com/ROBOTIS-GIT/ROBOTIS-OP.git
Build package
$ cd %YOUR_CATKIN_WORKSPACE_HOME%/
$ catkin_make
###1.2 Running the package
$ roslaunch color_recognition color_recognition_op.launch
##2. Nodes ###2.1 color_recognition_node 2.1.1 Subscribed Topics
- image_raw (sensor_msgs/Image) : The image topic. Should be remapped to the name of the real image topic.
2.1.2 Published Topics
-
Image_out (sensor_msgs/Image) : Output image topic.
-
head_led_color (std_msgs/ColorRGBA) : ROBOTIS OP2 led control topic.
2.1.3 Parameters
- None
##3. Code ###3.1 color_recognition.h
//std
#include <string>
//ros dependencies
#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
#include <std_msgs/ColorRGBA.h>
#include <sensor_msgs/image_encodings.h>
const unsigned int IMG_MONO = 0;
const unsigned int IMG_RGB8 = 1;
class ColorRecogNode
{
protected:
//ros node handle
ros::NodeHandle nh;
//image publisher/subscriber
image_transport::ImageTransport it;
image_transport::Publisher imagePub;
image_transport::Subscriber imageSubs;
cv_bridge::CvImagePtr cvImgPtr;
// led color publisher
std_msgs::ColorRGBA colorMsg;
ros::Publisher colorPub;
//flag indicating a new image has been received
bool newImageFlag;
//img encoding id
unsigned int imgEncoding;
int recog_size;
int image_height, image_width;
int start_height, start_width;
protected:
//callbacks to image subscription
void imageCallback(const sensor_msgs::Image::ConstPtr &msg);
public:
//constructor
ColorRecogNode();
//destructor
~ColorRecogNode();
//checks if a new image has been received
bool newImage();
//execute circle detection with the cureent image
void process();
//publish the output image (input image + marked circles)
void publishImage();
//publish the circle set data
void publishColor();
};
Let's break down the above header file.
#include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h>
Using image_transport for publishing and subscribing to images in ROS allows you to subscribe to compressed image streams. Remember to include image_transport in your package.xml.
Includes the header for CvBridge as well as some useful constants and functions related to image encodings. Remember to include cv_bridge in your package.xml.
//image publisher/subscriber
image_transport::ImageTransport it;
image_transport::Publisher imagePub;
image_transport::Subscriber imageSubs;
cv_bridge::CvImagePtr cvImgPtr;
// led color publisher
std_msgs::ColorRGBA colorMsg;
ros::Publisher colorPub;
Subscribe to an image topic "in" and advertise an image topic "out" using image_transport. And advertise a color topic to control OP led (colorPub).
###3.2 color_recognition_main.cpp
#include "color_recognition/color_recognition.h"
//node main
int main(int argc, char **argv)
{
//init ros
ros::init(argc, argv, "color_recognition_node");
//create ros wrapper object
ColorRecogNode color_node;
//set node loop rate
ros::Rate loop_rate(30);
ROS_INFO("Start color recongnition .....");
//node loop
while ( ros::ok() )
{
//if new image , do things
if ( color_node.newImage() )
{
color_node.process();
color_node.publishImage();
color_node.publishColor();
}
//execute pending callbacks
ros::spinOnce();
//relax to fit output rate
loop_rate.sleep();
}
//exit program
return 0;
}
Let's break down the above code.
//set node loop rate
ros::Rate loop_rate(30);
while ( ros::ok() )
{
…
//relax to fit output rate
loop_rate.sleep();
}
A ros::Rate object allows you to specify a frequency that you would like to loop at(in the above code, a loop rate is 30hz). It will keep track of how long it has been since the last call to Rate::sleep(), and sleep for the correct amount of time.
###3.3 color_recognition.cpp
#include "color_recognition/color_recognition.h"
ColorRecogNode::ColorRecogNode() : nh(ros::this_node::getName()) , it(this->nh)
{
//sets publishers
imagePub = it.advertise("/image_out", 100);
colorPub = nh.advertise<std_msgs::ColorRGBA>("/head_led_color", 100); // for head led
// colorPub = nh.advertise<std_msgs::ColorRGBA>("eye_led_color", 100); // for eye leds
//sets subscribers
imageSubs = it.subscribe("/image_raw", 1, &ColorRecogNode::imageCallback, this);
//initializes newImageFlag
newImageFlag = false;
}
ColorRecogNode::~ColorRecogNode()
{
}
bool ColorRecogNode::newImage()
{
if ( newImageFlag )
{
newImageFlag = false;
return true;
}
else
{
return false;
}
}
void ColorRecogNode::process()
{
if(this->imgEncoding != IMG_RGB8) return;
// get average of rgb value
if ( cvImgPtr != NULL )
{
int _sum_r = 0, _sum_g = 0, _sum_b = 0;
for(int _row = start_height; _row < start_height + recog_size; _row++)
{
for(int _col = start_width; _col < (start_width + recog_size); _col ++)
{
// split rgb channel
cv::Vec3b _bgr_color = cvImgPtr->image.at<cv::Vec3b>(_row, _col);
_sum_r += (int) _bgr_color.val[0];
_sum_g += (int) _bgr_color.val[1];
_sum_b += (int) _bgr_color.val[2];
}
}
colorMsg.r = _sum_r / (recog_size * recog_size);
colorMsg.g = _sum_g / (recog_size * recog_size);
colorMsg.b = _sum_b / (recog_size * recog_size);
}
}
void ColorRecogNode::publishImage()
{
int _line_thickness = recog_size * 0.1;
int _bg_thickness = _line_thickness + 2;
// draw a rectangle bg(black)
cv::rectangle(cvImgPtr->image,
cv::Point(start_width - _line_thickness * 0.5, start_height - _line_thickness * 0.5),
cv::Point(start_width + recog_size + _line_thickness, start_height + recog_size + _line_thickness),
cv::Scalar(0, 0, 0),
_bg_thickness);
// draw a rectangle
cv::rectangle(cvImgPtr->image,
cv::Point(start_width - _line_thickness * 0.5, start_height - _line_thickness * 0.5),
cv::Point(start_width + recog_size + _line_thickness, start_height + recog_size + _line_thickness),
cv::Scalar(colorMsg.r, colorMsg.g, colorMsg.b),
_line_thickness);
// convert OpenCV image to ROS image and publish image
imagePub.publish(cvImgPtr->toImageMsg());
}
void ColorRecogNode::publishColor()
{
// publish OP_LED message
colorPub.publish(colorMsg);
}
void ColorRecogNode::imageCallback(const sensor_msgs::Image::ConstPtr & msg)
{
// convert ROS image to OpenCV image
try
{
if ( msg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 ) this->imgEncoding = IMG_MONO;
if ( msg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0 ) this->imgEncoding = IMG_RGB8;
this->cvImgPtr = cv_bridge::toCvCopy(msg, msg->encoding);
}
catch (cv_bridge::Exception& e)
{
ROS_ERROR("cv_bridge exception: %s", e.what());
return;
}
image_height = msg->height;
image_width = msg->width;
recog_size = (int)(image_height * 0.1);
start_height = (int)((image_height - recog_size) * 0.5);
start_width = (int)((image_width - recog_size) * 0.5);
// set new image flag
this->newImageFlag = true;
return;
}
Let's break down the above header file.
ColorRecogNode::ColorRecogNode() : nh(ros::this_node::getName()) , it(this->nh)
{
//sets publishers
imagePub = it.advertise("/image_out", 100);
colorPub = nh.advertise<std_msgs::ColorRGBA>("/head_led_color", 100); // for head led
// colorPub = nh.advertise<std_msgs::ColorRGBA>("eye_led_color", 100); // for eye leds
//sets subscribers
imageSubs = it.subscribe("/image_raw", 1, &ColorRecogNode::imageCallback, this);
//initializes newImageFlag
newImageFlag = false;
}
Tell the master that we are going to be publishing messages. This lets the master tell any nodes listening on /image_out or /head_led_color that we are going to publish data on that topic. The second argument is the size of our publishing queue. In this case if we are publishing too quickly it will buffer up a maximum of 100 messages before beginning to throw away old ones.
for(int _row = start_height; _row < start_height + recog_size; _row++)
{
for(int _col = start_width; _col < (start_width + recog_size); _col ++)
{
// split rgb channel
cv::Vec3b _bgr_color = cvImgPtr->image.at<cv::Vec3b>(_row, _col);
_sum_r += (int) _bgr_color.val[0];
_sum_g += (int) _bgr_color.val[1];
_sum_b += (int) _bgr_color.val[2];
}
}
colorMsg.r = _sum_r / (recog_size * recog_size);
colorMsg.g = _sum_g / (recog_size * recog_size);
colorMsg.b = _sum_b / (recog_size * recog_size);
Access specific channel of image Mat, Calculate average values of each channel.
// convert OpenCV image to ROS image and publish image
imagePub.publish(cvImgPtr->toImageMsg());
To convert a CvImage into a ROS image message, use one the toImageMsg() function. If the CvImage is one you have allocated youself, don't forget to fill in the header and encoding fields.
// convert ROS image to OpenCV image
try
{
if ( msg->encoding.compare(sensor_msgs::image_encodings::MONO8) == 0 ) this->imgEncoding = IMG_MONO;
if ( msg->encoding.compare(sensor_msgs::image_encodings::RGB8) == 0 ) this->imgEncoding = IMG_RGB8;
this->cvImgPtr = cv_bridge::toCvCopy(msg, msg->encoding);
}
catch (cv_bridge::Exception& e)
{
ROS_ERROR("cv_bridge exception: %s", e.what());
return;
}
When converting a ROS sensor_msgs/Image message into a CvImage, CvBridge recognizes two distinct use cases: