Visual Servoing Platform  version 3.6.1 under development (2024-12-17)
tutorial-circle-hough.cpp
#include <iostream>
// ViSP includes
#include <visp3/core/vpConfig.h>
#include <visp3/core/vpException.h>
#include <visp3/core/vpImage.h>
#include <visp3/core/vpImageConvert.h>
#include <visp3/core/vpImageDraw.h>
#include <visp3/core/vpIoTools.h>
#include <visp3/core/vpTime.h>
#include <visp3/gui/vpDisplayFactory.h>
#include <visp3/imgproc/vpCircleHoughTransform.h>
#include <visp3/imgproc/vpImgproc.h>
#include <visp3/io/vpImageIo.h>
#include <visp3/io/vpVideoReader.h>
#include "drawingHelpers.h"
#ifdef ENABLE_VISP_NAMESPACE
using namespace VISP_NAMESPACE_NAME;
#endif
bool run_detection(const vpImage<unsigned char> &I_src, vpImage<vpRGBa> &I_disp, vpImage<vpRGBa> &I_dispCanny,
vpCircleHoughTransform &detector, const int &nbCirclesToDetect, const bool &blockingMode,
const bool &displayCanny)
{
std::vector<vpImageCircle> detectedCircles = detector.detect(I_src, nbCirclesToDetect);
std::vector<float> probas = detector.getDetectionsProbabilities();
std::cout << "Process time = " << (tF - t0) * 0.001 << "ms" << std::endl << std::flush;
vpImageConvert::convert(I_src, I_disp);
unsigned int id = 0;
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
#else
std::vector<vpColor> v_colors;
v_colors.push_back(vpColor::red);
v_colors.push_back(vpColor::purple);
v_colors.push_back(vpColor::orange);
v_colors.push_back(vpColor::yellow);
v_colors.push_back(vpColor::blue);
#endif
unsigned int idColor = 0;
const unsigned int nbCircle = static_cast<unsigned int>(detectedCircles.size());
for (unsigned int idCircle = 0; idCircle < nbCircle; ++idCircle) {
const vpImageCircle &circleCandidate = detectedCircles[idCircle];
vpImageDraw::drawCircle(I_disp, circleCandidate, v_colors[idColor], 2);
std::cout << "Circle #" << id << ":" << std::endl;
std::cout << "\tCenter: (" << circleCandidate.getCenter() << ")" << std::endl;
std::cout << "\tRadius: (" << circleCandidate.getRadius() << ")" << std::endl;
std::cout << "\tProba: " << probas[id] << std::endl;
std::cout << "\tTheoretical arc length: " << circleCandidate.computeArcLengthInRoI(vpRect(0, 0, I_src.getWidth(), I_src.getHeight())) << std::endl;
id++;
idColor = (idColor + 1) % v_colors.size();
}
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
std::optional<vpImage<bool>> opt_mask = std::nullopt;
std::optional<std::vector<std::vector<std::pair<unsigned int, unsigned int> > > > opt_votingPoints = std::nullopt;
#else
vpImage<bool> *opt_mask = nullptr;
std::vector<std::vector<std::pair<unsigned int, unsigned int> > > *opt_votingPoints = nullptr;
detector.computeVotingMask(I_src, detectedCircles, &opt_mask, &opt_votingPoints); // Get, if available, the voting points
#endif
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_17)
if (opt_votingPoints)
#else
if (opt_votingPoints != nullptr)
#endif
{
const unsigned int crossSize = 3;
const unsigned int crossThickness = 1;
unsigned int nbVotedCircles = static_cast<unsigned int>(opt_votingPoints->size());
for (unsigned int idCircle = 0; idCircle < nbVotedCircles; ++idCircle) {
// Get the voting points of a detected circle
const std::vector<std::pair<unsigned int, unsigned int> > &votingPoints = (*opt_votingPoints)[idCircle];
unsigned int nbVotingPoints = static_cast<unsigned int>(votingPoints.size());
for (unsigned int idPoint = 0; idPoint < nbVotingPoints; ++idPoint) {
// Draw the voting points
const std::pair<unsigned int, unsigned int> &pt = votingPoints[idPoint];
vpImageDraw::drawCross(I_disp, vpImagePoint(pt.first, pt.second), crossSize, vpColor::red, crossThickness);
}
}
}
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_17)
if (opt_mask != nullptr) {
delete opt_mask;
}
if (opt_votingPoints != nullptr) {
delete opt_votingPoints;
}
#endif
if (displayCanny) {
vpImage<unsigned char> edgeMap = detector.getEdgeMap();
drawingHelpers::display(edgeMap, I_dispCanny, "Edge map", blockingMode);
}
return drawingHelpers::display(I_disp, "Detection results", blockingMode);
}
int main(int argc, char **argv)
{
const std::string def_input("coins2.jpg");
const std::string def_jsonFilePath = std::string("");
const int def_nbCirclesToDetect = -1;
const int def_gaussianKernelSize = 5;
const float def_gaussianSigma = 1.f;
const int def_sobelKernelSize = 3;
const float def_lowerCannyThresh = -1.f;
const float def_upperCannyThresh = -1.f;
const int def_nbEdgeFilteringIter = 3;
const std::pair<int, int> def_centerXlimits = std::pair<int, int>(0, 1920);
const std::pair<int, int> def_centerYlimits = std::pair<int, int>(0, 1080);
const unsigned int def_minRadius = 34;
const unsigned int def_maxRadius = 75;
const int def_dilatationKernelSize = 5;
const float def_centerThresh = 70.f;
const float def_circleProbaThresh = 0.725f;
const float def_circlePerfectness = 0.85f;
const float def_centerDistanceThresh = 5.f;
const float def_radiusDifferenceThresh = 5.f;
const int def_averagingWindowSize = 5;
const float def_lowerCannyThreshRatio = 0.6f;
const float def_upperCannyThreshRatio = 0.9f;
const int def_expectedNbCenters = -1;
const bool def_recordVotingPoints = false;
const float def_visibilityRatioThresh = 0.1f;
std::string opt_input(def_input);
std::string opt_jsonFilePath = def_jsonFilePath;
int opt_nbCirclesToDetect = def_nbCirclesToDetect;
int opt_gaussianKernelSize = def_gaussianKernelSize;
float opt_gaussianSigma = def_gaussianSigma;
int opt_sobelKernelSize = def_sobelKernelSize;
float opt_lowerCannyThresh = def_lowerCannyThresh;
float opt_upperCannyThresh = def_upperCannyThresh;
int opt_nbEdgeFilteringIter = def_nbEdgeFilteringIter;
std::pair<int, int> opt_centerXlimits = def_centerXlimits;
std::pair<int, int> opt_centerYlimits = def_centerYlimits;
unsigned int opt_minRadius = def_minRadius;
unsigned int opt_maxRadius = def_maxRadius;
int opt_dilatationKerneSize = def_dilatationKernelSize;
float opt_centerThresh = def_centerThresh;
float opt_circleProbaThresh = def_circleProbaThresh;
float opt_circlePerfectness = def_circlePerfectness;
float opt_centerDistanceThresh = def_centerDistanceThresh;
float opt_radiusDifferenceThresh = def_radiusDifferenceThresh;
int opt_averagingWindowSize = def_averagingWindowSize;
vpImageFilter::vpCannyFilteringAndGradientType opt_filteringAndGradientType = def_filteringAndGradientType;
vpImageFilter::vpCannyBackendType opt_cannyBackendType = def_cannyBackendType;
float opt_lowerCannyThreshRatio = def_lowerCannyThreshRatio;
float opt_upperCannyThreshRatio = def_upperCannyThreshRatio;
int opt_expectedNbCenters = def_expectedNbCenters;
bool opt_recordVotingPoints = def_recordVotingPoints;
float opt_visibilityRatioThresh = def_visibilityRatioThresh;
bool opt_displayCanny = false;
for (int i = 1; i < argc; i++) {
std::string argName(argv[i]);
if (argName == "--input" && i + 1 < argc) {
opt_input = std::string(argv[i + 1]);
i++;
}
#ifdef VISP_HAVE_NLOHMANN_JSON
else if (argName == "--config" && i + 1 < argc) {
opt_jsonFilePath = std::string(argv[i + 1]);
i++;
}
#endif
else if (argName == "--nb-circles" && i + 1 < argc) {
opt_nbCirclesToDetect = atoi(argv[i + 1]);
i++;
}
else if (argName == "--gaussian-kernel" && i + 1 < argc) {
opt_gaussianKernelSize = atoi(argv[i + 1]);
i++;
}
else if (argName == "--gaussian-sigma" && i + 1 < argc) {
opt_gaussianSigma = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--gradient-kernel" && i + 1 < argc) {
opt_sobelKernelSize = atoi(argv[i + 1]);
i++;
}
else if (argName == "--canny-thresh" && i + 2 < argc) {
opt_lowerCannyThresh = static_cast<float>(atof(argv[i + 1]));
opt_upperCannyThresh = static_cast<float>(atof(argv[i + 2]));
i += 2;
}
else if (argName == "--edge-filter" && i + 1 < argc) {
opt_nbEdgeFilteringIter = atoi(argv[i + 1]);
i++;
}
else if (argName == "--dilatation-kernel-size" && i + 1 < argc) {
opt_dilatationKerneSize = atoi(argv[i + 1]);
i++;
}
else if (argName == "--averaging-window-size" && i + 1 < argc) {
opt_averagingWindowSize = atoi(argv[i + 1]);
i++;
}
else if (argName == "--radius-limits" && i + 2 < argc) {
opt_minRadius = atoi(argv[i + 1]);
opt_maxRadius = atoi(argv[i + 2]);
i += 2;
}
else if (argName == "--center-thresh" && i + 1 < argc) {
opt_centerThresh = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--center-xlim" && i + 2 < argc) {
opt_centerXlimits = std::pair<int, int>(atoi(argv[i + 1]), atoi(argv[i + 2]));
i += 2;
}
else if (argName == "--center-ylim" && i + 2 < argc) {
opt_centerYlimits = std::pair<int, int>(atoi(argv[i + 1]), atoi(argv[i + 2]));
i += 2;
}
else if (argName == "--circle-probability-thresh" && i + 1 < argc) {
opt_circleProbaThresh = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--circle-perfectness" && i + 1 < argc) {
opt_circlePerfectness = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--merging-thresh" && i + 2 < argc) {
opt_centerDistanceThresh = static_cast<float>(atof(argv[i + 1]));
opt_radiusDifferenceThresh = static_cast<float>(atof(argv[i + 2]));
i += 2;
}
else if (argName == "--filtering-type" && i + 1 < argc) {
opt_filteringAndGradientType = vpImageFilter::vpCannyFiltAndGradTypeFromStr(std::string(argv[i+1]));
i++;
}
else if (argName == "--canny-backend" && i + 1 < argc) {
opt_cannyBackendType = vpImageFilter::vpCannyBackendTypeFromString(std::string(argv[i+1]));
i++;
}
else if (argName == "--lower-canny-ratio" && i + 1 < argc) {
opt_lowerCannyThreshRatio = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--upper-canny-ratio" && i + 1 < argc) {
opt_upperCannyThreshRatio = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--expected-nb-centers" && i + 1 < argc) {
opt_expectedNbCenters = atoi(argv[i + 1]);
i++;
}
else if (argName == "--visibility-ratio-thresh" && i + 1 < argc) {
opt_visibilityRatioThresh = static_cast<float>(atof(argv[i + 1]));
i++;
}
else if (argName == "--record-voting-points") {
opt_recordVotingPoints = true;
}
else if (argName == "--display-edge-map") {
opt_displayCanny = true;
}
else if (argName == "--help" || argName == "-h") {
std::cout << "NAME" << std::endl;
std::cout << "\t" << argv[0] << " Test program for the home-made Hough Circle Detection algorithm" << std::endl
<< std::endl;
std::cout << "SYNOPSIS" << std::endl;
std::cout << "\t" << argv[0]
<< "\t [--input <path/to/file>]" << std::endl
#ifdef VISP_HAVE_NLOHMANN_JSON
<< "\t [--config <path/to/json/file>] (default: " << (def_jsonFilePath.empty() ? "unused" : def_jsonFilePath) << ")" << std::endl
#endif
<< "\t [--nb-circles <number-circles-to-detect>] (default: " << def_nbCirclesToDetect << ")" << std::endl
<< "\t [--gaussian-kernel <kernel-size>] (default: " << def_gaussianKernelSize << ")" << std::endl
<< "\t [--gaussian-sigma <stddev>] (default: " << def_gaussianSigma << ")" << std::endl
<< "\t [--gradient-kernel <kernel-size>] (default: " << def_sobelKernelSize << ")" << std::endl
<< "\t [--canny-thresh <lower-canny-thresh upper-canny-thresh>] (default: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << ")" << std::endl
<< "\t [--edge-filter <nb-iter>] (default: " << def_nbEdgeFilteringIter << ")" << std::endl
<< "\t [--radius-limits <radius-min> <radius-max>] (default: min = " << def_minRadius << ", max = " << def_maxRadius << ")" << std::endl
<< "\t [--dilatation-kernel-size <kernel-size>] (default: " << def_dilatationKernelSize << ")" << std::endl
<< "\t [--averaging-window-size <size>] (default: " << def_averagingWindowSize << ")" << std::endl
<< "\t [--center-thresh <center-detection-threshold>] (default: " << def_centerThresh << ")" << std::endl
<< "\t [--center-xlim <center-horizontal-min center-horizontal-max>] (default: " << def_centerXlimits.first << " , " << def_centerXlimits.second << ")" << std::endl
<< "\t [--center-ylim <center-vertical-min center-vertical-max>] (default: " << def_centerYlimits.first << " , " << def_centerYlimits.second << ")" << std::endl
<< "\t [--circle-probability-thresh <probability-threshold>] (default: " << def_circleProbaThresh << ")" << std::endl
<< "\t [--circle-perfectness <circle-perfectness-threshold>] (default: " << def_circlePerfectness << ")" << std::endl
<< "\t [--merging-thresh <center-distance-thresh> <radius-difference-thresh>] (default: centers distance threshold = " << def_centerDistanceThresh << ", radius difference threshold = " << def_radiusDifferenceThresh << ")" << std::endl
<< "\t [--filtering-type <type-name>]"
<< " (default: " << vpImageFilter::vpCannyFiltAndGradTypeToStr(def_filteringAndGradientType) << ")" << std::endl
<< "\t [--canny-backend <backend-name>]"
<< " (default: " << vpImageFilter::vpCannyBackendTypeToString(def_cannyBackendType) << ")" << std::endl
<< "\t [--lower-canny-ratio <value>]"
<< " (default: " << def_lowerCannyThreshRatio<< ")" << std::endl
<< "\t [--upper-canny-ratio <value>]"
<< " (default: " << def_upperCannyThreshRatio << ")" << std::endl
<< "\t [--expected-nb-centers <number>]"
#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
<< " (default: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << ")" << std::endl
#else
<< std::endl
#endif
<< "\t [--visibility-ratio-thresh <ratio ]0; 1[> ]"
<< " (default: " << def_visibilityRatioThresh << ")" << std::endl
<< "\t [--record-voting-points]" << std::endl
<< "\t [--display-edge-map]" << std::endl
<< "\t [--help, -h]" << std::endl
<< std::endl;
std::cout << "DESCRIPTION" << std::endl
<< "\t--input" << std::endl
<< "\t\tPermit to choose the input of the Hough Circle Algorithm." << std::endl
<< "\t\tIf you want to use a succession of images as video, their name must be in the format ${BASENAME}%d.{jpg, png}." << std::endl
<< "\t\tDefault: " << def_input << std::endl
<< std::endl
#ifdef VISP_HAVE_NLOHMANN_JSON
<< "\t--config" << std::endl
<< "\t\tPermit to configure the Hough Circle Algorithm using a JSON file." << std::endl
<< "\t\tDefault: " << (def_jsonFilePath.empty() ? "unused" : def_jsonFilePath) << std::endl
<< std::endl
#endif
<< "\t--nb-circles" << std::endl
<< "\t\tPermit to choose the number of circles we want to detect in the image" << std::endl
<< "\t\tThe results will be the circles having the greatest number of votes." << std::endl
<< "\t\tDefault: " << def_nbCirclesToDetect << std::endl
<< std::endl
<< "\t--gaussian-kernel" << std::endl
<< "\t\tPermit to set the size of the Gaussian filter used to smooth the input image and compute its gradients." << std::endl
<< "\t\tMust be an odd value." << std::endl
<< "\t\tDefault: " << def_gaussianKernelSize << std::endl
<< std::endl
<< "\t--gaussian-sigma" << std::endl
<< "\t\tPermit to set the standard deviation of the Gaussian filter." << std::endl
<< "\t\tMust be a positive value." << std::endl
<< "\t\tDefault: " << def_gaussianSigma << std::endl
<< std::endl
<< "\t--gradient-kernel" << std::endl
<< "\t\tPermit to set the size of the Gaussian filter used to smooth the input image and compute its gradients." << std::endl
<< "\t\tMust be an odd value." << std::endl
<< "\t\tDefault: " << def_gaussianKernelSize << std::endl
<< std::endl
<< "\t--canny-thresh" << std::endl
<< "\t\tPermit to set the lower and upper thresholds of the Canny edge detector." << std::endl
<< "\t\tIf a value is negative, it will be automatically computed." << std::endl
<< "\t\tDefault: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << std::endl
<< std::endl
<< "\t--edge-filter" << std::endl
<< "\t\tPermit to set the number of iteration of 8-neighbor filter iterations of the result of the Canny edge detector." << std::endl
<< "\t\tIf negative, no filtering is performed." << std::endl
<< "\t\tDefault: " << def_nbEdgeFilteringIter << std::endl
<< std::endl
<< "\t--radius-limits" << std::endl
<< "\t\tPermit to set the minimum and maximum radii of the circles we are looking for." << std::endl
<< "\t\tDefault: min = " << def_minRadius << ", max = " << def_maxRadius << std::endl
<< std::endl
<< "\t--dilatation-kernel-size" << std::endl
<< "\t\tPermit to set the size of the kernel of the dilatation operation used to detect the maxima of the centers votes." << std::endl
<< "\t\tMinimum tolerated value is 1." << std::endl
<< "\t\tDefault: " << def_dilatationKernelSize << std::endl
<< std::endl
<< "\t--averaging-window-size" << std::endl
<< "\t\tPermit to set the number size of the averaging window used to detect the maxima of the centers votes." << std::endl
<< "\t\tMust be odd." << std::endl
<< "\t\tDefault: " << def_averagingWindowSize << std::endl
<< std::endl
<< "\t--center-thresh" << std::endl
<< "\t\tPermit to set the minimum number of votes a point must reach to be considered as a center candidate." << std::endl
<< "\t\tIf the input is a real image, must be a positive value." << std::endl
<< "\t\tOtherwise, if the input is a synthetic image and the value is negative, a fine-tuned value will be used." << std::endl
<< "\t\tDefault: " << def_centerThresh << std::endl
<< std::endl
<< "\t--center-xlim" << std::endl
<< "\t\tPermit to set the minimum and maximum horizontal position to be considered as a center candidate." << std::endl
<< "\t\tThe search area is limited to [-maxRadius; +image.width + maxRadius]." << std::endl
<< "\t\tDefault: " << def_centerXlimits.first << " , " << def_centerXlimits.second << std::endl
<< std::endl
<< "\t--center-ylim" << std::endl
<< "\t\tPermit to set the minimum and maximum vertical position to be considered as a center candidate." << std::endl
<< "\t\tThe search area is limited to [-maxRadius; +image.height + maxRadius]." << std::endl
<< "\t\tDefault: " << def_centerYlimits.first << " , " << def_centerYlimits.second << std::endl
<< std::endl
<< "\t--circle-probability-thresh" << std::endl
<< "\t\tPermit to to set the minimum probability a circle must reach to be kept." << std::endl
<< "\t\tDefault: " << def_circleProbaThresh << std::endl
<< std::endl
<< "\t--circle-perfectness" << std::endl
<< "\t\tPermit to set the set the circle perfectness threshold." << std::endl
<< "\t\tThis parameter is used during the radius candidates computation." << std::endl
<< "\t\tThe scalar product radius RC_ij . gradient(Ep_j) >= m_circlePerfectness * || RC_ij || * || gradient(Ep_j) || to add a vote for the radius RC_ij." << std::endl
<< "\t\tDefault: " << def_circlePerfectness << std::endl
<< std::endl
<< "\t--merging-thresh" << std::endl
<< "\t\tPermit to set the thresholds used during the merging stage of the algorithm." << std::endl
<< "\t\tThe center distance threshold indicates the maximum distance the centers can be in order to be merged." << std::endl
<< "\t\tThe radius difference threshold indicates the maximum absolute difference between the two circle candidates in order to be merged." << std::endl
<< "\t\tTwo circle candidates must met these two conditions in order to be merged together." << std::endl
<< "\t\tDefault: centers distance threshold = " << def_centerDistanceThresh << ", radius difference threshold = " << def_radiusDifferenceThresh << std::endl
<< std::endl
<< "\t--filtering-type" << std::endl
<< "\t\tPermit to choose the gradient filters." << std::endl
<< "\t\tDefault: " << vpImageFilter::vpCannyFiltAndGradTypeToStr(def_filteringAndGradientType) << ", available: " << vpImageFilter::vpGetCannyFiltAndGradTypes() << std::endl
<< std::endl
<< "\t--canny-backend" << std::endl
<< "\t\tPermit to choose the backend used to compute the edge map." << std::endl
<< "\t\tDefault: " << vpImageFilter::vpCannyBackendTypeToString(def_cannyBackendType) << ", available: " << vpImageFilter::vpCannyBackendTypeList() << std::endl
<< std::endl
<< "\t--lower-canny-ratio" << std::endl
<< "\t\tPermit to choose the ratio for the lower threshold if automatic thresholding is chosen." << std::endl
<< "\t\tDefault: " << def_lowerCannyThreshRatio << std::endl
<< std::endl
<< "\t--upper-canny-ratio" << std::endl
<< "\t\tPermit to choose the ratio for the upper threshold if automatic thresholding is chosen." << std::endl
<< "\t\tDefault: " << def_upperCannyThreshRatio << std::endl
<< std::endl
<< "\t--expected-nb-centers" << std::endl
<< "\t\tPermit to choose the maximum number of centers having more votes than the threshold that are kept." << std::endl
<< "\t\tA negative value makes that all the centers having more votes than the threshold are kept." << std::endl
#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
<< "\t\tDefault: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << std::endl
#else
<< std::endl
#endif
<< std::endl
<< "\t--expected-nb-centers" << std::endl
<< "\t\tPermit to choose the maximum number of centers having more votes than the threshold that are kept." << std::endl
<< "\t\tA negative value makes that all the centers having more votes than the threshold are kept." << std::endl
#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
<< "\t\tDefault: " << (def_expectedNbCenters < 0 ? "no limits" : std::to_string(def_expectedNbCenters)) << std::endl
#else
<< std::endl
#endif
<< std::endl
<< "\t--record-voting-points" << std::endl
<< "\t\tPermit to display the edge map used to detect the circles" << std::endl
<< "\t\tDefault: off" << std::endl
<< std::endl
<< "\t--display-edge-map" << std::endl
<< "\t\tPermit to display the edge map used to detect the circles" << std::endl
<< "\t\tDefault: off" << std::endl
<< std::endl;
return EXIT_SUCCESS;
}
}
algoParams(opt_gaussianKernelSize
, opt_gaussianSigma
, opt_sobelKernelSize
, opt_lowerCannyThresh
, opt_upperCannyThresh
, opt_nbEdgeFilteringIter
, opt_centerXlimits
, opt_centerYlimits
, static_cast<float>(opt_minRadius)
, static_cast<float>(opt_maxRadius)
, opt_dilatationKerneSize
, opt_averagingWindowSize
, opt_centerThresh
, opt_circleProbaThresh
, opt_circlePerfectness
, opt_centerDistanceThresh
, opt_radiusDifferenceThresh
, opt_filteringAndGradientType
, opt_cannyBackendType
, opt_lowerCannyThreshRatio
, opt_upperCannyThreshRatio
, opt_expectedNbCenters
, opt_recordVotingPoints
, opt_visibilityRatioThresh
);
if (opt_jsonFilePath.empty()) {
std::cout << "Initializing detector from the program arguments [...]" << std::endl;
detector.init(algoParams);
}
else {
#ifdef VISP_HAVE_NLOHMANN_JSON
std::cout << "Initializing detector from JSON file \"" << opt_jsonFilePath << "\", some of the program arguments will be ignored [...]" << std::endl;
detector.initFromJSON(opt_jsonFilePath);
#else
throw(vpException(vpException::functionNotImplementedError, "You must install nlohmann JSON library to use this feature, see https://visp-doc.inria.fr/doxygen/visp-daily/supported-third-parties.html#soft_tool_json for more information."));
#endif
}
std::cout << detector;
vpImage<vpRGBa> I_dispCanny;
if (opt_input.find("%") != std::string::npos) {
// The user wants to read a sequence of images from different files
bool hasToContinue = true;
g.setFileName(opt_input);
g.open(I_src);
I_disp.resize(I_src.getHeight(), I_src.getWidth());
I_dispCanny.resize(I_src.getHeight(), I_src.getWidth());
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
std::shared_ptr<vpDisplay> dColor = vpDisplayFactory::createDisplay(I_disp, -1, -1, "Input image");;
std::shared_ptr<vpDisplay> dCanny(nullptr);
if (opt_displayCanny) {
dCanny = vpDisplayFactory::createDisplay(I_dispCanny, I_src.getWidth() + 40, -1, "Edge-map");
}
#else
vpDisplay *dColor = vpDisplayFactory::allocateDisplay(I_disp, -1, -1, "Input image");;
vpDisplay *dCanny(nullptr);
if (opt_displayCanny) {
dCanny = vpDisplayFactory::allocateDisplay(I_dispCanny, I_src.getWidth() + 40, -1, "Edge-map");
}
#endif
while (!g.end() && hasToContinue) {
g.acquire(I_src);
hasToContinue = run_detection(I_src, I_disp, I_dispCanny, detector, opt_nbCirclesToDetect, false, opt_displayCanny);
}
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
delete dColor;
if (dCanny != nullptr) {
if (opt_displayCanny) {
delete dCanny;
}
}
#endif
}
else {
// Check if opt_input exists
if (!vpIoTools::checkFilename(opt_input)) {
throw(vpException(vpException::ioError, "Input file \"" + opt_input + "\" does not exist !"));
}
// Read the image and perform detection on it
vpImageIo::read(I_src, opt_input);
I_disp.resize(I_src.getHeight(), I_src.getWidth());
I_dispCanny.resize(I_src.getHeight(), I_src.getWidth());
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
std::shared_ptr<vpDisplay> dColor = vpDisplayFactory::createDisplay(I_disp, -1, -1, "Input image");;
std::shared_ptr<vpDisplay> dCanny(nullptr);
if (opt_displayCanny) {
dCanny = vpDisplayFactory::createDisplay(I_dispCanny, I_src.getWidth() + 40, -1, "Edge-map");
}
#else
vpDisplay *dColor = vpDisplayFactory::allocateDisplay(I_disp, -1, -1, "Input image");;
vpDisplay *dCanny(nullptr);
if (opt_displayCanny) {
dCanny = vpDisplayFactory::allocateDisplay(I_dispCanny, I_src.getWidth() + 40, -1, "Edge-map");
}
#endif
run_detection(I_src, I_disp, I_dispCanny, detector, opt_nbCirclesToDetect, true, opt_displayCanny);
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
delete dColor;
if (dCanny != nullptr) {
if (opt_displayCanny) {
delete dCanny;
}
}
#endif
}
return EXIT_SUCCESS;
}
Class that gather the algorithm parameters.
Class that permits to detect 2D circles in a image using the gradient-based Circle Hough transform....
void computeVotingMask(const vpImage< unsigned char > &I, const std::vector< vpImageCircle > &detections, std::optional< vpImage< bool > > &mask, std::optional< std::vector< std::vector< std::pair< unsigned int, unsigned int >>>> &opt_votingPoints) const
Compute the mask containing pixels that voted for the detections.
vpImage< unsigned char > getEdgeMap() const
Get the Edge Map computed thanks to the Canny edge filter.
virtual std::vector< vpImageCircle > detect(const vpImage< vpRGBa > &I)
Convert the input image in a gray-scale image and then perform Circle Hough Transform to detect the c...
void init(const vpCircleHoughTransformParams &algoParams)
Initialize all the algorithm parameters.
std::vector< float > getDetectionsProbabilities() const
virtual void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
static const vpColor red
Definition: vpColor.h:217
static const vpColor orange
Definition: vpColor.h:227
static const vpColor blue
Definition: vpColor.h:223
static const vpColor purple
Definition: vpColor.h:228
static const vpColor yellow
Definition: vpColor.h:225
Class that defines generic functionalities for display.
Definition: vpDisplay.h:178
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ ioError
I/O error.
Definition: vpException.h:67
@ functionNotImplementedError
Function not implemented.
Definition: vpException.h:66
Class that defines a 2D circle in an image.
Definition: vpImageCircle.h:57
float getRadius() const
vpImagePoint getCenter() const
float computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance=0.001f) const
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static void drawCircle(vpImage< unsigned char > &I, const vpImageCircle &circle, unsigned char color, unsigned int thickness=1)
static void drawCross(vpImage< unsigned char > &I, const vpImagePoint &ip, unsigned int size, unsigned char color, unsigned int thickness=1)
static std::string vpCannyBackendTypeToString(const vpCannyBackendType &type)
Cast a vpImageFilter::vpCannyBackendTypeToString into a string, to know its name.
static std::string vpCannyBackendTypeList(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyBackendType.
static std::string vpCannyFiltAndGradTypeToStr(const vpCannyFilteringAndGradientType &type)
Cast a vpImageFilter::vpCannyFilteringAndGradientType into a string, to know its name.
vpCannyFilteringAndGradientType
Canny filter and gradient operators to apply on the image before the edge detection stage.
Definition: vpImageFilter.h:90
@ CANNY_GBLUR_SCHARR_FILTERING
Apply Gaussian blur + Scharr operator on the input image.
Definition: vpImageFilter.h:92
vpCannyBackendType
Canny filter backends for the edge detection operations.
Definition: vpImageFilter.h:75
@ CANNY_OPENCV_BACKEND
Use OpenCV.
Definition: vpImageFilter.h:76
static vpCannyFilteringAndGradientType vpCannyFiltAndGradTypeFromStr(const std::string &name)
Cast a string into a vpImageFilter::vpCannyFilteringAndGradientType.
static vpCannyBackendType vpCannyBackendTypeFromString(const std::string &name)
Cast a string into a vpImageFilter::vpCannyBackendTypeToString.
static std::string vpGetCannyFiltAndGradTypes(const std::string &pref="<", const std::string &sep=" , ", const std::string &suf=">")
Get the list of available vpCannyFilteringAndGradientType.
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:147
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:82
unsigned int getWidth() const
Definition: vpImage.h:242
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:544
unsigned int getHeight() const
Definition: vpImage.h:181
static bool checkFilename(const std::string &filename)
Definition: vpIoTools.cpp:786
Defines a rectangle in the plane.
Definition: vpRect.h:79
Class that enables to manipulate easily a video file or a sequence of images. As it inherits from the...
void acquire(vpImage< vpRGBa > &I)
void open(vpImage< vpRGBa > &I)
void setFileName(const std::string &filename)
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.
vpDisplay * allocateDisplay()
Return a newly allocated vpDisplay specialization if a GUI library is available or nullptr otherwise.
VISP_EXPORT int wait(double t0, double t)
VISP_EXPORT double measureTimeMicros()