很多情況下,使用一個全局單應變換並不能準確對齊圖像,需要一些後處理來削弱拼接的痕跡,比如尋找最佳拼接縫。
使用全局單應變換的對齊結果,實現代碼參考圖像拼接(六):OpenCV單應變換模型拼接兩幅圖像:
仔細觀察,在拼縫的下方出現了沒對齊的問題。
尋找最佳拼接縫算法中,Graph Cut很經典。它將計算機視覺問題和網絡流聯繫在一起。尋找最佳拼接縫等價於求網絡流的最小割。
在網絡流問題中,最小割和最大流相等。這些概念可能會使你困惑,瞭解一些概念,可參考百度文庫裏圖文並茂的PPT-最大流問題
不瞭解這些這些概念也沒關係,因爲在這裏我也沒打算自己造輪子實現。
OpenCV stitching模塊裏有相關的函數,實現裏基於最小圖割的最佳拼接縫尋找算法。官方文檔見這裏:cv::detail::GraphCutSeamFinder Class Reference
void cv::detail::GraphCutSeamFinder::find ( const std::vector< UMat > & src,
const std::vector< Point > & corners,
std::vector< UMat > & masks
)
具體怎麼操作呢?
先輸入兩幅用全局單應變換配準後的圖像,要統一座標系。在這裏圖像寬爲原始圖像的兩倍。
left
right
find
函數第一個參數是輸入源圖像集合;第二個參數corners
的是圖像左上角座標的集合;第三個參數會返回更新的拼接縫掩碼。
按這種思路,兩幅圖像左上角都是(0,0),輸入圖像完全是重合的。但測試發現,這樣使用函數並不能返回預期的結果。個人猜測,可能是因爲完全重合,算法找不到分割的起點和終點,或者是因爲圖像中有大面積的黑色。
最佳的使用情形是輸入圖像間有部分重合區域。
既然這樣,先剪切,需要注意不要忘記圖像的配準信息。
從中間剪,其實是原圖。
從1/4和3/4位置剪,(隱含了圖像間有一半重複的假設)
這樣,左圖的corner是(0,0),右圖的corner是(0.5*width,0)。
使用find
函數返回得到表現拼接縫位置的掩碼:
左圖掩碼:
右圖掩碼:
咦?爲啥拼接縫這麼豎直?不清楚內部原理的我,內心也有些許忐忑,擔心效果。
最後的效果說明結果是合理的,根據掩碼拼合圖像:
以上對函數的說明,屬於個人理解,如有錯誤,多謝指正幫助。
代碼:
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/stitching/detail/seam_finders.hpp>
#include<iostream>
using namespace cv;
using namespace cv::detail;
int main()
{
//統一座標下的兩幅圖
// 此例中canvas.width=2*src.width
Mat canvas1 = imread("left.jpg");
Mat canvas2 = imread("right.jpg");
//將兩幅圖剪切出來,剪切位置包含了配準(兩幅圖像的相對位置)信息
Mat image1 = canvas1(Range::all(), Range(0, canvas1.cols/2));
Mat image2 = canvas2(Range::all(), Range(canvas2.cols/4, canvas2.cols*3/4));//假設大概1/2重複區域
image1.convertTo(image1, CV_32FC3);
image2.convertTo(image2, CV_32FC3);
image1 /= 255.0;
image2 /= 255.0;
//在找拼縫的操作中,爲了減少計算量,用image_small
Mat image1_small;
Mat image2_small;
Size small_size1 = Size(image1.cols / 2, image1.rows / 2);
Size small_size2 = Size(image2.cols / 2, image2.rows / 2);
resize(image1, image1_small, small_size1);
resize(image2, image2_small, small_size2);
// 左圖的左上角座標
cv::Point corner1;
corner1.x = 0;
corner1.y = 0;
//右圖的左上角座標
cv::Point corner2;
corner2.x = image2_small.cols/2;
corner2.y = 0;
std::vector<cv::Point> corners;
corners.push_back(corner1);
corners.push_back(corner2);
std::vector<cv::Mat> masks;
Mat imageMask1(small_size1, CV_8U);
Mat imageMask2(small_size2, CV_8U);
imageMask1 = Scalar::all(255);
imageMask2 = Scalar::all(255);
masks.push_back(imageMask1);
masks.push_back(imageMask2);
std::vector<cv::Mat> sources;
sources.push_back(image1_small);
sources.push_back(image2_small);
Ptr<SeamFinder> seam_finder = new cv::detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR);
seam_finder->find(sources, corners, masks);
//將mask恢復放大
resize(masks[0], imageMask1, image1.size());
resize(masks[1], imageMask2, image2.size());
Mat canvas(image1.rows,image1.cols*3/2,CV_32FC3);
image1.copyTo(canvas(Range::all(), Range(0, canvas.cols*2/3)), imageMask1);
image2.copyTo(canvas(Range::all(), Range(canvas.cols / 3, canvas.cols)), imageMask2);
/*canvas *= 255;
canvas.convertTo(canvas, CV_8UC3);*/
imshow("canvas",canvas);
imshow("Mask1",masks[0]);
imshow("Mask2", masks[1]);
imshow("src1", sources[0]);
imshow("src2", sources[1]);
waitKey(0);
return 0;
}