图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找

很多情况下,使用一个全局单应变换并不能准确对齐图像,需要一些后处理来削弱拼接的痕迹,比如寻找最佳拼接缝。

使用全局单应变换的对齐结果,实现代码参考图像拼接(六):OpenCV单应变换模型拼接两幅图像:

图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第1张图片

仔细观察,在拼缝的下方出现了没对齐的问题。

寻找最佳拼接缝算法中,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
图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第2张图片

right
图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第3张图片

find函数第一个参数是输入源图像集合;第二个参数corners的是图像左上角坐标的集合;第三个参数会返回更新的拼接缝掩码。

按这种思路,两幅图像左上角都是(0,0),输入图像完全是重合的。但测试发现,这样使用函数并不能返回预期的结果。个人猜测,可能是因为完全重合,算法找不到分割的起点和终点,或者是因为图像中有大面积的黑色。

最佳的使用情形是输入图像间有部分重合区域。

既然这样,先剪切,需要注意不要忘记图像的配准信息。

从中间剪,其实是原图。
图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第4张图片

从1/4和3/4位置剪,(隐含了图像间有一半重复的假设)
图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第5张图片

这样,左图的corner是(0,0),右图的corner是(0.5*width,0)。

使用find函数返回得到表现拼接缝位置的掩码:

左图掩码:
图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第6张图片

右图掩码:
图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第7张图片

咦?为啥拼接缝这么竖直?不清楚内部原理的我,内心也有些许忐忑,担心效果。

最后的效果说明结果是合理的,根据掩码拼合图像:

图像拼接(十二):OpenCV SeamFinder+GraphCut+最佳拼接缝寻找_第8张图片

以上对函数的说明,属于个人理解,如有错误,多谢指正帮助。

代码:

#include 
#include 
#include 
#include

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 corners;

	corners.push_back(corner1);
	corners.push_back(corner2);

	std::vector 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 sources;

	sources.push_back(image1_small);
	sources.push_back(image2_small);

	Ptr 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;
}

你可能感兴趣的:(图像处理与机器视觉,图像拼接)