diff --git a/CMakeLists.txt b/CMakeLists.txt index e4f8f6ff..4bdd4a53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -263,7 +263,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 62f76e3d..9620bb73 100644 --- a/nodelet_plugins.xml +++ b/nodelet_plugins.xml @@ -127,6 +127,10 @@ Nodelet for histogram equalization + + 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);