5057|0

661

帖子

18

TA的资源

纯净的硅(初级)

楼主
 

【OpenCV入门教程之四】 ROI区域图像叠加&初级图像混合 全剖析 [复制链接]

本帖最后由 兰博 于 2017-12-19 17:20 编辑

在这篇文章里,我们一起学习了在OpenCV中如何定义感兴趣区域ROI,如何使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像混合操作。
PS:文章末尾提供了博文配套程序源代码的下载。

文章开头,依旧是先放一张截图:





一、设定感兴趣区域——ROI(region of interest)



在图像处理领域,我们常常需要设置感兴趣区域(ROI,region of interest),来专注或者简化我们的工作过程 。也就是从图像中选择的一个图像区域,这个区域是我们图像分析所关注的重点。我们圈定这个区域,以便进行进一步处理。而且,使用ROI指定我们想读入的目标,可以减少处理时间,增加精度,给图像处理来带不小的便利。

ROI区域定义的两种方法

定义ROI区域有两种方法,第一种是使用cv:Rect.顾名思义,cv::Rect表示一个矩形区域。指定矩形的左上角坐标(构造函数的前两个参数)和矩形的长宽(构造函数的后两个参数)就可以定义一个矩形区域。
[cpp] view plain copy


    1. //定义一个Mat类型并给其设定ROI区域  
    2. Mat imageROI;  
    3. //方法一  
    4. imageROI=image(Rect(500,250,logo.cols,logo.rows));  
    复制代码




另一种定义ROI的方式是指定感兴趣行或列的范围(Range)。Range是指从起始索引到终止索引(不包括终止索引)的一连段连续序列。cv::Range可以用来定义Range。如果使用cv::Range来定义ROI,那么前例中定义ROI的代码可以重写为:

[cpp] view plain copy


  • //方法二  
  • imageROI=srcImage3(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));  




好了,下面我们来看一个实例,显示如何利用ROI将一幅图加到另一幅图的指定位置。大家如果需要拷贝如下的函数中的代码直接运行的话,自己建一个基于console的程序,然后把函数体中的内容拷贝到main函数中,然后找两幅大小合适的图片,加入到工程目录下,并和代码中读取的文件名一致即可。
在下面的代码中,我们通过一个图像掩膜(mask),直接将插入处的像素设置为logo图像的像素值,这样效果会很赞很逼真:
[cpp] view plain copy


    1. //----------------------------------【ROI_AddImage( )函数】----------------------------------  
    2. // 函数名:ROI_AddImage()  
    3. //     描述:利用感兴趣区域ROI实现图像叠加  
    4. //----------------------------------------------------------------------------------------------  
    5. bool ROI_AddImage()  
    6. {  
    7.    
    8.        //【1】读入图像  
    9.        Mat srcImage1= imread("dota_pa.jpg");  
    10.        Mat logoImage= imread("dota_logo.jpg");  
    11.        if(!srcImage1.data ) { printf("你妹,读取srcImage1错误~! \n"); return false; }  
    12.        if(!logoImage.data ) { printf("你妹,读取logoImage错误~! \n"); return false; }  
    13.    
    14.        //【2】定义一个Mat类型并给其设定ROI区域  
    15.        Mat imageROI= srcImage1(Rect(200,250,logoImage.cols,logoImage.rows));  
    16.    
    17.        //【3】加载掩模(必须是灰度图)  
    18.        Mat mask= imread("dota_logo.jpg",0);  
    19.    
    20.        //【4】将掩膜拷贝到ROI  
    21.        logoImage.copyTo(imageROI,mask);  
    22.    
    23.        //【5】显示结果  
    24.        namedWindow("<1>利用ROI实现图像叠加示例窗口");  
    25.        imshow("<1>利用ROI实现图像叠加示例窗口",srcImage1);  
    26.    
    27.        return true;  
    28. }  
    复制代码





这个函数首先是载入了两张jpg图片到srcImage1和logoImage中,然后定义了一个Mat类型的imageROI,并使用cv::Rect设置其感兴趣区域为srcImage1中的一块区域,将imageROI和srcImage1关联起来。接着定义了一个Mat类型的的mask并读入dota_logo.jpg,顺势使用Mat:: copyTo把mask中的内容拷贝到imageROI中,于是就得到了最终的效果图,namedWindow和imshow配合使用,显示出最终的结果。

运行结果如下:

这里白色的dota2 logo,就是通过操作之后加上去的图像。


二、初级图像混合——线性混合操作


线性混合操作是一种典型的二元(两个输入)的像素操作,它的理论公式是这样的:
  
                                                              
如果看过我之前写的游戏编程Alpha混合那篇文章的朋友们应该有些熟悉,其实他们是差不多的:

我们通过在范围0到1之间改变alpha值,来对两幅图像(f0(x)和f1(x))或两段视频(同样为(f0(x)和f1(x))产生时间上的画面叠化(cross-dissolve)效果,就像幻灯片放映和电影制作中的那样。即在幻灯片翻页时设置的前后页缓慢过渡叠加效果,以及电影情节过渡时经常出现的画面叠加效果。
实现方面,我们主要运用了OpenCV中addWeighted函数,我们来全面的了解一下它:

addWeighted函数

这个函数的作用是,计算两个数组(图像阵列)的加权和。原型如下:

[cpp] view plain copy


  • void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);  




  • 第一个参数,InputArray类型的src1,表示需要加权的第一个数组,常常填一个Mat。
  • 第二个参数,alpha,表示第一个数组的权重
  • 第三个参数,src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数。
  • 第四个参数,beta,表示第二个数组的权重值。
  • 第五个参数,dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数。
  • 第六个参数,gamma,一个加到权重总和上的标量值。看下面的式子自然会理解。
  • 第七个参数,dtype,输出阵列的可选深度,有默认值-1。;当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

如果用数学公式来表达,addWeighted函数计算如下两个数组(src1和src2)的加权和,得到结果输出给第四个参数。即addWeighted函数的作用可以被表示为为如下的矩阵表达式为:
                                                      dst = src1[I]*alpha+ src2[I]*beta + gamma;
其中的I,是多维数组元素的索引值。而且,在遇到多通道数组的时候,每个通道都需要独立地进行处理。另外需要注意的是,当输出数组的深度为CV_32S时,这个函数就不适用了,这时候就会内存溢出或者算出的结果压根不对。
理论和函数的讲解就是上面这些,接着我们来看代码实例,以融会贯通。

[cpp] view plain copy


    1. //---------------------------------【LinearBlending()函数】-------------------------------------  
    2. // 函数名:LinearBlending()  
    3. // 描述:利用cv::addWeighted()函数实现图像线性混合  
    4. //--------------------------------------------------------------------------------------------  
    5. bool LinearBlending()  
    6. {  
    7.        //【0】定义一些局部变量  
    8.        double alphaValue = 0.5;  
    9.        double betaValue;  
    10.        Mat srcImage2, srcImage3, dstImage;  
    11.    
    12.        //【1】读取图像 ( 两幅图片需为同样的类型和尺寸 )  
    13.        srcImage2= imread("mogu.jpg");  
    14.        srcImage3= imread("rain.jpg");  
    15.    
    16.        if(!srcImage2.data ) { printf("你妹,读取srcImage2错误~! \n"); return false; }  
    17.        if(!srcImage3.data ) { printf("你妹,读取srcImage3错误~! \n"); return false; }  
    18.    
    19.        //【2】做图像混合加权操作  
    20.        betaValue= ( 1.0 - alphaValue );  
    21.        addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);  
    22.    
    23.        //【3】创建并显示原图窗口  
    24.        namedWindow("<2>线性混合示例窗口【原图】 by浅墨", 1);  
    25.        imshow("<2>线性混合示例窗口【原图】 by浅墨", srcImage2 );  
    26.    
    27.        namedWindow("<3>线性混合示例窗口【效果图】 by浅墨", 1);  
    28.        imshow("<3>线性混合示例窗口【效果图】 by浅墨", dstImage );  
    29.    
    30.        return true;  
    31.         
    32. }  
    复制代码






代码解析:

<0>首先当然是定义一些局部变量,alpha值beta值,三个Mat类型的变量。
[cpp] view plain copy


    1. //【0】定义一些局部变量  
    2.        double alphaValue = 0.5;  
    3.        double betaValue;  
    4.        Mat srcImage2, srcImage3, dstImage;  
    复制代码



      
在这里我们设置alpha值为0.5。
<1>读取两幅图像并作错误处理
这步很简单,直接上代码:
      
[cpp] view plain copy


    1. //读取图像 ( 两幅图片需为同样的类型和尺寸 )  
    2.        srcImage2= imread("mogu.jpg");  
    3.        srcImage3= imread("rain.jpg");  
    4. if(!srcImage2.data ) { printf("你妹,读取srcImage2错误~! \n"); return false; }  
    5.        if(!srcImage3.data ) { printf("你妹,读取srcImage3错误~! \n"); return false; }  
    复制代码




在这里需要注意的是,因为我们是对 srcImage1和srcImage2求和,所以它们必须要有相同的尺寸(宽度和高度)和类型,不然多余的部分没有对应的“伴”,肯定会出问题。

<2> 进行图像混合加权操作
载入图像后,我们就可以来生成混合图像,也就是之前公式中的g(x)。为此目的,使用函数 addWeighted 可以很方便地实现,也就是因为 addWeighted 进行了如下计算:

这里的对应于addWeighted的第2个参数alpha
这里的对应于addWeighted的第4个参数beta
这里的对应于addWeighted的第5个参数,在上面代码中被我们设为0.0。
代码其实很简单,就是这样:

[cpp] view plain copy


    1. //【2】进行图像混合加权操作  
    2.   betaValue = ( 1.0 - alphaValue );  
    3.   addWeighted( srcImage2, alphaValue, srcImage3,betaValue, 0.0, dstImage);  
    4. 其中beta值为1-alpha,gamma值为0。  
    复制代码





<3>创建显示窗口,显示图像。
[cpp] view plain copy


    1. // 【3】创建并显示原图窗口  
    2.        namedWindow("<2>线性混合示例窗口【原图】 by浅墨", 1);  
    3.        imshow("<2>线性混合示例窗口【原图】 by浅墨", srcImage2 );  
    4.    
    5.        namedWindow("<3>线性混合示例窗口【效果图】 by浅墨", 1);  
    6.        imshow("<3>线性混合示例窗口【效果图】 by浅墨", dstImage );  
    复制代码





接着来看一下运行效果图,首先是原图:



然后是效果图:





三、综合示例


在前面分别介绍的设定感兴趣区域ROI和使用addWeighted函数进行图像线性混合的基础上,我们还将他们两者中和起来使用,也就是先指定ROI,并用addWeighted函数对我们指定的ROI区域的图像进行混合操作,我们将其封装在了一个名为ROI_LinearBlending的函数中,方便大家分块学习。

[cpp] view plain copy


    1. //---------------------------------【ROI_LinearBlending()】-------------------------------------  
    2. // 函数名:ROI_LinearBlending()  
    3. // 描述:线性混合实现函数,指定区域线性图像混合.利用cv::addWeighted()函数结合定义  
    4. //                     感兴趣区域ROI,实现自定义区域的线性混合  
    5. //--------------------------------------------------------------------------------------------  
    6. bool ROI_LinearBlending()  
    7. {  
    8.    
    9.        //【1】读取图像  
    10.        Mat srcImage4= imread("dota_pa.jpg",1);  
    11.        Mat logoImage= imread("dota_logo.jpg");  
    12.    
    13.        if(!srcImage4.data ) { printf("你妹,读取srcImage4错误~! \n"); return false; }  
    14.        if(!logoImage.data ) { printf("你妹,读取logoImage错误~! \n"); return false; }  
    15.    
    16.        //【2】定义一个Mat类型并给其设定ROI区域  
    17.        Mat imageROI;  
    18.               //方法一  
    19.        imageROI=srcImage4(Rect(200,250,logoImage.cols,logoImage.rows));  
    20.        //方法二  
    21.        //imageROI=srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));  
    22.    
    23.        //【3】将logo加到原图上  
    24.        addWeighted(imageROI,0.5,logoImage,0.3,0.,imageROI);  
    25.    
    26.        //【4】显示结果  
    27.        namedWindow("<4>区域线性图像混合示例窗口 by浅墨");  
    28.        imshow("<4>区域线性图像混合示例窗口 by浅墨",srcImage4);  
    29.         
    30.        return true;  
    31. }  
    复制代码






从这篇文章开始,如果不出意外的话,为了方便大家分块各个击破学习,每讲一个部分,示例代码都将封装在一个函数中,免得大家像学习各种不是特别地道的OpenCV教程时一样,看到代码全放在main函数中,心都碎了。
好了,下面放出详细注释的本篇文章的全部示例源代码:
[cpp] view plain copy


    1. //-----------------------------------【程序说明】----------------------------------------------  
    2. //  程序名称::【OpenCV入门教程之四】 ROI区域图像叠加&初级图像混合 全剖析   配套源码  
    3. // VS2010版   OpenCV版本:2.4.8  
    4. //     2014年3月10日 Create by 浅墨  
    5. //    图片素材出处:dota2原画 dota2logo   
    6. //     浅墨的微博:@浅墨_毛星云  
    7. //------------------------------------------------------------------------------------------------  
    8.    
    9. //-----------------------------------【头文件包含部分】---------------------------------------  
    10. //     描述:包含程序所依赖的头文件  
    11. //----------------------------------------------------------------------------------------------                                                                                      
    12. #include <cv.h>  
    13. #include <highgui.h>  
    14. #include <iostream>  
    15.    
    16. //-----------------------------------【命名空间声明部分】---------------------------------------  
    17. //     描述:包含程序所使用的命名空间  
    18. //-----------------------------------------------------------------------------------------------   
    19. using namespace cv;  
    20. using namespace std;  
    21.    
    22.    
    23. //-----------------------------------【全局函数声明部分】--------------------------------------  
    24. //     描述:全局函数声明  
    25. //-----------------------------------------------------------------------------------------------  
    26. bool ROI_AddImage();  
    27. bool LinearBlending();  
    28. bool ROI_LinearBlending();  
    29.    
    30. //-----------------------------------【main( )函数】--------------------------------------------  
    31. //     描述:控制台应用程序的入口函数,我们的程序从这里开始  
    32. //-----------------------------------------------------------------------------------------------  
    33. int main(  )  
    34. {  
    35.        system("color 5E");  
    36.    
    37.        if(ROI_AddImage()&& LinearBlending( )&&ROI_LinearBlending( ))  
    38.        {  
    39.               cout<<endl<<"嗯。好了,得出了你需要的图像~! : )";  
    40.        }  
    41.    
    42.        waitKey(0);  
    43.        return 0;  
    44. }  
    45.    
    46. //----------------------------------【ROI_AddImage( )函数】----------------------------------  
    47. // 函数名:ROI_AddImage()  
    48. //     描述:利用感兴趣区域ROI实现图像叠加  
    49. //----------------------------------------------------------------------------------------------  
    50. bool ROI_AddImage()  
    51. {  
    52.    
    53.        //【1】读入图像  
    54.        Mat srcImage1= imread("dota_pa.jpg");  
    55.        Mat logoImage= imread("dota_logo.jpg");  
    56.        if(!srcImage1.data ) { printf("你妹,读取srcImage1错误~! \n"); return false; }  
    57.        if(!logoImage.data ) { printf("你妹,读取logoImage错误~! \n"); return false; }  
    58.    
    59.        //【2】定义一个Mat类型并给其设定ROI区域  
    60.        Mat imageROI= srcImage1(Rect(200,250,logoImage.cols,logoImage.rows));  
    61.    
    62.        //【3】加载掩模(必须是灰度图)  
    63.        Mat mask= imread("dota_logo.jpg",0);  
    64.    
    65.        //【4】将掩膜拷贝到ROI  
    66.        logoImage.copyTo(imageROI,mask);  
    67.    
    68.        //【5】显示结果  
    69.        namedWindow("<1>利用ROI实现图像叠加示例窗口");  
    70.        imshow("<1>利用ROI实现图像叠加示例窗口",srcImage1);  
    71.    
    72.        return true;  
    73. }  
    74.    
    75.    
    76. //---------------------------------【LinearBlending()函数】-------------------------------------  
    77. // 函数名:LinearBlending()  
    78. // 描述:利用cv::addWeighted()函数实现图像线性混合  
    79. //--------------------------------------------------------------------------------------------  
    80. bool LinearBlending()  
    81. {  
    82.        //【0】定义一些局部变量  
    83.        double alphaValue = 0.5;  
    84.        double betaValue;  
    85.        Mat srcImage2, srcImage3, dstImage;  
    86.    
    87.        //【1】读取图像 ( 两幅图片需为同样的类型和尺寸 )  
    88.        srcImage2= imread("mogu.jpg");  
    89.        srcImage3= imread("rain.jpg");  
    90.    
    91.        if(!srcImage2.data ) { printf("你妹,读取srcImage2错误~! \n"); return false; }  
    92.        if(!srcImage3.data ) { printf("你妹,读取srcImage3错误~! \n"); return false; }  
    93.    
    94.        //【2】进行图像混合加权操作  
    95.        betaValue= ( 1.0 - alphaValue );  
    96.        addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);  
    97.    
    98.        //【3】创建并显示原图窗口  
    99.        namedWindow("<2>线性混合示例窗口【原图】 by浅墨", 1);  
    100.        imshow("<2>线性混合示例窗口【原图】 by浅墨", srcImage2 );  
    101.    
    102.        namedWindow("<3>线性混合示例窗口【效果图】 by浅墨", 1);  
    103.        imshow("<3>线性混合示例窗口【效果图】 by浅墨", dstImage );  
    104.    
    105.        return true;  
    106.         
    107. }  
    108.    
    109. //---------------------------------【ROI_LinearBlending()】-------------------------------------  
    110. // 函数名:ROI_LinearBlending()  
    111. // 描述:线性混合实现函数,指定区域线性图像混合.利用cv::addWeighted()函数结合定义  
    112. //                     感兴趣区域ROI,实现自定义区域的线性混合  
    113. //--------------------------------------------------------------------------------------------  
    114. bool ROI_LinearBlending()  
    115. {  
    116.    
    117.        //【1】读取图像  
    118.        Mat srcImage4= imread("dota_pa.jpg",1);  
    119.        Mat logoImage= imread("dota_logo.jpg");  
    120.    
    121.        if(!srcImage4.data ) { printf("你妹,读取srcImage4错误~! \n"); return false; }  
    122.        if(!logoImage.data ) { printf("你妹,读取logoImage错误~! \n"); return false; }  
    123.    
    124.        //【2】定义一个Mat类型并给其设定ROI区域  
    125.        Mat imageROI;  
    126.               //方法一  
    127.        imageROI=srcImage4(Rect(200,250,logoImage.cols,logoImage.rows));  
    128.        //方法二  
    129.        //imageROI=srcImage4(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));  
    130.    
    131.        //【3】将logo加到原图上  
    132.        addWeighted(imageROI,0.5,logoImage,0.3,0.,imageROI);  
    133.    
    134.        //【4】显示结果  
    135.        namedWindow("<4>区域线性图像混合示例窗口 by浅墨");  
    136.        imshow("<4>区域线性图像混合示例窗口 by浅墨",srcImage4);  
    137.         
    138.        return true;  
    139. }  
    复制代码




最后看一下整体的运行效果图。
首先是经过背景颜色修改的console窗口:


然后依次是四张效果图:







点赞 关注(1)

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表