video_stream.cpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (Modified BSD License)
3  *
4  * Copyright (c) 2016, PAL Robotics, S.L.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of PAL Robotics, S.L. nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * @author Sammy Pfeiffer
35  */
36 
37 #include <ros/ros.h>
38 #include <nodelet/nodelet.h>
39 #include <dynamic_reconfigure/server.h>
42 #include <opencv2/highgui/highgui.hpp>
43 #include <opencv2/imgproc/imgproc.hpp>
44 #include <cv_bridge/cv_bridge.h>
45 #include <sstream>
46 #include <stdexcept>
47 #include <boost/filesystem.hpp>
48 #include <boost/assign/list_of.hpp>
49 #include <boost/thread/thread.hpp>
50 #include <queue>
51 #include <mutex>
52 #include <video_stream_opencv/VideoStreamConfig.h>
53 
54 namespace fs = boost::filesystem;
55 
57 
59 protected:
63 std::mutex q_mutex, s_mutex;
64 std::queue<cv::Mat> framesQueue;
65 cv::Mat frame;
69 std::string camera_name;
70 std::string camera_info_url;
71 std::string frame_id;
73 double fps;
83 boost::thread capture_thread;
85 sensor_msgs::CameraInfo cam_info_msg;
86 
87 // Based on the ros tutorial on transforming opencv images to Image messages
88 
89 virtual sensor_msgs::CameraInfo get_default_camera_info_from_image(sensor_msgs::ImagePtr img){
90  sensor_msgs::CameraInfo cam_info_msg;
91  cam_info_msg.header.frame_id = img->header.frame_id;
92  // Fill image size
93  cam_info_msg.height = img->height;
94  cam_info_msg.width = img->width;
95  NODELET_INFO_STREAM("The image width is: " << img->width);
96  NODELET_INFO_STREAM("The image height is: " << img->height);
97  // Add the most common distortion model as sensor_msgs/CameraInfo says
98  cam_info_msg.distortion_model = "plumb_bob";
99  // Don't let distorsion matrix be empty
100  cam_info_msg.D.resize(5, 0.0);
101  // Give a reasonable default intrinsic camera matrix
102  cam_info_msg.K = boost::assign::list_of(1.0) (0.0) (img->width/2.0)
103  (0.0) (1.0) (img->height/2.0)
104  (0.0) (0.0) (1.0);
105  // Give a reasonable default rectification matrix
106  cam_info_msg.R = boost::assign::list_of (1.0) (0.0) (0.0)
107  (0.0) (1.0) (0.0)
108  (0.0) (0.0) (1.0);
109  // Give a reasonable default projection matrix
110  cam_info_msg.P = boost::assign::list_of (1.0) (0.0) (img->width/2.0) (0.0)
111  (0.0) (1.0) (img->height/2.0) (0.0)
112  (0.0) (0.0) (1.0) (0.0);
113  return cam_info_msg;
114 }
115 
116 
117 virtual void do_capture() {
118  NODELET_DEBUG("Capture thread started");
119  cv::Mat frame;
120  ros::Rate camera_fps_rate(set_camera_fps);
121 
122  int frame_counter = 0;
123  // Read frames as fast as possible
124  capture_thread_running = true;
125  while (nh->ok() && capture_thread_running && subscriber_num > 0) {
126  if (!cap->isOpened()) {
127  NODELET_WARN("Waiting for device...");
128  cv::waitKey(100);
129  continue;
130  }
131  if (!cap->read(frame)) {
132  NODELET_ERROR("Could not capture frame");
133  if (reopen_on_read_failure) {
134  NODELET_WARN("trying to reopen the device");
135  unsubscribe();
136  subscribe();
137  }
138  }
139 
140  frame_counter++;
141  if (video_stream_provider_type == "videofile")
142  {
143  camera_fps_rate.sleep();
144  }
145  if (video_stream_provider_type == "videofile" &&
146  frame_counter == cap->get(CV_CAP_PROP_FRAME_COUNT))
147  {
148  if (loop_videofile)
149  {
150  cap->open(video_stream_provider);
151  frame_counter = 0;
152  }
153  else {
154  NODELET_INFO("Reached the end of frames");
155  break;
156  }
157  }
158 
159  if(!frame.empty()) {
160  std::lock_guard<std::mutex> g(q_mutex);
161  // accumulate only until max_queue_size
162  if (framesQueue.size() < max_queue_size) {
163  framesQueue.push(frame.clone());
164  }
165  // once reached, drop the oldest frame
166  else {
167  framesQueue.pop();
168  framesQueue.push(frame.clone());
169  }
170  }
171  }
172  NODELET_DEBUG("Capture thread finished");
173 }
174 
175 virtual void do_publish(const ros::TimerEvent& event) {
176  bool is_new_image = false;
177  sensor_msgs::ImagePtr msg;
178  std_msgs::Header header;
179  header.frame_id = frame_id;
180  {
181  std::lock_guard<std::mutex> g(q_mutex);
182  if (!framesQueue.empty()){
183  frame = framesQueue.front();
184  framesQueue.pop();
185  is_new_image = true;
186  }
187  }
188 
189  // Check if grabbed frame is actually filled with some content
190  if(!frame.empty()) {
191  // From http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#void flip(InputArray src, OutputArray dst, int flipCode)
192  // FLIP_HORIZONTAL == 1, FLIP_VERTICAL == 0 or FLIP_BOTH == -1
193  // Flip the image if necessary
194  if (is_new_image){
195  if (flip_horizontal && flip_vertical)
196  cv::flip(frame, frame, -1);
197  else if (flip_horizontal)
198  cv::flip(frame, frame, 1);
199  else if (flip_vertical)
200  cv::flip(frame, frame, 0);
201  }
202  msg = cv_bridge::CvImage(header, "bgr8", frame).toImageMsg();
203  // Create a default camera info if we didn't get a stored one on initialization
204  if (cam_info_msg.distortion_model == ""){
205  NODELET_WARN_STREAM("No calibration file given, publishing a reasonable default camera info.");
206  cam_info_msg = get_default_camera_info_from_image(msg);
207  // cam_info_manager.setCameraInfo(cam_info_msg);
208  }
209  // The timestamps are in sync thanks to this publisher
210  pub.publish(*msg, cam_info_msg, ros::Time::now());
211  }
212 }
213 
214 virtual void subscribe() {
215  ROS_DEBUG("Subscribe");
216  cap.reset(new cv::VideoCapture);
217  try {
218  int device_num = std::stoi(video_stream_provider);
219  NODELET_INFO_STREAM("Opening VideoCapture with provider: /dev/video" << device_num);
220  cap->open(device_num);
221  } catch (std::invalid_argument &ex) {
222  NODELET_INFO_STREAM("Opening VideoCapture with provider: " << video_stream_provider);
223  cap->open(video_stream_provider);
224  if (!cap->isOpened()) {
225  NODELET_FATAL_STREAM("Invalid 'video_stream_provider': " << video_stream_provider);
226  return;
227  }
228  }
229  NODELET_INFO_STREAM("Video stream provider type detected: " << video_stream_provider_type);
230 
231  double reported_camera_fps;
232  // OpenCV 2.4 returns -1 (instead of a 0 as the spec says) and prompts an error
233  // HIGHGUI ERROR: V4L2: Unable to get property <unknown property string>(5) - Invalid argument
234  reported_camera_fps = cap->get(CV_CAP_PROP_FPS);
235  if (reported_camera_fps > 0.0)
236  NODELET_INFO_STREAM("Camera reports FPS: " << reported_camera_fps);
237  else
238  NODELET_INFO_STREAM("Backend can't provide camera FPS information");
239 
240  cap->set(CV_CAP_PROP_FPS, set_camera_fps);
241  if(!cap->isOpened()){
242  NODELET_ERROR_STREAM("Could not open the stream.");
243  return;
244  }
245  if (width_target != 0 && height_target != 0){
246  cap->set(CV_CAP_PROP_FRAME_WIDTH, width_target);
247  cap->set(CV_CAP_PROP_FRAME_HEIGHT, height_target);
248  }
249 
250  try {
251  capture_thread = boost::thread(
252  boost::bind(&VideoStreamNodelet::do_capture, this));
253  publish_timer = nh->createTimer(
254  ros::Duration(1.0 / fps), &VideoStreamNodelet::do_publish, this);
255  } catch (std::exception& e) {
256  NODELET_ERROR_STREAM("Failed to start capture thread: " << e.what());
257  }
258 }
259 
260 virtual void unsubscribe() {
261  ROS_DEBUG("Unsubscribe");
262  publish_timer.stop();
263  capture_thread_running = false;
264  capture_thread.join();
265  cap.reset();
266 }
267 
268 virtual void connectionCallbackImpl() {
269  std::lock_guard<std::mutex> lock(s_mutex);
270  if (subscriber_num == 0) {
271  subscriber_num++;
272  subscribe();
273  }
274 }
275 
277  std::lock_guard<std::mutex> lock(s_mutex);
278  bool always_subscribe = false;
279  pnh->getParamCached("always_subscribe", always_subscribe);
280  if (video_stream_provider == "videofile" || always_subscribe) {
281  return;
282  }
283 
284  subscriber_num--;
285  if (subscriber_num == 0) {
286  unsubscribe();
287  }
288 }
289 
292 }
293 
296 }
297 
300 }
301 
304 }
305 
306 virtual void configCallback(VideoStreamConfig& config, uint32_t level) {
307  NODELET_DEBUG("configCallback");
308  bool need_resubscribe = false;
309 
310  if (camera_name != config.camera_name ||
311  camera_info_url != config.camera_info_url ||
312  frame_id != config.frame_id) {
313  camera_name = config.camera_name;
314  camera_info_url = config.camera_info_url;
315  frame_id = config.frame_id;
316  NODELET_INFO_STREAM("Camera name: " << camera_name);
317  NODELET_INFO_STREAM("Provided camera_info_url: '" << camera_info_url << "'");
318  NODELET_INFO_STREAM("Publishing with frame_id: " << frame_id);
319  camera_info_manager::CameraInfoManager cam_info_manager(*nh, camera_name, camera_info_url);
320  // Get the saved camera info if any
321  cam_info_msg = cam_info_manager.getCameraInfo();
322  cam_info_msg.header.frame_id = frame_id;
323  }
324 
325  if (set_camera_fps != config.set_camera_fps ||
326  fps != config.fps) {
327  if (config.fps > config.set_camera_fps) {
328  NODELET_WARN_STREAM("Asked to publish at 'fps' (" << config.fps
329  << ") which is higher than the 'set_camera_fps' (" << config.set_camera_fps <<
330  "), we can't publish faster than the camera provides images.");
331  config.fps = config.set_camera_fps;
332  }
333  set_camera_fps = config.set_camera_fps;
334  fps = config.fps;
335  NODELET_INFO_STREAM("Setting camera FPS to: " << set_camera_fps);
336  NODELET_INFO_STREAM("Throttling to fps: " << fps);
337  need_resubscribe = true;
338  }
339 
340  if (max_queue_size != config.buffer_queue_size) {
341  max_queue_size = config.buffer_queue_size;
342  NODELET_INFO_STREAM("Setting buffer size for capturing frames to: " << max_queue_size);
343  }
344 
345  if (flip_horizontal != config.flip_horizontal ||
346  flip_vertical != config.flip_vertical) {
347  flip_horizontal = config.flip_horizontal;
348  flip_vertical = config.flip_vertical;
349  NODELET_INFO_STREAM("Flip horizontal image is: " << ((flip_horizontal)?"true":"false"));
350  NODELET_INFO_STREAM("Flip vertical image is: " << ((flip_vertical)?"true":"false"));
351  }
352 
353  if (width_target != config.width ||
354  height_target != config.height) {
355  width_target = config.width;
356  height_target = config.height;
357  if (width_target != 0 && height_target != 0) {
358  NODELET_INFO_STREAM("Forced image width is: " << width_target);
359  NODELET_INFO_STREAM("Forced image height is: " << height_target);
360  }
361  need_resubscribe = true;
362  }
363 
364  if (cap && cap->isOpened()) {
365  cap->set(CV_CAP_PROP_BRIGHTNESS, config.brightness);
366  cap->set(CV_CAP_PROP_CONTRAST, config.contrast);
367  cap->set(CV_CAP_PROP_HUE, config.hue);
368  cap->set(CV_CAP_PROP_SATURATION, config.saturation);
369  if (config.auto_exposure) {
370  cap->set(CV_CAP_PROP_AUTO_EXPOSURE, 0.75);
371  config.exposure = 0.5;
372  } else {
373  cap->set(CV_CAP_PROP_AUTO_EXPOSURE, 0.25);
374  cap->set(CV_CAP_PROP_EXPOSURE, config.exposure);
375  }
376  }
377 
378  loop_videofile = config.loop_videofile;
379  reopen_on_read_failure = config.reopen_on_read_failure;
380 
381  if (subscriber_num > 0 && need_resubscribe) {
382  unsubscribe();
383  subscribe();
384  }
385 }
386 
387 virtual void onInit() {
388  nh.reset(new ros::NodeHandle(getNodeHandle()));
389  pnh.reset(new ros::NodeHandle(getPrivateNodeHandle()));
390  subscriber_num = 0;
391 
392  // 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)
393  pnh->param<std::string>("video_stream_provider", video_stream_provider, "0");
394  // check file type
395  try {
396  int device_num = std::stoi(video_stream_provider);
397  video_stream_provider_type ="videodevice";
398  } catch (std::invalid_argument &ex) {
399  if (video_stream_provider.find("http://") != std::string::npos ||
400  video_stream_provider.find("https://") != std::string::npos){
401  video_stream_provider_type = "http_stream";
402  }
403  else if(video_stream_provider.find("rtsp://") != std::string::npos){
404  video_stream_provider_type = "rtsp_stream";
405  }
406  else{
407  fs::file_type file_type = fs::status(fs::path(video_stream_provider)).type();
408  switch (file_type) {
409  case fs::file_type::character_file:
410  case fs::file_type::block_file:
411  video_stream_provider_type = "videodevice";
412  break;
413  case fs::file_type::regular_file:
414  video_stream_provider_type = "videofile";
415  break;
416  default:
417  video_stream_provider_type = "unknown";
418  }
419  }
420  }
421 
422  // set parameters from dynamic reconfigure server
423  dyn_srv = boost::make_shared<dynamic_reconfigure::Server<VideoStreamConfig> >(*pnh);
424  auto f = boost::bind(&VideoStreamNodelet::configCallback, this, _1, _2);
425  dyn_srv->setCallback(f);
426 
427  subscriber_num = 0;
429  boost::bind(&VideoStreamNodelet::connectionCallback, this, _1);
430  ros::SubscriberStatusCallback info_connect_cb =
431  boost::bind(&VideoStreamNodelet::infoConnectionCallback, this, _1);
433  boost::bind(&VideoStreamNodelet::disconnectionCallback, this, _1);
434  ros::SubscriberStatusCallback info_disconnect_cb =
435  boost::bind(&VideoStreamNodelet::infoDisconnectionCallback, this, _1);
437  "image_raw", 1,
438  connect_cb, connect_cb,
439  info_connect_cb, info_connect_cb,
440  ros::VoidPtr(), false);
441 }
442 
444  if (subscriber_num > 0)
445  subscriber_num = 0;
446  unsubscribe();
447 }
448 };
449 } // namespace
450 
#define NODELET_INFO_STREAM(...)
virtual void disconnectionCallback(const image_transport::SingleSubscriberPublisher &)
void publish(const sensor_msgs::Image &image, const sensor_msgs::CameraInfo &info) const
boost::function< void(const SingleSubscriberPublisher &)> SubscriberStatusCallback
#define NODELET_ERROR(...)
sensor_msgs::CameraInfo getCameraInfo(void)
#define NODELET_WARN(...)
f
void stop()
boost::function< void(const SingleSubscriberPublisher &)> SubscriberStatusCallback
#define NODELET_WARN_STREAM(...)
image_transport::CameraPublisher pub
ros::NodeHandle & getPrivateNodeHandle() const
#define NODELET_ERROR_STREAM(...)
boost::shared_ptr< ros::NodeHandle > nh
CameraPublisher advertiseCamera(const std::string &base_topic, uint32_t queue_size, bool latch=false)
virtual void configCallback(VideoStreamConfig &config, uint32_t level)
boost::shared_ptr< dynamic_reconfigure::Server< VideoStreamConfig > > dyn_srv
virtual sensor_msgs::CameraInfo get_default_camera_info_from_image(sensor_msgs::ImagePtr img)
boost::shared_ptr< cv::VideoCapture > cap
ros::NodeHandle & getNodeHandle() const
virtual void do_publish(const ros::TimerEvent &event)
virtual void infoDisconnectionCallback(const ros::SingleSubscriberPublisher &)
bool sleep()
virtual void connectionCallback(const image_transport::SingleSubscriberPublisher &)
#define NODELET_INFO(...)
sensor_msgs::CameraInfo cam_info_msg
#define NODELET_FATAL_STREAM(...)
static Time now()
const std::string header
boost::shared_ptr< ros::NodeHandle > pnh
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
virtual void infoConnectionCallback(const ros::SingleSubscriberPublisher &)
#define NODELET_DEBUG(...)
sensor_msgs::ImagePtr toImageMsg() const
#define ROS_DEBUG(...)


video_stream_opencv
Author(s): Sammy Pfeiffer
autogenerated on Wed Jun 19 2019 19:58:35