2

Como juntar dos vídeos con OpenCV+Python

¡Hola, humanos! Estos días he estado tratando de fusionar vídeos de varias formas con OpenCV y Python, y he pensado que es un tema suficientemente interesante como para escribir un artículo. ¡Y aquí estamos!

En este tutorial tendremos dos vídeos que vamos a “juntar” de tres formas diferentes:

  1. Colocar un vídeo encima del otro
  2. Fusionar las dos imágenes
  3. Empalmarlos (cuando acabe el primero, empezar el segundo)

En los tres casos, crearemos un archivo de vídeo con el output.

Si sóis lectores frecuentes de Robologs, sabéis que siempre que escribo un tutorial me gusta proponer una pieza de música (normalmente clásica) para que escuchéis mientras trabajamos. Hoy no podía decidirme por ningún autor en concreto, así que… ¿por qué no escucharlos todos a la vez?


Descargar vídeos de prueba

Para hacer este tutorial os voy a pasar dos vídeos, pero si queréis podéis usar otros. No hace falta que sean del mismo tamaño ni tengan la misma duración, pero sería bueno que el framerate fuera igual.

Descargar vídeo 1
Descargar vídeo 2

Los dos vídeos que os proporciono tienen el mismo tamaño y framerate (30 fps). Descargadlos y guardadlos en un directorio que recordéis.


1- Colocar un vídeo encima del otro

El primer mini-ejemplo consistirá en poner un vídeo encima del otro. Para hacerlo cargaremos los dos vídeos y extraeremos los frames individuales, uno a uno. Por cada frame, crearemos un array (que será el nuevo frame para el vídeo final) cuya altura será la suma de las alturas de los dos vídeos iniciales, y cuyo ancho será el mínimo de los dos vídeos.

Vídeos originales (izquierda) y vídeo final (derecha)

Bien, ¡vamos a ello! Empezad creando un fichero de texto llamado ‘code1.py’ dentro del mismo directorio dónde tengáis los vídeos.

Primero hay que importar la librería OpenCV y Numpy.

import numpy as np
import cv2

#Cargar los dos videos
video1 = cv2.VideoCapture('video1.mov')
video2 = cv2.VideoCapture('video2.mov')

Después, se guardan las dimensiones de los dos vídeos, y también se calcula el ancho del vídeo final buscando el mínimo entre los anchos de los vídeos iniciales.

#Guardar las dimensiones del primer video
ancho1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) 
alto1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#Guardar las dimensiones del segundo video
ancho2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#El ancho del video final sera el minimo de las dos longitudes horizontales
ancho = int(min(ancho1, ancho2))

Ahora hay que escoger un códec para encriptar el vídeo en el formato que queramos. Un códec es un algoritmo que sirve para comprimir y descomprimir vídeo digital, y lo necesitamos para poder crear un vídeo de output. Según el formato en el que queráis exportar, tendréis que usar un códec u otro. Para este ejemplo, como el vídeo final va a estar en formato ‘.avi’, usaremos el códec ‘XVID’.

Trabajar con los códecs de OpenCV es, y perdón por la expresión, un coñazo. La documentación es pobre y me he encontrado que no se puede exportar a algunos formatos como ‘.mp4’ o ‘.mpeg’ incluso usando el códec adecuado. El formato ‘.avi’ funciona bien, pero si queréis utilizar otro tendréis que ir probando códecs por ensayo y error.

#Usar un codec compatible con .avi
fourcc = cv2.cv.CV_FOURCC(*'XVID')

Para crear el vídeo de output, se utiliza un objeto de clase VideoWriter. El constructor recibe cuatro parámetros: el nombre del vídeo, el códec a utilizar, el framerate y las dimensiones (x,y) del vídeo. Es importante que el framerate sea el mismo que el de los vídeos que queráis juntar.

#Crear el objeto VideoWriter
out = cv2.VideoWriter('output.avi',fourcc, 30, (ancho,alto1+alto2))

Ahora hay que crear un bucle while infinito. Dentro, crearemos un array tridimensional vacío, que será el nuevo frame resultado de la unión de los frames de los dos vídeos. Utilizaremos la función np.zeros para que por defecto cada espacio del array contenga un cero.

¿Por qué un array tridimensional? Las componentes (x,y) del array se corresponden a las dimensiones de la imagen: ancho y largo. La tercera componente es un vector de tres elementos que se corresponde al color RGB del píxel. Por ejemplo: una imagen de 800×600 sería en realidad un array de 800x600x3, dónde el último ‘3’ es el valor RGB de cada píxel.

while(1):
    #En este frame guardaremos la union de los dos videos:
    frame = np.zeros((alto1+alto2,ancho,3), np.uint8)

También hay que leer un frame de cada vídeo. Las variables ‘frame1’ y ‘frame2’ guardan la imagen en sí, mientras que ‘ret1’ y ‘ret2’ son booleanos que sirven para indicar si el frame es válido o no. Un frame será válido si contiene una imagen.

Al leer el último frame de un vídeo se puede continuar leyendo frames, pero estos serán nulos. Los parámetros ‘ret1’ y ‘ret2’ nos servirán para saber cuándo pasa esto y, por tanto, cuándo hemos acabado de leer un vídeo.

    #Leer un frame de cada video
    ret1, frame1 = video1.read()
    ret2, frame2 = video2.read()

Antes de juntar los dos frames, hay que comprobar que ambos sean válidos. Si es así, se juntan los dos frames dentro del array vacío que hemos creado al principio del bucle, y se añade este nuevo frame al vídeo de output.

    #Mirar que ambos frames sean validos (uno de los videos podria ser mas corto que el otro)
    if ret1 and ret2:
        frame[0:alto2,0:ancho] = frame2 #Colocar el frame del primer video en la mitad superior
        frame[alto2:(alto2+alto1),0:ancho] = frame1 #Colocar el frame del segundo video en la mitad inferior
        out.write(frame) #Escribir el nuevo frame
        cv2.imshow('frame',frame) #Mostrarlo en una ventana

Pero si alguno de los dos frames es nulo, hay que salir del bucle para terminar el proceso.

    #Si frame1 o frame2 es nulo, salir del programa    
    else:
        break

Siempre es bueno añadir un fragmento de código que nos permita interrumpir el proceso en cualquier momento y salir del programa:

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

Una vez fuera del bucle se destruyen las ventanas y se libera la memoria.

#Si se ha terminado el proceso, limpiar...
video1.release()
video2.release()
out.release()
cv2.destroyAllWindows()

 

En resumen, el código del script debería haber quedado algo así:

import numpy as np
import cv2

#Cargar los dos videos
video1 = cv2.VideoCapture('video1.mov')
video2 = cv2.VideoCapture('video2.mov')

#Guardar las dimensiones del primer video
ancho1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) 
alto1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#Guardar las dimensiones del segundo video
ancho2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#El ancho del video final sera el minimo de las dos longitudes horizontales
ancho = int(min(ancho1, ancho2))

#Usar un codec compatible con .avi
fourcc = cv2.cv.CV_FOURCC(*'XVID')

#Crear el objeto VideoWriter
out = cv2.VideoWriter('output.avi',fourcc, 30, (ancho,alto1+alto2))

while(1):
    #En este frame guardaremos la union de los dos videos:
    frame = np.zeros((alto1+alto2,ancho,3), np.uint8)

    #Leer un frame de cada video
    ret1, frame1 = video1.read()
    ret2, frame2 = video2.read()


    #Mirar que ambos frames sean validos (uno de los videos podria ser mas corto que el otro)
    if ret1 and ret2:
        frame[0:alto2,0:ancho] = frame2 #Colocar el frame del primer video en la mitad superior
        frame[alto2:(alto2+alto1),0:ancho] = frame1 #Colocar el frame del segundo video en la mitad inferior
        out.write(frame) #Escribir el nuevo frame
        cv2.imshow('frame',frame) #Mostrarlo en una ventana

    #Si frame1 o frame2 es nulo, salir del programa    
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

#Si se ha terminado el proceso, limpiar...
video1.release()
video2.release()
out.release()
cv2.destroyAllWindows()

El script os creará un vídeo como este una vez lo ejecutéis.

Y bien, ¿ahora sabriáis como colocar un vídeo al lado del otro?


2- Fusionar dos vídeos

Ahora vamos a fusionar la imagen de los dos vídeos, ¿vale?

Aquí no hay que crear un frame nuevo con la función np.zeros como se ha hecho en el ejemplo anterior, sino que puede hacerse directamente con cv2.addWeighted(frame1, alpha1, frame2, alpha2, gamma).

Esta función recibe cinco parámetros.

  • ‘frame1’ y ‘frame2’ son las dos imágenes que hay que mezclar
  • ‘alpha1’ y ‘alpha2’ son la transparencia de cada una de estas imágenes. Ambos valores deben sumar 1.
  • ‘gamma’ es un escalar que puede añadirse a la hora de hacer la suma.

Esta función calcula el valor de cada nuevo píxel con la fórmula:

frame_final[i][j] = frame1[i][j]*alpha1 + frame2[i][j]*alpha2 + gamma

Cread un nuevo script Python llamado ‘code2.py’. La primera parte de este script no es muy diferente del código 1. La única cosa que cambia es que, como hay que sobreponer las dos imágenes, la altura de la imagen ahora será el mínimo de las alturas de los dos vídeos.

import numpy as np
import cv2

#Cargar los dos videos
video1 = cv2.VideoCapture('video1.mov')
video2 = cv2.VideoCapture('video2.mov')

#Guardar las dimensiones del primer video
ancho1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#Guardar las dimensiones del segundo video
ancho2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#El ancho del video sera el minimo de las dos longitudes horizontales
ancho = int(min(ancho1, ancho2))
#La altura tambien
alto = int(min(alto1, alto2))

#Usar un codec compatible con .avi
fourcc = cv2.cv.CV_FOURCC(*'XVID')
#Crear el objeto VideoWriter
out = cv2.VideoWriter('output.avi',fourcc, 30, (ancho,alto))

Dentro del bucle while se empieza por leer un frame de cada vídeo:

while(1):
    #Leer un frame de cada video
    ret1, frame1 = video1.read()
    ret2, frame2 = video2.read()

Y se fusionan si no son nulos:

    #Mirar que ambos frames sean validos (uno de los videos podria ser mas corto que el otro)
    if ret1 and ret2:
        #Fusionar ambas imagenes con una transparencia del 0.5
        frame = cv2.addWeighted(frame1,0.5,frame2,0.5,0)
        out.write(frame) #Escribir el nuevo frame        
        cv2.imshow('frame',frame)

El resto es igual que antes:

    #Si frame1 o frame2 es nulo, salir del programa    
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

#Si se ha terminado el proceso, limpiar...
video1.release()
out.release()
cv2.destroyAllWindows()

El script acabado luce así:

import numpy as np
import cv2

#Cargar los dos videos
video1 = cv2.VideoCapture('video1.mov')
video2 = cv2.VideoCapture('video2.mov')

#Guardar las dimensiones del primer video
ancho1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#Guardar las dimensiones del segundo video
ancho2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#El ancho del video sera el minimo de las dos longitudes horizontales
ancho = int(min(ancho1, ancho2))
#La altura tambien
alto = int(min(alto1, alto2))

#Usar un codec compatible con .avi
fourcc = cv2.cv.CV_FOURCC(*'XVID')
#Crear el objeto VideoWriter
out = cv2.VideoWriter('output.avi',fourcc, 30, (ancho,alto))

while(1):
    #Leer un frame de cada video
    ret1, frame1 = video1.read()
    ret2, frame2 = video2.read()



    #Mirar que ambos frames sean validos (uno de los videos podria ser mas corto que el otro)
    if ret1 and ret2:
        #Fusionar ambas imagenes con una transparencia del 0.5
        frame = cv2.addWeighted(frame1,0.5,frame2,0.5,0)
        out.write(frame) #Escribir el nuevo frame        
        cv2.imshow('frame',frame)

    #Si frame1 o frame2 es nulo, salir del programa    
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

#Si se ha terminado el proceso, limpiar...
video1.release()
out.release()
cv2.destroyAllWindows()

Al correrlo, debería crear un vídeo como este. ¿Qué os parece?


3- Empalmar dos vídeos

Por último vamos a empalmar dos vídeos: cuando acabe el primero, va a empezar el otro. ¡Todavía es más fácil que en los ejemplos anteriores! Lo que haremos será escribir dos bucles que escriban los frames de cada vídeo al vídeo final.

Cread un script python y llamadlo ‘code3.py’ . La primera parte es igual que el ejemplo 2:

import numpy as np
import cv2

#Cargar los dos videos
video1 = cv2.VideoCapture('video1.mov')
video2 = cv2.VideoCapture('video2.mov')

#Guardar las dimensiones del primer video
largo1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#Guardar las dimensiones del segundo video
largo2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#El ancho del video sera el minimo de las dos longitudes horizontales
largo = int(min(largo1, largo2))
#La altura tambien
alto = int(min(alto1, alto2))

#Usar un codec compatible con .avi
fourcc = cv2.cv.CV_FOURCC(*'XVID')
#Crear el objeto VideoWriter
out = cv2.VideoWriter('output.avi',fourcc, 30, (largo,alto))

El primer bucle lee los frames del vídeo 1 hasta que encuentre uno nulo, y los va escribiendo en el vídeo de output:

while(1):
    #Leer un frame del video 1
    ret1, frame1 = video1.read()

    #Si el frame no es nulo, escribirlo al video de salida
    if ret1:
        out.write(frame1)
        cv2.imshow('frame', frame1)
    
    #Si es nulo, salir del bucle
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

El segundo bucle es lo mismo, pero con el vídeo 2:

while(1):
    #Leer un frame del video 2
    ret2, frame2 = video2.read()

    #Si el frame no es nulo, escribirlo al video de salida
    if ret2:
        out.write(frame2)
        cv2.imshow('frame', frame2)

    #Si es nulo, salir del bucle
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

Y el script termina destruyendo las ventanas y liberando la memoria:

#Si se ha terminado el proceso, limpiar...
video1.release()
video2.release()
out.release()
cv2.destroyAllWindows()

En resumen, el script va a quedar así:

import numpy as np
import cv2

#Cargar los dos videos
video1 = cv2.VideoCapture('video1.mov')
video2 = cv2.VideoCapture('video2.mov')

#Guardar las dimensiones del primer video
largo1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto1 = int(video1.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#Guardar las dimensiones del segundo video
largo2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
alto2 = int(video2.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))

#El ancho del video sera el minimo de las dos longitudes horizontales
largo = int(min(largo1, largo2))
#La altura tambien
alto = int(min(alto1, alto2))

#Usar un codec compatible con .avi
fourcc = cv2.cv.CV_FOURCC(*'XVID')
#Crear el objeto VideoWriter
out = cv2.VideoWriter('output.avi',fourcc, 30, (largo,alto))

while(1):
    #Leer un frame del video 1
    ret1, frame1 = video1.read()

    #Si el frame no es nulo, escribirlo al video de salida
    if ret1:
        out.write(frame1)
        cv2.imshow('frame', frame1)
    
    #Si es nulo, salir del bucle
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

while(1):
    #Leer un frame del video 2
    ret2, frame2 = video2.read()

    #Si el frame no es nulo, escribirlo al video de salida
    if ret2:
        out.write(frame2)
        cv2.imshow('frame', frame2)

    #Si es nulo, salir del bucle
    else:
        break

    #Se puede interrumpir el proceso pulsando 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break


#Si se ha terminado el proceso, limpiar...
video1.release()
video2.release()
out.release()
cv2.destroyAllWindows()

Y ya no hay más. ¿Es fácil, verdad? Al correr este tercer script, se creará un vídeo como este.


Hemos visto, pues, que OpenCV es una buena herramienta para trabajar con vídeos. Estos son tres ejemplos básicos de formas de manipular los vídeos, pero estoy segura que se os ocurren muchos otros más interesantes ^^

Como siempre, si tenéis dudas o sugerencias, dejadme un comentario y os contestaré en breve.

 

 

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

Antes de comentar, por favor, lee las Normas

2 Comentarios en "Como juntar dos vídeos con OpenCV+Python"

avatar
Ordenar por:   más nuevos primero | más antiguos primero
Aprendiz
Humano

Como siempre gracias por el blog :
Serìa posible sustituir el fondo de un vídeo e insertarlo en otro o lo que es lo mismo poner el primer plano de un vìdeo en otro?
Si no hay un canal alfa se puede crear uno para extraer el primer plano?.

Y una ultima pregunta ¿Has usado dlib “http://blog.dlib.net”? no consigo que me funcione.
PD:
opencv 3.1.0 solo acepta la función
video.get(cv2.CAP_PROP_FRAME_WIDTH(HEIGHT))

wpDiscuz