发布时间:2022-09-16 18:00
这篇文章依旧是记录采用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,我就是放弃了好多遍又捡回来的(讲道理的时候说得可好听啦...)。
各部分基础说明
M要大写,路径间隔要是双斜杠或反斜杠,每句后要有小写的;
否则你会喜提一个感叹号,杠杠的!
创建一个名称为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);
Rect Rectcut = Rect(150, 150, 50, 50);// 定义一个名称为Rectcut的矩形,Rect括号里面分别表示这个矩形的左上角坐标,宽和高,同理下面的Rectmatched,不要试图翻译矩 矩切,矩 矩匹配,爱它没结果,要注重函数的构成形式,名称不重要,意义也不重要,重要的是理解函数实现了一个什么样功能的操作。
哈哈哈,终于碰到我眼熟的了,这是定义一个名称为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));
matchTemplate是函数的名称,()里面是其需要输入的参数变量。
首先需要理解,函数的功能,其次需要知道自己想要正确使用这个函数需要输入的正确对应参数。
譬如MatchTemplate函数,是在一幅图像中寻找与另一幅模板图像最匹配(相似)部分。
参数详解
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);
//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拷贝
个人总结一点程序调试的心得,这部分还是希望有大佬们多多指点我。
我自己的方法是,首先清楚程序的目的,流程和各部分的功能后,调试程序用的是笨办法,就是从上到下逐步取消注释运行部分程序看有没有报错,前面的没有报错,代表的前面没有问题,后面新的问题一定是出在了新的取消注释的部分中,然后先根据错误列表中首先看格式有没有问题,格式没问题了,看函数是否使用合适,参数是否符合函数的输入参数要求,譬如最后拼接出问题就是由于坐标值计算结果为负导致报错。
暂时就这么多啦,感谢在解决问题过程中各位大佬的帮助,如果我的回答帮助到你,还麻烦给我点赞加油呀~~