From 6102392774d28c83f6393a590ef9e128182ba169 Mon Sep 17 00:00:00 2001 From: rlagneau Date: Thu, 7 Dec 2023 16:07:18 +0100 Subject: [PATCH] [TUTO] Improved tutorial and removed synthetic images --- doc/tutorial/imgproc/tutorial-imgproc-cht.dox | 106 ++++--- .../hough-transform/config/detector_img.json | 2 +- .../hough-transform/tutorial-circle-hough.cpp | 295 ++++-------------- 3 files changed, 118 insertions(+), 285 deletions(-) diff --git a/doc/tutorial/imgproc/tutorial-imgproc-cht.dox b/doc/tutorial/imgproc/tutorial-imgproc-cht.dox index 852fdf6fe2..0c7411425a 100644 --- a/doc/tutorial/imgproc/tutorial-imgproc-cht.dox +++ b/doc/tutorial/imgproc/tutorial-imgproc-cht.dox @@ -22,7 +22,9 @@ we vote along a straight line that follows the gradient. Then, during the step where the algorithm votes for radius candidates for each center candidate, we check the colinearity between the gradient at a considered point and the line which links the point towards the center candidate. If they are "enough" colinear, we increment the corresponding -radius bin vote by 1. The "enough" characteristic is controlled by the circle perfectness +radius bin vote by 1. (*NB*: instead of incrementing one bin by one, we increment two bins by a number between +0 and 1 in our implementation to be more robust against the limits min and max of the radius and +the bin size). The "enough" characteristic is controlled by the circle perfectness parameter. \image html img-tutorial-cht-radius-votes.png @@ -40,44 +42,53 @@ $ cd tutorial/imgproc/hough-transform $ ./tutorial-circle-hough --help ``` -\subsection imgproc_cht_howto_synthetic How to use synthetic images - - -To run the software on the synthetic images using a JSON configuration file, -please run: +To run the software on an image like `coins2.jpg` provided with the tutorial and using a JSON configuration file, please run: ``` -$ TARGET=full # or TARGET=half # or TARGET=quarter -$ ./tutorial-circle-hough --input ${TARGET}_disks --config config/detector_${TARGET}.json +$ ./tutorial-circle-hough --input coins2.jpg --config config/detector_img.json ``` -To run the software on the synthetic images using the default parameters, -please run: -``` -$ TARGET=full # or TARGET=half # or TARGET=quarter -$ ./tutorial-circle-hough --input ${TARGET}_disks -``` - -\subsection imgproc_cht_howto_images How to use actual images - -To run the software on an actual image like `coins2.jpg` provided with the tutorial and using a JSON configuration file, please run: -``` -$ ./tutorial-circle-hough --input coins2.jpg --config config/detector_img.json +If you would rather use the command line arguments, please run: +``` +$ ./tutorial-circle-hough --input coins2.jpg \ + --averaging-window-size 5 \ + --canny-backend opencv-backend \ + --filtering-type gaussianblur+scharr-filtering \ + --canny-thresh -1 -1 \ + --lower-canny-ratio 0.6 \ + --upper-canny-ratio 0.9 \ + --gaussian-kernel 5 \ + --gaussian-sigma 1 \ + --dilatation-kernel-size 5 \ + --center-thresh 70 \ + --circle-probability-thresh 0.725 \ + --radius-limits 34 75 \ + --merging-thresh 5 5 \ + --circle-perfectness 0.65 \ + --circle-probability-thresh 0.725 \ + --center-xlim 0 1920 \ + --center-ylim 0 1080 \ + --expected-nb-centers -1 \ + --edge-filter 3 \ + --gradient-kernel 3 ``` \note The configuration file `config/detector_img.json` has been tuned to detect circles in the image `coins2.jpg`. -If the detections seem a bit off, you might need to change the parameters in `config/detector_img.json`. +If the detections seem a bit off, you might need to change the parameters in `config/detector_img.json` or in the +command line. -To run the software on an actual image using command line arguments instead, please run: +\note The default values of the program corresponds to these fine-tuned parameters. Running the program +without any additionnal parameters should give the same result: ``` -$ ./tutorial-circle-hough --input /path/to/my/image --gaussian-kernel 5 --gaussian-sigma 1 --canny-thresh 100. 200. --dilatation-repet 1 --center-thresh 200 --radius-bin 2 --circle-probability-thresh 0.75 --radius-limits 80 90 --merging-thresh 15 2 --circle-perfectness 0.9 +./tutorial-circle-hough ``` -If the detections seem a bit off, you might need to change the parameters - \subsection imgproc_cht_howto_video How to use a video You can use the software to run circle detection on a video saved as a -sequence of images that are named `${BASENAME}%d.png`. +sequence of images that are named +``` +${BASENAME}%d.png +``` For instance with `${BASENAME}` = `video_`, you can have the following list of images: `video_0001.png`, `video_0002.png` and so on. @@ -88,22 +99,31 @@ $ ./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png --config conf To run the software using the command arguments, please run: ``` -./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png --gaussian-kernel 5 --gaussian-sigma 1 --canny-thresh -1. --dilatation-repet 1 --center-thresh 200 --radius-bin 2 --radius-thresh 2 --radius-limits 80 90 --merging-thresh 15 2 --circle-perfectness 0.9 +$ ./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png \ + --averaging-window-size 5 \ + --canny-backend opencv-backend \ + --filtering-type gaussianblur+scharr-filtering \ + --canny-thresh -1 -1 \ + --lower-canny-ratio 0.6 \ + --upper-canny-ratio 0.9 \ + --gaussian-kernel 5 \ + --gaussian-sigma 1 \ + --dilatation-kernel-size 5 \ + --center-thresh 70 \ + --circle-probability-thresh 0.725 \ + --radius-limits 34 75 \ + --merging-thresh 5 5 \ + --circle-perfectness 0.65 \ + --circle-probability-thresh 0.725 \ + --center-xlim 0 1920 \ + --center-ylim 0 1080 \ + --expected-nb-centers -1 \ + --edge-filter 3 \ + --gradient-kernel 3 ``` \section imgproc_cht_explanations Detailed explanations about the tutorial -An enumeration permits to choose between the different types of synthetic images -or using actual images or videos: - -\snippet tutorial-circle-hough.cpp Enum input - -You can choose the type you want using the command line arguments. To know how to do it, -please run: -``` -$ ./tutorial-circle-hough --help -``` - If you decide to use a video as input, the relevant piece of code that permits to perform circle detection on the successive images of the video is the following: \snippet tutorial-circle-hough.cpp Manage video @@ -112,16 +132,6 @@ If you decide to use a single image as input, the relevant piece of code that pe perform circle detection on the image is the following: \snippet tutorial-circle-hough.cpp Manage single image -If you decide to use a synthetic image as input, the relevant piece of code that -launches the detection on the synthetic image is the following: -\snippet tutorial-circle-hough.cpp Manage synthetic image - -The function that draws the synthetic image is the following: -\snippet tutorial-circle-hough.cpp Draw synthetic - -It relies on the following function to draw the disks: -\snippet tutorial-circle-hough.cpp Draw disks - If you did not use a JSON file to configure the `vpCircleHoughTransform` detector, the following structure defines the parameters of the algorithm based on the command line arguments: diff --git a/tutorial/imgproc/hough-transform/config/detector_img.json b/tutorial/imgproc/hough-transform/config/detector_img.json index c9e9306ea9..aa428820c2 100644 --- a/tutorial/imgproc/hough-transform/config/detector_img.json +++ b/tutorial/imgproc/hough-transform/config/detector_img.json @@ -19,7 +19,7 @@ ], "circlePerfectnessThreshold": 0.65, "dilatationKernelSize": 5, - "edgeMapFilteringNbIter" : 5, + "edgeMapFilteringNbIter" : 3, "gaussianKernelSize": 5, "gaussianStdev": 1.0, "mergingRadiusDiffThresh": 5.0, diff --git a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp index 37b5d3a690..0134174c11 100644 --- a/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp +++ b/tutorial/imgproc/hough-transform/tutorial-circle-hough.cpp @@ -17,148 +17,7 @@ #include "drawingHelpers.h" -//! [Enum input] -typedef enum TypeInputImage -{ - FULL_DISKS = 0, - HALF_DISKS = 1, - QUARTER_DISKS = 2, - USER_IMG = 3 -}TypeInputImage; - -std::string typeInputImageToString(const TypeInputImage &type) -{ - std::string name; - switch (type) { - case FULL_DISKS: - name = "full_disks"; - break; - case HALF_DISKS: - name = "half_disks"; - break; - case QUARTER_DISKS: - name = "quarter_disks"; - break; - case USER_IMG: - name = "path/to/your/image"; - } - return name; -} -//! [Enum input] - -TypeInputImage typeInputImageFromString(const std::string &name) -{ - TypeInputImage type(USER_IMG); - bool hasFound(false); - for (unsigned int id = 0; id < USER_IMG && !hasFound; id++) { - TypeInputImage candidate = (TypeInputImage)id; - if (name == typeInputImageToString(candidate)) { - type = candidate; - hasFound = true; - } - } - return type; -} - -std::string getAvailableTypeInputImage(const std::string &prefix = "<", const std::string &sep = " , ", const std::string &suffix = ">") -{ - std::string list(prefix); - for (unsigned int id = 0; id < USER_IMG; id++) { - list += typeInputImageToString((TypeInputImage)id) + sep; - } - list += typeInputImageToString(USER_IMG) + suffix; - return list; -} - -//! [Draw disks] -void -drawDisk(vpImage &I, const vpImagePoint ¢er, const unsigned int &radius, - const unsigned int &borderColor, const unsigned int &fillingColor, const unsigned int &thickness, const unsigned int &bckg) - //! [Draw disks] -{ - vpImageDraw::drawCircle(I, center, radius, borderColor, thickness); - vp::floodFill(I, - center, - bckg, - fillingColor, - vpImageMorphology::CONNEXITY_4 - ); -} - -//! [Draw synthetic] -vpImage -generateImage(const TypeInputImage &inputType) -//! [Draw synthetic] -{ - // // Image dimensions and background - const unsigned int width = 640; - const unsigned int height = 480; - const unsigned int bckg = 0; - - // // Disks parameters - const unsigned int circleColor = 128; - const unsigned int circleRadius = 50; - const unsigned int circleThickness = 1; - - // // Disks position when full circles - const double topFull = height / 4; - const double bottomFull = 3 * height / 4; - const double leftFull = width / 4; - const double rightFull = 3 * width / 4; - - // // Disks position when Half of circles - const double topHalf = 1; // m_centerMinThresh(25) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) - const double bottomHalf = height - 1; - const double leftHalf = width / 4; - const double rightHalf = 3 * width / 4; - - // // Disks position when Quarter of circles - const double topQuarter = 1; // m_centerMinThresh(15) , m_radiusBinSize(10) , m_radiusRatioThresh(50) , m_mergingDistanceThresh(15) , m_mergingRadiusDiffThresh(1.5 * (double) m_radiusBinSize) - const double bottomQuarter = height - 1; - const double leftQuarter = 1; - const double rightQuarter = width - 1; - vpImage I_src(height, width, bckg); - - // // Selecting position of the disks depending on their visibility - double top, left, bottom, right; - switch (inputType) { - case FULL_DISKS: - top = topFull; - left = leftFull; - bottom = bottomFull; - right = rightFull; - break; - case HALF_DISKS: - top = topHalf; - left = leftHalf; - bottom = bottomHalf; - right = rightHalf; - break; - case QUARTER_DISKS: - top = topQuarter; - left = leftQuarter; - bottom = bottomQuarter; - right = rightQuarter; - break; - default: - throw(vpException(vpException::badValue, "Using other type of input than the one that has been implemented to generate disks.")); - break; - } - - drawDisk(I_src, vpImagePoint(top, left), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(top, left), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - drawDisk(I_src, vpImagePoint(bottom, left), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(bottom, left), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - drawDisk(I_src, vpImagePoint(top, right), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(top, right), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - drawDisk(I_src, vpImagePoint(bottom, right), circleRadius, circleColor, circleColor, circleThickness, bckg); - drawDisk(I_src, vpImagePoint(bottom, right), circleRadius / 2, circleColor / 2, circleColor / 2, circleThickness, circleColor); - - std::cout << "Done drawing" << std::endl << std::flush; - return I_src; -} - -bool test_detection(const vpImage &I_src, vpCircleHoughTransform &detector, const int &nbCirclesToDetect, const bool &blockingMode, const bool &displayCanny) +bool run_detection(const vpImage &I_src, vpCircleHoughTransform &detector, const int &nbCirclesToDetect, const bool &blockingMode, const bool &displayCanny) { double t0 = vpTime::measureTimeMicros(); //! [Run detection] @@ -195,32 +54,27 @@ bool test_detection(const vpImage &I_src, vpCircleHoughTransform int main(int argc, char **argv) { - const std::string def_input(typeInputImageToString(FULL_DISKS)); + 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; -#ifdef HAVE_OPENCV_IMGPROC - const float def_lowerCannyThresh = 50.f; - const float def_upperCannyThresh = 150.f; -#else - const float def_lowerCannyThresh = 8.f; - const float def_upperCannyThresh = 25.f; -#endif - const int def_nbEdgeFilteringIter = 2; - const std::pair def_centerXlimits = std::pair(0, 640); - const std::pair def_centerYlimits = std::pair(0, 480); - const unsigned int def_minRadius = 0; - const unsigned int def_maxRadius = 1000; - const int def_dilatationRepet = 1; - const float def_centerThresh = -1.f; - const float def_circleProbaThresh = 0.9f; - const float def_circlePerfectness = 0.85f; - const float def_centerDistanceThresh = 15.f; - const float def_radiusDifferenceThresh = 15.f; + const float def_lowerCannyThresh = -1.f; + const float def_upperCannyThresh = -1.f; + const int def_nbEdgeFilteringIter = 3; + const std::pair def_centerXlimits = std::pair(0, 1920); + const std::pair def_centerYlimits = std::pair(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.65f; + const float def_centerDistanceThresh = 5.f; + const float def_radiusDifferenceThresh = 5.f; const int def_averagingWindowSize = 5; - const vpImageFilter::vpCannyFilteringAndGradientType def_filteringAndGradientType = vpImageFilter::CANNY_GBLUR_SOBEL_FILTERING; + const vpImageFilter::vpCannyFilteringAndGradientType def_filteringAndGradientType = vpImageFilter::CANNY_GBLUR_SCHARR_FILTERING; const vpImageFilter::vpCannyBackendType def_cannyBackendType = vpImageFilter::CANNY_OPENCV_BACKEND; const float def_lowerCannyThreshRatio = 0.6f; const float def_upperCannyThreshRatio = 0.9f; @@ -239,7 +93,7 @@ int main(int argc, char **argv) std::pair opt_centerYlimits = def_centerYlimits; unsigned int opt_minRadius = def_minRadius; unsigned int opt_maxRadius = def_maxRadius; - int opt_dilatationRepet = def_dilatationRepet; + int opt_dilatationKerneSize = def_dilatationKernelSize; float opt_centerThresh = def_centerThresh; float opt_circleProbaThresh = def_circleProbaThresh; float opt_circlePerfectness = def_circlePerfectness; @@ -277,7 +131,7 @@ int main(int argc, char **argv) opt_gaussianSigma = static_cast(atof(argv[i + 1])); i++; } - else if (argName == "--sobel-kernel" && i + 1 < argc) { + else if (argName == "--gradient-kernel" && i + 1 < argc) { opt_sobelKernelSize = atoi(argv[i + 1]); i++; } @@ -290,8 +144,8 @@ int main(int argc, char **argv) opt_nbEdgeFilteringIter = atoi(argv[i + 1]); i++; } - else if (argName == "--dilatation-repet" && i + 1 < argc) { - opt_dilatationRepet = atoi(argv[i + 1]); + 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) { @@ -357,20 +211,20 @@ int main(int argc, char **argv) << std::endl; std::cout << "SYNOPSIS" << std::endl; std::cout << "\t" << argv[0] - << "\t [--input " << getAvailableTypeInputImage() << "]" << std::endl + << "\t [--input ]" << std::endl #ifdef VISP_HAVE_NLOHMANN_JSON << "\t [--config ] (default: " << (def_jsonFilePath.empty() ? "unused" : def_jsonFilePath) << ")" << std::endl #endif << "\t [--nb-circles ] (default: " << def_nbCirclesToDetect << ")" << std::endl << "\t [--gaussian-kernel ] (default: " << def_gaussianKernelSize << ")" << std::endl << "\t [--gaussian-sigma ] (default: " << def_gaussianSigma << ")" << std::endl - << "\t [--sobel-kernel ] (default: " << def_sobelKernelSize << ")" << std::endl + << "\t [--gradient-kernel ] (default: " << def_sobelKernelSize << ")" << std::endl << "\t [--canny-thresh ] (default: " << def_lowerCannyThresh << " ; " << def_upperCannyThresh << ")" << std::endl << "\t [--edge-filter ] (default: " << def_nbEdgeFilteringIter << ")" << std::endl << "\t [--radius-limits ] (default: min = " << def_minRadius << ", max = " << def_maxRadius << ")" << std::endl - << "\t [--dilatation-repet ] (default: " << def_dilatationRepet << ")" << std::endl + << "\t [--dilatation-kernel-size ] (default: " << def_dilatationKernelSize << ")" << std::endl << "\t [--averaging-window-size ] (default: " << def_averagingWindowSize << ")" << std::endl - << "\t [--center-thresh ] (default: " << (def_centerThresh < 0 ? "auto" : std::to_string(def_centerThresh)) << ")" << std::endl + << "\t [--center-thresh ] (default: " << def_centerThresh << ")" << std::endl << "\t [--center-xlim ] (default: " << def_centerXlimits.first << " , " << def_centerXlimits.second << ")" << std::endl << "\t [--center-ylim ] (default: " << def_centerYlimits.first << " , " << def_centerYlimits.second << ")" << std::endl << "\t [--circle-probability-thresh ] (default: " << def_circleProbaThresh << ")" << std::endl @@ -392,7 +246,8 @@ int main(int argc, char **argv) std::cout << "DESCRIPTION" << std::endl << "\t--input" << std::endl - << "\t\tPermit to choose the type of input of the Hough Circle Algorithm" << 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 @@ -416,6 +271,11 @@ int main(int argc, char **argv) << "\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 @@ -430,10 +290,10 @@ int main(int argc, char **argv) << "\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-repet" << std::endl - << "\t\tPermit to set the number of iterations of the dilatation operation used to detect the maxima of the centers votes." << 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_dilatationRepet << 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 @@ -444,7 +304,7 @@ int main(int argc, char **argv) << "\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 < 0 ? "auto" : std::to_string(def_centerThresh)) << 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 @@ -502,36 +362,6 @@ int main(int argc, char **argv) } } - if (opt_centerThresh < 0 && opt_jsonFilePath.empty()) { - // The user asked to use the parameter value that has been fine-tuned - TypeInputImage inputType = typeInputImageFromString(opt_input); - switch (inputType) { - case TypeInputImage::FULL_DISKS: -#ifdef HAVE_OPENCV_IMGPROC - opt_centerThresh = 100.; -#else - opt_centerThresh = 75.; -#endif - break; - case TypeInputImage::HALF_DISKS: -#ifdef HAVE_OPENCV_IMGPROC - opt_centerThresh = 50.; -#else - opt_centerThresh = 25.; -#endif - break; - case TypeInputImage::QUARTER_DISKS: -#ifdef HAVE_OPENCV_IMGPROC - opt_centerThresh = 25.; -#else - opt_centerThresh = 15.; -#endif - break; - default: - throw(vpException(vpException::badValue, "Missing center threshold value to use with actual pictures as input. See the help for more information.")); - } - } - //! [Algo params] vpCircleHoughTransform::vpCircleHoughTransformParameters algoParams(opt_gaussianKernelSize @@ -544,7 +374,7 @@ int main(int argc, char **argv) , opt_centerYlimits , opt_minRadius , opt_maxRadius - , opt_dilatationRepet + , opt_dilatationKerneSize , opt_centerThresh , opt_circleProbaThresh , opt_circlePerfectness @@ -578,39 +408,32 @@ int main(int argc, char **argv) detector.saveConfigurationInJSON("runConfiguration.json"); vpImage I_src; - TypeInputImage inputType = typeInputImageFromString(opt_input); - if (inputType == USER_IMG) { - //! [Manage video] - if (opt_input.find("%") != std::string::npos) { - // The user wants to read a sequence of images from different files - bool hasToContinue = true; - vpVideoReader g; - g.setFileName(opt_input); - g.open(I_src); - while (!g.end() && hasToContinue) { - g.acquire(I_src); - hasToContinue = test_detection(I_src, detector, opt_nbCirclesToDetect, false, opt_displayCanny); - vpTime::wait(40); - } - } - //! [Manage video] - else { - //! [Manage single image] - // 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); - test_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny); - //! [Manage single image] + + //! [Manage video] + if (opt_input.find("%") != std::string::npos) { + // The user wants to read a sequence of images from different files + bool hasToContinue = true; + vpVideoReader g; + g.setFileName(opt_input); + g.open(I_src); + while (!g.end() && hasToContinue) { + g.acquire(I_src); + hasToContinue = run_detection(I_src, detector, opt_nbCirclesToDetect, false, opt_displayCanny); + vpTime::wait(40); } } + //! [Manage video] else { - //! [Manage synthetic image] - I_src = generateImage(inputType); - test_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny); - //! [Manage synthetic image] + //! [Manage single image] + // 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); + run_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny); + //! [Manage single image] } + return EXIT_SUCCESS; }