From a23d33f08e2aad38d6e6aede4b1dc2c419cd89fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Tue, 14 Apr 2020 15:27:30 +0200 Subject: [PATCH 1/4] Add the intensity histogram app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël Écorchard --- CMakeLists.txt | 2 +- launch/intensity_histogram.launch | 16 ++ nodelet_plugins.xml | 4 + src/nodelet/intensity_histogram.cpp | 221 ++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 launch/intensity_histogram.launch create mode 100644 src/nodelet/intensity_histogram.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 852001b1..28fb4057 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,7 +261,7 @@ opencv_apps_add_nodelet(camshift src/nodelet/camshift_nodelet.cpp) # ./camshiftd # ./create_mask.cpp # ./dbt_face_detection.cpp # ./delaunay2.cpp -# ./demhist.cpp +opencv_apps_add_nodelet(intensity_histogram src/nodelet/intensity_histogram.cpp) # ./demhist.cpp # ./detect_blob.cpp # ./detect_mser.cpp # ./dft.cpp diff --git a/launch/intensity_histogram.launch b/launch/intensity_histogram.launch new file mode 100644 index 00000000..9eb8185c --- /dev/null +++ b/launch/intensity_histogram.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/nodelet_plugins.xml b/nodelet_plugins.xml index 8c473265..67719a92 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 of intensity histogram + + diff --git a/src/nodelet/intensity_histogram.cpp b/src/nodelet/intensity_histogram.cpp new file mode 100644 index 00000000..b8f0df91 --- /dev/null +++ b/src/nodelet/intensity_histogram.cpp @@ -0,0 +1,221 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2020, Gaël Écorchard, Czech Technical University. +* 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 Gaël Écorchard 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. +*********************************************************************/ +/** + * Compute the histogram of intensities and publish it as an image. + */ + +#include + +#include +//#include +#include +#include +#include +#include +#include +#include +#include + +namespace opencv_apps +{ + +namespace intensity_histogram +{ + +static const std::string OPENCV_WINDOW = "Image histogram"; + +cv::Mat grayHistogram(cv_bridge::CvImageConstPtr img) +{ + /* Inspired by https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp. */ + constexpr bool uniform = true; + constexpr bool accumulate = false; + constexpr int bin_count = 256; + + float range[] = {0, 256}; //the upper boundary is exclusive + const float* hist_range = {range}; + cv::Mat intensity_hist; + cv::calcHist(&img->image, 1, 0, cv::Mat(), intensity_hist, 1, &bin_count, &hist_range, uniform, accumulate); + int hist_w = 512; + int hist_h = 400; + int bin_w = cvRound(static_cast(hist_w / bin_count)); + cv::Mat hist_image(hist_h, hist_w, CV_8UC1, cv::Scalar(0)); + cv::normalize(intensity_hist, intensity_hist, 0, hist_image.rows, cv::NORM_MINMAX, -1, cv::Mat()); + for (unsigned int i = 1; i < bin_count; i++) + { + cv::line(hist_image, + cv::Point(bin_w * (i-1), hist_h - cvRound(intensity_hist.at(i-1))), + cv::Point(bin_w * (i), hist_h - cvRound(intensity_hist.at(i))), + cv::Scalar(255), 2, 8, 0); + } + return hist_image; +} + +cv::Mat bgrHistogram(cv_bridge::CvImageConstPtr img) +{ + /* Inspired by https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp. */ + constexpr bool uniform = true; + constexpr bool accumulate = false; + constexpr int bin_count = 256; + + std::vector bgr_planes; + cv::split(img->image, bgr_planes); + + float range[] = {0, 256}; //the upper boundary is exclusive + const float* hist_range = {range}; + cv::Mat b_hist; + cv::Mat g_hist; + cv::Mat r_hist; + cv::calcHist(&bgr_planes[0], 1, 0, cv::Mat(), b_hist, 1, &bin_count, &hist_range, uniform, accumulate); + cv::calcHist(&bgr_planes[1], 1, 0, cv::Mat(), g_hist, 1, &bin_count, &hist_range, uniform, accumulate); + cv::calcHist(&bgr_planes[2], 1, 0, cv::Mat(), r_hist, 1, &bin_count, &hist_range, uniform, accumulate); + int hist_w = 512; + int hist_h = 400; + int bin_w = cvRound(static_cast(hist_w / bin_count)); + cv::Mat hist_image(hist_h, hist_w, CV_8UC3, cv::Scalar(0, 0, 0)); + cv::normalize(b_hist, b_hist, 0, hist_image.rows, cv::NORM_MINMAX, -1, cv::Mat()); + cv::normalize(g_hist, g_hist, 0, hist_image.rows, cv::NORM_MINMAX, -1, cv::Mat()); + cv::normalize(r_hist, r_hist, 0, hist_image.rows, cv::NORM_MINMAX, -1, cv::Mat()); + for (unsigned int i = 1; i < bin_count; i++) + { + cv::line(hist_image, + cv::Point(bin_w * (i-1), hist_h - cvRound(b_hist.at(i-1))), + cv::Point(bin_w * (i), hist_h - cvRound(b_hist.at(i))), + cv::Scalar(255, 0, 0), 2, 8, 0); + cv::line(hist_image, + cv::Point(bin_w * (i-1), hist_h - cvRound(g_hist.at(i-1))), + cv::Point(bin_w * (i), hist_h - cvRound(g_hist.at(i))), + cv::Scalar(0, 255, 0), 2, 8, 0); + cv::line(hist_image, + cv::Point(bin_w * (i-1), hist_h - cvRound(r_hist.at(i-1))), + cv::Point(bin_w * (i), hist_h - cvRound(r_hist.at(i))), + cv::Scalar(0, 0, 255), 2, 8, 0); + } + return hist_image; +} + +class IntensityHistogram +{ + + public: + + IntensityHistogram() : + it_(nh_) + { + image_sub_ = it_.subscribe("image", queue_size_, &IntensityHistogram::imageCallback, this); + hist_pub_ = it_.advertise("image_hist", 1); + + ros::NodeHandle private_nh("~"); + private_nh.param("queue_size", queue_size_, 1); + private_nh.param("debug_view", debug_view_, false); + if (debug_view_) + { + cv::namedWindow(OPENCV_WINDOW); + } + } + + ~IntensityHistogram() + { + if (debug_view_) + { + cv::destroyWindow(OPENCV_WINDOW); + } + } + + void imageCallback(const sensor_msgs::ImageConstPtr& msg) + { + cv_bridge::CvImageConstPtr cv_ptr; + try + { + cv_ptr = cv_bridge::toCvShare(msg); + } + catch (cv_bridge::Exception& e) + { + ROS_ERROR("cv_bridge exception: %s", e.what()); + return; + } + + cv::Mat out_img; + sensor_msgs::Image::Ptr out_img_msg; + if (cv_ptr->image.channels() == 1) + { + out_img = grayHistogram(cv_ptr); + out_img_msg = cv_bridge::CvImage( + msg->header, sensor_msgs::image_encodings::MONO8, out_img).toImageMsg(); + + } + else + { + out_img = bgrHistogram(cv_ptr); + out_img_msg = cv_bridge::CvImage( + msg->header, sensor_msgs::image_encodings::RGB8, out_img).toImageMsg(); + } + + if (debug_view_) + { + // Update GUI Window + cv::imshow(OPENCV_WINDOW, out_img); + cv::waitKey(3); + } + + hist_pub_.publish(out_img_msg); + } + + private: + + ros::NodeHandle nh_; + image_transport::ImageTransport it_; + image_transport::Subscriber image_sub_; + image_transport::Publisher hist_pub_; + int queue_size_; + bool debug_view_; +}; + +} /* namespace intensity_histogram. */ + +class IntensityHistogramNodelet : public nodelet::Nodelet +{ + + public: + + virtual void onInit() // NOLINT(modernize-use-override) + { + intensity_histogram::IntensityHistogram ih; + ros::spin(); + } +}; + +} /* namespace opencv_apps. */ + +#include +PLUGINLIB_EXPORT_CLASS(opencv_apps::IntensityHistogramNodelet, nodelet::Nodelet); From bbab4828869473838d9095704f61d79862222fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Wed, 15 Apr 2020 14:46:42 +0200 Subject: [PATCH 2/4] Try to satisfy clang-format and clang-tidy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gaël Écorchard --- src/nodelet/intensity_histogram.cpp | 177 ++++++++++++++-------------- 1 file changed, 90 insertions(+), 87 deletions(-) diff --git a/src/nodelet/intensity_histogram.cpp b/src/nodelet/intensity_histogram.cpp index b8f0df91..567aa697 100644 --- a/src/nodelet/intensity_histogram.cpp +++ b/src/nodelet/intensity_histogram.cpp @@ -55,17 +55,18 @@ namespace intensity_histogram static const std::string OPENCV_WINDOW = "Image histogram"; -cv::Mat grayHistogram(cv_bridge::CvImageConstPtr img) +cv::Mat grayHistogram(const cv_bridge::CvImageConstPtr& img) { - /* Inspired by https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp. */ + /* Inspired by + * https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp. */ constexpr bool uniform = true; constexpr bool accumulate = false; constexpr int bin_count = 256; - float range[] = {0, 256}; //the upper boundary is exclusive - const float* hist_range = {range}; + float range[] = { 0, 256 }; //the upper boundary is exclusive + const float* hist_range = { range }; cv::Mat intensity_hist; - cv::calcHist(&img->image, 1, 0, cv::Mat(), intensity_hist, 1, &bin_count, &hist_range, uniform, accumulate); + cv::calcHist(&img->image, 1, nullptr, cv::Mat(), intensity_hist, 1, &bin_count, &hist_range, uniform, accumulate); int hist_w = 512; int hist_h = 400; int bin_w = cvRound(static_cast(hist_w / bin_count)); @@ -74,16 +75,17 @@ cv::Mat grayHistogram(cv_bridge::CvImageConstPtr img) for (unsigned int i = 1; i < bin_count; i++) { cv::line(hist_image, - cv::Point(bin_w * (i-1), hist_h - cvRound(intensity_hist.at(i-1))), - cv::Point(bin_w * (i), hist_h - cvRound(intensity_hist.at(i))), + cv::Point(bin_w * (i - 1), hist_h - cvRound(intensity_hist.at(i - 1))), + cv::Point(bin_w * i, hist_h - cvRound(intensity_hist.at(i))), cv::Scalar(255), 2, 8, 0); } return hist_image; } -cv::Mat bgrHistogram(cv_bridge::CvImageConstPtr img) +cv::Mat bgrHistogram(const cv_bridge::CvImageConstPtr& img) { - /* Inspired by https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp. */ + /* Inspired by + * https://github.com/opencv/opencv/tree/3.4/samples/cpp/tutorial_code/Histograms_Matching/calcHist_Demo.cpp. */ constexpr bool uniform = true; constexpr bool accumulate = false; constexpr int bin_count = 256; @@ -96,9 +98,9 @@ cv::Mat bgrHistogram(cv_bridge::CvImageConstPtr img) cv::Mat b_hist; cv::Mat g_hist; cv::Mat r_hist; - cv::calcHist(&bgr_planes[0], 1, 0, cv::Mat(), b_hist, 1, &bin_count, &hist_range, uniform, accumulate); - cv::calcHist(&bgr_planes[1], 1, 0, cv::Mat(), g_hist, 1, &bin_count, &hist_range, uniform, accumulate); - cv::calcHist(&bgr_planes[2], 1, 0, cv::Mat(), r_hist, 1, &bin_count, &hist_range, uniform, accumulate); + cv::calcHist(&bgr_planes[0], 1, nullptr, cv::Mat(), b_hist, 1, &bin_count, &hist_range, uniform, accumulate); + cv::calcHist(&bgr_planes[1], 1, nullptr, cv::Mat(), g_hist, 1, &bin_count, &hist_range, uniform, accumulate); + cv::calcHist(&bgr_planes[2], 1, nullptr, cv::Mat(), r_hist, 1, &bin_count, &hist_range, uniform, accumulate); int hist_w = 512; int hist_h = 400; int bin_w = cvRound(static_cast(hist_w / bin_count)); @@ -109,16 +111,16 @@ cv::Mat bgrHistogram(cv_bridge::CvImageConstPtr img) for (unsigned int i = 1; i < bin_count; i++) { cv::line(hist_image, - cv::Point(bin_w * (i-1), hist_h - cvRound(b_hist.at(i-1))), - cv::Point(bin_w * (i), hist_h - cvRound(b_hist.at(i))), + cv::Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at(i - 1))), + cv::Point(bin_w * i, hist_h - cvRound(b_hist.at(i))), cv::Scalar(255, 0, 0), 2, 8, 0); cv::line(hist_image, - cv::Point(bin_w * (i-1), hist_h - cvRound(g_hist.at(i-1))), - cv::Point(bin_w * (i), hist_h - cvRound(g_hist.at(i))), + cv::Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at(i - 1))), + cv::Point(bin_w * i, hist_h - cvRound(g_hist.at(i))), cv::Scalar(0, 255, 0), 2, 8, 0); cv::line(hist_image, - cv::Point(bin_w * (i-1), hist_h - cvRound(r_hist.at(i-1))), - cv::Point(bin_w * (i), hist_h - cvRound(r_hist.at(i))), + cv::Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at(i - 1))), + cv::Point(bin_w * i, hist_h - cvRound(r_hist.at(i))), cv::Scalar(0, 0, 255), 2, 8, 0); } return hist_image; @@ -127,95 +129,96 @@ cv::Mat bgrHistogram(cv_bridge::CvImageConstPtr img) class IntensityHistogram { - public: +public: + + IntensityHistogram() : + it_(nh_) + { + ros::NodeHandle private_nh("~"); + private_nh.param("queue_size", queue_size_, 1); + private_nh.param("debug_view", debug_view_, false); + + image_sub_ = it_.subscribe("image", queue_size_, &IntensityHistogram::imageCallback, this); + hist_pub_ = it_.advertise("image_hist", queue_size_); + + if (debug_view_) + { + cv::namedWindow(OPENCV_WINDOW); + } + } + + ~IntensityHistogram() + { + if (debug_view_) + { + cv::destroyWindow(OPENCV_WINDOW); + } + } - IntensityHistogram() : - it_(nh_) + void imageCallback(const sensor_msgs::ImageConstPtr& msg) + { + cv_bridge::CvImageConstPtr cv_ptr; + try + { + cv_ptr = cv_bridge::toCvShare(msg); + } + catch (cv_bridge::Exception& e) { - image_sub_ = it_.subscribe("image", queue_size_, &IntensityHistogram::imageCallback, this); - hist_pub_ = it_.advertise("image_hist", 1); - - ros::NodeHandle private_nh("~"); - private_nh.param("queue_size", queue_size_, 1); - private_nh.param("debug_view", debug_view_, false); - if (debug_view_) - { - cv::namedWindow(OPENCV_WINDOW); - } + ROS_ERROR("cv_bridge exception: %s", e.what()); + return; } - ~IntensityHistogram() + cv::Mat out_img; + sensor_msgs::Image::Ptr out_img_msg; + if (cv_ptr->image.channels() == 1) + { + out_img = grayHistogram(cv_ptr); + out_img_msg = cv_bridge::CvImage( + msg->header, sensor_msgs::image_encodings::MONO8, out_img).toImageMsg(); + + } + else { - if (debug_view_) - { - cv::destroyWindow(OPENCV_WINDOW); - } + out_img = bgrHistogram(cv_ptr); + out_img_msg = cv_bridge::CvImage( + msg->header, sensor_msgs::image_encodings::RGB8, out_img).toImageMsg(); } - void imageCallback(const sensor_msgs::ImageConstPtr& msg) + if (debug_view_) { - cv_bridge::CvImageConstPtr cv_ptr; - try - { - cv_ptr = cv_bridge::toCvShare(msg); - } - catch (cv_bridge::Exception& e) - { - ROS_ERROR("cv_bridge exception: %s", e.what()); - return; - } - - cv::Mat out_img; - sensor_msgs::Image::Ptr out_img_msg; - if (cv_ptr->image.channels() == 1) - { - out_img = grayHistogram(cv_ptr); - out_img_msg = cv_bridge::CvImage( - msg->header, sensor_msgs::image_encodings::MONO8, out_img).toImageMsg(); - - } - else - { - out_img = bgrHistogram(cv_ptr); - out_img_msg = cv_bridge::CvImage( - msg->header, sensor_msgs::image_encodings::RGB8, out_img).toImageMsg(); - } - - if (debug_view_) - { - // Update GUI Window - cv::imshow(OPENCV_WINDOW, out_img); - cv::waitKey(3); - } - - hist_pub_.publish(out_img_msg); + // Update GUI Window + cv::imshow(OPENCV_WINDOW, out_img); + cv::waitKey(3); } - private: + hist_pub_.publish(out_img_msg); + } + +private: - ros::NodeHandle nh_; - image_transport::ImageTransport it_; - image_transport::Subscriber image_sub_; - image_transport::Publisher hist_pub_; - int queue_size_; - bool debug_view_; + ros::NodeHandle nh_; + image_transport::ImageTransport it_; + image_transport::Subscriber image_sub_; + image_transport::Publisher hist_pub_; + int queue_size_; + bool debug_view_; }; -} /* namespace intensity_histogram. */ +} /* namespace intensity_histogram. */ class IntensityHistogramNodelet : public nodelet::Nodelet { - public: +public: - virtual void onInit() // NOLINT(modernize-use-override) - { - intensity_histogram::IntensityHistogram ih; - ros::spin(); - } + virtual void onInit() // NOLINT(modernize-use-override) + { + intensity_histogram::IntensityHistogram ih; + ros::spin(); + } }; -} /* namespace opencv_apps. */ +} /* namespace opencv_apps. */ #include PLUGINLIB_EXPORT_CLASS(opencv_apps::IntensityHistogramNodelet, nodelet::Nodelet); From d2f53b6d32154a4992109bd78a68cf8812818c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Wed, 15 Apr 2020 14:53:01 +0200 Subject: [PATCH 3/4] Try to satisfy catkin_lint --- CMakeLists.txt | 2 +- package.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28fb4057..b9987c36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ generate_messages( std_msgs ) -catkin_package(CATKIN_DEPENDS message_runtime sensor_msgs std_msgs +catkin_package(CATKIN_DEPENDS dynamic_reconfigure message_runtime nodelet roscpp sensor_msgs std_msgs std_srvs # DEPENDS OpenCV INCLUDE_DIRS include LIBRARIES ${PROJECT_NAME} diff --git a/package.xml b/package.xml index ea68f4f3..97cc43e5 100644 --- a/package.xml +++ b/package.xml @@ -31,6 +31,7 @@ cv_bridge dynamic_reconfigure image_transport + image_view message_runtime nodelet roscpp From 7c748b80b85e0f5b78a45eb4430d21d71fedb556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20=C3=89corchard?= Date: Wed, 15 Apr 2020 14:57:35 +0200 Subject: [PATCH 4/4] Remove redundant test_depend to image_view --- package.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/package.xml b/package.xml index 97cc43e5..92041faa 100644 --- a/package.xml +++ b/package.xml @@ -45,7 +45,6 @@ rosservice rostopic image_proc - image_view topic_tools compressed_image_transport