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”.
Practicar el acceso y manipulación de datos en un arreglo.
Utilizar ciclos anidados para implementar técnicas de “pantalla verde”.
Utilizar expresiones aritméticas y estructuras de selección para transformar colores de píxeles.
Acceder píxeles en una imagen y descomponerlos en sus componentes rojo, azul y verde.
Antes de llegar al laboratorio debes haber:
Repasado los conceptos básicos relacionados a estructuras de repetición, ciclos anidados y arreglos bi-dimensionales.
Entendido los métodos básicos de QImage
para manipular los píxeles de las imágenes.
Estudiado los conceptos e instrucciones para la sesión de laboratorio.
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=9203875280
Tomado el quiz Pre-Lab, disponible en Moodle.
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.
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.
Figura 2. Ilustración de cómo el algoritmo decide cuáles píxeles de la imagen A incluir en la imagen C.
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).
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.
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 editareditedImage
// contendrá la imagen editadaLos 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 imagenheight()
// devuelve el valor entero de la altura de la imagenpixel(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íxelqGreen(pixel)
// devuelve el tono del color verde del píxelqBlue(pixel)
// devuelve el tono del color azul del píxelqRgb(int red, int green, int blue)
// devuelve un píxel QRgb
compuesto de los valores de rojo, verde y azul recibidos.QRgb myRgb = qRgb(0xff, 0x00, 0xff);
: Asigna a myRgb
el valor 0xff00ff
que representa el color
Nota que el valor 0xff00ff
representa los valores 0xff
, 0x0
, 0xff
, que corresponden a los componentes rojo, verde y azul de myRgb
.
Si la siguiente imagen 4 x 4
de píxeles representa el objeto originalImage
,
entonces originalImage.pixel(2,1)
devuelve un valor rgb
que representa el color azul (0x0000ff
).
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));
.
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));
.
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;
}
Observa la Figura 4 abajo. Aunque el fondo en la imagen A parece uniforme, realmente incluye píxeles de diferentes colores (aunque parecidos).
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:
$$distancia = \sqrt{(P_R-S_R)^2+(P_G-S_G)^2+(P_B-S_B)^2},$$
donde $$P_R, P_G, P_B$$ son los valores de los componentes rojo, verde y azul del píxel bajo consideración, y $$S_R, S_G, S_B$$ son los valores de los componentes rojo, verde y azul del fondo sólido. En nuestro ejemplo, $$S_R=S_B=0$$ y $$S_G=255$$.
!INCLUDE “../../eip-diagnostic/green-screen/es/diag-green-screen-01.html”
!INCLUDE “../../eip-diagnostic/green-screen/es/diag-green-screen-02.html”
!INCLUDE “../../eip-diagnostic/green-screen/es/diag-green-screen-03.html”
!INCLUDE “../../eip-diagnostic/green-screen/es/diag-green-screen-04.html”
!INCLUDE “../../eip-diagnostic/green-screen/es/diag-green-screen-05.html”
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ólidobackgroundImage
: referencia a la imagen para el fondomergedImage
: referencia a la imagen compuestathreshold
: 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)
.Carga a QtCreator
el proyecto GreenScreenLab
. Hay dos maneras de hacer esto:
GreenScreenLab.pro
que se encuentra en el directorio /home/eip/labs/arrays-greenscreen
de la máquina virtual.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
.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.
Figura 5. Interfaz de la aplicación GreenScreen.
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.
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
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)
.
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.
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.
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)
$$N_R=\frac{S_R+B_R}{2}$$ $$N_G=\frac{S_G+B_G}{2}$$ $$N_B=\frac{S_B+B_B}{2},$$
en donde $$N_R, N_G, N_B$$ son los componentes rojo, verde y azul del nuevo píxel fantasma, $$S_R, S_G, S_B$$ son los componentes de la imagen del objeto, y $$B_R, B_G, B_B$$ son los componentes de la imagen de fondo.
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.
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)
.
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.
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.
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.
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.
[1] http://en.wikipedia.org/wiki/Greenscreen(disambiguation)
[2] http://en.wikipedia.org/wiki/Chroma_key
[3] http://doc.qt.io/qt-4.8/qimage.html.