這種拼接方法的假設前提是:待拼接的兩幅圖像之間的變換模型是平移模型,即兩幅圖像同名點位置之間只相差兩個未知量:ΔxΔx 和ΔyΔy,自由度為2,模型收得最緊。所以只有所有圖像都是用同一水平線或者同一已知傾斜角的攝像機拍攝時,這種方法才適用。?
整個過程為:首先對輸入的兩幅圖像做柱面投影;然后通過模板匹配求取ΔxΔx 和ΔyΔy;最后采用漸入漸出的融合方式拼接兩幅圖像。
柱面投影
為了保證拼接后的視覺一致性,所以需要將待拼接的圖像分別投影到一個標準的坐標系下,然后再進行圖像拼接。由于柱面坐標的變換比較簡單并且投影圖像與其投影到圓柱表面的位置無關,用其描述的柱面全景圖像可在水平方向上滿足360度環視,具有較好的視覺效果,所以可采用柱面投影完成圖像拼接。?
下面為柱面投影采用的公式,x′x′和y′y′為柱面投影后的圖像坐標,xx和yy為圖像原來的坐標,widthwidth和heightheight為圖像寬高,ff為相機焦距,我的理解是:因為widthwidth和heightheight都是在圖像坐標系下,所以這個ff是相對于圖像的,我是根據圖像大小以及視場角最做的估計。?
x′=f?atan(x?0.5?widthf)+f?atan(0.5?widthf)
x′=f?atan(x?0.5?widthf)+f?atan(0.5?widthf)
y′=f?(y?0.5?height)(x?0.5?width)2+f2??????????????????√+0.5?height
y′=f?(y?0.5?height)(x?0.5?width)2+f2+0.5?height
下面是柱面校正的代碼,事無巨細都是自己寫的,因為沒有找到上述公式的反演公式,直接正向插值了,好在出來的效果還不錯。
/**柱面投影函數
?*參數列表中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=d?img1+(1?d)?img2img=d?img1+(1?d)?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。?
可以看出整體效果還行,但細節很差,比如重疊區域的墻上電線出現了重影。時間效率方面,自己寫的柱面投影函數耗時明顯,有待優化
---------------------?
作者:czl389?
來源:CSDN?
原文:https://blog.csdn.net/czl389/article/details/54599253?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!