数字图像处理(十五)图像旋转

文章目录

  • 前言
  • 一、图像旋转算法
    • 1.算法原理
    • 2. 一些需要注意的点
    • 3.举例
    • 4. 均值插值法
  • 二、编程实现
    • 1.C++代码
    • 2.实验结果
  • 参考资料


前言

  图像的旋转是指以图像中的某一点为原点以逆时针或者顺时针方向旋转一定的角度。通常是绕图像的起始点以逆时针进行旋转。


一、图像旋转算法

1.算法原理

  图像旋转计算公式如下: i ′ = i cos ⁡ θ − j sin ⁡ θ i'=i\cos\theta-j\sin \theta i=icosθjsinθ j ′ = i sin ⁡ θ + j cos ⁡ θ j'=i\sin\theta+j\cos\theta j=isinθ+jcosθ
其中, ( i , j ) (i,j) (i,j)是原图像 f ( i , j ) f(i,j) f(i,j)中的像素点坐标; ( i ′ , j ′ ) (i',j') (i,j)是对应像素点 ( i , j ) (i,j) (i,j)经过旋转变换后的图像 g ( i ′ , j ′ ) g(i',j') g(i,j)的坐标。

2. 一些需要注意的点

  1. 旋转只改变图像中像素的位置,不改变像素的值;
  2. 因为图像的坐标值只能是正整数,所以根据上式计算出的新的坐标位置还需要进行处理;
  3. 因为旋转后的图像中间会出现空白点,所以要插值法对空白点进行插值。常用的插值方法有邻近插值法,均值插值法;
  4. 因为旋转后图像的画布大小会发生变化,所以我们还要提前计算新图的画图大小。

3.举例

设原图为 f = [ f 11 f 12 f 13 f 21 f 22 f 23 f 31 f 32 f 33 ] f = \begin{bmatrix} f_{11} & f_{12} & f_{13}\\ f_{21} & f_{22} & f_{23}\\ f_{31} & f_{32} & f_{33}\\ \end{bmatrix} f=f11f21f31f12f22f32f13f23f33
i i i构成的矩阵为: i = [ 1 1 1 2 2 2 3 3 3 ] i= \begin{bmatrix} 1 & 1 & 1\\ 2 & 2 & 2\\ 3 & 3 &3\\ \end{bmatrix} i=123123123
j j j构成的矩阵为: j = [ 1 2 3 1 2 3 1 2 3 ] j= \begin{bmatrix} 1 & 2 & 3\\ 1 & 2 & 3\\ 1 & 2 & 3\\ \end{bmatrix} j=111222333
假设此时要逆时针旋转30度,则根据上面的公式计算 i ′ i' i j ′ j' j
i ′ = i cos ⁡ 30 ° − j sin ⁡ 30 ° i'=i\cos30\degree-j\sin 30\degree i=icos30°jsin30° j ′ = i sin ⁡ 30 ° + j cos ⁡ 30 ° j'=i\sin30\degree+j\cos30\degree j=isin30°+jcos30°
如果要顺时针旋转30度,加个负号就可以了。
计算出的 i ′ i' i构成的矩阵为:
i ′ = [ 0.4 − 0.1 − 0.6 1.2 0.7 0.2 2.1 1.6 1.1 ] i'= \begin{bmatrix} 0.4& -0.1 & -0.6\\ 1.2 & 0.7 & 0.2\\ 2.1 & 1.6 &1.1\\ \end{bmatrix} i=0.41.22.10.10.71.60.60.21.1
此时我们可以看到 i ′ i' i中出现了负数和小数,这在我们编程中是行不通的,所以我们将 i ′ i' i这个矩阵先进行四舍五入。
i ′ = i ′ − min ⁡ ( i ′ ) = i ′ − ( − 0.6 ) = [ 0 0 − 1 1 1 0 2 2 1 ] i'=i'-\min(i')=i'-(-0.6)=\begin{bmatrix} 0 & 0 & -1 \\ 1 & 1 & 0\\ 2 & 2 &1 \\ \end{bmatrix} i=imin(i)=i(0.6)=012012101
又因为坐标没有负数,所以我们将 i ′ i' i中的每个元素加上2变成正整数得
i ′ = [ 2 2 1 3 3 2 4 4 3 ] i'= \begin{bmatrix} 2& 2 & 1\\ 3 & 3 & 2\\ 4 &4 &3\\ \end{bmatrix} i=234234123
同理可得 j ′ = [ 1 2 3 2 3 4 2 3 4 ] j'= \begin{bmatrix} 1 & 2 & 3 \\ 2 & 3 & 4 \\ 2 & 3 &4 \\ \end{bmatrix} j=122233344

现在我们可以找到对应关系了,例如:原图中的(1,1)点对应新图中的(2,1)点。旋转处理后的的图像数据为:
g = [ 0 0 f 13 0 f 11 f 12 0 f 23 0 f 21 f 22 f 33 0 f 31 f 32 0 ] g=\begin{bmatrix} 0 & 0 & f_{13} & 0\\ f_{11} & f_{12} & 0 & f_{23} \\ 0 & f_{21} &f_{22} &f_{33} \\ 0 & f_{31} & f_{32} & 0 \end{bmatrix} g=0f11000f12f21f31f130f22f320f23f330
从得到的 g g g矩阵中我们可以看到, g 23 g_{23} g23是一个空穴点,这一点我们需要利用插值法进行填补。其他为0的点则是属于画布上的空白点。

4. 均值插值法

  均值插值法是将空穴像素周围像素值的均值作为填充值填在该空穴点中,如下图所示:
数字图像处理(十五)图像旋转_第1张图片
背景为浅蓝色的点为空穴点,像素值为0。那为什么其他像素值为0的点不是空穴点呢?这里我们给出一个定义:如果一个像素值为0的点的上、下、左、右四个位置处的像素值都不为0,那我们就认为该点是空穴点,需要进行填充。
填充方式就是: g 23 = ( f 13 + f 12 + f 23 + f 22 ) / 4 g_{23}=(f_{13}+f_{12}+f_{23}+f_{22})/4 g23=(f13+f12+f23+f22)/4

二、编程实现

1.C++代码

int main()
{
    cv::Mat img = cv::imread("Lena.bmp");

    cv::Mat gray_img(img.cols,img.rows, CV_8UC1);
    cv::cvtColor(img, gray_img, CV_BGR2GRAY);
    float theta =45;
    float curvature = theta / 180 * CV_PI;

    cv::Mat x = cv::Mat::zeros(img.size(), CV_32FC1);
    cv::Mat y = cv::Mat::zeros(img.size(), CV_32FC1);

    for (int row = 0; row < img.rows; row++)
    {
        for (int col = 0; col < img.cols; col++)
        {
            // 计算新的坐标
            x.at<float>(row, col) = round(row * cos(curvature) - col * sin(curvature));
            y.at<float>(row, col) = round(row * sin(curvature) + col * cos(curvature));
        }
    }
    double x_min, x_max;
    double y_min, y_max;
    
    cv::minMaxLoc(x, &x_min, &x_max);
    cv::minMaxLoc(y, &y_min, &y_max);
    x = x - x_min; 
    y = y - y_min;

    cv::minMaxLoc(x, &x_min, &x_max); // 画布的高
    cv::minMaxLoc(y, &y_min, &y_max); // 画布的宽

    cv::Mat dst = cv::Mat::zeros(x_max+1, y_max+1, CV_8UC1);  //幕布
    cv::Mat flag = cv::Mat::zeros(x_max + 1, y_max + 1, CV_8UC1);

    for (int row = 0; row < gray_img.rows; row++)
    {
        for (int col = 0; col < gray_img.cols; col++)
        {
            int i = (int)x.at<float>(row, col);
            int j = (int)y.at<float>(row, col);
            dst.at<uchar>(i, j) = gray_img.at<uchar>(row, col);
            flag.at<uchar>(i, j) = 1;
        }
    }
    //均值插值法对空穴进行插值
    for (int row = 1; row < dst.rows-1; row++)
    {
        for (int col = 1; col < dst.cols-1; col++)
        {
            if (flag.at<uchar>(row, col - 1) == 1 && flag.at<uchar>(row, col + 1) == 1 &&
                flag.at<uchar>(row - 1, col) == 1 && flag.at<uchar>(row + 1, col) == 1
                && flag.at<uchar>(row, col) == 0)
            {
                dst.at<uchar>(row, col) = uchar((dst.at<uchar>(row, col - 1) + dst.at<uchar>(row, col + 1) +
                    dst.at<uchar>(row - 1, col) + dst.at<uchar>(row + 1, col)) / 4);
            }
        }
    }
    cv::imshow("input", gray_img);
    cv::imshow("output", dst);
    cv::waitKey(0);
    return 0;
}

2.实验结果


参考资料

1.数字图像处理基础.朱虹.

你可能感兴趣的:(数字图像处理,算法,图像处理,图像旋转)