顺时针旋转2d矩阵会产生奇怪的结果

时间:2013-09-08 15:33:51

标签: c++ math opencv matrix matrix-multiplication

使用旋转矩阵:

R(-t) = [cos(t)  sin(t)]
        [-sin(t) cos(t)]

其中t =以弧度表示的角度

功能表达为:

[x] = [cos(t)  sin(t)][x]
[y]   [-sin(t) cos(t)][y]

x' =  x * cos(t) + y * sin(t)
y' = -x * sin(t) + y * cos(t)

我将一个2d矩阵顺时针旋转30度,尺寸为150x150。

代码中的

看起来像这样:

    cv::Mat m = cv::Mat::zeros(150, 150, CV_8UC1);
    cv::Mat nm = cv::Mat::zeros(150, 150, CV_8UC1);
    cv::rectangle(m, cv::Rect(0,0,150,150), cv::Scalar(100,100,100),-1);

    double rad = (30.0 * M_PI/180.0);
    for (int c = 0; c < m.cols;c++){
        for (int r = 0; r < m.rows;r++){
            int x = (m.cols*0.5)-c;
            int y = (m.rows*0.5)-r;
            int xn = (x*cos(rad) + y*sin(rad));
            int yn = (-x*sin(rad) + y*cos(rad));
            xn += (m.cols*0.5);
            yn += (m.rows*0.5);
            if (xn<0||xn>=m.cols||yn<0||yn>=m.rows){
                continue;
            }
            nm.at<uchar>(xn,yn)=m.at<uchar>(c,r);    
        }
    }

在数学上它看起来是正确的,但这些是我得到的结果

未旋转的矩阵:

Unrotated matrix

旋转矩阵:

Rotated matrix

旋转的矩阵呈现颗粒状。是什么导致这个?它是如何修复的?我更喜欢用数学方法解释。

PS。请不要指向opencv的预煮方法,我知道它们存在。

3 个答案:

答案 0 :(得分:3)

我认为你正在尝试的是moire pattern的一种形式,因为当你旋转像素时,你不是旋转数学点,而是旋转小方块。我找到了一个很好的图像表示我正在说here(向下滚动)。

解决方案是应用抗锯齿旋转算法。

修改

一个快速而肮脏的解决方案,我想到了(我不知道有多大效果 - 你应该测试它),是在旋转之前对图像进行过采样,然后将其下采样到原始尺寸。但这 真的 在计算上浪费了。它可能用于测试。

答案 1 :(得分:2)

诀窍是使用逆变换,然后对结果图像上的每个像素进行采样。假设您要旋转(或以其他方式转换)图像I1(x1,y1)以生成图像I2(x2,y2),其中I(x,y)是在x,y点处给出图像强度的函数。假设你想到的变换是T(x1,y1)=(x2,y2)。

现在迭代目标图像I2中的每个点,并应用逆变换Tinv来查找原始图像中的相应像素:

I2(x,y):= I1(Tinv(x,y))

这应该会给你一个同质的盒子,至少在你的示例图像中。为了获得更好的图像质量,您需要使用一些插值,因为Tinv(x,y)不会完全是I1中像素的中心。

看起来有一些image warping in opencv,它可以给你你想要的东西。 (或者您是否有特殊原因要求使用opencv预煮方法?)

答案 2 :(得分:0)

模式的数学解释:

考虑未旋转的点(0, 0)(1, 0)

旋转30度后,它们分别变为(0, 0)(0.866, -0.5)

在数学上,这些是不同的观点。但是,这两个点都分配给int个变量,这些变量向下舍入为正数,向上向下舍入为负数。

这意味着旋转点的计算值实际上是(0, 0)(0, 0)

因为两个值都分配给同一个像素,所以会出现双重上升,看起来好像一个像素已从旋转的方块中消失。每当出现像素坐标的两倍时,此过程就会继续发生,从而在旋转的正方形上产生图案。

修复:

要解决此问题,您必须向后处理问题:将每个方块视为已经旋转,并查看它是否与原始方块上的点相关。

要执行此操作,请使用旋转矩阵的反转(您已完成):

X' =  X * cos(t) + Y * sin(t)
Y' = -X * sin(t) + Y * cos(t)

所以,你需要做的只是增加你的计算循环:

for (int r = 0; r < nm.rows; ++r){ 
    for (int c = 0; c < nm.cols; ++c){ 
        int x = c - (m.cols*0.5); //Translates from the origin
        int y = r - (m.rows*0.5); 
        int xn = (x*cos(rad) + y*sin(rad)) + 0.5f; //The 0.5 corrects rounding
        int yn = (-x*sin(rad) + y*cos(rad)) + 0.5f;
        xn += (m.cols*0.5); //Translates back to the origin
        yn += (m.rows*0.5);
        if (xn >= 0 && xn < m.cols && yn >= 0 && yn < m.rows) 
            nm.at<uchar>(c,r) = m.at<uchar>(xn,yn);
            //Changed the above line so that nm is cycled through, not m
    }
}

修复工作原理:

你有两个矩形:一个空白:nm,一个填充一个:m

最初你在所有m的像素中循环,旋转它们,并在nm上设置旋转点,这导致了有趣的模式。

增强版本的反面相反:它循环遍历nm的所有点并将它们旋转回原始正方形。如果该点在旋转回来时位于m内,则nm上的对应点有效。否则,它无效,并且不会被绘制。