图像拼接(一):柱面投影+模板匹配+渐入渐出融合

时间:2022-02-26 06:20:35

这种拼接方法的假设前提是:待拼接的两幅图像之间的变换模型是平移模型,即两幅图像同名点位置之间只相差两个未知量: Δx Δy ,*度为2,模型收得最紧。所以只有所有图像都是用同一水平线或者同一已知倾斜角的摄像机拍摄时,这种方法才适用。
整个过程为:首先对输入的两幅图像做柱面投影;然后通过模板匹配求取 Δx Δy ;最后采用渐入渐出的融合方式拼接两幅图像。

柱面投影

为了保证拼接后的视觉一致性,所以需要将待拼接的图像分别投影到一个标准的坐标系下,然后再进行图像拼接。由于柱面坐标的变换比较简单并且投影图像与其投影到圆柱表面的位置无关,用其描述的柱面全景图像可在水平方向上满足360度环视,具有较好的视觉效果,所以可采用柱面投影完成图像拼接。
下面为柱面投影采用的公式, x y 为柱面投影后的图像坐标, x y 为图像原来的坐标, width height 为图像宽高, f 为相机焦距,我的理解是:因为 width height 都是在图像坐标系下,所以这个 f 是相对于图像的,我是根据图像大小以及视场角最做的估计。

x=fatan(x0.5widthf)+fatan(0.5widthf)
y=f(y0.5height)(x0.5width)2+f2+0.5height

下面是柱面校正的代码,事无巨细都是自己写的,因为没有找到上述公式的反演公式,直接正向插值了,好在出来的效果还不错。

/**柱面投影函数
*参数列表中imgIn为输入图像,f为焦距
*返回值为柱面投影后的图像
*/

Mat cylinder(Mat imgIn, int f)
{
int colNum, rowNum;
colNum = 2 * f*atan(0.5*imgIn.cols / f);//柱面图像宽
rowNum = 0.5*imgIn.rows*f / sqrt(pow(f, 2)) + 0.5*imgIn.rows;//柱面图像高

Mat imgOut = Mat::zeros(rowNum, colNum, CV_8UC1);
Mat_<uchar> im1(imgIn);
Mat_<uchar> im2(imgOut);

//正向插值
int x1(0), y1(0);
for (int i = 0; i < imgIn.rows; i++)
for (int j = 0; j < imgIn.cols; j++)
{
x1 = f*atan((j - 0.5*imgIn.cols) / f) + f*atan(0.5*imgIn.cols / f);
y1 = f*(i - 0.5*imgIn.rows) / sqrt(pow(j - 0.5*imgIn.cols, 2) + pow(f, 2)) + 0.5*imgIn.rows;
if (x1 >= 0 && x1 < colNum&&y1 >= 0 && y1<rowNum)
{
im2(y1, x1) = im1(i, j);
}
}
return imgOut;
}

模板匹配

通过模板匹配的方法求取平移变换参数

/**求平移量
*参数表为输入两幅图像(有一定重叠区域)
*返回值为点类型,存储x,y方向的偏移量
*/

Point2i getOffset(Mat img, Mat img1)
{
Mat templ(img1, Rect(0, 0.4*img1.rows, 0.2*img1.cols, 0.2*img1.rows));
Mat result(img.cols - templ.cols + 1, img.rows - templ.rows + 1, CV_8UC1);//result存放匹配位置信息
matchTemplate(img, templ, result, CV_TM_CCORR_NORMED);
normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc;
minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
matchLoc = maxLoc;//获得最佳匹配位置
int dx = matchLoc.x;
int dy = matchLoc.y - 0.4*img1.rows;//右图像相对左图像的位移
Point2i a(dx, dy);
return a;
}

线性融合

采用渐入渐出融合,其实就是在重叠区域,对两幅图像的像素,线性地分配权值。公式: img=dimg1+(1d)img2 ;其中img为融合后的图像,img1和img2为待拼接的两幅图像。d为重叠区域中某个像素点到边界的距离。

/*渐入渐出拼接
*参数列表中,img1,img2为待拼接的两幅图像,a为偏移量
*返回值为拼接后的图像
*/

Mat linearStitch(Mat img, Mat img1, Point2i a)
{
int d = img.cols - a.x;//过渡区宽度
int ms = img.rows - abs(a.y);//拼接图行数
int ns = img.cols + a.x;//拼接图列数
Mat stitch = Mat::zeros(ms, ns, CV_8UC1);
//拼接
Mat_<uchar> ims(stitch);
Mat_<uchar> im(img);
Mat_<uchar> im1(img1);

if (a.y >= 0)
{
Mat roi1(stitch, Rect(0, 0, a.x, ms));
img(Range(a.y, img.rows), Range(0, a.x)).copyTo(roi1);
Mat roi2(stitch, Rect(img.cols, 0, a.x, ms));
img1(Range(0, ms), Range(d, img1.cols)).copyTo(roi2);
for (int i = 0; i < ms; i++)
for (int j = a.x; j < img.cols; j++)
ims(i, j) = uchar((img.cols - j) / float(d)*im(i + a.y, j) + (j - a.x) / float(d)*im1(i, j - a.x));

}
else
{
Mat roi1(stitch, Rect(0, 0, a.x, ms));
img(Range(0, ms), Range(0, a.x)).copyTo(roi1);
Mat roi2(stitch, Rect(img.cols, 0, a.x, ms));
img1(Range(-a.y, img.rows), Range(d, img1.cols)).copyTo(roi2);
for (int i = 0; i < ms; i++)
for (int j = a.x; j < img.cols; j++)
ims(i, j) = uchar((img.cols - j) / float(d)*im(i, j) + (j - a.x) / float(d)*im1(i + abs(a.y), j - a.x));
}


return stitch;
}

实验效果

写一个包含主函数的文件调用上述方法:

#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include<iostream>
#include<time.h>

int main()
{
Mat img = imread("frame1.jpg", 0);//左图像
Mat img1 = imread("frame2.jpg", 0);//右图像
imshow("源图像-左", img);
imshow("源图像-右", img1);
double t = (double)getTickCount();
//柱形投影
double t3 = (double)getTickCount();
img = cylinder(img,1000);
img1 = cylinder(img1, 1000);
t3 = ((double)getTickCount() - t3) / getTickFrequency();
//匹配
double t1 = (double)getTickCount();
Point2i a = getOffset(img, img1);
t1 = ((double)getTickCount() - t1) / getTickFrequency();
//拼接
double t2 = (double)getTickCount();
Mat stitch = linearStitch(img, img1, a);
t2 = ((double)getTickCount() - t2) / getTickFrequency();
t = ((double)getTickCount() - t) / getTickFrequency();

cout << "各阶段耗时:"<< endl;
cout << "柱面投影:" << t3 << '\n' << "模板匹配:" << t1 << '\n' << "渐入渐出拼接:" << t2 << endl;
cout << "总时间:" << t << endl;

imshow("柱面校正-左图像", img);
imshow("柱面校正-右图像", img1);
imshow("拼接结果", stitch);
imwrite("rectify.jpg", img);
imwrite("rectify1.jpg", img1);
imwrite("stitch.jpg", stitch);
waitKey(0);
return 0;
}

首先读取两幅源图像:
图像拼接(一):柱面投影+模板匹配+渐入渐出融合

图像拼接(一):柱面投影+模板匹配+渐入渐出融合

柱面投影之后:
图像拼接(一):柱面投影+模板匹配+渐入渐出融合

图像拼接(一):柱面投影+模板匹配+渐入渐出融合
最后的拼接结果:
图像拼接(一):柱面投影+模板匹配+渐入渐出融合
拼接时间效率:
测试环境为Intel(R) Core(TM) i3-2350M CPU @ 2.3GHz;Win 10+VS2013;源图像像素分辨率为640*480。
图像拼接(一):柱面投影+模板匹配+渐入渐出融合

可以看出整体效果还行,但细节很差,比如重叠区域的墙上电线出现了重影。时间效率方面,自己写的柱面投影函数耗时明显,有待优化。