(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Camera Picture Server Tutorial

Description: This tutorial is a simple example for practice in service usage and image transportation in ROS. It explains how to build a server that takes a picture from a camera. A publisher was created to send camera content continuously, and a server receives a command to take and save a picture.

Keywords: Service Server, Opencv, Camera

Tutorial Level: INTERMEDIATE

Next Tutorial: Camera+Dynamixel Controller Dynamixel Position Control

Creating your package

In package creation, some components will be necessary, so start with:

catkin_create_pkg picture_server sensor_msgs cv_bridge roscpp std_msgs image_transport

Camera image publisher

In src folder, create a image publisher node, which continuously sends webcam data through the topic cam_pub:

   1 #include <ros/ros.h>
   2 #include <image_transport/image_transport.h>
   3 #include <opencv2/highgui/highgui.hpp>
   4 #include <cv_bridge/cv_bridge.h>
   5 #include <sstream> // for converting the command line parameter to integer
   6 
   7 int main(int argc, char** argv){
   8   // Check if video source has been passed as a parameter
   9   if(argv[1] == NULL) return 1;
  10 
  11   ros::init(argc, argv, "image_publisher");
  12   ros::NodeHandle nh;
  13   image_transport::ImageTransport it(nh);
  14   image_transport::Publisher pub = it.advertise("cam_pub", 1);
  15   int cam_device;
  16   //condition to verify if the camera device was passed as the first
  17   //terminal parameter "argv[1]" or as the "cam_device" parameter in the .launch.
  18   //id 0 device is passed as default
  19   if (argc < 2)
  20      nh.param("cam_device", cam_device, 0);
  21   else{
  22      // Convert the passed as command line parameter index
  23      //for the video device to an integer
  24      std::istringstream cam_deviceCmd(argv[1]);
  25      // Check if it is indeed a number
  26      if(!(cam_deviceCmd >> cam_device)) return 1;
  27   }
  28   //initialize a VideoCapture variable to get camera data
  29   cv::VideoCapture cap(cam_device);
  30   // Check if video device can be opened with the given index
  31   if(!cap.isOpened()) return 1;
  32   cv::Mat frame;
  33   sensor_msgs::ImagePtr msg;
  34 
  35   int image_show;
  36   nh.param("image_show", image_show, 0);
  37 
  38   while (nh.ok()) {
  39      cap >> frame;
  40      //show the image
  41      if (image_show){
  42         cv::imshow("Cam View", frame);
  43         cv::waitKey(30);
  44      }
  45      //Check if grabbed frame is actually full with some content,
  46      //then publish the image
  47      if(!frame.empty()) {
  48         msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", frame).toImageMsg();
  49         pub.publish(msg);
  50      }
  51   }
  52 }

Picture writer service server

Also in src, create the image server, which receives a boolean command (to save the current camera image), the path where the image will be saved and the image name, returning a integer representing wheter the service was properly done:

   1 #include <string>
   2 #include <ros/ros.h>
   3 #include <image_transport/image_transport.h>
   4 #include <opencv2/highgui/highgui.hpp>
   5 #include <cv_bridge/cv_bridge.h>
   6 #include "picture_server/image_cmd.h"
   7 #include <sstream>
   8 //Class creation to allow the use of camera callback msg in the service
   9 class PictureServer{
  10    cv::Mat picture;
  11 
  12 public:
  13    //callback to get camera data through "image_pub" topic
  14    void imageCallback(const sensor_msgs::ImageConstPtr& msg){
  15       try{
  16          picture = cv_bridge::toCvShare(msg, "bgr8")->image.clone();
  17       }
  18       catch (cv_bridge::Exception& e){
  19          ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str());
  20       }
  21    }
  22    // service callback that receives "angle" (int representing image name),
  23    // "path" (path to save image data) and
  24    // "cmd" (comand confirming if the camera data
  25    // should be saved). The service response should return a "result" returning 1
  26    // if the data was correctly saved
  27    bool check_and_print(picture_server::image_cmd::Request &req,
  28                         picture_server::image_cmd::Response &res){
  29       if (req.cmd){
  30          //image name composed by path (finished with "/")+ capture angle+extension
  31          std::string im_name = req.path + req.num_name+ ".png";
  32          //checking if the picture has a valid content,
  33          //otherwise system would failed and stop trying to write the image
  34          if(!picture.empty()){
  35             if (!cv::imwrite (im_name, picture)){
  36                res.result = 0;
  37                std::cout<<"Image can not be saved as '"<<im_name<<"'\n";
  38             }else{
  39                // represent success to save the image
  40                std::cout<<"Image saved in '"<<im_name<<"'\n";
  41                res.result = 1;
  42             }
  43          }else{
  44             // represent fail to save the image
  45             res.result = 0;
  46             ROS_ERROR("Failed to save image\n");
  47          }
  48       }else{
  49          // represent that server was called, but image was not requested
  50          res.result = 2;
  51       }
  52    }
  53 };
  54 
  55 int main(int argc, char **argv)
  56 {
  57    PictureServer mi;
  58    ros::init(argc, argv, "Img_Ctrl_server");
  59    ros::NodeHandle nh;
  60    image_transport::ImageTransport it(nh);
  61    ros::ServiceServer service = nh.advertiseService("image_cmd", &PictureServer::check_and_print, &mi);
  62   image_transport::Subscriber sub = it.subscribe("cam_pub", 1, &PictureServer::imageCallback, &mi);
  63 
  64    ros::spin();
  65 }

Service file

Create the srv services folder and a "image_cmd.srv" service file inside it, containing a request with the command to take the picture, the path to save the image and the image name and the response with the server result:

bool cmd
string path
string num_name
---
int32 result

Package configuration

Now it is necessary to edit the package.xml by including:

<build_depend>message_generation</build_depend>
<build_depend>image_transport</build_depend>
<exec_depend>message_runtime</exec_depend>
<exec_depend>image_transport</exec_depend>

CMakelists configuration

It is also necessary to edit some lines to the generated CMaklists.txt. Insert the following line after the block find_package:

find_package(OpenCV REQUIRED)

Uncommnent and change the add_service_files and generate_messages block to be like this:

add_service_files(
   FILES
   image_cmd.srv
 )

 generate_messages(
   DEPENDENCIES
   #sensor_msgs   #std_msgs
 )

Finally, we add our executables at the end of the file:

add_executable(cam_publisher src/cam_publisher.cpp)
target_link_libraries(cam_publisher ${catkin_LIBRARIES} ${OpenCV_LIBS})

add_executable(picture_server src/picture_server.cpp)
target_link_libraries(picture_server ${catkin_LIBRARIES} ${OpenCV_LIBS})

To finish, it is just call:

catkin_make install

Note: The whole package is available in https://github.com/leonejesus/picture_server.

Wiki: Camera+DynamixelRobotSample/CameraPictureServer (last edited 2018-04-19 19:34:34 by Leone)