一、序言
面对图像处理的时候,我们会旋转缩放图像,例如前面所提高的resize 插值改变,也是几何变换:
几何运算需要空间变换和灰度级差值两个步骤的算法,像素通过变换映射到新的坐标位置,新的位置可能是在几个像素之间,即不一定为整数坐标。这时就需要灰度级差值将映射的新坐标匹配到输出像素之间。最简单的插值方法是最近邻插值,就是令输出像素的灰度值等于映射最近的位置像素,该方法可能会产生锯齿。这种方法也叫零阶插值,相应比较复杂的还有一阶和高阶插值。
除了插值算法感觉只要了解就可以了,图像处理中比较需要理解的还是空间变换。
空间变换对应矩阵的仿射变换。一个坐标通过函数变换的新的坐标位置:
所以在程序中我们可以使用一个2*3的数组结构来存储变换矩阵:
以最简单的平移变换为例,平移(b1,b2)坐标可以表示为:
因此,平移变换的变换矩阵及逆矩阵记为:
缩放变换:将图像横坐标放大(或缩小)sx倍,纵坐标放大(或缩小)sy倍,变换矩阵及逆矩阵为:
选择变换:图像绕原点逆时针旋转a角,其变换矩阵及逆矩阵(顺时针选择)为:
二、重映射
重映射:
把一个图像中一个位置的像素放置到另一个图片指定位置的过程.
为了完成映射过程, 有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的.
简单的说就是改变图片的位置(左,右,上,下,颠倒)
<span style="font-size:18px;">C++: void remap(InputArray src, OutputArraydst, InputArray map1, InputArray map2, int interpolation, intborderMode=BORDER_CONSTANT , const Scalar& borderValue=Scalar()) </span>
- 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。
- 第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型。
- 第三个参数,InputArray类型的map1,它有两种可能的表示对象。
- 表示点(x,y)的第一个映射。
- 表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。
- 第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示那种对象。
- 若map1表示点(x,y)时。这个参数不代表任何值。
- 表示CV_16UC1 , CV_32FC1类型的Y值(第二个值)。
- 第五个参数,int类型的interpolation,插值方式,之前的resize( )函数中有讲到,需要注意,resize( )函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:
- INTER_NEAREST - 最近邻插值
- INTER_LINEAR – 双线性插值(默认值)
- INTER_CUBIC – 双三次样条插值(逾4×4像素邻域内的双三次插值)
- INTER_LANCZOS4 -Lanczos插值(逾8×8像素邻域的Lanczos插值)
- 第六个参数,int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。
- 第七个参数,const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。
其中要变换的模式如下
三、仿射变换
仿射变换(Affine Transformation)是空间直角坐标系的变换,从一个二维坐标变换到另一个二维坐标,仿射变换是一个线性变换,他保持了图像的“平行性”和“平直性”,即图像中原来的直线和平行线,变换后仍然保持原来的直线和平行线,仿射变换比较常用的特殊变换有平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。
其中,点1, 2 和 3 (在图一中形成一个三角形) 与图二中三个点是一一映射的关系, 且他们仍然形成三角形, 但形状已经和之前不一样了。我们能通过这样两组三点求出仿射变换 (可以选择自己喜欢的点),
接着就可以把仿射变换应用到图像中去。
而我们通常使用2 x 3的矩阵来表示仿射变换。
考虑到我们要使用矩阵 A 和 B 对二维向量 做变换,
所以也能表示为下列形式:
或者
即:
也可以理解是坐标系的旋转和缩放、平移
<span style="font-size:18px;">C++: void warpAffine(InputArray src,OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, intborderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar()) </span>
- 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
- 第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,需和源图片有一样的尺寸和类型。
- 第三个参数,InputArray类型的M,2×3的变换矩阵。
- 第四个参数,Size类型的dsize,表示输出图像的尺寸。
- 第五个参数,int类型的flags,插值方法的标识符。此参数有默认值INTER_LINEAR(线性插值),可选的插值方式如下:
- INTER_NEAREST - 最近邻插值
- INTER_LINEAR - 线性插值(默认值)
- INTER_AREA - 区域插值
- INTER_CUBIC –三次样条插值
- INTER_LANCZOS4 -Lanczos插值
- CV_WARP_FILL_OUTLIERS - 填充所有输出图像的象素。如果部分象素落在输入图像的边界外,那么它们的值设定为 fillval.
- CV_WARP_INVERSE_MAP –表示M为输出图像到输入图像的反变换,即 。因此可以直接用来做象素插值。否则, warpAffine函数从M矩阵得到反变换。
- 第六个参数,int类型的borderMode,边界像素模式,默认值为BORDER_CONSTANT。
- 第七个参数,const Scalar&类型的borderValue,在恒定的边界情况下取的值,默认值为Scalar(),即0。
四、例子
<span style="font-size:18px;">#include<opencv2/opencv.hpp> #include<iostream> #include<vector> using namespace cv; using namespace std; int main() { Mat srcImage = imread("lena.jpg", 1); imshow("【原图】", srcImage); Mat grayImage; cvtColor(srcImage, grayImage, CV_BGR2GRAY); Mat XImage, YImage; Mat dstImage; dstImage.create(srcImage.size(), srcImage.type()); XImage.create(srcImage.size(), CV_32FC1); YImage.create(srcImage.size(), CV_32FC1); for (int i = 0; i < srcImage.rows; i++) { for (int j = 0; j < srcImage.cols; j++) { XImage.at<float>(i, j) = static_cast<float>(srcImage.cols - j); YImage.at<float>(i, j) = static_cast<float>(i); } } remap(srcImage, dstImage, XImage, YImage, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0)); imshow("【重映射后】", dstImage); waitKey(0); return 0; } </span>
<span style="font-size:18px;">#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace cv; using namespace std; char* source_window = "Source image"; char* warp_window = "Warp"; char* warp_rotate_window = "Warp + Rotate"; int main( int argc, char** argv ) { Point2f srcTri[3]; Point2f dstTri[3]; Mat rot_mat( 2, 3, CV_32FC1 ); Mat warp_mat( 2, 3, CV_32FC1 ); Mat src, warp_dst, warp_rotate_dst; src = imread("lena.jpg", 1 ); warp_dst = Mat::zeros( src.rows, src.cols, src.type() ); srcTri[0] = Point2f( 0,0 ); srcTri[1] = Point2f( src.cols - 1, 0 ); srcTri[2] = Point2f( 0, src.rows - 1 ); dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 ); dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 ); dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 ); warp_mat = getAffineTransform( srcTri, dstTri ); warpAffine( src, warp_dst, warp_mat, warp_dst.size() ); Point center = Point( warp_dst.cols/2, warp_dst.rows/2 ); double angle = -50.0; double scale = 0.6; rot_mat = getRotationMatrix2D( center, angle, scale ); warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() ); namedWindow( source_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src ); namedWindow( warp_window, CV_WINDOW_AUTOSIZE ); imshow( warp_window, warp_dst ); namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE ); imshow( warp_rotate_window, warp_rotate_dst ); waitKey(0); return 0; }</span>
五、matlab
f=imread('d:\lena.jpg'); tform=maketform('affine',[-1 0 0;0 1 0;0 0 1]); ff=imtransform(f,tform); imshow(f) figure imshow(ff)
图像识别算法交流 QQ群:145076161,欢迎图像识别与图像算法,共同学习与交流