Как стыковать png с альфой/прозрачностью в кадре в реальном времени

Я работаю на примере 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;
    }

person VTR2015    schedule 28.04.2016    source источник
comment
Спрашиваете, как альфа-смешивание с opencv? (Смотрите пояснение в конце и перенесите эти две строки на java).   -  person Dan Mašek    schedule 28.04.2016
comment
Я проверил ваш код, и получилось следующее: PNG с черным фоном и альфа-эффектом. То есть png видимо заряжается этим черным фоном но исходное изображение без фона!   -  person VTR2015    schedule 28.04.2016
comment
@DanMašek, спасибо за ответ, но я попробовал этот носитель и не смог. если изображение png становится полностью прозрачным, оставляя только видимые контуры изображения. Нужно удалить черную область, которая изначально прозрачна... Независимо от комбинации значений альфа, бета и гамма, и результат не ожидается... Core.addWeighted(mRgba.submat(eyeArea), 1, maskEye, 1, 1, mRgba.submat(область глаза));   -  person VTR2015    schedule 12.05.2016
comment
эй @ VTR2015, ты перенес код Python из ответа DanMašek на Java? можешь поделиться?   -  person delkant    schedule 10.05.2019
comment
Я думаю, что в этой теме есть более простое решение: stackoverflow.com/questions/47248053/   -  person Alexei Masterov    schedule 07.07.2020


Ответы (1)


Примечание. Я объясню общий принцип и приведу пример реализации на Python, так как у меня не настроена среда разработки Android. Должно быть довольно просто перенести это на Java. Не стесняйтесь публиковать свой код как отдельный ответ.


Вам нужно сделать что-то похожее на то, что делает операция addWeighted, то есть операция

Формула линейной смеси

Однако в вашем случае должна быть матрица (т.е. нам нужен другой коэффициент смешивания для каждого пикселя).


Примеры изображений

Давайте используем несколько примеров изображений, чтобы проиллюстрировать это. Мы можем использовать изображение Лены в качестве образца лица:

Образец лица

Это изображение как наложение с прозрачностью:

Наложение с альфа-каналом

И это изображение как наложение без прозрачности:

Наложение без альфа-канала


Матрица смешивания

Чтобы получить альфа-матрицу, мы можем либо определить маски переднего плана (наложение) и фона (лицо) с помощью пороговой обработки, либо использовать альфа-канал из входного изображения, если он доступен.

Это полезно выполнять для изображений с плавающей запятой со значениями в диапазоне 0,0 .. 1,0. Затем мы можем выразить отношение между двумя масками как

foreground_mask = 1.0 - background_mask

то есть две маски, сложенные вместе, дают все единицы.

Для накладываемого изображения в формате RGBA мы получаем следующие маски переднего плана и фона:

Маска переднего плана из прозрачности

Маска фона из прозрачности

Когда мы используем пороговое значение, эрозию и размытие в случае формата RGB, мы получаем следующие маски переднего плана и фона:

Маска переднего плана от порога

Фоновая маска от порога


Взвешенная сумма

Теперь мы можем вычислить две взвешенные части:

foreground_part = overlay_image * foreground_mask
background_part = face_image * background_mask

Для наложения RGBA части переднего плана и фона выглядят следующим образом:

Часть переднего плана (наложение RGBA)

Фоновая часть (наложение RGBA)

А для наложения RGB части переднего плана и фона выглядят так:

Часть переднего плана (наложение RGB)

Фоновая часть (наложение RGB)


И, наконец, сложите их вместе и преобразуйте изображение обратно в 8-битные целые числа в диапазоне 0-255.

Результат операций выглядит следующим образом (наложение RGBA и RGB соответственно):

Объединено (наложение 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)
person Dan Mašek    schedule 12.05.2016
comment
Этот код (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) - person xabi; 10.02.2017
comment
Алгоритм смешивания требует, чтобы изображения были одинакового размера. Это легко исправить в вашем коде из другого вопроса, просто измените строку 35. до rotated = cv2.warpPerspective(glasses, M, (face.shape[1], face.shape[0])). - person Dan Mašek; 10.02.2017
comment
TMI... Слишком много изображений ;) Спасибо за такой прямой ответ! - person linusg; 18.04.2017
comment
@linusg :) Да, это немного тяжелое изображение, хотя все они, казалось, имели отношение к объяснению, когда я его писал (мне нравится предоставлять входные данные, чтобы позволить читателю воспроизвести его, а также показывать промежуточные шаги и полученные результаты). Тем не менее, если у вас есть идеи/предложения по его улучшению, дайте мне знать (или, что еще лучше, отредактируйте ответ напрямую). Рад, что это было полезно. - person Dan Mašek; 18.04.2017
comment
Нет, шучу :P Мне также нравится иметь все шаги между вводом и выводом, так легче понять все шаги. Кстати, только что протестировал код в моем проекте минуту назад, и он отлично работает: D - person linusg; 18.04.2017
comment
@EB Конечно, но в этом случае имеет значение только первый вариант, поскольку JPEG не поддерживает прозрачность. Вам также, возможно, придется настроить часть, которая определяет маску, так как могут быть некоторые артефакты, вызванные сжатием с потерями. - person Dan Mašek; 17.11.2018
comment
Спасибо вам большое за это! - person drew181; 12.06.2020
comment
привет Дэн Масек. Могу я кое-что спросить? Когда я пробую ваш ccode для своего видео (не изображения), я получаю следующую ошибку ==> overlay_img = overlay_t_img[:,:,:3] # Захват плоскостей BRG TypeError: объект 'cv2.VideoCapture' не подлежит подписке. Я знаю, что это значит, но я не знаю, как это решить. могу ли я попросить вас руководство для решения этой проблемы? - person Ö. ALP EREN GÜL; 06.02.2021