31#include <visp3/core/vpImageConvert.h>
32#include <visp3/core/vpImageFilter.h>
33#include <visp3/core/vpImageMorphology.h>
35#include <visp3/imgproc/vpCircleHoughTransform.h>
37#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
41 initGaussianFilters();
45 : m_algoParams(algoParams)
47 initGaussianFilters();
53 m_algoParams = algoParams;
54 initGaussianFilters();
60#ifdef VISP_HAVE_NLOHMANN_JSON
69 std::ifstream file(jsonPath);
72 ss <<
"Problem opening file " << jsonPath <<
". Make sure it exists and is readable" << std::endl;
77 j = json::parse(file);
79 catch (json::parse_error &e) {
80 std::stringstream msg;
81 msg <<
"Could not parse JSON file : \n";
83 msg << e.what() << std::endl;
84 msg <<
"Byte position of error: " << e.byte;
89 initGaussianFilters();
100vpCircleHoughTransform::initGaussianFilters()
102 m_fg.
resize(1, (m_algoParams.m_gaussianKernelSize + 1)/2);
104 m_fgDg.
resize(1, (m_algoParams.m_gaussianKernelSize + 1)/2);
109std::vector<vpImageCircle>
117#ifdef HAVE_OPENCV_CORE
118std::vector<vpImageCircle>
127std::vector<vpImageCircle>
130 std::vector<vpImageCircle> detections =
detect(I);
131 size_t nbDetections = detections.size();
132 std::vector<vpImageCircle> bestCircles;
133 std::vector<std::pair<vpImageCircle, unsigned int> > detectionsWithVotes;
134 for (
size_t i = 0; i < nbDetections; i++) {
135 std::pair<vpImageCircle, unsigned int> detectionWithVote(detections[i], m_finalCircleVotes[i]);
136 detectionsWithVotes.push_back(detectionWithVote);
139 bool (*hasMoreVotes)(std::pair<vpImageCircle, unsigned int>, std::pair<vpImageCircle, unsigned int>)
140 = [](std::pair<vpImageCircle, unsigned int> a, std::pair<vpImageCircle, unsigned int> b) {
142 return (a.second / a.first.getRadius() > b.second / b.first.getRadius());
145 std::sort(detectionsWithVotes.begin(), detectionsWithVotes.end(), hasMoreVotes);
149 limitMin = nbDetections;
152 limitMin = std::min(nbDetections, (
size_t)nbCircles);
154 for (
size_t i = 0; i < limitMin; i++) {
155 bestCircles.push_back(detectionsWithVotes[i].first);
161std::vector<vpImageCircle>
165 m_centerCandidatesList.clear();
166 m_centerVotes.clear();
167 m_edgePointsList.clear();
168 m_circleCandidates.clear();
169 m_circleCandidatesVotes.clear();
170 m_finalCircles.clear();
171 m_finalCircleVotes.clear();
175 computeGradientsAfterGaussianSmoothing(I);
184 computeCenterCandidates();
191 computeCircleCandidates();
195 mergeCircleCandidates();
197 return m_finalCircles;
207 m_algoParams.m_gaussianKernelSize
213 m_algoParams.m_gaussianKernelSize
220#if defined(HAVE_OPENCV_IMGPROC)
221 float upperCannyThresh = m_algoParams.m_upperCannyThresh;
222 float lowerCannyThresh = m_algoParams.m_lowerCannyThresh;
225 if (m_algoParams.m_upperCannyThresh < 0.) {
228 else if (m_algoParams.m_lowerCannyThresh < 0) {
229 lowerCannyThresh = upperCannyThresh / 3.f;
231 vpImageFilter::canny(I, m_edgeMap, m_algoParams.m_gaussianKernelSize, lowerCannyThresh, upperCannyThresh, m_algoParams.m_sobelKernelSize);
233 m_cannyVisp.
setCannyThresholds(m_algoParams.m_lowerCannyThresh, m_algoParams.m_upperCannyThresh);
235 m_edgeMap = m_cannyVisp.
detect(I);
238 for (
int i = 0; i < m_algoParams.m_edgeMapFilteringNbIter; i++) {
244vpCircleHoughTransform::filterEdgeMap()
248 for (
unsigned int i = 1; i < J.
getHeight() - 1; i++) {
249 for (
unsigned int j = 1; j < J.
getWidth() - 1; j++) {
250 if (J[i][j] == 255) {
252 int topLeftPixel = (int)J[i - 1][j - 1];
253 int topPixel = (int)J[i - 1][j];
254 int topRightPixel = (int)J[i - 1][j + 1];
255 int botLeftPixel = (int)J[i + 1][j - 1];
256 int bottomPixel = (int)J[i + 1][j];
257 int botRightPixel = (int)J[i + 1][j + 1];
258 int leftPixel = (int)J[i][j - 1];
259 int rightPixel = (int)J[i][j + 1];
260 if ((topLeftPixel + topPixel + topRightPixel
261 + botLeftPixel + bottomPixel + botRightPixel
262 + leftPixel + rightPixel
266 m_edgeMap[i][j] = 255;
278vpCircleHoughTransform::computeCenterCandidates()
285 unsigned int nbRows = m_edgeMap.
getRows();
286 unsigned int nbCols = m_edgeMap.
getCols();
288 m_algoParams.m_maxRadius = std::min(m_algoParams.m_maxRadius, std::min(nbCols, nbRows));
294 int minimumXposition = std::max(m_algoParams.m_centerXlimits.first, -1 * (
int)m_algoParams.m_maxRadius);
295 int maximumXposition = std::min(m_algoParams.m_centerXlimits.second, (
int)(m_algoParams.m_maxRadius + nbCols));
296 minimumXposition = std::min(minimumXposition, maximumXposition - 1);
297 float minimumXpositionFloat =
static_cast<float>(minimumXposition);
298 int offsetX = minimumXposition;
299 int accumulatorWidth = maximumXposition - minimumXposition + 1;
300 if (accumulatorWidth <= 0) {
308 int minimumYposition = std::max(m_algoParams.m_centerYlimits.first, -1 * (
int)m_algoParams.m_maxRadius);
309 int maximumYposition = std::min(m_algoParams.m_centerYlimits.second, (
int)(m_algoParams.m_maxRadius + nbRows));
310 minimumYposition = std::min(minimumYposition, maximumYposition - 1);
311 float minimumYpositionFloat =
static_cast<float>(minimumYposition);
312 int offsetY = minimumYposition;
313 int accumulatorHeight = maximumYposition - minimumYposition + 1;
314 if (accumulatorHeight <= 0) {
318 vpImage<float> centersAccum(accumulatorHeight, accumulatorWidth + 1, 0.);
320 for (
unsigned int r = 0; r < nbRows; r++) {
321 for (
unsigned int c = 0; c < nbCols; c++) {
322 if (m_edgeMap[r][c] == 255) {
324 m_edgePointsList.push_back(std::pair<unsigned int, unsigned int>(r, c));
328 float mag = std::sqrt(m_dIx[r][c] * m_dIx[r][c] + m_dIy[r][c] * m_dIy[r][c]);
330 float sx = m_dIx[r][c] / mag;
331 float sy = m_dIy[r][c] / mag;
333 int int_minRad = (int)m_algoParams.m_minRadius;
334 int int_maxRad = (int)m_algoParams.m_maxRadius;
336 for (
int k1 = 0; k1 < 2; k1++) {
337 bool hasToStopLoop =
false;
338 for (
int rad = int_minRad; rad <= int_maxRad && !hasToStopLoop; rad++) {
339 float x1 = (float)c + (
float)rad * sx;
340 float y1 = (float)r + (
float)rad * sy;
342 if (x1 < minimumXpositionFloat || y1 < minimumYpositionFloat) {
350 x_low =
static_cast<int>(std::floor(x1));
351 x_high =
static_cast<int>(std::ceil(x1));
354 x_low = -(
static_cast<int>(std::ceil(-x1)));
355 x_high = -(
static_cast<int>(std::floor(-x1)));
359 y_low =
static_cast<int>(std::floor(y1));
360 y_high =
static_cast<int>(std::ceil(y1));
363 y_low = -(
static_cast<int>(std::ceil(-1. * y1)));
364 y_high = -(
static_cast<int>(std::floor(-1. * y1)));
367 auto updateAccumulator =
368 [](
const float &x_orig,
const float &y_orig,
369 const unsigned int &x,
const unsigned int &y,
370 const int &offsetX,
const int &offsetY,
371 const unsigned int &nbCols,
const unsigned int &nbRows,
373 if (x - offsetX >= nbCols ||
374 y - offsetY >= nbRows
379 float dx = (x_orig - (float)x);
380 float dy = (y_orig - (float)y);
381 accum[y - offsetY][x - offsetX] += std::abs(dx) + std::abs(dy);
385 updateAccumulator(x1, y1, x_low, y_low,
387 accumulatorWidth, accumulatorHeight,
388 centersAccum, hasToStopLoop
391 updateAccumulator(x1, y1, x_high, y_high,
393 accumulatorWidth, accumulatorHeight,
394 centersAccum, hasToStopLoop
408 int niters = std::max(m_algoParams.m_dilatationNbIter, 1);
409 for (
int i = 0; i < niters; i++) {
416 int nbColsAccum = centersAccum.getCols();
417 int nbRowsAccum = centersAccum.getRows();
419 for (
int y = 0; y < nbRowsAccum; y++) {
421 for (
int x = 0; x < nbColsAccum; x++) {
422 if (centersAccum[y][x] >= m_algoParams.m_centerThresh
423 && centersAccum[y][x] == centerCandidatesMaxima[y][x]
424 && centersAccum[y][x] > centersAccum[y][x + 1]
428 nbVotes = std::max(nbVotes, (
int)centersAccum[y][x]);
430 else if (left >= 0) {
431 int cx = (int)((left + x - 1) * 0.5f);
432 m_centerCandidatesList.push_back(std::pair<int, int>(y + offsetY, cx + offsetX));
433 m_centerVotes.push_back(nbVotes);
442vpCircleHoughTransform::computeCircleCandidates()
444 size_t nbCenterCandidates = m_centerCandidatesList.size();
445 unsigned int nbBins =
static_cast<unsigned int>((m_algoParams.m_maxRadius - m_algoParams.m_minRadius + 1)/ m_algoParams.m_centerMinDist);
446 nbBins = std::max((
unsigned int)1, nbBins);
447 std::vector<unsigned int> radiusAccumList;
448 std::vector<float> radiusActualValueList;
450 unsigned int rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius;
451 unsigned int rmax2 =
static_cast<unsigned int>(m_algoParams.m_maxRadius * m_algoParams.m_maxRadius);
452 int circlePerfectness2 =
static_cast<int>(m_algoParams.m_circlePerfectness * m_algoParams.m_circlePerfectness);
454 for (
size_t i = 0; i < nbCenterCandidates; i++) {
455 std::pair<int, int> centerCandidate = m_centerCandidatesList[i];
457 radiusAccumList.clear();
458 radiusAccumList.resize(nbBins, 0);
459 radiusActualValueList.clear();
460 radiusActualValueList.resize(nbBins, 0.);
462 for (
auto edgePoint : m_edgePointsList) {
464 unsigned int rx = edgePoint.first - centerCandidate.first;
465 unsigned int ry = edgePoint.second - centerCandidate.second;
466 unsigned int r2 = rx * rx + ry * ry;
468 if ((r2 > rmin2) && (r2 < rmax2)) {
469 float gx = m_dIx[edgePoint.first][edgePoint.second];
470 float gy = m_dIy[edgePoint.first][edgePoint.second];
471 float grad2 = gx * gx + gy * gy;
473 float scalProd = rx * gx + ry * gy;
474 float scalProd2 = scalProd * scalProd;
475 if (scalProd2 >= circlePerfectness2 * r2 * grad2) {
477 float r =
static_cast<float>(std::sqrt(r2));
478 unsigned int r_bin =
static_cast<unsigned int>(std::ceil((r - m_algoParams.m_minRadius)/ m_algoParams.m_centerMinDist));
479 r_bin = std::min(r_bin, nbBins - 1);
480 radiusAccumList[r_bin]++;
481 radiusActualValueList[r_bin] += r;
486 for (
unsigned int idBin = 0; idBin < nbBins; idBin++) {
489 float r_effective = radiusActualValueList[idBin] / (float)radiusAccumList[idBin];
490 if ((
float)radiusAccumList[idBin] / r_effective > m_algoParams.m_radiusRatioThresh) {
495 m_circleCandidatesVotes.push_back(radiusAccumList[idBin]);
502vpCircleHoughTransform::mergeCircleCandidates()
505 std::vector<vpImageCircle> circleCandidates = m_circleCandidates;
506 std::vector<unsigned int> circleCandidatesVotes = m_circleCandidatesVotes;
507 size_t nbCandidates = m_circleCandidates.size();
508 for (
size_t i = 0; i < nbCandidates; i++) {
511 for (
size_t j = i + 1; j < nbCandidates; j++) {
516 bool areCirclesSimilar = (distanceBetweenCenters < m_algoParams.m_centerMinDist
517 && radiusDifference < m_algoParams.m_mergingRadiusDiffThresh
520 if (areCirclesSimilar) {
522 unsigned int totalVotes = circleCandidatesVotes[i] + circleCandidatesVotes[j];
523 float newRadius = (cic_i.
getRadius() * circleCandidatesVotes[i] + cic_j.
getRadius() * circleCandidatesVotes[j]) / totalVotes;
526 circleCandidates[j] = circleCandidates[nbCandidates - 1];
527 circleCandidatesVotes[i] = totalVotes;
528 circleCandidatesVotes[j] = circleCandidatesVotes[nbCandidates - 1];
529 circleCandidates.pop_back();
530 circleCandidatesVotes.pop_back();
536 m_finalCircles.push_back(cic_i);
539 nbCandidates = m_finalCircles.size();
540 for (
size_t i = 0; i < nbCandidates; i++) {
543 for (
size_t j = i + 1; j < nbCandidates; j++) {
548 bool areCirclesSimilar = (distanceBetweenCenters < m_algoParams.m_centerMinDist
549 && radiusDifference < m_algoParams.m_mergingRadiusDiffThresh
552 if (areCirclesSimilar) {
554 unsigned int totalVotes = circleCandidatesVotes[i] + circleCandidatesVotes[j];
556 float newRadius = (cic_i.
getRadius() * circleCandidatesVotes[i] + cic_j.
getRadius() * circleCandidatesVotes[j]) / totalVotes;
558 m_finalCircles[j] = m_finalCircles[nbCandidates - 1];
559 circleCandidatesVotes[i] = totalVotes;
560 circleCandidatesVotes[j] = circleCandidatesVotes[nbCandidates - 1];
561 m_finalCircles.pop_back();
562 circleCandidatesVotes.pop_back();
568 m_finalCircles[i] = cic_i;
570 m_finalCircleVotes = circleCandidatesVotes;
Type * data
Address of the first element of the data array.
void resize(unsigned int nrows, unsigned int ncols, bool flagNullify=true, bool recopy_=true)
vpImage< unsigned char > detect(const vpImage< vpRGBa > &I_color)
Detect the edges in an image. Convert the color image into a gray-scale image.
void setGradients(const vpImage< float > &dIx, const vpImage< float > &dIy)
Set the Gradients of the image that will be processed.
void setCannyThresholds(const float &lowerThresh, const float &upperThresh)
Set the lower and upper Canny Thresholds used to qualify the edge point candidates....
void setGaussianFilterParameters(const int &kernelSize, const float &stdev)
Set the Gaussian Filters kernel size and standard deviation and initialize the aforementioned filters...
error that can be emitted by ViSP classes.
@ dimensionError
Bad dimension.
Class that defines a 2D circle in an image.
vpImagePoint getCenter() const
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static float computeCannyThreshold(const cv::Mat &cv_I, const cv::Mat *p_cv_blur, float &lowerThresh)
Compute the upper Canny edge filter threshold.
static void getGradYGauss2D(const vpImage< ImageType > &I, vpImage< FilterType > &dIy, const FilterType *gaussianKernel, const FilterType *gaussianDerivativeKernel, unsigned int size)
static void getGradXGauss2D(const vpImage< ImageType > &I, vpImage< FilterType > &dIx, const FilterType *gaussianKernel, const FilterType *gaussianDerivativeKernel, unsigned int size)
static void getGaussianDerivativeKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static void getGaussianKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static void canny(const vpImage< unsigned char > &I, vpImage< unsigned char > &Ic, unsigned int gaussianFilterSize, float thresholdCanny, unsigned int apertureSobel)
static void dilatation(vpImage< Type > &I, Type value, Type value_out, vpConnexityType connexity=CONNEXITY_4)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
static double distance(const vpImagePoint &iP1, const vpImagePoint &iP2)
Definition of the vpImage class member functions.
unsigned int getWidth() const
unsigned int getCols() const
unsigned int getHeight() const
unsigned int getRows() const