很多情况下,使用一个全局单应变换并不能准确对齐图像,需要一些后处理来削弱拼接的痕迹,比如寻找最佳拼接缝。
使用全局单应变换的对齐结果,实现代码参考图像拼接(六):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
)
具体怎么操作呢?
先输入两幅用全局单应变换配准后的图像,要统一坐标系。在这里图像宽为原始图像的两倍。
find
函数第一个参数是输入源图像集合;第二个参数corners
的是图像左上角坐标的集合;第三个参数会返回更新的拼接缝掩码。
按这种思路,两幅图像左上角都是(0,0),输入图像完全是重合的。但测试发现,这样使用函数并不能返回预期的结果。个人猜测,可能是因为完全重合,算法找不到分割的起点和终点,或者是因为图像中有大面积的黑色。
最佳的使用情形是输入图像间有部分重合区域。
既然这样,先剪切,需要注意不要忘记图像的配准信息。
这样,左图的corner是(0,0),右图的corner是(0.5*width,0)。
使用find
函数返回得到表现拼接缝位置的掩码:
咦?为啥拼接缝这么竖直?不清楚内部原理的我,内心也有些许忐忑,担心效果。
最后的效果说明结果是合理的,根据掩码拼合图像:
以上对函数的说明,属于个人理解,如有错误,多谢指正帮助。
代码:
#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;
}