web-dev-qa-db-ja.com

指定された点から指定された楕円までの距離

Center Point、radiusX、radiusYで定義される楕円があり、ポイントがあります。与えられた点に最も近い楕円上の点を見つけたいです。次の図では、それは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

楕円上で実際に「近い」ポイントを見つけていることがわかりますが、「最も近い」ポイントではありません。私が意図的に欲しいのはこれです:(私の貧弱な絵を許してください)

snapshot2

最後の画像の線を延長すると、楕円の中心と交差しますが、前の画像の線はそうではありません。
写真を撮っていただければ幸いです。誰かが私が間違っていることを教えてもらえますか?

15
user2950911

楕円上の最も近い点を通過する、指定された点(c、d)の周りの境界円について考えてみます。図から、最も近い点は、そこから指定された点に引かれた線が、楕円と円の共有接線に垂直でなければならないようなものであることが明らかです。他のポイントは円の外側にあるため、指定されたポイントからさらに離れている必要があります。

enter image description here

したがって、探している点はではなく線と楕円の交点ですが、図の点(x、y)です。

接線の勾配:

enter image description here

線の勾配:

enter image description here

椎弓根線の条件-勾配の積= -1:

enter image description here

enter image description here

enter image description here

再配置して楕円の方程式に代入すると...

enter image description here

...これにより、xまたはyのいずれかに関して2つの厄介な四次(4次多項式)方程式が得られます。 AFAIKには、それらを解決するための一般的な分析(正確な代数)メソッドはありません。反復法を試すことができます-ニュートン-ラフソン反復求根アルゴリズムを調べてください。

このテーマに関するこの非常に優れた論文をご覧ください: http://www.spaceroots.org/documents/distance/distance-to-ellipse.pdf

不完全な答えで申し訳ありません-私は数学と自然の法則を完全に非難します...

編集:おっと、私は図xDでaとbが間違った方向にあるようです

23
user3235832

ニュートン法よりも収束性の高い比較的単純な数値法があります。なぜそれが機能するのかについてのブログ投稿があります http://wet-robots.ghost.io/simple-method-for-distance-to-ellipse/

この実装は、トリガー関数なしで機能します。

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

クレジットto Adrian Stephens for Trig-Free Optimization

5
user364952

楕円を解決するために、このペーパーから実装された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;
    }
4
johnml1135

次のpythonコードは、 " 点から楕円までの距離 "で説明されている方程式を実装し、ニュートン法を使用して根を見つけ、そこから最も近い点を見つけます。ポイントへの楕円。

残念ながら、例からわかるように、楕円の外側でのみ正確であるように見えます。楕円の中で奇妙なことが起こります。

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 楕円と、ヒートマップとしての楕円の境界からの距離。ご覧のとおり、境界では距離はゼロ(濃い青)です。

2
Matt

[P1,P0]と楕円の交点S1を計算する必要があります。

線の方程式が次の場合:

enter image description here

楕円形の質問は次のとおりです。

elipse equesion

S1の値は次のようになります。

enter image description here

ここで、S1からP1までの距離を計算する必要があります。式(A,Bポイントの場合)は次のとおりです。 distance

1
idanuda

与えられた楕円[〜#〜] e [〜#〜]パラメトリック形式と点[〜#〜] p [〜#〜]

eqn

[〜#〜] p [〜#〜]E(t)の間の距離の2乗は

eqn

最小値は満たす必要があります

eqn

三角関数公式の使用

eqn

と置換

enter image description here

次の四次方程式が得られます。

enter image description here

これは、 四次関数を直接解く で、楕円上の最も近い点についてsin(t)およびcos(t)を計算するC関数の例です。

void nearest(double a, double b, double x, double y, double *ecos_ret, double *esin_ret) {
    double ax = fabs(a*x);
    double by = fabs(b*y);
    double r  = b*b - a*a;
    double c, d;
    int switched = 0;

    if (ax <= by) {
        if (by == 0) {
            if (r >= 0) { *ecos_ret = 1; *esin_ret = 0; }
            else        { *ecos_ret = 0; *esin_ret = 1; }
            return;
        }
        c = (ax - r) / by;
        d = (ax + r) / by;
    } else {
        c = (by + r) / ax;
        d = (by - r) / ax;
        switched = 1;
    }

    double cc = c*c;
    double D0 = 12*(c*d + 1);      // *-4
    double D1 = 54*(d*d - cc);     // *4
    double D  = D1*D1 + D0*D0*D0;  // *16

    double St;
    if (D < 0) {
        double t = sqrt(-D0);             // *2
        double phi = acos(D1 / (t*t*t));
        St = 2*t*cos((1.0/3)*phi);        // *2
    } else {
        double Q = cbrt(D1 + sqrt(D));    // *2
        St = Q - D0 / Q;                  // *2
    }

    double p    = 3*cc;                          // *-2
    double SS   = (1.0/3)*(p + St);              // *4
    double S    = sqrt(SS);                      // *2
    double q    = 2*cc*c + 4*d;                  // *2
    double l    = sqrt(p - SS + q / S) - S - c;  // *2
    double ll   = l*l;                           // *4
    double ll4  = ll + 4;                        // *4
    double esin = (4*l)    / ll4;
    double ecos = (4 - ll) / ll4;

    if (switched) {
        double t = esin;
        esin = ecos;
        ecos = t;
    }

    *ecos_ret = copysign(ecos, a*x);
    *esin_ret = copysign(esin, b*y);
}

オンラインでお試しください!

1
nwellnhof

焦点を介して距離の問題を解決しました。

楕円上のすべての点について

r1 + r2 = 2 * a0

どこ

r1-与えられた点から焦点1までのユークリッド距離

r2-与えられた点から焦点2までのユークリッド距離

a0-半主軸の長さ

また、任意の点のr1とr2を計算することもできます。これにより、この点が存在する、指定された楕円と同心の別の楕円が得られます。だから距離は

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

0
Robert Hudjakov