video_stream.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (Modified BSD License)
00003  *
00004  *  Copyright (c) 2016, PAL Robotics, S.L.
00005  *  All rights reserved.
00006  *
00007  *  Redistribution and use in source and binary forms, with or without
00008  *  modification, are permitted provided that the following conditions
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of PAL Robotics, S.L. nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
00033  *
00034  * @author Sammy Pfeiffer
00035  */
00036 
00037 #include <ros/ros.h>
00038 #include <nodelet/nodelet.h>
00039 #include <dynamic_reconfigure/server.h>
00040 #include <image_transport/image_transport.h>
00041 #include <camera_info_manager/camera_info_manager.h>
00042 #include <opencv2/highgui/highgui.hpp>
00043 #include <opencv2/imgproc/imgproc.hpp>
00044 #include <cv_bridge/cv_bridge.h>
00045 #include <sstream>
00046 #include <stdexcept>
00047 #include <boost/filesystem.hpp>
00048 #include <boost/assign/list_of.hpp>
00049 #include <boost/thread/thread.hpp>
00050 #include <queue>
00051 #include <mutex>
00052 #include <video_stream_opencv/VideoStreamConfig.h>
00053 
00054 namespace fs = boost::filesystem;
00055 
00056 namespace video_stream_opencv {
00057 
00058 class VideoStreamNodelet: public nodelet::Nodelet {
00059 protected:
00060 boost::shared_ptr<ros::NodeHandle> nh, pnh;
00061 image_transport::CameraPublisher pub;
00062 boost::shared_ptr<dynamic_reconfigure::Server<VideoStreamConfig> > dyn_srv;
00063 std::mutex q_mutex, s_mutex;
00064 std::queue<cv::Mat> framesQueue;
00065 cv::Mat frame;
00066 boost::shared_ptr<cv::VideoCapture> cap;
00067 std::string video_stream_provider;
00068 std::string video_stream_provider_type;
00069 std::string camera_name;
00070 std::string camera_info_url;
00071 std::string frame_id;
00072 double set_camera_fps;
00073 double fps;
00074 int max_queue_size;
00075 bool loop_videofile;
00076 int subscriber_num;
00077 int width_target;
00078 int height_target;
00079 bool flip_horizontal;
00080 bool flip_vertical;
00081 bool capture_thread_running;
00082 bool reopen_on_read_failure;
00083 boost::thread capture_thread;
00084 ros::Timer publish_timer;
00085 sensor_msgs::CameraInfo cam_info_msg;
00086 
00087 // Based on the ros tutorial on transforming opencv images to Image messages
00088 
00089 virtual sensor_msgs::CameraInfo get_default_camera_info_from_image(sensor_msgs::ImagePtr img){
00090     sensor_msgs::CameraInfo cam_info_msg;
00091     cam_info_msg.header.frame_id = img->header.frame_id;
00092     // Fill image size
00093     cam_info_msg.height = img->height;
00094     cam_info_msg.width = img->width;
00095     NODELET_INFO_STREAM("The image width is: " << img->width);
00096     NODELET_INFO_STREAM("The image height is: " << img->height);
00097     // Add the most common distortion model as sensor_msgs/CameraInfo says
00098     cam_info_msg.distortion_model = "plumb_bob";
00099     // Don't let distorsion matrix be empty
00100     cam_info_msg.D.resize(5, 0.0);
00101     // Give a reasonable default intrinsic camera matrix
00102     cam_info_msg.K = boost::assign::list_of(1.0) (0.0) (img->width/2.0)
00103             (0.0) (1.0) (img->height/2.0)
00104             (0.0) (0.0) (1.0);
00105     // Give a reasonable default rectification matrix
00106     cam_info_msg.R = boost::assign::list_of (1.0) (0.0) (0.0)
00107             (0.0) (1.0) (0.0)
00108             (0.0) (0.0) (1.0);
00109     // Give a reasonable default projection matrix
00110     cam_info_msg.P = boost::assign::list_of (1.0) (0.0) (img->width/2.0) (0.0)
00111             (0.0) (1.0) (img->height/2.0) (0.0)
00112             (0.0) (0.0) (1.0) (0.0);
00113     return cam_info_msg;
00114 }
00115 
00116 
00117 virtual void do_capture() {
00118     NODELET_DEBUG("Capture thread started");
00119     cv::Mat frame;
00120     ros::Rate camera_fps_rate(set_camera_fps);
00121 
00122     int frame_counter = 0;
00123     // Read frames as fast as possible
00124     capture_thread_running = true;
00125     while (nh->ok() && capture_thread_running && subscriber_num > 0) {
00126         if (!cap->isOpened()) {
00127           NODELET_WARN("Waiting for device...");
00128           cv::waitKey(100);
00129           continue;
00130         }
00131         if (!cap->read(frame)) {
00132           NODELET_ERROR("Could not capture frame");
00133           if (reopen_on_read_failure) {
00134             NODELET_WARN("trying to reopen the device");
00135             unsubscribe();
00136             subscribe();
00137           }
00138         }
00139 
00140         frame_counter++;
00141         if (video_stream_provider_type == "videofile")
00142         {
00143             camera_fps_rate.sleep();
00144         }
00145         if (video_stream_provider_type == "videofile" &&
00146             frame_counter == cap->get(CV_CAP_PROP_FRAME_COUNT)) 
00147         {
00148             if (loop_videofile)
00149             {
00150                 cap->open(video_stream_provider);
00151                 frame_counter = 0;
00152             }
00153             else {
00154               NODELET_INFO("Reached the end of frames");
00155               break;
00156             }
00157         }
00158 
00159         if(!frame.empty()) {
00160             std::lock_guard<std::mutex> g(q_mutex);
00161             // accumulate only until max_queue_size
00162             if (framesQueue.size() < max_queue_size) {
00163                 framesQueue.push(frame.clone());
00164             }
00165             // once reached, drop the oldest frame
00166             else {
00167                 framesQueue.pop();
00168                 framesQueue.push(frame.clone());
00169             }
00170         }
00171     }
00172     NODELET_DEBUG("Capture thread finished");
00173 }
00174 
00175 virtual void do_publish(const ros::TimerEvent& event) {
00176     bool is_new_image = false;
00177     sensor_msgs::ImagePtr msg;
00178     std_msgs::Header header;
00179     header.frame_id = frame_id;
00180     {
00181         std::lock_guard<std::mutex> g(q_mutex);
00182         if (!framesQueue.empty()){
00183             frame = framesQueue.front();
00184             framesQueue.pop();
00185             is_new_image = true;
00186         }
00187     }
00188 
00189     // Check if grabbed frame is actually filled with some content
00190     if(!frame.empty()) {
00191         // From http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#void flip(InputArray src, OutputArray dst, int flipCode)
00192         // FLIP_HORIZONTAL == 1, FLIP_VERTICAL == 0 or FLIP_BOTH == -1
00193         // Flip the image if necessary
00194         if (is_new_image){
00195           if (flip_horizontal && flip_vertical)
00196             cv::flip(frame, frame, -1);
00197           else if (flip_horizontal)
00198             cv::flip(frame, frame, 1);
00199           else if (flip_vertical)
00200             cv::flip(frame, frame, 0);
00201         }
00202         msg = cv_bridge::CvImage(header, "bgr8", frame).toImageMsg();
00203         // Create a default camera info if we didn't get a stored one on initialization
00204         if (cam_info_msg.distortion_model == ""){
00205             NODELET_WARN_STREAM("No calibration file given, publishing a reasonable default camera info.");
00206             cam_info_msg = get_default_camera_info_from_image(msg);
00207             // cam_info_manager.setCameraInfo(cam_info_msg);
00208         }
00209         // The timestamps are in sync thanks to this publisher
00210         pub.publish(*msg, cam_info_msg, ros::Time::now());
00211     }
00212 }
00213 
00214 virtual void subscribe() {
00215   ROS_DEBUG("Subscribe");
00216   cap.reset(new cv::VideoCapture);
00217   try {
00218     int device_num = std::stoi(video_stream_provider);
00219     NODELET_INFO_STREAM("Opening VideoCapture with provider: /dev/video" << device_num);
00220     cap->open(device_num);
00221   } catch (std::invalid_argument &ex) {
00222     NODELET_INFO_STREAM("Opening VideoCapture with provider: " << video_stream_provider);
00223     cap->open(video_stream_provider);
00224     if (!cap->isOpened()) {
00225       NODELET_FATAL_STREAM("Invalid 'video_stream_provider': " << video_stream_provider);
00226       return;
00227     }
00228   }
00229   NODELET_INFO_STREAM("Video stream provider type detected: " << video_stream_provider_type);
00230 
00231   double reported_camera_fps;
00232   // OpenCV 2.4 returns -1 (instead of a 0 as the spec says) and prompts an error
00233   // HIGHGUI ERROR: V4L2: Unable to get property <unknown property string>(5) - Invalid argument
00234   reported_camera_fps = cap->get(CV_CAP_PROP_FPS);
00235   if (reported_camera_fps > 0.0)
00236     NODELET_INFO_STREAM("Camera reports FPS: " << reported_camera_fps);
00237   else
00238     NODELET_INFO_STREAM("Backend can't provide camera FPS information");
00239 
00240   cap->set(CV_CAP_PROP_FPS, set_camera_fps);
00241   if(!cap->isOpened()){
00242     NODELET_ERROR_STREAM("Could not open the stream.");
00243     return;
00244   }
00245   if (width_target != 0 && height_target != 0){
00246     cap->set(CV_CAP_PROP_FRAME_WIDTH, width_target);
00247     cap->set(CV_CAP_PROP_FRAME_HEIGHT, height_target);
00248   }
00249 
00250   try {
00251     capture_thread = boost::thread(
00252       boost::bind(&VideoStreamNodelet::do_capture, this));
00253     publish_timer = nh->createTimer(
00254       ros::Duration(1.0 / fps), &VideoStreamNodelet::do_publish, this);
00255   } catch (std::exception& e) {
00256     NODELET_ERROR_STREAM("Failed to start capture thread: " << e.what());
00257   }
00258 }
00259 
00260 virtual void unsubscribe() {
00261   ROS_DEBUG("Unsubscribe");
00262   publish_timer.stop();
00263   capture_thread_running = false;
00264   capture_thread.join();
00265   cap.reset();
00266 }
00267 
00268 virtual void connectionCallbackImpl() {
00269   std::lock_guard<std::mutex> lock(s_mutex);
00270   if (subscriber_num == 0) {
00271     subscriber_num++;
00272     subscribe();
00273   }
00274 }
00275 
00276 virtual void disconnectionCallbackImpl() {
00277   std::lock_guard<std::mutex> lock(s_mutex);
00278   bool always_subscribe = false;
00279   pnh->getParamCached("always_subscribe", always_subscribe);
00280   if (video_stream_provider == "videofile" || always_subscribe) {
00281     return;
00282   }
00283 
00284   subscriber_num--;
00285   if (subscriber_num == 0) {
00286     unsubscribe();
00287   }
00288 }
00289 
00290 virtual void connectionCallback(const image_transport::SingleSubscriberPublisher&) {
00291   connectionCallbackImpl();
00292 }
00293 
00294 virtual void infoConnectionCallback(const ros::SingleSubscriberPublisher&) {
00295   connectionCallbackImpl();
00296 }
00297 
00298 virtual void disconnectionCallback(const image_transport::SingleSubscriberPublisher&) {
00299   disconnectionCallbackImpl();
00300 }
00301 
00302 virtual void infoDisconnectionCallback(const ros::SingleSubscriberPublisher&) {
00303   disconnectionCallbackImpl();
00304 }
00305 
00306 virtual void configCallback(VideoStreamConfig& config, uint32_t level) {
00307     NODELET_DEBUG("configCallback");
00308     bool need_resubscribe = false;
00309 
00310     if (camera_name != config.camera_name ||
00311         camera_info_url != config.camera_info_url ||
00312         frame_id != config.frame_id) {
00313       camera_name = config.camera_name;
00314       camera_info_url = config.camera_info_url;
00315       frame_id = config.frame_id;
00316       NODELET_INFO_STREAM("Camera name: " << camera_name);
00317       NODELET_INFO_STREAM("Provided camera_info_url: '" << camera_info_url << "'");
00318       NODELET_INFO_STREAM("Publishing with frame_id: " << frame_id);
00319       camera_info_manager::CameraInfoManager cam_info_manager(*nh, camera_name, camera_info_url);
00320       // Get the saved camera info if any
00321       cam_info_msg = cam_info_manager.getCameraInfo();
00322       cam_info_msg.header.frame_id = frame_id;
00323     }
00324 
00325     if (set_camera_fps != config.set_camera_fps ||
00326         fps != config.fps) {
00327       if (config.fps > config.set_camera_fps) {
00328         NODELET_WARN_STREAM("Asked to publish at 'fps' (" << config.fps
00329                             << ") which is higher than the 'set_camera_fps' (" << config.set_camera_fps <<
00330                             "), we can't publish faster than the camera provides images.");
00331         config.fps = config.set_camera_fps;
00332       }
00333       set_camera_fps = config.set_camera_fps;
00334       fps = config.fps;
00335       NODELET_INFO_STREAM("Setting camera FPS to: " << set_camera_fps);
00336       NODELET_INFO_STREAM("Throttling to fps: " << fps);
00337       need_resubscribe = true;
00338     }
00339 
00340     if (max_queue_size != config.buffer_queue_size) {
00341       max_queue_size = config.buffer_queue_size;
00342       NODELET_INFO_STREAM("Setting buffer size for capturing frames to: " << max_queue_size);
00343     }
00344 
00345     if (flip_horizontal != config.flip_horizontal ||
00346         flip_vertical != config.flip_vertical) {
00347       flip_horizontal = config.flip_horizontal;
00348       flip_vertical = config.flip_vertical;
00349       NODELET_INFO_STREAM("Flip horizontal image is: " << ((flip_horizontal)?"true":"false"));
00350       NODELET_INFO_STREAM("Flip vertical image is: " << ((flip_vertical)?"true":"false"));
00351     }
00352 
00353     if (width_target != config.width ||
00354         height_target != config.height) {
00355       width_target = config.width;
00356       height_target = config.height;
00357       if (width_target != 0 && height_target != 0) {
00358         NODELET_INFO_STREAM("Forced image width is: " << width_target);
00359         NODELET_INFO_STREAM("Forced image height is: " << height_target);
00360       }
00361       need_resubscribe = true;
00362     }
00363 
00364     if (cap && cap->isOpened()) {
00365       cap->set(CV_CAP_PROP_BRIGHTNESS, config.brightness);
00366       cap->set(CV_CAP_PROP_CONTRAST, config.contrast);
00367       cap->set(CV_CAP_PROP_HUE, config.hue);
00368       cap->set(CV_CAP_PROP_SATURATION, config.saturation);
00369       if (config.auto_exposure) {
00370         cap->set(CV_CAP_PROP_AUTO_EXPOSURE, 0.75);
00371         config.exposure = 0.5;
00372       } else {
00373         cap->set(CV_CAP_PROP_AUTO_EXPOSURE, 0.25);
00374         cap->set(CV_CAP_PROP_EXPOSURE, config.exposure);
00375       }
00376     }
00377 
00378     loop_videofile = config.loop_videofile;
00379     reopen_on_read_failure = config.reopen_on_read_failure;
00380 
00381     if (subscriber_num > 0 && need_resubscribe) {
00382       unsubscribe();
00383       subscribe();
00384     }
00385 }
00386 
00387 virtual void onInit() {
00388     nh.reset(new ros::NodeHandle(getNodeHandle()));
00389     pnh.reset(new ros::NodeHandle(getPrivateNodeHandle()));
00390     subscriber_num = 0;
00391 
00392     // provider can be an url (e.g.: rtsp://10.0.0.1:554) or a number of device, (e.g.: 0 would be /dev/video0)
00393     pnh->param<std::string>("video_stream_provider", video_stream_provider, "0");
00394     // check file type
00395     try {
00396       int device_num = std::stoi(video_stream_provider);
00397       video_stream_provider_type ="videodevice";
00398     } catch (std::invalid_argument &ex) {
00399       if (video_stream_provider.find("http://") != std::string::npos ||
00400           video_stream_provider.find("https://") != std::string::npos){
00401         video_stream_provider_type = "http_stream";
00402       }
00403       else if(video_stream_provider.find("rtsp://") != std::string::npos){
00404         video_stream_provider_type = "rtsp_stream";
00405       }
00406       else{
00407         fs::file_type file_type = fs::status(fs::path(video_stream_provider)).type();
00408         switch (file_type) {
00409         case fs::file_type::character_file:
00410         case fs::file_type::block_file:
00411           video_stream_provider_type = "videodevice";
00412           break;
00413         case fs::file_type::regular_file:
00414           video_stream_provider_type = "videofile";
00415           break;
00416         default:
00417           video_stream_provider_type = "unknown";
00418         }
00419       }
00420     }
00421 
00422     // set parameters from dynamic reconfigure server
00423     dyn_srv = boost::make_shared<dynamic_reconfigure::Server<VideoStreamConfig> >(*pnh);
00424     auto f = boost::bind(&VideoStreamNodelet::configCallback, this, _1, _2);
00425     dyn_srv->setCallback(f);
00426 
00427     subscriber_num = 0;
00428     image_transport::SubscriberStatusCallback connect_cb =
00429       boost::bind(&VideoStreamNodelet::connectionCallback, this, _1);
00430     ros::SubscriberStatusCallback info_connect_cb =
00431       boost::bind(&VideoStreamNodelet::infoConnectionCallback, this, _1);
00432     image_transport::SubscriberStatusCallback disconnect_cb =
00433       boost::bind(&VideoStreamNodelet::disconnectionCallback, this, _1);
00434     ros::SubscriberStatusCallback info_disconnect_cb =
00435       boost::bind(&VideoStreamNodelet::infoDisconnectionCallback, this, _1);
00436     pub = image_transport::ImageTransport(*nh).advertiseCamera(
00437       "image_raw", 1,
00438       connect_cb, connect_cb,
00439       info_connect_cb, info_connect_cb,
00440       ros::VoidPtr(), false);
00441 }
00442 
00443 virtual ~VideoStreamNodelet() {
00444   if (subscriber_num > 0)
00445     subscriber_num = 0;
00446     unsubscribe();
00447 }
00448 };
00449 } // namespace
00450 
00451 #include <pluginlib/class_list_macros.h>
00452 PLUGINLIB_EXPORT_CLASS(video_stream_opencv::VideoStreamNodelet, nodelet::Nodelet)


video_stream_opencv
Author(s): Sammy Pfeiffer
autogenerated on Wed Jun 19 2019 19:51:32