2008-09-17 72 views
21

如果椭圆的长轴是垂直或水平的,那么计算边界框很容易,但椭圆旋转的时候怎么办?如何计算椭圆的轴对齐边界框?

目前我能想到的唯一方法是计算周边的所有点并找到最大/最小x和y值。似乎应该有一个更简单的方法。

如果有一个函数(在数学意义上)描述一个任意角度的椭圆,然后我可以使用它的导数找到斜率为零或未定义的点,但我似乎无法找到一个点。

编辑:为了澄清,我需要轴对齐的边界框,即它不应该与椭圆一起旋转,但保持与x轴对齐,因此转换边界框将不起作用。

回答

31

你可以尝试使用参数化椭圆的方程以任意角度旋转:

x = h + a*cos(t)*cos(phi) - b*sin(t)*sin(phi) [1] 
y = k + b*sin(t)*cos(phi) + a*cos(t)*sin(phi) [2] 

...其中椭圆的中心(h,k)半长轴a和半短轴b旋转了角度φ。

然后可以区分和解决梯度= 0:

0 = dx/dt = -a*sin(t)*cos(phi) - b*cos(t)*sin(phi) 

=>

tan(t) = -b*tan(phi)/a [3] 

这应该给你在t许多解决方案(其中两个您有兴趣),插头返回到[1]以获得最大和最小x。

重复为[2]:

0 = dy/dt = b*cos(t)*cos(phi) - a*sin(t)*sin(phi) 

=>

tan(t) = b*cot(phi)/a [4] 

让我们试着一个例子:其中a = 2

考虑在(0,0)的椭圆,b = 1,通过PI/4旋转:

[1] =>

x = 2*cos(t)*cos(PI/4) - sin(t)*sin(PI/4) 

[3] =>

tan(t) = -tan(PI/4)/2 = -1/2 

=>

t = -0.4636 + n*PI 

我们感兴趣的是T = -0.4636和t = -3.6052

所以我们得到:

x = 2*cos(-0.4636)*cos(PI/4) - sin(-0.4636)*sin(PI/4) = 1.5811 

x = 2*cos(-3.6052)*cos(PI/4) - sin(-3.6052)*sin(PI/4) = -1.5811 
+0

谢谢。这是有效的,除非你在方程式二中有一个错字。减号应该是一个加号。 – 2008-09-17 23:00:44

+0

固定,似乎我也跟着[2] tan(t)的解决方案,所以我也修正了这一点。希望你能发现我所有的错误 - 这里都是在信封的背面涂写的..;) – 2008-09-18 00:18:01

2

我认为最有用的公式是这一个。从原点的角度披旋转省略号具有如方程:

alt text

alt text

,其中(H,K)是中心,a和b的长轴和短轴的尺寸和t从-pi到pi不等。

从这一点,你应该能够得出其中T DX/DT或DY/DT变为0

+0

我现在感觉很慢,花了很多时间写了我的答案T.T – 2008-09-17 21:51:09

5

这是相对简单的,但有点难以解释,因为你没有给我们你代表你的椭圆的方式。有很多方法可以做到这一点..

无论如何,总的原则是这样的:你不能直接计算轴对齐的边界框。但是,您可以将x和y中的椭圆的极值计算为二维空间中的点。 (t)= ellipse_equation(t)和y(t)= ellipse_equation(t),这就足够了。获取它的一阶导数并解决它的根源。因为我们正在处理基于三角函数的椭圆,这很简单。你应该得到一个方程,通过atan,acos或asin得到根。

提示:要检查您的代码,请尝试使用未旋转的椭圆:您应该在0,Pi/2,Pi和3 * Pi/2处获取根。

对每个轴(x和y)都这样做。您最多可以获得四个根(如果您的椭圆退化了,则减少,例如其中一个半径为零)。评估根部的位置并获得椭圆的所有极值点。

现在你快到了。获取椭圆的边界框就像扫描xmin,xmax,ymin和ymax这四个点一样简单。

顺便说一句 - 如果您在找到椭圆方程时遇到问题:尝试将其缩小为具有中心,两个半径和中心周围的旋转角度的轴对齐的椭圆的情况。

如果这样做的公式变成:

// the ellipse unrotated: 
    temp_x (t) = radius.x * cos(t); 
    temp_y (t) = radius.y = sin(t); 

    // the ellipse with rotation applied: 
    x(t) = temp_x(t) * cos(angle) - temp_y(t) * sin(angle) + center.x; 
    y(t) = temp_x(t) * sin(angle) + temp_y(t) * cos(angle) + center.y; 
4

我发现了一个简单的公式在http://www.iquilezles.org/www/articles/ellipses/ellipses.htm(而忽略了Z轴)。

我实现它大致是这样的:

num ux = ellipse.r1 * cos(ellipse.phi); 
num uy = ellipse.r1 * sin(ellipse.phi); 
num vx = ellipse.r2 * cos(ellipse.phi+PI/2); 
num vy = ellipse.r2 * sin(ellipse.phi+PI/2); 

num bbox_halfwidth = sqrt(ux*ux + vx*vx); 
num bbox_halfheight = sqrt(uy*uy + vy*vy); 

Point bbox_ul_corner = new Point(ellipse.center.x - bbox_halfwidth, 
           ellipse.center.y - bbox_halfheight); 

Point bbox_br_corner = new Point(ellipse.center.x + bbox_halfwidth, 
           ellipse.center.y + bbox_halfheight); 
0

该代码是基于所述码user1789690上面作出了贡献,但在Delphi实现。我已经测试了这一点,并且据我所知可以完美地工作。我花了整整一天的时间寻找一个算法或一些代码,测试了一些不起作用的结果,我很高兴终于找到了上面的代码。我希望有人认为这很有用。此代码将计算旋转椭圆的边界框。边界框是轴对齐的,而不是随着椭圆旋转。半径是旋转前的椭圆。

type 

    TSingleRect = record 
    X:  Single; 
    Y:  Single; 
    Width: Single; 
    Height: Single; 
    end; 

function GetBoundingBoxForRotatedEllipse(EllipseCenterX, EllipseCenterY, EllipseRadiusX, EllipseRadiusY, EllipseAngle: Single): TSingleRect; 
var 
    a: Single; 
    b: Single; 
    c: Single; 
    d: Single; 
begin 
    a := EllipseRadiusX * Cos(EllipseAngle); 
    b := EllipseRadiusY * Sin(EllipseAngle); 
    c := EllipseRadiusX * Sin(EllipseAngle); 
    d := EllipseRadiusY * Cos(EllipseAngle); 
    Result.Width := Hypot(a, b) * 2; 
    Result.Height := Hypot(c, d) * 2; 
    Result.X  := EllipseCenterX - Result.Width * 0.5; 
    Result.Y  := EllipseCenterY - Result.Height * 0.5; 
end; 
1

这里是的情况下的公式,如果椭圆由其灶和偏心(为在那里通过轴的长度,中心和角度给定的情况下,请参阅例如在答复user1789690)给出。

即,如果病灶是(X0,Y0)和(x1,y1)和偏心率e,然后

bbox_halfwidth = sqrt(k2*dx2 + (k2-1)*dy2)/2 
bbox_halfheight = sqrt((k2-1)*dx2 + k2*dy2)/2 

其中

dx = x1-x0 
dy = y1-y0 
dx2 = dx*dx 
dy2 = dy*dy 
k2 = 1.0/(e*e) 

我导出的公式从应答由用户1789690和约翰尼尔森。

1

Brilian Johan Nilsson。 我已经转录你的代码为C# - ellipseAngle现在在度:

private static RectangleF EllipseBoundingBox(int ellipseCenterX, int ellipseCenterY, int ellipseRadiusX, int ellipseRadiusY, double ellipseAngle) 
{ 
    double angle = ellipseAngle * Math.PI/180; 
    double a = ellipseRadiusX * Math.Cos(angle); 
    double b = ellipseRadiusY * Math.Sin(angle); 
    double c = ellipseRadiusX * Math.Sin(angle); 
    double d = ellipseRadiusY * Math.Cos(angle); 
    double width = Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2)) * 2; 
    double height = Math.Sqrt(Math.Pow(c, 2) + Math.Pow(d, 2)) * 2; 
    var x= ellipseCenterX - width * 0.5; 
    var y= ellipseCenterY + height * 0.5; 
    return new Rectangle((int)x, (int)y, (int)width, (int)height); 
} 
0

这是我寻找紧密配合矩形任意方位椭圆函数

我有OpenCV的矩形和点实施:

CG - 中心的椭圆的

大小 - 椭圆的主要,次要轴

角 - 椭圆

cv::Rect ellipse_bounding_box(const cv::Point2f &cg, const cv::Size2f &size, const float angle) { 

    float a = size.width/2; 
    float b = size.height/2; 
    cv::Point pts[4]; 

    float phi = angle * (CV_PI/180); 
    float tan_angle = tan(phi); 
    float t = atan((-b*tan_angle)/a); 
    float x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi); 
    float y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi); 
    pts[0] = cv::Point(cvRound(x), cvRound(y)); 

    t = atan((b*(1/tan(phi)))/a); 
    x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi); 
    y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi); 
    pts[1] = cv::Point(cvRound(x), cvRound(y)); 

    phi += CV_PI; 
    tan_angle = tan(phi); 
    t = atan((-b*tan_angle)/a); 
    x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi); 
    y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi); 
    pts[2] = cv::Point(cvRound(x), cvRound(y)); 

    t = atan((b*(1/tan(phi)))/a); 
    x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi); 
    y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi); 
    pts[3] = cv::Point(cvRound(x), cvRound(y)); 

    long left = 0xfffffff, top = 0xfffffff, right = 0, bottom = 0; 
    for (int i = 0; i < 4; i++) { 
     left = left < pts[i].x ? left : pts[i].x; 
     top = top < pts[i].y ? top : pts[i].y; 
     right = right > pts[i].x ? right : pts[i].x; 
     bottom = bottom > pts[i].y ? bottom : pts[i].y; 
    } 
    cv::Rect fit_rect(left, top, (right - left) + 1, (bottom - top) + 1); 
    return fit_rect; 
} 
1

如果您的OpenCV/C++操作和使用cv::fitEllipse(..)功能的定位,你可能需要边界椭圆的矩形。在这里我用迈克的答案提出了一个解决方案:

// tau = 2 * pi, see tau manifest 
const double TAU = 2 * std::acos(-1); 

cv::Rect calcEllipseBoundingBox(const cv::RotatedRect &anEllipse) 
{ 
    if (std::fmod(std::abs(anEllipse.angle), 90.0) <= 0.01) { 
     return anEllipse.boundingRect(); 
    } 

    double phi = anEllipse.angle * TAU/360; 
    double major = anEllipse.size.width/2.0; 
    double minor = anEllipse.size.height/2.0; 

    if (minor > major) { 
     std::swap(minor, major); 
     phi += TAU/4; 
    } 

    double cosPhi = std::cos(phi), sinPhi = std::sin(phi); 
    double tanPhi = sinPhi/cosPhi; 

    double tx = std::atan(-minor * tanPhi/major); 
    cv::Vec2d eqx{ major * cosPhi, - minor * sinPhi }; 
    double x1 = eqx.dot({ std::cos(tx),   std::sin(tx)   }); 
    double x2 = eqx.dot({ std::cos(tx + TAU/2), std::sin(tx + TAU/2) }); 

    double ty = std::atan(minor/(major * tanPhi)); 
    cv::Vec2d eqy{ major * sinPhi, minor * cosPhi }; 
    double y1 = eqy.dot({ std::cos(ty),   std::sin(ty)   }); 
    double y2 = eqy.dot({ std::cos(ty + TAU/2), std::sin(ty + TAU/2) }); 

    cv::Rect_<float> bb{ 
     cv::Point2f(std::min(x1, x2), std::min(y1, y2)), 
     cv::Point2f(std::max(x1, x2), std::max(y1, y2)) 
    }; 

    return bb + anEllipse.center; 
}