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
Sin embargo, en su caso, α debe ser una matriz (es decir, necesitamos un coeficiente de mezcla diferente por píxel).
Vamos a usar algunas imágenes de muestra para ilustrar esto. Podemos usar la imagen de Lena como una cara de muestra:
Esta imagen como una superposición con transparencia:
Y esta imagen como superposición sin transparencia:
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:
Cuando usamos umbrales, erosionamos y difuminamos en el caso del formato RGB, obtenemos las siguientes máscaras de fondo y primer plano:
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:
Y para la superposición RGB, las partes de fondo y primer plano se ven como tales:
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):
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)
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)