从给定点到给定椭圆的距离

时间:2014-04-09 10:25:09

标签: c++ opencv math ellipse

我有一个椭圆,由中心点,radiusX和radiusY定义,我有一个Point。我想在椭圆上找到最接近给定点的点。在下图中,那将是S1。

graph1

现在我已经有了代码,但在其中的某个地方存在逻辑错误,而我似乎无法找到它。我将问题分解为以下代码示例:

#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <math.h>

using namespace std;

void dostuff();

int main()
{
    dostuff();
    return 0;
}

typedef std::vector<cv::Point> vectorOfCvPoints;

void dostuff()
{

    const double ellipseCenterX = 250;
    const double ellipseCenterY = 250;
    const double ellipseRadiusX = 150;
    const double ellipseRadiusY = 100;

    vectorOfCvPoints datapoints;

    for (int i = 0; i < 360; i+=5)
    {
        double angle = i / 180.0 * CV_PI;
        double x = ellipseRadiusX * cos(angle);
        double y = ellipseRadiusY * sin(angle);
        x *= 1.4;
        y *= 1.4;
        x += ellipseCenterX;
        y += ellipseCenterY;
        datapoints.push_back(cv::Point(x,y));
    }

    cv::Mat drawing = cv::Mat::zeros( 500, 500, CV_8UC1 );

    for (int i = 0; i < datapoints.size(); i++)
    {
        const cv::Point & curPoint = datapoints[i];
        const double curPointX = curPoint.x;
        const double curPointY = curPoint.y * -1; //transform from image coordinates to geometric coordinates

        double angleToEllipseCenter = atan2(curPointY - ellipseCenterY * -1, curPointX - ellipseCenterX); //ellipseCenterY * -1 for transformation to geometric coords (from image coords)

        double nearestEllipseX = ellipseCenterX + ellipseRadiusX * cos(angleToEllipseCenter);
        double nearestEllipseY = ellipseCenterY * -1 + ellipseRadiusY * sin(angleToEllipseCenter); //ellipseCenterY * -1 for transformation to geometric coords (from image coords)


        cv::Point center(ellipseCenterX, ellipseCenterY);
        cv::Size axes(ellipseRadiusX, ellipseRadiusY);
        cv::ellipse(drawing, center, axes, 0, 0, 360, cv::Scalar(255));
        cv::line(drawing, curPoint, cv::Point(nearestEllipseX,nearestEllipseY*-1), cv::Scalar(180));

    }
    cv::namedWindow( "ellipse", CV_WINDOW_AUTOSIZE );
    cv::imshow( "ellipse", drawing );
    cv::waitKey(0);
}

它产生以下图像:

snapshot1

你可以看到它实际上找到了&#34; near&#34;椭圆上的点,但它不是&#34;最近的&#34;点。我故意想要的是:(原谅我糟糕的画作)

snapshot2

你会扩大最后一幅图像中的线条,它们会穿过椭圆的中心,但前一幅图像中的线条不是这种情况。
我希望你能得到这张照片。谁能告诉我我做错了什么?

7 个答案:

答案 0 :(得分:22)

考虑给定点(c,d)周围的边界圆,它绕过椭圆上的最近点。从图中可以清楚地看出,最近的点是这样的,从它绘制到给定点的线必须垂直于椭圆和圆的共用切线。任何其他点都在圆圈之外,因此必须远离给定点。

enter image description here

所以你要找的点是不是线和椭圆之间的交点,而是图中的点(x,y)。

切线渐变:

enter image description here

线条渐变:

enter image description here

perpedicular line的条件 - 梯度的乘积= -1:

enter image description here

enter image description here

enter image description here

重新排列并替换为椭圆方程时......

enter image description here

...这将根据x或y给出两个讨厌的四次(4次多项式)方程。 AFAIK没有通用分析(精确代数)方法来解决它们。您可以尝试迭代方法 - 查找Newton-Raphson迭代根寻找算法。

看看这篇关于这个主题的非常好的论文: http://www.spaceroots.org/documents/distance/distance-to-ellipse.pdf

对不完整的答案感到抱歉 - 我完全责怪数学和自然法则......

编辑:oops,我似乎在图xD

中有一个错误的方法

答案 1 :(得分:5)

有一种比牛顿方法更好的收敛的相对简单的数值方法。我有一篇关于它为何起作用的博客文章http://wet-robots.ghost.io/simple-method-for-distance-to-ellipse/

此实现在没有任何trig函数的情况下工作:

def solve(semi_major, semi_minor, p):  
    px = abs(p[0])
    py = abs(p[1])

    tx = 0.707
    ty = 0.707

    a = semi_major
    b = semi_minor

    for x in range(0, 3):
        x = a * tx
        y = b * ty

        ex = (a*a - b*b) * tx**3 / a
        ey = (b*b - a*a) * ty**3 / b

        rx = x - ex
        ry = y - ey

        qx = px - ex
        qy = py - ey

        r = math.hypot(ry, rx)
        q = math.hypot(qy, qx)

        tx = min(1, max(0, (qx * r / q + ex) / a))
        ty = min(1, max(0, (qy * r / q + ey) / b))
        t = math.hypot(ty, tx)
        tx /= t 
        ty /= t 

    return (math.copysign(a * tx, p[0]), math.copysign(b * ty, p[1]))

Convergence

Adrian Stephens感谢Trig-Free Optimization

答案 2 :(得分:4)

以下是本文实现的C#代码,用于求解椭圆: http://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf

请注意,此代码未经测试 - 如果您发现任何错误,请告知我们。

    //Pseudocode for robustly computing the closest ellipse point and distance to a query point. It
    //is required that e0 >= e1 > 0, y0 >= 0, and y1 >= 0.
    //e0,e1 = ellipse dimension 0 and 1, where 0 is greater and both are positive.
    //y0,y1 = initial point on ellipse axis (center of ellipse is 0,0)
    //x0,x1 = intersection point

    double GetRoot ( double r0 , double z0 , double z1 , double g )
    {
        double n0 = r0*z0;
        double s0 = z1 - 1; 
        double s1 = ( g < 0 ? 0 : Math.Sqrt(n0*n0+z1*z1) - 1 ) ;
        double s = 0;
        for ( int i = 0; i < maxIter; ++i ){
            s = ( s0 + s1 ) / 2 ;
            if ( s == s0 || s == s1 ) {break; }
            double ratio0 = n0 /( s + r0 );
            double ratio1 = z1 /( s + 1 );
            g = ratio0*ratio0 + ratio1*ratio1 - 1 ;
            if (g > 0) {s0 = s;} else if (g < 0) {s1 = s ;} else {break ;}
        }
        return s;
    }
    double DistancePointEllipse( double e0 , double e1 , double y0 , double y1 , out double x0 , out double x1)
    {
        double distance;
        if ( y1 > 0){
            if ( y0 > 0){
                double z0 = y0 / e0; 
                double z1 = y1 / e1; 
                double g = z0*z0+z1*z1 - 1;
                if ( g != 0){
                    double r0 = (e0/e1)*(e0/e1);
                    double sbar = GetRoot(r0 , z0 , z1 , g);
                    x0 = r0 * y0 /( sbar + r0 );
                    x1 = y1 /( sbar + 1 );
                    distance = Math.Sqrt( (x0-y0)*(x0-y0) + (x1-y1)*(x1-y1) );
                    }else{
                        x0 = y0; 
                        x1 = y1;
                        distance = 0;
                    }
                }
                else // y0 == 0
                    x0 = 0 ; x1 = e1 ; distance = Math.Abs( y1 - e1 );
        }else{ // y1 == 0
            double numer0 = e0*y0 , denom0 = e0*e0 - e1*e1;
            if ( numer0 < denom0 ){
                    double xde0 = numer0/denom0;
                    x0 = e0*xde0 ; x1 = e1*Math.Sqrt(1 - xde0*xde0 );
                    distance = Math.Sqrt( (x0-y0)*(x0-y0) + x1*x1 );
                }else{
                    x0 = e0; 
                    x1 = 0; 
                    distance = Math.Abs( y0 - e0 );
            }
        }
        return distance;
    }

答案 3 :(得分:2)

以下python代码实现了&#34; Distance from a Point to an Ellipse&#34;中描述的等式。并使用牛顿法找到根,并从椭圆上的最近点到该点。

不幸的是,从示例中可以看出,它似乎只在椭圆外部准确。在椭圆内部,奇怪的事情发生了。

from math import sin, cos, atan2, pi, fabs


def ellipe_tan_dot(rx, ry, px, py, theta):
    '''Dot product of the equation of the line formed by the point
    with another point on the ellipse's boundary and the tangent of the ellipse
    at that point on the boundary.
    '''
    return ((rx ** 2 - ry ** 2) * cos(theta) * sin(theta) -
            px * rx * sin(theta) + py * ry * cos(theta))


def ellipe_tan_dot_derivative(rx, ry, px, py, theta):
    '''The derivative of ellipe_tan_dot.
    '''
    return ((rx ** 2 - ry ** 2) * (cos(theta) ** 2 - sin(theta) ** 2) -
            px * rx * cos(theta) - py * ry * sin(theta))


def estimate_distance(x, y, rx, ry, x0=0, y0=0, angle=0, error=1e-5):
    '''Given a point (x, y), and an ellipse with major - minor axis (rx, ry),
    its center at (x0, y0), and with a counter clockwise rotation of
    `angle` degrees, will return the distance between the ellipse and the
    closest point on the ellipses boundary.
    '''
    x -= x0
    y -= y0
    if angle:
        # rotate the points onto an ellipse whose rx, and ry lay on the x, y
        # axis
        angle = -pi / 180. * angle
        x, y = x * cos(angle) - y * sin(angle), x * sin(angle) + y * cos(angle)

    theta = atan2(rx * y, ry * x)
    while fabs(ellipe_tan_dot(rx, ry, x, y, theta)) > error:
        theta -= ellipe_tan_dot(
            rx, ry, x, y, theta) / \
            ellipe_tan_dot_derivative(rx, ry, x, y, theta)

    px, py = rx * cos(theta), ry * sin(theta)
    return ((x - px) ** 2 + (y - py) ** 2) ** .5

以下是一个例子:

rx, ry = 12, 35  # major, minor ellipse axis
x0 = y0 = 50  # center point of the ellipse
angle = 45  # ellipse's rotation counter clockwise
sx, sy = s = 100, 100  # size of the canvas background

dist = np.zeros(s)
for x in range(sx):
    for y in range(sy):
        dist[x, y] = estimate_distance(x, y, rx, ry, x0, y0, angle)

plt.imshow(dist.T, extent=(0, sx, 0, sy), origin="lower")
plt.colorbar()
ax = plt.gca()
ellipse = Ellipse(xy=(x0, y0), width=2 * rx, height=2 * ry, angle=angle,
                  edgecolor='r', fc='None', linestyle='dashed')
ax.add_patch(ellipse)
plt.show()

生成rotated ellipse椭圆和距椭圆边界的距离作为热图。可以看出,在边界处距离为零(深蓝色)。

答案 4 :(得分:1)

您只需要计算[P1,P0]行到您的elipse的交点S1

如果行清单是:

enter image description here

和elipse equesion是:

elipse equesion

的值不是S1的值:

enter image description here

现在您只需要计算S1P1之间的距离,公式(A,B点)是: distance

答案 5 :(得分:1)

给定参数形式的椭圆 E 和点 P

eqn

P E(t)之间距离的平方

eqn

最低要求必须满足

eqn

使用三角标识

eqn

并代以

enter image description here

产生以下四次方程:

enter image description here

这是一个示例C函数,solves the quartic directly并计算椭圆上最近点的 sin(t) cos(t)

constructor(
    private peopleStore: Store<fromPeopleStore.PeopleState>,
    private toolbarStore: Store<fromToolbarStore.CustomerState>,
    public dialog: MatDialog
  ) {
    this.peopleStore.dispatch(new fromPeopleStore.LoadPeople());
    this.people$ = this.peopleStore.select(fromPeopleStore.getAllPeople);
    this.selectedCustomers$ = this.toolbarStore.select(fromToolbarStore.getSelectedCustomers);
  }

  ngOnInit() {
    this.selectedCustomers$.subscribe(selected => {
      if (selected.length !== 0) {
        this.selectedCustomersPeople$ = this.getSelectedCustomersPeople();
        this.selectedCustomersPeople$.subscribe(people => {
          this.dataSource = new MatTableDataSource(people);
          this.dataSource.paginator = this.paginator;
          this.dataSource.sort = this.sort;
        });
      } else {
        this.people$.subscribe(people => {
          this.dataSource = new MatTableDataSource(people);
          this.dataSource.paginator = this.paginator;
          this.dataSource.sort = this.sort;
        });
      }
    });
  }

  getSelectedCustomersPeople(): Observable<Person[]> {
    return combineLatest(this.selectedCustomers$, this.people$, (customers, people) => {
      const selectedCustomersPeople = customers.map(customer =>
        Object.assign(people.filter(person => person.companyId === customer.id))
      );
      const flattenSelectedPeople = [].concat.apply([], selectedCustomersPeople);

      return flattenSelectedPeople;
    });
  }

  applyFilter(filterValue = ' ') {
    filterValue = filterValue.trim();
    filterValue = filterValue.toLowerCase();
    this.dataSource.filter = filterValue;
  }

<mat-table #table [dataSource]="dataSource" matSort [@animateStagger]="{value:'50'}">

    <!-- Name Column -->
    <ng-container cdkColumnDef="firstName">
        <mat-header-cell *cdkHeaderCellDef mat-sort-header>First Name</mat-header-cell>
        <mat-cell *cdkCellDef="let person">
            <p class="text-truncate font-weight-600">{{person.firstName}} {{person.familyName}}</p>
        </mat-cell>
    </ng-container>

    <mat-header-row *cdkHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *cdkRowDef="let person; columns: displayedColumns;" class="person" (click)="editPerson(person)" [ngClass]="{'mat-light-blue-50-bg':checkboxes[person.id]}"
        matRipple [@animate]="{value:'*',params:{y:'100%'}}">
    </mat-row>
</mat-table>

Try it online!

答案 6 :(得分:0)

我已经通过焦点解决了距离问题。

对于椭圆上的每个点

r1 + r2 = 2 * a0

其中

r1-从给定点到焦点1的欧几里得距离

r2-从给定点到焦点2的欧几里得距离

a0-半长轴长度

我还可以计算任意给定点的r1和r2,这给了我另一个椭圆,该点位于与给定椭圆同心的位置上。所以距离是

d = Abs((r1 + r2)/ 2-a0)