圖像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接縫尋找

很多情況下,使用一個全局單應變換並不能準確對齊圖像,需要一些後處理來削弱拼接的痕跡,比如尋找最佳拼接縫。

使用全局單應變換的對齊結果,實現代碼參考圖像拼接(六):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;
}
發佈了75 篇原創文章 · 獲贊 127 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章