replace_colors_nodelet.cpp
Go to the documentation of this file.
00001 // *****************************************************************************
00002 //
00003 // Copyright (c) 2017, Southwest Research Institute® (SwRI®)
00004 // All rights reserved.
00005 //
00006 // Redistribution and use in source and binary forms, with or without
00007 // modification, are permitted provided that the following conditions are met:
00008 //     * Redistributions of source code must retain the above copyright
00009 //       notice, this list of conditions and the following disclaimer.
00010 //     * Redistributions in binary form must reproduce the above copyright
00011 //       notice, this list of conditions and the following disclaimer in the
00012 //       documentation and/or other materials provided with the distribution.
00013 //     * Neither the name of Southwest Research Institute® (SwRI®) nor the
00014 //       names of its contributors may be used to endorse or promote products
00015 //       derived from this software without specific prior written permission.
00016 //
00017 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020 // ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
00021 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00022 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00023 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00024 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00025 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00026 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00027 //
00028 // *****************************************************************************
00029 #include <opencv2/core/version.hpp>
00030 #if CV_MAJOR_VERSION == 2
00031 #include <opencv2/contrib/contrib.hpp>
00032 #endif
00033 #include <opencv2/imgproc/imgproc.hpp>
00034 #include <ros/ros.h>
00035 #include <image_transport/image_transport.h>
00036 #include <cv_bridge/cv_bridge.h>
00037 #include <nodelet/nodelet.h>
00038 #include <sensor_msgs/Image.h>
00039 #include <sensor_msgs/image_encodings.h>
00040 #include <swri_image_util/replace_colors.h>
00041 
00042 namespace swri_image_util
00043 {
00044   // This constant defines how large our lookup and transform tables are.
00045   // Currently assumes 8 bit mono encoded images, so there are 256 gray colors
00046   // to potentially replace with a different color
00047   const int32_t NUM_GRAY_VALUES = 256;
00048   // The output is an RGB8 image. This constant checks that the user passes
00049   // in a valid RGB value to replace a gray level with
00050   const int32_t MAX_RGB_VALUE = 255;
00051 
00052   // ROS nodelet for replacing colors in an image
00053   class ReplaceColorsNodelet : public nodelet::Nodelet
00054   {
00055   public:
00056     ReplaceColorsNodelet();
00057     ~ReplaceColorsNodelet();
00058   
00059   private:
00060     // Does the actual initialization since this is a nodelet
00061     virtual void onInit();
00062     // Callback for the input image
00063     void imageCallback(const sensor_msgs::ImageConstPtr& image_msg);
00064     // Initialize the lookup table
00065     void initLut();
00066     // Read in user requested colormap
00067     void readColormap(const XmlRpc::XmlRpcValue& param);
00068     // Helper function for getting color mapping from parameter server
00069     void readUserLut(const XmlRpc::XmlRpcValue& param);
00070     // Helper function for displaying the type of parameter that was read
00071     std::string getValueTypeString(const XmlRpc::XmlRpcValue& value);
00072 
00073     // Lookup table defining color replacement strategy. The row indices 
00074     // correspond to the gray scale values, and the values in the rows are RGB
00075     // values to replace the gray scale values with
00076     cv::Mat color_lut_;
00077     // Publishes the modified image
00078     image_transport::Publisher image_pub_;
00079     // Subscribes to the original image
00080     image_transport::Subscriber image_sub_;
00081     // Mapping from a colormap name to the OpenCV integer representation
00082     std::map<std::string, int32_t> colormap_names_;
00083   };
00084 
00085   ReplaceColorsNodelet::ReplaceColorsNodelet()
00086   {
00087     // Initialize the colormap name mapping. Every OpenCV colormap should have
00088     // a string identifiying it. This allows the node to easily take a user
00089     // parameter and convert it to a representation the algorithm can use.
00090     // OpenCV 2.x does not have the Parula colormap, so only include this
00091     // when OpenCV 3.x is available
00092     colormap_names_["autumn"] = cv::COLORMAP_AUTUMN;
00093     colormap_names_["bone"] = cv::COLORMAP_BONE;
00094     colormap_names_["jet"] = cv::COLORMAP_JET;
00095     colormap_names_["winter"] = cv::COLORMAP_WINTER;
00096     colormap_names_["rainbow"] = cv::COLORMAP_RAINBOW;
00097     colormap_names_["ocean"] = cv::COLORMAP_OCEAN;
00098     colormap_names_["summer"] = cv::COLORMAP_SUMMER;
00099     colormap_names_["spring"] = cv::COLORMAP_SPRING;
00100     colormap_names_["cool"] = cv::COLORMAP_COOL;
00101     colormap_names_["hsv"] = cv::COLORMAP_HSV;
00102     colormap_names_["pink"] = cv::COLORMAP_PINK;
00103     colormap_names_["hot"] = cv::COLORMAP_HOT;
00104     #if CV_MAJOR_VERSION == 3
00105     colormap_names_["parula"] = cv::COLORMAP_PARULA;
00106     #endif
00107   }
00108 
00109   ReplaceColorsNodelet::~ReplaceColorsNodelet()
00110   {
00111     // Nothing to clean up on destruction
00112   }
00113 
00114   // Initialize the nodelet
00115   void ReplaceColorsNodelet::onInit()
00116   {
00117     // Node handles for interacting with ROS
00118     ros::NodeHandle& nh = getNodeHandle();
00119     ros::NodeHandle& priv_nh  = getPrivateNodeHandle();
00120 
00121     // Lookup table to replace colors with. By default will just convert the
00122     // gray scale values to their RGB equivalents. If this node is ever extended
00123     // to more than gray scale, this will have to be changed
00124     color_lut_ = cv::Mat::zeros(1, NUM_GRAY_VALUES, CV_8UC3);
00125     initLut();
00126 
00127     // Get the array representing the color mapping from the parameter server.
00128     // This will be in the format [[u0, [r0, g0, b0]], [u1, [r1, g1, b1]]]
00129     // where u is the gray scale value to replace, and r, g, b are the RGB
00130     // components to replace it with.
00131     XmlRpc::XmlRpcValue color_param;
00132 
00133     // This node has two different methods of changing gray scale values to
00134     // color imagery. The first maps the gray scale values to OpenCV colormaps
00135     // This call checks for that option
00136     bool colormap_specified = priv_nh.getParam("colormap", color_param);
00137     if (colormap_specified)
00138     {
00139       //  Use the colormap the user has requested
00140       readColormap(color_param);
00141     }
00142 
00143     // The other option for modifying the grayscale images is to define a
00144     // lookup table that maps grayscale levels to user defined RGB values.
00145     // This can be used in conjunction with the colormap option to replace
00146     // values in the OpenCV colormap with user values. This call checks
00147     // if the user is attempting this operation, and reads the lookup table
00148     // from the parameter server if necessary 
00149     if (priv_nh.getParam("colors", color_param))
00150     {
00151       // Try to read in the lookup values from the parameter server.
00152       readUserLut(color_param);
00153     }
00154     else if (!colormap_specified)
00155     {
00156       ROS_ERROR("Color transformation was not specified. Images will ");
00157       ROS_ERROR("only be converted to their gray scale equivalents");
00158     }
00159 
00160     // Set up the ROS interface
00161     image_transport::ImageTransport it(nh);
00162     image_pub_ = it.advertise("modified_image", 1);
00163     image_sub_ = it.subscribe(
00164       "image",
00165       1,
00166       &ReplaceColorsNodelet::imageCallback,
00167       this);
00168   }
00169 
00170   // Callback for getting the input image to change the colors on
00171   void ReplaceColorsNodelet::imageCallback(
00172     const sensor_msgs::ImageConstPtr& image_msg)
00173   {
00174     // Only do the color conversion if someone is subscribing to the data
00175     if (image_pub_.getNumSubscribers() == 0)
00176     {
00177       return;
00178     }
00179 
00180     // This node currently only supports changing gray scale images. Display an
00181     // error message if this is not the case, but limit the error reporting rate
00182     // to once every minute avoid spamming the user with redundant warnings
00183     if (image_msg->encoding != sensor_msgs::image_encodings::MONO8)
00184     {
00185       ROS_ERROR_THROTTLE(60, 
00186         "Changing image colors is only supported for MONO8 images");
00187       return;
00188     }
00189 
00190     // Convert image data from ROS to OpenCV type
00191     cv_bridge::CvImageConstPtr original_image = cv_bridge::toCvShare(image_msg);
00192 
00193     // Allocate space for the modified image
00194     cv::Mat modified_image = cv::Mat::zeros(
00195       original_image->image.rows,
00196       original_image->image.cols,
00197       CV_8UC3);
00198 
00199     // Do the actual color replacement
00200     swri_image_util::replaceColors(
00201       original_image->image,
00202       color_lut_,
00203       modified_image);
00204 
00205     // Copy results to output message and set up the header and encoding values
00206     cv_bridge::CvImagePtr output = boost::make_shared<cv_bridge::CvImage>();
00207     output->image = modified_image;
00208     output->encoding = sensor_msgs::image_encodings::RGB8;
00209     output->header = image_msg->header;
00210 
00211     // Publish the modified image to the rest of the system
00212     image_pub_.publish(output->toImageMsg());
00213   }
00214 
00215   // Initialize grayscale lookup table
00216   void ReplaceColorsNodelet::initLut()
00217   {
00218     // Sets every row in the lookup table to a triple <x, x, x>,
00219     // where x is the grayscale value. This will directly map
00220     // the gray values to their equivalent RGB representation
00221     for (uint32_t idx; idx < NUM_GRAY_VALUES; idx++)
00222     {
00223       color_lut_.at<cv::Vec3b>(0, idx) = cv::Vec3b(idx, idx, idx);
00224     }
00225   }
00226 
00227   // Read in the colormap and parameters the parameter server has for this
00228   // node
00229   void ReplaceColorsNodelet::readColormap(const XmlRpc::XmlRpcValue& param)
00230   {
00231     // This is a multistep process, and if any step goes wrong then the rest
00232     // of the process should be aborted.
00233     bool success = true;
00234 
00235     // The colormap parameters should be formatted like:
00236     // ["colormap_name", num_colors]
00237     // This checks to make sure the parameter is a two element array
00238     if ((param.getType() != XmlRpc::XmlRpcValue::TypeArray) ||
00239       (param.size() != 2))
00240     {
00241       ROS_ERROR("Colormap specification must be a two entry array with the");
00242       ROS_ERROR("Following format:");
00243       ROS_ERROR("\t[\"colormap_name\", num_colors]");
00244       success = false;
00245     }
00246 
00247     // These are the parameters to read from the parameter server
00248     std::string colormap_name;
00249     int32_t num_entries;
00250     int32_t colormap_idx;
00251 
00252     if (success)
00253     {
00254       // Get the XmpRpc representation of the colormap name the number of
00255       // colors to get from that colormap
00256       XmlRpc::XmlRpcValue colormap_param = param[0];
00257       XmlRpc::XmlRpcValue num_entries_param = param[1];
00258 
00259       // Make sure the colormap name is a string
00260       if (colormap_param.getType() != XmlRpc::XmlRpcValue::TypeString)
00261       {
00262         ROS_ERROR("First colormap parameter must be a string");
00263         success = false;
00264       }
00265 
00266       // Make sure the number of colors to get from the colormap is
00267       // an integer
00268       if (num_entries_param.getType() != XmlRpc::XmlRpcValue::TypeInt)
00269       {
00270         ROS_ERROR("Second colormap parameter must be an integer");
00271         success = false;
00272       }
00273 
00274       // Parameters had the correct types. Now XmlRpc datatypes to C++ types
00275       if (success)
00276       {
00277         colormap_name = static_cast<std::string>(colormap_param);
00278         num_entries = static_cast<int32_t>(num_entries_param);
00279       }
00280     }
00281 
00282     // Sanity check on the number of classes the user specified
00283     if (success && (num_entries <= 1))
00284     {
00285       ROS_ERROR("Must use at least two colors from the colormap");
00286       success = false;
00287     }
00288 
00289     // Make sure the number of values from the colormap is at most the number
00290     // of grayscale values in our transformation
00291     if (success)
00292     {
00293       if (num_entries > NUM_GRAY_VALUES)
00294       {
00295         ROS_ERROR("Number of colormap entries was greater");
00296         ROS_ERROR(" than %d", NUM_GRAY_VALUES);
00297         success = false;
00298       }
00299     }
00300 
00301     // Get the OpenCV representation of the requested colormap
00302     if (success)
00303     {
00304       std::map<std::string, int32_t>::iterator iter = 
00305         colormap_names_.find(colormap_name);
00306       if (iter != colormap_names_.end())
00307       {
00308         colormap_idx = colormap_names_[colormap_name]; 
00309       }
00310       else
00311       {
00312         ROS_ERROR("Unknown colormap: %s requested", colormap_name.c_str());
00313         success = false;
00314       }
00315     }
00316 
00317     // Now get the specified number of colors from the requested colormap
00318     if (success)
00319     {
00320       // Make a copy of the grayscale LUT as a working variable
00321       cv::Mat original_colors = color_lut_.clone();
00322       // color_lut_ will have the transformation from the grayscale values
00323       // to the RGB values after this call for every grayscale value
00324       cv::applyColorMap(original_colors, color_lut_, colormap_idx);
00325 
00326       // Now modify the original colormap to only have the number
00327       // of distinct entries specified by the user
00328       original_colors = color_lut_.clone();
00329 
00330       int32_t lut_size = color_lut_.cols;
00331 
00332       // Frequently the input image may have some small subset of values,
00333       // like 0-5. In this case, just mapping to a colormap will make the 
00334       // resulting image look like one color, because the first 6 colors from
00335       // the colormap will be used, which for most colormaps are almost the
00336       // same value. This will more intelligently remap this values, so that
00337       // the 0, 50, 100, 150, 200, 250 color indices are used from the
00338       // colormap. This "pushes" the color values apart to make them more
00339       // visually apparent.
00340       for (int32_t replace_idx = 0; replace_idx < lut_size; replace_idx++)
00341       {
00342         int32_t lookup_idx = replace_idx % num_entries;
00343         lookup_idx = lookup_idx * lut_size / num_entries;
00344         color_lut_.at<cv::Vec3b>(0, static_cast<uint8_t>(replace_idx)) = 
00345           original_colors.at<cv::Vec3b>(0, static_cast<uint8_t>(lookup_idx));
00346       }
00347     }
00348   }
00349 
00350   // Read the user lookup table from the parameter server
00351   void ReplaceColorsNodelet::readUserLut(const XmlRpc::XmlRpcValue& param)
00352   {
00353     // Assume the parameter parsing works by default
00354     bool success = true;
00355     if (param.getType() != XmlRpc::XmlRpcValue::TypeArray)
00356     {
00357       ROS_ERROR("LUT must be an array, but type was: %s",
00358         getValueTypeString(param).c_str());
00359       success = false;
00360     }
00361 
00362     // Make a copy of the current LUT. The copy will be modified, and only
00363     // if the complete parameter reading works will the real LUT be modified.
00364     cv::Mat temp_lut = color_lut_.clone();
00365 
00366     // Loop over all values that will be replaced. The casting in here is 
00367     // complicated because XmlRpc cannot go directly from the parameters to 
00368     // uint8_t values, so they must first be cast to int32_t, and then to the
00369     // smaller value
00370     for (uint32_t lut_idx = 0; success && (lut_idx < param.size()); lut_idx++)
00371     {
00372       // Each row will be of the form [key, [r, g, b]]. The next two checks make
00373       // sure the right type and number of entries was passed in
00374       XmlRpc::XmlRpcValue lut_row = param[lut_idx];
00375       if (lut_row.getType() != XmlRpc::XmlRpcValue::TypeArray)
00376       {
00377         ROS_ERROR("LUT entries must be two entry arrays with the following");
00378         ROS_ERROR("format:");
00379         ROS_ERROR("\t[gray_value, [r, g, b]]");
00380         ROS_ERROR("but for entry %d a %s was given", 
00381           lut_idx, getValueTypeString(lut_row).c_str());
00382         success = false;
00383         break;
00384       }
00385 
00386       if (lut_row.size() != 2)
00387       {
00388         ROS_ERROR("LUT entries must be two entry arrays with the following");
00389         ROS_ERROR("format:");
00390         ROS_ERROR("\t[gray_value, [r, g, b]]");
00391         ROS_ERROR("but entry %d had an array with only %d values", 
00392           lut_idx, lut_row.size());
00393         success = false;
00394         break;
00395       }
00396 
00397       // The first element in the gray scale value to replace. Make sure it
00398       // is of the appropriate type
00399       XmlRpc::XmlRpcValue map_key = lut_row[0];
00400       if (map_key.getType() != XmlRpc::XmlRpcValue::TypeInt)
00401       {
00402         ROS_ERROR("Color to replace must be an integer, but a %s was given for",
00403           getValueTypeString(map_key).c_str());
00404         ROS_ERROR("entry %d", lut_idx);
00405         success = false;
00406         break;
00407       }
00408 
00409       // Make sure the gray scale index is in the proper range
00410       int32_t gray_index = static_cast<int32_t>(map_key);
00411       if ((gray_index >= NUM_GRAY_VALUES) || (gray_index < 0))
00412       {
00413         ROS_ERROR("Gray scale value for LUT entry %d was %d, but must be", 
00414           lut_idx, gray_index);
00415         ROS_ERROR("between 0 and %d", NUM_GRAY_VALUES - 1);
00416         success = false;
00417         break;
00418       }
00419 
00420       // Convert to the value used to index into the LUT
00421       uint8_t color_index = static_cast<uint8_t>(gray_index);
00422 
00423       // Now read the RGB values. There must be three of them, they must be
00424       // integers, and less than MAX_RGB_VALUE 
00425       XmlRpc::XmlRpcValue rgb_values = lut_row[1];
00426       if (rgb_values.getType() != XmlRpc::XmlRpcValue::TypeArray)
00427       {
00428         ROS_ERROR("RGB entries must be three entry arrays of integers, but");
00429         ROS_ERROR("LUT row %d was a %s", lut_idx,
00430           getValueTypeString(rgb_values).c_str());
00431         success = false;
00432         break;
00433       }
00434 
00435       if (rgb_values.size() != 3)
00436       {
00437         ROS_ERROR("RGB entries must be three entry arrays of integers, but");
00438         ROS_ERROR("LUT row %d had %d entries", lut_idx, rgb_values.size());
00439         success = false;
00440         break;
00441       }
00442 
00443       if (rgb_values[0].getType() != XmlRpc::XmlRpcValue::TypeInt)
00444       {
00445         ROS_ERROR("RGB entries must be three entry arrays of integers, but");
00446         ROS_ERROR("LUT row %d had a red value with type %s", lut_idx,
00447           getValueTypeString(rgb_values[0]).c_str());
00448         success = false;
00449         break;
00450       }
00451 
00452       if (rgb_values[1].getType() != XmlRpc::XmlRpcValue::TypeInt)
00453       {
00454         ROS_ERROR("RGB entries must be three entry arrays of integers, but");
00455         ROS_ERROR("LUT row %d had a green value with type %s", lut_idx,
00456           getValueTypeString(rgb_values[1]).c_str());
00457         success = false;
00458         break;
00459       }
00460 
00461       if (rgb_values[2].getType() != XmlRpc::XmlRpcValue::TypeInt)
00462       {
00463         ROS_ERROR("RGB entries must be three entry arrays of integers, but");
00464         ROS_ERROR("LUT row %d had a blue value with type %s", lut_idx,
00465           getValueTypeString(rgb_values[2]).c_str());
00466         success = false;
00467         break;
00468       }
00469 
00470       // Make sure the RGB values are between 0 and MAX_RGB_VALUE 
00471       int32_t original_red = static_cast<int32_t>(rgb_values[0]);
00472       if ((original_red > MAX_RGB_VALUE) || (original_red < 0))
00473       {
00474         ROS_ERROR("Red value on row %d was %d, and must be between 0 and %d",
00475           lut_idx, original_red, MAX_RGB_VALUE);
00476         success = false;
00477         break;
00478       }
00479       int32_t original_green = static_cast<int32_t>(rgb_values[1]);
00480       if ((original_green > MAX_RGB_VALUE) || (original_green < 0))
00481       {
00482         ROS_ERROR("Green value on row %d was %d, and must be between 0 and %d",
00483           lut_idx, original_green, MAX_RGB_VALUE);
00484         success = false;
00485         break;
00486       }
00487       int32_t original_blue = static_cast<int32_t>(rgb_values[2]);
00488       if ((original_blue > MAX_RGB_VALUE) || (original_blue < 0))
00489       {
00490         ROS_ERROR("Blue value on row %d was %d, and must be between 0 and %d",
00491           lut_idx, original_blue, MAX_RGB_VALUE);
00492         success = false;
00493         break;
00494       }
00495 
00496       // make the RGB values the correct type
00497       uint8_t red = static_cast<uint8_t>(original_red);
00498       uint8_t green = static_cast<uint8_t>(original_green);
00499       uint8_t blue = static_cast<uint8_t>(original_blue);
00500 
00501       // Convert the RGB values to OpenCV types and put them in the LUT
00502       cv::Vec3b rgb_entry(red, green, blue);
00503       temp_lut.at<cv::Vec3b>(0, color_index) = rgb_entry;
00504     }
00505 
00506     // If the parametre were successfully read the modified LUT will be
00507     // copied back to the persistent LUT.
00508     if (success)
00509     {
00510       color_lut_ = temp_lut.clone();
00511     }
00512   }
00513 
00514   std::string ReplaceColorsNodelet::getValueTypeString(
00515     const XmlRpc::XmlRpcValue& value)
00516   {
00517     std::string type_str = "unknown";
00518     switch(value.getType())
00519     {
00520       case XmlRpc::XmlRpcValue::TypeInvalid:
00521         type_str = "invalid";
00522         break;
00523 
00524       case XmlRpc::XmlRpcValue::TypeBoolean:
00525         type_str = "boolean";
00526         break;
00527       
00528       case XmlRpc::XmlRpcValue::TypeInt:
00529         type_str = "int";
00530         break;
00531 
00532       case XmlRpc::XmlRpcValue::TypeDouble:
00533         type_str = "double";
00534         break;
00535 
00536       case XmlRpc::XmlRpcValue::TypeString:
00537         type_str = "string";
00538         break;
00539         
00540       case XmlRpc::XmlRpcValue::TypeDateTime:
00541         type_str = "date time";
00542         break;
00543         
00544       case XmlRpc::XmlRpcValue::TypeBase64:
00545         type_str = "base 64";
00546         break;
00547         
00548       case XmlRpc::XmlRpcValue::TypeArray:
00549         type_str = "array";
00550         break;
00551         
00552       case XmlRpc::XmlRpcValue::TypeStruct:
00553         type_str = "struct";
00554         break;
00555 
00556       default:
00557         ROS_ERROR("Unknown XML RPC value type encountered");
00558         type_str = "unknown";
00559         break;
00560     }
00561 
00562     return type_str;
00563   }
00564 }
00565 
00566 #include <pluginlib/class_list_macros.h>
00567 PLUGINLIB_DECLARE_CLASS(
00568   swri_image_util,
00569   replace_colors,
00570   swri_image_util::ReplaceColorsNodelet,
00571   nodelet::Nodelet)


swri_image_util
Author(s): Kris Kozak
autogenerated on Tue Oct 3 2017 03:19:34