【问题标题】:Calculate Bounding box coordinates from a rotated rectangle从旋转的矩形计算边界框坐标
【发布时间】:2010-10-11 22:48:42
【问题描述】:

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

我正在尝试获取矩形周围实际框的边界坐标。

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

  • 最小 y、最大 y、最小 x、最大 x?

A 点并不总是在最小界限上,它可以在任何地方。

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

【问题讨论】:

  • 图片(图片)不可见..(图片上写着:点击并发现Imageshack)!!!
  • 对,我的错,我想知道我是否可以从谷歌档案或其他地方取回它。
  • A 点是什么?

标签: actionscript-3 math geometry rotation bounding-box


【解决方案1】:
  • 转换所有四个角的坐标
  • 找出所有四个 x 中最小的一个作为min_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) 是您旋转的中心。您可能需要根据您的三角函数(它们是否期望度数或弧度)、坐标系的感觉/符号与您指定角度的方式等来修改这个。

【讨论】:

  • 确实,对所有角落使用矩阵并进行比较就可以了,谢谢!
  • 其实由于对称性,你只需要变换2个角,再稍微考虑一下,旋转的只是1个角。
  • @ysap 这仅在您围绕矩形中心旋转时有效。
  • @sidon - 这是真的。然而,大多数绘图程序都是这样做的。
  • @MarkusQ 你提到的 x2 方程应该是 x0+(x-x0)*cos(theta)- (y-y0)*sin(theta) 而不是 x0+(x-x0)*cos (theta)+(y-y0)*sin(theta),我说的对吗?
【解决方案2】:

我知道您要求的是 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)
}

【讨论】:

  • 想补充一点,角度必须是弧度,而不是度数。可以为您节省一些时间。 ;)
【解决方案3】:

MarkusQ 概述的方法非常有效,但请记住,如果您已经有 A 点,则无需转换其他三个角。

另一种更有效的方法是测试您的旋转角度在哪个象限,然后直接计算答案。这更有效,因为您只有两个 if 语句(检查角度)的最坏情况,而另一种方法的最坏情况是 12 个(在检查其他三个角以查看它们是否大于当前时,每个组件 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 和位于 [-180, 180] 范围内的 theta 值。我还假设 theta 沿顺时针方向增加,因为这就是图表中旋转 30 度的矩形似乎表明您正在使用的东西,我不确定右边的部分试图表示什么。如果这是错误的方法,那么只需交换对称子句以及 st 项的符号。

【讨论】:

  • 我知道这已经很老了,但它是谷歌的第一次点击,所以在这里我会注意:只需在此之前将比例应用于 h 和 w,它应该可以正常工作。这个答案目前是我最喜欢这个问题的答案,因为它很好地将计算分解成小块。 2 个分支并在 ios 中具有一些 NEON 魔法,4-6 次操作或更少,具体取决于您的技巧。
  • 人们一直在错误地编辑第二个 if 语句中的“度数”。这是伪代码,所以它永远不会编译。明确说明“度”的目的是因为theta 显然不是度数。在实施正确的工作代码以使角度单位正确时,它会作为提醒。
  • 好的,那么为了避免这种混淆,如果您将所有 if 语句更改为 if (theta &gt; 0 degrees)if (theta &gt; -90 degrees) 以使其保持一致,将会有所帮助。不一致会提示人们对其进行编辑。
【解决方案4】:
    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];
        }

【讨论】:

    【解决方案5】:

    如果您使用 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
    

    结束子

    【讨论】:

      【解决方案6】:

      虽然 Code Guru 说明了 GetBounds() 方法,但我注意到这个问题被标记为 as3, flex,所以这里有一个 as3 sn-p 来说明这个想法。

      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()

      【讨论】:

      【解决方案7】:
      /**
           * 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);
          }
      

      【讨论】:

      • 在问题中,用户说他有旋转度数。您的解决方案需要一个转换矩阵。如何从度数旋转到旋转变换矩阵?
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

        以下是我的开源库中的三个函数。这些函数在 Java 中经过全面测试,但公式可以轻松翻译成任何语言。

        签名是:

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

        public static 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 返回的半径。 从右上角乘以 2 的 X 位置应该得到新的宽度,从左上角乘以 2 的 Y 位置应该得到新的高度。

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

        这些代码 sn-ps 来自我的开源库:https://bitbucket.org/warwick/hgdialrepohttps://bitbucket.org/warwick/hacergestov2。 一个是安卓的手势库,另一个是安卓的拨号控件。还有一个拨盘控制的OpenGLES 2.0实现在:https://bitbucket.org/warwick/hggldial

        【讨论】:

          【解决方案10】:

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

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

          【讨论】:

            【解决方案11】:

            我使用 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;
                    }
            
                }
            

            【讨论】:

              猜你喜欢
              • 2019-01-05
              • 2012-04-15
              • 2014-05-25
              • 1970-01-01
              • 2021-11-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-11-07
              相关资源
              最近更新 更多