【OpenCV图像处理】五、图像的几何变换(下)

来源:互联网 发布:高级java招聘 编辑:程序博客网 时间:2024/05/12 08:49

5.1 图像的缩放变换

图像的缩放指的是将图像的尺寸变小或变大的过程,也就是减少或增加原图像数据的像素个数。简单来说,就是通过增加或删除像素点来改变图像的尺寸。当图像缩小时,图像会变得更加清晰,当图像放大时,图像的质量会有所下降,因此需要进行插值处理。

在图像缩放中常常会用到两个概念,也就是水平缩放系数和垂直缩放系数,水平缩放系数控制水平像素的缩放比例,垂直缩放系数控制垂直方向上像素的缩放比例。在实际运用缩放时,常常需要保持原始图像的宽度和高度的比例,也就是让水平缩放系数和垂直缩放系数相同。因为这种缩放系数不会使得缩放后的图像发生变形。

设水平缩放系数为Sx,垂直缩放系数为Sy,则缩放的坐标映射关系如下:


因为在进行图像缩放时会改变图像的大小,因此这里更关系向后映射矩阵,此时向后映射矩阵为:


也就是有



利用向后映射进行图像缩放的过程为:

首先进行计算新图像的大小,在这里设newWidth和newHeight分别表示新图像的宽度和高度,width和height表示原始图像的宽度和高度,则有newWidth=Sx*width,newHeight=Sy*height,然后再进行枚举新图像每个像素的坐标,通过向后映射计算出该像素映射在原始图像的坐标位置,再进行获取该像素的值。

需要注意的是,在进行后向映射的过程中可能会产生浮点数坐标,但是数字图像是以离散型整数存储数据的,所以无法得到浮点数坐标对应的像素值,这里就需要进行插值算法计算坐标是浮点型的像素值。

差值算法简要介绍:

插值算法主要用于处理图像在几何变换中出现的浮点坐标像素,算法的基本思想就是通过一系列算法获得浮点坐标像素的近似值,正因为浮点坐标是“插入”在整数坐标之间的,所以这种处理算法被称为“差值算法”。比较常见的插值算法有最邻近插值法,双线性插值法和二次立方插值法等。一般来说,最邻近插值法的效果最差,图像在进行放大后出现很明显的马赛克效应,使得图像细节变得十分模糊,而双线性插值法便大大的改善了放大图像后图像的质量,较好的避免了马赛克效应的产生,但是细节体现得依旧不够清晰,二次立方插值法的效果最好,放大后的图像细节较双线性插值法也有了较好的改善,但是它的计算复杂度非常高,因此导致运算的时间最长。

基本原理:

这里只简单地介绍一下最临近插值法和双线性插值方法

最邻近插值法也就是零阶插值,这种插值方法简单粗暴,就是常常说的“四舍五入”,也就是浮点数坐标的像素值等于离这点最近的输入图像的像素值,也就是看这点距离周围的四个点的哪个距离更近,那么就取哪个像素值。

双线性插值法就是二次线性插值法,它的主要思想就是计算出浮点坐标像素的近似值,将周围的4个点的像素值按照一定比例进行混合,最终得到这个浮点坐标的像素值。

在OpenCV中利用resize函数的进行图像的缩放操作,函数的原型为:

resize( InputArray src, OutputArray dst,Size dsize, double fx=0, double fy=0,int interpolation=INTER_LINEAR );

前两个参数分别为输入和输出图像。dsize表示输出图像的大小,如果为0,则

dsize=Size(round(fxsrc.cols),round(fysrc.rows))

dsize和fx、fy不能同时为0。fx、fy是沿x轴和y轴的缩放系数;默认取0时,计算如下

fx=(double)dsize.width/src.cols
fy=(double)dsize.height/src.rows

最优一个参数interpolation表示插值方式,有以下几种:
INTER_NEAREST - 最近邻插值
INTER_LINEAR - 线性插值(默认)
INTER_AREA - 区域插值
INTER_CUBIC - 三次样条插值
INTER_LANCZOS4 - Lanczos插值


编程实现如下:

//使用resize函数对图像进行缩放操作#include <iostream>#include <opencv2\core\core.hpp>#include <opencv2\highgui\highgui.hpp>#include <opencv2\imgproc\imgproc.hpp>using namespace std;using namespace cv;int main(){Mat srcImg, dstImg1,dstImg2,dstImg3;srcImg = imread("2345.jpg");imshow("原图像", srcImg);resize(srcImg, dstImg1, Size(0, 0), 1.5, 1.5, INTER_NEAREST);resize(srcImg, dstImg2, Size(0, 0), 1.5, 1.5, INTER_LINEAR);resize(srcImg, dstImg3, Size(0, 0), 1.5, 1.5, INTER_CUBIC);imshow("放大后的图像1", dstImg1);imshow("放大后的图像2", dstImg2);imshow("放大后的图像3", dstImg2);waitKey();return 0;}

这个程序实现的是将图像进行放大1.5倍的操作,而且可以通过结果看出,立方插值的效果好于双线性插值好于最临近插值。

5.2 图像的旋转变换

图像旋转就是指图像绕着某一点以逆时针或是顺时针方向旋转一定的角度,实际中常常按照逆时针进行旋转。为了得到旋转后新图像的坐标,通常要经过三个步骤,也就是①坐标原点平移到图像中心处(每个像素坐标都做平移变换)②针对新的原点对平移后的坐标做旋转③再将坐标原点移回到屏幕的左上角处。

同样的,图像在进行了旋转操作后,可能会出现一些空白的像素,需要对这些空白的像素做灰度级插值处理,否则将会影响旋转后图像的质量。在图像经过旋转变换后,它的宽度和高度都要发生一些相应的改变,所以原始图像中心点和输出图像的中心点的坐标的不相同的。而这也是造成旋转表换比较不容易实现的原因之一。


在OpenCV中没有写好的单一图像旋转函数,大多数情况我们会使用仿射变换函数warpAffine来进行图像旋转相关的操作,在这里先简单介绍一下图像的仿射变换,在仿射变换中主要有缩放,翻转与旋转三种变换形式,因为所有的仿射变换都是一种线性变换,对应变换可以表示成为矩阵相乘的形式,在空间直角坐标系中可以表示成为如下的形式:


在OpenCV中很容易构造仿射变换矩阵,一般使用一个2x3的矩阵来表示放射变换矩阵,也就是将上图中的矩阵等式的系数和常数项提出来构造矩阵即可。如下所示:


应用图像仿射变换矩阵,可以得到大部分的几何变换结果,例如之前提到的平移变换等,根据平移变换矩阵可以很容易的得到实现平移功能的仿射变换矩阵,如下所示:


对于图像缩放来说,设水平方向的缩放因子为a,垂直方向缩放因子为b,则用仿射矩阵实现图缩放功能的仿射矩阵为:


而对于图像旋转来说,设旋转角度为θ,利用仿射变换实现图像旋转操作的仿射矩阵为:


而对于较为特殊的斜切变换,同样的,设斜切的角度为θ,则仿射矩阵为:


需要注意的是,在OpenCV中使用仿射变换函数是,通常会先计算一个仿射变换矩阵,以此来获得放射变换矩阵,为了实现这个功能,常常使用getRotationMatrix2D()函数用来计算二维旋转矩阵,这个变换会将旋转中心映射到它自身。这里给出它的函数声明:

Mat getRotationMatrix2D( Point2f center, double angle, double scale );
这个函数中有三个参数,第一个参数是Point2f类型的center,也就是原图像的旋转中心;第二个参数是double 类型的angle,也就是我们说的旋转角度,值得一提的是,当angle的值为正时,表示的是逆时针旋转,当angle的值为负时,表示的是顺时针旋转。第三个参数scale表示的是缩放系数,在这个函数计算的是下面这个矩阵:


其中:



最后补充一下warpAffine的函数声明和参数意义:

void warpAffine( InputArray src, OutputArray dst,InputArray M, Size dsize,int flags=INTER_LINEAR,nt borderMode=BORDER_CONSTANT,const Scalar& borderValue=Scalar());
第一个参数和第二个参数分别表示原图像和函数操作结束后输出的图像,二者的尺寸和类型应该相等,只需要填Mat类的对象即可。第三个参数是一个Mat类型的矩阵M,是一个2x3大小的变换矩阵,第四个参数是Size类型的dsize,表示输出图像的尺寸,第五个参数是int类型的flags,表示的是不同插值方法的标识符,这个参数拥有默认值CV_INTER_LINEAR(线性插值),第六个参数是int类型的bodertype,表示的是边界像素模式,也拥有默认值CV_BODER_CONSTANT。第七个参数为const Scalar&类型的boderValue,默认值为0.

下面使用warpAffine函数实现图像旋转的这一简单功能。


//使用opencv的warpAffine函数对图像进行旋转#include <iostream>#include <opencv2\core\core.hpp>#include <opencv2\highgui\highgui.hpp>#include <opencv2\imgproc\imgproc.hpp>using namespace std;using namespace cv;int main(){Mat srcImage, dstImage;srcImage = imread("2345.jpg");if(!srcImage.data){cout << "读入图片有误!" << endl;return -1;}imshow("原图像", srcImage);dstImage.create(srcImage.size(), srcImage.type());double degree;cout << "请输入旋转角度:";cin >> degree;double a = sin(degree * CV_PI / 180);double b = cos(degree * CV_PI / 180);int width = srcImage.cols;int height = srcImage.rows;int rotate_width = int(height * fabs(a) + width * fabs(b));int rotate_height = int(width * fabs(a) + height * fabs(b));Point center = Point(srcImage.cols / 2, srcImage.rows / 2);Mat map_matrix = getRotationMatrix2D(center, degree, 1.0);map_matrix.at<double>(0, 2) += (rotate_width - width) / 2;     // 修改坐标偏移map_matrix.at<double>(1, 2) += (rotate_height - height) / 2;   // 修改坐标偏移warpAffine(srcImage, dstImage, map_matrix, { rotate_width, rotate_height }, CV_INTER_CUBIC);imshow("旋转后的图像", dstImage);waitKey();return 0;

 执行后设置旋转角度为+30°,程序的结果如下:




0 0
原创粉丝点击