Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpCircleHoughTransform_circles.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2024 by Inria. All rights reserved.
4 *
5 * This software is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 */
30
31#include <visp3/core/vpImageConvert.h>
32#include <visp3/core/vpImageMorphology.h>
33
34#include <visp3/imgproc/vpCircleHoughTransform.h>
35
37
38#ifndef DOXYGEN_SHOULD_SKIP_THIS
39namespace
40{
41#if (VISP_CXX_STANDARD == VISP_CXX_STANDARD_98)
42float computeEffectiveRadius(const float &votes, const float &weigthedSumRadius)
43{
44 float r_effective = -1.f;
45 if (votes > std::numeric_limits<float>::epsilon()) {
46 r_effective = weigthedSumRadius / votes;
47 }
48 return r_effective;
49}
50#endif
51
52typedef struct vpDataUpdateRadAccum
53{
54 int m_nbBins;
55 std::pair<float, float> m_centerCandidate;
56 std::pair<unsigned int, unsigned int> m_edgePoint;
57 const vpImage<float> &m_dIx;
58 const vpImage<float> &m_dIy;
59 float m_rx;
60 float m_ry;
61 float m_circlePerfectness2;
62 float m_r2;
63 float m_minRadius;
64 float m_mergingRadiusDiffThresh;
65 bool m_recordVotingPoints;
66
67 vpDataUpdateRadAccum(const vpImage<float> &dIx, const vpImage<float> &dIy)
68 : m_dIx(dIx)
69 , m_dIy(dIy)
70 { }
71} vpDataUpdateRadAccum;
72
73void
74updateRadiusAccumulator(const vpDataUpdateRadAccum &data, std::vector<float> &radiusAccumList,
75 std::vector<float> &radiusActualValueList,
76 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > &votingPoints)
77{
78 float gx = data.m_dIx[data.m_edgePoint.first][data.m_edgePoint.second];
79 float gy = data.m_dIy[data.m_edgePoint.first][data.m_edgePoint.second];
80 float grad2 = (gx * gx) + (gy * gy);
81 float scalProd = (data.m_rx * gx) + (data.m_ry * gy);
82 float scalProd2 = scalProd * scalProd;
83 if (scalProd2 >= (data.m_circlePerfectness2 * data.m_r2 * grad2)) {
84 // Look for the Radius Candidate Bin RCB_k to which d_ij is "the closest" will have an additional vote
85 float r = static_cast<float>(std::sqrt(data.m_r2));
86 int r_bin = static_cast<int>(std::floor((r - data.m_minRadius) / data.m_mergingRadiusDiffThresh));
87 r_bin = std::min<int>(r_bin, data.m_nbBins - 1);
88 if ((r < (data.m_minRadius + (data.m_mergingRadiusDiffThresh * 0.5f)))
89 || (r >(data.m_minRadius + (data.m_mergingRadiusDiffThresh * (static_cast<float>(data.m_nbBins - 1) + 0.5f))))) {
90 // If the radius is at the very beginning of the allowed radii or at the very end, we do not span the vote
91 radiusAccumList[r_bin] += 1.f;
92 radiusActualValueList[r_bin] += r;
93 if (data.m_recordVotingPoints) {
94 votingPoints[r_bin].push_back(data.m_edgePoint);
95 }
96 }
97 else {
98 float midRadiusPrevBin = data.m_minRadius + (data.m_mergingRadiusDiffThresh * ((r_bin - 1.f) + 0.5f));
99 float midRadiusCurBin = data.m_minRadius + (data.m_mergingRadiusDiffThresh * (r_bin + 0.5f));
100 float midRadiusNextBin = data.m_minRadius + (data.m_mergingRadiusDiffThresh * (r_bin + 1.f + 0.5f));
101
102 if ((r >= midRadiusCurBin) && (r <= midRadiusNextBin)) {
103 // The radius is at the end of the current bin or beginning of the next, we span the vote with the next bin
104 float voteCurBin = (midRadiusNextBin - r) / data.m_mergingRadiusDiffThresh; // If the difference is big, it means that we are closer to the current bin
105 float voteNextBin = 1.f - voteCurBin;
106 radiusAccumList[r_bin] += voteCurBin;
107 radiusActualValueList[r_bin] += r * voteCurBin;
108 radiusAccumList[r_bin + 1] += voteNextBin;
109 radiusActualValueList[r_bin + 1] += r * voteNextBin;
110 if (data.m_recordVotingPoints) {
111 votingPoints[r_bin].push_back(data.m_edgePoint);
112 votingPoints[r_bin + 1].push_back(data.m_edgePoint);
113 }
114 }
115 else {
116 // The radius is at the end of the previous bin or beginning of the current, we span the vote with the previous bin
117 float votePrevBin = (r - midRadiusPrevBin) / data.m_mergingRadiusDiffThresh; // If the difference is big, it means that we are closer to the previous bin
118 float voteCurBin = 1.f - votePrevBin;
119 radiusAccumList[r_bin] += voteCurBin;
120 radiusActualValueList[r_bin] += r * voteCurBin;
121 radiusAccumList[r_bin - 1] += votePrevBin;
122 radiusActualValueList[r_bin - 1] += r * votePrevBin;
123 if (data.m_recordVotingPoints) {
124 votingPoints[r_bin].push_back(data.m_edgePoint);
125 votingPoints[r_bin - 1].push_back(data.m_edgePoint);
126 }
127 }
128 }
129 }
130}
131}
132#endif
133
134void
136{
137 size_t nbCenterCandidates = m_centerCandidatesList.size();
138 int nbBins = static_cast<int>(((m_algoParams.m_maxRadius - m_algoParams.m_minRadius) + 1) / m_algoParams.m_mergingRadiusDiffThresh);
139 nbBins = std::max<int>(static_cast<int>(1), nbBins); // Avoid having 0 bins, which causes segfault
140 std::vector<float> radiusAccumList; // Radius accumulator for each center candidates.
141 std::vector<float> radiusActualValueList; // Vector that contains the actual distance between the edge points and the center candidates.
142
143 float rmin2 = m_algoParams.m_minRadius * m_algoParams.m_minRadius;
144 float rmax2 = m_algoParams.m_maxRadius * m_algoParams.m_maxRadius;
145 float circlePerfectness2 = m_algoParams.m_circlePerfectness * m_algoParams.m_circlePerfectness;
146
147 vpDataUpdateRadAccum data(m_dIx, m_dIy);
148 data.m_nbBins = nbBins;
149 data.m_circlePerfectness2 = circlePerfectness2;
150 data.m_minRadius = m_algoParams.m_minRadius;
151 data.m_mergingRadiusDiffThresh = m_algoParams.m_mergingRadiusDiffThresh;
152 data.m_recordVotingPoints = m_algoParams.m_recordVotingPoints;
153
154 for (size_t i = 0; i < nbCenterCandidates; ++i) {
155 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > votingPoints(nbBins); // Vectors that contain the points voting for each radius bin
156 std::pair<float, float> centerCandidate = m_centerCandidatesList[i];
157 // Initialize the radius accumulator of the candidate with 0s
158 radiusAccumList.clear();
159 radiusAccumList.resize(nbBins, 0);
160 radiusActualValueList.clear();
161 radiusActualValueList.resize(nbBins, 0.);
162
163 const unsigned int nbEdgePoints = static_cast<unsigned int>(m_edgePointsList.size());
164 for (unsigned int e = 0; e < nbEdgePoints; ++e) {
165 const std::pair<unsigned int, unsigned int> &edgePoint = m_edgePointsList[e];
166
167 // For each center candidate CeC_i, compute the distance with each edge point EP_j d_ij = dist(CeC_i; EP_j)
168 float rx = edgePoint.second - centerCandidate.second;
169 float ry = edgePoint.first - centerCandidate.first;
170 float r2 = (rx * rx) + (ry * ry);
171 if ((r2 > rmin2) && (r2 < rmax2)) {
172 data.m_centerCandidate = centerCandidate;
173 data.m_edgePoint = edgePoint;
174 data.m_rx = rx;
175 data.m_ry = ry;
176 data.m_r2 = r2;
177 updateRadiusAccumulator(data, radiusAccumList, radiusActualValueList, votingPoints);
178 }
179 }
180
181#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
182 // Lambda to compute the effective radius (i.e. barycenter) of each radius bin
183 auto computeEffectiveRadius = [](const float &votes, const float &weigthedSumRadius) {
184 float r_effective = -1.f;
185 if (votes > std::numeric_limits<float>::epsilon()) {
186 r_effective = weigthedSumRadius / votes;
187 }
188 return r_effective;
189 };
190#endif
191
192 // Merging similar candidates
193 std::vector<float> v_r_effective; // Vector of radius of each candidate after the merge step
194 std::vector<float> v_votes_effective; // Vector of number of votes of each candidate after the merge step
195 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > v_votingPoints_effective; // Vector of voting points after the merge step
196 std::vector<bool> v_hasMerged_effective; // Vector indicating if merge has been performed for the different candidates
197 for (int idBin = 0; idBin < nbBins; ++idBin) {
198 float r_effective = computeEffectiveRadius(radiusAccumList[idBin], radiusActualValueList[idBin]);
199 float votes_effective = radiusAccumList[idBin];
200 std::vector<std::pair<unsigned int, unsigned int> > votingPoints_effective = votingPoints[idBin];
201 bool is_r_effective_similar = (r_effective > 0.f);
202 // Looking for potential similar radii in the following bins
203 // If so, compute the barycenter radius between them
204 int idCandidate = idBin + 1;
205 bool hasMerged = false;
206 while ((idCandidate < nbBins) && is_r_effective_similar) {
207 float r_effective_candidate = computeEffectiveRadius(radiusAccumList[idCandidate], radiusActualValueList[idCandidate]);
208 if (std::abs(r_effective_candidate - r_effective) < m_algoParams.m_mergingRadiusDiffThresh) {
209 r_effective = ((r_effective * votes_effective) + (r_effective_candidate * radiusAccumList[idCandidate])) / (votes_effective + radiusAccumList[idCandidate]);
210 votes_effective += radiusAccumList[idCandidate];
211 radiusAccumList[idCandidate] = -.1f;
212 radiusActualValueList[idCandidate] = -1.f;
213 is_r_effective_similar = true;
214 if (m_algoParams.m_recordVotingPoints) {
215 // Move elements from votingPoints[idCandidate] to votingPoints_effective.
216 // votingPoints[idCandidate] is left in undefined but safe-to-destruct state.
217#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
218 votingPoints_effective.insert(
219 votingPoints_effective.end(),
220 std::make_move_iterator(votingPoints[idCandidate].begin()),
221 std::make_move_iterator(votingPoints[idCandidate].end())
222 );
223#else
224 votingPoints_effective.insert(
225 votingPoints_effective.end(),
226 votingPoints[idCandidate].begin(),
227 votingPoints[idCandidate].end()
228 );
229#endif
230 hasMerged = true;
231 }
232 }
233 else {
234 is_r_effective_similar = false;
235 }
236 ++idCandidate;
237 }
238
239 if ((votes_effective > m_algoParams.m_centerMinThresh) && (votes_effective >= (m_algoParams.m_circleVisibilityRatioThresh * 2.f * M_PI_FLOAT * r_effective))) {
240 // Only the circles having enough votes and being visible enough are considered
241 v_r_effective.push_back(r_effective);
242 v_votes_effective.push_back(votes_effective);
243 if (m_algoParams.m_recordVotingPoints) {
244 v_votingPoints_effective.push_back(votingPoints_effective);
245 v_hasMerged_effective.push_back(hasMerged);
246 }
247 }
248 }
249
250 unsigned int nbCandidates = static_cast<unsigned int>(v_r_effective.size());
251 for (unsigned int idBin = 0; idBin < nbCandidates; ++idBin) {
252 // If the circle of center CeC_i and radius RCB_k has enough votes, it is added to the list
253 // of Circle Candidates
254 float r_effective = v_r_effective[idBin];
255 vpImageCircle candidateCircle(vpImagePoint(centerCandidate.first, centerCandidate.second), r_effective);
256 float proba = computeCircleProbability(candidateCircle, static_cast<unsigned int>(v_votes_effective[idBin]));
257 if (proba > m_algoParams.m_circleProbaThresh) {
258 m_circleCandidates.push_back(candidateCircle);
259 m_circleCandidatesProbabilities.push_back(proba);
260 m_circleCandidatesVotes.push_back(static_cast<unsigned int>(v_votes_effective[idBin]));
261 if (m_algoParams.m_recordVotingPoints) {
262 if (v_hasMerged_effective[idBin]) {
263 // Remove potential duplicated points
264 std::sort(v_votingPoints_effective[idBin].begin(), v_votingPoints_effective[idBin].end());
265 v_votingPoints_effective[idBin].erase(std::unique(v_votingPoints_effective[idBin].begin(), v_votingPoints_effective[idBin].end()), v_votingPoints_effective[idBin].end());
266 }
267 // Save the points
268 m_circleCandidatesVotingPoints.push_back(v_votingPoints_effective[idBin]);
269 }
270 }
271 }
272 }
273}
274
275float
276vpCircleHoughTransform::computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes)
277{
278 float proba(0.f);
279 float visibleArc(static_cast<float>(nbVotes));
280 float theoreticalLenght;
281 if (mp_mask != nullptr) {
282 theoreticalLenght = static_cast<float>(circle.computePixelsInMask(*mp_mask));
283 }
284 else {
285 theoreticalLenght = circle.computeArcLengthInRoI(vpRect(vpImagePoint(0, 0), m_edgeMap.getWidth(), m_edgeMap.getHeight()));
286 }
287 if (theoreticalLenght < std::numeric_limits<float>::epsilon()) {
288 proba = 0.f;
289 }
290 else {
291 proba = std::min(visibleArc / theoreticalLenght, 1.f);
292 }
293 return proba;
294}
295
296void
298{
299 std::vector<vpImageCircle> circleCandidates = m_circleCandidates;
300 std::vector<unsigned int> circleCandidatesVotes = m_circleCandidatesVotes;
301 std::vector<float> circleCandidatesProba = m_circleCandidatesProbabilities;
302 std::vector<std::vector<std::pair<unsigned int, unsigned int> > > circleCandidatesVotingPoints = m_circleCandidatesVotingPoints;
303 // First iteration of merge
304 mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba, circleCandidatesVotingPoints);
305
306 // Second iteration of merge
307 mergeCandidates(circleCandidates, circleCandidatesVotes, circleCandidatesProba, circleCandidatesVotingPoints);
308
309 // Saving the results
310 m_finalCircles = circleCandidates;
311 m_finalCircleVotes = circleCandidatesVotes;
312 m_finalCirclesProbabilities = circleCandidatesProba;
313 m_finalCirclesVotingPoints = circleCandidatesVotingPoints;
314}
315
316void
317vpCircleHoughTransform::mergeCandidates(std::vector<vpImageCircle> &circleCandidates, std::vector<unsigned int> &circleCandidatesVotes,
318 std::vector<float> &circleCandidatesProba, std::vector<std::vector<std::pair<unsigned int, unsigned int> > > &votingPoints)
319{
320 size_t nbCandidates = circleCandidates.size();
321 size_t i = 0;
322 while (i < nbCandidates) {
323 vpImageCircle cic_i = circleCandidates[i];
324 bool hasPerformedMerge = false;
325 // // For each other circle candidate CiC_j do:
326 size_t j = i + 1;
327 while (j < nbCandidates) {
328 vpImageCircle cic_j = circleCandidates[j];
329 // // // Compute the similarity between CiC_i and CiC_j
330 double distanceBetweenCenters = vpImagePoint::distance(cic_i.getCenter(), cic_j.getCenter());
331 double radiusDifference = std::abs(cic_i.getRadius() - cic_j.getRadius());
332 bool areCirclesSimilar = ((distanceBetweenCenters < m_algoParams.m_centerMinDist)
333 && (radiusDifference < m_algoParams.m_mergingRadiusDiffThresh)
334 );
335
336 if (areCirclesSimilar) {
337 hasPerformedMerge = true;
338 // // // If the similarity exceeds a threshold, merge the circle candidates CiC_i and CiC_j and remove CiC_j of the list
339 unsigned int totalVotes = circleCandidatesVotes[i] + circleCandidatesVotes[j];
340 float totalProba = circleCandidatesProba[i] + circleCandidatesProba[j];
341 float newProba = 0.5f * totalProba;
342 float newRadius = ((cic_i.getRadius() * circleCandidatesProba[i]) + (cic_j.getRadius() * circleCandidatesProba[j])) / totalProba;
343 vpImagePoint newCenter = ((cic_i.getCenter() * circleCandidatesProba[i]) + (cic_j.getCenter() * circleCandidatesProba[j])) / totalProba;
344 cic_i = vpImageCircle(newCenter, newRadius);
345 circleCandidates[j] = circleCandidates[nbCandidates - 1];
346 const unsigned int var2 = 2;
347 circleCandidatesVotes[i] = totalVotes / var2; // Compute the mean vote
348 circleCandidatesVotes[j] = circleCandidatesVotes[nbCandidates - 1];
349 circleCandidatesProba[i] = newProba;
350 circleCandidatesProba[j] = circleCandidatesProba[nbCandidates - 1];
351 if (m_algoParams.m_recordVotingPoints) {
352#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
353 votingPoints[i].insert(
354 votingPoints[i].end(),
355 std::make_move_iterator(votingPoints[j].begin()),
356 std::make_move_iterator(votingPoints[j].end())
357 );
358#else
359 votingPoints[i].insert(
360 votingPoints[i].end(),
361 votingPoints[j].begin(),
362 votingPoints[j].end()
363 );
364#endif
365 votingPoints.pop_back();
366 }
367 circleCandidates.pop_back();
368 circleCandidatesVotes.pop_back();
369 circleCandidatesProba.pop_back();
370 --nbCandidates;
371 // We do not update j because the new j-th candidate has not been evaluated yet
372 }
373 else {
374 // We will evaluate the next candidate
375 ++j;
376 }
377 }
378 // // Add the circle candidate CiC_i, potentially merged with other circle candidates, to the final list of detected circles
379 circleCandidates[i] = cic_i;
380 if (hasPerformedMerge && m_algoParams.m_recordVotingPoints) {
381 // Remove duplicated points
382 std::sort(votingPoints[i].begin(), votingPoints[i].end());
383 votingPoints[i].erase(std::unique(votingPoints[i].begin(), votingPoints[i].end()), votingPoints[i].end());
384 }
385 ++i;
386 }
387}
388
389END_VISP_NAMESPACE
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_circleCandidatesVotingPoints
std::vector< std::pair< float, float > > m_centerCandidatesList
std::vector< std::vector< std::pair< unsigned int, unsigned int > > > m_finalCirclesVotingPoints
std::vector< float > m_circleCandidatesProbabilities
std::vector< vpImageCircle > m_finalCircles
std::vector< std::pair< unsigned int, unsigned int > > m_edgePointsList
virtual void computeCircleCandidates()
For each center candidate CeC_i, do:
vpImage< unsigned char > m_edgeMap
virtual float computeCircleProbability(const vpImageCircle &circle, const unsigned int &nbVotes)
Compute the probability of circle given the number of pixels voting for it nbVotes....
const vpImage< bool > * mp_mask
std::vector< unsigned int > m_circleCandidatesVotes
std::vector< unsigned int > m_finalCircleVotes
vpCircleHoughTransformParams m_algoParams
std::vector< float > m_finalCirclesProbabilities
virtual void mergeCircleCandidates()
For each circle candidate CiC_i, check if similar circles have also been detected and if so merges th...
virtual void mergeCandidates(std::vector< vpImageCircle > &circleCandidates, std::vector< unsigned int > &circleCandidatesVotes, std::vector< float > &circleCandidatesProba, std::vector< std::vector< std::pair< unsigned int, unsigned int > > > &votingPoints)
For each circle candidate CiC_i do:
std::vector< vpImageCircle > m_circleCandidates
Class that defines a 2D circle in an image.
float getRadius() const
vpImagePoint getCenter() const
float computeArcLengthInRoI(const vpRect &roi, const float &roundingTolerance=0.001f) const
unsigned int computePixelsInMask(const vpImage< bool > &mask) const
Count the number of pixels of the circle whose value in the mask is true.
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)
Defines a rectangle in the plane.
Definition vpRect.h:79