Visual Servoing Platform version 3.6.0
Loading...
Searching...
No Matches
Tutorial: Gradient-based Circle Hough Transform

Introduction

The Circle Hough Transform (CHT) is an image processing algorithm that permits to detect circles in an image. We refer the interested reader to the Wikipedia page to have a better understanding on the principles of the algorithm.

The ViSP implementation relies on the Gradient-based implementation of the algorithm.

During the step where the algorithm votes for center candidates, we use the gradient information in order to reduce the dimensionality of the search space. Instead of voting in circular pattern, 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 parameter.

Requirements

With the current implementation, the vpCircleHoughTransform requires ViSP to be compiled with OpenCV. If you do not know how to do it, please refer to the installation guidelines of OpenCV.

How to use the tutorial

It is possible to configure the vpCircleHoughTransform class using a JSON file. To do so, you need to install JSON for modern C++ and compile ViSP with it.

You can also configure the vpCircleHoughTransform class using command line arguments. To know what are the different command line arguments the software accept, please run:

$ cd tutorial/imgproc/hough-transform
$ ./tutorial-circle-hough --help

How to use synthetic images

To run the software on the synthetic images 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

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

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
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.

To run the software on an actual image using command line arguments instead, please run:

$ ./tutorial-circle-hough --input /path/to/my/image --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

If the detections seem a bit off, you might need to change the parameters

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. For instance with ${BASENAME} = video_, you can have the following list of images: video_0001.png, video_0002.png and so on.

To run the software using a JSON configuration file, please run:

$ ./tutorial-circle-hough --input /path/to/video/${BASENAME}%d.png --config config/detector_img.json

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

Detailed explanations about the tutorial

An enumeration permits to choose between the different types of synthetic images or using actual images or videos:

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;
}

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:

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);
while (!g.end() && hasToContinue) {
g.acquire(I_src);
hasToContinue = test_detection(I_src, detector, opt_nbCirclesToDetect, false, opt_displayCanny);
}
}
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)
VISP_EXPORT int wait(double t0, double t)

If you decide to use a single image as input, the relevant piece of code that permits to perform circle detection on the image is the following:

// 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);
error that can be emitted by ViSP classes.
Definition vpException.h:59
@ ioError
I/O error.
Definition vpException.h:79
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
static bool checkFilename(const std::string &filename)

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:

I_src = generateImage(inputType);
test_detection(I_src, detector, opt_nbCirclesToDetect, true, opt_displayCanny);

The function that draws the synthetic image is the following:

generateImage(const TypeInputImage &inputType)
Definition of the vpImage class member functions.
Definition vpImage.h:135

It relies on the following function to draw the disks:

void
drawDisk(vpImage<unsigned char> &I, const vpImagePoint &center, const unsigned int &radius,
const unsigned int &borderColor, const unsigned int &fillingColor, const unsigned int &thickness, const unsigned int &bckg)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...

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:

algoParams(opt_gaussianKernelSize
, opt_gaussianSigma
, opt_sobelKernelSize
, opt_lowerCannyThresh
, opt_upperCannyThresh
, opt_nbEdgeFilteringIter
, opt_centerXlimits
, opt_centerYlimits
, opt_minRadius
, opt_maxRadius
, opt_dilatationRepet
, opt_centerThresh
, opt_radiusThreshRatio
, opt_circlePerfectness
, opt_centerDistanceThresh
, opt_radiusDifferenceThresh
);

The initialization of the algorithm is performed in the following piece of code. If a JSON configuration file is given as input configuration, it will be preferred to the command line arguments:

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
}
Class that permits to detect 2D circles in a image using the gradient-based Circle Hough transform....
void init(const vpCircleHoughTransformParameters &algoParams)
Initialize all the algorithm parameters.
void initFromJSON(const std::string &jsonPath)
Initialize all the algorithm parameters using the JSON file whose path is jsonPath....
@ functionNotImplementedError
Function not implemented.
Definition vpException.h:78

To run the circle detection, you must call the following method:

std::vector<vpImageCircle> detectedCircles = detector.detect(I_src, nbCirclesToDetect);
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...

You could have also used the following method to get only the num_best best detections:

int num_best; // Set it to the number of circles you want to keep
std::vector<vpImageCircle> detections = detector.detect(I, num_best);

Then, you can iterate on the vector of detections using a synthax similar to the following:

for (auto circleCandidate : detectedCircles) {
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;
id++;
idColor = (idColor + 1) % v_colors.size();
}
static void drawCircle(vpImage< unsigned char > &I, const vpImageCircle &circle, unsigned char color, unsigned int thickness=1)