web-dev-qa-db-fra.com

Calculer le plus grand rectangle dans un rectangle rotatif

J'essaie de trouver le meilleur moyen de calculer la plus grande rectangle (dans la zone) qui peut être contenue à l'intérieur d'un rectangle rotatif.

Certaines images devraient aider (j'espère) à visualiser ce que je veux dire:

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

La largeur et la hauteur du rectangle d'entrée sont données et est donc l'angle de le faire pivoter. Le rectangle de sortie n'est ni pivoté ni asymétrique.

Je descends la route à long terme que je ne suis même pas sûre si elle gérera les cas de coin (pas de jeu de mots). Je suis certain qu'il y a une solution élégante à cela. Des conseils?

ÉDITER : Les points de rectangle de sortie ne doivent pas nécessairement toucher les bords des rectangles d'entrée. (Merci à MR)

54
zaf

Je viens de venir ici à la recherche de la même réponse. Après avoir frissonné la pensée de tant de mathématiques impliquées, je pensais que je serais recourir à une supposition semi-éduquée. Doodler un peu je suis arrivé à la conclusion (intuitive et probablement pas entièrement exacte) que le plus grand rectangle est proportionnel au rectangle résultant extérieur et ses deux coins opposés se situent à l'intersection des diagonales du rectangle extérieur avec le côté le plus long de la rectangle rotatif. Pour les carrés, aucune des diagonales et des côtés ferait ... Je suppose que je suis assez heureux avec cela et je vais maintenant commencer à brosser les toiles d'araignées de mes compétences rouillées de Trig (pathétique, je sais).

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

Mise à jour mineure ... a réussi à faire des calculs de Trig. Ceci est pour le cas lorsque la hauteur de l'image est supérieure à la largeur.

Some trig scribbles

Mettre à jour. Obtenu le tout fonctionne. Voici un code JS. Il est connecté à un programme plus vaste et la plupart des variables sont en dehors du cadre des fonctions et sont modifiées directement de l'intérieur des fonctions. Je sais que ce n'est pas bon, mais j'utilise cela dans une situation isolée, où il n'y aura pas de confusion avec d'autres scripts: expurgé


J'ai pris la liberté de nettoyer le code et j'ai extraire une fonction:

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
    };
}

J'ai rencontré des problèmes avec le calcul gamma-- Calcul et la modifier pour prendre en compte dans quelle direction la boîte d'origine est la plus longue.

- Magnus Hoff

23
Andri

Essayer de ne pas casser la tradition mettant la solution du problème comme une image :)

enter image description here


EDIT: Les troisième équations sont fausses. Le bon est:

3.w * cos (α) * [~ # ~ ~] x [~ # ~ ~] + w * sin (α) * [~ # ~] Y [~ # ~ ~] - W * W * SIN (α) * COS (α) - W * H = 0

Pour résoudre le système d'équations linéaires, vous pouvez utiliser Rule Cramer ou méthode Gauss .

13
Mihran Hovsepyan

Premièrement, nous nous occupons du cas trivial où l'angle est zéro ou un multiple de PI/2. Ensuite, le plus grand rectangle est le même que le rectangle d'origine.

En général, le rectangle interne aura 3 points sur les limites du rectangle extérieur. Si ce n'est pas le cas, il peut être déplacé de manière à ce que un sommet soit sur le fond et qu'un sommet sera à gauche. Vous pouvez ensuite agrandir le rectangle intérieur jusqu'à ce que l'un des deux sommets restants frappe une frontière.

Nous appelons les côtés du rectangle extérieur R1 et R2. Sans perte de généralité, nous pouvons supposer que R1 <= R2. Si nous appelons les côtés du rectangle intérieur H et W, alors nous avons que

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

Comme nous avons au moins 3 points sur les limites, au moins une de ces inégalités doit en réalité être une égalité. Utilisons le premier. Il est facile de voir que:

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

et donc la zone est

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

Nous pouvons prendre le dérivé WRT. H et besoin qu'il soit égal à 0:

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

Résolution de H et utilisant l'expression pour W ci-dessus, nous trouvons que:

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

Substituer cela dans la deuxième inégalité devient, après une certaine manipulation,

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

Le facteur sur le côté gauche est toujours au moins 1. Si l'inégalité est satisfaite, nous avons la solution. Si ce n'est pas satisfait, la solution est celle qui satisfait à la fois les inégalités comme des égalités. En d'autres termes: c'est le rectangle qui touche les quatre côtés du rectangle extérieur. Ceci est un système linéaire avec 2 inconnues qui sont facilement résolues:

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

En ce qui concerne les coordonnées d'origine, nous obtenons:

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

Modifier: Ma réponse Mathematica ci-dessous est fausse - je résolvais un problème légèrement différent de ce que je pense que vous demandez vraiment.

Pour résoudre le problème, vous demandez vraiment, j'utiliserais les algorithmes suivants :

sur le problème de rectangle vide maximum

L'utilisation de cet algorithme, note une quantité finie de points qui forment la limite du rectangle rotatif (peut-être une 100 environ, et assurez-vous d'inclure les coins) - celles-ci constitueraient le jeu S décrivé dans le papier.

.

.

.

.

.

Pour l'amour de la postérité, j'ai quitté mon poste d'origine ci-dessous:

Le rectangle intérieur avec la plus grande surface sera toujours le rectangle où le coin inférieur moyen du rectangle (le coin près de l'alpha sur votre diagramme) est égal à la moitié de la largeur du rectangle extérieur.

J'ai un peu trompé et utilisé Mathematica pour résoudre l'algèbre pour moi:

enter image description here

À partir de là, vous pouvez voir que la surface maximale du rectangle interne est égale à 1/4 largeur ^ 2 * Cosécant de l'angle fois le sécant de l'angle.

Maintenant, je dois déterminer quelle est la valeur X du coin inférieur de cette condition optimale. Utilisation de la fonction de résolution dans Mathematica sur la formule de ma région, je reçois ce qui suit:

enter image description here

Ce qui montre que la coordonnée X du coin inférieur est égale à la moitié de la largeur.

Maintenant, juste pour m'assurer que je vais tester notre réponse empiriquement. Avec les résultats ci-dessous, vous pouvez voir que la plus haute superficie de tous mes tests (définitivement pas exhaustive, mais vous obtenez le point) est lorsque la valeur X du coin inférieur = la moitié de la largeur du rectangle externe. enter image description here

5
Jason Moore

@Andri ne fonctionne pas correctement pour l'image où width > height Comme j'ai testé. Donc, j'ai réparé et optimisé son code de cette manière (avec seulement deux fonctions trigonométriques):

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
    }
}

MISE À JOUR

J'ai également décidé de poster la fonction suivante pour calculer le calcul de rectancement proportionnel:

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

Voici le moyen le plus simple de le faire ... :)

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 a résolu ce problème sur un autre fil ( https://stackoverflow.com/a/16778797 ) de manière simple et efficace. En outre, il a donné une très bonne explication et python code là-bas.

Vous trouverez ci-dessous ma mise en œuvre de ma clémab de sa solution:

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

désolé de ne pas avoir donné de dérivation ici, mais j'ai résolu ce problème à Mathematica il y a quelques jours et a proposé la procédure suivante, que les personnes non-Mathematica devraient pouvoir lire. En cas de doute, veuillez consulter http://reference.wolfram.com/mathematica/guide/mathematica.html

La procédure ci-dessous renvoie la largeur et la hauteur d'un rectangle avec une zone maximale qui s'adapte à un autre rectangle de largeur avec une hauteur h qui a été tournée par 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]}]
  ]
]
2
Markus van Almsick