最近在随浅墨的博客学习OpenCV,受益匪浅,这系列博客记录OpenCV的学习历程,也对自己的学习做一个笔记。
首先感谢浅墨的OpenCV学习系列教程,帮助很大,在此添加浅墨文章的链接。
以下结合教程写出关于使用OpenCV实现图像叠加的代码以及一些原理解释,同时结合MATLAB进行学习。
图像的叠加:
由浅入深一步一步学习,首先实现图像叠加,这里添出background图(大图),logo图(小图),实现logo嵌入background任意感兴趣部分,这里有两种方法,先把两种方法的代码添出,再做进一步讨论。先添图片,background和logo。
background:
logo:
方法一:
//--------------------------------------------------------------------------- // 【程序说明】 // 实现功能:图像叠加 // 重要函数:imread,imshow,namedWindow(可参考浅墨文章) // Rect,Range,copyTo,addWeighted(参考文章分解) //--------------------------------------------------------------------------- #include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> using namespace cv; int main( ) { Mat background= imread("dota2.jpg"); Mat logo= imread("logo.jpg"); //载入后先显示 namedWindow("background"); imshow("background",background); namedWindow("logo"); imshow("logo",logo); Mat imageROI_1; //ROI图像与原始图像共享数据 imageROI_1=background(Rect(200,10,logo.cols,logo.rows)); namedWindow("imageROI_1"); imshow("imageROI_1",imageROI_1);//显示ROI logo.copyTo(imageROI_1);//覆盖logo namedWindow("合成图1"); imshow("合成图1",background); waitKey(); return 0; }得到ROI图与合成图:
关于代码的头文件包含以及using类的运用,建议查看浅墨文章。这里不作阐述这里主要解析一下
imageROI_1=background(Rect(200,10,logo.cols,logo.rows));
以及
logo.copyTo(imageROI_1);
两段代码。
Rect函数是在图片当中选出ROI图像部分(即你感兴趣的部分),输入图片为background。
Rect(200,10,logo.cols,logo.rows);其中200,10为background图片当中的像素坐标,也是你感兴趣部分(这个部分为一个矩形)的最左上角的点的坐标,而后面两个参数分别为感兴趣部分矩形的横向长度(logo.cols即logo的列数)与竖向长度(logo.rows即logo的行数),我们这里想要选取的感兴趣的矩形与我们要嵌入到background中的logo的大小需要一致。
注:我们可以参考上面添出的ROI图可以看到,ROI图就是从background提出的感兴趣部分矩形。但要注意一点,ROI图经过Rect提出过后,并不是单纯的复制数据,ROI图的数据与background中感兴趣部分矩形的数据是共享的,如果你改变ROI图的数据,background中感兴趣部分矩形的数据也会相应改变,这正是我们下一个函数copyTo想要做的。logo.copyTo(imageROI_1);此函数的输入为logo图片,将logo复制到ROI图片当中,background的相应感兴趣的部分也会相应的改变(正如上面所说的),从而达到图片叠加的效果。
注:文章中的东西都是我通俗的理解,语言比较粗糙。将专业的知识转化成为我们通俗的直感,我一直以来觉得很有必要的,望海涵。
方法二:background与logo都不变,只是选取的区域改变了,代码如下:
//---------------------------------------------------------------------------------
// 【程序说明】 // 实现功能:图像叠加 // 重要函数:imread,imshow,namedWindow(可参考浅墨文章) // Rect,Range,copyTo,addWeighted(参考文章分解) //--------------------------------------------------------------------------------- #include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> using namespace cv; int main( ) { Mat background= imread("dota2.jpg"); Mat logo= imread("logo.jpg"); //载入后先显示 namedWindow("background"); imshow("background",background); namedWindow("logo"); imshow("logo",logo); Mat imageROI_2; //ROI图像与原始图像共享数据 imageROI_2=background(Range(250,250+logo.rows),Range(350,350+logo.cols)); namedWindow("imageROI_2"); imshow("imageROI_2",imageROI_2);//显示ROI <span style="font-size:24px;">addWeighted(imageROI_2,0.5,logo,0.5,0.,imageROI_2);</span> namedWindow("合成图2"); imshow("合成图2",background); waitKey(); return 0; }先添出合成图效果:
代码和方法一的代码大同小异,只是有两个不同的函数
imageROI_2=background(Range(250,250+logo.rows),Range(350,350+logo.cols));
Range是定义一个范围的数组,从横向和竖向两个方向定义就可以框出一个感兴趣的区域。第一个Range(250,250+logo.rows)定义的是竖向的范围(注意,与Rect定义的顺序不同,Rect是先定义横向),第二个Range(350,350+logo.cols)定义的是横向的范围,以这样的方法也可以选定出自己感兴趣的矩形,很简单。
下面这个函数就是addWeighted,作用是计算两个数组(图像阵列)的加权和:
addWeighted(imageROI_2,0.5,logo,0.5,0.,imageROI_2);
对于这个函数的参数,共有六个(后面用符号代替):
imageROI_2-----------M1
0.5------------------------a1
logo----------------------M2
0.5------------------------a2
0.--------------------------a3
imageROI_2------------M3
这个函数即经过的如下运算:
M3=M1*a1+M2*a2+a3
其中M3为这个函数的输出,M1,M2为Mat类型的数组或是图像阵列,这里M1为ROI图,M2为logo图,a1为ROI图的加权系数,a2为M2的加权系数,a3是一个直流分量,可取0(0.就是0)。
所以你可以看到第二个方法中,嵌入的logo有一定的透明性,是因为logo的加权系数a2没有取到1,如果把a2取到1,a1取0,效果就和方法一copyTo相同了。
想看关于addWeighted更专业的分解,可参考浅墨入门教程四
把两种方法合在一幅图上做做对比:
背景透明化
如果我们嵌入的logo图不想要它的背景,比如我们的dota2的logo图片,我们不想要它的白色背景,只要前面的标记,刀塔的字样,那我们需要将logo图片进行预处理,将你不想要的部分全部涂成黑色,便可利用copyTo函数进行透明化处理,我在网上重新下载了logo和background,先贴出来:
代码如下:
//--------------------------------------------------------------------------------- // 【程序说明】 // 实现功能:图像叠加 // 重要函数:imread,imshow,namedWindow(可参考浅墨文章) // Rect,Range,copyTo,addWeighted(参考文章分解) //--------------------------------------------------------------------------------- #include<iostream> #include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> using namespace cv; int main( ) { Mat background= imread("pic.jpg"); Mat logo= imread("alpha.png"); Mat mask=imread("alpha.png",1); //载入后先显示 namedWindow("background"); imshow("background",background); namedWindow("logo"); imshow("logo",logo); Mat imageROI_4; //ROI图像与原始图像共享数据 imageROI_4=background(Rect(40,10,logo.cols,logo.rows)); namedWindow("imageROI_4"); imshow("imageROI_4",imageROI_4);//显示ROI logo.copyTo(imageROI_4,mask);//覆盖logo namedWindow("合成图4"); imshow("合成图4",background); waitKey(); return 0; }这个与上面方法一的代码基本一样,但是有两个不同。
多定义了一个mask:
Mat mask=imread("alpha.png",1);
这个mask被称为掩码,是一个标准,后面的参数1在这里有没有都没关系,都是输出原图像,如果你把参数改为0,输出的是这个图像的灰度图。原图与灰度图都可以作为掩码,我这里使用原图作为掩码效果较好,读者可以使用灰度图作为掩码试试。
贴出最后的合成效果图:
另一个不同就是copyTo函数这里
logo.copyTo(imageROI_4,mask);
对掩码mask进行了一个使用,这里将logo复制到ROI图当中的时候,首先对mask进行一个判断。mask当中灰度值为0的区域,相对应的ROI当中的区域就不变化,维持原样;当mask当中灰度值不为0的区域,相对应的ROI当中的区域就把logo复制上去。
之所以我们要把我们logo背景全部涂成黑色,就是为了当logo作为mask进行判断时,我们的背景区域不需要,就把它搞成0(黑色),就不会进行操作,维持ROI的原样,而其他的部分,我们用logo复制到ROI中,相应的background就会发生变化(数据共享嘛,前面说过了的)。
以上就是这个第一阶段的OpenCV的学习,先到这里,稍后继续添加内容,以及用MATLAB进行有关实现。