Arreglos - Pantalla Verde

main1.png main2.png main3.png

Los arreglos de datos (arrays) nos facilitan guardar y trabajar con grupos de datos del mismo tipo. Los datos se guardan en espacios de memoria consecutivos a los que se puede acceder utilizando el nombre del arreglo e índices o suscritos que indican la posición en donde se encuentra el dato. Las estructuras de repetición nos proveen una manera simple de acceder los datos de un arreglo. En la experiencia de laboratorio de hoy practicarás el uso de ciclos anidados en la manipulación de arreglos bi-dimensionales usando técnicas de "pantalla verde".

Objetivos:

  1. Practicar el acceso y manipulación de datos en un arreglo.

  2. Utilizar ciclos anidados para implementar técnicas de "pantalla verde".

  3. Utilizar expresiones aritméticas y estructuras de selección para transformar colores de píxeles.

  4. Acceder píxeles en una imagen y descomponerlos en sus componentes rojo, azul y verde.

Pre-Lab:

Antes de llegar al laboratorio debes haber:

  1. Repasado los conceptos básicos relacionados a estructuras de repetición, ciclos anidados y arreglos bi-dimensionales.

  2. Entendido los métodos básicos de QImage para manipular los píxeles de las imágenes.

  3. Estudiado los conceptos e instrucciones para la sesión de laboratorio.

  4. Visitado la siguiente página de Facebook para ver cómo se ha usado la tecnología de pantalla verde en algunas películas de Hollywood: https://www.facebook.com/video.php?v=920387528037801

  5. Tomado el quiz Pre-Lab, disponible en Moodle.



Tecnología de pantalla verde ("Green Screen")

En esta experiencia de laboratorio, aprenderás los conceptos y destrezas básicas de la tecnología de pantalla verde que se usa en boletines informativos de televisión, películas, videojuegos y otros. La composición de pantalla verde, o composición cromática, es una técnica que se usa para combinar dos imágenes o cuadros de video [1]. Esta técnica de post-producción crea efectos especiales al componer dos imágenes o transmisiones de video sustituyendo el área de un color sólido por otra imágen [2]. La composición cromática se puede hacer con imágenes de objetos sobre fondos de cualquier color que sean uniformes y diferentes a los de la imagen. Los fondos azules y verdes son los que se usan con más frecuencia porque se distinguen con más facilidad de los tonos de la mayoría de los colores de piel humanos.

Para esta experiencia de laboratorio te proveemos un interfaz gráfico (GUI) simple que permite al usuario cargar una imagen con un objeto sobre un fondo de color sólido (preferiblemente azul o verde) y una imagen para sustituir el fondo. Tu tarea es crear e implementar una función que cree una tercera imagen compuesta en la cual la imagen del objeto con el fondo de color sólido se le removerá el color de fondo y el objeto aparecerá sobre la imagen que será el nuevo fondo. La Figura 1 muestra un ejemplo de los resultados esperados.


figure1.png

Figura 1. Ejemplo de los resultados esperados. El objeto de interés es la mano con las gafas.


Con el propósito de ilustrar el procedimiento, llamemos la imagen del objeto con el fondo de color sólido imagen A, y supongamos que el color sólido en el fondo tiene un "RGB" 0x00ff00 (verde puro). Llamemos imagen B a la imagen que usaremos para el fondo, un fondo que resulte interesante. Para este ejemplo, supongamos también que los tamaños de ambas imágenes son iguales (mismo ancho y alto).

Para producir la imagen compuesta (imagen C), podríamos comenzar copiando toda la imagen B que usaremos de fondo a la imagen C. Luego, para insertar solo el objeto que nos interesa en la imagen compuesta podemos recorrer la imagen A píxel por píxel. Compararíamos el color de cada píxel p en la imagen A con el color de fondo 0x00ff00. Si son similares, el píxel de la imagen A corresponde al color sólido de fondo y dejamos el píxel de la imagen C como está (el fondo nuevo). Si el color de p no es similar a 0x00ff00, modificamos el píxel correspondiente en la imagen C, copiando el color del píxel del objeto a la imagen compuesta. Esto se ilustra en la Figura 2.


figure2.png

Figura 2. Ilustración de cómo el algoritmo decide cuáles píxeles de la imagen A incluir en la imagen C.



Píxeles

Al elemento más pequeño de una imagen se le llama un píxel. Esta unidad consiste de un solo color. Como cada color es una combinación de tonalidades de los colores primarios rojo, verde y azul, se codifica como un entero sin signo cuyos bytes representan los tonos de rojo, verde y azul del píxel (Figura 3). A esta combinación se le llama el RGB del color por las siglas de "Red-Green-Blue". Por ejemplo un píxel de color rojo (puro) tiene una representación RGB 0x00ff0000, mientras que un píxel de color blanco tiene una representación RGB de 0x00FFFFFF (ya que el color blanco es la combinación de los tonos rojo, verde y azul en toda su intensidad).


figure3.png

Figura 3. Distribución de bits para las tonalidades de rojo, verde y azul dentro de la representación RGB. Cada tonalidad puede tener valores entre 0x00 (los ocho bits en 0) y 0xFF (los 8 bits en 1).


En Qt se utiliza el tipo QRgb para representar valores RGB. Utilizando ciertas funciones que describimos abajo podemos obtener los componentes rojo, verde y azul del valor QRgb del píxel y así manipular imágenes.

Biblioteca

La experiencia de laboratorio de hoy utilizará la clase QImage. Esta clase permite acceder a los datos de los píxeles de una imagen para poder manipularla. La documentación de la clase QImage se encuentra en http://doc.qt.io/qt-4.8/qimage.html.

El código que te proveemos contiene los siguiente objetos de la clase QImage:

  • originalImage // contiene la información de la imagen original que vas a editar
  • editedImage // contendrá la imagen editada

Los objetos de clase QImage tienen los siguiente métodos que serán útiles para la experiencia de laboratorio de hoy:

  • width() // devuelve el valor entero del ancho de la imagen
  • height() // devuelve el valor entero de la altura de la imagen
  • pixel(i, j) // devuelve el QRgb del píxel en la posición (i,j)
  • setPixel(i,j, pixel) // modifica el valor del píxel en la posición (i, j) al valor píxel QRgb

Las siguientes funciones son útiles para trabajar con datos de tipo QRgb:

  • qRed(pixel) // devuelve el tono del color rojo del píxel
  • qGreen(pixel) // devuelve el tono del color verde del píxel
  • qBlue(pixel) // devuelve el tono del color azul del píxel
  • qRgb(int red, int green, int blue) // devuelve un píxel QRgb compuesto de los valores de rojo, verde y azul recibidos.

Ejemplos

  1. QRgb myRgb = qRgb(0xff, 0x00, 0xff);: Asigna a myRgb el valor 0xff00ff que representa el color figure4.png

    Nota que el valor 0xff00ff representa los valores 0xff, 0x0, 0xff, que corresponden a los componentes rojo, verde y azul de myRgb.

  2. Si la siguiente imagen 4 x 4 de píxeles representa el objeto originalImage,

    ejemplo.png

    entonces originalImage.pixel(2,1) devuelve un valor rgb que representa el color azul (0x0000ff).

  3. La siguiente instrucción asigna el color rojo al píxel en posición (2,3) en la imagen editada: editedImage.setPixel(2,3,qRgb(0xff,0x00,0x00));.

  4. La siguiente instrucción le asigna a greenContent el valor del tono de verde que contiene el píxel (1,1) de originalImage: int greenContent = qGreen(originalImage.pixel(1,1));.

  5. El siguiente programa crea un objeto de clase QImage e imprime los componentes rojo, verde y azul del píxel en el centro de la imagen. La imagen utilizada es la que se especifica dentro del paréntesis durante la creación del objeto, esto es, el archivo chuck.png.


#include <QImage>
#include <iostream>

using namespace std;
int main() {
    QImage myImage(“/Users/rarce/Downloads/chuck.png”);
    QRgb    centralPixel;

    centralPixel = myImage.pixel(myImage.width() / 2, myImage.height() / 2);

    cout    << hex;

    cout    << “Los componentes rojo, verde y azul del píxel central son: “
        << qRed(centralPixel) << “, “
        << qGreen(centralPixel) << “, “
        << qBlue(centralPixel) << endl;
    return 0;
}

Midiendo la similaridad de los colores de los píxeles

Observa la Figura 4 abajo. Aunque el fondo en la imagen A parece uniforme, realmente incluye píxeles de diferentes colores (aunque parecidos).


figure5.png

Figura 4. Lo que puede parecer un color sólido, realmente no lo es.


Por esto, en lugar de solo considerar como parte del fondo sólido los píxeles cuyo color es exactamente 0x00FF00, medimos la distancia del valor del color del píxel al valor del color puro. Una distancia pequeña significa que el color es casi verde puro. La ecuación para la distancia es:

donde son los valores de los componentes rojo, verde y azul del píxel bajo consideración, y son los valores de los componentes rojo, verde y azul del fondo sólido. En nuestro ejemplo, y .



Dado dos objetos A y Z de clase QImage. Escoge la mejor descripción del siguiente código:

greenComp = qGreen( A.getPixel(0,0) );

result = greenComp > 230 ? 0xff0000: 0x0000ff;

Z.setPixel( 0, 0, result);

Cambia el color del pixel en la imagen Z a puro rojo si el componente verde del pixel de la imagen A es mayor 230 Cambia el color del pixel en la imagen Z a puro azul si el componente verde del pixel de la imagen A es mayor 230. Cambia el color del pixel en la imagen Z a puro verde si el componente verde del pixel de la imagen A es mayor 230. La instrucción greenComp = qGreen( A.getPixel(0,0) ); extrae el valor del componente verde de un pixel de la imagen A. Si el valor de greenComp es mayor que 230, el valor asignado a result corresponde a color puramente rojo.

¿Cual de los siguientes cambia un pixel puramente rojo a uno puramente azul? if ( qRed( A.getPixel(0,0) ) == 0xff ) A.setPixel(0, 0, 0x0000ff); if ( A.getPixel(0,0) == 0xff0000 ) A.setPixel(0, 0, 0x0000ff); if ( A.getPixel(0,0) == qRed(0xff0000) ) A.setPixel(0x0000ff); La primera opción no realiza una comparación adecuada pues compara el componente rojo del pixel 0xff, sin tomar en cuenta los valores de los otros componentes. La segunda opción es la correcta pues compara el valor del pixel con el valor del color rojo puro (0xff0000). La tercera opción tampoco realiza una comparación adecuada pues intenta comparar el valor del pixel contra el valor del componente rojo.

A continuación mostramos el objeto A de clase QImage que tiene ancho y alto de 5 pixeles, i.e. el pixel (0,0) es rojo, el (4,0) es negro, el (2,2) es verde.


¿Cuál de lo siguientes sería el output de la siguiente instrucción: cout << qRed( A.pixel(1,0) )?
0xff 0x77 0x00 El pixel que se encuentra en la posición (1,0) es color negro (valor 0x000000) por lo tanto su componente rojo es 0x00.

A continuación mostramos el objeto A clase QImage que tiene ancho y alto de 5 pixeles, i.e. el pixel (0,0) es rojo, el (4,0) es negro, el (2,2) es verde.


¿Cuál de los siguientes sería el output de la siguiente instrucción: cout << A.getPixel(2,3)?
0x00ff00 0xff0000 0xffffff 0x000000 El píxel que se encuentra en la posición (2,3) es color verde por lo tanto su valor será semejante a 0x00ff00.

¿Cúal de los siguientes códigos crearía una línea horizontal por en el centro de la imágen A? for (int i = 0; i < A.width(); i++) A.setPixel(i, A.height()/2, 0); for (int i = 0; i < A.height(); i++) A.setPixel(A.width()/2, i, 0); for (int i = 0; i < A.width(); i++) A.setPixel(i/2, i/2, 0); Ninguno de los anteriores pues hace falta un loop anidado. La primera opción crea una línea horizontal en el centro de la imagen. La segunda opción crea una línea vertical por el centro de la imagen. La tercera opción es peligrosa pues usa la variable i en ambos argumentos y podría resultar en rebasar los valores válidos del alto de la imagen. La cuarta opción es falsa, no necesitamos un loop anidado para trazar una simple línea.



Sesión de laboratorio:

En el laboratorio de hoy, comenzando con una imagen con un objeto de interés sobre un fondo de color sólido y una imagen para utilizar de fondo, definirás e implantarás una función que cree una tercera imagen compuesta en la cual, a la imagen del objeto de interés se le removerá el color de fondo y aparecerá sobre la imagen para el fondo.

Estarás trabajando con el archivo Filter.cpp. Lo que sigue es un resumen de las variables en este archivo.

  • objectImage: referencia a la imagen del objeto de interés y fondo sólido
  • backgroundImage: referencia a la imagen para el fondo
  • mergedImage: referencia a la imagen compuesta
  • threshold: valor umbral usado para comparar las distancias entre el valor del color del píxel de la imagen con el objeto sobre fondo sólido. En el código que se provee, el valor del umbral se lee del valor de la barra deslizable.
  • ghost: valor Booleano utilizado para aplicar el filtro "fantasma" a los píxeles.
  • (x, y): coordenadas de un píxel de la imagen del objeto sobre fondo sólido. El valor predeterminado es (0,0).
  • (offset_x, offset_y): coordenadas de la imagen compuesta en donde la esquina superior izquierda de la imagen del objeto sobre fondo sólido será insertada. El valor predeterminado es (0,0).

Ejercicio 1 - Crear imagen compuesta

Instrucciones

  1. Carga a QtCreator el proyecto GreenScreenLab. Hay dos maneras de hacer esto:

    • Utilizando la máquina virtual: Haz doble “click” en el archivo GreenScreenLab.pro que se encuentra en el directorio /home/eip/labs/arrays-greenscreen de la máquina virtual.
    • Descargando la carpeta del proyecto de Bitbucket: Utiliza un terminal y escribe el comando git clone http:/bitbucket.org/eip-uprrp/arrays-greenscreen para descargar la carpeta arrays-greenscreen de Bitbucket. En esa carpeta, haz doble “click” en el archivo GreenScreenLab.pro.
  2. Configura el proyecto y corre el programa. El código que te proveemos crea la interfaz de la Figura 5. Los botones Select Image y Select Background Image ya han sido programados.


    figure6.png

    Figura 5. Interfaz de la aplicación GreenScreen.


  3. Marca el botón para cargar una imagen del objeto de interés sobre fondo sólido, luego marca el botón para seleccionar la imagen para el fondo. El directorio con los archivos fuente contiene una carpeta llamada landscapes que contiene imágenes de fondo, y una carpeta llamada green_background que contiene imágenes de objetos sobre fondo de color sólido.

  4. Tu primera tarea es completar la función MergeImages en el archivo Filter.cpp. La función MergeImages se invoca cuando el usuario marca el botón Merge Images y cuando se desliza la barra. La función MergeImages recibe las referencias a la imagen con objeto de interés y fondo sólido, la imagen para el fondo y la imagen compuesta, un valor umbral, las coordenadas (x,y) de un píxel de la imagen del objeto sobre fondo sólido, y las coordenadas (offset_x, offset_y) de la imagen compuesta.

Para este ejercicio puedes ignorar el filtro "fantasma" ghost y las coordenadas (offset_x, offset_y), y solo componer la imagen con el objeto de interés en la imagen de fondo, comenzando en la posición (0,0).

Algoritmo

  1. Adquiere el valor del color sólido. El color sólido será el color del píxel en la posición (x,y) en la imagen del objeto sobre fondo sólido. El valor por defecto para (x,y) es (0,0).

  2. Para todas las posiciones (i,j), adquiere el valor del color del píxel en la posición (i,j) de la imagen con el objeto. Computa la distancia entre el color de la imagen con el objeto y el valor del color sólido. Si la distancia entre el color sólido y el color del píxel de la imagen es mayor que el valor umbral, cambia el valor del color del píxel en la posición (i,j) de la imagen de fondo al valor del color de la imagen con el objeto.

Prueba tu implantación cargando imágenes de objetos e imágenes para el fondo y verificando la imagen compuesta.

Ejercicio 2 - Crear imagen compuesta usando filtro ghost

En este ejercicio modificarás el Ejercicio 1 para aplicar el filtro fantasma a cada uno de los píxeles que se compondrán sobre la imagen de fondo en el caso de que la variable ghost sea cierta. El filtro fantasma creará el efecto de que el objeto en la imagen compuesta se verá como un "fantasma" sobre la imagen de fondo, como en la Figura 6.


figure7.png

Figura 6. Imagen con filtro fantasma. En este ejemplo, el perro en la imagen con el fondo sólido se compone sobre la imagen de fondo utilizando el filtro fantasma.


El efecto fantasma se consigue promediando el valor del color del píxel del fondo con el valor del color del píxel correspondiente del objeto, en lugar de solo reemplazar el valor del color del píxel del fondo por el del objeto. Calculamos el promedio de cada uno de los componentes (rojo, verde y azul)

en donde son los componentes rojo, verde y azul del nuevo píxel fantasma, son los componentes de la imagen del objeto, y son los componentes de la imagen de fondo.

Ejercicio 3 - Crear imagen compuesta colocando el objeto en una posición específica

El "widget" que despliega el fondo fue programado para que detecte la posición marcada por el usuario. En este ejercicio programarás la función MergeImages para que el objeto sea desplegado en la posición marcada por el usuario en la imagen de fondo, en lugar de ser desplegado en la esquina superior izquierda. Las Figuras 7 y 8 muestran el efecto. Nota los valores de Selected Coord bajo la imagen del medio.


figure8.png

Figura 7. En este ejemplo, la imagen del fondo no ha sido marcada y Selected Coord tiene (0,0) que es su valor por defecto. El perro se inserta en la imagen compuesta con su esquina superior izquierda en el lugar (0,0).


figure9.png

Figura 8. En este ejemplo, la imagen del fondo fue marcada en las coordenadas (827,593). La imagen del perro se inserta en la imagen compuesta con su esquina superior izquierda en la posición (827,593).


Tu tarea en este ejercicio es la misma que en el Ejercicio 1, pero esta vez debes ajustar la imagen del objeto dentro de la composición con las cantidades especificadas en los parámetros offset_x y offset_y. Recuerda tomar en consideración los límites de la imagen compuesta cuando insertes el objeto; el usuario pudiera especificar unos parámetros que se salgan de los límites y el objeto se cortará, como sucede en la Figura 9.


figure10.png

Figura 9. En este ejemplo, el usuario seleccionó una posición que asignó valores muy grandes para offset_x y offset_y; la implementación hizo el ajuste para que parte de la imagen del perro saliera en la imagen compuesta.


El ejemplo de la Figura 10 muestra cómo se comportará la imagen del objeto al sobreponerla en la imagen que queremos de fondo. Las variables offset_x, offset_y representan el punto en la imagen de fondo en el que se colocará la esquina superior izquierda de la imagen del objeto. Nota que si se escoge un punto muy cerca del borde para la composición, parte de la imagen del objeto se sale de los límites de la imagen de fondo. Como hemos visto en la manipulación de arreglos, si se intenta acceder o alterar elementos que están fuera del rango de tamaño del arreglo, al compilar ocurre un error fatal. Lo mismo sucede con las imágenes.

Debes asegurarte de que tu implementación toma en cuenta los valores de offset_x y offset_y para que la composición no intente acceder o alterar píxeles fuera del límite de la imagen de fondo. Si intentas acceder o alterar píxeles fuera de esos límites, resulta en un error fatal.


figure11.png

Figura 10. Ilustración de la imagen del objeto de interés con píxeles que se salen de los límites de la imagen de fondo. Si no se toma en consideración esta posibilidad en la implementación, ocurrirá un error fatal.


Valida tu implantación seleccionando varios valores para el ajuste de "offset" y observando el efecto que tienen en la imagen compuesta. Asegúrate de tratar casos en los que tus valores para offset_x y offset_y ocasionarían que la imagen fuera cortada como ocurrió en la imagen compuesta de la Figura 9.



Entrega

Utiliza "Entrega" en Moodle para entregar el archivo Filter.cpp que contiene la función MergeImages. Recuerda utilizar buenas prácticas de programación, incluya el nombre de los programadores y documenta tu programa.



Referencias

[1] http://en.wikipedia.org/wiki/Green_screen_(disambiguation)

[2] http://en.wikipedia.org/wiki/Chroma_key

[3] http://doc.qt.io/qt-4.8/qimage.html.




results matching ""

    No results matching ""