diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b2dbb50..24791a9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,8 @@ generate_dynamic_reconfigure_options( cfg/HLSColorFilter.cfg cfg/HSVColorFilter.cfg cfg/WatershedSegmentation.cfg + # + cfg/Sample.cfg ) ## Generate messages in the 'msg' folder @@ -342,6 +344,9 @@ endif() # https://github.com/opencv/opencv/blob/2.4/samples/cpp/video_homography.cpp # https://github.com/opencv/opencv/blob/2.4/samples/cpp/videocapture_pvapi.cpp +# SAMPLE +opencv_apps_add_nodelet(sample src/nodelet/sample_nodelet.cpp) + add_library(${PROJECT_NAME} SHARED src/nodelet/nodelet.cpp ${_opencv_apps_nodelet_cppfiles} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..42cc54cb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +Contributing to opencv_apps +=========================== + +Thanks for getting involved! We always welcome your contributions. The `opencv_apps` is the collection of image processing ROS nodes based on [OpenCV(Open Source Computer Vision Library)](https://github.com/opencv/opencv) + +How to contribute +================= + +If you find a missing feature, please find corresponding OpenCV sample program. For example, if you willing to add histogram examples, find [tutorial_code/Histograms_Matching](https://github.com/opencv/opencv/tree/master/samples/cpp/tutorial_code/Histograms_Matching). +Then look for [CMakeLists.txt](https://github.com/ros-perception/opencv_apps/blob/cbafaa05dc32495b9aa6d487cbd411a405ad14bc/CMakeLists.txt#L167) file. + +Then add source and cfg file. See [sample_nodelet](https://github.com/ros-perception/opencv_apps/blob/contrib/src/nodelet/sample_nodelet.cpp) and [Sample.cfg](https://github.com/ros-perception/opencv_apps/blob/contrib/cfg/Sample.cfg) for example. +Please see @TODO section and modify to your contributed node. +Note that all C++ code is validated by `clang-format` on [TravisCI](https://travis-ci.org/ros-perception/opencv_apps). So run following command before you commit source code. +``` +find . -name '*.h' -or -name '*.hpp' -or -name '*.cpp' | xargs clang-format-3.9 -i -style=file +``` + +Then use `opencv_apps_add_nodelet` macro to add your source code. This magic macro will generate nodelet as well as a standalone executable. +``` +opencv_apps_add_nodelet(equalize_histogram src/nodelet/equalize_histogram_nodelet.cpp) # ./tutorial_code/Histograms_Matching/EqualizeHist_Demo.cpp +``` + +Nodelet also requires to add your contributed code to [nodelet_plugins.xml](https://github.com/ros-perception/opencv_apps/blob/contrib/nodelet_plugins.xml#L118-L120). + +We also require sample launch file and test launch file. See [sample.launch](https://github.com/ros-perception/opencv_apps/blob/contrib/launch/sample.launch) and [Sample.cfg](https://github.com/ros-perception/opencv_apps/blob/contrib/test/test-sample.test) for example. Please see @TODO section and modify to your contributed node. + + diff --git a/cfg/Sample.cfg b/cfg/Sample.cfg new file mode 100755 index 00000000..cabb63c2 --- /dev/null +++ b/cfg/Sample.cfg @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +PACKAGE = "opencv_apps" + +from dynamic_reconfigure.parameter_generator_catkin import * + +gen = ParameterGenerator() + +gen.add("use_camera_info", bool_t, 0, "Indicates that the camera_info topic should be subscribed to to get the default input_frame_id. Otherwise the frame from the image message will be used.", False) + +exit(gen.generate(PACKAGE, "sample", "Sample")) diff --git a/launch/sample.launch b/launch/sample.launch new file mode 100644 index 00000000..f65a3479 --- /dev/null +++ b/launch/sample.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 8c473265..f27c7e86 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -115,6 +115,10 @@ Nodelet to demonstrate the famous watershed segmentation algorithm in OpenCV: watershed() + + Nodelet to demonstrate the simple image processing + + diff --git a/src/nodelet/sample_nodelet.cpp b/src/nodelet/sample_nodelet.cpp new file mode 100644 index 00000000..63f47762 --- /dev/null +++ b/src/nodelet/sample_nodelet.cpp @@ -0,0 +1,237 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) @TODO Please fill your name here +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* * Neither the name of the Kei Okada nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*********************************************************************/ + +// @TODO Please add link to original sample program +/** + * This is a demo of @TODO + */ + +#include +#include "opencv_apps/nodelet.h" +#include +#include +#include +#include + +#include +#include + +// @TODO Fill more header files hHere +#include + +// @TODO Note that please try to use existing opencv_apps/msg. They refrect opencv data classes. +#include "opencv_apps/Point2DStamped.h" + +// +#include "opencv_apps/SampleConfig.h" +#include "opencv_apps/nodelet.h" + +namespace opencv_apps +{ +class SampleNodelet : public opencv_apps::Nodelet +{ + image_transport::Publisher img_pub_; + image_transport::Subscriber img_sub_; + image_transport::CameraSubscriber cam_sub_; + ros::Publisher msg_pub_; + + boost::shared_ptr it_; + + typedef opencv_apps::SampleConfig Config; + typedef dynamic_reconfigure::Server ReconfigureServer; + Config config_; + boost::shared_ptr reconfigure_server_; + + int queue_size_; + bool debug_view_; + ros::Time prev_stamp_; + + std::string window_name_; + static bool need_config_update_; + + bool needToInit; + + // @TODO Fill more vairables here + static cv::Point2f point_; + + // @TODO Define callback functions as static + static void onMouse(int event, int x, int y, int /*flags*/, void* /*param*/) + { + ROS_INFO("onMouse %d %d %d", event, x, y); // @TODO static funcion needs ROS_INFO, but others can use NODELET_INFO + if (event == CV_EVENT_LBUTTONDOWN) + { + point_ = cv::Point2f((float)x, (float)y); + } + } + + void reconfigureCallback(Config& new_config, uint32_t level) + { + config_ = new_config; + // @TODO Fill your code here + } + + void imageCallbackWithInfo(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cam_info) + { + doWork(msg, cam_info->header.frame_id); + } + + void imageCallback(const sensor_msgs::ImageConstPtr& msg) + { + doWork(msg, msg->header.frame_id); + } + + void doWork(const sensor_msgs::ImageConstPtr& msg, const std::string& input_frame_from_msg) + { + // Work on the image. + try + { + // Convert the image into something opencv can handle. + cv::Mat image = cv_bridge::toCvShare(msg, sensor_msgs::image_encodings::BGR8)->image; + + // Messages + opencv_apps::Point2DStamped output_msg; + output_msg.header = msg->header; + + if (debug_view_) + { + // Create windows + cv::namedWindow(window_name_, cv::WINDOW_AUTOSIZE); + + // @TODO Create other GUI tools + cv::setMouseCallback(window_name_, onMouse, 0); + + if (need_config_update_) + { + reconfigure_server_->updateConfig(config_); + // @TODO Update parameters + need_config_update_ = false; + } + } + + // Do the work + if (needToInit) + { + // @TODO Do initial process + } + + // @TODO Do the work + output_msg.point.x = point_.x; + output_msg.point.y = point_.y; + + needToInit = false; + if (debug_view_) + { + cv::imshow(window_name_, image); + + char c = (char)cv::waitKey(1); + // if( c == 27 ) + // break; + switch (c) + { + case 'r': + needToInit = true; + break; + } + } + + // Publish the image. + sensor_msgs::Image::Ptr out_img = cv_bridge::CvImage(msg->header, msg->encoding, image).toImageMsg(); + img_pub_.publish(out_img); + msg_pub_.publish(output_msg); + } + catch (cv::Exception& e) + { + NODELET_ERROR("Image processing error: %s %s %s %i", e.err.c_str(), e.func.c_str(), e.file.c_str(), e.line); + } + + prev_stamp_ = msg->header.stamp; + } + + void subscribe() // NOLINT(modernize-use-override) + { + NODELET_DEBUG("Subscribing to image topic."); + if (config_.use_camera_info) + cam_sub_ = it_->subscribeCamera("image", queue_size_, &SampleNodelet::imageCallbackWithInfo, this); + else + img_sub_ = it_->subscribe("image", queue_size_, &SampleNodelet::imageCallback, this); + } + + void unsubscribe() // NOLINT(modernize-use-override) + { + NODELET_DEBUG("Unsubscribing from image topic."); + img_sub_.shutdown(); + cam_sub_.shutdown(); + } + +public: + virtual void onInit() // NOLINT(modernize-use-override) + { + Nodelet::onInit(); + it_ = boost::shared_ptr(new image_transport::ImageTransport(*nh_)); + + pnh_->param("queue_size", queue_size_, 3); + pnh_->param("debug_view", debug_view_, false); + if (debug_view_) + { + always_subscribe_ = true; + } + prev_stamp_ = ros::Time(0, 0); + + window_name_ = "Sample"; // @TODO Add program name + needToInit = true; + + reconfigure_server_ = boost::make_shared >(*pnh_); + dynamic_reconfigure::Server::CallbackType f = + boost::bind(&SampleNodelet::reconfigureCallback, this, _1, _2); + reconfigure_server_->setCallback(f); + + img_pub_ = advertiseImage(*pnh_, "image", 1); + msg_pub_ = advertise(*pnh_, "output", 1); + // @TODO add more advertise/services + + NODELET_INFO("Hot keys: "); + NODELET_INFO("\tESC - quit the program"); + NODELET_INFO("\tr - auto-initialize tracking"); + // @ TODO Add more messages + + onInitPostProcess(); + } +}; +bool SampleNodelet::need_config_update_ = false; +// @TODO static variable must initialized here +cv::Point2f SampleNodelet::point_ = cv::Point2f(0, 0); +} // namespace opencv_apps + +#include +PLUGINLIB_EXPORT_CLASS(opencv_apps::SampleNodelet, nodelet::Nodelet); diff --git a/test/test-sample.test b/test/test-sample.test new file mode 100644 index 00000000..be79f700 --- /dev/null +++ b/test/test-sample.test @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +