如何将一个二维数组以小于90度的最佳近似方式进行旋转?

5

假设我有一个以0°旋转存储的数组:

0 0 1 0 0
0 0 1 0 0 
1 1 1 0 0 
0 0 0 0 0
0 0 0 0 0 

如果我传递30°作为参数,我希望您以较好的近似值返回结果,大概是这样的:

0 0 0 1 0
1 1 0 1 0
0 0 1 0 0 
0 0 0 0 0 
0 0 0 0 0 

45°将会是

1 0 0 0 1
0 1 0 1 0
0 0 1 0 0 
0 0 0 0 0 
0 0 0 0 0

我知道有一些关于90度旋转的解决方案(参考链接),但我觉得这并不能帮助我。因为我没有示例代码,所以我甚至不知道该从哪里开始查找。如果有任何关键词可以让我搜索到某些公式,以便我可以进行调整,那就太好了。Spectre在C#中的代码解决方案:
    class Rotation
{

    public Rotation() {
        A = new int[xs,ys]{
{0,0,0,9,0,0,0},
{0,0,0,9,0,0,0},
{0,0,0,9,0,0,0},
{9,9,9,9,0,0,0},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0},
};
        B = new int[xs, ys];

        deg = (float)(Math.PI / 180.0);
    }

    public const int xs = 7; // matrix size
    public const int ys = 7;
    const int x0 = 3; // rotation center cell
    const int y0 = 3;
    readonly float deg; 
    public int[,] A;
    public int[,] B;

    //---------------------------------------------------------------------------

    public void rotcv(float ang) {
        rotcw(Rotation.x0, Rotation.y0, ang);
    }
    private void rotcw(int x0, int y0, float ang) // rotate A -> B by angle ang around (x0,y0) CW if ang>0
    {
        int x, y, ix0, iy0, ix1, iy1, q;
        double xx, yy, fx, fy, c, s;
        // circle kernel
        c = Math.Cos(-ang); s = Math.Sin(-ang);
        // rotate
        for (y = 0; y < ys; y++)
            for (x = 0; x < xs; x++)
            {
                // offset so (0,0) is center of rotation
                xx = x - x0;
                yy = y - y0;
                // rotate (fx,fy) by ang
                fx = ((xx * c) - (yy * s));
                fy = ((xx * s) + (yy * c));
                // offset back and convert to ints and weights
                fx += x0; ix0 = (int)Math.Floor(fx); fx -= ix0; ix1 = ix0 + 1; if (ix1 >= xs) ix1 = ix0;
                fy += y0; iy0 = (int)Math.Floor (fy); fy -= iy0; iy1 = iy0 + 1; if (iy1 >= ys) iy1 = iy0;
                // bilinear interpolation A[fx][fy] -> B[x][y]
                if ((ix0 >= 0) && (ix0 < xs) && (iy0 >= 0) && (iy0 < ys))
                {
                    xx = (A[ix0,iy0]) + ((A[ix1,iy0] - A[ix0,iy0]) * fx);
                    yy = (A[ix0,iy0]) + ((A[ix1,iy0] - A[ix0,iy0]) * fx);
                    xx = xx + ((yy - xx) * fy); q =(int) xx;
                }
                else q = 0;
                B[x,y] = q;
            }
    }
}

测试:

 static void Main(string[] args)
    {
        Rotation rot = new Rotation();

        for (int x = 0; x < Rotation.xs; x++) {
            for (int y = 0; y < Rotation.xs; y++) {
                Console.Write(rot.A[x,y] + " ");
            }
            Console.WriteLine();
        }
        Console.WriteLine();
        float rotAngle = 0;
        while (true)
        {
            rotAngle += (float)(Math.PI/180f)*90;
            rot.rotcv(rotAngle);
            for (int x = 0; x < Rotation.xs; x++)
            {
                for (int y = 0; y < Rotation.xs; y++)
                {
                    Console.Write(rot.B[x, y] + " ");
                }
                Console.WriteLine();
            }
            Console.WriteLine();
            Console.ReadLine();
        }

    }

1
如果您真的旋转了数组,那可能会导致永久性价值的损失。 - AxelH
1
您可以使用圆形核进行90度旋转,使用正方形核进行45度旋转,请参见在2D 3 x 3网格中旋转对角线 - 需要旋转矩阵吗?。使用圆形核的非90度倍数角度旋转不是1:1映射,这意味着一些单元格将被映射到多个单元格,从而在矩阵中引起大问题。此外,由于旋转后的矩阵具有不同的边界框大小,因此您会丢失越界信息...最好的方法是双线性过滤旋转,但这将更改单元格值,在某些情况下可能不需要。 - Spektre
1
@Juvanis,这与Rob所说的接近。我正在创建一个3D世界中每个物体的2D俯视快照。该物体的2D快照用于路径规划。对于每个物体实例,将应用“近似”旋转到2D步行网格的2D快照,并将其标记为不可行走。 - user3488765
1
@Spektre 失去信息并不是什么大问题,因为每次新的调用都会再次使用0°原始值。您提供的链接似乎就是我要找的。:) - user3488765
1
@user3488765 和单元格的值是否可以进行插值?输出将不仅为 {0,1},而且对于非精确匹配位置也会有中间值。 - Spektre
显示剩余11条评论
3个回答

1
假设你的矩阵是一个二维世界,其中原点(0, 0)是中心像素。例如,左上角点的坐标为(-2,2)。如果您将此向量乘以二维旋转矩阵,则会得到旋转后的新坐标。在这里,您可以根据需要进行近似(四舍五入,向下取整,向上取整等)。
以下是旋转矩阵的定义:
 | cos a      -sin a|
 | sin a      cos a |

其中a是您的旋转角度。以下是如何进行乘法:

https://wikimedia.org/api/rest_v1/media/math/render/svg/50622f9a4a7ba2961f5df5f7e0882983cf2f1d2f

在这个图像中,(x, y) 是原始坐标(例如,左上角像素的 (-2, 2)),而 (x', y') 则是新的(近似)坐标。

我认为这不会起作用。如果您使用此方法将正方形旋转45度,则其中相当大的一部分将超出目标区域。您还需要将角落向内压缩并将边缘向外拉伸。 - Dawood ibn Kareem
@DavidWallace 是的,也许稍微压缩和拉伸一下就可以解决问题了。这个问题也可以使用一个简单的 if 条件语句来解决,例如 if (x > 2) x=2; - AhmadWabbi
@AhmadWabbi 我尝试实现了你的建议,但是哪里出错了呢? - user3488765
@user3488765,请将rotatedIdx[0] = (int)Math.Floor((originalIndexes[0] * (Math.Cos(angle) - Math.Sin(angle))));替换为rotatedIdx[0] = (int)Math.Floor(originalIndexes[0] * Math.Cos(angle) - originalIndexes[1] * Math.Sin(angle)); - AhmadWabbi
@user3488765,另外将(int)Math.Floor((originalIndexes[1] * (Math.Sin(angle) + Math.Cos(angle))));更改为rotatedIdx[1] = (int)Math.Floor(originalIndexes[0] * Math.Sin(angle) - originalIndexes[1] * Math.Cos(angle)); - AhmadWabbi
@user3488765 在从rotatedIndexes返回之前,请确保坐标在-2和2之间。例如,对于rotatedIdx [0],添加if(rotatedIdx [0]> 2)rotatedIds [0] = 2if(rotatedIdx [0] < -2)rotatedIds [0] = -2 - AhmadWabbi

1

已经有很多次在图像处理中实现了,比如Photoshop,GIMP和Imagemagick。

您可以使用库(例如Python和numpy)将矩阵转换为位图图像,使用ImageMagick将其旋转x度,并从旋转后的图像中读取结果返回到矩阵中。您必须决定如何处理角落,如果要将n * n网格旋转到n * n网格,则它们将被截断。

这可以用几行代码完成,您不必重新发明轮子,如果旋转点不完全落在单元格中,则会在旋转点周围得到灰度值。

迷你图片: enter image description here enter image description here

使用Python读取结果:

import matplotlib.image as img
image = img.imread('matrix.png')

print image[:,:,0] # Only reads grayscale information.



# Original
[[ 0.  0.  1.  0.  0.]
 [ 0.  0.  1.  0.  0.]
 [ 1.  1.  1.  0.  0.]
 [ 0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]]

# Rotated by 30°
[[ 0.07450981  0.          0.08235294  0.8509804   0.        ]
 [ 0.68627453  0.82745099  0.53725493  0.78039217  0.        ]
 [ 0.          0.50980395  0.97647059  0.22745098  0.        ]
 [ 0.          0.          0.05882353  0.          0.        ]
 [ 0.          0.          0.          0.          0.        ]]

如果您需要Java解决方案,AffineTransform 可以帮助您。 这里是一个示例


这正是我所需要的!但我需要Java或C#代码。 - user3488765
https://docs.oracle.com/javase/7/docs/api/java/awt/geom/AffineTransform.html 可以帮助你学习Java。这里有一个例子:https://dev59.com/vWoy5IYBdhLWcg3wUcRL - Eric Duminil

1

好的,这是承诺的内容。首先是C++代码:

//---------------------------------------------------------------------------
#include <math.h>
//---------------------------------------------------------------------------
const int xs=7; // matrix size
const int ys=7;
const int x0=3; // rotation center cell
const int y0=3;
const float deg=M_PI/180.0;
int A[xs][ys]=
    {
    {0,0,0,9,0,0,0},
    {0,0,0,9,0,0,0},
    {0,0,0,9,0,0,0},
    {9,9,9,9,0,0,0},
    {0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0},
    };
int B[xs][ys];
//---------------------------------------------------------------------------
void rotcw(int x0,int y0,float ang) // rotate A -> B by angle ang around (x0,y0) CW if ang>0
    {
    int x,y,ix0,iy0,ix1,iy1,q;
    float xx,yy,fx,fy,c,s;
    // circle kernel
    c=cos(-ang); s=sin(-ang);
    // rotate
    for (y=0;y<ys;y++)
     for (x=0;x<xs;x++)
        {
        // offset so (0,0) is center of rotation
        xx=x-x0;
        yy=y-y0;
        // rotate (fx,fy) by ang
        fx=float((xx*c)-(yy*s));
        fy=float((xx*s)+(yy*c));
        // offset back and convert to ints and weights
        fx+=x0; ix0=floor(fx); fx-=ix0; ix1=ix0+1; if (ix1>=xs) ix1=ix0;
        fy+=y0; iy0=floor(fy); fy-=iy0; iy1=iy0+1; if (iy1>=ys) iy1=iy0;
        // bilinear interpolation A[ix0+fx][iy0+fy] -> B[x][y]
        if ((ix0>=0)&&(ix0<xs)&&(iy0>=0)&&(iy0<ys))
            {
            xx=float(A[ix0][iy0])+(float(A[ix1][iy0]-A[ix0][iy0])*fx);
            yy=float(A[ix0][iy0])+(float(A[ix1][iy0]-A[ix0][iy0])*fx);
            xx=xx+((yy-xx)*fy); q=xx;
            } else q=0;
        B[x][y]=q;
        }
    }
//---------------------------------------------------------------------------

这里是7x7的预览,每次旋转15度:

preview

我觉得中间需要微调一下,可以把单元格的中心向内移动一半或者其他方式(因为中心过于模糊了)

矩阵A是源,B是目标...

你还可以添加阈值处理...例如:

if (q>=5) q=9; else q=0;

1
非常感谢,我在C#中尝试了一下,它起作用了。转换在我的OP中。 - user3488765
@user3488765,你可以使用 deg 常量... rotAngle += 90.0*deg; 这就是它的作用... - Spektre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接