opencv-第六章-图像变换
重映射
在幕后,许多变换都有一个共同点。具体来说,它们会把一幅图像中一个位置的像素重映射到另一个位置。在这种情况下,就始终需要一些平滑的映射(我们希望实现的),但并不是总能做到像素一一对应。
cvRemap
对图像进行普通几何变换
void cvRemap( const CvArr* src, CvArr* dst,
const CvArr* mapx, const CvArr* mapy,
int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,
CvScalar fillval=cvScalarAll(0) );
src
输入图像.
dst
输出图像.
mapx
x坐标的映射 (32fC1 image).
mapy
y坐标的映射 (32fC1 image).
flags
插值方法和以下开关选项的组合:
CV_WARP_FILL_OUTLIERS - 填充边界外的像素. 如果输出图像的部分象素落在变换后的边界外,那么它们的值设定为 fillval。
fillval
用来填充边界外面的值.
函数 cvRemap 利用下面指定的矩阵变换输入图像:
dst(x,y)<-src(mapx(x,y),mapy(x,y))
与其它几何变换类似,可以使用一些插值方法(由用户指定,同cvResize)来计算非整数坐标的像素值。
cvWarpAffine附加标志位的值
flags:
CV_INTER_NN 最近邻
CV_INTER_LINEAR 双线性(默认)
CV_INTER_AREA 像素区域重新采样
CV_INTER_CUBIC 双三次插值
拉伸、收缩、扭曲和旋转
这些拉伸、扭曲、旋转图像的函数叫做几何转换函数。对于平面区域。有两种方式的几何转换:一种是基于2x3矩阵进行的变换,也叫仿射变换;另一种是基于3x3矩阵的变换,又叫做透视变换或者单应性映射。可以把后一种变换当做一个三维平面被一个特定观察者感知的计算方法,而该观察者也许不是垂直观测该平面。
仿射变换可以将矩形转换成平行四边形。它可以将矩形的边压扁但必须保持边是平行的,也可以将矩形旋转或者按比例变化。透视变换提供了更大的灵活性,一个透视变换可以将矩形变换成梯形。当然,因为平行四边形也是梯形,所以仿射变换是透视变换的子集。
仿射变换
有两种情况会用到仿射变换。第一种是有一幅图像想要转换的图像(或者感兴趣的区域),第二种是我们有一个点序列并想以此计算出变换。
稠密仿射变换
在第一种情况下,很明显输入和输出的格式是图像,并且隐含的要求是扭曲假设对于所使用的图像,其像素必须是其稠密的表现形式。这意味着图像扭曲必须进行一些插值运算以使输出的图像平滑并且看起来自然一些。opencv为稠密变换提供的转换函数是cvWarpAffine。
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) //用来填充边界外的值
);
来看一下flags参数,是控制插值的方法以及下面一个或两个附加选项(通常用布尔或操作来组合)。
CV_WARP_FILL_OUTLIERS-通常,变换的src图像不能喝dst图像完美匹配-从源图像映射的像素可能实际上并不存在。如果设置了标志位,这些失去的值就会由fillval补充。
CV_WARP_INVERSE_MAP-这个标志位用于方便地进行从dst到src的逆向变形,而不是从src到dst变换。
cvWarpAffine涉及开销问题。另一种方法是利用cvGetQuadrangleSubPix,这个函数的选项更少但优点更多。尤其是,它的开销比较小而且可以处理源图像是8位而目标图像是32位浮点图像的特殊情况。同时它能处理多通道图像。
void cvGetQuadrangleSubPix(
const CvArr* src, //输入图像
CvArr* dst, // 提取的四边形
const CvMat* map_matrix //2*3的变换矩阵
);
cvGetQuadrangleSubPix所做的就是计算从src图像的点(通过插值)映射到dst图像上的所有的点,这个映射是通过仿射变换即乘一个2x3矩阵实现的。
仿射映射矩阵的计算
opencv提供了两个函数以帮助生成映射矩阵map_matrix。如果已有两幅图像要通过仿射变换发生关联或希望以那种方式来逼近则可以使用第一个函数如下所示:
CvMat* cvGetAffineTransform(const CvPoint2D32f* pts_src,
const CvPoint2D32f* pos_dst,CvMat* map_matrix);
这里src和dst是包含三个二维点(x,y)的数组,map_matrix所表示的仿射变换就是通过这些点来计算。
cvGetAffineTransform()中的pts_src和pts_dst是其中包含三个点的数组,它们定义了两个平行四边形。描述仿射变换的简单方法是把pts_src设置为原图像的三个角-例如,原图像的左上角和左下角以及右上角。从源图像到目标图像的映射完全由特定pts_dst定义,这三个点的位置将会被映射到目标图像。一旦这三个独立点(实际上是指定一个“有代表性”的平行四边形)的映射完成,所有其他点会一次变形。
稀疏仿射变换
cvWarpAffine是解决密集映射的正确方法。对于稀疏映射(例如,对一系列独立点的映射),最好的办法是用cvTransform():
void cvTransform(const CvArr* src,CvArr* dst,
const CvMat* transmat,const CvMat* shiftvec=NULL);
一般情况下,src是Ds通道,Nx1的数组,N是将要转换的点的数量,Ds是这些源图像点的维数。输出的目标数组dst必须相同大小但可以有不同的通道数Dd。
转换矩阵transmat是一个DsxDd的矩阵,被应用到源图像的每一个元素,结果被置入dst中。可选向量shiftvec,如果非空,则必须是一个Dsx1的数组,在结果被置入目标图像之前将其加到dst中。
透视变换
为了获得透视变换(单应性)所提供的更多灵活性,我们需要一个新的函数,使之能实现广泛意义上的变换。首先我们注意到,即使一个透视投影由一个单独矩阵完全确定,但这个投影实际上并不是线性变换。这是因为变换需要与最后一维(通常是Z)元素相除,因此在处理或运算中会减少一维。
与仿射变换一样,图像操作(密集变换)是由不同的函数来处理的相较于点集变换(稀疏变换)所用的函数,而不是通过对点集的变换(稀疏变换)。
密集透视变换
密集透视变换用到的opencv函数与提供的密集仿射变换是类似的。特别地,cvWarpPerspective的参数与cvWarpAffine相同,但是有一个小的但很重要的区别是所采用的映射矩阵必须是3x3的。
void cvWarpPerspective(const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,CvScalar fillval=cvScalarAll(0));
这里的flags与仿射情形是一样的。
计算透视映射矩阵
对于仿射变换,在前面的代码里,针对在代码执行期间填充map_matrix,我们有一个方便的函数,可以通过对应点列表计算变换矩阵:
CvMat* cvGetPerspectiveTransform(const CvPoint2D32f* pts_src,
const CvPoint2D32f* pts_dst,CvMat* map_matrix);
这里pts_src和pts_dst是四个点的数组(而不是三个),所以我们能独立地控制如何将pts_src中的矩形(典型的)四个角映射到pts_dst中的普通棱形上。我们的变换完全由源图像上的四个点所指定的目标定义。如前所述,对透视变换,我们必须为map_matrix分配一个2x3数组。
稀疏透视变换
这里有一个特殊函数cvPerspectiveTransform(),在一系列的点上完成透视变换,我们不能用cvTransform(),因为它只局限于线性操作,因为如此,它不能处理透视变换因为透视变换需要齐次表达式的第三坐标来除各项(x=f*X/Z,y=f*Y/Z)。这个特殊的函数cvPerspectiveTransform()需要引起注意。
void cvPerspectiveTransform(const CvArr* src,CvArr* dst,const CvMat* mat);
通常,src和dst参数分别是被变换的原始点数组以及目标点数组。这些数组必须是三通道、浮点类型的。矩阵mat既可以是3x3,也可以是4x4的。如果为3x3,便投影从2维变成2维;如果是4x4的,投影就是从4维变成3维。
当前,我们将一幅图像中的点集转换成另一幅图像的点集,听起来类似于将两维映射到另外两维。但这并不是很准确的,因为透视变换实际上是一个嵌入在三维空间的二维平面上的实际映射点映射回一个不同的二维子空间。把这个情况想象为摄像机的行为。摄像机得到三维空间的点,然后利用摄像机成像仪将其映射到二维空间。这实际上是当原始点必须采取“齐次坐标”时的意思。我们为这些点增加额外的一维,即引入了Z维,并将所有Z的值设为1.投影变换然后将该空间反投影到我们输出的二维空间。这里我们用相当大的篇幅解析了当一个点从一幅图像映射到另一幅图像,为何需要一个3x3矩阵的原因。