OpenCV图像旋转原理及示例
OpenCV计算机视觉开发实践:基于Qt C++ - 商品搜索 - 京东
图像旋转是数字图像处理的一个非常重要的环节,是图像的几何变换手法之一。图像旋转算法是图像处理的基础算法。在数字图像处理过程中,经常要用到旋转,例如在进行图像扫描时,需要运用旋转实现图像的倾斜校正;在进行多幅图像的比较、模式识别及对图像进行剪裁和拼接前,都需要进行图像的旋转处理。
图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的过程。图像旋转通常可以分为两种情况,一种是以坐标原点为中心进行旋转;另外一种是以任意图形中的某点为坐标原点进行旋转。图像的旋转变换是图像的位置变换,旋转后图像的大小一般会改变。在图像旋转变换中,既可以把转出显示区域的图像截去,也可以扩大图像范围以显示所有的图像。
1. 以坐标原点为中心进行旋转的原理
点P0绕坐标原点逆时针旋转θ角度得到点P1,如图7-2所示。
图7-2
2. 以任意图形中的某点为坐标原点进行旋转的原理
以任意图形中的某点为坐标原点进行旋转的原理如图7-3所示。
图7-3
由图7-3可以看出,以任意图形中心点为坐标原点进行旋转需要如下3步:
将坐标系Ⅰ变成坐标系Ⅱ。
由Figure1得到Figure2可知,变换矩阵为:
在坐标系Ⅱ中旋转θ角度。参考以坐标原点为中心进行旋转的原理。
将坐标系Ⅱ变成坐标系Ⅰ。
由图7-3中的Figure3得到Figure4可知,变换矩阵为(其实就是步骤01中变换矩阵的逆变换):
在OpenCV中,图像旋转首先根据旋转角度和旋转中心获取旋转矩阵,然后根据旋转矩阵进行变换,即可实现任意角度和任意中心的旋转效果。
下面我们实现图像旋转算法,有两种方式:一种是根据算法手动实现,另一种是根据OpenCV提供的现成函数自动实现。现在先来介绍根据算法手动实现的方式。
【例7.2】手动实现图像旋转
打开Qt Creator,新建一个控制台工程,工程名是test。
在工程中打开main.cpp,输入如下代码:
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <string>
#include <cmath>using namespace cv;
Mat imgRotate(Mat matSrc, float angle, bool direction)
{float theta = angle * CV_PI / 180.0;const int nRowsSrc = matSrc.rows;const int nColsSrc = matSrc.cols;// 如果是顺时针旋转if (!direction)theta = 2 * CV_PI - theta;// 全部以逆时针旋转来计算// 逆时针旋转矩阵float matRotate[3][3]{{std::cos(theta), -std::sin(theta), 0},{std::sin(theta), std::cos(theta), 0 },{0, 0, 1}};float pt[3][2]{ { 0, (float)nRowsSrc}, {(float)nColsSrc, (float)nRowsSrc}, {(float)nColsSrc, 0}};for (int i = 0; i < 3; i++){float x = pt[i][0] * matRotate[0][0] + pt[i][1] * matRotate[1][0];float y = pt[i][0] * matRotate[0][1] + pt[i][1] * matRotate[1][1];pt[i][0] = x;pt[i][1] = y;}// 计算出旋转后图像的极值点和尺寸float fMin_x = min(min(min(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0);float fMin_y = min(min(min(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0);float fMax_x = max(max(max(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0);float fMax_y = max(max(max(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0);int nRows = cvRound(fMax_y - fMin_y + 0.5) + 1;int nCols = cvRound(fMax_x - fMin_x + 0.5) + 1;int nMin_x = cvRound(fMin_x + 0.5);int nMin_y = cvRound(fMin_y + 0.5);// 拷贝输出图像Mat matRet(nRows, nCols, matSrc.type(), Scalar(0));for (int j = 0; j < nRows; j++){for (int i = 0; i < nCols; i++){// 计算出输出图像在原图像中对应点的坐标,然后复制该坐标的灰度值// 因为是逆时针转换,所以这里映射到原图像时可以看作输出图像顺时针旋转到原图像// 而顺时针旋转矩阵刚好是逆时针旋转矩阵的转置// 同时还要考虑把旋转后的图像的左上角移动到坐标原点int x = (i + nMin_x) * matRotate[0][0] + (j + nMin_y) * matRotate[0][1];int y = (i + nMin_x) * matRotate[1][0] + (j + nMin_y) * matRotate[1][1];if (x >= 0 && x < nColsSrc && y >= 0 && y < nRowsSrc){matRet.at<Vec3b>(j, i) = matSrc.at<Vec3b>(y, x);}}}return matRet;
}int main(){Mat matSrc = imread("cat.png");if (matSrc.empty())return 1;float angle = 30;Mat matRet = imgRotate(matSrc, angle, true);imshow("src", matSrc);imshow("rotate", matRet);// 保存图像imwrite("rotate_panda.jpg", matRet);waitKey();return 0;}
以上代码完全是根据前面的算法原理公式来实现的。图像旋转是指图像按照某个位置转动一定角度的过程,旋转中图像仍保持原始尺寸。图像旋转后,图像的水平对称轴、垂直对称轴及中心坐标原点都可能发生变换,因此需要对图像旋转中的坐标进行相应转换。
保存工程并运行,结果如图7-4所示。
如果不想通过算法公式手动实现图像旋转,也可以利用OpenCV提供的库函数getRotationMatrix2D来实现图像旋转。该函数用来计算旋转矩阵,其声明如下:
Mat getRotationMatrix2D(Point2f center, double angle, double scale);
其中参数center表示旋转的中心点;angle表示旋转的角度;scale表示图像缩放因子。该函数的返回值为一个2×3的矩阵,其中矩阵前两列代表旋转,最后一列代表平移。
图7-4
计算出旋转矩阵后,还需要把旋转应用到仿射变换的输出,仿射变换函数是warpAffine,该函数在下一节介绍。
【例7.3】使用函数实现图像的旋转
打开Qt Creator,新建一个控制台工程,工程名是test。
在工程中打开main.cpp,输入如下代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;// 图像旋转,angle表示要旋转的角度
void Rotate(const Mat &srcImage, Mat &destImage, double angle)
{Point2f center(srcImage.cols / 2, srcImage.rows / 2);// 中心Mat M = getRotationMatrix2D(center, angle, 1);// 计算旋转的仿射变换矩阵 // 现在把旋转应用到仿射变换的输出warpAffine(srcImage, destImage, M, Size(srcImage.cols, srcImage.rows));// 仿射变换 circle(destImage, center, 2, Scalar(255, 0, 0));
}int main()
{// 读入图像,并判断图像是否读入正确cv::Mat srcImage = imread("lena.png");if (!srcImage.data){puts("Fail to open file.");return -1;}imshow("srcImage", srcImage); // 原图像也绘制出来以作参照Mat destImage;double angle = 9.9;// 角度Rotate(srcImage, destImage, angle);// 最后把仿射变换和旋转的结果绘制在窗体中imshow("dst", destImage);waitKey(0);return 0;
}
保存工程并运行,结果如图7-5所示。
图7-5