还是看OpenCV官方手册,我觉得这样可以同时学习如何使用函数和如何理解一些基本概念。
首先,这里的几何变换geometrical transformations是针对2D图像而言的,不改变图像内容而是将像素网格变形deform the pixel grid,映射到目标图像destination image。目标图像不是由源图像直接得到,因为数字图像是离散的,所以源图像的像素点的映射无法保证落在方格之内,所以在图像处理中采用的都是逆向法,即对目标图像的每一个像素点dst(x,y),在源图像中寻找对dst(x,y)产生贡献的点,这些点的值将直接决定dst(x,y)的值。在寻找的过程,就是反向映射inverse mapping 〈fx,fy〉的过程。这是寻找映射点的通用公式:
有两个问题是需要仔细考虑的:
外插Extrapolation,这是对于不存在的点而言的,因为逆向映射fx(x,y)或者fy(x,y)可能会映射到源图像之外,就像滤波函数一样,OpenCV提供了外插的方法,此外,还提供了BORDER_TRANSPARENT.意味着不会对目标图像中的像素进行修改。
内插,反向映射如果是仿射或者透视变换affine or perspective transformation,或者径向透镜畸变校正radial lens distortion correction,找到的点坐标fx(x,y)或者fy(x,y)通常是浮点数floating-point numbers,而我们应该找到像素的分数坐标?fractional coordinates,最简单的是找到距离浮点数坐标最近的点,即最近邻内插,当然有更复杂的内插方法可以使用浮点数坐标的邻点进行插值,可以得到更好的效果。此外,OpenCV宏定义的其他内插方法有INTER_LINEAR、INTER_CUBIC、INTER_AREA、INTER_LANCZOS4等。
其中INTER_AREA 使用像素区域的关系。当图像下采样decimation(维基百科中downsampling and decimation是一个词条)时效果较好,因为不会产生摩尔纹moire'-free,但是当图像放大zoomed时,效果和最近邻方法相似。下图就是使用不同内插方法的放大效果,可以看到区域内插(第三行)和最近邻内插(第一行)效果近似。
INTER_LANCZOS4 使用了8x8 neighborhood
下面看几个OpenCV中的图像变换函数
Remap就是一个映射函数
void cv::remap ( InputArray
src,
OutputArray
dst,
InputArray
map1,
InputArray
map2,
int interpolation,
int borderMode = BORDER_CONSTANT,
const Scalar &
borderValue = Scalar()
)
Mapx和mapy可以以浮点数floating-point的形式存放在map1和map2中,也可以交叉存储的形式都放在map1中。为了加快remapping速度(yield much faster (2x)),也可以使用convertMaps转换成更加紧凑more compact的fixed-point(可以理解为整型)的map,这样map1中存放pairs (cvFloor(x), cvFloor(y)),map2中存放插值系数表table of interpolation coefficients.
浮点数是表示小数的一种方法。所谓浮点就是小数点的位置不固定,浮点数由尾数和基数两部分组成,X = M * 2^E。与此相反有定点数,即小数点的位置固定。整数可以看做是一种特殊的定点数,即小数点在末尾。这里所说的“小数点”是一个隐式的小数点,并不是我们通常说的小数点。定点DSP处理一个浮点数一般需要两个定点运算器(一个处理尾数、一个处理基数)。定点DSP下,浮点运算比定点运算慢几个数量级。http://blog.sina.com.cn/s/blog_ebbe6d790102uwap.html
Remap将两幅图像之间的所有像素点的映射关系都通过映射图表现出来,所以适用于源图像和目标图像大小一样的左右镜像,上下镜像(翻转)等简单的规则变换。
#include<stdafx.h>
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
Mat srcImage = imread("D:\\b.jpg");
Mat dstImage, map_x, map_y;
imshow("", srcImage);
//创建和原始图像一样的效果图,x重映射图,y重映射图
dstImage.create(srcImage.size(), srcImage.type());
map_x.create(srcImage.size(), CV_32FC1);
map_y.create(srcImage.size(), CV_32FC1);
//双层循环,遍历每一个像素点,改变map_x和map_y的值
for (int j = 0; j<srcImage.rows; j++)
{
for (int i = 0; i<srcImage.cols; i++)
{
//改变map_x和map_y的值
map_x.at<float>(j, i) = static_cast<float>(i );//y坐标在前image.at(x1, x2)=image.at(Point(x2, x1))
//map_x.at<float>(j, i) = static_cast<float>(srcImage.rows - i);
map_y.at<float>(j, i) = static_cast<float>(srcImage.rows - j);
}
}
//进行重映射操作
remap(srcImage, dstImage, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(255, 0, 0));
//显示效果图
imshow("效果图", dstImage);
waitKey(0);
return 0;
}
仿射变换warpAffine
映射回源图像的x,y坐标都由目标图像的x,y和矩阵M中的元素决定。矩阵M是2x3的。这个矩阵在已知映射前后对应点对的坐标时也可以求到,使用函数getAffineTransform。
比仿射变换更复杂的是透视变换warpPerspective
函数是warpPerspective,变换矩阵M大小是3x3.可以由函数getPerspectiveTransform 计算变换矩阵
消除镜头失真undistort
这个函数是两个函数的结合a combination of cv::initUndistortRectifyMap (with unity R ) and cv::remap (with bilinear interpolation),前一个计算出失真并且以map的形式给出,第二个remap通过映射表将像素点进行映射。这个函数的主要参数有:
cameraMatrix,
distCoeffs,失真系数组成的数组vector,如果 vector 是 NULL/empty, 就假设0失真
newCameraMatrix,单目相机中,这个参数通常和cameraMatrix相等,立体相机中,通过函数cv::stereoRectify归一化到P1或者P2. 新的不同的相机在坐标空间coordinate space的方向需要根据R进行不同的调整。
R,物体空间object space中的光学矫正变换Optional rectification transformation。R1,R2,通过函数stereoRectify计算得到,这是一个3x3的矩阵,如果矩阵为空,则认为两个镜头的变换是一致的。
无失真的图像相当于是使用相机矩阵为newCameraMatrix并且失真系数为0 camera matrix =newCameraMatrix and zero distortion的相机得到的。
更多相机矫正和三维重建https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html