Just finished programming two packages needed to quickly determine the thresholds for an image :
- colorThresholdReconfigure: GUI based on wxWidgets helps determine the parameters
- colorThresholdProcess: Code that uses OpenCV to threshold image and show the result in a HighGUI window
When you run both the nodes, you will see the following GUI:

Way to run it
- Download both the packages in to a folder: DOWNLOAD LINK , and make sure to add both the folders to the ROS_PACKAGE_PATH. I have them in my HOME directory, so i added the following two lines at the end of my .bashrc:
export ROS_PACKAGE_PATH=~/colorThresholdReconfigure:$ROS_PACKAGE_PATH
export ROS_PACKAGE_PATH=~/colorThresholdProcess:$ROS_PACKAGE_PATH
Make sure to restart the terminal windows for it to take effect.
- Next open a terminal window and run roscore.
- Open another terminal window and type the following:
$ sudo bash
Provide your password, and you will then have root access.
# roscd colorThresholdReconfigure/
# rosmake colorThresholdReconfigure
Make sure that the package built with no errors.
To run it, type:
# rosrun colorThresholdReconfigure colorThresholdReconfigure
- Open another terminal window and type the following:
$ sudo bash
Provide your password, and you will then have root access.
# roscd colorThresholdProcess/
# rosmake colorThresholdProcess
Make sure that the package built with no errors.
To run it, type:
# rosrun colorThresholdProcess colorThresholdProcess
- Have FUN with it! If you break it, let me know how you managed to do it, and i will try and fix……maybe
colorThresholdReconfigure Source Code:
/*
Copyright (c) 2011, Siddhant Ahuja (Sid)
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 <organization> 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 <COPYRIGHT HOLDER> 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.
*/
/*
This program provides a GUI interface for determining parameters needed to
threshold an image.
*/
//Includes all the headers necessary to use the ROS system.
#include <ros/ros.h>
//Include headers for wxWidgets GUI handling.
#include <wx/wx.h>
#include <wx/aboutdlg.h>
#include <wx/spinctrl.h>
//Declare a list of unique IDs which would be used for event handlers.
enum
{
ID_About=1,
ID_Mode,
ID_sourceColorSpace,
ID_convertToColorSpace,
ID_Output,
ID_minChannel_1,
ID_maxChannel_1,
ID_minChannel_2,
ID_maxChannel_2,
ID_minChannel_3,
ID_maxChannel_3
};
/*
Declare the App to set and get application-wide properties, implement the
windowing system message or event loop, initiate application processing via
wxApp::OnInit, and allow default processing of events not handled by other
objects in the application.
*/
class CTRApp : public wxApp
{
public:
//Initialization function.
virtual bool OnInit();
//Exit function.
int OnExit();
//Global Variables.
char** local_argv_;
};
/*
Declare macro IMPLEMENT_APP(appClass) to tell wxWidgets how to create an
instance of the application class.
*/
IMPLEMENT_APP(CTRApp);
/*
Use DECLARE_APP(appClass) so that wxGetApp function (which returns a reference
to your application object) is visible to other files.
*/
DECLARE_APP(CTRApp);
//Declare a frame-window class.
class CTRFrame: public wxFrame
{
public:
CTRFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
protected:
//Called when the user clicks the About Option in the ToolBar.
void OnAbout(wxCommandEvent& event);
void OnUpdateMode(wxCommandEvent& event);
void OnUpdateSourceColorSpace(wxCommandEvent& event);
void OnUpdateConvertToColorSpace(wxCommandEvent& event);
void OnUpdateOutput(wxCommandEvent& event);
void OnUpdateMinChannel_1(wxSpinEvent& event);
void OnUpdateMaxChannel_1(wxSpinEvent& event);
void OnUpdateMinChannel_2(wxSpinEvent& event);
void OnUpdateMaxChannel_2(wxSpinEvent& event);
void OnUpdateMinChannel_3(wxSpinEvent& event);
void OnUpdateMaxChannel_3(wxSpinEvent& event);
//Global Variables.
wxString currImagePath;
private:
//Every event handler needs its event table entry.
DECLARE_EVENT_TABLE();
};
//Declare Event Table.
BEGIN_EVENT_TABLE(CTRFrame, wxFrame)
EVT_MENU(ID_About, CTRFrame::OnAbout)
EVT_CHOICE(ID_Mode, CTRFrame::OnUpdateMode)
EVT_CHOICE(ID_sourceColorSpace, CTRFrame::OnUpdateSourceColorSpace)
EVT_CHOICE(ID_convertToColorSpace, CTRFrame::OnUpdateConvertToColorSpace)
EVT_CHOICE(ID_Output, CTRFrame::OnUpdateOutput)
EVT_SPINCTRL(ID_minChannel_1, CTRFrame::OnUpdateMinChannel_1)
EVT_SPINCTRL(ID_maxChannel_1, CTRFrame::OnUpdateMaxChannel_1)
EVT_SPINCTRL(ID_minChannel_2, CTRFrame::OnUpdateMinChannel_2)
EVT_SPINCTRL(ID_maxChannel_2, CTRFrame::OnUpdateMaxChannel_2)
EVT_SPINCTRL(ID_minChannel_3, CTRFrame::OnUpdateMinChannel_3)
EVT_SPINCTRL(ID_maxChannel_3, CTRFrame::OnUpdateMaxChannel_3)
END_EVENT_TABLE()
//Initialize the Frame.
CTRFrame::CTRFrame(const wxString& title, const wxPoint& pos, const wxSize& size): wxFrame( NULL, -1, title, pos, size )
{
//Declare a File Menu.
wxMenu *menuFile = new wxMenu;
//Add Items to the array.
menuFile->Append( ID_About, _("&About...") );
//Declare the Menu Bar.
wxMenuBar *menuBar = new wxMenuBar;
//Add items from File Menu into the Menu Bar.
menuBar->Append( menuFile, _("&File") );
//Set the Menu Bar.
SetMenuBar( menuBar );
//Initialize All Local Variables
int CTR_mode;
int CTR_sourceColorSpace;
int CTR_convertToColorSpace;
int CTR_output;
int CTR_minChannel_1;
int CTR_maxChannel_1;
int CTR_minChannel_2;
int CTR_maxChannel_2;
int CTR_minChannel_3;
int CTR_maxChannel_3;
std::string CTR_imagePath;
//Populate all Local Variables
ros::param::get("/CTR_mode", CTR_mode);
ros::param::get("/CTR_sourceColorSpace", CTR_sourceColorSpace);
ros::param::get("/CTR_convertToColorSpace", CTR_convertToColorSpace);
ros::param::get("/CTR_output", CTR_output);
ros::param::get("/CTR_minChannel_1", CTR_minChannel_1);
ros::param::get("/CTR_maxChannel_1", CTR_maxChannel_1);
ros::param::get("/CTR_minChannel_2", CTR_minChannel_2);
ros::param::get("/CTR_maxChannel_2", CTR_maxChannel_2);
ros::param::get("/CTR_minChannel_3", CTR_minChannel_3);
ros::param::get("/CTR_maxChannel_3", CTR_maxChannel_3);
ros::param::get("/CTR_imagePath", CTR_imagePath);
//Declare all Labels.
wxStaticText *lblSelectMode = new wxStaticText(this, -1,_T("Mode"),wxPoint(10, 20), wxSize(100,25));
wxStaticText *lblSelectSourceColorSpace = new wxStaticText(this, -1,_T("Source Color Space"),wxPoint(10, 60), wxSize(155,25));
wxStaticText *lblSelectConvertToColorSpace = new wxStaticText(this, -1,_T("Convert Color Space To"),wxPoint(10, 100), wxSize(155,25));
wxStaticText *lblOutputType = new wxStaticText(this, -1,_T("Output Type"),wxPoint(10, 140), wxSize(100,25));
wxStaticText *lblMin = new wxStaticText(this, -1,_T("Min"),wxPoint(195, 180), wxSize(100,25));
wxStaticText *lblMax = new wxStaticText(this, -1,_T("Max"),wxPoint(320, 180), wxSize(100,25));
wxStaticText *lblChannel_1 = new wxStaticText(this, -1,_T("Channel 1"),wxPoint(10, 220), wxSize(100,25));
wxStaticText *lblChannel_2 = new wxStaticText(this, -1,_T("Channel 2"),wxPoint(10, 260), wxSize(100,25));
wxStaticText *lblChannel_3 = new wxStaticText(this, -1,_T("Channel 3"),wxPoint(10, 300), wxSize(100,25));
//Initialize all Controls
wxChoice *modeSelect=new wxChoice(this, ID_Mode, wxPoint(195,15), wxSize(200,25));
modeSelect->Append(_T("Select Mode"));
modeSelect->Append(_T("Image"));
modeSelect->SetSelection(CTR_mode);
wxChoice *sourceColorSpaceSelect=new wxChoice(this, ID_sourceColorSpace, wxPoint(195,55), wxSize(200,25));
sourceColorSpaceSelect->Append(_T("Select Color Space"));
sourceColorSpaceSelect->Append(_T("BGR"));
sourceColorSpaceSelect->Append(_T("RGB"));
sourceColorSpaceSelect->SetSelection(CTR_sourceColorSpace);
wxChoice *convertToColorSpaceSelect=new wxChoice(this, ID_convertToColorSpace, wxPoint(195,95), wxSize(200,25));
convertToColorSpaceSelect->Append(_T("Select Color Space"));
convertToColorSpaceSelect->Append(_T("BGR"));
convertToColorSpaceSelect->Append(_T("RGB"));
convertToColorSpaceSelect->Append(_T("Gray"));
convertToColorSpaceSelect->Append(_T("HSV"));
convertToColorSpaceSelect->Append(_T("HLS"));
convertToColorSpaceSelect->Append(_T("CIE L*a*b*"));
convertToColorSpaceSelect->Append(_T("CIE L*u*v*"));
convertToColorSpaceSelect->Append(_T("CIE XYZ"));
convertToColorSpaceSelect->Append(_T("YCrCb JPEG (a.k.a. YCC)"));
convertToColorSpaceSelect->SetSelection(CTR_convertToColorSpace);
wxChoice *outputTypeSelect=new wxChoice(this, ID_Output, wxPoint(195,135), wxSize(200,25));
outputTypeSelect->Append(_T("Select Output Type"));
outputTypeSelect->Append(_T("Color Preserve"));
outputTypeSelect->Append(_T("Binarized"));
outputTypeSelect->Append(_T("Binarized Inverted"));
outputTypeSelect->SetSelection(CTR_output);
wxSpinCtrl *minChannel_1 = new wxSpinCtrl(this, ID_minChannel_1, wxEmptyString, wxPoint(195,215), wxSize(65, 25), wxSP_ARROW_KEYS, 0, 255, CTR_minChannel_1 , _T("spinCtrlMinChannel_1"));
wxSpinCtrl *maxChannel_1 = new wxSpinCtrl(this, ID_maxChannel_1, wxEmptyString, wxPoint(320,215), wxSize(65, 25), wxSP_ARROW_KEYS, 0, 255, CTR_maxChannel_1, _T("spinCtrlMaxChannel_1"));
wxSpinCtrl *minChannel_2 = new wxSpinCtrl(this, ID_minChannel_2, wxEmptyString, wxPoint(195,255), wxSize(65, 25), wxSP_ARROW_KEYS, 0, 255, CTR_minChannel_2, _T("spinCtrlMinChannel_2"));
wxSpinCtrl *maxChannel_2 = new wxSpinCtrl(this, ID_maxChannel_2, wxEmptyString, wxPoint(320,255), wxSize(65, 25), wxSP_ARROW_KEYS, 0, 255, CTR_maxChannel_2, _T("spinCtrlMaxChannel_2"));
wxSpinCtrl *minChannel_3 = new wxSpinCtrl(this, ID_minChannel_3, wxEmptyString, wxPoint(195,295), wxSize(65, 25), wxSP_ARROW_KEYS, 0, 255, CTR_minChannel_3, _T("spinCtrlMinChannel_3"));
wxSpinCtrl *maxChannel_3 = new wxSpinCtrl(this, ID_maxChannel_3, wxEmptyString, wxPoint(320,295), wxSize(65, 25), wxSP_ARROW_KEYS, 0, 255, CTR_maxChannel_3, _T("spinCtrlMaxChannel_3"));
//Create th Status Bar
CreateStatusBar();
//Set Status Bar Text
SetStatusText(wxT("Welcome to Color Threshold Reconfigure App"));
};
//Application Initialization function.
bool CTRApp::OnInit()
{
ROS_INFO("Color Threshold Reconfigure App::Initializing");
// Create local copy of argv, with regular char*s.
local_argv_ = new char*[ argc ];
for ( int i = 0; i < argc; ++i )
{
local_argv_[ i ] = strdup( wxString( argv[ i ] ).mb_str() );
}
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line. For programmatic
* remappings you can use a different version of init() which takes remappings
* directly, but for most command-line programs, passing argc and argv is the easiest
* way to do it. The third argument to init() is the name of the node. Node names must be unique in a running system.
* The name used here must be a base name, ie. it cannot have a / in it.
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, local_argv_, "colorThresholdReconfigure");
//Initialize all Session Wide Parameters
ros::param::set("CTR_mode", 0);
ros::param::set("CTR_sourceColorSpace", 0);
ros::param::set("CTR_convertToColorSpace", 0);
ros::param::set("CTR_output", 0);
ros::param::set("CTR_minChannel_1", 0);
ros::param::set("CTR_maxChannel_1", 255);
ros::param::set("CTR_minChannel_2", 0);
ros::param::set("CTR_maxChannel_2", 255);
ros::param::set("CTR_minChannel_3", 0);
ros::param::set("CTR_maxChannel_3", 255);
ros::param::set("CTR_imagePath", "");
//Declare the frame window
CTRFrame *frame = new CTRFrame(_("Color Threshold Reconfigure App"), wxPoint(50, 50), wxSize(410,400) );
//Show the frame
frame->Show(TRUE);
//Set it as the top most window
SetTopWindow(frame);
return TRUE;
};
//Application Exit function.
int CTRApp::OnExit()
{
ROS_INFO("Color Threshold Reconfigure App::Exiting");
for ( int i = 0; i < argc; ++i )
{
free( local_argv_[ i ] );
}
delete [] local_argv_;
//Delete All Parameters
ros::param::del("CTR_mode");
ros::param::del("CTR_sourceColorSpace");
ros::param::del("CTR_convertToColorSpace");
ros::param::del("CTR_output");
ros::param::del("CTR_minChannel_1");
ros::param::del("CTR_maxChannel_1");
ros::param::del("CTR_minChannel_2");
ros::param::del("CTR_maxChannel_2");
ros::param::del("CTR_minChannel_3");
ros::param::del("CTR_maxChannel_3");
ros::param::del("CTR_imagePath");
return 0;
};
//Called when user clicks the About item in the menu
void CTRFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
ROS_INFO("Color Threshold Reconfigure App::Menu::About");
wxAboutDialogInfo *info=new wxAboutDialogInfo;
info->SetName(_("Color Threshold Reconfigure App"));
info->SetVersion(_("1.0.0"));
info->SetDescription(_("This program provides a GUI interface for determining parameters needed to threshold an image"));
info->SetCopyright(_T("(C) 2011 Siddhant Ahuja (Sid) <siddahuja@gmail.com>"));
wxAboutBox(*info);
};
void CTRFrame::OnUpdateMode(wxCommandEvent& event)
{
wxChoice* choice= dynamic_cast<wxChoice*>(event.GetEventObject());
int currModeValue=choice->GetCurrentSelection();
wxString currModeString=choice->GetStringSelection();
//Set Parameter
ros::param::set("CTR_mode", currModeValue);
ROS_INFO("Color Threshold Reconfigure App::Current Mode Selected < Value , String > : < %d, %s >",currModeValue, (const char*)currModeString.mb_str());
switch (currModeValue)
{
case 0:
{
//Reset All Params
ros::param::set("CTR_imagePath", "");
}
break;
case 1:
{
ROS_INFO("Color Threshold Reconfigure App::Select Image");
wxFileDialog *imageOpen=new wxFileDialog(this, _T("Choose a file"), wxEmptyString, wxEmptyString, _T("All Supported Image Formats|*.bmp;*.dib;*.jpeg;*.jpg;*.jpe;*.jp2;*.png;*.pbm;*.pgm;*.ppm;*.sr;*.ras;*.tiff;*.tif"), wxFD_DEFAULT_STYLE, wxDefaultPosition, wxDefaultSize, _T("Open an Image"));
// Creates a Open Image Dialog
if (imageOpen->ShowModal() == wxID_OK)
{
//ROS_INFO("Color Threshold Reconfigure App::Select Image::OK");
//Get Image Path
wxString currImagePath=imageOpen->GetPath();
ROS_INFO("Color Threshold Reconfigure App::Select Image::Current Image Path : %s", (const char*)currImagePath.mb_str());
//Set Parameter
ros::param::set("CTR_imagePath", (const char*)currImagePath.mb_str());
}
else
{
//Set Parameter
ros::param::set("CTR_imagePath", "");
}
imageOpen->Destroy();
}
break;
default:
break;
}
};
void CTRFrame::OnUpdateSourceColorSpace(wxCommandEvent& event)
{
wxChoice* choice= dynamic_cast<wxChoice*>(event.GetEventObject());
int currColorSpaceValue=choice->GetCurrentSelection();
wxString currColorSpaceString=choice->GetStringSelection();
//Set Parameter
ros::param::set("CTR_sourceColorSpace", currColorSpaceValue);
ROS_INFO("Color Threshold Reconfigure App::Current Source Color Space Selected <Value,String> : < %d, %s >",currColorSpaceValue, (const char*)currColorSpaceString.mb_str());
};
void CTRFrame::OnUpdateConvertToColorSpace(wxCommandEvent& event)
{
wxChoice* choice= dynamic_cast<wxChoice*>(event.GetEventObject());
int currColorSpaceValue=choice->GetCurrentSelection();
wxString currColorSpaceString=choice->GetStringSelection();
//Set Parameter
ros::param::set("CTR_convertToColorSpace", currColorSpaceValue);
ROS_INFO("Color Threshold Reconfigure App::Current Convert To Color Space Selected <Value,String> : < %d, %s >",currColorSpaceValue, (const char*)currColorSpaceString.mb_str());
};
void CTRFrame::OnUpdateOutput(wxCommandEvent& event)
{
wxChoice* choice= dynamic_cast<wxChoice*>(event.GetEventObject());
int currOutputValue=choice->GetCurrentSelection();
wxString currOutputString=choice->GetStringSelection();
//Set Parameter
ros::param::set("CTR_output", currOutputValue);
ROS_INFO("Color Threshold Reconfigure App::Current Output Selected <Value,String> : < %d, %s >",currOutputValue, (const char*)currOutputString.mb_str());
};
void CTRFrame::OnUpdateMinChannel_1(wxSpinEvent& event)
{
wxSpinCtrl* spinCtrl= dynamic_cast<wxSpinCtrl*>(event.GetEventObject());
int currMinChannel_1_Value=spinCtrl->GetValue();
//Set Parameter
ros::param::set("CTR_minChannel_1", currMinChannel_1_Value);
ROS_INFO("Color Threshold Reconfigure App::Current Min Channel 1 Selected <Value> : < %d >",currMinChannel_1_Value);
};
void CTRFrame::OnUpdateMaxChannel_1(wxSpinEvent& event)
{
wxSpinCtrl* spinCtrl= dynamic_cast<wxSpinCtrl*>(event.GetEventObject());
int currMaxChannel_1_Value=spinCtrl->GetValue();
//Set Parameter
ros::param::set("CTR_maxChannel_1", currMaxChannel_1_Value);
ROS_INFO("Color Threshold Reconfigure App::Current Max Channel 1 Selected <Value> : < %d >",currMaxChannel_1_Value);
};
void CTRFrame::OnUpdateMinChannel_2(wxSpinEvent& event)
{
wxSpinCtrl* spinCtrl= dynamic_cast<wxSpinCtrl*>(event.GetEventObject());
int currMinChannel_2_Value=spinCtrl->GetValue();
//Set Parameter
ros::param::set("CTR_minChannel_2", currMinChannel_2_Value);
ROS_INFO("Color Threshold Reconfigure App::Current Min Channel 2 Selected <Value> : < %d >",currMinChannel_2_Value);
};
void CTRFrame::OnUpdateMaxChannel_2(wxSpinEvent& event)
{
wxSpinCtrl* spinCtrl= dynamic_cast<wxSpinCtrl*>(event.GetEventObject());
int currMaxChannel_2_Value=spinCtrl->GetValue();
//Set Parameter
ros::param::set("CTR_maxChannel_2", currMaxChannel_2_Value);
ROS_INFO("Color Threshold Reconfigure App::Current Max Channel 2 Selected <Value> : < %d >",currMaxChannel_2_Value);
};
void CTRFrame::OnUpdateMinChannel_3(wxSpinEvent& event)
{
wxSpinCtrl* spinCtrl= dynamic_cast<wxSpinCtrl*>(event.GetEventObject());
int currMinChannel_3_Value=spinCtrl->GetValue();
//Set Parameter
ros::param::set("CTR_minChannel_3", currMinChannel_3_Value);
ROS_INFO("Color Threshold Reconfigure App::Current Min Channel 3 Selected <Value> : < %d >",currMinChannel_3_Value);
};
void CTRFrame::OnUpdateMaxChannel_3(wxSpinEvent& event)
{
wxSpinCtrl* spinCtrl= dynamic_cast<wxSpinCtrl*>(event.GetEventObject());
int currMaxChannel_3_Value=spinCtrl->GetValue();
//Set Parameter
ros::param::set("CTR_maxChannel_3", currMaxChannel_3_Value);
ROS_INFO("Color Threshold Reconfigure App::Current Max Channel 3 Selected <Value> : < %d >",currMaxChannel_3_Value);
};
colorThresholdProcess Source Code:
/*
Copyright (c) 2011, Siddhant Ahuja (Sid)
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 <organization> 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 <COPYRIGHT HOLDER> 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.
*/
/*
This program gets the system parameters saved from the GUI interface from
package colorThresholdReconfigure and then uses OpenCV to threshold an image.
The resultant thresholded image is displayed in a window
and also published as a topic.
*/
//Includes all the headers necessary to use the most common public pieces of the ROS system.
#include <ros/ros.h>
//Include headers for OpenCV Image processing
#include <opencv2/imgproc/imgproc.hpp>
//Include headers for OpenCV GUI handling
#include <opencv2/highgui/highgui.hpp>
using namespace std;
//Declare a string with the name of the window that we will create using OpenCV where processed images will be displayed.
static const char WINDOW[] = "Color Threshold Process";
//Initialize All Global Variables
int CTR_mode;
int CTR_sourceColorSpace;
int CTR_convertToColorSpace;
int CTR_output;
int CTR_minChannel_1;
int CTR_maxChannel_1;
int CTR_minChannel_2;
int CTR_maxChannel_2;
int CTR_minChannel_3;
int CTR_maxChannel_3;
std::string CTR_imagePath;
//Declare a 3 channel matrix that stores the input
cv::Mat inputImg;
//Declare a 3 channel matrix that stores the output
cv::Mat outputImg;
//Create a Matrix of Zeros to be shown by default in the OpenCV Window
cv::Mat imgZeros;
//Declare the Lookup Table that will store the color space conversion constants
cv::Mat LUT_ColorSpaceConvert(2, 9, CV_8UC1);
void imgConvertColorSpace()
{
ROS_INFO("Color Threshold Process App::Convert Color Space");
//ROS_INFO("Color Threshold Process App::Convert Color Space::Source Color Space : < value > : < %d >", CTR_sourceColorSpace);
//ROS_INFO("Color Threshold Process App::Convert Color Space::Convert To Color Space : < value > : < %d >", CTR_convertToColorSpace);
if(CTR_sourceColorSpace!=CTR_convertToColorSpace && CTR_sourceColorSpace!=0 && CTR_convertToColorSpace!=0)
{
//ROS_INFO("Color Threshold Process App::Convert Color Space::Source and convert to color spaces DO NOT match. Attempting to convert color space.");
//Search in the Lookup Table to find the corresponding color conversion code
unsigned char colorConversionCode = LUT_ColorSpaceConvert.at<unsigned char>(CTR_sourceColorSpace-1,CTR_convertToColorSpace-1);
//ROS_INFO("Color Threshold Process App::Convert Color Space::Color Conversion Code Located: < value > : < %d >", (int)colorConversionCode);
switch (colorConversionCode)
{
case 0: //Conversion between same color space
ROS_WARN("Color Threshold Process App::Convert Color Space::Attempt to convert between same color spaces.");
break;
case 1: //No Direct Conversion available. Convert to intermittent space like BGR
ROS_WARN("Color Threshold Process App::Convert Color Space::No Direct Conversion available. Convert to intermittent space like BGR.");
break;
default: //Convert color space and store the result
cv::cvtColor(inputImg, outputImg, colorConversionCode, 0);
break;
}
}
else
{
//ROS_INFO("Color Threshold Process App::Convert Color Space::Source and convert to Color spaces match <or> One of color spaces not selected. Do nothing.");
}
};
void imgThreshold()
{
ROS_INFO("Color Threshold Process App::Image Threshold");
//Declare common variables
unsigned char ch1, ch2, ch3; //stores the channel 1, channel 2, and channel 3 data
bool dstValue;
//Perform threshold
for (int i=0; i< outputImg.rows; i++)
{
for (int j = 0; j< outputImg.cols; j++)
{
if(outputImg.channels()>1)
{
//Populate color values
ch1 = outputImg.at<cv::Vec3b>(i,j)[0];
ch2 = outputImg.at<cv::Vec3b>(i,j)[1];
ch3 = outputImg.at<cv::Vec3b>(i,j)[2];
//check range
if(ch1>=CTR_minChannel_1 && ch1<=CTR_maxChannel_1 && ch2>=CTR_minChannel_2 && ch2<=CTR_maxChannel_2 && ch3>=CTR_minChannel_3 && ch3<=CTR_maxChannel_3)
{
dstValue=true;
}
else
{
dstValue=false;
}
if(dstValue)
{
switch(CTR_output)
{
case 0: //do nothing
break;
case 1: //Color Preserve
{
outputImg.at<cv::Vec3b>(i,j)[0]=ch1;
outputImg.at<cv::Vec3b>(i,j)[1]=ch2;
outputImg.at<cv::Vec3b>(i,j)[2]=ch3;
}
break;
case 2: //Binarized
{
outputImg.at<cv::Vec3b>(i,j)[0]=255;
outputImg.at<cv::Vec3b>(i,j)[1]=255;
outputImg.at<cv::Vec3b>(i,j)[2]=255;
}
break;
case 3: //Binarized Inverted
{
outputImg.at<cv::Vec3b>(i,j)[0]=0;
outputImg.at<cv::Vec3b>(i,j)[1]=0;
outputImg.at<cv::Vec3b>(i,j)[2]=0;
}
break;
default:
break;
}
}
else
{
switch(CTR_output)
{
case 0: //do nothing
break;
case 1: //Color Preserve
{
outputImg.at<cv::Vec3b>(i,j)[0]=0;
outputImg.at<cv::Vec3b>(i,j)[1]=0;
outputImg.at<cv::Vec3b>(i,j)[2]=0;
}
break;
case 2: //Binarized
{
outputImg.at<cv::Vec3b>(i,j)[0]=0;
outputImg.at<cv::Vec3b>(i,j)[1]=0;
outputImg.at<cv::Vec3b>(i,j)[2]=0;
}
break;
case 3: //Binarized Inverted
{
outputImg.at<cv::Vec3b>(i,j)[0]=255;
outputImg.at<cv::Vec3b>(i,j)[1]=255;
outputImg.at<cv::Vec3b>(i,j)[2]=255;
}
break;
default:
break;
}
}
}
else
{
//Gray Scale Image
//Populate color values
ch1 = outputImg.at<unsigned char>(i,j);
//check range
if(ch1>=CTR_minChannel_1 && ch1<=CTR_maxChannel_1)
{
dstValue=true;
}
else
{
dstValue=false;
}
if(dstValue)
{
switch(CTR_output)
{
case 0: //do nothing
break;
case 1: //Color Preserve
{
outputImg.at<unsigned char>(i,j)=ch1;
}
break;
case 2: //Binarized
{
outputImg.at<unsigned char>(i,j)=255;
}
break;
case 3: //Binarized Inverted
{
outputImg.at<unsigned char>(i,j)=0;
}
break;
default:
break;
}
}
else
{
switch(CTR_output)
{
case 0: //do nothing
break;
case 1: //Color Preserve
{
outputImg.at<unsigned char>(i,j)=0;
}
break;
case 2: //Binarized
{
outputImg.at<unsigned char>(i,j)=0;
}
break;
case 3: //Binarized Inverted
{
outputImg.at<unsigned char>(i,j)=255;
}
break;
default:
break;
}
}
}
}
}
};
void populateLUTColorSpaceConvert()
{
ROS_INFO("Color Threshold Process App::Populating Color Space Conversion Lookup Table");
//Row/Col order: BGR, RGB, Gray, HSV, HLS, Lab, Luv, XYZ, YCC
//0: Conversion between same color space
//1: No Direct Conversion available. Convert to intermittent space like BGR
//BGR to BGR
LUT_ColorSpaceConvert.at<unsigned char>(0,0) = 0;
//BGR to RGB
LUT_ColorSpaceConvert.at<unsigned char>(0,1) = CV_BGR2RGB;
//BGR to Gray
LUT_ColorSpaceConvert.at<unsigned char>(0,2) = CV_BGR2GRAY;
//BGR to HSV
LUT_ColorSpaceConvert.at<unsigned char>(0,3) = CV_BGR2HSV;
//BGR to HLS
LUT_ColorSpaceConvert.at<unsigned char>(0,4) = CV_BGR2HLS;
//BGR to Lab
LUT_ColorSpaceConvert.at<unsigned char>(0,5) = CV_BGR2Lab;
//BGR to Luv
LUT_ColorSpaceConvert.at<unsigned char>(0,6) = CV_BGR2Luv;
//BGR to XYZ
LUT_ColorSpaceConvert.at<unsigned char>(0,7) = CV_BGR2XYZ;
//BGR to YCC
LUT_ColorSpaceConvert.at<unsigned char>(0,8) = CV_BGR2YCrCb;
//RGB to BGR
LUT_ColorSpaceConvert.at<unsigned char>(1,0) = CV_RGB2BGR;
//RGB to RGB
LUT_ColorSpaceConvert.at<unsigned char>(1,1) = 0;
//RGB to Gray
LUT_ColorSpaceConvert.at<unsigned char>(1,2) = CV_RGB2GRAY;
//RGB to HSV
LUT_ColorSpaceConvert.at<unsigned char>(1,3) = CV_RGB2HSV;
//RGB to HLS
LUT_ColorSpaceConvert.at<unsigned char>(1,4) = CV_RGB2HLS;
//RGB to Lab
LUT_ColorSpaceConvert.at<unsigned char>(1,5) = CV_RGB2Lab;
//RGB to Luv
LUT_ColorSpaceConvert.at<unsigned char>(1,6) = CV_RGB2Luv;
//RGB to XYZ
LUT_ColorSpaceConvert.at<unsigned char>(1,7) = CV_RGB2XYZ;
//RGB to YCC
LUT_ColorSpaceConvert.at<unsigned char>(1,8) = CV_RGB2YCrCb;
};
/**
* This tutorial demonstrates simple image conversion between ROS image message and OpenCV formats and image processing
*/
int main(int argc, char **argv)
{
ROS_INFO("Color Threshold Process App::Initializing");
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line. For programmatic
* remappings you can use a different version of init() which takes remappings
* directly, but for most command-line programs, passing argc and argv is the easiest
* way to do it. The third argument to init() is the name of the node. Node names must be unique in a running system.
* The name used here must be a base name, ie. it cannot have a / in it.
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, argv, "colorThresholdProcess");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
ros::NodeHandle nh;
ros::Rate loop_rate(10);
//OpenCV HighGUI call to create a display window on start-up.
cv::namedWindow(WINDOW, CV_WINDOW_AUTOSIZE);
cvMoveWindow(WINDOW, 460, 50);
//Create a Matrix of Zeros to be shown by default in the OpenCV Window
imgZeros=cv::Mat::zeros(240, 320, CV_8UC3);
//Populate the Lookup Table
populateLUTColorSpaceConvert();
while (ros::ok())
{
//Populate all Local Variables
ros::param::get("/CTR_mode", CTR_mode);
ros::param::get("/CTR_sourceColorSpace", CTR_sourceColorSpace);
ros::param::get("/CTR_convertToColorSpace", CTR_convertToColorSpace);
ros::param::get("/CTR_output", CTR_output);
ros::param::get("/CTR_minChannel_1", CTR_minChannel_1);
ros::param::get("/CTR_maxChannel_1", CTR_maxChannel_1);
ros::param::get("/CTR_minChannel_2", CTR_minChannel_2);
ros::param::get("/CTR_maxChannel_2", CTR_maxChannel_2);
ros::param::get("/CTR_minChannel_3", CTR_minChannel_3);
ros::param::get("/CTR_maxChannel_3", CTR_maxChannel_3);
ros::param::get("/CTR_imagePath", CTR_imagePath);
switch(CTR_mode)
{
case 0: //No option selected
{
//ROS_INFO("Color Threshold Process App::CTR_mode < value > : < %d > ::Clear Window", CTR_mode);
//Display a Blank Screen
cv::imshow(WINDOW, imgZeros);
}
break;
case 1: //Process an Image
{
ROS_INFO("Color Threshold Process App::CTR_mode < value > : < %d > ::Process an Image", CTR_mode);
if(!CTR_imagePath.empty())
{
//ROS_INFO("Color Threshold Process App::CTR_imagePath < string > : < %s >", CTR_imagePath.c_str());
inputImg = cv::imread(CTR_imagePath.c_str(),1);
if(inputImg.empty())
{
ROS_ERROR("Color Threshold Process App::Could not read image!");
return -1;
}
outputImg = inputImg.clone();
imgConvertColorSpace();
//ROS_INFO("Color Threshold Process App::Number of Channels < value > : < %d >", outputImg.channels());
imgThreshold();
cv::imshow(WINDOW, outputImg);
}
else
{
//Display a Blank Screen
cv::imshow(WINDOW, imgZeros);
}
}
break;
default:
break;
}
//Add some delay in miliseconds. The function only works if there is at least one HighGUI window created and the window is active. If there are several HighGUI windows, any of them can be active.
cv::waitKey(3);
ros::spinOnce();
loop_rate.sleep();
}
//OpenCV HighGUI call to destroy a display window on shut-down.
cv::destroyWindow(WINDOW);
//ROS_INFO is the replacement for printf/cout.
ROS_INFO("Color Threshold Process::No error.");
return 0;
}