从旋转的矩形计算边界框坐标


75

我有一个矩形左上角的坐标,以及它的宽度,高度和从0到180和-0到-180的旋转。

我试图获取围绕矩形的实际框的边界坐标。

计算边界框坐标的简单方法是什么

  • 最小y,最大y,最小x,最大x?

A点并不总是在最小范围内,它可以在任何地方。

如果需要,我可以在as3中使用矩阵转换工具包。


1
图片(图像)不可见..(图像显示:单击并发现Imageshack)!
布拉汉姆,2015年

对了,我不好,我想知道是否可以从Google存档中找回它。
coulix

1
什么是A点?
Xhark '16

Answers:


83
  • 变换所有四个角的坐标
  • 找到所有四个x中的最小值 min_x
  • 查找所有四个x中最大的x,并将其称为 max_x
  • 与y同上
  • 您的边界框是 (min_x,min_y), (min_x,max_y), (max_x,max_y), (max_x,min_y)

AFAIK,没有任何皇家道路可以使您更快到达那里。

如果您想知道如何变换坐标,请尝试:

x2 = x0+(x-x0)*cos(theta)+(y-y0)*sin(theta)
y2 = y0-(x-x0)*sin(theta)+(y-y0)*cos(theta)

其中(x0,y0)是旋转中心。您可能需要根据自己的触发函数(它们期望角度或弧度)来修改此坐标系统的感觉/符号,以及如何指定角度等。


确实,在各个角落使用矩阵并进行比较可以完成工作,谢谢!
coulix

3
实际上,由于对称性,您只需要变换2个角,并且如果您稍加考虑,则旋转仅需要1个角。
ysap 2010年

1
@ysap仅在围绕矩形中心旋转时有效。
顿(Sidon)2012年

@sidon-是的。但是,大多数绘图程序都是这样做的。
ysap 2012年

5
@MarkusQ您提到的x2的等式应为x0 +(x-x0)* cos(theta)-(y-y0)*sinθ(而不是x0 +(x-x0)* cos(theta)+(y-y0) )* sin(theta),对吗?
Mihir

27

我知道您要的是ActionScript,但万一有人来找iOS或OS-X答案,就是这样:

+ (CGRect) boundingRectAfterRotatingRect: (CGRect) rect toAngle: (float) radians
{
    CGAffineTransform xfrm = CGAffineTransformMakeRotation(radians);
    CGRect result = CGRectApplyAffineTransform (rect, xfrm);

    return result;
}

如果您的操作系统愿意为您完成所有艰苦的工作,那就去吧!:)

迅速:

func boundingRectAfterRotatingRect(rect: CGRect, toAngle radians: CGFloat) -> CGRect {
    let xfrm = CGAffineTransformMakeRotation(radians)
    return CGRectApplyAffineTransform (rect, xfrm)
}

2
要补充一点,角度必须以弧度为单位,而不是以度为单位。可以为您节省时间。;)
Thomas Johannesmeyer 2014年

13

MarkusQ概述的方法可以很好地工作,但是请记住,如果您已经有了A点,则不需要变换其他三个角。

另一种更有效的方法是测试旋转角度在哪个象限中,然后直接直接计算答案。这效率更高,因为最坏的情况是两个if语句(检查角度),而另一种方法的最坏情况是十二个(检查其他三个角以查看它们是否大于当前值时,每个分量为6)我认为最大或小于当前最小值)。

基本算法仅使用毕达哥拉斯定理的一系列应用,如下所示。我用theta表示旋转角度,并以度表示校验,因为它是伪代码。

ct = cos( theta );
st = sin( theta );

hct = h * ct;
wct = w * ct;
hst = h * st;
wst = w * st;

if ( theta > 0 )
{
    if ( theta < 90 degrees )
    {
        // 0 < theta < 90
        y_min = A_y;
        y_max = A_y + hct + wst;
        x_min = A_x - hst;
        x_max = A_x + wct;
    }
    else
    {
        // 90 <= theta <= 180
        y_min = A_y + hct;
        y_max = A_y + wst;
        x_min = A_x - hst + wct;
        x_max = A_x;
    }
}
else
{
    if ( theta > -90 )
    {
        // -90 < theta <= 0
        y_min = A_y + wst;
        y_max = A_y + hct;
        x_min = A_x;
        x_max = A_x + wct - hst;
    }
    else
    {
        // -180 <= theta <= -90
        y_min = A_y + wst + hct;
        y_max = A_y;
        x_min = A_x + wct;
        x_max = A_x - hst;
    }
}

这种方法假设您拥有您所说的内容,即点A和theta的值在[-180,180]范围内。我还假设theta沿顺时针方向增加,因为这就是您的图表中旋转了30度的矩形似乎表明您正在使用的位置,我不确定右边的部分试图表示什么。如果这是错误的方法,则只需交换对称子句以及st项的符号。


我知道这已经很老了,但是对Google来说是第一个热门产品,因此在这里我要指出:在此之前,只需将标度应用到h和w上就可以了。这个问题目前是我最喜欢的答案,因为它很好地将计算分解成小块。2个分支,并在ios中带有一些NEON魔术,可以进行4到6个操作或更少,具体取决于您的技巧。
Diniden

人们会继续错误地编辑第二个if语句中的“度”。这是伪代码,因此它将永远不会编译。明确说明“度”的目的是因为theta显然不是度。在执行适当的工作代码以正确设置角度单位时,它会提醒您。
Troubadour

好了,好了那么要避免这种混乱,这将有助于如果你改变了所有的if语句,使他们if (theta > 0 degrees)if (theta > -90 degrees)使其一致。不一致提示人们进行编辑。
wcmatthysen

7
    fitRect: function( rw,rh,radians ){
            var x1 = -rw/2,
                x2 = rw/2,
                x3 = rw/2,
                x4 = -rw/2,
                y1 = rh/2,
                y2 = rh/2,
                y3 = -rh/2,
                y4 = -rh/2;

            var x11 = x1 * Math.cos(radians) + y1 * Math.sin(radians),
                y11 = -x1 * Math.sin(radians) + y1 * Math.cos(radians),
                x21 = x2 * Math.cos(radians) + y2 * Math.sin(radians),
                y21 = -x2 * Math.sin(radians) + y2 * Math.cos(radians), 
                x31 = x3 * Math.cos(radians) + y3 * Math.sin(radians),
                y31 = -x3 * Math.sin(radians) + y3 * Math.cos(radians),
                x41 = x4 * Math.cos(radians) + y4 * Math.sin(radians),
                y41 = -x4 * Math.sin(radians) + y4 * Math.cos(radians);

            var x_min = Math.min(x11,x21,x31,x41),
                x_max = Math.max(x11,x21,x31,x41);

            var y_min = Math.min(y11,y21,y31,y41);
                y_max = Math.max(y11,y21,y31,y41);

            return [x_max-x_min,y_max-y_min];
        }

3

如果您使用的是GDI +,则可以创建一个新的GrpaphicsPath->向其中添加任何点或形状->应用旋转变换->使用GraphicsPath.GetBounds(),它将返回一个矩形,该矩形限制了旋转的形状。

(编辑)VB.Net示例

Public Shared Sub RotateImage(ByRef img As Bitmap, degrees As Integer)
' http://stackoverflow.com/questions/622140/calculate-bounding-box-coordinates-from-a-rotated-rectangle-picture-inside#680877
'
Using gp As New GraphicsPath
  gp.AddRectangle(New Rectangle(0, 0, img.Width, img.Height))

  Dim translateMatrix As New Matrix
  translateMatrix.RotateAt(degrees, New PointF(img.Width \ 2, img.Height \ 2))
  gp.Transform(translateMatrix)

  Dim gpb = gp.GetBounds

  Dim newwidth = CInt(gpb.Width)
  Dim newheight = CInt(gpb.Height)

  ' http://www.codeproject.com/Articles/58815/C-Image-PictureBox-Rotations
  '
  Dim rotatedBmp As New Bitmap(newwidth, newheight)

  rotatedBmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)

  Using g As Graphics = Graphics.FromImage(rotatedBmp)
    g.Clear(Color.White)
    translateMatrix = New Matrix
    translateMatrix.Translate(newwidth \ 2, newheight \ 2)
    translateMatrix.Rotate(degrees)
    translateMatrix.Translate(-img.Width \ 2, -img.Height \ 2)
    g.Transform = translateMatrix
    g.DrawImage(img, New PointF(0, 0))
  End Using
  img.Dispose()
  img = rotatedBmp
End Using

结束子


2

尽管Code Guru声明了GetBounds()方法,但我注意到该问题被标记为as3,flex,所以这里有一个as3片段,说明了这一想法。

var box:Shape = new Shape();
box.graphics.beginFill(0,.5);
box.graphics.drawRect(0,0,100,50);
box.graphics.endFill();
box.rotation = 20;
box.x = box.y = 100;
addChild(box);

var bounds:Rectangle = box.getBounds(this);

var boundingBox:Shape = new Shape();
boundingBox.graphics.lineStyle(1);
boundingBox.graphics.drawRect(bounds.x,bounds.y,bounds.width,bounds.height);
addChild(boundingBox);

我注意到,有两种方法似乎做同样的事情:getBounds()和getRect()


getRect执行与getBounds相同的操作,但是减去对象上的笔触:help.adobe.com/zh_CN/FlashPlatform/reference/actionscript/3/…–
Danny Parker

2
/**
     * Applies the given transformation matrix to the rectangle and returns
     * a new bounding box to the transformed rectangle.
     */
    public static function getBoundsAfterTransformation(bounds:Rectangle, m:Matrix):Rectangle {
        if (m == null) return bounds;

        var topLeft:Point = m.transformPoint(bounds.topLeft);
        var topRight:Point = m.transformPoint(new Point(bounds.right, bounds.top));
        var bottomRight:Point = m.transformPoint(bounds.bottomRight);
        var bottomLeft:Point = m.transformPoint(new Point(bounds.left, bounds.bottom));

        var left:Number = Math.min(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var top:Number = Math.min(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        var right:Number = Math.max(topLeft.x, topRight.x, bottomRight.x, bottomLeft.x);
        var bottom:Number = Math.max(topLeft.y, topRight.y, bottomRight.y, bottomLeft.y);
        return new Rectangle(left, top, right - left, bottom - top);
    }

1
在问题中,用户说他旋转了度数。您的解决方案需要一个转换矩阵。您如何从度旋转到旋转变换矩阵?
pgreen2

1

将旋转矩阵应用于拐角点。然后分别使用获得的x,y坐标的最小值/最大值来定义新的边界框。


1

这是我的开源库中的三个函数。这些功能已使用Java进行了全面测试,但公式可以轻松转换为任何语言。

签名是:

公共静态浮点数getAngleFromPoint(最终点centerPoint,最终点touchPoint)

公共静态float getTwoFingerDistance(float firstTouchX,float firstTouchY,float secondTouchX,float secondTouchY)

Point getPointFromAngle(最终双角,最终双半径)

该解决方案假定像素密度均匀分布。旋转对象之前,请执行以下操作:

  1. 使用getAngleFromPoint计算从中心到右上角的角度(可以说返回20度),这意味着左上角为-20度或340度。

  2. 使用getTwoFingerDistance返回中心点和右上角之间的对角线距离(此距离在所有角点上都应该是相同的,此距离将在下一次计算中使用)。

  3. 现在假设我们将对象顺时针旋转30度。现在我们知道右上角必须为50度,左上角为10度。

  4. 现在,您应该可以使用左上角和右上角的getPointFromAngle函数。使用从第2步返回的半径。X位置乘以右上角的2将为您提供新的宽度,Y位置乘以距左上角的2将为您提供新的高度。

以上这4个步骤应根据您将对象旋转了多远而设置为其他条件,否则您可以将高度作为宽度返回,将宽度作为高度返回。

切记角度函数以0-1而不是0-360的因子表示(在适当的情况下只需乘以360除以)即可:

//从表示为0 -1的因子的两个点获取角度(0为0 / 360,0.25为90度等)

public float getAngleFromPoint(final Point centerPoint, final Point touchPoint) {

    float returnVal = 0;

    //+0 - 0.5
    if(touchPoint.x > centerPoint.x) {

        returnVal = (float) (Math.atan2((touchPoint.x - centerPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI);

    }
    //+0.5
    else if(touchPoint.x < centerPoint.x) {

        returnVal = (float) (1 - (Math.atan2((centerPoint.x - touchPoint.x), (centerPoint.y - touchPoint.y)) * 0.5 / Math.PI));

    }//End if(touchPoint.x > centerPoint.x)

    return returnVal;

}

//测量两点之间的对角线距离

public float getTwoFingerDistance(final float firstTouchX, final float firstTouchY, final float secondTouchX, final float secondTouchY) {

    float pinchDistanceX = 0;
    float pinchDistanceY = 0;

    if(firstTouchX > secondTouchX) {

        pinchDistanceX = Math.abs(secondTouchX - firstTouchX);

    }
    else if(firstTouchX < secondTouchX) {

        pinchDistanceX = Math.abs(firstTouchX - secondTouchX);

    }//End if(firstTouchX > secondTouchX)

    if(firstTouchY > secondTouchY) {

        pinchDistanceY = Math.abs(secondTouchY - firstTouchY);

    }
    else if(firstTouchY < secondTouchY) {

        pinchDistanceY = Math.abs(firstTouchY - secondTouchY);

    }//End if(firstTouchY > secondTouchY)

    if(pinchDistanceX == 0 && pinchDistanceY == 0) {

        return 0;

    }
    else {

        pinchDistanceX = (pinchDistanceX * pinchDistanceX);
        pinchDistanceY = (pinchDistanceY * pinchDistanceY);
        return (float) Math.abs(Math.sqrt(pinchDistanceX + pinchDistanceY));

    }//End if(pinchDistanceX == 0 && pinchDistanceY == 0)

}

//从给定半径的角度获取XY坐标(角度以0-1 0表示0/360度,而0.75表示270等)

public Point getPointFromAngle(final double angle, final double radius) {

    final Point coords = new Point();
    coords.x = (int) (radius * Math.sin((angle) * 2 * Math.PI));
    coords.y = (int) -(radius * Math.cos((angle) * 2 * Math.PI));

    return coords;

}

这些代码段来自我的开源库:https : //bitbucket.org/warwick/hgdialrepohttps://bitbucket.org/warwick/hacergestov2。一个是Android的手势库,另一个是Android的拨号控件。在以下位置也有拨号控件的OpenGLES 2.0实现:https//bitbucket.org/warwick/hggldial


0

我不确定我是否理解,但是复合转换矩阵会为您提供所有相关点的新坐标。如果您认为矩形在转换后可能会溢出可想象的区域,请应用剪切路径。

如果您不熟悉矩阵的确切定义,请在这里查看


0

我先使用Region来旋转矩形,然后使用该旋转的区域来检测该矩形

        r = new Rectangle(new Point(100, 200), new Size(200, 200));         
        Color BorderColor = Color.WhiteSmoke;
        Color FillColor = Color.FromArgb(66, 85, 67);
        int angle = 13;
        Point pt = new Point(r.X, r.Y);
        PointF rectPt = new PointF(r.Left + (r.Width / 2),
                               r.Top + (r.Height / 2));
       //declare myRegion globally 
        myRegion = new Region(r);

        // Create a transform matrix and set it to have a 13 degree

        // rotation.
        Matrix transformMatrix = new Matrix();
        transformMatrix.RotateAt(angle, pt);

        // Apply the transform to the region.
        myRegion.Transform(transformMatrix);
        g.FillRegion(Brushes.Green, myRegion);
        g.ResetTransform();

现在要检测该矩形

        private void panel_MouseMove(object sender, MouseEventArgs e)
    {


        Point point = e.Location;
        if (myRegion.IsVisible(point, _graphics))
        {
            // The point is in the region. Use an opaque brush.
            this.Cursor = Cursors.Hand;
        }
        else {
            this.Cursor = Cursors.Cross;
        }

    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.