web-dev-qa-db-ja.com

回転した長方形の中で最大の長方形を計算する

私は、回転した長方形の中に含めることができる最大の(領域内の)長方形を計算する最良の方法を見つけようとしています。

いくつかの写真は、私が何を意味するのかを視覚化するのに役立つはずです(願っています)。

input rectangle with given width and heightrotate erctangle by alpha degreesoutput inner rectangle

入力長方形の幅と高さが与えられ、それを回転させる角度も与えられます。出力長方形は回転または傾斜していません。

私はそれが隅のケースを処理するかどうかさえわからない長い曲がりくねったルートを下ります(意図的なしゃれはありません)。これにはエレガントな解決策があると確信しています。任意のヒント?

[〜#〜] edit [〜#〜]:出力長方形ポイントは必ずしも入力長方形エッジに接触する必要はありません。 (Eさんに感謝)

54
zaf

私は同じ答えを探してここに来ました。非常に多くの数学が関わっているという考えに身震いした後、私は半教育的な推測に頼るつもりだと思いました。少しだらだらと思った(直感的で、おそらく完全に正確ではない)最大の長方形は外側の長方形に比例し、その2つの対角の角は、外側の長方形の対角線と最も長い辺の交点にあるという結論に達しました。回転した長方形。四角形の場合、対角線と辺のいずれでも問題ありません...私はこれで十分満足していると思います。さびたトリガーのスキルからクモの巣を磨き始めます(哀れな、私は知っています)。

Probably not the best solution, but good enough for what I'm about to do

マイナーアップデート...いくつかのトリガー計算を行うことができました。これは、画像の高さが幅よりも大きい場合です。

Some trig scribbles

更新。すべてが機能しました。ここにいくつかの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- calculationでいくつかの問題が発生し、元のボックスが最も長い方向を考慮に入れるように変更しました。

-マグナスホフ

23
Andri

問題の解決策を絵として描き、伝統を破らないように努めます:)

enter image description here


編集:3番目の方程式が間違っています。正しいものは次のとおりです。

3.w * cos(α)*[〜#〜] x [〜#〜]+ w * sin(α)*[〜#〜] y [〜#〜]-w * w * sin(α)* cos(α)-w * h = 0

線形連立方程式を解くには、 Cramer規則 、または ガウス法 を使用できます。

13

最初に、角度がゼロまたはpi/2の倍数である些細なケースを扱います。次に、最大の長方形は元の長方形と同じです。

一般に、内側の四角形は、外側の四角形の境界に3つのポイントがあります。そうでない場合は、1つの頂点が下になり、1つの頂点が左になるように移動できます。次に、残りの2つの頂点の1つが境界にぶつかるまで、内側の長方形を拡大できます。

外側の長方形の辺をR1とR2と呼びます。一般性を失うことなく、R1 <= R2であると想定できます。内側の長方形の辺をHとWと呼ぶと、

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

境界には少なくとも3つのポイントがあるため、これらの不等式の少なくとも1つは実際には等式でなければなりません。最初のものを使いましょう。次のことが簡単にわかります。

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)

これを2番目の不等式に代入すると、なんらかの操作の後、

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

左側の係数は常に少なくとも1です。不等式が満たされている場合、解決策があります。それが満たされない場合、解決策は両方の不等式を等式として満たすものです。つまり、外側の長方形の4辺すべてに接するのは長方形です。これは、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
9
Jeffrey Sax

編集:以下のMathematicaの答えは間違っています-私があなたが本当に求めているものとは少し異なる問題を解決していました。

あなたが本当に求めている問題を解決するには、次のアルゴリズムを使用します:

最大の空の四角形の問題について

このアルゴリズムを使用して、回転した長方形の境界を形成する有限量のポイントを示します(おそらく100程度か、コーナーを含めるようにしてください)。これらは、論文で説明されているセットSになります。

後世のために、元の投稿を以下に残しました。

面積が最大の内側の四角形は常に、四角形の下中央の角(図のアルファの近くの角)が外側の四角形の幅の半分に等しい長方形になります。

私は一種のだまされ、Mathematicaを使って代数を解いてくれました:

enter image description here

これから、内側の四角形の最大面積は、角度の割線に角度の割線を掛けたものの1/4幅^ 2 *コセカントに等しいことがわかります。

ここで、この最適な条件の下隅のx値が何であるかを理解する必要があります。 Mathematicaのソルバ関数を面積式に使用すると、次のようになります:

enter image description here

これは、下隅のx座標が幅の半分に等しいことを示しています。

念のため、答えを経験的にテストします。以下の結果を見ると、実際に私のすべてのテストで最も高い領域(完全に網羅されているわけではありませんが、ポイントがわかります)は、下隅のx値=外側の長方形の幅の半分であることがわかります。 enter image description here

5
Jason Moore

テストしたとおり、@ Andriはwidth > heightの画像に対して正しく機能しません。したがって、私は彼のコードを修正して最適化しました(2つの三角関数のみを使用)。

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
    }
}
5
Ivan Kochurkin

enter image description here

これがこれを行う最も簡単な方法です... :)

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);
3
Arfan Mirza

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),:);
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