几何变换能够当作图像中物体(或像素)空间位置改变,或者说是像素的移动。算法
几何运算须要空间变换和灰度级差值两个步骤的算法,像素经过变换映射到新的坐标位置,新的位置多是在几个像素之间,即不必定为整数坐标。这时就须要灰度级差值将映射的新坐标匹配到输出像素之间。最简单的插值方法是最近邻插值,就是令输出像素的灰度值等于映射最近的位置像素,该方法可能会产生锯齿。这种方法也叫零阶插值,相应比较复杂的还有一阶和高阶插值。数组
插值算法感受只要了解就能够了,图像处理中比较须要理解的仍是空间变换。缓存
空间变换对应矩阵的仿射变换。一个坐标经过函数变换的新的坐标位置:函数
因此在程序中咱们可使用一个2*3的数组结构来存储变换矩阵:工具
以最简单的平移变换为例,平移(b1,b2)坐标能够表示为:学习
所以,平移变换的变换矩阵及逆矩阵记为:spa
缩放变换:将图像横坐标放大(或缩小)sx倍,纵坐标放大(或缩小)sy倍,变换矩阵及逆矩阵为:.net
选择变换:图像绕原点逆时针旋转a角,其变换矩阵及逆矩阵(顺时针选择)为:code
基本的放射变换函数:orm
void cvWarpAffine( const CvArr* src,//输入图像 CvArr* dst, //输出图像 const CvMat* map_matrix, //2*3的变换矩阵 int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, //插值方法的组合 CvScalar fillval=cvScalarAll(0) //用来填充边界外的值 );
另一个比较相似的函数是cvGetQuadrangleSubPix:
void cvGetQuadrangleSubPix( const CvArr* src, //输入图像 CvArr* dst, // 提取的四边形 const CvMat* map_matrix //2*3的变换矩阵 );
这个函数用以提取输入图像中的四边形,并经过map_matrix变换存储到dst中,与WarpAffine变换意义相同,
即对应每一个点的变换:
WarpAffine与 GetQuadrangleSubPix 不一样的在于cvWarpAffine 要求输入和输出图像具备一样的数据类型,有更大的资源开销(所以对小图像不太合适)并且输出图像的部分能够保留不变。而 cvGetQuadrangleSubPix 能够精确地从8位图像中提取四边形到浮点数缓存区中,具备比较小的系统开销,并且老是所有改变输出图像的内容。
首先用cvWarpAffine实验将图像逆时针旋转degree角度。
//逆时针旋转图像degree角度(原尺寸) void rotateImage(IplImage* img, IplImage *img_rotate,int degree) { //旋转中心为图像中心 CvPoint2D32f center; center.x=float (img->width/2.0+0.5); center.y=float (img->height/2.0+0.5); //计算二维旋转的仿射变换矩阵 float m[6]; CvMat M = cvMat( 2, 3, CV_32F, m ); cv2DRotationMatrix( center, degree,1, &M); //变换图像,并用黑色填充其他值 cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) ); }
逆时针旋转30度结果:
这里咱们将新的图像还保留原来的图像尺寸。这样的效果显然不太好,咱们经过计算相应放大图像尺寸。
须要计算新图的尺寸,示意图以下:
因此新图size为(width*cos(a)+height*sin(a), height*cos(a)+width*sin(a))
//旋转图像内容不变,尺寸相应变大 IplImage* rotateImage1(IplImage* img,int degree){ double angle = degree * CV_PI / 180.; // 弧度 double a = sin(angle), b = cos(angle); int width = img->width; int height = img->height; int width_rotate= int(height * fabs(a) + width * fabs(b)); int height_rotate=int(width * fabs(a) + height * fabs(b)); //旋转数组map // [ m0 m1 m2 ] ===> [ A11 A12 b1 ] // [ m3 m4 m5 ] ===> [ A21 A22 b2 ] float map[6]; CvMat map_matrix = cvMat(2, 3, CV_32F, map); // 旋转中心 CvPoint2D32f center = cvPoint2D32f(width / 2, height / 2); cv2DRotationMatrix(center, degree, 1.0, &map_matrix); map[2] += (width_rotate - width) / 2; map[5] += (height_rotate - height) / 2; IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), 8, 3); //对图像作仿射变换 //CV_WARP_FILL_OUTLIERS - 填充全部输出图像的象素。 //若是部分象素落在输入图像的边界外,那么它们的值设定为 fillval. //CV_WARP_INVERSE_MAP - 指定 map_matrix 是输出图像到输入图像的反变换, cvWarpAffine( img,img_rotate, &map_matrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll(0)); return img_rotate; }
试一下用cvGetQuadrangleSubPix函数:
//旋转图像内容不变,尺寸相应变大 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; //旋转后的新图尺寸 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); //保证原图能够任意角度旋转的最小尺寸 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); //将原图复制到临时图像tmp中心 cvSetImageROI(temp, cvRect(tempX, tempY, width, height)); cvCopy(img, temp, NULL); cvResetImageROI(temp); //旋转数组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]; // 将旋转中心移至图像中间 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; }
//旋转图像内容不变,尺寸相应变大 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; //旋转后的新图尺寸 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); //保证原图能够任意角度旋转的最小尺寸 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); //将原图复制到临时图像tmp中心 cvSetImageROI(temp, cvRect(tempX, tempY, width, height)); cvCopy(img, temp, NULL); cvResetImageROI(temp); //旋转数组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]; // 将旋转中心移至图像中间 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以上版本的支持)
int main( ) { 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( "baboon.jpg", 1 ); warp_dst = Mat::zeros( src.rows, src.cols, src.type() ); // 用3个点肯定A仿射变换 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() ); ////OpenCV 1.0的形式 //IplImage * img=cvLoadImage("baboon.jpg"); //IplImage *img_rotate=cvCloneImage(img); //CvMat M =warp_mat; //cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) ); //cvShowImage("Wrap2",img_rotate); namedWindow( "Source", CV_WINDOW_AUTOSIZE ); imshow( "Source", src ); namedWindow( "Wrap", CV_WINDOW_AUTOSIZE ); imshow( "Wrap", warp_dst ); namedWindow("Wrap+Rotate", CV_WINDOW_AUTOSIZE ); imshow( "Wrap+Rotate", warp_rotate_dst ); waitKey(0); return 0; }
int main( ) { 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( "baboon.jpg", 1 ); warp_dst = Mat::zeros( src.rows, src.cols, src.type() ); // 用3个点肯定A仿射变换 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() ); ////OpenCV 1.0的形式 //IplImage * img=cvLoadImage("baboon.jpg"); //IplImage *img_rotate=cvCloneImage(img); //CvMat M =warp_mat; //cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) ); //cvShowImage("Wrap2",img_rotate); namedWindow( "Source", CV_WINDOW_AUTOSIZE ); imshow( "Source", src ); namedWindow( "Wrap", CV_WINDOW_AUTOSIZE ); imshow( "Wrap", warp_dst ); namedWindow("Wrap+Rotate", CV_WINDOW_AUTOSIZE ); imshow( "Wrap+Rotate", warp_rotate_dst ); waitKey(0); return 0; }
变换结果:
写在最后的一点点闲话
以前一直用的2.1的版本,后来装了2.3,只是据说2.3很强大,但我刚开始学,用的也基础,彻底没感受出不一样。直到今天突然看到了2.3的手册,才发现从2.0开始函数和基本结构都有了很大的改变,而我一直仍是用的1.0风格的函数(好比cvMat,cvLoadImage)。个人两个学习工具《Learnning OpenCV》和《OpenCV中文参考手册》都是基于1.0的,这也是我到今天才看到Mat,而后直接被惊艳到了。
别人总结出来的东西能帮助咱们在一开始迅速入门,但要学深,学精,终归仍是要本身去努力挖的。