OpenCV2马拉松第4圈——图片混合

收入囊中

  • 了解线性混合
  • 会使用addWeighted进行任意混合

葵花宝典
Computer Vision:Algorithm and Application(以后我简称CVAA)的3.1.3提到: C=(1α)B+αF. 
B是背景色,F是前景色,这是一个万能公式(其实越万能的公式使用起来越不方便,这个公式貌似什么都没说)
CVAA的这一小节是讲抠图与合成,抠图难度比较大


初识API


下面我进入Sample文件夹下的ImgProc子目录,首先研究下AddingImages.cpp,下面是源代码

#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace cv;

int main( void )
{

   double alpha = 0.5; double beta; double input;

   Mat src1, src2, dst;

   /// Ask the user enter alpha
   std::cout<<" Simple Linear Blender "<<std::endl;
   std::cout<<"-----------------------"<<std::endl;
   std::cout<<"* Enter alpha [0-1]: ";
   std::cin>>input;

   // We use the alpha provided by the user iff it is between 0 and 1
   if( alpha >= 0 && alpha <= 1 )
     { alpha = input; }

   /// Read image ( same size, same type )
   src1 = imread("../images/LinuxLogo.jpg");
   src2 = imread("../images/WindowsLogo.jpg");

   if( !src1.data ) { std::cout<< "Error loading src1"<<std::endl; return -1; }
   if( !src2.data ) { std::cout<< "Error loading src2"<<std::endl; return -1; }

   /// Create Windows
   namedWindow("Linear Blend", 1);

   beta = ( 1.0 - alpha );
   addWeighted( src1, alpha, src2, beta, 0.0, dst);

   imshow( "Linear Blend", dst );


   waitKey(0);
   return 0;
}

代码很简单,第一幅图像占的比例是alpha,第二幅是1-alpha

dst = src1[I]*alpha+ src2[I]*beta + gamma;

下面我们看下addWeighted的源代码,在arithm.cpp中


void cv::addWeighted( InputArray src1, double alpha, InputArray src2,
                      double beta, double gamma, OutputArray dst, int dtype )
{
    double scalars[] = {alpha, beta, gamma};
    arithm_op(src1, src2, dst, noArray(), dtype, getAddWeightedTab(), true, scalars);
}

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

下面我们要仔细来看看arithm_op这个函数了
static void arithm_op(InputArray _src1, InputArray _src2, OutputArray _dst,
               InputArray _mask, int dtype, BinaryFunc* tab, bool muldiv=false, void* usrdata=0)

我们发现 addWeighted中传了一个noArray(),就是没有掩码了,还有个tab参数,这是什么呢,看名字好像是一个函数指针?往上看,我发现了
static BinaryFunc* getAddWeightedTab()
{
    static BinaryFunc addWeightedTab[] =
    {
        (BinaryFunc)GET_OPTIMIZED(addWeighted8u), (BinaryFunc)GET_OPTIMIZED(addWeighted8s), (BinaryFunc)GET_OPTIMIZED(addWeighted16u),
        (BinaryFunc)GET_OPTIMIZED(addWeighted16s), (BinaryFunc)GET_OPTIMIZED(addWeighted32s), (BinaryFunc)addWeighted32f,
        (BinaryFunc)addWeighted64f, 0
    };

    return addWeightedTab;
}
看样子这些事函数指针的数组,那么GET_OPTIMIZED又是什么呢
#define GET_OPTIMIZED(func) (func)
很多定义都来不及细看,下次有需要再细细研究,这些addWeighted8u等函数都在同个cpp下面定义了,我们的tab就是一系列函数指针。在arithm_op有一行函数是这样的,
BinaryFunc func = tab[CV_MAT_DEPTH(wtype)];
根据数据类型拿到相应的加权函数

arithm_op中的函数大多都是数据类型,掩码的这种判断,主要操作都是在这个func里进行的,而里面又调用了addWeighted_这个模版函数
template<typename T, typename WT> static void
addWeighted_( const T* src1, size_t step1, const T* src2, size_t step2,
              T* dst, size_t step, Size size, void* _scalars )
{
    const double* scalars = (const double*)_scalars;
    WT alpha = (WT)scalars[0], beta = (WT)scalars[1], gamma = (WT)scalars[2];
    step1 /= sizeof(src1[0]);
    step2 /= sizeof(src2[0]);
    step /= sizeof(dst[0]);

    for( ; size.height--; src1 += step1, src2 += step2, dst += step )
    {
        int x = 0;
        #if CV_ENABLE_UNROLLED
        for( ; x <= size.width - 4; x += 4 )
        {
            T t0 = saturate_cast<T>(src1[x]*alpha + src2[x]*beta + gamma);
            T t1 = saturate_cast<T>(src1[x+1]*alpha + src2[x+1]*beta + gamma);
            dst[x] = t0; dst[x+1] = t1;

            t0 = saturate_cast<T>(src1[x+2]*alpha + src2[x+2]*beta + gamma);
            t1 = saturate_cast<T>(src1[x+3]*alpha + src2[x+3]*beta + gamma);
            dst[x+2] = t0; dst[x+3] = t1;
        }
        #endif
        for( ; x < size.width; x++ )
            dst[x] = saturate_cast<T>(src1[x]*alpha + src2[x]*beta + gamma);
    }
}
这个函数就是我们的算法所在,可以很清晰看到

dst = src1[I]*alpha+ src2[I]*beta + gamma;


举一反三

addWeighted函数要求我们两个矩阵大小必须相同,那如果我想要不一样怎么办?

其实非常非常简单,要知道在OpenCV2中,只有clone和copyto两个函数才是复制数据的,其他的都是只是新建Mat的Header(包括行列等信息),数据却还是指向原来的地方,ref count增加了1次.

因此,只要我们能取到矩阵的一部分,进行混合不就可以了么。

假设src1是1000*1000,而src是100*100的小人头,我们想把小人头和src1的(500,500)坐标处进行混合

方法1:blendimg = src1(Range(500, 500 + src2.rows), Range(500, 500 + src2.cols));  

方法2:blendimg = src1(Rect(500, 500, src2.cols, src2.rows));

再使用addWeighted函数即可

你可能感兴趣的:(opencv)