如果椭圆的主轴是垂直或水平,则很容易计算边界框,但是旋转椭圆时会怎样呢?
到目前为止,我唯一想到的方法是计算周长周围的所有点,并找到最大/最小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 |
=>
我们对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角度旋转的省略号具有以下公式:
其中(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; |