图像拼接(七):OpenCV单应变换模型拼接多幅图像

上篇博客图像拼接(六):OpenCV单应变换模型拼接两幅图像 实现了两幅图像的拼接,主要是使用了单应矩阵和warpPerspective()这个库函数。


#求取每相邻两幅图像的单应矩阵
拼接多幅图像,需要计算每相邻两幅图像的单应矩阵,上篇已经封装了求取单应矩阵的类,可以拿来用。
现有4幅图像: i m g 1 img1 img1 i m g 2 img2 img2 i m g 3 img3 img3 i m g 4 img4 img4。依次从右向左排列,拼接图像以最左侧的 i m g 4 img4 img4为参考图像。

Homography homo12(img1,img2);
Homography homo23(img2, img3);
Homography homo34(img3, img4);

Mat h12 = homo12.getHomography();
Mat h23 = homo23.getHomography();
Mat h34 = homo34.getHomography();

#透视变换

warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize)

这个函数却显得不够灵活。一方面,目标投影的图像不能够选取兴趣区域,使得每拼接两幅都会产生一个结果图像;另一方面,投影参考原点为图像左上角,如果投影后的图像在左方,就不能显示出,所以需要左侧的图像为参考图像。

为了实现方便,拼接4幅图像以最左侧图像作为参考(其实这种方案会产生累加误差和图像变形,最佳的方案应该是选择中间幅作为参考基准),计算其余3幅图像到它的变换。

	Mat h24 = h34*h23;
	Mat h14 = h24*h12;

	float scale_h24 = h24.at<double>(2, 2);
	float scale_h14 = h14.at<double>(2, 2);
	h24 = h24 / scale_h24;
	h14 = h14 / scale_h14;

	Mat warp1;
	warpPerspective(img1_color, warp1, h14, Size(img1.cols * 4, img1.rows));
	Mat warp2;
	warpPerspective(img2_color, warp2, h24, Size(img1.cols * 4, img1.rows));
	Mat warp3;
	warpPerspective(img3_color, warp3, h34, Size(img1.cols * 4, img1.rows));

投影结果:

img3->img4
这里写图片描述

img2->img4
这里写图片描述

img1->img4
这里写图片描述

再把上述中间结果放在一个最终的结果图像中。怎么自动地确定水平方向剪切的边界呢?我的一个解决思路是:根据单应矩阵的8个参数,提取出水平位移量。将单应变换公式展开

$\begin{bmatrix} x^\prime \ y^\prime \1 \end{bmatrix} =\begin{bmatrix} 1+h_{00} &h_{01} & h_{02} \ h_{10} & 1+h_{11}&h_{12} \h_{20}&h_{21}&1 \end{bmatrix} \begin{bmatrix} x \ y \1 \end{bmatrix} $

x ′ = ( 1 + h 00 ) x + h 01 y + h 02 h 20 x + h 21 y + 1 x^\prime=\dfrac{(1+h_{00})x+h_{01}y+h_{02}}{h_{20}x+h_{21}y+1} x=h20x+h21y+1(1+h00)x+h01y+h02

对不同的 x x x y y y,位移量不同,不可能提取出一个统一的值。这里采取粗略的方式,取 x = 0 x=0 x=0 y = 0 y=0 y=0时得出的 x ′ x^\prime x值作为水平位移量。因为在边缘剪容易出现黑色像素,所以再加上 2 5 \frac{2}{5} 52原始图像宽度,选取大概靠图像中间的位置作为剪切位置。当然这个中间位置的计算是及其粗略的,但至少避开了边缘。

剪切代码

	int d = img1.cols*2/5;

	int x3 = h34.at<double>(0,2)+d;
	int x2 = x3 + h23.at<double>(0, 2);
	int x1 = x2 + h12.at<double>(0, 2);

	Mat canvas(img1.rows,img1.cols*4,CV_8UC3);
	img4_color.copyTo(canvas(Range::all(), Range(0, img1.cols)));
	warp3(Range::all(), Range(x3, x2)).copyTo(canvas(Range::all(), Range(x3, x2)));
	warp2(Range::all(), Range(x2, x1)).copyTo(canvas(Range::all(), Range(x2, x1)));
	warp1(Range::all(), Range(x1, x1 + img1.cols)).copyTo(canvas(Range::all(), Range(x1, x1 + img1.cols)));

最终结果

这里写图片描述

由拼接结果可以看出,图像越往右侧,图像变形越大,出现了明显的拼接误差,这个可以通过后期融合来消除。另一方面,由于接缝位置选择不当,第4张图像只显示了一部分和出现了不对齐的情况,可以采用最佳拼接缝算法,使不对齐的情况最小化。本篇博客未实现接缝融合和最佳拼接缝寻找。


#完整代码和图像素材

#include"Homography.h"

int main()
{
	//从右向左升序
	string imgPath1 = "trees_003.jpg";
	string imgPath2 = "trees_002.jpg";
	string imgPath3 = "trees_001.jpg";
	string imgPath4 = "trees_000.jpg";

	Mat img1 = imread(imgPath1, CV_LOAD_IMAGE_GRAYSCALE);
	Mat img2 = imread(imgPath2, CV_LOAD_IMAGE_GRAYSCALE);
	Mat img3 = imread(imgPath3, CV_LOAD_IMAGE_GRAYSCALE);
	Mat img4 = imread(imgPath4, CV_LOAD_IMAGE_GRAYSCALE);

	Mat img1_color = imread(imgPath1, CV_LOAD_IMAGE_COLOR);
	Mat img2_color = imread(imgPath2, CV_LOAD_IMAGE_COLOR);
	Mat img3_color = imread(imgPath3, CV_LOAD_IMAGE_COLOR);
	Mat img4_color = imread(imgPath4, CV_LOAD_IMAGE_COLOR);

	Homography homo12(img1,img2);
	Homography homo23(img2, img3);
	Homography homo34(img3, img4);

	Mat h12 = homo12.getHomography();
	Mat h23 = homo23.getHomography();
	Mat h34 = homo34.getHomography();

	/*homo12.drawMatches();
	homo23.drawMatches();
	homo34.drawMatches();*/

	Mat h24 = h34*h23;
	Mat h14 = h24*h12;

	float scale_h24 = h24.at<double>(2, 2);
	float scale_h14 = h14.at<double>(2, 2);
	h24 = h24 / scale_h24;
	h14 = h14 / scale_h14;

	Mat warp1;
	warpPerspective(img1_color, warp1, h14, Size(img1.cols * 4, img1.rows));
	Mat warp2;
	warpPerspective(img2_color, warp2, h24, Size(img1.cols * 4, img1.rows));
	Mat warp3;
	warpPerspective(img3_color, warp3, h34, Size(img1.cols * 4, img1.rows));

	imshow("warp1", warp1);
	imshow("warp2",warp2);
	imshow("warp3", warp3);
	
	imwrite("warp1.jpg", warp1);
	imwrite("warp2.jpg", warp2);
	imwrite("warp3.jpg", warp3);

	int d = img1.cols*2/5;
	int x3 = h34.at<double>(0,2)+d;
	int x2 = x3 + h23.at<double>(0, 2);
	int x1 = x2 + h12.at<double>(0, 2);

	Mat canvas(img1.rows,img1.cols*4,CV_8UC3);
	img4_color.copyTo(canvas(Range::all(), Range(0, img1.cols)));
	warp3(Range::all(), Range(x3, x2)).copyTo(canvas(Range::all(), Range(x3, x2)));
	warp2(Range::all(), Range(x2, x1)).copyTo(canvas(Range::all(), Range(x2, x1)));
	warp1(Range::all(), Range(x1, x1 + img1.cols)).copyTo(canvas(Range::all(), Range(x1, x1 + img1.cols)));
	

	imwrite("canvas.jpg",canvas);
	imshow("canvas",canvas);

	waitKey(0);
	return 0;
}

img4
这里写图片描述

img3
这里写图片描述

img2
这里写图片描述

img1
这里写图片描述

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值