opencv/doc
s-trinh e258f2595e
Merge pull request #26299 from s-trinh:feat/getClosestEllipsePoints_2
Add getClosestEllipsePoints() function to get the closest point on an ellipse #26299

Following https://github.com/opencv/opencv/issues/26078, I was thinking that a function to get for a considered 2d point the corresponding closest point (or maybe directly the distance?) on an ellipse could be useful.
This would allow computing the fitting error with `fitEllipse()` for instance.

Code is based from:
- https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse/46007540#46007540
- https://blog.chatfield.io/simple-method-for-distance-to-ellipse/
- https://github.com/0xfaded/ellipse_demo

---

Demo code:

<details>
  <summary>code</summary>
 
```cpp
#include <iostream>
#include <opencv2/opencv.hpp>

namespace
{
void scaleApplyColormap(const cv::Mat &img_float, cv::Mat &img)
{
  cv::Mat img_scale = cv::Mat::zeros(img_float.size(), CV_8UC3);

  double min_val = 0, max_val = 0;
  cv::minMaxLoc(img_float, &min_val, &max_val);
  std::cout << "min_val=" << min_val << " ; max_val=" << max_val << std::endl;

  if (max_val - min_val > 1e-2) {
    float a = 255 / (max_val - min_val);
    float b = -a * min_val;

    cv::convertScaleAbs(img_float, img_scale, a, b);
    cv::applyColorMap(img_scale, img, cv::COLORMAP_TURBO);
  }
  else {
    std::cerr << "max_val - min_val <= 1e-2" << std::endl;
  }
}

cv::Mat drawEllipseDistanceMap(const cv::RotatedRect &ellipse_params)
{
  float bb_rect_w = ellipse_params.center.x + ellipse_params.size.width;
  float bb_rect_h = ellipse_params.center.y + ellipse_params.size.height;

  std::vector<cv::Point2f> points_list;
  points_list.resize(1);
  cv::Mat pointsf;
  cv::Mat closest_pts;
  cv::Mat dist_map = cv::Mat::zeros(bb_rect_h*1.5, bb_rect_w*1.5, CV_32F);
  for (int i = 0; i < dist_map.rows; i++) {
    for (int j = 0; j < dist_map.cols; j++) {
      points_list[0].x = j;
      points_list[0].y = i;
      cv::Mat(points_list).convertTo(pointsf, CV_32F);
      cv::getClosestEllipsePoints(ellipse_params, pointsf, closest_pts);
      dist_map.at<float>(i, j) = std::hypot(closest_pts.at<cv::Point2f>(0).x-j, closest_pts.at<cv::Point2f>(0).y-i);
    }
  }

  cv::Mat dist_map_8u;
  scaleApplyColormap(dist_map, dist_map_8u);
  return dist_map_8u;
}
}

int main()
{
  std::vector<cv::Point2f> points_list;

  // [1434, 308], [1434, 309], [1433, 310], [1427, 310], [1427, 312], [1426, 313], [1422, 313], [1422, 314],
  points_list.push_back(cv::Point2f(1434, 308));
  points_list.push_back(cv::Point2f(1434, 309));
  points_list.push_back(cv::Point2f(1433, 310));
  points_list.push_back(cv::Point2f(1427, 310));
  points_list.push_back(cv::Point2f(1427, 312));
  points_list.push_back(cv::Point2f(1426, 313));
  points_list.push_back(cv::Point2f(1422, 313));
  points_list.push_back(cv::Point2f(1422, 314));

  // [1421, 315], [1415, 315], [1415, 316], [1414, 317], [1408, 317], [1408, 319], [1407, 320], [1403, 320],
  points_list.push_back(cv::Point2f(1421, 315));
  points_list.push_back(cv::Point2f(1415, 315));
  points_list.push_back(cv::Point2f(1415, 316));
  points_list.push_back(cv::Point2f(1414, 317));
  points_list.push_back(cv::Point2f(1408, 317));
  points_list.push_back(cv::Point2f(1408, 319));
  points_list.push_back(cv::Point2f(1407, 320));
  points_list.push_back(cv::Point2f(1403, 320));

  // [1403, 321], [1402, 322], [1396, 322], [1396, 323], [1395, 324], [1389, 324], [1389, 326], [1388, 327],
  points_list.push_back(cv::Point2f(1403, 321));
  points_list.push_back(cv::Point2f(1402, 322));
  points_list.push_back(cv::Point2f(1396, 322));
  points_list.push_back(cv::Point2f(1396, 323));
  points_list.push_back(cv::Point2f(1395, 324));
  points_list.push_back(cv::Point2f(1389, 324));
  points_list.push_back(cv::Point2f(1389, 326));
  points_list.push_back(cv::Point2f(1388, 327));

  // [1382, 327], [1382, 328], [1381, 329], [1376, 329], [1376, 330], [1375, 331], [1369, 331], [1369, 333],
  points_list.push_back(cv::Point2f(1382, 327));
  points_list.push_back(cv::Point2f(1382, 328));
  points_list.push_back(cv::Point2f(1381, 329));
  points_list.push_back(cv::Point2f(1376, 329));
  points_list.push_back(cv::Point2f(1376, 330));
  points_list.push_back(cv::Point2f(1375, 331));
  points_list.push_back(cv::Point2f(1369, 331));
  points_list.push_back(cv::Point2f(1369, 333));

  // [1368, 334], [1362, 334], [1362, 335], [1361, 336], [1359, 336], [1359, 1016], [1365, 1016], [1366, 1017],
  points_list.push_back(cv::Point2f(1368, 334));
  points_list.push_back(cv::Point2f(1362, 334));
  points_list.push_back(cv::Point2f(1362, 335));
  points_list.push_back(cv::Point2f(1361, 336));
  points_list.push_back(cv::Point2f(1359, 336));
  points_list.push_back(cv::Point2f(1359, 1016));
  points_list.push_back(cv::Point2f(1365, 1016));
  points_list.push_back(cv::Point2f(1366, 1017));

  // [1366, 1019], [1430, 1019], [1430, 1017], [1431, 1016], [1440, 1016], [1440, 308]
  points_list.push_back(cv::Point2f(1366, 1019));
  points_list.push_back(cv::Point2f(1430, 1019));
  points_list.push_back(cv::Point2f(1430, 1017));
  points_list.push_back(cv::Point2f(1431, 1016));
  points_list.push_back(cv::Point2f(1440, 1016));
  points_list.push_back(cv::Point2f(1440, 308));

  cv::Mat pointsf;
  cv::Mat(points_list).convertTo(pointsf, CV_32F);

  cv::RotatedRect ellipse_params = cv::fitEllipseAMS(pointsf);
  std::cout << "ellipse_params, center=" << ellipse_params.center << " ; size=" << ellipse_params.size
    << " ; angle=" << ellipse_params.angle << std::endl;

  cv::TickMeter tm;
  tm.start();
  cv::Mat dist_map_8u = drawEllipseDistanceMap(ellipse_params);
  tm.stop();
  std::cout << "Elapsed time: " << tm.getAvgTimeSec() << " sec" << std::endl;

  cv::Point center(ellipse_params.center.x, ellipse_params.center.y);
  cv::Point axis(ellipse_params.size.width/2, ellipse_params.size.height/2);
  std::vector<cv::Point> ellipse_pts_list;
  cv::ellipse2Poly(center, axis, ellipse_params.angle, 0, 360, 1, ellipse_pts_list);
  cv::polylines(dist_map_8u, ellipse_pts_list, false, cv::Scalar(0, 0, 0), 3);

  // Points to be fitted
  cv::Mat closest_pts;
  cv::getClosestEllipsePoints(ellipse_params, pointsf, closest_pts);
  for (int i = 0; i < closest_pts.rows; i++) {
    cv::Point pt;
    pt.x = closest_pts.at<cv::Point2f>(i).x;
    pt.y = closest_pts.at<cv::Point2f>(i).y;
    cv::circle(dist_map_8u, pt, 8, cv::Scalar(0, 0, 255), 2);
  }

  cv::imwrite("dist_map_8u.png", dist_map_8u);

  return EXIT_SUCCESS;
}
```
</details>

![image](https://github.com/user-attachments/assets/3345cc86-ba83-44f9-ac78-74058a33a7dc)

---

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
2025-06-03 17:12:16 +03:00
..
images Assorted documentation fixes 2019-08-31 01:50:11 +03:00
js_tutorials Update js_houghcircles_HoughCirclesP.html 2025-04-04 17:36:42 +03:00
pattern_tools Svgfig library update to version 1.0.1 as it uses BSD-3 license. 2024-06-18 11:13:32 +03:00
py_tutorials Update old URL 2025-03-20 19:45:58 +00:00
tools Merge remote-tracking branch 'upstream/3.4' into merge-3.4 2021-04-15 20:50:26 +00:00
tutorials Updating doc markdown to include API in FastCV gemm HAL 2025-05-30 17:01:07 +05:30
acircles_pattern.png Update chessboard pattern image to remove squares overlap. Update also asymmetric pattern image. 2018-03-15 13:22:56 +01:00
bodybg.png Some documentation customization 2014-12-26 19:17:18 +03:00
charuco_board_pattern.png Merge pull request #23363 from vovka643:4.x_generate_charuco 2023-05-19 20:25:59 +03:00
CMakeLists.txt doc: upgraded for compatibility with doxygen 1.12 2024-12-09 23:08:18 +03:00
disabled_doc_warnings.txt Doxygen documentation: BiB references and fixes 2014-12-01 15:50:55 +03:00
Doxyfile.in doc: upgraded for compatibility with doxygen 1.12 2024-12-09 23:08:18 +03:00
DoxygenLayout.xml docs: move detail section of group to the top of the page 2024-02-19 08:13:22 +00:00
faq.markdown Migrated FAQ page to Github Wiki 2020-04-08 13:55:44 +03:00
footer.html doc: upgraded for compatibility with doxygen 1.12 2024-12-09 23:08:18 +03:00
header.html doc: upgraded for compatibility with doxygen 1.12 2024-12-09 23:08:18 +03:00
LICENSE_BSD.txt Merge pull request #18073 from vpisarev:apache2_license 2020-08-17 11:49:11 +00:00
LICENSE_CHANGE_NOTICE.txt Merge pull request #24521 from dkurt:fix_broken_urls 2023-11-11 09:06:10 +03:00
mymath.js doc: fix formulas in JavaDoc broken after Doxygen upgrade 2024-03-11 23:47:23 +03:00
mymath.sty Use camera intrinsic matrix everywhere. Add cameramatrix, distcoeffs and distcoeffsfisheye macros to avoid copy/paste errors. 2020-08-24 05:39:23 +02:00
opencv-logo2.png Updated the OpenCV logo 2020-07-28 18:31:21 +03:00
opencv-logo-small.png Updated the OpenCV logo 2020-07-28 18:31:21 +03:00
opencv-logo-white.png Updated the OpenCV logo 2020-07-28 18:31:21 +03:00
opencv-logo.md Added a note about OpenCV logo 2020-08-21 11:18:40 +03:00
opencv-logo.png Updated the OpenCV logo 2020-07-28 18:31:21 +03:00
opencv.bib Merge pull request #26299 from s-trinh:feat/getClosestEllipsePoints_2 2025-06-03 17:12:16 +03:00
opencv.ico Updated the OpenCV logo 2020-07-28 18:31:21 +03:00
opencv.jpg "atomic bomb" commit. Reorganized OpenCV directory structure 2010-05-11 17:44:00 +00:00
pattern.png Update chessboard pattern image to remove squares overlap. Update also asymmetric pattern image. 2018-03-15 13:22:56 +01:00
root.markdown.in Merge pull request #9466 from huningxin:js 2017-09-25 16:52:07 +03:00
stylesheet.css doc: upgraded for compatibility with doxygen 1.12 2024-12-09 23:08:18 +03:00
tutorial-utils.js Merge pull request #26340 from Kumataro:wa26339 2024-10-22 11:57:14 +03:00