Skip to content

Commit

Permalink
[TUTO][CLEAN] Cleaned options management in Canny tutorial + [TUTO][C…
Browse files Browse the repository at this point in the history
…ORE] Added test of edge-list feature

The software arguments management was a bit messy, so introduced a structure holding all the parameters

Introduced a test function to ensure that the edge-list correctly reflect the Canny output image
  • Loading branch information
LAGNEAU Romain committed Oct 29, 2024
1 parent ca2656e commit 3d1ed82
Showing 1 changed file with 145 additions and 64 deletions.
209 changes: 145 additions & 64 deletions tutorial/image/tutorial-canny.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,43 @@
using namespace VISP_NAMESPACE_NAME;
#endif

typedef struct SoftwareArguments
{
std::string m_img;
bool m_gradientOutsideClass;
bool m_useVpImageFilterCanny;
bool m_saveEdgeList;
int m_gaussianKernelSize;
int m_apertureSize;
vpImageFilter::vpCannyFilteringAndGradientType m_filteringType;
float m_gaussianStdev;
float m_lowerThresh;
float m_upperThresh;
float m_lowerThreshRatio;
float m_upperThreshRatio;
vpImageFilter::vpCannyBackendType m_backend;

SoftwareArguments()
: m_img("")
, m_gradientOutsideClass(false)
, m_useVpImageFilterCanny(false)
, m_saveEdgeList(false)
, m_gaussianKernelSize(3)
, m_apertureSize(3)
, m_filteringType(vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING)
, m_gaussianStdev(1.)
, m_lowerThresh(-1.)
, m_upperThresh(-1.)
, m_lowerThreshRatio(0.6f)
, m_upperThreshRatio(0.8f)
#ifdef VISP_HAVE_OPENCV
, m_backend(vpImageFilter::CANNY_OPENCV_BACKEND)
#else
, m_backend(vpImageFilter::CANNY_VISP_BACKEND)
#endif
{ }
}SoftwareArguments;

template <class T>
void computeMeanMaxStdev(const vpImage<T> &I, float &mean, float &max, float &stdev)
{
Expand Down Expand Up @@ -117,9 +154,54 @@ void setGradientOutsideClass(const vpImage<unsigned char> &I, const int &gaussia
drawingHelpers::display(dIy_uchar, title);
}

void usage(const std::string &softName, int gaussianKernelSize, float gaussianStdev, float lowerThresh, float upperThresh,
int apertureSize, vpImageFilter::vpCannyFilteringAndGradientType filteringType,
float lowerThreshRatio, float upperThreshRatio, vpImageFilter::vpCannyBackendType backend)
bool sortImagePoints(const vpImagePoint &a, const vpImagePoint &b)
{
if (a.get_i() < b.get_i()) {
return true;
}
else if (vpMath::equal(a.get_i(), b.get_i()) && (a.get_j() < b.get_j())) {
return true;
}
return false;
}

void checkEdgeList(const vpCannyEdgeDetection &cannyDetector, const vpImage<unsigned char> &I_canny_visp)
{
std::vector<vpImagePoint> listEdgePoints = cannyDetector.getEdgePointsList();
// Check if the edge points are uniquely present in the edge lists
std::vector<vpImagePoint> cpyListEdgePoints = listEdgePoints;
std::sort(cpyListEdgePoints.begin(), cpyListEdgePoints.end(), sortImagePoints);
std::unique(cpyListEdgePoints.begin(), cpyListEdgePoints.end());
if (listEdgePoints.size() != cpyListEdgePoints.size()) {
throw(vpException(vpException::fatalError, "There are duplicated points in the edge points list !"));
}
// Check if all the edge points in the list have been set in the image
std::vector<vpImagePoint>::iterator start = listEdgePoints.begin();
std::vector<vpImagePoint>::iterator stop = listEdgePoints.end();
for (std::vector<vpImagePoint>::iterator it = start; it != stop; ++it) {
if (I_canny_visp[static_cast<unsigned int>(it->get_i())][static_cast<unsigned int>(it->get_j())] != 255) {
throw(vpException(vpException::fatalError, "A point of the edge-point list has not been set in the image !"));
}
}
// Check if all the edge points in the image have been set in the list
unsigned int nbRows = I_canny_visp.getRows();
unsigned int nbCols = I_canny_visp.getCols();
for (unsigned int i = 0; i < nbRows; ++i) {
for (unsigned int j = 0; j < nbCols; ++j) {
if (I_canny_visp[i][j] == 255) {
vpImagePoint searchedPoint(i, j);
std::vector<vpImagePoint>::iterator idx = std::find(listEdgePoints.begin(), listEdgePoints.end(), searchedPoint);
if (idx == listEdgePoints.end()) {
throw(vpException(vpException::fatalError, "A point of the image has not been set in the edge-point list !"));
}
}
}
}

std::cout << "All the edge-point list tests went well !" << std::endl;
}

void usage(const std::string &softName, const SoftwareArguments &options)
{
std::cout << "NAME" << std::endl;
std::cout << softName << ": software to test the vpCannyEdgeComputation class and vpImageFilter::canny method" << std::endl;
Expand All @@ -132,6 +214,7 @@ void usage(const std::string &softName, int gaussianKernelSize, float gaussianSt
<< " [-f, --filter <filterName>]"
<< " [-r, --ratio <lowerThreshRatio upperThreshRatio>]"
<< " [-b, --backend <backendName>]"
<< " [-e, --edge-list]" << std::endl
<< " [-h, --help]" << std::endl
<< std::endl;
std::cout << "DESCRIPTION" << std::endl;
Expand All @@ -143,35 +226,39 @@ void usage(const std::string &softName, int gaussianKernelSize, float gaussianSt
<< "\t\tPermits to compute the gradients of the image outside the vpCanny class." << std::endl
<< "\t\tFirst parameter is the size of the Gaussian kernel used to compute the gradients." << std::endl
<< "\t\tSecond parameter is the standard deviation of the Gaussian kernel used to compute the gradients." << std::endl
<< "\t\tDefault: " << gaussianKernelSize << " " << gaussianStdev << std::endl
<< "\t\tDefault: " << options.m_gaussianKernelSize << " " << options.m_gaussianStdev << std::endl
<< std::endl;
std::cout << "\t-t, --thresh <lowerThresh upperThresh>" << std::endl
<< "\t\tPermits to set the lower and upper thresholds of the vpCanny class." << std::endl
<< "\t\tFirst parameter is the lower threshold." << std::endl
<< "\t\tSecond parameter is the upper threshold." << std::endl
<< "\t\tWhen set to -1 thresholds are computed automatically." << std::endl
<< "\t\tDefault: " << lowerThresh << " " << upperThresh << std::endl
<< "\t\tDefault: " << options.m_lowerThresh << " " << options.m_upperThresh << std::endl
<< std::endl;
std::cout << "\t-a, --aperture <apertureSize>" << std::endl
<< "\t\tPermits to set the size of the gradient filter kernel." << std::endl
<< "\t\tParameter must be odd and positive." << std::endl
<< "\t\tDefault: " << apertureSize << std::endl
<< "\t\tDefault: " << options.m_apertureSize << std::endl
<< std::endl;
std::cout << "\t-f, --filter <filterName>" << std::endl
<< "\t\tPermits to choose the type of filter to apply to compute the gradient." << std::endl
<< "\t\tAvailable values: " << vpImageFilter::vpGetCannyFiltAndGradTypes("<", " | ", ">") << std::endl
<< "\t\tDefault: " << vpImageFilter::vpCannyFiltAndGradTypeToStr(filteringType) << std::endl
<< "\t\tDefault: " << vpImageFilter::vpCannyFiltAndGradTypeToStr(options.m_filteringType) << std::endl
<< std::endl;
std::cout << "\t-r, --ratio <lowerThreshRatio upperThreshRatio>" << std::endl
<< "\t\tPermits to set the lower and upper thresholds ratio of the vpCanny class." << std::endl
<< "\t\tFirst parameter is the lower threshold ratio." << std::endl
<< "\t\tSecond parameter is the upper threshold ratio." << std::endl
<< "\t\tDefault: " << lowerThreshRatio << " " << upperThreshRatio << std::endl
<< "\t\tDefault: " << options.m_lowerThreshRatio << " " << options.m_upperThreshRatio << std::endl
<< std::endl;
std::cout << "\t-b, --backend <backendName>" << std::endl
<< "\t\tPermits to use the vpImageFilter::canny method for comparison." << std::endl
<< "\t\tAvailable values: " << vpImageFilter::vpCannyBackendTypeList("<", " | ", ">") << std::endl
<< "\t\tDefault: " << vpImageFilter::vpCannyBackendTypeToString(backend) << std::endl
<< "\t\tDefault: " << vpImageFilter::vpCannyBackendTypeToString(options.m_backend) << std::endl
<< std::endl;
std::cout << "\t-e, --edge-list" << std::endl
<< "\t\tPermits to save the edge list." << std::endl
<< "\t\tDefault: OFF" << std::endl
<< std::endl;
std::cout << "\t-h, --help" << std::endl
<< "\t\tPermits to display the different arguments this software handles." << std::endl
Expand All @@ -180,56 +267,47 @@ void usage(const std::string &softName, int gaussianKernelSize, float gaussianSt

int main(int argc, const char *argv[])
{
std::string opt_img;
bool opt_gradientOutsideClass = false;
bool opt_useVpImageFilterCanny = false;
int opt_gaussianKernelSize = 3;
int opt_apertureSize = 3;
vpImageFilter::vpCannyFilteringAndGradientType opt_filteringType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING;
float opt_gaussianStdev = 1.;
float opt_lowerThresh = -1.;
float opt_upperThresh = -1.;
float opt_lowerThreshRatio = 0.6f;
float opt_upperThreshRatio = 0.8f;
vpImageFilter::vpCannyBackendType opt_backend = vpImageFilter::CANNY_VISP_BACKEND;
SoftwareArguments options;
for (int i = 1; i < argc; i++) {
std::string argv_str = std::string(argv[i]);
if ((argv_str == "-i" || argv_str == "--image") && i + 1 < argc) {
opt_img = std::string(argv[i + 1]);
options.m_img = std::string(argv[i + 1]);
i++;
}
else if ((argv_str == "-g" || argv_str == "--gradient") && i + 2 < argc) {
opt_gradientOutsideClass = true;
opt_gaussianKernelSize = atoi(argv[i + 1]);
opt_gaussianStdev = static_cast<float>(atof(argv[i + 2]));
options.m_gradientOutsideClass = true;
options.m_gaussianKernelSize = atoi(argv[i + 1]);
options.m_gaussianStdev = static_cast<float>(atof(argv[i + 2]));
i += 2;
}
else if ((argv_str == "-t" || argv_str == "--thresh") && i + 2 < argc) {
opt_lowerThresh = static_cast<float>(atof(argv[i + 1]));
opt_upperThresh = static_cast<float>(atof(argv[i + 2]));
options.m_lowerThresh = static_cast<float>(atof(argv[i + 1]));
options.m_upperThresh = static_cast<float>(atof(argv[i + 2]));
i += 2;
}
else if ((argv_str == "-a" || argv_str == "--aperture") && i + 1 < argc) {
opt_apertureSize = std::atoi(argv[i + 1]);
options.m_apertureSize = std::atoi(argv[i + 1]);
i++;
}
else if ((argv_str == "-f" || argv_str == "--filter") && i + 1 < argc) {
opt_filteringType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(std::string(argv[i + 1]));
options.m_filteringType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(std::string(argv[i + 1]));
i++;
}
else if ((argv_str == "-r" || argv_str == "--ratio") && i + 2 < argc) {
opt_lowerThreshRatio = static_cast<float>(std::atof(argv[i + 1]));
opt_upperThreshRatio = static_cast<float>(std::atof(argv[i + 2]));
options.m_lowerThreshRatio = static_cast<float>(std::atof(argv[i + 1]));
options.m_upperThreshRatio = static_cast<float>(std::atof(argv[i + 2]));
i += 2;
}
else if ((argv_str == "-b" || argv_str == "--backend") && i + 1 < argc) {
opt_useVpImageFilterCanny = true;
opt_backend = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1]));
options.m_useVpImageFilterCanny = true;
options.m_backend = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1]));
i++;
}
else if ((argv_str == "-e") || (argv_str == "--edge-list")) {
options.m_saveEdgeList = true;
}
else if (argv_str == "-h" || argv_str == "--help") {
usage(std::string(argv[0]), opt_gaussianKernelSize, opt_gaussianStdev, opt_lowerThresh, opt_upperThresh,
opt_apertureSize, opt_filteringType, opt_lowerThreshRatio, opt_upperThreshRatio, opt_backend);
usage(std::string(argv[0]), options);
return EXIT_SUCCESS;
}
else {
Expand All @@ -239,33 +317,33 @@ int main(int argc, const char *argv[])
}

std::string configAsTxt("Canny Configuration:\n");
configAsTxt += "\tFiltering + gradient operators = " + vpImageFilter::vpCannyFiltAndGradTypeToStr(opt_filteringType) + "\n";
configAsTxt += "\tFiltering + gradient operators = " + vpImageFilter::vpCannyFiltAndGradTypeToStr(options.m_filteringType) + "\n";
#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
configAsTxt += "\tGaussian filter kernel size = " + std::to_string(opt_gaussianKernelSize) + "\n";
configAsTxt += "\tGaussian filter standard deviation = " + std::to_string(opt_gaussianStdev) + "\n";
configAsTxt += "\tGradient filter kernel size = " + std::to_string(opt_apertureSize) + "\n";
configAsTxt += "\tCanny edge filter thresholds = [" + std::to_string(opt_lowerThresh) + " ; " + std::to_string(opt_upperThresh) + "]\n";
configAsTxt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(opt_lowerThreshRatio) + " ; " + std::to_string(opt_upperThreshRatio) + "]\n";
configAsTxt += "\tGaussian filter kernel size = " + std::to_string(options.m_gaussianKernelSize) + "\n";
configAsTxt += "\tGaussian filter standard deviation = " + std::to_string(options.m_gaussianStdev) + "\n";
configAsTxt += "\tGradient filter kernel size = " + std::to_string(options.m_apertureSize) + "\n";
configAsTxt += "\tCanny edge filter thresholds = [" + std::to_string(options.m_lowerThresh) + " ; " + std::to_string(options.m_upperThresh) + "]\n";
configAsTxt += "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" + std::to_string(options.m_lowerThreshRatio) + " ; " + std::to_string(options.m_upperThreshRatio) + "]\n";
#else
{
std::stringstream ss;
ss << "\tGaussian filter kernel size = " << opt_gaussianKernelSize << "\n";
ss << "\tGaussian filter standard deviation = " << opt_gaussianStdev << "\n";
ss << "\tGradient filter kernel size = " << opt_apertureSize << "\n";
ss << "\tCanny edge filter thresholds = [" << opt_lowerThresh << " ; " << opt_upperThresh << "]\n";
ss << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << opt_lowerThreshRatio << " ; " << opt_upperThreshRatio << "]\n";
ss << "\tGaussian filter kernel size = " << options.m_gaussianKernelSize << "\n";
ss << "\tGaussian filter standard deviation = " << options.m_gaussianStdev << "\n";
ss << "\tGradient filter kernel size = " << options.m_apertureSize << "\n";
ss << "\tCanny edge filter thresholds = [" << options.m_lowerThresh << " ; " << options.m_upperThresh << "]\n";
ss << "\tCanny edge filter thresholds ratio (for auto-thresholding) = [" << options.m_lowerThreshRatio << " ; " << options.m_upperThreshRatio << "]\n";
configAsTxt += ss.str();
}
#endif
std::cout << configAsTxt << std::endl;

vpCannyEdgeDetection cannyDetector(opt_gaussianKernelSize, opt_gaussianStdev, opt_apertureSize,
opt_lowerThresh, opt_upperThresh, opt_lowerThreshRatio, opt_upperThreshRatio,
opt_filteringType);
vpCannyEdgeDetection cannyDetector(options.m_gaussianKernelSize, options.m_gaussianStdev, options.m_apertureSize,
options.m_lowerThresh, options.m_upperThresh, options.m_lowerThreshRatio, options.m_upperThreshRatio,
options.m_filteringType, options.m_saveEdgeList);
vpImage<unsigned char> I_canny_input, I_canny_visp, dIx_uchar, dIy_uchar, I_canny_imgFilter;
if (!opt_img.empty()) {
if (!options.m_img.empty()) {
// Detection on the user image
vpImageIo::read(I_canny_input, opt_img);
vpImageIo::read(I_canny_input, options.m_img);
}
else {
// Detection on a fake image of a square
Expand All @@ -281,24 +359,27 @@ int main(int argc, const char *argv[])
I_canny_visp = I_canny_imgFilter = dIx_uchar = dIy_uchar = I_canny_input;
vpImage<unsigned char> *p_dIx = nullptr, *p_dIy = nullptr, *p_IcannyImgFilter = nullptr;

if (opt_gradientOutsideClass) {
if (options.m_gradientOutsideClass) {
p_dIx = &dIx_uchar;
p_dIy = &dIy_uchar;
}

if (opt_useVpImageFilterCanny) {
if (options.m_useVpImageFilterCanny) {
p_IcannyImgFilter = &I_canny_imgFilter;
}
drawingHelpers::init(I_canny_input, I_canny_visp, p_dIx, p_dIy, p_IcannyImgFilter);

// Computing the gradient outside the vpCannyEdgeDetection class if asked
if (opt_gradientOutsideClass) {
setGradientOutsideClass(I_canny_input, opt_gaussianKernelSize, opt_gaussianStdev, cannyDetector, opt_apertureSize,
opt_filteringType, dIx_uchar, dIy_uchar);
if (options.m_gradientOutsideClass) {
setGradientOutsideClass(I_canny_input, options.m_gaussianKernelSize, options.m_gaussianStdev, cannyDetector, options.m_apertureSize,
options.m_filteringType, dIx_uchar, dIy_uchar);
}
I_canny_visp = cannyDetector.detect(I_canny_input);
float mean, max, stdev;
computeMeanMaxStdev(I_canny_input, mean, max, stdev);
if (options.m_saveEdgeList) {
checkEdgeList(cannyDetector, I_canny_visp);
}
#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
std::string title("Input of the Canny edge detector. Mean = " + std::to_string(mean) + "+/-" + std::to_string(stdev) + " Max = " + std::to_string(max));
#else
Expand All @@ -310,15 +391,15 @@ int main(int argc, const char *argv[])
}
#endif
drawingHelpers::display(I_canny_input, title);
drawingHelpers::display(I_canny_visp, "Canny results on image " + opt_img);
drawingHelpers::display(I_canny_visp, "Canny results on image " + options.m_img);

if (opt_useVpImageFilterCanny) {
float cannyThresh = opt_upperThresh;
float lowerThresh(opt_lowerThresh);
vpImageFilter::canny(I_canny_input, I_canny_imgFilter, opt_gaussianKernelSize, lowerThresh, cannyThresh,
opt_apertureSize, opt_gaussianStdev, opt_lowerThreshRatio, opt_upperThreshRatio, true,
opt_backend, opt_filteringType);
drawingHelpers::display(I_canny_imgFilter, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(opt_backend) + "\" backend");
if (options.m_useVpImageFilterCanny) {
float cannyThresh = options.m_upperThresh;
float lowerThresh(options.m_lowerThresh);
vpImageFilter::canny(I_canny_input, I_canny_imgFilter, options.m_gaussianKernelSize, lowerThresh, cannyThresh,
options.m_apertureSize, options.m_gaussianStdev, options.m_lowerThreshRatio, options.m_upperThreshRatio, true,
options.m_backend, options.m_filteringType);
drawingHelpers::display(I_canny_imgFilter, "Canny results with \"" + vpImageFilter::vpCannyBackendTypeToString(options.m_backend) + "\" backend");
}

drawingHelpers::waitForClick(I_canny_input, true);
Expand Down

0 comments on commit 3d1ed82

Please sign in to comment.