Cómo unir png con alfa / transparencia en un cuadro en tiempo real

Estoy trabajando en el ejemplo de OpenCV android 2.4.11 que detecta rostros usando la cámara. En lugar de dibujar un rectángulo en la cara encontrada, estoy tratando de colocar una máscara (imagen png) en la cara. Pero para mostrar la imagen en la cara, la imagen png viene con un fondo negro donde había transparencia.

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; } 

Nota: explicaré el principio general y le daré un ejemplo de implementación en Python, ya que no tengo configurado el entorno de desarrollo de Android. Debería ser bastante sencillo trasladar esto a Java. Siéntase libre de publicar su código como una respuesta por separado.


addWeighted hacer algo similar a lo que hace la operación addWeighted , que es la operación

Fórmula de mezcla lineal

Sin embargo, en su caso, α debe ser una matriz (es decir, necesitamos un coeficiente de mezcla diferente por píxel).


Imágenes de muestra

Vamos a usar algunas imágenes de muestra para ilustrar esto. Podemos usar la imagen de Lena como una cara de muestra:

Cara de muestra

Esta imagen como una superposición con transparencia:

Superposición con alfa

Y esta imagen como superposición sin transparencia:

Superposición sin alfa


Matriz de mezcla

Para obtener la matriz alfa , podemos determinar las máscaras de primer plano (superposición) y de fondo (la cara) utilizando el umbral, o usar el canal alfa de la imagen de entrada si está disponible.

Es útil realizar esto en imágenes de punto flotante con valores en el rango 0.0 .. 1.0. Entonces podemos express la relación entre las dos máscaras como

 foreground_mask = 1.0 - background_mask 

es decir, las dos máscaras sumdas dan como resultado todos unos.

Para la imagen de superposición en formato RGBA obtenemos las siguientes máscaras de fondo y primer plano:

Máscara de primer plano de la transparencia.

Máscara de fondo de la transparencia

Cuando usamos umbrales, erosionamos y difuminamos en el caso del formato RGB, obtenemos las siguientes máscaras de fondo y primer plano:

Máscara de primer plano desde el umbral

Máscara de fondo desde el umbral


Suma ponderada

Ahora podemos calcular dos partes ponderadas:

 foreground_part = overlay_image * foreground_mask background_part = face_image * background_mask 

Para la superposición RGBA, las partes de primer plano y de fondo tienen el aspecto siguiente:

Parte de primer plano (superposición RGBA)

Parte de fondo (superposición RGBA)

Y para la superposición RGB, las partes de fondo y primer plano se ven como tales:

Parte de primer plano (superposición RGB)

Parte de fondo (superposición RGB)


Y, finalmente, agréguelos y convierta la imagen a números enteros de 8 bits en el rango 0-255.

El resultado de las operaciones se ve de la siguiente manera (superposición de RGBA y RGB respectivamente):

Fusionado (superposición RGBA)

Fusionado (superposición RGB)


Ejemplo de código – Superposición 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) 

Ejemplo de código – Superposición 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)