原文:Alpha Blending using OpenCV (C++ / Python) - 2019.04.10

作者:Sunita Nayak

Matting - 图像抠图简记 - AIUAI 中简单说明了下抠图问题的定义及采用 PIL Image.blend() 函数的图像融合.

这里,主要关于 如何将透明 PNG 图(Alpha图)与图片进行融合. 并给出了基于 OpenCV 的 C++ 和 Python 实现.

1. Alpha 通道融合(Alpha blending)

Alpha 融合是将一张前景透明图像覆盖到一张背景图片上的过程.

透明图往往是四通道的图像(如,透明 PNG 图),且是可分离的. 透明 mask 图也被叫作 alpha maskalpha matte.

图:(上左)-前景图像;(上右)-alpha mask;(下左)-背景图像;(下右)-最终组合图像. From: http://www.alphamatting.com/datasets.phphttps://www.publicdomainpictures.net/en/view-image.php?image=21543&picture=drink-on-beach.

如图,左上图为前景图像,右上图为 alpha mask 图,下左为背景图像,下右为采用 alpha mask 将前景图像和背景图像融合得到的组合图.

Alpha blending 背后的数据公式为:

$$ I = \alpha F +(1 - \alpha)B $$

对于图像的每个像素,需要采用 alpha mask ($\alpha$) 将前景图像($F$) 和背景图像($B$)进行组合.

其中,$0 \leq \alpha \leq 1$.

[1] - 当 $\alpha = 0$ 时,输出像素值为背景图像.

[2] - 当 $\alpha = 1$ 时,输出像素值为前景图像.

[3] - 当 $ 0 < \alpha < 1$ 时,输出像素值是背景图像和前景图像的融合. 实际场景中,alpha mask 边界处的像素值往往在 [0, 1] 区间.

2. Python 实现

import cv2
 
#读取图像
foreground = cv2.imread("puppets.png")  #前景
background = cv2.imread("ocean.png")    #背景
alpha = cv2.imread("puppets_alpha.png") #alpha
 
#Convert uint8 to float
foreground = foreground.astype(float)
background = background.astype(float)
 
#Normalize the alpha mask to keep intensity between 0 and 1
alpha = alpha.astype(float)/255
 
#Multiply the foreground with the alpha matte
foreground = cv2.multiply(alpha, foreground)
 
#Multiply the background with ( 1 - alpha )
background = cv2.multiply(1.0 - alpha, background)
 
#Add the masked foreground and background.
outImage = cv2.add(foreground, background)
 
#Display image
cv2.imshow("outImg", outImage/255)
cv2.waitKey(0)

3. C++ 实现

#include opencv2/opencv.hpp
 
using namespace cv;
using namespace std;
 
int main(int argc, char** argv)
{
 
    //Read the images
    Mat foreground = imread("puppets.png");
    Mat background = imread("ocean.png");
    Mat alpha = imread("puppets_alpha.png");
     
    //Convert Mat to float data type
    foreground.convertTo(foreground, CV_32FC3);
    background.convertTo(background, CV_32FC3);
    
    //Normalize the alpha mask to keep intensity between 0 and 1
    alpha.convertTo(alpha, CV_32FC3, 1.0/255); // 
 
    //Storage for output image
    Mat ouImage = Mat::zeros(foreground.size(), foreground.type());
 
    //Multiply the foreground with the alpha matte
    multiply(alpha, foreground, foreground); 
 
    //Multiply the background with ( 1 - alpha )
    multiply(Scalar::all(1.0)-alpha, background, background); 
 
    //Add the masked foreground and background.
    add(foreground, background, ouImage); 
     
    //Display image
    imshow("alpha blended image", ouImage/255);
    waitKey(0);
     
    return 0;
}

4. 更有效的 C++ 实现

上述的Python和C++实现的 alpha blending 代码是比较整洁的,但其效率不够. 因为在 alpha blending 的过程中包含两次对前景图像的处理,分别用于与 alpha 相乘和与 masked 背景图像的相加. 类似地,对背景图像也包含类似的处理.

对于小尺寸(如250x250)和中尺寸(如800x800) 的图像,上述实现还能应付,但其效率是很有待提升的,尤其是对于大尺寸(如 2000x2000)的图像.

void alphaBlend(Mat& foreground, Mat& background, Mat& alpha, Mat& outImage)
{
     //Find number of pixels. 
     int numberOfPixels = foreground.rows * foreground.cols * foreground.channels();
 
     //Get floating point pointers to the data matrices
     float* fptr = reinterpret_cast<float*>(foreground.data);
     float* bptr = reinterpret_cast<float*>(background.data);
     float* aptr = reinterpret_cast<float*>(alpha.data);
     float* outImagePtr = reinterpret_cast<float*>(outImage.data);
 
     //Loop over all pixesl ONCE
     for( 
       int i = 0; 
       i < numberOfPixels; 
       i++, outImagePtr++, fptr++, aptr++, bptr++
     )
     {
         *outImagePtr = (*fptr)*(*aptr) + (*bptr)*(1 - *aptr);
     }
}

5. 效率对比

对比 C++ 实现更有效的 C++ 实现 对于不同尺寸图像的效率对比. 如下表(运行 3000 次以上求平均后的值). 可以看出,对于大尺寸大图像,后者具有更快的速度,耗费时间几乎减半. 有一点疑问之处在于,对于小尺寸图片,后者的效率反而更慢了.

图像尺寸Using functions - multiply, add (in milliseconds)Using direct access (in milliseconds)
230 x 1620.1560.364
1000 x 7047.8567.102
2296 x 161754.01439.985
4592 x 3234355.502161.34
Last modification:August 27th, 2019 at 06:01 pm