几何变换
几何变换可以看成图像中物体(或像素)空间位置改变,或者说是像素的移动。
几何运算需要空间变换和灰度级差值两个步骤的算法,像素通过变换映射到新的坐标位置,新的位置可能是在几个像素之间,即不一定为整数坐标。这时就需要灰度级差值将映射的新坐标匹配到输出像素之间。最简单的插值方法是最近邻插值,就是令输出像素的灰度值等于映射最近的位置像素,该方法可能会产生锯齿。这种方法也叫零阶插值,相应比较复杂的还有一阶和高阶插值。
插值算法感觉只要了解就可以了,图像处理中比较需要理解的还是空间变换。
空间变换
空间变换对应矩阵的仿射变换。一个坐标通过函数变换的新的坐标位置:
所以在程序中我们可以使用一个2*3的数组结构来存储变换矩阵:
以最简单的平移变换为例,平移(b1,b2)坐标可以表示为:
因此,平移变换的变换矩阵及逆矩阵记为:
缩放变换:将图像横坐标放大(或缩小)sx倍,纵坐标放大(或缩小)sy倍,变换矩阵及逆矩阵为:
选择变换:图像绕原点逆时针旋转a角,其变换矩阵及逆矩阵(顺时针选择)为:
OpenCV中的图像变换函数
基本的放射变换函数:
void cvWarpAffine( const CvArr* src,//输入<strong>图像</strong> CvArr* dst, //输出<strong>图像</strong> const CvMat* map_matrix, //2*3的<strong>变换</strong>矩阵 int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, //插值方法的组合 CvScalar fillval=cvScalarAll(0) //用来填充边界外的值 );
另外一个比较类似的函数是cvGetQuadrangleSubPix:
void cvGetQuadrangleSubPix( const CvArr* src, //输入<strong>图像</strong> CvArr* dst, // 提取的四边形 const CvMat* map_matrix //2*3的<strong>变换</strong>矩阵 );
这个函数用以提取输入图像中的四边形,并通过map_matrix变换存储到dst中,与WarpAffine变换意义相同,
即对应每个点的变换:
WarpAffine与 GetQuadrangleSubPix 不同的在于cvWarpAffine 要求输入和输出图像具有同样的数据类型,有更大的资源开销(因此对小图像不太合适)而且输出图像的部分可以保留不变。而 cvGetQuadrangleSubPix 可以精确地从8位图像中提取四边形到浮点数缓存区中,具有比较小的系统开销,而且总是全部改变输出图像的内容。
实践:图像旋转变换(原尺寸)
首先用cvWarpAffine实验将图像逆时针旋转degree角度。
这里我们将新的图像还保留原来的图像尺寸。这样的效果显然不太好,我们通过计算相应放大图像尺寸。
实践:图像旋转变换(保留原图内容,放大尺寸)
需要计算新图的尺寸,示意图如下:
所以新图size为(width*cos(a)+height*sin(a), height*cos(a)+width*sin(a))
实践:图像旋转变换(保留原图内容,放大尺寸)-2
试一下用cvGetQuadrangleSubPix函数:
//<strong>旋转</strong><strong>图像</strong>内容不变,尺寸相应变大 IplImage* rotateImage2(IplImage* img, int degree) { double angle = degree * CV_PI / 180.; double a = sin(angle), b = cos(angle); int width=img->width, height=img->height; //<strong>旋转</strong>后的新图尺寸 int width_rotate= int(height * fabs(a) + width * fabs(b)); int height_rotate=int(width * fabs(a) + height * fabs(b)); IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), img->depth, img->nChannels); cvZero(img_rotate); //保证原图可以任意角度<strong>旋转</strong>的最小尺寸 int tempLength = sqrt((double)width * width + (double)height *height) + 10; int tempX = (tempLength + 1) / 2 - width / 2; int tempY = (tempLength + 1) / 2 - height / 2; IplImage* temp = cvCreateImage(cvSize(tempLength, tempLength), img->depth, img->nChannels); cvZero(temp); //将原图复制到临时<strong>图像</strong>tmp中心 cvSetImageROI(temp, cvRect(tempX, tempY, width, height)); cvCopy(img, temp, NULL); cvResetImageROI(temp); //<strong>旋转</strong>数组map // [ m0 m1 m2 ] ===> [ A11 A12 b1 ] // [ m3 m4 m5 ] ===> [ A21 A22 b2 ] float m[6]; int w = temp->width; int h = temp->height; m[0] = b; m[1] = a; m[3] = -m[1]; m[4] = m[0]; // 将<strong>旋转</strong>中心移至<strong>图像</strong>中间 m[2] = w * 0.5f; m[5] = h * 0.5f; CvMat M = cvMat(2, 3, CV_32F, m); cvGetQuadrangleSubPix(temp, img_rotate, &M); cvReleaseImage(&temp); return img_rotate; }
实践:图像放射变换(通过三点确定变换矩阵)
在OpenCV 2.3的参考手册中《opencv_tutorials》介绍了另一种确定变换矩阵的方法,通过三个点变换的几何关系映射实现变换。变换示意图如下:
即通过三个点就可以确定一个变换矩阵。(矩形变换后一定为平行四边形)
以下是基于OpenCV 2.3的代码(需至少2.0以上版本的支持)