[opencv] 图像几何变换:旋转,缩放,斜切

2022-12-19,,,

几何变换

几何变换可以看成图像中物体(或像素)空间位置改变,或者说是像素的移动。

几何运算需要空间变换和灰度级差值两个步骤的算法,像素通过变换映射到新的坐标位置,新的位置可能是在几个像素之间,即不一定为整数坐标。这时就需要灰度级差值将映射的新坐标匹配到输出像素之间。最简单的插值方法是最近邻插值,就是令输出像素的灰度值等于映射最近的位置像素,该方法可能会产生锯齿。这种方法也叫零阶插值,相应比较复杂的还有一阶和高阶插值。

插值算法感觉只要了解就可以了,图像处理中比较需要理解的还是空间变换。

空间变换

空间变换对应矩阵的仿射变换。一个坐标通过函数变换的新的坐标位置:

所以在程序中我们可以使用一个2*3的数组结构来存储变换矩阵:

以最简单的平移变换为例,平移(b1,b2)坐标可以表示为:

因此,平移变换的变换矩阵及逆矩阵记为:

缩放变换:将图像横坐标放大(或缩小)sx倍,纵坐标放大(或缩小)sy倍,变换矩阵及逆矩阵为:

选择变换:图像绕原点逆时针旋转a角,其变换矩阵及逆矩阵(顺时针选择)为:

OpenCV中的图像变换函数

基本的放射变换函数:

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() //用来填充边界外的值
);

另外一个比较类似的函数是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, int degree)
{
IplImage *img_rotate = cvCloneImage(img);
cvZero(img_rotate);
//旋转中心为图像中心
CvPoint2D32f center;
center.x=float (img->width/2.0+0.5);
center.y=float (img->height/2.0+0.5);
//计算二维旋转的仿射变换矩阵
float m[];
CvMat M = cvMat( , , CV_32F, m );
cv2DRotationMatrix( center, degree,, &M);
//变换图像,并用黑色填充其余值
cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll() ); cvNamedWindow("原图");
cvNamedWindow("旋转后的图像");
cvShowImage("原图",img);
cvShowImage("旋转后的图像",img_rotate);
}

逆时针旋转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 / .; // 弧度
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[];
CvMat map_matrix = cvMat(, , CV_32F, map);
// 旋转中心
CvPoint2D32f center = cvPoint2D32f(width / , height / );
//函数返回一个指向2X3矩阵的指针
//PointF center:源图像的旋转中心
//double angle:源图像旋转的角度,正值表示逆时针旋转(坐标原点假设在图像左上角)
//double scale:等向比例因子
//IntPer mapMatrix:用于返回的2X3矩阵
cv2DRotationMatrix(center, degree, 1.0, &map_matrix);
map[] += (width_rotate - width) / ;
map[] += (height_rotate - height) / ;
IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), , );
//对图像做仿射变换
//输入图像
//输出图像
//2*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()); cvNamedWindow("原图");
cvNamedWindow("旋转后的图像");
cvShowImage("原图",img);
cvShowImage("旋转后的图像",img_rotate); return img_rotate;
}

实践:图像旋转变换(保留原图内容,放大尺寸)-2

试一下用cvGetQuadrangleSubPix函数:

//旋转图像内容不变,尺寸相应变大
IplImage* rotateImage2(IplImage* img, int degree) //顺时针旋转
{
double angle = degree * CV_PI / .;
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) + ; //sqrt()开平方根
int tempX = (tempLength + ) / - width / ;
int tempY = (tempLength + ) / - height / ;
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); cvNamedWindow("临时图像");
cvShowImage("临时图像",temp); //旋转数组map
// [ m0 m1 m2 ] ===> [ A11 A12 b1 ]
// [ m3 m4 m5 ] ===> [ A21 A22 b2 ]
float m[];
int w = temp->width;
int h = temp->height;
m[] = b;
m[] = a;
m[] = -m[];
m[] = m[];
// 将旋转中心移至图像中间
m[] = w * 0.5f;
m[] = h * 0.5f;
CvMat M = cvMat(, , CV_32F, m);
cvGetQuadrangleSubPix(temp, img_rotate, &M);
cvReleaseImage(&temp); cvNamedWindow("原图");
cvNamedWindow("旋转后的图像");
cvShowImage("原图",img);
cvShowImage("旋转后的图像",img_rotate); return img_rotate;
}

 

实践:图像放射变换(通过三点确定变换矩阵)

在OpenCV 2.3的参考手册中《opencv_tutorials》介绍了另一种确定变换矩阵的方法,通过三个点变换的几何关系映射实现变换。

变换示意图如下:

即通过三个点就可以确定一个变换矩阵。(矩形变换后一定为平行四边形)
以下是基于OpenCV 2.3的代码(需至少2.0以上版本的支持)

//以下代码理论可参考:http://blog.csdn.net/fengbingchun/article/details/17713429
Mat rotateImage3(Mat src){ //仿射变换
Point2f srcTri[];
Point2f dstTri[];
Mat rot_mat( , , CV_32FC1 );
Mat warp_mat( , , CV_32FC1 );
Mat warp_dst, warp_rotate_dst;
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
// 用3个点确定A仿射变换
srcTri[] = Point2f( , );
srcTri[] = Point2f( src.cols - , );
srcTri[] = Point2f( , src.rows - );
dstTri[] = Point2f( src.cols*0.0, src.rows*0.33 );
dstTri[] = Point2f( src.cols*0.85, src.rows*0.25 );
dstTri[] = 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/, warp_dst.rows/ );
double angle = -50.0;
double scale = 0.6;
////获取旋转矩阵 scale为缩放因子(x、y方向保持一致),angle为旋转角度(弧长),centerx,centery为旋转中心
// | a b (1-a)*center.x-b*center.y | a=scale*cos(angle)
// M= | |
// | -b a b*center.x+(1-a)*center.y| b=scale*sin(angle)
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 );
return warp_dst; }

变换结果:

主函数:

// ImageRotation2.cpp : 定义控制台应用程序的入口点。
// 本文代码参考:http://www.cnblogs.com/slysky/archive/2012/03/21/2410743.html #include "stdafx.h"
#include <opencv2/core/core.hpp> //cvGetSize cvCreateImage
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp> //cvResize cvInitMatHeader cvGetMinMaxHistValue cvCvtColor
#include <opencv2/imgproc/imgproc.hpp> #ifdef _DEBUG
#pragma comment(lib, "opencv_core244d")
#pragma comment(lib, "opencv_highgui244d")
#pragma comment(lib, "opencv_imgproc244d") //cvResize
#else
#pragma comment(lib, "opencv_core244")
#pragma comment(lib, "opencv_highgui244")
#pragma comment(lib, "opencv_imgproc244") //cvResize
#endif using namespace std;
//隐藏控制台窗口
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") int main( )
{
//读入图像
//Mat src = imread( "./images/baboon.jpg", 1 );
//src = rotateImage3(src); int degree = ;
IplImage *src = cvLoadImage("./images/meng3.jpg",CV_LOAD_IMAGE_UNCHANGED);
rotateImage(src, degree); //src = rotateImage1(src, degree); //src = rotateImage2(src, degree); waitKey();
return ;
}

本文转自:http://blog.csdn.net/xiaowei_cqu/article/details/7616044

[opencv] 图像几何变换:旋转,缩放,斜切的相关教程结束。

《[opencv] 图像几何变换:旋转,缩放,斜切.doc》

下载本文的Word格式文档,以方便收藏与打印。