Algoritmo de matización ModNet y ejemplo de matización de cámara en tiempo real

Tabla de contenido

1. Razones para usar pantalla verde en video matting

1. El motivo del color de la cámara.

2. Razones para el efecto de corte

3. Costo económico

2. Conocimiento previo del recorte

1, Trimapa

2. ¿Qué es el recorte?

3. Clasificación de los algoritmos de matización

3. Algoritmo de superposición de imágenes profundas

1. Diagrama de estructura de red

2. Interpretación de algoritmos

(1) Etapa codificador-decodificador

(2) Etapa de refinamiento

4. Algoritmo ModNet: Matizado de retratos sin Trimap en tiempo real

1. Diagrama de estructura de red

2. Interpretación de algoritmos

Cinco, práctica de esteras ModNet


1. Razones para usar pantalla verde en video matting

1. El motivo del color de la cámara.

El sensor principal de la cámara es RGB de tres canales, por lo que para lograr un efecto mate más preciso, es mejor usar el color original de los tres colores primarios. Además, la mayor parte de la matriz del sensor CMOS de la cámara adopta una matriz de Bayer. Hay 2 puntos fotosensibles verdes en la matriz, más altos que el rojo y el azul, por lo que la información es más rica y fácil de eliminar.

2. Razones para el efecto de corte

La mayoría de los personajes y máscaras en el video son colores complementarios verdes con alto contraste, lo que facilita que la computadora distinga los bordes y los pelos texturizados durante el procesamiento de renderizado, lo que reduce la carga de trabajo del mate.

3. Costo económico

El fondo verde tiene un alto brillo y el brillo se puede reducir para ahorrar energía al disparar.

2. Conocimiento previo del recorte

Retrato mate: descripción general del algoritmo e implementación de ingeniería (1) - Comunidad en la nube - HUAWEI CLOUD

1, Trimapa

El conocimiento previo más utilizado es un mapa ternario, cada píxel es uno de {0, 128, 255}, que representa el primer plano, el desconocido y el fondo, respectivamente.

2. ¿Qué es el recorte?

Para una imagen I, la parte del retrato que nos interesa se llama el primer plano F, y el resto es el fondo B, entonces la imagen I puede considerarse como una fusión ponderada de F y B: I= alpha ∗
F + ( 1−alfa) B alfa La forma es consistente con I.

La tarea de matización es encontrar la matriz alfa de peso adecuada.

El proceso de fusionar la imagen de primer plano y la imagen de fondo de acuerdo con la fórmula anterior se ejemplifica de la siguiente manera:

Supongamos que la parte del círculo central de una imagen es el primer plano y el resto es el fondo. Después de combinar las dos imágenes anteriores de acuerdo con la fórmula, el círculo central son todos los píxeles relacionados con el primer plano y el exterior del círculo son todos los píxeles relacionados con el fondo. Alfa corresponde a la matriz de probabilidad de la imagen de primer plano.

Si se completó el entrenamiento alfa, si desea completar el recorte de una imagen, solo necesita alfa * imagen original + (1-alfa) * imagen de fondo blanco.

Alpha es un valor continuo entre [0, 1], que puede entenderse como la probabilidad de que un píxel pertenezca al primer plano, lo que es diferente de la segmentación de retrato. En la tarea de segmentación de retratos, alfa solo puede ser 0 o 1, que es esencialmente una tarea de clasificación, mientras que el matizado es una tarea de regresión.

Para la verdad básica de la tarea de corte de imagen, se puede observar que los valores se distribuyen entre 0 y 1.

La verdad básica de la segmentación semántica puede ver que el valor es 0 o 1.

3. Clasificación de los algoritmos de matización

Los algoritmos de matización actualmente populares se pueden dividir aproximadamente en dos categorías.

Uno es el método basado en Trimap que requiere información previa. La información previa amplia incluye Trimap, máscara aproximada, imagen de fondo no tripulada, información de pose, etc. La red utiliza información previa e información de imagen para predecir conjuntamente alfa

El otro es el método sin Trimap, que predice alfa solo en función de la información de la imagen, que es más amigable para las aplicaciones prácticas, pero el efecto generalmente no es tan bueno como el método basado en Trimap.

La corriente principal actual es el algoritmo sin recorte.

3. Algoritmo de superposición de imágenes profundas

1. Diagrama de estructura de red

2. Interpretación de algoritmos

La red incluye la etapa de Codificador-Decodificador y la etapa de Refinamiento

(1) Etapa codificador-decodificador

La entrada es el parche de la imagen RGB y el concat correspondiente al trimap, por lo que contiene 4 canales, y la pred alfa sin procesar de un solo canal se emite después de codificar y decodificar. La pérdida de esta etapa consta de dos partes:

La primera parte es el error absoluto entre el alfa predicho y el alfa real. Teniendo en cuenta que la pérdida L1 no es diferenciable en 0, use la pérdida de Charbonnier para aproximar:

La segunda parte es el error absoluto entre la imagen RGB compuesta por el alfa predicho, el primer plano real y el fondo real, y la imagen RGB real. Su función es imponer restricciones en la red, y también se utiliza la pérdida de Charbonnier para aproximar:

La pérdida final es una suma ponderada de dos partes:

(2) Etapa de refinamiento

Su entrada es la concatenación de la salida alfa sin procesar de la etapa de codificador-decodificador y la imagen RGB original, que también tiene 4 canales, y el RGB original puede proporcionar información de detalles de límites para refinar. El objetivo es utilizar una conexión de salto para realizar una operación de adición en la salida de predicción alfa sin procesar de la etapa de codificador-descodificador y la salida de predicción alfa refinada de la etapa de refinamiento, y luego generar el resultado final de la predicción. De hecho, la etapa de Refinamiento es un bloque residual, y la información de los límites se modela a través del aprendizaje residual, que es exactamente igual que el modelado de ruido del modelo de eliminación de ruido.

Solo hay una pérdida en la etapa de refinamiento: alfa pred refinada y mate alfa GT calculan la pérdida de Charbonnier.

4. Algoritmo ModNet: Matizado de retratos sin Trimap en tiempo real

1. Diagrama de estructura de red

2. Interpretación de algoritmos

La estructura de la red consta de: rama de estimación semántica, rama de predicción de detalle y rama de fusión semántica-detalle.

Cinco, práctica de esteras ModNet

Artículo de referencia:

[Estera] MODNet: Retrato en tiempo real de la estera model-onnx python deployment_onnx model download_Dudu Taicai Blog-CSDN Blog

Enlace del modelo onnix del autor original: https://download.csdn.net/download/qq_40035462/85046509

Ejemplo de código:

import cv2
import time
from tqdm import tqdm
import numpy as np
import onnxruntime as rt


class Matting:
    def __init__(self, model_path='onnx_model\modnet.onnx', input_size=(512, 512)):
        self.model_path = model_path
        self.sess = rt.InferenceSession(self.model_path, providers=['CUDAExecutionProvider'])
        # self.sess = rt.InferenceSession(self.model_path)  # 默认使用cpu
        self.input_name = self.sess.get_inputs()[0].name
        self.label_name = self.sess.get_outputs()[0].name
        self.input_size = input_size
        self.txt_font = cv2.FONT_HERSHEY_PLAIN

    def normalize(self, im, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]):
        im = im.astype(np.float32, copy=False) / 255.0
        im -= mean
        im /= std
        return im

    def resize(self, im, target_size=608, interp=cv2.INTER_LINEAR):
        if isinstance(target_size, list) or isinstance(target_size, tuple):
            w = target_size[0]
            h = target_size[1]
        else:
            w = target_size
            h = target_size
        im = cv2.resize(im, (w, h), interpolation=interp)
        return im

    def preprocess(self, image, target_size=(512, 512), interp=cv2.INTER_LINEAR):
        image = self.normalize(image)
        image = self.resize(image, target_size=target_size, interp=interp)
        image = np.transpose(image, [2, 0, 1])
        image = image[None, :, :, :]
        return image

    def predict_frame(self, bgr_image):
        assert len(bgr_image.shape) == 3, "Please input RGB image."
        raw_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB)
        h, w, c = raw_image.shape
        image = self.preprocess(raw_image, target_size=self.input_size)

        pred = self.sess.run(
            [self.label_name],
            {self.input_name: image.astype(np.float32)}
        )[0]
        pred = pred[0, 0]
        matte_np = self.resize(pred, target_size=(w, h), interp=cv2.INTER_NEAREST)
        matte_np = np.expand_dims(matte_np, axis=-1)
        return matte_np

    def predict_image(self, source_image_path, save_image_path):
        bgr_image = cv2.imread(source_image_path)
        assert len(bgr_image.shape) == 3, "Please input RGB image."
        matte_np = self.predict_frame(bgr_image)
        matting_frame = matte_np * bgr_image + (1 - matte_np) * np.full(bgr_image.shape, 255.0)
        matting_frame = matting_frame.astype('uint8')
        cv2.imwrite(save_image_path, matting_frame)

    def predict_camera(self):
        cap_video = cv2.VideoCapture(0)
        if not cap_video.isOpened():
            raise IOError("Error opening video stream or file.")
        beg = time.time()
        count = 0
        while cap_video.isOpened():
            ret, raw_frame = cap_video.read()
            if ret:
                count += 1
                matte_np = self.predict_frame(raw_frame)
                matting_frame = matte_np * raw_frame + (1 - matte_np) * np.full(raw_frame.shape, 255.0)
                matting_frame = matting_frame.astype('uint8')

                end = time.time()
                fps = round(count / (end - beg), 2)
                if count >= 50:
                    count = 0
                    beg = end

                cv2.putText(matting_frame, "fps: " + str(fps), (20, 20), self.txt_font, 2, (0, 0, 255), 1)

                cv2.imshow('Matting', matting_frame)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break
            else:
                break
        cap_video.release()
        cv2.destroyWindow()

    def check_video(self, src_path, dst_path):
        cap1 = cv2.VideoCapture(src_path)
        fps1 = int(cap1.get(cv2.CAP_PROP_FPS))
        number_frames1 = cap1.get(cv2.CAP_PROP_FRAME_COUNT)
        cap2 = cv2.VideoCapture(dst_path)
        fps2 = int(cap2.get(cv2.CAP_PROP_FPS))
        number_frames2 = cap2.get(cv2.CAP_PROP_FRAME_COUNT)
        assert fps1 == fps2 and number_frames1 == number_frames2, "fps or number of frames not equal."

    def predict_video(self, video_path, save_path, threshold=2e-7):
        # 使用odf策略
        time_beg = time.time()
        pre_t2 = None  # 前2步matte
        pre_t1 = None  # 前1步matte

        cap = cv2.VideoCapture(video_path)
        fps = int(cap.get(cv2.CAP_PROP_FPS))
        size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
                int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
        number_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
        print("source video fps: {}, video resolution: {}, video frames: {}".format(fps, size, number_frames))
        videoWriter = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size)

        ret, frame = cap.read()
        with tqdm(range(int(number_frames))) as t:
            for c in t:
                matte_np = self.predict_frame(frame)
                if pre_t2 is None:
                    pre_t2 = matte_np
                elif pre_t1 is None:
                    pre_t1 = matte_np
                    # 第一帧写入
                    matting_frame = pre_t2 * frame + (1 - pre_t2) * np.full(frame.shape, 255.0)
                    videoWriter.write(matting_frame.astype('uint8'))
                else:
                    # odf
                    error_interval = np.mean(np.abs(pre_t2 - matte_np))
                    error_neigh = np.mean(np.abs(pre_t1 - pre_t2))
                    if error_interval < threshold < error_neigh:
                        pre_t1 = pre_t2

                    matting_frame = pre_t1 * frame + (1 - pre_t1) * np.full(frame.shape, 255.0)
                    videoWriter.write(matting_frame.astype('uint8'))
                    pre_t2 = pre_t1
                    pre_t1 = matte_np

                ret, frame = cap.read()
            # 最后一帧写入
            matting_frame = pre_t1 * frame + (1 - pre_t1) * np.full(frame.shape, 255.0)
            videoWriter.write(matting_frame.astype('uint8'))
            cap.release()
        print("video matting over, time consume: {}, fps: {}".format(time.time() - time_beg, number_frames / (time.time() - time_beg)))


if __name__ == '__main__':
    model = Matting(model_path='onnx_model\modnet.onnx', input_size=(512, 512))
    model.predict_camera()
    # model.predict_image('images\\1.jpeg', 'output\\1.png')
    # model.predict_image('images\\2.jpeg', 'output\\2.png')
    # model.predict_image('images\\3.jpeg', 'output\\3.png')
    # model.predict_image('images\\4.jpeg', 'output\\4.png')
    # model.predict_video("video\dance.avi", "output\dance_matting.avi")

Consulte el archivo adjunto superior para el archivo modnet.onnx involucrado en el código. 

Supongo que te gusta

Origin blog.csdn.net/benben044/article/details/131136506
Recomendado
Clasificación