Merge pull request #27704 from MaximSmolskiy:fix_checking_that_point_lies_inside_ellipse

Fix checking that point lies inside ellipse #27704

### Pull Request Readiness Checklist

Previous `check_pt_in_ellipse` implementation was incorrect. For points on ellipse `cv::norm(to_pt)` should be equal to `el_dist`.

I tested current implementation with following Python script:
```
import cv2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse

def check_pt_in_ellipse(pt, el):
    center, axes, angle = ellipse
    to_pt = pt - center
    el_angle = angle * np.pi / 180
    to_pt_r_x = to_pt[0] * np.cos(-el_angle) - to_pt[1] * np.sin(-el_angle)
    to_pt_r_y = to_pt[0] * np.sin(-el_angle) + to_pt[1] * np.cos(-el_angle)
    pt_angle = np.arctan2(to_pt_r_y / axes[1], to_pt_r_x / axes[0])
    x_dist = 0.5 * axes[0] * np.cos(pt_angle)
    y_dist = 0.5 * axes[1] * np.sin(pt_angle)
    el_dist = np.sqrt(x_dist * x_dist + y_dist * y_dist)
    assert abs(np.linalg.norm(to_pt) - el_dist) < 1e-10

# TEST(Imgproc_FitEllipse_Issue_4515, accuracy) {
points = np.array([
    [327, 317],
    [328, 316],
    [329, 315],
    [330, 314],
    [331, 314],
    [332, 314],
    [333, 315],
    [333, 316],
    [333, 317],
    [333, 318],
    [333, 319],
    [333, 320],
])

ellipse = cv2.fitEllipseDirect(points)

center, axes, angle = ellipse

angle_rad = np.deg2rad(angle)
points_on_ellipse = []
for point_angle_deg in range(0, 360, 10):
    point_angle = np.deg2rad(point_angle_deg)
    point = np.array([0., 0.])
    point_x = axes[0] * 0.5 * np.cos(point_angle)
    point_y = axes[1] * 0.5 * np.sin(point_angle)
    point[0] = point_x * np.cos(angle_rad) - point_y * np.sin(angle_rad)
    point[1] = point_x * np.sin(angle_rad) + point_y * np.cos(angle_rad)
    point[0] += center[0]
    point[1] += center[1]
    points_on_ellipse.append(point)

points_on_ellipse = np.array(points_on_ellipse)

for point in points_on_ellipse:
    check_pt_in_ellipse(point, ellipse)

plt.figure(figsize=(8, 8))
plt.scatter(points[:, 0], points[:, 1], c='red', label='points')
plt.scatter(points_on_ellipse[:, 0], points_on_ellipse[:, 1], c='yellow', label='ellipse')
ellipse = Ellipse(xy=center, width=axes[0], height=axes[1], 
                  angle=angle, facecolor='none', edgecolor='b')
plt.gca().add_patch(ellipse)
plt.gca().set_aspect('equal')
plt.legend()
plt.show()
```

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
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Maxim Smolskiy 2025-08-26 14:02:53 +03:00 committed by GitHub
parent ff9da98c7c
commit ad560f69f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -11,10 +11,13 @@ namespace opencv_test { namespace {
// return true if point lies inside ellipse // return true if point lies inside ellipse
static bool check_pt_in_ellipse(const Point2f& pt, const RotatedRect& el) { static bool check_pt_in_ellipse(const Point2f& pt, const RotatedRect& el) {
Point2f to_pt = pt - el.center; Point2f to_pt = pt - el.center;
double pt_angle = atan2(to_pt.y, to_pt.x);
double el_angle = el.angle * CV_PI / 180; double el_angle = el.angle * CV_PI / 180;
double x_dist = 0.5 * el.size.width * cos(pt_angle + el_angle); const Point2d to_pt_el(
double y_dist = 0.5 * el.size.height * sin(pt_angle + el_angle); to_pt.x * cos(-el_angle) - to_pt.y * sin(-el_angle),
to_pt.x * sin(-el_angle) + to_pt.y * cos(-el_angle));
const double pt_angle = atan2(to_pt_el.y / el.size.height, to_pt_el.x / el.size.width);
const double x_dist = 0.5 * el.size.width * cos(pt_angle);
const double y_dist = 0.5 * el.size.height * sin(pt_angle);
double el_dist = sqrt(x_dist * x_dist + y_dist * y_dist); double el_dist = sqrt(x_dist * x_dist + y_dist * y_dist);
return cv::norm(to_pt) < el_dist; return cv::norm(to_pt) < el_dist;
} }