[英]Calculate largest rectangle in a rotated rectangle
我试图找到计算可以包含在旋转矩形内的最大(面积)矩形的最佳方法。
一些图片应该有助于(我希望)形象化我的意思:
给出了输入矩形的宽度和高度,以及旋转它的角度。 输出矩形未旋转或倾斜。
我将沿着冗长的路线走下去,我什至不确定它是否会处理极端情况(没有双关语)。 我确信有一个优雅的解决方案。 有小费吗?
编辑:输出矩形点不一定要接触输入矩形边缘。 (感谢E先生)
我只是来这里寻找相同的答案。 在想到涉及这么多数学时不寒而栗后,我想我会诉诸于半知半解的猜测。 稍微涂鸦一下,我得出了(直观且可能不完全准确)的结论,即最大的矩形与生成的外部矩形成正比,并且它的两个对角位于外部矩形的对角线与矩形的最长边的交点处。旋转的矩形。 对于正方形,任何对角线和边都可以...我想我对此很满意,现在将开始刷掉我生锈的三角技能上的蜘蛛网(可悲,我知道)。
次要更新...设法进行了一些触发计算。 这是当图像的高度大于宽度时的情况。
更新。 搞定了整个事情。 这是一些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
计算中遇到了一些问题,并对其进行了修改以考虑原始框在哪个方向最长。
——马格努斯·霍夫
首先,我们处理角度为零或 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
我们可以采用导数wrt。 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
@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
}
}
更新
我还决定发布以下用于比例矩形计算的函数:
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
}
}
编辑:我在下面的 Mathematica 答案是错误的 - 我正在解决一个与我认为您真正要问的问题略有不同的问题。
为了解决您真正要问的问题,我将使用以下算法:
使用此算法,表示形成旋转矩形边界的有限数量的点(可能是 100 左右,并确保包括角)——这些将是论文中描述的集合 S。
.
.
.
.
.
为了后人的缘故,我将我的原始帖子留在下面:
面积最大的内部矩形将始终是矩形的下中角(图表上靠近 alpha 的角)等于外部矩形宽度的一半的矩形。
我有点作弊并使用 Mathematica 为我解决代数:
由此可以看出,内部矩形的最大面积等于 1/4 width^2 * 角的余割乘以角的割线。
现在我需要弄清楚这个最佳条件下底角的 x 值是多少。 在我的面积公式上使用 mathematica 中的 Solve 函数,我得到以下结果:
这表明底角的 x 坐标等于宽度的一半。
现在只是为了确保,我将凭经验测试我们的答案。 通过下面的结果,您可以看到我所有测试的最高区域(绝对不是详尽的,但您明白这一点)是当底角的 x 值 = 外部矩形宽度的一半时。
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),:);
很抱歉在这里没有给出推导,但我几天前在 Mathematica 中解决了这个问题,并提出了以下程序,非 Mathematica 的人应该能够阅读。 如有疑问,请咨询http://reference.wolfram.com/mathematica/guide/Mathematica.html
下面的过程返回具有最大面积的矩形的宽度和高度,该矩形适合另一个宽度为 w 和高度为 h 的矩形,该矩形已被 alpha 旋转。
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]}]
]
]
这是执行此操作的最简单方法... :)
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);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.