在PS、画图板等工具里,我们常常用到放大、缩小、扭曲、旋转等等的一些图像变换,那么这一次,我们使用openCV来实现以下图像常用的几种变换方法。
一、重映射
什么是重映射?
名字听上去是比较高端,但是实际上,它的原理很简单,就是将原图像通过一定的数学公式映射到另一幅图像中去,通俗点讲就是把一幅图像中某位置的元素放置到另一幅图像中的指定位置的这么一个方法。
重映射中的差值过程:
因为重映射中x,y计算后可以为double类型,这意味着计算后需要四舍五入,很有可能造成目标图像某些点没有像素,那么此时就需要插值,openCV重映射函数提供了四种插值方法:
1.最近邻插值
2.双线性(默认)
3.像素区域重新采样
4.双三次插值
openCV中提供的重映射函数:
void cvRemap(
const CvArr* src,
CvArr* dst,
const CvArr* mapx,
const CvArr* mapy,
int flag = CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS
CvScalar fillval = cvScalarAll(0)
);
我们讨论如下四种映射:
(1)图像缩小并显示在窗口中心位置:
映射关系:dst.x = 2 * scr.x -0.5 * width
dst.y = 2 * scr.y -0.5 * height
(推的时候反推比较好,就是从scr向dst推导)
(2)图像上下颠倒:
映射关系:
dst.x = scr.x
dst.y = height - scr.y
(3)图像左右颠倒:
映射关系:
dst.x = width - scr.x
dst.y = scr.y
(4)图像上下左右均颠倒:
映射关系:
dst.x = width - scr.x
dst.y = height - scr.y
编程关键函数如下:
IplImage* MyRemap(IplImage* img, int choose)
{
IplImage* dst = cvCreateImage(cvGetSize(img), 8, 3);
CvMat* matx = cvCreateMat(img->height, img->width, CV_32FC1);
CvMat* maty = cvCreateMat(img->height, img->width, CV_32FC1);
for (int i = 0; i < img->height; i++)
{
for (int j = 0; j < img->width; j++)
{
switch (choose)
{
case 1://缩小并显示在最中心
if (j > img->width*0.25 && j < img->width*0.75 && i > img->height*0.25 && i < img->height*0.75)
{
*((float*)CV_MAT_ELEM_PTR(*matx, i, j)) = 2 * (j - img->width*0.25);
*((float*)CV_MAT_ELEM_PTR(*maty, i, j)) = 2 * (i - img->height*0.25);
}
else
{
*((float*)CV_MAT_ELEM_PTR(*matx, i, j)) = 0;
*((float*)CV_MAT_ELEM_PTR(*maty, i, j)) = 0;
}
break;
case 2:
*((float*)CV_MAT_ELEM_PTR(*matx, i, j)) = j;
*((float*)CV_MAT_ELEM_PTR(*maty, i, j)) = img->height - i;
break;
case 3:
*((float*)CV_MAT_ELEM_PTR(*matx, i, j)) = img->width - j;
*((float*)CV_MAT_ELEM_PTR(*maty, i, j)) = i;
break;
case 4:
*((float*)CV_MAT_ELEM_PTR(*matx, i, j)) = img->width - j;
*((float*)CV_MAT_ELEM_PTR(*maty, i, j)) = img->height - i;
break;
default:
break;
}
}
}
cvRemap(img, dst, matx, maty, CV_INTER_LINEAR);
return dst;
}
结果分别:
图像缩小并居中显示 图像上下颠倒
图像左右颠倒 图像上下左右颠倒
二、仿射变换
对平面图像进行几何变换,有两种转换方式:一种是基于2*3矩阵进行的变换,称为仿射变换;另一种是基于3*3矩阵进行的变换,称作透视变换。那么这里先讨论仿射变换,以下两种情况我们将使用到仿射变换:第一种是有一幅想要转换的图像,我们使用稠密仿射转换;第二种是我们有一个点序列并向以此进行转换,我们使用稀疏仿射变换,这里我们着重介绍稠密仿射变换。
过多的理论知识这里我不作解释,因为解释太过空洞,我想用通俗易懂,简单直接的方式来介绍到底什么是仿射变换。
仿射变换到底是用来干什么的?
实际上,仿射变换的作用可以说是将图像中一个平行四边形通过数学方法变换到另一个平行四边形,图像内容没有变换但是所承载的区域位置发生的变换,给人最直观的感觉就是旋转,缩放。
仿射如何变换,也就是我们要输入什么,输出什么?
上述解释道仿射变换实际上就是平行四边形的变换,那么有两种方法进行这种变换:
(1)坐标确定法:三个点将确定一个平行四边形,那么原图确定一个平行四边形,目标图确定指定的平行四边形,即可变换;
(2)旋转角变换法:目标图的平行四边形可以看做是原图的平行四边形缩放、旋转得到的,那么确定这个旋转中心、旋转角、缩放比例,即可变换。
openCV函数如何实现该变换?
实现稠密仿射变换的函数是 void cvWarpAffine(
const CvArr* scr,
CvArr* dst,
const CvMat* map_matirx,
int flags = CV_INTEAR | CV_WARP_FILL_OUTLIERS,
CvScalar fillval = cvScalarAll(0)
);
这里map_matrix是变换的关键,也就是上述所说的2*3的变换矩阵,关键是要得到这个变换矩阵,openCV根据我说的两种变换方法,提供了两种得到变换矩阵的方法:
(1)坐标确定法: CvMat* cvGetAffineTransform(
const CvPoint2D32f* pts_src,
const CvPoint2D32f* pts_dst,
CvMat* map_matrix
);
这里的pts_src、pts_dst分别是原图和目标图的三个二维坐标点。
(2)旋转确定法 CvMat* cv2DRotationMatrix(
CvPoint2D32f center,
double angle,
double scale,
CvMat* map_matrix
);
这里的center、angle、scale分别是旋转中心、旋转角(度数)、缩放比例系数。
关键函数代码实现:
IplImage* MyWarpAffine1(IplImage* img)
{//使用坐标点确定平行四边形的方法
IplImage* dst = cvCreateImage(cvGetSize(img), 8, 3);
CvMat* mymat = cvCreateMat(2, 3, CV_32FC1);
CvPoint2D32f scrTri[3], dstTri[3];
//设置scr和dst的平行四边形的点
scrTri[0].x = 0;
scrTri[0].y = 0;//原图左上角
scrTri[2].x = 0;
scrTri[2].y = img->height;//原图左下角
scrTri[1].x = img->width;
scrTri[1].y = 0;//原图右上角
dstTri[0].x = img->width * 0.0;
dstTri[0].y = img->height * 0.33;
dstTri[1].x = img->width * 0.85;
dstTri[1].y = img->height * 0.25;
dstTri[2].x = img->width * 0.15;
dstTri[2].y = img->height * 0.7;
cvGetAffineTransform(scrTri, dstTri, mymat);
cvWarpAffine(img, dst, mymat);
return dst;
}
IplImage* MyWarpAffine2(IplImage* img)
{//使用旋转角确定平行四边形的方法
IplImage* dst = cvCreateImage(cvGetSize(img), 8, 3);
CvMat* mymat = cvCreateMat(2, 3, CV_32FC1);
CvPoint2D32f center;
center.x = img->width / 2;
center.y = img->height / 2;
double angle = 45;
double scale = 0.5;
cv2DRotationMatrix(center, angle, scale, mymat);
cvWarpAffine(img, dst, mymat);
return dst;
}
注意:对于坐标确定法,坐标的顺序要是相互对应的,比如说src是左上、左下、右上则dst的顺序也必须是左上、左下、右上!!!
实验结果:
从《Learning openCV》一书了解到,因为开销问题,有一个更佳的仿射函数:
void cvGetQuadranglesubpix(
const CvArr* scr,
CvArr* dst,
const CvMat* map_matirx,
);
使用方法与上述函数类似,直接替代即可,这里不再赘述。
对于稀疏仿射变换,可以使用cvTransform(
const CvArr* scr,
CvArr* dst,
const CvMat* transmat,
const CvMat* shiftvec = NULL
);
函数
三、透视变换
和仿射变换相类似,不同的是,透视变换可以将一个平行四边形变成任意的梯形。这使得变换更加的灵活。因为很多参数及其使用都与仿射变换相同,这里只会简单介绍,并标明需要注意与仿射的区别。
openCV函数:
变换函数: void cvWarpPerspective(
const CvArr* scr,
CvArr* dst,
const CvMat* map_matirx,
int flags = CV_INTEAR | CV_WARP_FILL_OUTLIERS,
CvScalar fillval = cvScalarAll(0)
);
这里的变换矩阵维数是3*3的。
矩阵计算函数: CvMat* cvGetPerspectiveTransform(
const CvPoint2D32f* pts_src,
const CvPoint2D32f* pts_dst,
CvMat* map_matrix
);
这里与仿射不同的是,pts_src、pts_dst都是四个二维坐标点而不是三个!
关键函数编程如下:
IplImage* mywarpperspectiv(IplImage* img)
{
IplImage* dst = cvCreateImage(cvGetSize(img), 8, 3);
CvMat* mymat = cvCreateMat(3, 3, CV_32FC1);
CvPoint2D32f srcQuad[4], dstQuad[4];
srcQuad[0].x = 0;
srcQuad[0].y = 0;
srcQuad[1].x = img->width;
srcQuad[1].y = 0;
srcQuad[2].x = 0;
srcQuad[2].y = img->height;
srcQuad[3].x = img->width;
srcQuad[3].y = img->height;
dstQuad[0].x = img->width * 0.05;
dstQuad[0].y = img->height * 0.33;
dstQuad[1].x = img->width * 0.9;
dstQuad[1].y = img->height * 0.25;
dstQuad[2].x = img->width * 0.2;
dstQuad[2].y = img->height * 0.7;
dstQuad[3].x = img->width * 0.8;
dstQuad[3].y = img->height * 0.9;
cvGetPerspectiveTransform(srcQuad, dstQuad, mymat);
cvWarpPerspective(img, dst, mymat);
return dst;
}
同仿射,上述说明的是稠密透视变换的变换方法!