如何在帧中实时加入具有 alpha / 透明度的 png

Posted

技术标签:

【中文标题】如何在帧中实时加入具有 alpha / 透明度的 png【英文标题】:How to join png with alpha / transparency in a frame in realtime 【发布时间】:2016-08-23 14:53:57 【问题描述】:

我正在使用相机检测人脸的 OpenCV android 2.4.11 示例下工作。 我没有在找到的脸上画一个矩形,而是试图在脸上放一个面具(png图像)。 但是为了在脸上显示图像,png 图像带有黑色背景,并且有透明度。

FdActivity.java

public void onCameraViewStarted(int width, int height) 
        mGray = new Mat();
        mRgba = new Mat();

        //Load my mask png
        Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.mask_1);

        mask = new Mat();

        Utils.bitmapToMat(image, mask);



public Mat onCameraFrame(CvCameraViewFrame inputFrame) 

        mRgba = inputFrame.rgba();
        mGray = inputFrame.gray();

        if (mAbsoluteFaceSize == 0) 
            int height = mGray.rows();
            if (Math.round(height * mRelativeFaceSize) > 0) 
                mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
            
            mNativeDetector.setMinFaceSize(mAbsoluteFaceSize);
        

        MatOfRect faces = new MatOfRect();

        if (mDetectorType == JAVA_DETECTOR) 
            if (mJavaDetector != null)
                mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2,
                        new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
        
        else if (mDetectorType == NATIVE_DETECTOR) 
            if (mNativeDetector != null)
                mNativeDetector.detect(mGray, faces);
        
        else 
            Log.e(TAG, "Detection method is not selected!");
        

        Rect[] facesArray = faces.toArray();


        for (int i = 0; i < facesArray.length; i++) 

              overlayImage(mRgba, mask, facesArray[i]);

        

        return mRgba;
    

    public Mat overlayImage(Mat background, Mat foregroundMask, Rect faceRect)
    
        Mat mask = new Mat();

        Imgproc.resize(this.mask, mask, faceRect.size());

        Mat source = new Mat();
        Imgproc.resize(foregroundMask, source, background.size());

        mask.copyTo( background.submat( new Rect((int) faceRect.tl().x, (int) faceRect.tl().y, mask.cols(), mask.rows())) );

        source.release();
        mask.release();
        return background;
    

【问题讨论】:

正在询问如何alpha blend with opencv? (见最后的解释,并将这两行移植到 java)。 我检查了你的代码,结果是那个带有阿尔法效果的黑色背景的 PNG。也就是说,png显然是被这个黑色背景充电的,但原始图像是没有背景的! @DanMašek,感谢您的回复,但我尝试了这种媒体,但我做不到。如果 png 图像变得完全透明,只留下可见的图像轮廓。需要去除原本透明的黑色区域... 无论 alpha 值、beta 和 gamma 组合,结果都不是预期的... Core.addWeighted(mRgba.submat(eyeArea), 1, maskEye, 1, 1、mRgba.submat(eyeArea)); 嘿@VTR2015 你有没有从 DanMašek 对 Java 的回答中移植 python 代码?可以分享一下吗? 我认为这个帖子有一个更简单的解决方案:***.com/questions/47248053/… 【参考方案1】:

注意:由于我没有设置Android开发环境,我将解释一般原理并给你一个Python的示例实现。将它移植到 Java 应该相当简单。随时将您的代码作为单独的答案发布。


你需要做一些类似于addWeighted操作的操作,也就是操作

但是,在您的情况下,α 需要是一个矩阵(即我们需要每个像素不同的混合系数)。


示例图片

让我们使用一些示例图像来说明这一点。我们可以使用 Lena 图像作为样本人脸:

此图像作为具有透明度的叠加层:

而这张图片是没有透明度的叠加层:


混合矩阵

要获得 alpha 矩阵,我们可以使用阈值处理来确定前景(覆盖)和背景(面部)蒙版,或者使用输入图像中的 alpha 通道(如果可用)。

对值在 0.0 .. 1.0 范围内的浮点图像执行此操作很有用。然后我们可以将两个掩码之间的关系表示为

foreground_mask = 1.0 - background_mask

即两个掩码相加得到全部为 1。

对于 RGBA 格式的叠加图像,我们得到以下前景和背景蒙版:

当我们在 RGB 格式的情况下使用阈值、腐蚀和模糊时,我们会得到以下前景和背景蒙版:


加权和

现在我们可以计算两个加权部分:

foreground_part = overlay_image * foreground_mask
background_part = face_image * background_mask

对于 RGBA 叠加,前景和背景部分如下所示:

对于 RGB 叠加,前景和背景部分如下所示:


最后将它们相加,并将图像转换回 0-255 范围内的 8 位整数。

运算结果如下(分别为RGBA和RGB叠加):


代码示例 - RGB 叠加

import numpy as np
import cv2

# ==============================================================================

def blend_non_transparent(face_img, overlay_img):
    # Let's find a mask covering all the non-black (foreground) pixels
    # NB: We need to do this on grayscale version of the image
    gray_overlay = cv2.cvtColor(overlay_img, cv2.COLOR_BGR2GRAY)
    overlay_mask = cv2.threshold(gray_overlay, 1, 255, cv2.THRESH_BINARY)[1]

    # Let's shrink and blur it a little to make the transitions smoother...
    overlay_mask = cv2.erode(overlay_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
    overlay_mask = cv2.blur(overlay_mask, (3, 3))

    # And the inverse mask, that covers all the black (background) pixels
    background_mask = 255 - overlay_mask

    # Turn the masks into three channel, so we can use them as weights
    overlay_mask = cv2.cvtColor(overlay_mask, cv2.COLOR_GRAY2BGR)
    background_mask = cv2.cvtColor(background_mask, cv2.COLOR_GRAY2BGR)

    # Create a masked out face image, and masked out overlay
    # We convert the images to floating point in range 0.0 - 1.0
    face_part = (face_img * (1 / 255.0)) * (background_mask * (1 / 255.0))
    overlay_part = (overlay_img * (1 / 255.0)) * (overlay_mask * (1 / 255.0))

    # And finally just add them together, and rescale it back to an 8bit integer image
    return np.uint8(cv2.addWeighted(face_part, 255.0, overlay_part, 255.0, 0.0))

# ==============================================================================

# We load the images
face_img = cv2.imread("lena.png", -1)
overlay_img = cv2.imread("overlay.png", -1)

result_1 = blend_non_transparent(face_img, overlay_img)
cv2.imwrite("merged.png", result_1)

代码示例 - RGBA 覆盖

import numpy as np
import cv2

# ==============================================================================

def blend_transparent(face_img, overlay_t_img):
    # Split out the transparency mask from the colour info
    overlay_img = overlay_t_img[:,:,:3] # Grab the BRG planes
    overlay_mask = overlay_t_img[:,:,3:]  # And the alpha plane

    # Again calculate the inverse mask
    background_mask = 255 - overlay_mask

    # Turn the masks into three channel, so we can use them as weights
    overlay_mask = cv2.cvtColor(overlay_mask, cv2.COLOR_GRAY2BGR)
    background_mask = cv2.cvtColor(background_mask, cv2.COLOR_GRAY2BGR)

    # Create a masked out face image, and masked out overlay
    # We convert the images to floating point in range 0.0 - 1.0
    face_part = (face_img * (1 / 255.0)) * (background_mask * (1 / 255.0))
    overlay_part = (overlay_img * (1 / 255.0)) * (overlay_mask * (1 / 255.0))

    # And finally just add them together, and rescale it back to an 8bit integer image    
    return np.uint8(cv2.addWeighted(face_part, 255.0, overlay_part, 255.0, 0.0))

# ==============================================================================

# We load the images
face_img = cv2.imread("lena.png", -1)
overlay_t_img = cv2.imread("overlay_transparent.png", -1) # Load with transparency

result_2 = blend_transparent(face_img, overlay_t_img)
cv2.imwrite("merged_transparent.png", result_2)

【讨论】:

这段代码(blend_transparent)给了我这个错误:File "./test.py", line 19, in blend_transparent face_part = (face_img * (1 / 255.0)) * (background_mask * (1 / 255.0)) ValueError: operands could not be broadcast together with shapes (614,500,3) (640,500,3) 混合算法要求图像大小相同。从另一个问题解决 your code 中的问题很简单,只需将第 35 行更改为 rotated = cv2.warpPerspective(glasses, M, (face.shape[1], face.shape[0])) TMI...图片太多;)感谢您的直截了当的回答! @linusg :) 是的,图像有点重,尽管它们似乎都与我编写它时的解释相关(我喜欢提供输入以允许读者重现它,因为以及显示中间步骤和结果)。也就是说,如果您对如何改进它有一些想法/建议,请告诉我(或者更好的是,直接编辑答案)。很高兴它很有用。 不,开个玩笑 :P 我也喜欢输入和输出之间的所有步骤,这样更容易理解所有步骤。顺便说一句,一分钟前刚刚在我的项目中测试了代码,它工作得很好:D

以上是关于如何在帧中实时加入具有 alpha / 透明度的 png的主要内容,如果未能解决你的问题,请参考以下文章

如何在 react-native 中实现具有透明背景的视频

PyGame:具有每像素 alpha 的半透明精灵

如何设置 UIImage 的不透明度/alpha?

MPDF如何使用alpha透明?

Libgdx/Opengl alpha blending(结果 alpha 被源 alpha 替换)

具有 haskell GLUT 绑定的透明度/Alpha