4、图像投影变换
4.1 原理
前文我们已经说过,每幅图像是相机在不同角度下拍摄得到的,它们并不在同一个投影平面上,如果对重叠部分直接进行拼接,则会破坏实际场景的视觉一致性。所以我们需要在拼接之前,对图像进行投影变换,即对图像进行扭曲变形。
设图像中某像素点的二维坐标为(x, y),它所对应的世界坐标为(X, Y, Z),两者之间的关系为:
(70)
式中,R为旋转矩阵,K为相机的内参数矩阵。像素点可以映射到不同的表面上,最简单的是映射到平面上,设(u, v)为映射后的二维坐标,则
(71)
如果世界坐标有平移,并且投影图像有尺度的变化,则式71改写为:
(72)
式中,s表示尺度,与相机焦距成正比,t1、t2和t3表示三个坐标轴的位移。
上面的变换是由源图像变换到投影图像上,即由(x, y)映射为(u, v),我们称为正向投影。如果是由投影图像变换为源图像,我们称为反向投影。反向投影的公式为:
(73)
(74)
平面投影简单,但拼接图像较多时,视觉效果并不好。另一种常见的投影方式是柱面投影。柱面投影是以相机为圆柱中心点,相机焦距为半径的一个柱面作为投影面。它的投影图像与投影到的圆柱表面的位置无关,柱面全景图像可在水平方向上满足360度环视,具有较好的视觉效果,并且柱面投影也符合我们对相机位置的假设(相机只做旋转动作)。柱面投影后的坐标为:
(75)
柱面投影的反向映射关系为:
(76)
由(X, Y, Z)得到(x, y)的公式也是式74。
球面投影是将图像序列投影到以一点为坐标中心的球面上。人的眼睛在看东西时的原理就类似于球面投影,因此,以视点为中心的球面投影模型是最自然的投影模型。但是球面投影模型也存在着一些缺点,比如球面上的像素点不是行列均匀排列的关系,球面不能展开成平面,这些都使得很多图像处理算法很难用在平面投影上。球面投影的正向映射为:
(77)
球面投影的反向映射为:
(78)
立方体投影是为了克服球形投影缺点而提出的投影模型。这种投影模型的优点是方便计算机处理与储存图像。立方体投影的正向映射为:
(79)
立方体投影的反向映射为:
(80)
鱼眼投影图像具有较大的视角,非常适用于导航、监视和检测等方面。它的正向映射为:
(81)
鱼眼投影的反向映射为:
(82)
在图像拼接过程中,我们首先需要把图像进行正向映射,又因为最终图像还是要在平面上进行展示,所以还是需要再进行反向映射。最终被映射到的平面就是全景图像所在的平面,这是因为在上一步,我们已经通过最大生成树得到了基准图像,相机的内参数都是基于该基准图像的,所以所有的图像最终都映射到了该基准图像所在的平面上,这样就构成了一幅全景图像。
4.2 源码
RotationWarper类是只处理因旋转而引起的图像扭曲的接口类,它是RotationWarperBase类的基类:
template <class P>
class CV_EXPORTS RotationWarperBase : public RotationWarper
{
public:
//表示投影图像的像素点,pt为源像素点,它通过P.mapForward函数得到投影点(该函数的返回值),K为相机的内参数,R为相机的旋转矩阵,通过P.setCameraParams函数设置
Point2f warpPoint(const Point2f &pt, const Mat &K, const Mat &R);
//由给定的相机数据建立投影关系,src_size为源图像区域,xmap和ymap是分别表示坐标值由两次映射的值,该函数返回投影图区域
Rect buildMaps(Size src_size, const Mat &K, const Mat &R, Mat &xmap, Mat &ymap);
//表示由源图src经buildMaps函数得到投影图像dst,interp_mode和border_mode分别表示投影时用到的插值算法和边界扩展方法,该函数返回dst在最终的全景图像投影后的左上角坐标
Point warp(const Mat &src, const Mat &K, const Mat &R, int interp_mode, int border_mode,
Mat &dst);
//与buildMaps函数相类似,只不过该函数使用的是P.mapForward函数
void warpBackward(const Mat &src, const Mat &K, const Mat &R, int interp_mode, int border_mode,
Size dst_size, Mat &dst);
//表示确定扭曲图像区域
Rect warpRoi(Size src_size, const Mat &K, const Mat &R);
float getScale() const { return projector_.scale; } //得到尺度
void setScale(float val) { projector_.scale = val; } //设置尺度
protected:
// Detects ROI of the destination image. It's correct for any projection.
//该虚函数用于得到目标图像的区域
virtual void detectResultRoi(Size src_size, Point &dst_tl, Point &dst_br);
// Detects ROI of the destination image by walking over image border.
// Correctness for any projection isn't guaranteed.
//该函数仅由源图像的边界得到目标图像的区域
void detectResultRoiByBorder(Size src_size, Point &dst_tl, Point &dst_br);
P projector_; //表示投影的方法
};
下面我们给出RotationWarperBase类中主要函数的介绍:
template <class P>
Point2f RotationWarperBase<P>::warpPoint(const Point2f &pt, const Mat &K, const Mat &R)
//pt表示投射的源点
//K表示相机的内参数
//R表示相机的旋转参数
//该函数返回投射点
{
projector_.setCameraParams(K,