利用C++中的opencv进行图像拼接


这篇文章依旧是记录采用C++复现图像拼接过程解决遇到的问题。因为自己没有学过C++,大学学的C考完试立马还给老师了,Python也是现学的,只会一点点MATLAB,所以遇到的问题和解决都很基础,目的是自己做一些记录,并分享给与我相同的图像入门小白,若有错误还希望大佬们指正。

一、拼接方法介绍

拼接的主要参考文献:

OpenCV常用图像拼接方法(二):基于模板匹配拼接_Color Space的博客-CSDN博客_图像拼接素材

图像拼接的程序的目的是将imgL(1)与imgR(2)拼接起来,方法是通过截取imgL中的一小部分特征区域与imgR的图像进行比对,找到最相似的区域坐标,以此作为拼接的联系坐标,将两幅图拼接起来(大白话就是将两图最相似的区域对齐叠起来达到拼接效果。)

  Mat imgL = imread("A.jpg");
  Mat imgR = imread("B.jpg");
  double start = getTickCount();
  Mat grayL, grayR;
  cvtColor(imgL, grayL, COLOR_BGR2GRAY);
  cvtColor(imgR, grayR, COLOR_BGR2GRAY);

  Rect rectCut = Rect(372, 122, 128, 360);
  Rect rectMatched = Rect(0, 0, imgR.cols / 2, imgR.rows);
  Mat imgTemp = grayL(Rect(rectCut));
  Mat imgMatched = grayR(Rect(rectMatched));

  int width = imgMatched.cols - imgTemp.cols + 1;
  int height = imgMatched.rows - imgTemp.rows + 1;
  Mat matchResult(height, width, CV_32FC1);
  matchTemplate(imgMatched, imgTemp, matchResult, TM_CCORR_NORMED);
  normalize(matchResult, matchResult, 0, 1, NORM_MINMAX, -1);  //归一化到0--1范围

  double minValue, maxValue;
  Point minLoc, maxLoc;
  minMaxLoc(matchResult, &minValue, &maxValue, &minLoc, &maxLoc);

  Mat dstImg(imgL.rows, imgR.cols + rectCut.x - maxLoc.x, CV_8UC3, Scalar::all(0));
  Mat roiLeft = dstImg(Rect(0, 0, imgL.cols, imgL.rows));
  imgL.copyTo(roiLeft);

  Mat debugImg = imgR.clone();
  rectangle(debugImg, Rect(maxLoc.x, maxLoc.y, imgTemp.cols, imgTemp.rows), Scalar(0, 255, 0), 2, 8);
  imwrite("match.jpg", debugImg);

  Mat roiMatched = imgR(Rect(maxLoc.x, maxLoc.y - rectCut.y, imgR.cols - maxLoc.x, imgR.rows - 1 - (maxLoc.y - rectCut.y)));
  Mat roiRight = dstImg(Rect(rectCut.x, 0, roiMatched.cols, roiMatched.rows));

  roiMatched.copyTo(roiRight);

  double end = getTickCount();
  double useTime = (end - start) / getTickFrequency();
  cout << "use-time : " << useTime << "s" << endl;

  imwrite("dst.jpg", dstImg);
  cout << "Done!" << endl;

基础背景说明

1、首先这个程序是C++程序,不是Python程序,我在Python中跑半天是不是很令人震惊。

我在Visual studio中创建一个C++的空项目进行复现(VS如何配置opencv环境以及创建空项目我后面估计会再分享一个博客记录说明,因为我自己也老忘,好麻烦呀,反观MATLAB好香)

写好啦:Visual Studio中设置opencv环境_日拱一卒不慌忙的博客-CSDN博客

2、其次上面这个函数是一个子函数的内容,直接复制到VS中是不能运行的,需要加入一些头文件,并把函数放进主函数中才能运行。

#include 
#include 
#include 
using namespace std;
using namespace cv;

int main()
{
	//1、读取图像,并将RGB图像转换为灰度图
    Mat imgL = imread("C:\\Users\\AC\\Desktop\\A.jpg");
	Mat imgR = imread("C:\\Users\\AC\\Desktop\\B.jpg");
	double start = getTickCount();   //计时开始
	Mat grayL, grayR;
	cvtColor(imgL, grayL, COLOR_BGR2GRAY);
	cvtColor(imgR, grayR, COLOR_BGR2GRAY);

    //2、在左图中截取一小块特征区域作为匹配模板imgtemp
	Rect Rectcut = Rect(150, 150, 50, 50);
	int widthR = Rectcut.x;
	int heightR = Rectcut.y;
	Rect Rectmatched = Rect(0, 0, imgR.cols / 2, imgR.rows);   
	Mat imgtemp = grayL(Rect(Rectcut));
	Mat imgmatched = grayR(Rect(Rectmatched));

    //3、采用特征模板在右图中进行相似比对,找到最相似的区域位置坐标maxLoc
	int width = imgmatched.cols - imgtemp.cols + 1;
	int height = imgmatched.rows - imgtemp.rows + 1;
	Mat matchResult(height, width, CV_32FC1);  //CV_32 32比特,F代表单精度浮点型,C1单通道图像,2三通道,3四通道    
	matchTemplate(imgmatched, imgtemp, matchResult, TM_CCORR_NORMED);   
	normalize(matchResult, matchResult, 0, 1, NORM_MINMAX, -1);  //归一化到0--1范围 
	double minValue, maxValue;   
	Point minLoc, maxLoc;
	minMaxLoc(matchResult, &minValue, &maxValue, &minLoc, &maxLoc); 
  
	//4、创建一个大的空图,先将左图复制上去,再将右图根据重叠区域位置坐标截切后复制到大图中,形成最终的拼接图像dstImg
    Mat dstImg(imgL.rows, imgR.cols + Rectcut.x - maxLoc.x, CV_8UC3, Scalar::all(0));   
	Mat roiLeft = dstImg(Rect(0, 0, imgL.cols, imgL.rows));     
	Mat debugImg = imgR.clone();  
	rectangle(debugImg, Rect(maxLoc.x, maxLoc.y, imgtemp.cols, imgtemp.rows), Scalar(0, 255, 0), 2, 8);  
	imwrite("match.jpg", debugImg);

	Mat roiMatched = imgR(Rect(maxLoc.x, maxLoc.y - Rectcut.y, imgR.cols - maxLoc.x, imgR.rows - 1 - (maxLoc.y - Rectcut.y)));   
	Mat roiRight = dstImg(Rect(Rectcut.x, 0, roiMatched.cols, roiMatched.rows));
	roiMatched.copyTo(roiRight);
    
    //5、存图,还有仪式感
	double end = getTickCount();    //计时结束
	double useTime = (end - start) / getTickFrequency();
	cout << "use-time : " << useTime << "s" << endl;

	imshow("图像", dstImg);
	waitKey(0);
	cout << "Done!" << endl;
	return 0;
}

二、拼接程序分析

程序基础准备

首先我们拿到一个程序需要清楚程序的目的(在做什么),程序的流程(怎么做的),对应到程序各部分的功能(每一个part到底在做啥,我附注在上面每段的程序上了),有背景基础才能有助于对新的语言所表达的内容进行理解,要不然就陷入到一堆字母中啊?啊?啊?放弃X,我就是放弃了好多遍又捡回来的(讲道理的时候说得可好听啦...)。

各部分基础说明

1、Mat imgL=imread("路径(双斜杠)")

M要大写,路径间隔要是双斜杠或反斜杠,每句后要有小写的;  

否则你会喜提一个感叹号,杠杠的!

利用C++中的opencv进行图像拼接_第1张图片

创建一个名称为imgL的空的mat数据矩阵存放读取进的图像,图像的每个像素的灰度值会被记录在矩阵的每个对应位置上,同理Mat grayL, grayR,也是创建了两个名称为grayL, grayR的空的数据矩阵存放一会RGB转换为灰度图的图像,cvtcolor是一个转换函数,具体细节百度一下。

    Mat imgL = imread("C:\\Users\\AC\\Desktop\\A.jpg");
	Mat imgR = imread("C:\\Users\\AC\\Desktop\\B.jpg");
	Mat grayL, grayR;
	cvtColor(imgL, grayL, COLOR_BGR2GRAY);
	cvtColor(imgR, grayR, COLOR_BGR2GRAY);

2、Rect类

Rect Rectcut = Rect(150, 150, 50, 50);// 定义一个名称为Rectcut的矩形,Rect括号里面分别表示这个矩形的左上角坐标,宽和高,同理下面的Rectmatched,不要试图翻译矩 矩切,矩 矩匹配,爱它没结果,要注重函数的构成形式,名称不重要,意义也不重要,重要的是理解函数实现了一个什么样功能的操作

3、int类,double类

哈哈哈,终于碰到我眼熟的了,这是定义一个名称为widthR的整型,将Rectcut.x的值或是计算的结果存储到widthR中。

int widthR = Rectcut.x

1. OpenCV 数据结构与基本绘图_zcx_xxx的博客-CSDN博客

	Rect Rectcut = Rect(150, 150, 50, 50);
	int widthR = Rectcut.x;
	int heightR = Rectcut.y;
	Rect Rectmatched = Rect(0, 0, imgR.cols / 2, imgR.rows);   
	Mat imgtemp = grayL(Rect(Rectcut));
	Mat imgmatched = grayR(Rect(Rectmatched));

4、匹配函数matchTemplate

matchTemplate是函数的名称,()里面是其需要输入的参数变量。

首先需要理解,函数的功能,其次需要知道自己想要正确使用这个函数需要输入的正确对应参数。

譬如MatchTemplate函数,是在一幅图像中寻找与另一幅模板图像最匹配(相似)部分。

利用C++中的opencv进行图像拼接_第2张图片

参数详解 

    MatchTemplate(InputArray image, InputArray templ, OutputArray result, int method);

        image:输入一个待匹配的图像,支持8U或者32F。

        templ:输入一个模板图像,与image相同类型。

        result:输出保存结果的矩阵,32F类型。

        method:要使用的数据比较方法。

MatchTemplate函数参考下面这个博文
【 OpenCV 】MatchTemplate函数参数详解及原理分析_Nick大帅仔的博客-CSDN博客_matchtemplate

 同理normalize这个函数是用来归一化的,minMaxloc是用来寻找最大值和最小值的坐标位置(&a代表取到a的地址)。

归一化函数normalize详解 - 百度文库

OpenCV 找出图像中最小值最大值函数minMaxLoc的使用 - 灰信网(软件开发博客聚合)

    //3、采用特征模板在右图中进行相似比对,找到最相似的区域位置坐标maxLoc
	int width = imgmatched.cols - imgtemp.cols + 1;
	int height = imgmatched.rows - imgtemp.rows + 1;
	Mat matchResult(height, width, CV_32FC1);  //CV_32 32比特,F代表单精度浮点型,C1单通道图像,2三通道,3四通道    
	matchTemplate(imgmatched, imgtemp, matchResult, TM_CCORR_NORMED);   
	normalize(matchResult, matchResult, 0, 1, NORM_MINMAX, -1);  //归一化到0--1范围 
	double minValue, maxValue;   
	Point minLoc, maxLoc;
	minMaxLoc(matchResult, &minValue, &maxValue, &minLoc, &maxLoc); 

5、拼图(浅拷贝和深拷贝)

	//4、创建一个大的空图,先将左图复制上去,再将右图根据重叠区域位置坐标截切后复制到大图中,形成最终的拼接图像dstImg
    Mat dstImg(imgL.rows, imgR.cols + Rectcut.x - maxLoc.x, CV_8UC3, Scalar::all(0));   
	Mat roiLeft = dstImg(Rect(0, 0, imgL.cols, imgL.rows));     
	Mat debugImg = imgR.clone();  
	rectangle(debugImg, Rect(maxLoc.x, maxLoc.y, imgtemp.cols, imgtemp.rows), Scalar(0, 255, 0), 2, 8);  
	imwrite("match.jpg", debugImg);

	Mat roiMatched = imgR(Rect(maxLoc.x, maxLoc.y - Rectcut.y, imgR.cols - maxLoc.x, imgR.rows - 1 - (maxLoc.y - Rectcut.y)));   
	Mat roiRight = dstImg(Rect(Rectcut.x, 0, roiMatched.cols, roiMatched.rows));
	roiMatched.copyTo(roiRight);

这里会涉及到拷贝的问题,就是我们要形成最后的拼接图像,必然要先从原来的两幅图中的信息提取出来,然后进行一些修剪,再将其组合起来。

首先数据传递和处理不能影响到原图imgL(深拷贝),其次分图roiLeft的数据的处理要体现在最后的大的拼接图dstImg中(浅拷贝)。

深拷贝:分配新内存的同时拷贝数据!当被赋值的容器被修改时,原始容器数据不会改变。
浅拷贝:仅拷贝数据!当被赋值容器修改时,原始容器数据也会做同样改变。(感觉和C++中引用同理)
深拷贝是 b = a.clone(); 和 a.copyTo(b);浅拷贝是 b = a;和 b(a);

OpenCV中cv::Mat的深拷贝 浅拷贝问题_verystory的博客-CSDN博客_cv::mat拷贝

三、个人心得

个人总结一点程序调试的心得,这部分还是希望有大佬们多多指点我。

我自己的方法是,首先清楚程序的目的,流程和各部分的功能后,调试程序用的是笨办法,就是从上到下逐步取消注释运行部分程序看有没有报错,前面的没有报错,代表的前面没有问题,后面新的问题一定是出在了新的取消注释的部分中,然后先根据错误列表中首先看格式有没有问题,格式没问题了,看函数是否使用合适,参数是否符合函数的输入参数要求,譬如最后拼接出问题就是由于坐标值计算结果为负导致报错。

暂时就这么多啦,感谢在解决问题过程中各位大佬的帮助,如果我的回答帮助到你,还麻烦给我点赞加油呀~~

你可能感兴趣的:(opencv,计算机视觉,图像处理)