计算旋转矩形中的最大矩形

问题描述 投票:49回答:8

我正在尝试找到计算最大(面积)矩形的最佳方法,该矩形可以包含在旋转的矩形内。

一些图片应该有助于(我希望)可视化我的意思:

给出输入矩形的宽度和高度,以及旋转它的角度。输出矩形不会旋转或倾斜。

我正沿着漫长的路线往下走,我甚至不确定它是否会处理角落的情况(没有双关语)。我确信这有一个优雅的解决方案。有小费吗?

编辑:输出矩形点不一定要触摸输入矩形边。 (感谢E先生)

algorithm math language-agnostic geometry
8个回答
22
投票

我刚来这里寻找相同的答案。在想到如此多的数学考虑之后,我想我会采用半教育的猜测。有点涂鸦我得出(直观的,可能不完全准确)的结论,即最大的矩形与外部产生的矩形成比例,并且它的两个相对的角落位于外部矩形的对角线与最长边的交点处。旋转的矩形。对于正方形,任何对角线和侧面都可以......我想我对此很开心,现在开始刷掉我生锈的触发技术的蜘蛛网(可怜,我知道)。

次要更新...管理进行一些三角形计算。这适用于图像高度大于宽度的情况。

更新。得到了整个工作。这是一些js代码。它连接到一个更大的程序,大多数变量超出了函数的范围,并直接从函数内部进行修改。我知道这不好,但我在一个孤立的情况下使用它,在那里不会与其他脚本混淆:编辑


我冒昧地清理代码并将其解压缩到一个函数:

function getCropCoordinates(angleInRadians, imageDimensions) {
    var ang = angleInRadians;
    var img = imageDimensions;

    var quadrant = Math.floor(ang / (Math.PI / 2)) & 3;
    var sign_alpha = (quadrant & 1) === 0 ? ang : Math.PI - ang;
    var alpha = (sign_alpha % Math.PI + Math.PI) % Math.PI;

    var bb = {
        w: img.w * Math.cos(alpha) + img.h * Math.sin(alpha),
        h: img.w * Math.sin(alpha) + img.h * Math.cos(alpha)
    };

    var gamma = img.w < img.h ? Math.atan2(bb.w, bb.h) : Math.atan2(bb.h, bb.w);

    var delta = Math.PI - alpha - gamma;

    var length = img.w < img.h ? img.h : img.w;
    var d = length * Math.cos(alpha);
    var a = d * Math.sin(alpha) / Math.sin(delta);

    var y = a * Math.cos(gamma);
    var x = y * Math.tan(gamma);

    return {
        x: x,
        y: y,
        w: bb.w - 2 * x,
        h: bb.h - 2 * y
    };
}

我在gamma计算中遇到了一些问题,并对其进行了修改,以考虑原始框最长的方向。

- 马格努斯霍夫


13
投票

试图不打破传统把问题的解决方案作为图片:)


编辑:第三个方程是错误的。正确的是:

(A)* w * cos(α)* w * sin(α)* Y - w * w * sin

要求解线性方程组,您可以使用Cramer ruleGauss method


9
投票

首先,我们处理角度为零或pi / 2的倍数的平凡情况。然后最大的矩形与原始矩形相同。

通常,内部矩形在外部矩形的边界上将具有3个点。如果没有,则可以移动它,使得一个顶点位于底部,一个顶点位于左侧。然后,您可以放大内部矩形,直到其余两个顶点中的一个碰到边界。

我们称外部矩形R1和R2的边。不失一般性,我们可以假设R1 <= R2。如果我们调用内部矩形H和W的边,那么我们就有了

H cos a + W sin a <= R1
H sin a + W cos a <= R2

由于我们在边界上至少有3个点,因此这些不等式中的至少一个实际上必须是相等的。让我们使用第一个。很容易看出:

W = (R1 - H cos a) / sin a

所以该地区是

A = H W = H (R1 - H cos a) / sin a

我们可以采用衍生工具。 H并要求它等于0:

dA/dH = ((R1 - H cos a) - H cos a) / sin a

求解H并使用上面W的表达式,我们发现:

H = R1 / (2 cos a)
W = R1 / (2 sin a)

在经过一些操纵之后,将其替换为第二个不等式

R1 (tan a + 1/tan a) / 2 <= R2

左侧的因子总是至少为1.如果满足不等式,那么我们就有了解决方案。如果不满意,那么解决方案就是满足两个不等式作为等式的解决方案。换句话说:它是接触外部矩形的所有四个边的矩形。这是一个有2个未知数的线性系统,很容易解决:

H = (R2 cos a - R1 sin a) / cos 2a
W = (R1 cos a - R2 sin a) / cos 2a

根据原始坐标,我们得到:

x1 = x4 = W sin a cos a
y1 = y2 = R2 sin a - W sin^2 a 
x2 = x3 = x1 + H
y3 = y4 = y2 + W

5
投票

编辑:下面我的Mathematica答案是错误的 - 我解决的问题与我认为你真正要问的问题略有不同。

要解决你真正要问的问题,我会使用以下算法:

On the Maximum Empty Rectangle Problem

使用这个算法,表示形成旋转矩形边界的有限数量的点(可能是100左右,并确保包括角) - 这些将是文中描述的集合S.

.

.

.

.

.

为了后人的缘故,我把原来的帖子留在了下面:

具有最大面积的内部矩形将始终是矩形,其中矩形的下中间角(图中的alpha附近的角)等于外部矩形的宽度的一半。

我有点欺骗并使用Mathematica为我解决代数:

由此可以看出,内部矩形的最大面积等于1/4宽度^ 2 *角度的cosecant乘以角度的割线。

现在我需要弄清楚这个最佳条件下底角的x值是多少。在mathematica中使用我的区域公式中的Solve函数,我得到以下结果:

这表明底角的x坐标等于宽度的一半。

现在只是为了确保,我将根据经验测试我们的答案。通过下面的结果,你可以看到我的所有测试中的最高区域(肯定不是详尽无遗但你得到的结果)是底角的x值=外部矩形宽度的一半。


5
投票

@Andri在我测试的width > height图像上工作不正常。所以,我通过这种方式修复和优化了他的代码(只有两个三角函数):

calculateLargestRect = function(angle, origWidth, origHeight) {
    var w0, h0;
    if (origWidth <= origHeight) {
        w0 = origWidth;
        h0 = origHeight;
    }
    else {
        w0 = origHeight;
        h0 = origWidth;
    }
    // Angle normalization in range [-PI..PI)
    var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI; 
    ang = Math.abs(ang);      
    if (ang > Math.PI / 2)
        ang = Math.PI - ang;
    var sina = Math.sin(ang);
    var cosa = Math.cos(ang);
    var sinAcosA = sina * cosa;
    var w1 = w0 * cosa + h0 * sina;
    var h1 = w0 * sina + h0 * cosa;
    var c = h0 * sinAcosA / (2 * h0 * sinAcosA + w0);
    var x = w1 * c;
    var y = h1 * c;
    var w, h;
    if (origWidth <= origHeight) {
        w = w1 - 2 * x;
        h = h1 - 2 * y;
    }
    else {
        w = h1 - 2 * y;
        h = w1 - 2 * x;
    }
    return {
        w: w,
        h: h
    }
}

UPDATE

我还决定为比例矩形计算器发布以下函数:

calculateLargestProportionalRect = function(angle, origWidth, origHeight) {
    var w0, h0;
    if (origWidth <= origHeight) {
        w0 = origWidth;
        h0 = origHeight;
    }
    else {
        w0 = origHeight;
        h0 = origWidth;
    }
    // Angle normalization in range [-PI..PI)
    var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI; 
    ang = Math.abs(ang);      
    if (ang > Math.PI / 2)
        ang = Math.PI - ang;
    var c = w0 / (h0 * Math.sin(ang) + w0 * Math.cos(ang));
    var w, h;
    if (origWidth <= origHeight) {
        w = w0 * c;
        h = h0 * c;
    }
    else {
        w = h0 * c;
        h = w0 * c;
    }
    return {
        w: w,
        h: h
    }
}

3
投票

这是最简单的方法...... :)

Step 1
//Before Rotation

int originalWidth = 640;
int originalHeight = 480;

Step 2
//After Rotation
int newWidth = 701;  //int newWidth = 654;  //int newWidth = 513;
int newHeight = 564; //int newHeight = 757; //int newHeight = 664;

Step 3
//Difference in height and width
int widthDiff ;
int heightDiff;
int ASPECT_RATIO = originalWidth/originalHeight; //Double check the Aspect Ratio

if (newHeight > newWidth) {

    int ratioDiff = newHeight - newWidth;
    if (newWidth < Constant.camWidth) {
        widthDiff = (int) Math.floor(newWidth / ASPECT_RATIO);
        heightDiff = (int) Math.floor((originalHeight - (newHeight - originalHeight)) / ASPECT_RATIO);
    }
    else {
        widthDiff = (int) Math.floor((originalWidth - (newWidth - originalWidth) - ratioDiff) / ASPECT_RATIO);
        heightDiff = originalHeight - (newHeight - originalHeight);
    }

} else {
    widthDiff = originalWidth - (originalWidth);
    heightDiff = originalHeight - (newHeight - originalHeight);
}

Step 4
//Calculation
int targetRectanleWidth = originalWidth - widthDiff;
int targetRectanleHeight = originalHeight - heightDiff;

Step 5
int centerPointX = newWidth/2;
int centerPointY = newHeight/2;

Step 6
int x1 = centerPointX - (targetRectanleWidth / 2); 
int y1 = centerPointY - (targetRectanleHeight / 2);
int x2 = centerPointX + (targetRectanleWidth / 2);
int y2 = centerPointY + (targetRectanleHeight / 2);

Step 7
x1 = (x1 < 0 ? 0 : x1);
y1 = (y1 < 0 ? 0 : y1);

2
投票

很抱歉没有在这里提供推导,但我几天前在Mathematica解决了这个问题,并提出了以下程序,非Mathematica人员应该能够阅读。如有疑问,请咨询http://reference.wolfram.com/mathematica/guide/Mathematica.html

下面的过程返回一个矩形的宽度和高度,该矩形的最大区域适合另一个宽度为w的矩形和高度为h的已旋转的矩形。

CropRotatedDimensionsForMaxArea[{w_, h_}, alpha_] := 
With[
  {phi = Abs@Mod[alpha, Pi, -Pi/2]},
  Which[
   w == h, {w,h} Csc[phi + Pi/4]/Sqrt[2],
   w > h, 
     If[ Cos[2 phi]^2 < 1 - (h/w)^2, 
       h/2 {Csc[phi], Sec[phi]}, 
       Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}],
   w < h, 
     If[ Cos[2 phi]^2 < 1 - (w/h)^2, 
       w/2 {Sec[phi], Csc[phi]}, 
       Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}]
  ]
]

2
投票

Coproc以简单有效的方式在另一个线程(https://stackoverflow.com/a/16778797)上解决了这个问题。此外,他在那里给出了非常好的解释和python代码。

下面是我的Matlab实现他的解决方案:

function [ CI, T ] = rotateAndCrop( I, ang )
%ROTATEANDCROP Rotate an image 'I' by 'ang' degrees, and crop its biggest
% inner rectangle.

[h,w,~] = size(I);
ang = deg2rad(ang);

% Affine rotation
R = [cos(ang) -sin(ang) 0; sin(ang) cos(ang) 0; 0 0 1];
T = affine2d(R);
B = imwarp(I,T);

% Largest rectangle
% solution from https://stackoverflow.com/a/16778797

wb = w >= h;
sl = w*wb + h*~wb;
ss = h*wb + w*~wb;

cosa = abs(cos(ang));
sina = abs(sin(ang));

if ss <= 2*sina*cosa*sl
    x = .5*min([w h]);
    wh = wb*[x/sina x/cosa] + ~wb*[x/cosa x/sina];
else
    cos2a = (cosa^2) - (sina^2);
    wh = [(w*cosa - h*sina)/cos2a (h*cosa - w*sina)/cos2a]; 
end

hw = flip(wh);

% Top-left corner
tl = round(max(size(B)/2 - hw/2,1));

% Bottom-right corner
br = tl + round(hw);

% Cropped image
CI = B(tl(1):br(1),tl(2):br(2),:);
© www.soinside.com 2019 - 2024. All rights reserved.