关于数学:如何计算椭圆的轴对齐边界框?

How do you calculate the axis-aligned bounding box of an ellipse?

如果椭圆的主轴是垂直或水平,则很容易计算边界框,但是旋转椭圆时会怎样呢?

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

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

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


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

1
2
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,并旋转角度phi。

然后可以区分并求解梯度= 0:

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

=>

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

哪个应该为您提供许多t的解决方案(您感兴趣的两个),然后将其重新插入[1]以获取最大和最小x。

重复[2]:

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

=>

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

让我们尝试一个例子:

考虑一个椭圆(0,0)的a = 2,b = 1,由PI / 4旋转:

[1] =>

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

[3] =>

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

=>

1
t = -0.4636 + n*PI

我们对t = -0.4636和t = -3.6052有兴趣

这样我们得到:

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

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

我在http://www.iquilezles.org/www/articles/ellipses/ellipses.htm中找到了一个简单的公式(忽略了z轴)。

我大致是这样实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

这是相对简单的,但由于您没有给我们提供椭圆的表示方式,因此很难解释。有很多方法可以做到。

无论如何,一般原则是这样的:您不能直接计算轴对齐的边界框。但是,您可以将x和y中的椭圆的极值计算为2D空间中的点。

为此,将等式x(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一样简单。

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

如果这样做,则等式变为:

1
2
3
4
5
6
7
  // 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;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

我认为最有用的公式就是这个。从原点以phi角度旋转的省略号具有以下公式:

alt text

alt text

其中(h,k)是中心,a和b的长轴和短轴的大小以及t在-pi到pi之间变化。

由此,您应该能够得出t dx / dt或dy / dt变为0的原因。


如果使用OpenCV / C ++并使用cv::fitEllipse(..)函数,则可能需要椭圆的边界矩形。 在这里,我使用麦克的答案提出了一个解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 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;
}


这里是椭圆由其焦点和偏心率给出的情况的公式(对于由轴长,中心和角度给出的情况,参见例如user1789690的回答)。

即,如果焦点是(x0,y0)和(x1,y1)并且偏心距是e,则

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

哪里

1
2
3
4
5
dx = x1-x0
dy = y1-y0
dx2 = dx*dx
dy2 = dy*dy
k2 = 1.0/(e*e)

我从user1789690和Johan Nilsson的答案中得出了公式。


这是我的函数,用于查找紧配合矩形以任意方向椭圆

我有opencv rect和实施点:

cg-椭圆的中心

大小-椭圆的长,短轴

椭圆的角度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
}

该代码基于上面提供的代码user1789690,但在Delphi中实现。我已经对此进行了测试,据我所知它可以完美运行。我花了一整天的时间寻找算法或某些代码,测试了一些无效的代码,很高兴最终找到上面的代码。我希望有人觉得这有用。此代码将计算旋转椭圆的边界框。边界框与轴对齐,并且不随椭圆旋转。半径是椭圆旋转之前的半径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;


推荐阅读

    重启计算机命令linux?

    重启计算机命令linux?,系统,工作,命令,服务,标准,设备,灵活,首要,意义,参数,L

    linux计算机的命令?

    linux计算机的命令?,系统,工作,信息,设备,技术,命令,网站,管理,灵活,基础,lin

    linux用计算器的命令?

    linux用计算器的命令?,代码,环境,情况,异常,工具,数据,位置,平台,精密,设计,

    linux使用命令的方法?

    linux使用命令的方法?,系统,信息,工具,标准,数据,命令,左下角,目录,文件夹,

    linux的数学计算命令?

    linux的数学计算命令?,工作,系统,信息,地址,数字,目录,命令,百分比,情况,管

    linux云计算查看命令?

    linux云计算查看命令?,系统,信息,地址,工作,命令,情况,标准,服务,软件,网络,l

    linux打开计算器命令?

    linux打开计算器命令?,密码,电脑,工作,设备,数字,系统,手机,指数,情况,服务,

    linux命令输出计算?

    linux命令输出计算?,标准,地址,工作,信息,系统,命令,软件,数据,文件,控制台,l

    linux代码对齐命令?

    linux代码对齐命令?,系统,地址,标准,信息,对比,名称,代码,命令,文件,工作,lin

    linux计算总数命令?

    linux计算总数命令?,系统,第一,情况,数据,信息,电脑,命令,百分比,单位,工作,l

    linux中计算器命令?

    linux中计算器命令?,地址,数据,位置,网络,设备,时间,环境,平台,软件,命令,说

    linux退出计算器命令?

    linux退出计算器命令?,工作,地址,系统,命令,通信,信息,电脑,目录,路径,操作,

    重启计算机linux命令?

    重启计算机linux命令?,系统,设备,工作,标准,名称,命令,状态,数据,服务,提示,L

    linux计算器打开命令?

    linux计算器打开命令?,工作,地址,命令,标准,管理,系统,目录,路径,管道,控制

    linux计算摘要的命令?

    linux计算摘要的命令?,数据,网络,数字,密码,工具,名称,正规,标准,代码,文件,l

    linux命令计算时间差?

    linux命令计算时间差?,时间,系统,标准,流程,状态,单位,名称,表达式,命令,时

    linux命令重启计算机?

    linux命令重启计算机?,服务,系统,工作,设备,标准,命令,名称,进程,级别,用户,l

    linux命令计算圆周率?

    linux命令计算圆周率?,代码,圆周率,无理数,直径,比值,表示,圆周,常量,常数,

    linux字节对齐命令?

    linux字节对齐命令?,工作,地址,系统,信息,标准,目录,命令,工具,管理,文件,lin

    linux命令输入方法?

    linux命令输入方法?,系统,电脑,密码,管理,地址,命令,网站,工具,首开,位置,lin