2121#include < cmath>
2222#include < limits>
2323#include < vector>
24- #include < set>
2524
2625namespace sks
2726{
@@ -54,14 +53,6 @@ struct FiducialPoint
5453 }
5554};
5655
57- struct KeyPointSizeSorter
58- {
59- bool operator ()(const cv::KeyPoint& k1, const cv::KeyPoint& k2) const
60- {
61- return k1.size > k2.size ;
62- }
63- };
64-
6556cv::Ptr<cv::SimpleBlobDetector> CreateDetector ()
6657{
6758 cv::SimpleBlobDetector::Params params;
@@ -85,8 +76,6 @@ cv::Mat ExtractDots(
8576 const cv::Mat& distortionCoefficients,
8677 const cv::Mat& gridPoints,
8778 const cv::Mat& indexesOfFourReferencePoints,
88- int referenceImageWidth,
89- int referenceImageHeight,
9079 bool isDistorted
9180 )
9281{
@@ -102,49 +91,69 @@ cv::Mat ExtractDots(
10291 cv::ADAPTIVE_THRESH_MEAN_C , cv::THRESH_BINARY ,
10392 THRESHOLD_WINDOW_SIZE , THRESHOLD_OFFSET );
10493
105- // Step 2: Detect blobs in the (possibly distorted) thresholded image
94+ // Step 2: Single blob detection on the thresholded image
10695 cv::Ptr<cv::SimpleBlobDetector> detector = CreateDetector ();
10796 std::vector<cv::KeyPoint> keypoints;
10897 detector->detect (thresholded, keypoints);
10998
110- // Step 3: Undistort and re-detect if needed
111- cv::Mat undistortedImage;
112- std::vector<cv::KeyPoint> undistortedKeypoints;
113-
114- if (isDistorted)
99+ int numberOfKeypoints = static_cast <int >(keypoints.size ());
100+ if (numberOfKeypoints <= 4 )
115101 {
116- cv::undistort (smoothed, undistortedImage, intrinsicMatrix, distortionCoefficients);
117-
118- cv::Mat undistortedThresholded;
119- cv::adaptiveThreshold (undistortedImage, undistortedThresholded, 255 ,
120- cv::ADAPTIVE_THRESH_MEAN_C , cv::THRESH_BINARY ,
121- THRESHOLD_WINDOW_SIZE , THRESHOLD_OFFSET );
122-
123- detector->detect (undistortedThresholded, undistortedKeypoints);
102+ return emptyResult;
124103 }
125- else
104+
105+ // Step 3: Extract keypoint coordinates (distorted image space) and sizes
106+ std::vector<cv::Point2f> distortedPts (numberOfKeypoints);
107+ std::vector<float > keypointSizes (numberOfKeypoints);
108+ for (int i = 0 ; i < numberOfKeypoints; i++)
126109 {
127- undistortedImage = smoothed ;
128- undistortedKeypoints = keypoints;
110+ distortedPts[i] = keypoints[i]. pt ;
111+ keypointSizes[i] = keypoints[i]. size ;
129112 }
130113
131- // Need at least 5 points (4 fiducials + at least 1 other)
132- if (keypoints.size () <= 4 || undistortedKeypoints.size () <= 4 )
114+ // Step 4: Get points in undistorted space for homography estimation
115+ std::vector<cv::Point2f> ptsForHomography;
116+ if (isDistorted)
133117 {
134- return emptyResult;
118+ cv::Mat distortedMat (numberOfKeypoints, 1 , CV_32FC2 );
119+ for (int i = 0 ; i < numberOfKeypoints; i++)
120+ {
121+ distortedMat.at <cv::Vec2f>(i, 0 ) = cv::Vec2f (distortedPts[i].x ,
122+ distortedPts[i].y );
123+ }
124+ cv::Mat undistortedMat;
125+ cv::undistortPoints (distortedMat, undistortedMat,
126+ intrinsicMatrix, distortionCoefficients,
127+ cv::noArray (), intrinsicMatrix);
128+ ptsForHomography.resize (numberOfKeypoints);
129+ for (int i = 0 ; i < numberOfKeypoints; i++)
130+ {
131+ cv::Vec2f pt = undistortedMat.at <cv::Vec2f>(i, 0 );
132+ ptsForHomography[i] = cv::Point2f (pt[0 ], pt[1 ]);
133+ }
134+ }
135+ else
136+ {
137+ ptsForHomography = distortedPts;
135138 }
136139
137- // Step 4: Find the four largest blobs (fiducials) in undistorted image
138- std::sort (undistortedKeypoints.begin (), undistortedKeypoints.end (),
139- KeyPointSizeSorter ());
140+ // Step 5: Sort by size and pick biggest 4 as fiducials
141+ std::vector<int > sortedIndices (numberOfKeypoints);
142+ for (int i = 0 ; i < numberOfKeypoints; i++) sortedIndices[i] = i;
143+ std::sort (sortedIndices.begin (), sortedIndices.end (),
144+ [&keypointSizes](int a, int b) {
145+ return keypointSizes[a] < keypointSizes[b];
146+ });
140147
141148 std::vector<FiducialPoint> biggestFour;
142- biggestFour.emplace_back (undistortedKeypoints[0 ].pt .x , undistortedKeypoints[0 ].pt .y );
143- biggestFour.emplace_back (undistortedKeypoints[1 ].pt .x , undistortedKeypoints[1 ].pt .y );
144- biggestFour.emplace_back (undistortedKeypoints[2 ].pt .x , undistortedKeypoints[2 ].pt .y );
145- biggestFour.emplace_back (undistortedKeypoints[3 ].pt .x , undistortedKeypoints[3 ].pt .y );
149+ for (int i = numberOfKeypoints - 4 ; i < numberOfKeypoints; i++)
150+ {
151+ int idx = sortedIndices[i];
152+ biggestFour.emplace_back (ptsForHomography[idx].x ,
153+ ptsForHomography[idx].y );
154+ }
146155
147- // Step 5 : Classify as TL, TR, BL, BR based on centroid
156+ // Step 6 : Classify as TL, TR, BL, BR based on centroid
148157 double cx = 0 , cy = 0 ;
149158 for (const auto & pt : biggestFour) { cx += pt.x ; cy += pt.y ; }
150159 cx /= 4.0 ; cy /= 4.0 ;
@@ -159,7 +168,7 @@ cv::Mat ExtractDots(
159168 // Sort by score: TL=0, TR=1, BL=2, BR=3
160169 std::sort (biggestFour.begin (), biggestFour.end ());
161170
162- // Step 6 : Compute homography from detected fiducials to reference grid
171+ // Step 7 : Compute homography from undistorted fiducials to reference grid
163172 cv::Mat sourceFiducials = cv::Mat::zeros (4 , 2 , CV_64F );
164173 cv::Mat targetFiducials = cv::Mat::zeros (4 , 2 , CV_64F );
165174 for (int i = 0 ; i < 4 ; i++)
@@ -177,35 +186,21 @@ cv::Mat ExtractDots(
177186 return emptyResult;
178187 }
179188
180- // Step 7: Warp undistorted image to canonical face-on view, re-detect
181- cv::Size refSize (referenceImageWidth, referenceImageHeight);
182- cv::Mat warped;
183- cv::warpPerspective (undistortedImage, warped, homography, refSize);
184-
185- cv::Mat warpedThresholded;
186- cv::adaptiveThreshold (warped, warpedThresholded, 255 ,
187- cv::ADAPTIVE_THRESH_MEAN_C , cv::THRESH_BINARY ,
188- THRESHOLD_WINDOW_SIZE , THRESHOLD_OFFSET );
189-
190- std::vector<cv::KeyPoint> warpedKeypoints;
191- detector->detect (warpedThresholded, warpedKeypoints);
189+ // Step 8: Warp all undistorted points into reference space using
190+ // perspectiveTransform (no image warping needed)
191+ std::vector<cv::Point2f> warpedPts;
192+ cv::perspectiveTransform (ptsForHomography, warpedPts, homography);
192193
193- int numberOfWarpedKeypoints = static_cast <int >(warpedKeypoints.size ());
194- if (numberOfWarpedKeypoints == 0 )
195- {
196- return emptyResult;
197- }
198-
199- // Step 8: For each warped dot, find closest point in reference grid
200- std::vector<int > assignedGridIndex (numberOfWarpedKeypoints);
194+ // Step 9: Match each warped point to the nearest reference grid point
195+ std::vector<int > assignedGridIndex (numberOfKeypoints);
201196 double rmsError = 0 ;
202197
203- for (int i = 0 ; i < numberOfWarpedKeypoints ; i++)
198+ for (int i = 0 ; i < numberOfKeypoints ; i++)
204199 {
205200 double bestDist = std::numeric_limits<double >::max ();
206201 int bestIdx = -1 ;
207- double wx = warpedKeypoints [i]. pt .x ;
208- double wy = warpedKeypoints [i]. pt .y ;
202+ double wx = warpedPts [i].x ;
203+ double wy = warpedPts [i].y ;
209204
210205 for (int j = 0 ; j < gridPoints.rows ; j++)
211206 {
@@ -222,72 +217,22 @@ cv::Mat ExtractDots(
222217 rmsError += std::sqrt (bestDist);
223218 }
224219
225- rmsError /= static_cast <double >(numberOfWarpedKeypoints );
220+ rmsError /= static_cast <double >(numberOfKeypoints );
226221
227222 if (rmsError > RMS_TOLERANCE )
228223 {
229224 return emptyResult;
230225 }
231226
232- // Step 9: Inverse-transform warped points back to undistorted coordinates
233- std::vector<cv::Point2f> warpedPoints;
234- cv::KeyPoint::convert (warpedKeypoints, warpedPoints);
235-
236- cv::Mat homographyInv = homography.inv ();
237- std::vector<cv::Point2f> undistortedPoints;
238- cv::perspectiveTransform (warpedPoints, undistortedPoints, homographyInv);
239-
240- // Step 10: If distorted input, re-distort and match to original keypoints
241- std::vector<cv::Point2f> finalImagePoints (numberOfWarpedKeypoints);
242-
243- if (isDistorted)
244- {
245- double fx = intrinsicMatrix.at <double >(0 , 0 );
246- double fy = intrinsicMatrix.at <double >(1 , 1 );
247- double pcx = intrinsicMatrix.at <double >(0 , 2 );
248- double pcy = intrinsicMatrix.at <double >(1 , 2 );
249- double k1 = distortionCoefficients.at <double >(0 , 0 );
250- double k2 = distortionCoefficients.at <double >(0 , 1 );
251- double p1 = distortionCoefficients.at <double >(0 , 2 );
252- double p2 = distortionCoefficients.at <double >(0 , 3 );
253- double k3 = distortionCoefficients.at <double >(0 , 4 );
254-
255- for (int i = 0 ; i < numberOfWarpedKeypoints; i++)
256- {
257- // Re-distort the undistorted point
258- double relX = (undistortedPoints[i].x - pcx) / fx;
259- double relY = (undistortedPoints[i].y - pcy) / fy;
260- double r2 = relX * relX + relY * relY;
261- double r4 = r2 * r2;
262- double r6 = r2 * r4;
263- double radial = 1.0 + k1 * r2 + k2 * r4 + k3 * r6;
264-
265- double distX = relX * radial + (2.0 * p1 * relX * relY + p2 * (r2 + 2.0 * relX * relX));
266- double distY = relY * radial + (p1 * (r2 + 2.0 * relY * relY) + 2.0 * p2 * relX * relY);
267-
268- distX = distX * fx + pcx;
269- distY = distY * fy + pcy;
270-
271- finalImagePoints[i] = cv::Point2f (static_cast <float >(distX),
272- static_cast <float >(distY));
273- }
274- }
275- else
276- {
277- finalImagePoints = undistortedPoints;
278- }
279-
280- // Step 11: Remove duplicate ID assignments
281- // Find which IDs appear more than once and exclude them
227+ // Step 10: Remove duplicate ID assignments (keep only unique matches)
282228 std::vector<int > idCount (gridPoints.rows , 0 );
283- for (int i = 0 ; i < numberOfWarpedKeypoints ; i++)
229+ for (int i = 0 ; i < numberOfKeypoints ; i++)
284230 {
285231 idCount[assignedGridIndex[i]]++;
286232 }
287233
288- // Build final result with only unique assignments
289234 std::vector<int > validIndexes;
290- for (int i = 0 ; i < numberOfWarpedKeypoints ; i++)
235+ for (int i = 0 ; i < numberOfKeypoints ; i++)
291236 {
292237 if (idCount[assignedGridIndex[i]] == 1 )
293238 {
@@ -300,6 +245,7 @@ cv::Mat ExtractDots(
300245 return emptyResult;
301246 }
302247
248+ // Step 11: Build result using original distorted image coordinates
303249 cv::Mat result (static_cast <int >(validIndexes.size ()), 6 , CV_64F );
304250
305251 for (int i = 0 ; i < static_cast <int >(validIndexes.size ()); i++)
@@ -308,8 +254,8 @@ cv::Mat ExtractDots(
308254 int gridIdx = assignedGridIndex[idx];
309255
310256 result.at <double >(i, 0 ) = gridPoints.at <double >(gridIdx, 0 ); // id
311- result.at <double >(i, 1 ) = finalImagePoints [idx].x ; // x_pix
312- result.at <double >(i, 2 ) = finalImagePoints [idx].y ; // y_pix
257+ result.at <double >(i, 1 ) = distortedPts [idx].x ; // x_pix
258+ result.at <double >(i, 2 ) = distortedPts [idx].y ; // y_pix
313259 result.at <double >(i, 3 ) = gridPoints.at <double >(gridIdx, 3 ); // x_mm
314260 result.at <double >(i, 4 ) = gridPoints.at <double >(gridIdx, 4 ); // y_mm
315261 result.at <double >(i, 5 ) = gridPoints.at <double >(gridIdx, 5 ); // z_mm
0 commit comments