Trabajo Fin de Grado Grado en Ingeniería de las...

87
Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías Industriales Desemborronado de Imágenes con OpenCL Autor: Jesús Aires Barea Tutor: Manuel Ruiz Arahal Dep. Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2015

Transcript of Trabajo Fin de Grado Grado en Ingeniería de las...

Page 1: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Equation Chapter 1 Section 1

Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías Industriales

Desemborronado de Imágenes con OpenCL

Autor: Jesús Aires Barea

Tutor: Manuel Ruiz Arahal

Dep. Ingeniería de Sistemas y Automática Escuela Técnica Superior de Ingeniería

Universidad de Sevilla

Sevilla, 2015

Page 2: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 3: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

iii

Trabajo Fin de Grado Grado en Ingeniería de las Tecnologías Industriales

Desemborronado de Imágenes con OpenCL

Autor:

Jesús Aires Barea

Tutor:

Manuel Ruiz Arahal

Profesor Catedrático

Dep. de Ingeniería de Sistemas y Automática

Escuela Técnica Superior de Ingeniería

Universidad de Sevilla Sevilla, 2015

Page 4: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 5: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

v

Trabajo Fin de Grado: Desemborronado de Imágenes con OpenCL

Autor: Jesús Aires Barea

Tutor: Manuel Ruiz Arahal

El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros:

Presidente:

Vocales:

Secretario:

Acuerdan otorgarle la calificación de:

Sevilla, 2015

Page 6: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

El Secretario del Tribunal

Page 7: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

vii

A mi familia

A mis maestros

Page 8: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 9: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

ix

Resumen

En este trabajo resolveremos un problema de tratamiento de imágenes. Intentaremos desemborronar una imagen que fue capturada con un objetivo desenfocado. Para ello primero tendremos que entender el modelo matemático que se lleva a cabo cuando se emborrona una imagen. Posteriormente trataremos de encontrar el mejor método para desemborronarla y conseguir una imagen nítida.

Para llevar a cabo el método elegido de desemborronado, desarrollaremos una aplicación informática. Ésta se ejecutará en una unidad de procesamiento gráfico (GPU) en nuestro ordenador. Nos ayudaremos de la herramienta OpenCL, una serie de bibliotecas de código que nos permitirán el desarrollo de tal aplicación en un entorno de trabajo en paralelo. Así, dividiremos el trabajo y conseguiremos una aplicación rápida.

Se convierte también en un problema de velocidad e intentaremos hacer el programa lo más rápido posible. Comprobaremos su velocidad comparándola con otros dispositivos como la unidad de procesamiento central (CPU) del ordenador.

Page 10: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 11: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

xi

Abstract

In this paper we solve a problem of image processing. We try to deblur an image that was captured with an objective out-of-focus. To do this we must first understand the mathematical model that is performed when an image is blurred. Then we try to find the best method for deblurring and get a shaper picture. To carry out the chosen method of deblurring, we develop a computer application. It will run on a Graphics Processing Unit (GPU) in our personal computer. We help the OpenCL tool, a set of code libraries that allow us to develop such an application in a work environment in parallel. So, we divide the work and get a quick application. It also becomes a problem of speed and try to make the program as quickly as possible. We will check your speed compared with other devices such as central processing unit (CPU) of the computer.

Page 12: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 13: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

xiii

Índice

Resumen  ix 

Abstract  xi 

Índice  xiii 

Índice de Tablas  xv 

Índice de Figuras  xvii 

1  Introducción  1 1.1. Objetivo    1 1.2. Contexto    1 

2  Algoritmo de deconvolución  3 2.1. Modelo matemático de convolución  3 2.1.1  Operación matemática  3 2.1.2  Obtención de matriz PSF  7 2.1.3  Condiciones de contorno  9 

2.2. Cálculo de matrices estructuradas  12 2.2.1  Matrices BCCB: bordes periódicos  12 2.2.2  Matrices BTTB + BTHB + BHTB + BHHB: bordes reflejados  14 

2.3. Regularización: métodos de filtrado  15 

3  OpenCL  19 3.1. Cálculo paralelo  19 3.2. Modelo de plataforma  20 3.3. Modelo de ejecución  21 3.4. Programación en OpenCL  23 3.4.1.  Detección y selección de la plataforma y los dispositivos  24 3.4.2.  Creación del contexto  25 3.4.3.  Creación de la cola de comandos  25 3.4.4.  Definición de los objetos de memoria  25 3.4.5.  Compilación del programa y creación del kernel  25 3.1.2.  Liberar recursos  26 

4  Resolución del problema  27 4.1. Entorno particular de trabajo  27 4.2. Planificación de trabajo  28 4.3. Aplicación del host de convolución  29 4.3.1.  Función readSource  29 4.3.2.  Función chk  30 4.3.3.  Función readImage  30 4.3.4.  Función storeImage  31 4.3.5.  Función main  32 4.3.6.  Kernel convolution.cl  32 

4.4. Aplicación del host para desemborronar una imagen  32 4.4.1.  Kernel deblur1.cl  34 4.4.2.  Kernel deblur2.cl  34 

Page 14: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

5  Resultados  35 5.1. Resultados numéricos  36 5.1.1.  Comparativa de tiempos de ejecución CPU/GPU  36 5.1.2.  Comparativa de tiempos de ejecución según elementos  37 5.1.3.  Conclusiones de resultados numéricos  40 

5.2. Resultados gráficos  40 5.3.Trabajo futuro  42 

6  Bibliografía  43 

 

Anexo A. Aplicación de convolución  43 1. Aplicación del host  43 2. Kernel convolution.cl  50 

 

Anexo B. Aplicación de deconvolución  53 1. Aplicación del host  53 2. Kernel deblur1.cl  63 3. Kernel deblur2.cl  65 

Page 15: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

xv

ÍNDICE DE TABLAS

Tabla 4.1. Características de la plataforma 28

Tabla 5.1. Comparativa tiempos de ejecución en CPU y GPU 37

Tabla 5.2. Comparativa tiempos según número de elementos 38

Tabla 5.3. Comparativa velocidad de ejecución por número de elementos 39

Page 16: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 17: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

xvii

ÍNDICE DE FIGURAS

Figura 1.1. Izquierda: Imagen original. Derecha: Imagen desemborronada. 1

Figura 2.1. Izquierda: pixel solitario brillante. Derecha: pixel solitario después de convolución. 4

Figura 2.2. Operación de convolución óptica. 4

Figura 2.3. Ejemplo gráfico de convolución discreta de dos dimensiones 6

Figura 2.4. Arriba: imagen emborronada de un pixel solitario (izq.) y un zoom de él (derecha). Abajo: dos imágenes emborronadas de píxeles solitaros cercanos al borde. 7

Figura 2.5. Arriba: Emborronamiento Horizontal (izquierda) y Desenfoque (derecha). Abajo: Emborronamiento Gaussiano (izquierda) y Emborronamiento de Moffat (derecha). 9

Figura 2.6. El PSF se esparce sobre el filo en el borde de la imagen (línea amarilla). 9

Figura 3.1. Diagrama de tareas secuencial y paralelo 20

Figura 3.2. Esquema del modelo de plataforma en OpenCL 21

Figura 3.3. Espacio global 22

Figura 3.4. Esquema de Contexto y elementos de los que se compone. 22

Figura 3.5. Esquema de memoria en una aplicación OpenCL. 23

Figura 3.6. Flujo de trabajo en OpenCL 24

Figura 4.1. Imagen de GPU AMD RADEON HD 6970 27

Figura 4.2. Diagrama de aplicación de convolución 29

Figura 4.3. Diagrama de función readSource 30

Figura 4.4. Diagrama de función readImage 31

Figura 4.5. Diagrama de función storeImage 31

Figura 4.6. Diagrama de aplicación de deconvolución 33

Figura 5.1. Arriba: Imagen original. Abajo: Imagen desemborronada 35

Figura 5.2. Gráfica Tiempo de ejecución-Elementos 39

Figura 5.3. Gráfica Velocidad de ejecución – Elementos 40

Figura 5.4. Ejemplo de desemborronado: La Gioconda 41

Figura 5.5. Ejemplo de desemborronado: La Giralda 41

Figura 5.6. Ejemplo de desemborronado: La Tierra a color 42

Page 18: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las
Page 19: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

1

1 INTRODUCCIÓN

uando usamos una cámara, queremos que la imagen captada sea una fiel representación de la escena que vemos, pero es algo normal encontrarse con imágenes más o menos emborronadas. Por esta razón, el desemborronado de imágenes es fundamental para conseguir fotografías definidas.

Figura 1.1. Izquierda: Imagen original. Derecha: Imagen desemborronada.

Para ello, se puede desarrollar un programa informático usando OpenCL (Open Computing Language, en español lenguaje de computación abierto) el cual consta de un lenguaje de programación que permite paralelizar la tarea de desemborronado.

Objetivo

El principal objetivo de este trabajo es desarrollar una aplicación que desemborrone imágenes aprovechando las ventajas que nos ofrece OpenCL. Implementaremos un algoritmo que será ejecutado tanto por una unidad central de computación (CPU) como por una unidad de procesamiento gráfico (GPU). Veremos si efectivamente se produce un incremento de velocidad al paralelizar el trabajo en la GPU respecto de la CPU mediante recogida de datos y estadísticas.

Contexto

Cuando hablamos de desemborronado de imágenes nos referimos al proceso de deconvolución. Este proceso no es más que una serie de operaciones matemáticas que se emplean para recuperar información de una señal que ha sido degradada por un proceso físico si este es se puede modelar como una convolución.

C

No hay nada peor que una imagen nítida de un concepto difuso.

- Ansel Adams -

Page 20: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Introducción

Con la convolución definimos el proceso de emborronado de una imagen. Aunque en realidad es un operador matemático que transforma dos funciones f y g en una tercera función, en óptica muchos tipos de “manchas” se modelan como convoluciones. Por ejemplo, una fotografía desenfocada es la convolución de la escena real (función f) con el círculo borroso formado por el diafragma del iris (función g).

Habrá que aplicar el operador matemático de deconvolución sobre una imagen digital la cual ha sufrido una “convolución física” previamente.

Una imagen digital está compuesta por píxeles, que son la menor unidad homogénea en color. A nivel de datos, una imagen se traduce en un vector de dos o tres dimensiones. Cada elemento del vector corresponde a un pixel que con su valor determina la intensidad de color en ese punto. En este trabajo sólo usaremos imágenes en escala de grises pues las imágenes en color no añaden complejidad. Una imagen en formato RGB, por ejemplo, está formada por un vector de tres dimensiones, o lo que es lo mismo, por tres vectores de dos dimensiones. Cada uno de estos tres vectores puede ser una imagen en escala de grises. Por tanto, desemborronar una imagen a color (RGB) es lo mismo que aplicar el proceso de deconvolución a cada una de las imágenes en blanco y negro.

Una pequeña imagen típica de 600 x 400 tiene 240.000 píxeles, aunque hoy en día cualquier smartphone posee una cámara capaz de tomar fotografías de entre 5 y 15 millones de píxeles. Por esta razón parece lógico paralelizar el trabajo para acelerar el proceso. Esto nos lo facilita OpenCL con sus librerías. Implementaremos el algoritmo de manera que cada porción de imagen sea independiente del resto y así poder calcular varios elementos al mismo tiempo.

Existen programas que realizan la tarea de desemborronado, e incluso otros lenguajes para tareas gráficas y paralelización como CUDA (propiedad de NVIDIA) pero en este trabajo nos centraremos en implementar un algoritmo matemático en OpenCL e interesarnos por el incremento de velocidad al ejecutar el programa en una GPU respecto a ejecutarlo en una CPU.

Page 21: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

3

2 ALGORITMO DE DECONVOLUCIÓN

na imagen puede ser mala por muchas razones. En este trabajo sólo tenemos en cuenta las imágenes emborronadas donde el “emborronamiento” es producido por algún proceso mecánico o físico que puede ser descrito por un modelo matemático lineal. Este modelo es importante ya que será lo que nos permita

establecer una ecuación cuya solución, al menos en principio, sea la imagen correcta.

Modelo matemático de convolución

2.1.1 Operación matemática

Recordamos que una imagen en escala de grises es sólo una matriz de dimensión m x n, cuyos elementos (píxeles) representan la intensidad de la luz. Esa matriz podemos disponerla en forma de vector con longitud mn. Cuando nos referimos a una imagen emborronada, usamos la notación B. Para la representación en vector de una matriz cualquiera usamos letras minúsculas. Por ejemplo, b será la representación de B en forma de vector:

b = vec(B)

Además, podemos imaginar la existencia de una imagen exacta que sería la que hubiéramos captado idealmente. Asumimos que la imagen exacta tiene las mismas dimensiones que la imagen emborronada, m x n. Así, representaremos la imagen exacta y la misma en forma de vector con la notación:

x = vec(X)

En el modelo lineal, existirá una matriz A de dimensiones N x N, con N = mn, tal que b y x está relacionadas mediante la ecuación:

A x = b

U

Usted no puede confiar en sus ojos si su imaginación está desenfocada.

- Mark Twain -

Page 22: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

La matriz A representa el proceso de emborronado que tiene lugar desde la imagen exacta a la imagen emborronada.

En este punto sabemos que existe tal matriz, pero ¿cómo la obtenemos? Imaginemos el siguiente experimento. Suponemos que la imagen exacta es totalmente negra, excepto por un pixel muy brillante en el centro. Si tomamos una fotografía desenfocada lo que ocurrirá es que el brillo del pixel central se difumina por los píxeles vecinos.

Figura 2.1. Izquierda: pixel solitario brillante. Derecha: pixel solitario después de convolución.

El pixel solitario brillante es llamado point source (en español: punto fuente), mientras que la función que resulta de su convolución point spread function (en adelante PSF, en español: función de dispersión de punto).

Ahora que conocemos la matriz PSF, la cual define cómo la información de cada pixel es esparcida por cada uno de sus píxeles vecinos, podemos entender mejor el concepto de convolución.

En matemáticas y, en particular, análisis funcional, una convolución es un operador matemático que transforma dos funciones f y g (que serán imágenes en nuestro caso) en una tercera función (otra imagen) que en cierto sentido representa la magnitud en la que se superponen f y una versión trasladada e invertida de g.

Figura 2.2 . Operación de convolución óptica.

Page 23: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

5 Desemborronado de Imágenes con OpenCL

Para dos funciones cualquiera, se define convolución como la integral del producto de ambas funciones después de desplazar una de ellas una distancia t:

Para funciones discretas se puede usar una forma discreta de la convolución:

Cada valor de la función b[m] es esencialmente una media ponderada de los valores de x[m], donde el peso de la ponderación es dada por la función psf[m]. A modo general, suponemos unos vectores dados:

;

Donde tanto wi como yi representan pixeles en la escena original que en realidad están fuera del campo de visión. El valor de los pixeles que quedan fuera del campo de visión dependerán de las condiciones de contorno que utilicemos y que explicaremos en el siguiente apartado. Con estos vectores, si hacemos la convolución de ellos obtenemos:

Page 24: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

Así, la convolución puede ser escrita como una multiplicación de matriz - vector de la forma b = A x:

(2.1)

Si entendemos la convolución para una sola dimensión, no es dificil entender la convolución de dos dimensiones. Siendo ahora X, B y PSF sendas matrices de dimensiónes 3x3.

, , ⨂ , , ,

A modo de ejemplo, el elemento b22 de la imagen de salida valdrá:

De una manera más gráfica, se puede ver la operación matemática de uno de los elementos con el siguiente esquema:

Figura 2.3. Ejemplo gráfico de convolución discreta de dos dimensiones

Vemos que la convolución no es más que una transformada de dos funciones o series de elementos discretos. En imágenes se puede tratar como recorrer una imagen (X) con un filtro de dispersión (PSF) para recoger parte de la información de los píxeles vecinos y concentrarlo en uno sólo. Si se toma una fotografía desenfocada, la

Page 25: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

7 Desemborronado de Imágenes con OpenCL

luz que debe ir a un solo píxel se dispersa sobre sus píxeles vecinos o, lo que es lo mismo, cada pixel almacenado contiene sólo parte de información de su propio punto de luz sumado a parte de información de los puntos de luz vecinos. Entendiendo los dos procesos, es lógico intentar desemborronar una imagen mediante una deconvolución, aunque el proceso inverso no sea una convolución matemática sino un proceso físico.

La matriz de emborronado A está determinada por dos ingredientes: la matriz PSF, que define cómo es emborronado cada pixel, y las condiciones de contorno, que especificarán qué ocurre cuando nos salimos del rango de la matriz imagen. Estos dos elementos es lo que vamos a tratar en los dos próximos subapartados.

2.1.2 Obtención de PSF

Podemos aprender varias propiedades importantes del proceso de emborronado con sólo mirar algunos filtros PSF. Considerar, por ejemplo, los siguientes, que muestran varios PSF de distintas formas con dimensiones de 120x120:

Figura 2.4. Arriba: imagen emborronada de un pixel solitario (izq.) y un zoom de él (derecha). Abajo: dos imágenes emborronadas de píxeles solitaros cercanos al borde.

En este ejemplo (y en muchos otros) la intensidad de la luz del PSF está reducido a una pequeña área alrededor del “centro” (localización del point source) y más allá de un cierto radio, la intensidad es esencialmente cero. En estos ejemplos, el PSF vale cero a partir de 15 píxeles desde el centro. En otras palabras, el emborronamiento es un fenómeno local. Además, si asumimos que se captura toda la luz del punto brillante entonces la suma de los valores de todos los píxeles del PSF debe valer 1.

Observamos también que el PSF es el mismo sin importar dónde se coloque el centro (sin salirnos de la imagen) por lo que podemos decir que el emborronado no varía con respecto al lugar de aplicación. En el proceso físico, esto no siempre es verdad, pero asumiremos que sí ya que es lo más frecuente en imágenes emborronadas.

Como consecuencia de la naturaleza lineal y local del PSF, para ahorrar espacio podemos considerar una matriz PSF mucho menor que la imagen a tratar (la imagen de arriba a la derecha en la Figura 2.4). Sin embargo, los algoritmos utilizados se servirán de una matriz de igual tamaño que la imagen por lo que nos veremos obligados a rellenar con ceros cada vez que nos salgamos del PSF.

En la mayoría de los casos el PSF puede ser descrito analíticamente mediante una función. Por ejemplo, el emborronamiento causado por un movimiento horizontal esparce el point source a través de una línea horizontal. Si ésta línea cubre r píxeles, entonces los elementos distintos de cero valdrán r -1. Para el proceso vertical es la

Page 26: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

misma función.

En otros casos, si conocemos cuál ha sido el proceso físico que ha causado el emborronamiento, es directo el formular el PSF. Por ejemplo, para un proceso de desenfoque la matriz es determinada por la función:

1

0

Donde (k, l) es el centro de PSF y r es el radio del emborronado.

Otro caso típico es el emborronamiento causado por turbulencias atmosféricas que puede ser descrito por una función Gaussiana bidimensional. Los valores de los elementos serán en este caso:

12

Y por último tenemos la función de Moffat que modela el PSF propio de un telescopio astronómico y viene dada por:

1

En éstas dos últimas los parámetros s1, s2 y ρ determinan el tamaño y la orientación del filtro, centrado de nuevo en las coordenadas (k, l). El parámetro positivo β de la función de Moffat controla cómo decaen los valores al alejarse del centro. En Moffat decaen más despacio que en Gauss.

Si ρ = 0 entonces los PSF son simétricos respecto los ejes horizontal y vertical y las funciones toman una forma más simple:

12

12

1

Si además s1 = s2, entonces el PSF es rotacionalmente simétrico. Hay que recordar que todas las funciones PSF deben sumar 1 por lo que habría que escalar las funciones dividiendo por la suma de todos los elementos de la matriz.

Page 27: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

9 Desemborronado de Imágenes con OpenCL

En la siguiente figura vemos un ejemplo de cada uno de los cuatro tipos de PSF de los que hemos hablado:

Figura 2.5. Arriba: Emborronamiento Horizontal (izquierda) y Desenfoque (derecha).

Abajo: Emborronamiento Gaussiano (izquierda) y Emborronamiento de Moffat (derecha).

2.1.3 Condiciones de contorno

La discusión del PSF en la sección anterior revela una dificultad en los bordes de la imagen, donde parte de la información de la escena real que queda fuera de la captura se introduce en la imagen emborronada. Obviamente, perdemos información que no puede ser recuperada. Consideramos un pixel bij cercano al borde. Recordamos que el pixel obtenido es una suma ponderada del pixel xij y sus vecinos, algunos de los cuales pueden estar fuera del campo de visión. En la siguiente figura se muestra un ejemplo:

Figura 2.6. El PSF se esparce sobre el filo en el borde de la imagen (línea amarilla), por lo que los valores de la escena real fuera de la imagen afecta a lo que es capturado.

Nosotros tendremos que suponer y asumir unas condiciones de contorno para poder reconstruir la imagen. Existen muchas técnicas, algunas de ellas basadas en extrapolación mediante análisis estático de la imagen, las cuales no veremos en este proyecto. Las técnicas que veremos serán de distintos tipos.

La condición de contorno más simple es asumir que la imagen exacta es negra más allá del borde. Para una

Page 28: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

10 

dimensión se puede ver volviendo a la ecuación (2.1). En este caso wi = yi = 0 por lo que b = A x quedaría:

(2.2)

Una matriz cuyos elementos son constantes en cada diagonal como en (2.2) se denomina matriz de Toeplitz con parámetros p.

Para dos dimensiones se puede conseguir rodeando la matriz X de matrices nulas (representadas como 0) de tamaño igual a la imagen:

Si ignoramos las condiciones de contorno con Xext estaremos implícitamente asumiendo condiciones de contorno cero (CCC).

Este tipo es una buena opción cuando la imagen exacta es en su mayoría negra más allá de los bordes como puede ser el caso de imágenes astronómicas. Desafortunadamente, la CCC tiene un mal resultado en reconstrucción de imágenes cuando éstas tienen unos bordes distintos de cero.

Por su simplicidad se podrá usar éste método en muchas ocasiones, sin embargo, habrá muchas otras en las que necesitaremos añadir información a los bordes distinta de cero. La información de la que disponemos es limitada y de hecho sólo tenemos la que nos proporciona la propia imagen. Por esta razón, en las siguientes técnicas de definición de condiciones de contorno usaremos la información que nos dan los píxeles cercanos al borde de la imagen.

La primera a considerar es la condición de contorno periódico (CCP) la cual es usada frecuentemente en procesamiento de imágenes. Ésta implica que la imagen se repite en todas las direcciones. De nuevo, para una dimensión se puede conseguir dando valores a los parámetros: w1 = x4, w2 = x5, y1 = x1, y2 = x2, o lo que es lo mismo:

(2.3)

Una matriz de Toeplitz en la cual cada fila (y columna) es un desplazamiento periódico de la fila (columna) anterior, como en (2.3), se denomina matriz circulante.

Para dos dimensiones podemos imaginarlo con la matriz extendida formada por la repetición de la matriz imagen:

Page 29: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

11 Desemborronado de Imágenes con OpenCL

En otras ocasiones podremos usar la condición de contorno reflejado (CCR). En esta se considera que la escena más allá del borde es un espejo de la imagen captada. Para una dimensión se puede ver si damos valores a los parámetros en la ecuación (2.1): w1 = x2, w2 = x1, y1 = x5, y2 = x4 quedando la operación:

(2.3)

Una matriz cuyos elementos son constantes en cada antidiagonal se denomina matriz de Hankel. La matriz en (2.3) es una suma de una matriz de Toeplitz con una de Hankel.

Para dos dimensiones se verá mejor si definimos tres matrices: Xlr, Xud y Xx que son la propia captura volteada horizontalmente, verticalmente y doblemente volteada en ambos ejes, respectivamente. Con ellas podemos formar la matriz extendida:

Las condiciones de contorno especifican nuestras suposiciones de lo que ocurrirá más allá del borde la imagen captada. Con la finalidad de obtener una imagen desemborronada de alta calidad, deberemos escoger las condiciones de contorno apropiadamente. Ignorarlas es equivalente a asumir el uso de CCC.

Por último, vamos a ver un ejemplo de dos dimensiones con matrices de 3x3 transformadas a vectores. En particular, para CCC el modelo lineal B = A X queda:

Si se presta atención, los elementos pij de la matriz A forman una estructura de Toeplitz según la coordenada j y cada bloque es además una matriz de Toeplitz según la coordenada i. Por tanto, estamos ante una matriz de bloque Toeplitz formada por bloques Toeplitz (BTTB). Estructuras similares se pueden formar con los demás tipos de matrices comentados:

BCCB: Bloque circulante con bloques circulantes. BHHB: Bloque Hankel con bloques Hankel. BTHB: Bloque Toeplitz con bloques Hankel. BHTB: Bloques Hankel con bloques Toeplitz.

Page 30: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

12 

Con esta notación, podemos definir con precisión la estructura de los coeficientes en la matriz de convolución para las distintas condiciones de contorno:

CCC: Como hemos visto, es una matriz BTTB. CCP: Formada por una matriz BCCB. CCR: En este caso la matriz está formada por una suma de matrices BTTB, BTHB, BHTB y

BHHB. Como vimos antes, estas matrices tienen en cuenta las contribuciones de X, Xlr, Xud y Xx respectivamente.

2.2 Cálculos con matrices estructuradas

Una vez definido el modelo de convolución, las operaciones matemáticas necesarias, obtener una matriz de filtro PSF y qué hacer cuando con la operación nos vamos más allá del borde hemos modelado por completo el proceso de emborronado de una imagen. De ahora en adelante definiremos la operación inversa. Para ello tendremos que ver la descomposición espectral de la matriz A para CCP y CCR. Es necesaria hacer esta descomposición para llevar a cabo los algoritmos que nos ocupan. Sin embargo, veremos más adelante que no será necesario formar estas matrices a partir del PSF para hacer la descomposición.

2.2.1 Matrices BCCB: Bordes periódicos

En esta sección consideramos las matrices BCCB. Éstas son espacialmente constantes en el desemborronado de imágenes cuando se emplean condiciones de contorno periódicas. Para el ejemplo de matrices de dimensiones 3x3 obtenemos:

Observamos que cualquier matriz BCCB es normal; esto es, A*A = AA* donde A* es la matriz transpuesta conjugada de A. Por esta razón, sabemos que A tiene una descomposición espectral unitaria. Es conocido que cualquier matriz BCCB tiene la descomposición espectral particular:

A = F* Λ F

Donde F es la matriz de la transformada de Fourier discreta unitaria bidimensional (DFT) y Λ es la matriz diagonal de los autovalores de A. Esta matriz tiene una propiedad muy conveniente: se puede enfocar la multiplicación con F y F*, sin la construcción de F explícitamente, usando la transformada rápida de Fourier (FFT).

Page 31: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

13 Desemborronado de Imágenes con OpenCL

La transformada rápida de Fourier para dos dimensiones y su forma inversa tienen la forma:

,1

√,

,1

√,

0 , 1, 0 , 1

A la primera la llamaremos fft2 y a la segunda ifft2. Debemos que tener en cuenta que aunque no sea necesaria la obtención de los autovalores de A, éstos deberían estar ordenados de mayor a menor. Por esta razón es necesario reordenar los valores de la matriz PSF. Se voltearán filas y columnas para que el centro de la dispersión (donde se encontrará el valor máximo) esté en las coordenadas (1,1). Para el ejemplo de 3x3, resulta:

Una vez conocida la descomposición espectral, podemos realizar eficientemente muchos cálculos con las matrices BCCB. Por ejemplo, para el emborronado de una imagen, seguiríamos los pasos:

1. PSF’ → Voltear matriz para que el centro quede en (1,1); 2. S, Fx → Transformada del filtro y de la imagen: S = fft2 (PSF’), Fx = fft2 (X); 3. FB → Transformada de la imagen emborronada, producto elemento a elemento de las matrices

calculadas: FB (i, j) = S (i, j) * Fx (i, j); 4. B → Parte real de la transformada inversa de lo anterior: B = real (ifft2 (FB));

Como dijimos, en los algoritmos la matriz PSF deberá tener el mismo tamaño que la imagen, por lo que habrá que rellenarla de ceros. Tanto la imagen como el filtro están compuestos por números reales, por lo que la salida B debe estar compuesta únicamente por números reales. Por lo tanto, despreciaremos la parte imaginaria que, si los cálculos son correctos, será muy pequeña respecto a la real.

Llegados a este punto, suponemos que existe la matriz A-1 para resolver el sistema:

b = A x ↔ x = A-1 b

Page 32: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

14 

Así, X puede ser calculada con un algoritmo similar al usado para la convolución:

1. PSF’ → Voltear matriz para que el centro quede en (1,1); 2. S, FB → Transformada del filtro y de la imagen: S = fft2 (PSF’), FB = fft2 (B); 3. FX → Transformada de la imagen real, producto elemento a elemento de las matrices

calculadas: FX (i, j) = FB (i, j) / S (i, j); 4. X → Parte real de la transformada inversa de lo anterior: X = real (ifft2 (FX));

2.2.2 Matrices BTTB + BTHB + BHTB + BHHB: Bordes reflejados

En este caso, suponemos que el filtro posee una doble simetría, lo cual es bastante común en la realidad. Por ejemplo, el modelo Gaussiano del emborronado por turbulencia atmosférica da una matriz A simétrica. A su vez, cada bloque que compone esta matriz es simétrico. Así, se puede observar que A es una matriz normal y tiene la descomposición espectral:

A = CT Λ C

Donde C es la matriz de la transformación discreta de coseno ortogonal para dos dimensiones (DCT). Es una matriz similar a F y el gasto computacional es aproximadamente el mismo en sus algoritmos, con la salvedad de que en este caso sólo usamos números reales y no tenemos que trabajar con números complejos.

La transformada discreta de coseno y su inversa tienen, para dos dimensiones, la forma:

, ,2 12

2 12

, ,2 12

2 12

0 , 1, 0 , 1

Page 33: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

15 Desemborronado de Imágenes con OpenCL

Donde los parámetros αp y αq valdrán:

1

√, 0

2,1 1

;

1

√, 0

2,1 1

A la transformada la llamaremos dct2 y a la inversa idct2. Como en el caso de la FFT, en DCT no es necesario la obtención de los autovalores del filtro, pero sí una pequeña traslación de los elementos de la matriz. O mejor dicho, cuatro pequeñas traslaciones ya que en realidad nuestra matriz de filtro está formada por la suma de cuatro matrices y así se verá más fácilmente. Suponiendo una matriz PSF dada de tamaño 3x3 entonces PSF’ quedará:

00

0 0 0+

0 00 0

0 0 0+

00 0 00 0 0

+0 0

0 0 00 0 0

Además de esto, su transformada ha de ser dividida por la transformada de la matriz e1 la cual está formada por ceros a excepción del elemento (1, 1) que es un 1.

El algoritmo de desemborronado, para condiciones de contorno reflejadas será por tanto:

1. PSF’ → Obtención de la matriz volteada; 2. S → Transformada del filtro: S (i, j)= dct2 (PSF’) (i, j) / dct2(e1) (i, j); 3. FB → Transformada de la imagen emborronada: FB = dct2 (B); 4. FX → Transformada de la imagen real: FX = FB / S; 5. X → Inversa de la matriz anterior: X = idct2 (FX);

Es importante destacar que si B además de haber sido emborronada contiene un poco de ruido, con estos algoritmos se consigue una muy pobre aproximación de la imagen real. Por lo tanto, será necesario el uso de filtros para el ruido, cuyo proceso se conoce también como regularización.

Regularización: métodos de filtrado

Para la regularización será necesario aplicar ciertas condiciones de regularidad, y el grado de éstas se rige por un parámetro que debe ser elegido cuidadosamente. Hablaremos de dos métodos de regularización para finalmente elegir uno de ellos en nuestro trabajo.

Page 34: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

16 

Hemos dicho que resolver la ecuación A x = b exactamente no produce una buena solución cuando los datos de b está contaminados por ruido. En vez de eso, usaremos un filtro en la solución espectral. Los métodos de filtrado espectral trabajan mediante la elección de los factores de filtro φi en la ecuación:

Donde ⋯ 0 son los autovalores de A. Estos métodos operan en los datos de b en las coordenadas determinadas por el vector ui (i=1,…, N) y expresan la solución xfilt en las coordenadas determinadas por el vector vi (i=1,…, N). Los vectores u y v serían, en cada caso los elementos matriciales de F o C según se usen FFT o DCT.

El Método de TSVD. En español, descomposición del valor singular truncado. Para este método definimos los factores de filtrado de manera:

≡1, 1,… , 0, 1, … ,

Donde k es el llamado parámetro de truncado. Con el conocido valor decreciente de los autovalores de A, el parámetro valdrá uno para valores grandes y cero para valores pequeños.

El Método de Tikhonov. Para este método definimos los factores de filtrado de manera:

Donde α > 0 es el llamado parámetro de regularización.

Para la regularización TSVD, elegimos el parámetro de truncado k de manera que el residual de ‖ ‖ sea razonablemente pequeña, pero la solución x no incluya componentes correspondientes a valores singulares pequeños , … , .

El parámetro α en el método de Tikhonov actúa en el mismo sentido que el parámetro k en el método TSVD: controla qué componentes queremos disminuir o filtrar. Además vemos que no es posible elegir α fuera del intervalo , .

Si asumimos que A tiene inversa, entonces la solución filtrada quedará:

Page 35: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

17 Desemborronado de Imágenes con OpenCL

Donde es una matriz diagonal cuyos elementos son los factores de filtro según el método elegido. Hemos dicho poco sobre cómo se eligen los valores de los parámetros k o α, excepto que deben satisfacer 1 y . Existen métodos “automáticos” de elección de parámetros, pero por ahora intentaremos elegirlos experimentalmente.

Para terminar, el algoritmo de deconvolución de una imagen sería el siguiente, dependiendo de las condiciones de contorno elegidas y del método de filtrado:

Datos:

PSF = Matriz de filtro de convolución. B = Imagen emborronada. Si usamos CCC, CCP o CCR. Si usaremos método de TSVD o Tikhonov.

Para condiciones de contorno periódicas:

1. PSF’ → Voltear matriz para que el centro quede en (1,1); 2. S, FB → Transformada del filtro y de la imagen: S = fft2 (PSF’), FB = fft2 (B); 3. φ → Dependiendo del método utilizado:

a. TSVD: φ (i, j) = abs(S(i, j)) ≥ σk; b. Tikh.: φ (i, j) = abs(S(i, j))2 / (abs(S(i, j))2 + α2);

4. Sfilt → Transformada filtrada: Sfilt(i, j) = φ(i, j) / S(i, j); 5. FX → Transformada de la imagen real, producto elemento a elemento de las matrices

calculadas: FX (i, j) = FB (i, j) * Sfilt (i, j); 6. X → Parte real de la transformada inversa de lo anterior: X = real (ifft2 (FX));

Para condiciones de contorno reflejadas:

1. PSF’ → Obtención de la matriz volteada; 2. S → Transformada del filtro: S (i, j)= dct2 (PSF’) (i, j) / dct2(e1) (i, j); 3. φ → Dependiendo del método utilizado:

a. TSVD: φ (i, j) = abs(S(i, j)) ≥ tol; b. Tikh.: φ (i, j) = abs(S(i, j))2 / (abs(S(i, j))2 + α2);

4. Sfilt → Transformada filtrada: Sfilt(i, j) = φ(i, j) / S(i, j); 5. FB → Transformada de la imagen emborronada: FB = dct2 (B); 6. FX → Transformada de la imagen real: FX (i, j) = FB (i, j) * Sfilt (i, j); 7. X → Inversa de la matriz anterior: X = idct2 (FX);

Page 36: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Algoritmo de deconvolución

18 

Page 37: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

19

3 OPENCL

legados a este punto conocemos el algoritmo de desemborronado por más de una forma. En el presente apartado nos centraremos en la programación de OpenCL para más tarde implementar el algoritmo de deconvolución que nos interese.

3.1 Cálculo paralelo

El cálculo paralelo es una expresión general usada para describir la utilización de recursos computacionales que ejecutan tareas en paralelo, los cuales no necesariamente tienen la misma naturaleza, sino que pueden ser de distinto tipo, fabricante e incluso finalidad. Su objetivo práctico es el aprovechamiento de dispositivos dotados de paralelismo para el tratamiento masivo de datos que se han vuelto muy asequibles en los últimos tiempos, concretamente las tarjetas gráficas aceleradas o GPUs.

El paralelismo contemplado es completo y a nivel de hardware, es decir, en cada instante de tiempo hay más de una tarea ejecutándose. A diferencia de pseudoparalelismos a nivel de software común en los procesadores con un solo núcleo y sistema operativo multitarea.

En el siguiente diagrama se observa la diferencia entre un proceso secuencial y uno paralelo, siendo las tareas A y B paralelizables, no siéndolo la tarea C. El ahorro de tiempo es obvio.

L

Ninguno de nosotros es tan bueno como todos nosotros juntos.

- Ray Kroc -

Page 38: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

OpenCL

20 

Figura 3.1. Diagrama de tareas secuencial y paralelo

La herramienta utilizada en la realización de este trabajo es OpenCL, que consta de una interfaz de programación de aplicaciones y de un lenguaje de programación. Juntos permiten crear aplicaciones con paralelismo a nivel de datos y de tareas que pueden ejecutarse tanto en CPUs como en GPUs. El lenguaje está basado en C99, eliminando cierta funcionalidad y extendiendo con operaciones vectoriales al lenguaje C. Es un estándar abierto y libre de derechos mantenido por Khronos Group y soportado por la marca AMD. El principal rival de este sistema es CUDA, mantenido por la marca nVidia, existiendo un alto grado de compatibilidad entre ambos.

3.2 Modelo de plataforma

Una plataforma de hoy día suele incluir al menos una CPU y al menos una GPU, siendo opcional la instalación de algún tipo de acelerador.

La plataforma se define como un host o anfitrión (que será siempre la CPU) conectado a uno o más dispositivos de cálculo (que pueden ser la CPU, la GPU o ambas a la vez).

El host es el entorno de trabajo compuesto por el ordenador, el sistema operativo y la aplicación en la cual se programa y se lanza a su vez una aplicación que produce y gestiona las cómo se ejecutan las tareas en los dispositivos de cálculo.

Un dispositivo de cálculo (en inglés Computing Device, CD) se puede identificar con un sistema hardware que procesa datos, es decir, realiza cálculos. En este caso estamos hablando de CPU o GPU. Un dispositivo de cálculo está formado por una o varias unidades de cálculo.

Una unidad de cálculo (en inglés Computing Unit, CU) se puede identificar con los núcleos del dispositivo. Cada unidad del dispositivo es un motor de cálculo que pueden trabajar juntos o por separado. A su vez, está dividido en elementos de procesamiento.

El elemento de procesamiento (en inglés Processing Element, PE) se trata de cada una de las unidades de ejecución de una instrucción. Cada elemento es capaz de actuar por separado y a la vez. Nuestro objetivo será utilizar los máximos posibles.

Page 39: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

21 Desemborronado de Imágenes con OpenCL

Figura 3.2. Esquema del modelo de plataforma en OpenCL

Una aplicación en OpenCL estará formada por dos tipos de código. Uno se ejecutará en el host (escrito en C/C++). Este se encargará de enviar órdenes a los dispositivos como la transferencia de datos entre la memoria del host y las memorias del dispositivo. Además, se encargará de enviar y procurar que se ejecute el otro tipo de código (escrito en C99) el cual se ejecuta en cada dispositivo de cálculo.

Típicamente el host se encargará de ejecutar en serie un solo hilo de código en la CPU. Mientras, varios hilos de código se ejecutarán en paralelo en el dispositivo de cálculo elegido (CPU o GPU) a través de las unidades de cálculo en cada uno de los múltiples elementos de procesamiento.

3.3 Modeo de ejecución

Para lograr el paralelismo que buscamos, la tarea debe diferenciar su resultado en función de un dato de entrada, nunca en función de otro tipo de condiciones en sus operaciones.

En el modelo de ejecución la idea es descomponer la tarea en work-items (WI). Para ello, hay que definir las N dimensiones en las que trabajamos y ejecutar un kernel en cada punto del espacio de trabajo. Por ejemplo, si consideramos la operación de sumar elemento a elemento dos vectores para obtener un tercero, la diferencia entre un bucle tradicional en C y un kernel de OpenCL sería que mientras en C necesitamos un bucle para recorrer todas las coordenadas del vector (por ejemplo un bucle for) y sumar los elementos uno a uno, en OpenCL podemos dividir el trabajo y que cada unidad de cálculo se ocupe de una coordenada.

Tendremos, por tanto, que encontrar la mejor manera de dividir el espacio de trabajo en dimensiones para nuestro algoritmo. Los kernels son ejecutados a través del espacio de trabajo global, que se divide en work-groups, que a su vez están divididos en work-items.

Page 40: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

OpenCL

22 

Figura 3.3. Espacio global

El tamaño del espacio global ha de ser múltiplo del tamaño del work-group, aunque no todos los work-items que eso representa sean necesarios.

Como sabemos, la aplicación se ejecuta en el host donde envía el trabajo a los dispositivos. Para ello, es necesaria la definición de un contexto, que es el entorno en que los work-items se ejecutan y engloba los dispositivos y sus memorias, así como las colas de comandos. Esto último, es la cola usada por la aplicación del host para enviar trabajo al dispositivo, por ejemplo, podrá enviar la petición de ejecución del kernel.

Figura 3.4. Esquema de Contexto y elementos de los que se compone.

Por último, definimos los distintos tipos de memoria. La memoria que usa una aplicación se dividirá de la misma forma que dividimos el espacio de trabajo. Así, cada work-item trabaja sobre una memoria privada propia. Las memorias privadas de cada work-item no pueden comunicarse entre ellas, pero sí con una memoria local, perteneciente al work-group. De la misma manera, las memorias locales se comunicarán con la memoria global del dispositivo, pero no entre ellas. Por último, el host tendrá su propia memoria en la CPU.

Page 41: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

23 Desemborronado de Imágenes con OpenCL

Figura 3.5. Esquema de memoria en una aplicación OpenCL.

Cabe destacar que la gestión de los datos debe ser siempre en vertical, es decir: host, global, local, privada o en sentido contrario. Así, si queremos que dos work-items se comuniquen tendremos que usar la memoria local de su work-group.

3.4 Programación en OpenCL

Como hemos visto en los apartados anteriores, será necesario definir y crear muchos objetos antes de ejecutar un kernel: elegir plataforma y dispositivo, creación de contexto, creación de cola de comandos… Aunque parezca un trabajo arduo, sea cual sea el kernel los pasos de preparación de una aplicación OpenCL son los mismos, por lo que se convierte en un proceso bastante mecánico y repetitivo. El flujo de trabajo sería el siguiente:

Page 42: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

OpenCL

24 

Figura 3.6. Flujo de trabajo en OpenCL

3.4.1 Detección y selección de la plataforma y los dispositivos

Tendremos que “preguntar” al ordenador por las plataformas y los correspondientes dispositivos que tiene instalados con un controlador compatible con OpenCL. Podremos encontrarnos ante, por ejemplo, una CPU Intel y una GPU nVIDIA cada una de ellas con su correspondiente controlador. Esto aparecerá como dos plataformas y cada una con un dispositivo. Sin embargo, existe la posibilidad de que nuestro ordenador monte una placa AMD con una GPU también AMD, regidas ambas por el mismo controlador. En este caso, aparecerá en la aplicación como una sola plataforma con dos dispositivos.

Las funciones que nos permiten detectar y almacenar los identificadores de plataformas y dispositivos son:

clGetPlatformIDs: habrá que recurrir a ella dos veces. En la primera sólo obtendremos el número de plataformas disponibles, pero esto nos permitirá crear el vector en el que se guardarán sus identificadores una vez hagamos la segunda llamada.

clGetDeviceIDs: al llamar a esta función será necesario, obviamente, especificar de qué plataforma queremos conocer los dispositivos. Además, debemos seleccionar mediante un flag si queremos discriminar los dispositivos según su tipo.

Son útiles también las funciones clGetPlatformInfo y clGetDeviceInfo para identificar las plataformas y los dispositivos con los que vamos a trabajar y conocer sus características tales como el número de CUs, tamaños de las memorias local y global o frecuencia del reloj.

Page 43: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

25 Desemborronado de Imágenes con OpenCL

3.4.2 Creación del contexto

Antes de crear un contexto debemos saber que un contexto sólo puede pertenecer a una plataforma. Sin embargo, uno o varios dispositivos de la misma pueden pertenecer al mismo contexto. Usaremos la función clCreateContext para crearlo.

3.4.3 Creación de la cola de commandos

Dentro del contexto, a cada dispositivo le corresponde una cola de comandos. Usaremos la función clCreateCommandQueue para crearla. En sus atributos podremos decir para qué dispositivo lo estamos haciendo, si queremos que la ejecución sea en orden o no (in-order o out-of-order) y algunas propiedades más.

3.4.4 Definición de los objetos de memoria

Existen dos tipos de objetos de memoria: objetos de buffer u objetos imágenes. Los objetos de memoria pueden ser copiados al host, copiados desde el host o copiados con otros objetos de memoria.

Un objeto de buffer es un vector con una sola dimensión. Sus elementos son escalares, vectores o cualquier estructura definida por el usuario. Se puede acceder a ellos desde el código del dispositivo a través de punteros. Se crea con la función clCreateBuffer.

Un objeto imagen sin embargo está constituido por un vector de dos o tres dimensiones. Sus elementos son vectores de cuatro componentes de una lista predefinida de formatos. Se accede a ellos desde el código del dispositivo a través de funciones integradas. Se crea con la función clCreateImage. Uno de sus argumentos será una estructura con las propiedades de la imagen: dimensiones, tamaño, etc.

Una vez creados los objetos de memoria, los pondremos en la cola de comandos creada anteriormente con las funciones clEnqueueWriteBuffer y clEnqueueWriteImage.

3.4.5 Compilación del programa y creación del kernel

El siguiente paso consiste en asociar un objeto programa al contexto. El objeto programa es el resultado de la traducción, compilación y enlazado que se realiza sobre el código fuente escrito en lenguaje OpenCL para cada dispositivo. Este programa objeto puede obtenerse a partir del código escrito en un fichero de texto plano, a partir de una cadena de caracteres definida en el mismo código de la aplicación del host o bien a través de los archivos binarios producidos por una compilación anterior.

Para los dos primeros casos y después de haber pasado el texto del fichero a cadena de caracteres, se usará la función clCreateProgramWithSource. Si lo que tenemos es un archivo binario usaremos clCreateProgramWithBinary. Una vez creado, tendremos que compilarlo con la función clBuildProgram.

El siguiente paso será crear el kernel con la función clCreateKernel y copiarle los parámetros necesarios con la función clSetKernelArg.

Llegados a este punto, el código del host invoca al kernel sobre un espacio de trabajo llamado NDRange. Este es el rango de dimensiones de los work-items. Puede ser un espacio de una, dos o tres dimensiones. El número de dimensiones del work-group tiene que ser el mismo que el número de dimensiones de los work-items. El kernel de N dimensiones se encola con la función clEnqueueNDRangeKernel.

Para terminar, hay que llamar a la función clEnqueueReadBuffer o a clEnqueueReadImage para guardar la información de salida en los objetos de memoria correspondientes.

Page 44: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

OpenCL

26 

3.4.6 Liberar recursos

La liberación de recursos se consigue con las funciones clReleaseKernel, clReleaseMemObject, clReleaseProgram, clReleaseCommandQueue y clReleaseContext.

Page 45: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

27

4 RESOLUCIÓN DEL PROBLEMA

na vez llegados hasta aquí, ya conocemos los cálculos matemáticos necesarios para una deconvolución de una imagen así como los principios básicos de programación en OpenCL. Pasaremos a resolver el problema que se nos plantea en este trabajo.

4.1 Entorno particular de trabajo

Como ya sabemos, la herramienta de trabajo OpenCL consta de una interfaz de programación de aplicaciones (App SDK) y de un lenguaje de programación. Juntos nos permiten crear aplicaciones con paralelismo a nivel de datos y de tareas. El lenguaje está basado en C99, eliminando cierta funcionalidad y extendiéndolo con operaciones vectoriales. Es un estándar abierto y libre de derechos mantenido por Khronos Group y soportado por AMD.

Para el caso que nos ocupa, el dispositivo usado es una GPU AMD RADEON HD 6970. Su integrado recibe el nombre de Cayman XT, perteneciente al grupo de arquitecturas Northen Islands, de tipo VLIW (Very Long Instruction Word).

Figura 4.1. Imagen de GPU AMD RADEON HD 6970

U

No podemos resolver problemas pensando de la misma manera que cuando los creamos.

- Albert Einstein -

Page 46: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resolución del problema

28 

Gracias a las funciones clGetPlatformInfo y clGetDeviceInfo podemos obtener muchos datos de la plataforma y los dispositivos utilizados. Así, la plataforma se llama AMD Accelerated Parallel Processing, compatible con OpenCL 1.2 y monta conjuntamente CPU y GPU. Ambos dispositivos de la plataforma tienen las siguientes características interesantes:

Dispositivo Cayman XT AMD Phenom™ II X4 975

Máx. Unidades de cálculo 24 4

Máx. Tamaño de work-group 256 1024

Máx. Frecuencia de reloj (MHz) 880 3612

Tamaño memoria local (kB) 32 32

Tamaño memoria global (GB) 2 2

Tabla 4.1. Características de la plataforma

Como vemos, la GPU consta de 24 unidades de cálculo y cada una de ellas posee 16 ALU (de sus siglas en inglés Arithmetic Logic Unit). Las ALUs son cada una de las partes que son capaces de realizar operaciones aritméticas en un una unidad de cálculo mediante elementos de procesamiento. Esta posee 4 elementos de procesamiento por cada ALU.

En total tenemos 24x16x4=1536 elementos de procesado y cada uno de ellos constituirá, en la ejecución de un kernel, un work-item. El tamaño por defecto de los work-group será los que entran en una unidad de cálculo, es decir 16x4=64 work-items, siendo el tamaño máximo 256.

4.2 Planificación del trabajo

Los pasos que seguimos para la resolución del problema son, una vez entendidos los conceptos de los apartados 2 y 3:

Programar aplicación que emborrone una imagen. Aplicar uno de los algoritmos de deconvolución. Optimización de la aplicación. Ejecutar con GPU y CPU. Comparar resultados.

Las aplicaciones del host de convolución y deconvolución son bastante parecidas. La lectura, escritura de imágenes, preparación de dispositivos, compilación del kernel/kernels, etc. son idénticos. La diferencia se encuentra en el kernel o kernels utilizados.

Para ejecutarlas sólo tenemos que ejecutar el .exe correspondiente y que en su misma carpeta esté alojada la imagen que deseamos desemborronar con el nombre “input.bmp” y los archivos de texto plano con los kernels como “convolution.cl” para la primera aplicación y “deblur1.cl” y “deblur2.cl” para la segunda.

Page 47: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

29 Desemborronado de Imágenes con OpenCL

Para la aplicación de convolución será necesario un solo kernel que aplique un filtro a una imagen dada y escriba como salida la misma imagen emborronada.

La aplicación de deconvolución sin embargo usará dos kernels. Ya que, sean cuales sean las condiciones de contorno elegidas, es necesaria una transformación rápida y posteriormente la inversa de tal transformación. Esto no complica demasiado la aplicación del host respecto al anterior ya que será como si se aplicaran dos procesos con imágenes en cascada usando una imagen auxiliar. Ésta será la salida del primer kernel y la salida del segundo.

La optimización se basa en elegir adecuadamente el tamaño de cada work-group y del tamaño del espacio global de trabajo. Como sabemos, el tamaño máximo de work-group es 256, por lo que el tamaño de nuestro work-group bidimensional será 16x16. El tamaño del espacio global ha de ser un múltiplo entero del tamaño del work-group. En la mayoría de los casos, la imagen no tendrá unas dimensiones ajustadas a un número determinado de work-groups, por lo que el espacio global tendrá un tamaño mayor que el de la imagen. Será necesario, dentro del kernel, discriminar la ejecución si nos encontramos en unas coordenadas que no corresponden a la imagen. Aunque en un principio trabajar en un espacio mayor nos lleve a pensar que es más lento, veremos que al trabajar de manera “ordenada” la velocidad es mayor.

Finalmente, con la aplicación optimizada para nuestra tarjeta gráfica, haremos diversas pruebas de velocidad y de desemborronado.

4.3 Aplicación del host de convolución

La aplicación del host de convolución prepara la plataforma para leer una imagen, leer un archivo de texto plano que compilará creando el kernel y tras ejecutarse, escribirá la imagen de salida. En el siguiente diagrama podemos verlo mejor, y las funciones que lo hacen posible.

Figura 4.2. Diagrama de aplicación de convolución

En los siguientes subapartados explicaremos cada función, dejando el código completo para los anexos.

Page 48: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resolución del problema

30 

4.3.1 Función readSource

Esta función no hace más que recibir como entrada una cadena de caracteres que determina la ruta a seguir para encontrar el archivo de texto plano en el que se encuentra el código del kernel. Guardar el código en una cadena de caracteres como salida. La ruta al kernel está generalizada en este ejemplo: “./convolution.cl”. Esto quiere decir que el archivo tiene que estar en la misma carpeta del proyecto (sea cual sea la ubicación de tal carpeta) para que sea leído.

Figura 4.3. Diagrama de función readSource

4.3.2 Función chk

Función para el tratamiento de errores. Toma como entrada la variable status, que pondremos como salida de las funciones propias para obtener el código de error. Como segundo argumento de entrada se dará una cadena de caracteres para indicar en qué función se produjo el error. No tendrá variable de salida ya que su propósito es mostrar por pantalla el error obtenido y terminar el programa.

4.3.3 Función readImage

Es la encargada de leer los archivos de mapa de bits (BMP) y almacenarlo como tabla numérica (float *). Para ello recibe como entrada el nombre del archivo que debe abrir y dos punteros a enteros para almacenar el ancho y el alto de la imagen.

Leer el archivo BMP es igual que leer una cadena de caracteres teniendo varias consideraciones en cuenta. La primera es que el propio archivo te da la información del tamaño de la imagen, lo cual es muy útil para reservar la memoria que utilizará la imagen. La segunda consideración es conocer cómo están almacenados los datos. En mapa de bits, la imagen se almacena de izquierda a derecha y de abajo a arriba. Primero la leeremos tal como viene y posteriormente se volteará.

Por último transformamos los caracteres que hemos leído a formato numérico.

Page 49: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

31 Desemborronado de Imágenes con OpenCL

Figura 4.4. Diagrama de función readImage

4.3.4 Función storeImage

En esta función haremos lo contrario que en la anterior, a partir de una cadena numérica, crearemos una imagen BMP. La función recibe como entradas la cadena de números float, la cadena de caracteres que indica el nombre que debe tener la imagen, tamaño de la imagen y la cadena de caracteres con el nombre del archivo de entrada como referencia.

Figura 4.5. Diagrama de función storeImage

Page 50: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resolución del problema

32 

Usaremos la imagen de entrada para determinar el offset para escribir el tamaño y los datos de la imagen. Como dijimos en la función readImage los datos están ordenados de abajo a arriba en el archivo por lo que tendremos que escribirlos de tal manera.

4.3.5 Función main

Última función y como su nombre indica, es la más importante. Prepara el equipo y lleva el hilo principal de ejecución del programa. Sigue el flujo que vimos en la Figura 3.5. y usa las funciones que hemos visto anteriormente como en la Figura 4.1.

Lo primero será la declaración de las variables. Tamaño de la imagen, declaramos los nombres de la imagen de entrada y salida que serán siempre “input.bmp” y “output.bmp”. Leemos y almacenamos la imagen de entrada con la función readImage y sabiendo su tamaño, reservamos la memoria necesaria para la imagen de salida. Además, creamos el filtro PSF, que será constante, de tipo gaussiano y tamaño 5x5.

El siguiente paso es detectar la plataforma y los dispositivos. Elegimos que queremos utilizar el primero dispositivo que se pueda con GPU. Así elegiremos la tarjeta con la que estamos trabajando.

Lo siguiente es la creación de los objetos necesarios. El primero, creación de contexto para el dispositivo elegido y después creación de la cola de comandos para tal contexto. Posteriormente habrá que crear objetos para las imágenes de salida, entrada y para el buffer del filtro.

Pasamos a encolar imagen de entrada y filtro y creamos el sampler de la imagen, lo que determinará cómo es leída dentro del kernel.

El siguiente paso será leer y almacenar en cadena de caracteres el código del kernel con la función readSource. Una vez hecho esto, creamos el objeto programa y lo compilamos a través de lo que hemos leído anteriormente. Una vez hecho esto habrá que crear el objeto del programa y determinar sus argumentos. Por último, habrá que encolar el kernel lo que hará que se ejecute.

Encolamos la lectura de la imagen de salida y usamos la función storeImage para guardarlo en un archivo. Después de eso acabamos el programa.

4.3.6 Kernel convolution.cl

El kernel se ejecutará para cada pixel de la imagen de entrada. Sabido esto, habrá que recorrer con dos bucles “for” el tamaño del filtro y sumar la multiplicación del elemento filtro por los pixeles vecinos y sumarlos en el pixel sobre el que estamos trabajando.

4.4 Aplicación del host para desemborronar una imagen

Como dijimos antes, la aplicación del host de convolución y deconvolución son muy similares.

Los cambios más significativos se producen en la función main donde habrá que ejecutar dos kernels. Para ello, habrá que definir dos de lo necesario como crear dos objetos programa, definir los argumentos de cada kernel y encolarlos. Además, usaremos una imagen auxiliar que nos guardará la información a la salida del primer kernel y lo introducirá a la entrada del segundo como método de sincronismo.

Page 51: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

33 Desemborronado de Imágenes con OpenCL

Figura 4.5. Diagrama de aplicación de deconvolución

Existen métodos de sincronismo más sofisticados en OpenCL pero esta manera además de simple es más versátil sea cual sea el tamaño de trabajo.

Además, con el objetivo de la optimización del trabajo, hemos de determinar un nuevo espacio global de trabajo. El máximo tamaño de un work-group es de 256 elementos, por lo que para dos dimensiones parece apropiado dividirlo en 16x16 elementos. Sin embargo, el tamaño global debe ser un múltiplo entero de work-groups. Para cada dimensión, dividiremos el tamaño de la imagen por el tamaño local. Si el resultado resulta no ser entero, redondearemos al siguiente entero mayor que el resultante. Una vez determinado el número de work-groups que caben en cada dimensión, multiplicamos este número por el tamaño local para obtener el tamaño global.

Recordamos el algoritmo que vamos a utilizar. Consideraremos condiciones de contorno reflejadas y el método de TSVD, por lo que el algoritmo es:

1. PSF’ → Obtención de la matriz volteada; 2. S → Transformada del filtro: S (i, j)= dct2 (PSF’) (i, j) / dct2(e1) (i, j); 3. φ → Dependiendo del método utilizado:

a. TSVD: φ (i, j) = abs(S(i, j)) ≥ tol; 4. Sfilt → Transformada filtrada: Sfilt(i, j) = φ(i, j) / S(i, j); 5. FB → Transformada de la imagen emborronada: FB = dct2 (B); 6. FX → Transformada de la imagen real: FX (i, j) = FB (i, j) * Sfilt (i, j); 7. X → Inversa de la matriz anterior: X = idct2 (FX);

Como usaremos siempre el mismo filtro y el mismo método, nos ahorrará tiempo si lo consideramos ya volteado en nuestro código. El primer kernel se encargará de los puntos en los que trabajamos con la transformada, mientras que el segundo kernel se ocupará del último punto, con el único objetivo de hacerle la inversa de la transformación.

Como método de medición antes y después de ejecutar los kernels se han añadido varias líneas de código para medir el tiempo de ejecución.

Page 52: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resolución del problema

34 

4.4.1 Kernel deblur1.cl

Para empezar, tenemos que saber que los argumentos de entrada son una imagen de entrada, una imagen de salida, número de filas y número de columnas de la imagen, el filtro y el tamaño del filtro y por último el sampler de la imagen. Sólo tendremos la capacidad de escribir sobre la imagen de salida.

Dicho esto, lo primero será saber en qué punto del espacio de trabajo estamos. Lo conseguimos con llamadas a la función get_global_id para cada dimensión, y almacenamos su resultado en dos constantes que nos servirán de coordenadas alrededor de las cuales trabajar. Como hemos dicho antes, el espacio de trabajo es mayor que el tamaño de la imagen, por lo que será necesaria una discriminación por coordenadas. El kernel sólo trabajará si las coordenadas en la que nos encontramos están dentro de la imagen. Si no, saldrá automáticamente.

Declaramos las variables que vamos a necesitar como coordenadas para bucles, variables de acumulación para los sumatorios, parámetros y una variable que almacene el valor del coseno, que será igual para los tres transformadas que tendremos que hacer: a la imagen, al filtro y a la matriz e1. Además, nos harán falta unas coordenadas (int2) para acceder a la imagen de entrada y una variable pixel (float4) para almacenar el valor de salida.

Creamos un bucle que recorre toda la imagen, para realizar los sumatorios de la transformada de coseno discreta. Parece que recorrer la imagen completa y hacer un sumatorio de sus elementos es algo que se podría paralelizar. Sin embargo, el sumatorio depende de las coordenadas del pixel en cuestión, por lo que hay que hacer un sumatorio por cada pixel obligatoriamente. Discriminamos el espacio para e1, ya que sólo vale 1 para el elemento (1,1) y cero para el resto de la imagen. De la misma manera, sólo sumaremos para el tamaño del filtro dado, ya que fuera todo valdrá cero.

Una vez salimos del bucle, multiplicamos por los parámetros alfa de la transformada según dónde estemos. Con ello, obtenemos el valor de S=dct2(PSF’)/dct2(e1) y de FB=dct2(B), para el pixel en el que trabajamos.

El siguiente paso es filtrar la transformada S y si corresponde hacerle la inversa para después multiplicar por B. O lo que es lo mismo, si pasa el filtro dividimos B/S.

Con esto, ya tenemos el resultado final de la transformada y escribimos en la imagen de salida el resultado. Si escribiéramos el resultado en un archivo BMP sería una imagen sin ningún sentido, aunque sólo nos queda la operación de hacerle una transformación inversa de coseno discreto.

4.4.2 Kernel deblur2.cl

Entendiendo la operación directa es más fácil entender la inversa. Para empezar, este kernel tiene dos argumentos de entrada menos, ya que el filtro ya no es necesario. Sólo utilizaremos las imágenes de entrada y salida, el tamaño de la imagen y el sampler de lectura.

Como antes, lo primero es ver dónde nos encontramos con get_global_id y comprobar si debemos ejecutar el kernel o no. Como variables, también nos harán falta coordenadas y variables de almacenamiento para los sumatorios y los parámetros.

De la misma manera, se realizarán dos bucles alrededor de toda la imagen para hacer dos sumatorios de cosenos. Al salir de los sumatorios, se comprueba si el resultado es mayor que cero y en el caso de ser negativo se convierte en cero. Como es normal, habrá pequeños errores de cálculo en tantos elementos por lo que es normal encontrarse valores fuera del rango de valores de una imagen ([0,255]). Si el valor es menor que cero en la imagen se verán cosas que no tienen sentido. En cambio, si el valor es mayor que 255 este se ajustará a 255 automáticamente.

Para terminar, escribimos el resultado en la imagen de salida.

Page 53: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

35

5 RESULTADOS

a imagen elegida para las pruebas tiene un tamaño de 200x300, al igual que la imagen que resulta:

Figura 5.1. Arriba: Imagen original. Abajo: Imagen desemborronada

L

El éxito es un 99% de fracaso.

- Soichiro Honda -

Page 54: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resultados

36 

5.1 Resultados numéricos

En este apartado intentaremos analizar resultados numéricos que podamos obtener de la ejecución de la aplicación. Lo más inmediato es pensar en el tiempo que tarda en hacerlo. Podemos entender la optimización de la aplicación como lo rápida que sea en ejecutarse. Para comprobar cómo de rápida es necesitamos compararlo con otras mediciones. Por esta razón compararemos primero lo que se tarda en ejecutar en otro dispositivo distinto a la GPU con la que trabajamos como por ejemplo la CPU de la misma plataforma. Otra comprobación que debemos hacer es comparar los tiempos de ejecución para distintos tamaño de imagen. Para ellos usaremos distintos recortes de la imagen original.

Para todas las pruebas tenemos que tener en cuenta que el tiempo de ejecución no será siempre el mismo, por lo que tendremos que repetir cada experimento varias veces y calcular la media y la desviación típica de los resultados con las fórmulas:

1

1

Donde N es el número de experimentos realizados y xi el valor obtenido en cada experimento.

5.1.1 Comparativa de tiempos de ejecución CPU/GPU

En primer lugar, haremos pruebas de velocidad al ejecutar el programa con la CPU y con la GPU. Partimos de las hipótesis de que el tiempo de ejecución puede resultar distinto en un mismo dispositivo en unas mismas condiciones de un intento a otro y que además el tiempo será menor en la GPU respecto de la CPU. Para ello, medimos el tiempo de ejecución en cada uno de los dispositivos en varias ocasiones. Obtendremos la media aritmética y la desviación típica de los resultados:

Page 55: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

37 Desemborronado de Imágenes con OpenCL

Tiempo de ejecución 

En la CPU tCPU(s) 

En la GPU tGPU(s) 

1  146,866  1,966 

2  127,656  1,966 

3  127,359  1,950 

4  142,756  1,965 

5  145,798  1,950 

6  128,591  1,966 

7  119,917  1,950 

8  119,512  1,950 

9  119,777  1,965 

10  119,425  1,950 

  129,7657  1,9578 

σ  11,24804  0,00823 

σ /   8,668%  0,420% 

Tabla 5.1. Comparativa tiempos de ejecución en CPU y GPU

Con respecto a las hipótesis tomadas, observamos que existe un increíble incremento de velocidad al ejecutar el programa con la GPU. Si hacemos una relación entre la media de tiempos obtenida en cada caso:

129,76571.9578

66,281

Resulta que el tiempo medio de ejecución en la CPU es del orden de 60 veces mayor que en la GPU, o lo que es lo mismo, la ejecución en la GPU es 60 veces más rápida que en la CPU.

Teniendo en cuenta la desviación típica de las muestras tomadas, vemos que es mucho mayor en la CPU. Esto indica que su velocidad de trabajo es menos estable con respecto a la GPU, que suele tener una velocidad más constante. Esto puede ser debido a que durante la ejecución del programa la CPU tiene que atender a otros procesos aunque el usuario no esté usando el ordenador. Esto no ocurre en la GPU, que está dedicada totalmente al proceso.

5.1.2 Comparativa de tiempos de ejecución según número de elementos

En el siguiente experimento veamos cómo varía la velocidad según el tamaño de la imagen. Partimos de la hipótesis de que a mayor número de elementos, mayor tiempo de ejecución. Queremos observar si la forma en la que se produce el incremento es lineal o no. Para ello hemos usado recortes de la imagen original de 200x200, 200x100, 200x50 y 50x50. Calculamos la media de los resultados y su desviación típica.

Page 56: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resultados

38 

Tiempo (s)  300x200  250x200  200x200  150x200  200x100  200x50  50x50 

1  2,777  1,982  1,311  0,765  0,390  0,141  0,015 

2  2,761  1,965  1,310  0,733  0,359  0,124  0,016 

3  2,762  1,981  1,295  0,749  0,359  0,125  0,015 

4  2,761  1,966  1,310  0,749  0,374  0,125  0,016 

5  2,761  1,966  1,311  0,733  0,359  0,125  0,016 

6  2,761  1,965  1,294  0,749  0,359  0,125  0,015 

7  2,761  1,966  1,311  0,748  0,374  0,124  0,016 

8  2,761  1,965  1,310  0,749  0,359  0,125  0,000 

9  2,761  1,982  1,295  0,733  0,359  0,109  0,015 

10  2,777  1,965  1,310  0,749  0,374  0,125  0,016 

  2,7643  1,9703  1,3057  0,7457  0,3666  0,1248  0,0156 

σ  0,00670  0,00786  0,00763  0,01009  0,01084  0,00755  0,00494 

σ /   0,242%  0,399%  0,584%  1,353%  2,958%  6,053%  31,784% 

Tabla 5.2. Comparativa tiempos según número de elementos

Teniendo en cuenta la media aritmética de los resultados, efectivamente existe un incremento en el tiempo de ejecución para mayor número de elementos. En la Figura 5.2 vemos la curva que forma la relación tiempo-número de elementos.

Además, observamos que la desviación típica respecto de la media se va acercand a cero cuanto mayor es la imagen. Esto parece lógico estadísticamente si la probabilidad de retraso en un elemento es constante. A más elementos en nuestra imagen, el número de elementos que fallan será una proporción constante del total de elementos.

Como se puede observar hay un crecimiento potencial en el tiempo de ejecución a mayor número de elementos. Esto puede ser debido a que a más elementos, el tiempo se multiplica por aumentar el número de iteraciones del kernel, pero además habrá que recorrer matrices más grandes en cada ejecución.

Page 57: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

39 Desemborronado de Imágenes con OpenCL

Figura 5.2. Gráfica Tiempo de ejecución-Elementos

Si en lugar del tiempo comparamos velocidades, podemos crear una tabla con los cocientes de número de elementos/tiempo de ejecución.

Miles de elementos 

(ud) 

Velocidad  media (ud/s) 

60  21,71 

50  25,38 

40  30,63 

30  40,23 

20  54,56 

10  80,13 

2,5  160,71 

Tabla 5.3. Comparativa velocidad de ejecución por número de elementos

0,0000

0,5000

1,0000

1,5000

2,0000

2,5000

3,0000

0 10 20 30 40 50 60 70

Tiem

po de ejecucución (s)

Miles de elementos (ud.)

Tiempo de ejecución ‐ Elementos

Page 58: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resultados

40 

Figura 5.3. Gráfica Velocidad de ejecución – Elementos

Observando las velocidades vemos efectivamente que cada work-item que se encarga de trabajar con un solo pixel será más lento cuanto mayor sea la imagen con la que se trabaja.

5.1.3 Conclusiones de resultados numéricos

Como hemos visto existe un incremento de velocidad considerable al usar la GPU como dispositivo de computación en el procesamiento de imágenes. El desarrollo y evolución de estos dispositivos, además de aliviar la carga de trabajo de la CPU, hace que podamos disfrutar de mayor calidad de gráficos en nuestros ordenadores. Por ejemplo, programas de edición de vídeo, diseño asistido por ordenador (CAD) o incluso videojuegos pueden aumentar su calidad ya que el soporte hardware le acompañará con un aumento de la velocidad de cálculo.

Es un problema a tener en cuenta que el incremento del tiempo de ejecución sea exponencial para imágenes más grandes. Se podría plantear el hecho de dividir la imagen en varias partes y desemborronarlas por separado. Esto aumentaría la velocidad pero empeoraría la calidad del resultado porque, como estamos viendo, es necesaria la información de los pixeles vecinos para calcular una deconvolución. El objetivo sería encontrar una relación óptima de tamaño de la imagen – tiempo de ejecución para cumplir con unos requisitos dados.

5.2 Resultados gráficos

Para este apartado he seleccionado algunas imágenes emborronadas previamente y las he desemborronado. Este es el resultado:

0,00

20,00

40,00

60,00

80,00

100,00

120,00

140,00

160,00

180,00

0 10 20 30 40 50 60 70

Velocidad

 de ejecución (ud./s)

Miles de elementos (ud.)

Velocidad de ejecución ‐ Elementos

Page 59: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

41 Desemborronado de Imágenes con OpenCL

Figura 5.4. Ejemplo de desemborronado: La Gioconda

Figura 5.5. Ejemplo de desemborronado: La Giralda

Page 60: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Resultados

42 

Figura 5.6. Ejemplo de desemborronado: La Tierra a color

Como vemos, los resultados son buenos ya que efectivamente, conseguimos desemborronar imágenes el cual es el objetivo de nuestro trabajo. Sin embargo, no hemos usado ninguna imagen con un emborronamiento distinto al que puede ser modelado por una convolución de Gauss rotacionalmente simétrica. Por ejemplo, la captura de una imagen en movimiento. Para ello, sólo habría que encontrar el filtro PSF adecuado e introducirlo como entrada en el algoritmo.

5.3 Trabajo futuro

A vista de los resultados obtenidos con nuestra aplicación desarrollada tenemos que echar un vistazo más allá de este trabajo. Por ejemplo, podríamos probar el algoritmo que usa la transformada rápida de Fourier y comparar resultados. Podríamos encontrarnos ante un incremento de velocidad respecto al que hemos usado o determinar para qué tipo de imagen es más apropiado uno u otro.

Además, sería posible desarrollar una aplicación con un algoritmo de deconvolución ciega. Esto es la deconvolución sin introducir un filtro PSF. La aplicación sería capaz de encontrar la solución óptima después de varias iteraciones. Con este algoritmo los resultados gráficos mejorarían considerablemente, aunque perderíamos en velocidad de ejecución.

Por último, este trabajo ha sido desarrollado y optimizado pensando en el dispositivo que hemos usado. En el futuro, podríamos ampliar el campo y desarrollar la aplicación para que sea lo más óptima posible sea cual sea el dispositivo.

Page 61: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

43

6 BIBLIOGRAFÍA

 

[1] Hansen, P. C., Nagy, J. G., and O'Leary, D. P. Debarring Images: Matrices, Spectra, and Filtering(2006)

[2] Web del Khronos Group con manual de referencia para OpenCL: www.khronos.org/opencl/

[3] Advanced Micro Devices Inc.: Introduction to OpenCL Programming. Training Guide (2010).

[4] Advanced Micro Devices Inc.: HD 6900 Series Instruction Set Architecture (2011).

[5] Advanced Micro Devices Inc.: OpenCL Programming Guide (2013).

[6] Ravishekhar Banger, Koushik Bhattacharyya: OpenCL Programming by Example (2013).

[7] Benedict R. Gaster et al.: Heterogeneous Computing with OpenCL (2013).

[8] Oliva Torres, Alejandro J.: Computación paralela heterogénea en OpenCL. Aplicación a un problema de eliminación de armónicos. (2015)

Page 62: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Bibliografía

44 

Page 63: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

43

ANEXO A. CÓDIGO APLICACIÓN CONVOLUCIÓN

1. Aplicación del host

/******************************************************************** Aplicacion del host para emborronar imágenes Jesus Aires Barea ‐‐ [email protected]  ********************************************************************/ // Librerias estandares: #include <stdio.h> #include <stdlib.h> #include <CL/cl.h>  #define CAMINO_AL_KERNEL "./convolution.cl"  // Configuracion de los dispositivos: typedef struct {   cl_platform_id platform;  // Identificador de la plataforma   cl_device_id device;   // Identificador del dispositivo   cl_context context;      // Identificador del contexto   cl_command_queue comQueue; // Identificador de la cola de com.   cl_program program;      // Identificador del programa   cl_uint maxCompUnits;   cl_ulong localMemSize; } cl_config;  typedef unsigned char uchar;  char* readSource(char* kernelPath) {     cl_int status;    FILE *fp;    char *source;    long int size;     printf("Program file is: %s\n", kernelPath);     fp = fopen(kernelPath, "rb");    if(!fp) {       printf("Could not open kernel file\n");     system("PAUSE");       exit(‐1);    }    status = fseek(fp, 0, SEEK_END);    if(status != 0) {       printf("Error seeking to end of file\n");     system("PAUSE");       exit(‐1);    }    size = ftell(fp);    if(size < 0) {       printf("Error getting file position\n");     system("PAUSE");       exit(‐1);    }     rewind(fp); 

Page 64: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

ANEXO A. Código aplicación convolución

44 

    source = (char *)malloc(size + 1);     int i;    for (i = 0; i < size+1; i++) {       source[i]='\0';    }     if(source == NULL) {       printf("Error allocating space for the kernel source\n");     system("PAUSE");       exit(‐1);    }     fread(source, 1, size, fp);    source[size] = '\0';     return source; }  void chk(cl_int status, const char* cmd) {     if(status != CL_SUCCESS) {       printf("%s failed (%d)\n", cmd, status);     system("PAUSE");       exit(‐1);    } }  float* readImage(const char *filename, int* widthOut, int* heightOut) {     uchar* imageData;     int height, width;    uchar tmp;    int offset;    int i, j;     printf("Leer imagen de entrada desde %s\n", filename);    FILE *fp = fopen(filename, "rb");    if(fp == NULL) {        perror(filename);      system("PAUSE");        exit(‐1);    }     fseek(fp, 10, SEEK_SET);    fread(&offset, 4, 1, fp);     fseek(fp, 18, SEEK_SET);    fread(&width, 4, 1, fp);    fread(&height, 4, 1, fp);     printf("Anchura = %d\n", width);    printf("Altura = %d\n", height);     *widthOut = width;    *heightOut = height;         imageData = (uchar*)malloc(width*height);    if(imageData == NULL) {        perror("malloc");      system("PAUSE"); 

Page 65: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

45 Desemborronado de Imágenes con OpenCL

       exit(‐1);    }     fseek(fp, offset, SEEK_SET);    fflush(NULL);     int mod = width % 4;    if(mod != 0) {        mod = 4 ‐ mod;    }     // NOTE bitmaps are stored in upside‐down raster order.  So we begin    // reading from the bottom left pixel, then going from left‐to‐right,     // read from the bottom to the top of the image.  For image analysis,     // we want the image to be right‐side up, so we'll modify it here.     // First we read the image in upside‐down     // Read in the actual image    for(i = 0; i < height; i++) {        // add actual data to the image       for(j = 0; j < width; j++) {          fread(&tmp, sizeof(char), 1, fp);          imageData[i*width + j] = tmp;       }       // For the bmp format, each row has to be a multiple of 4,        // so I need to read in the junk data and throw it away       for(j = 0; j < mod; j++) {          fread(&tmp, sizeof(char), 1, fp);       }    }     // Then we flip it over    int flipRow;    for(i = 0; i < height/2; i++) {       flipRow = height ‐ (i+1);       for(j = 0; j < width; j++) {          tmp = imageData[i*width+j];          imageData[i*width+j] = imageData[flipRow*width+j];          imageData[flipRow*width+j] = tmp;       }    }     fclose(fp);     // Input image on the host    float* floatImage = NULL;    floatImage = (float*)malloc(sizeof(float)*width*height);    if(floatImage == NULL) {       perror("malloc");     system("PAUSE");       exit(‐1);    }     // Convert the BMP image to float (not required)    for(i = 0; i < height; i++) {       for(j = 0; j < width; j++) {          floatImage[i*width+j] = (float)imageData[i*width+j];       }    }     free(imageData);    return floatImage; 

Page 66: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

ANEXO A. Código aplicación convolución

46 

}  void storeImage(float *imageOut, const char *filename, int rows, int cols, const char* refFilename) {     FILE *ifp, *ofp;    unsigned char tmp;    int offset;    unsigned char *buffer;    int i, j;     int bytes;     int height, width;     ifp = fopen(refFilename, "rb");    if(ifp == NULL) {       perror(filename);     system("PAUSE");       exit(‐1);    }     fseek(ifp, 10, SEEK_SET);    fread(&offset, 4, 1, ifp);     fseek(ifp, 18, SEEK_SET);    fread(&width, 4, 1, ifp);    fread(&height, 4, 1, ifp);     fseek(ifp, 0, SEEK_SET);     buffer = (unsigned char *)malloc(offset);    if(buffer == NULL) {       perror("malloc");     system("PAUSE");       exit(‐1);    }     fread(buffer, 1, offset, ifp);     printf("Writing output image to %s\n", filename);    ofp = fopen(filename, "wb");    if(ofp == NULL) {       perror("opening output file");     system("PAUSE");       exit(‐1);    }    bytes = fwrite(buffer, 1, offset, ofp);    if(bytes != offset) {       printf("error writing header!\n");     system("PAUSE");       exit(‐1);    }     // NOTE bmp formats store data in reverse raster order (see comment in    // readImage function), so we need to flip it upside down here.      int mod = width % 4;    if(mod != 0) {       mod = 4 ‐ mod;    }    //   printf("mod = %d\n", mod);    for(i = height‐1; i >= 0; i‐‐) {       for(j = 0; j < width; j++) { 

Page 67: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

47 Desemborronado de Imágenes con OpenCL

         tmp = (unsigned char)imageOut[i*cols+j];          fwrite(&tmp, sizeof(char), 1, ofp);       }       // In bmp format, rows must be a multiple of 4‐bytes.         // So if we're not at a multiple of 4, add junk padding.       for(j = 0; j < mod; j++) {          fwrite(&tmp, sizeof(char), 1, ofp);       }    }      fclose(ofp);    fclose(ifp);     free(buffer); }  cl_int main (int argc, char* argv[]) {  // Filas y columnas de la imagen de entrada    int imageHeight;    int imageWidth;     const char* inputFile = "input.bmp";    const char* outputFile = "output.bmp";  // Lectura de imagen en BMP    float* inputImage = readImage(inputFile, &imageWidth, &imageHeight);  // Tamaño de las imagenes de entrada y salida en el host    int dataSize = imageHeight*imageWidth*sizeof(float);  // Imagen de salida en el host    float* outputImage = NULL;    outputImage = (float*)malloc(dataSize);  // Filtro a aplicar   float filter[25] =   {  1.f/100,    2.f/100,    4.f/100,    2.f/100,   1.f/100,           2.f/100,    4.f/100,    8.f/100,    4.f/100,   2.f/100,           4.f/100,    8.f/100,    16.f/100,    8.f/100,   4.f/100,           2.f/100,    4.f/100,    8.f/100,    4.f/100,   2.f/100,     1.f/100,    2.f/100,    4.f/100,    2.f/100,   1.f/100  };    // El filtro de convolucion es 3x3   int filterWidth = 5;     int filterSize  = filterWidth*filterWidth;    // Condigurar entorno OpenCL   cl_int status;   cl_config configGPU;  // Deteccion de la plataforma.   cl_uint numPlatforms;   status = clGetPlatformIDs (0, NULL, &numPlatforms);   chk(status, "clGetPlatformIDs_1");   cl_platform_id *platforms = new cl_platform_id [numPlatforms];   status = clGetPlatformIDs (numPlatforms, platforms, NULL);   chk(status, "clGetPlatformIDs_2"); 

Page 68: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

ANEXO A. Código aplicación convolución

48 

  configGPU.platform = platforms [0];    char* buffer = new char [10240];   clGetPlatformInfo (configGPU.platform, CL_PLATFORM_NAME, 10240, buffer, NULL);   printf("Plataforma: %s\n", buffer);   delete [] platforms;  // Deteccion de los dispositivos.   status = clGetDeviceIDs (configGPU.platform, CL_DEVICE_TYPE_GPU, 1, &(configGPU.device), NULL);   chk(status, "clGetDeviceIDs");    clGetDeviceInfo(configGPU.device, CL_DEVICE_NAME, 10240, buffer, NULL);   printf("Dispositivo: %s\n", buffer);   delete [] buffer;  // Creacion del contexto.     cl_context_properties props[3] = {CL_CONTEXT_PLATFORM, (cl_context_properties)(configGPU.platform), 0};   configGPU.context = clCreateContext (NULL, 1, &(configGPU.device), NULL, NULL, &status);   chk(status, "clCreateContext");  // Creacion de la cola de comandos.   configGPU.comQueue = clCreateCommandQueue (configGPU.context, configGPU.device, CL_QUEUE_PROFILING_ENABLE, &status);   chk(status, "clCreateCommandQueue");  // El formato de la imagen describe cómo los datos seran guardados en memoria   cl_image_format format;   format.image_channel_order     = CL_R;     // single channel   format.image_channel_data_type = CL_FLOAT; // float data type  // Descriptor de la imagen   cl_image_desc image_desc;   image_desc.image_type = CL_MEM_OBJECT_IMAGE2D;   image_desc.image_width = imageWidth;   image_desc.image_height = imageHeight;   image_desc.image_depth = 1;   image_desc.image_array_size = 1;   image_desc.image_row_pitch = 0;   image_desc.image_slice_pitch = 0;   image_desc.num_mip_levels = 0;   image_desc.num_samples = 0;   image_desc.buffer= NULL;  // Alocacion de la imagen de entrada en el dispositivo   cl_mem d_inputImage = clCreateImage(configGPU.context, 0, &format, &image_desc, NULL, &status);    chk(status, "clCreateImage");  // Alocacion de la imagen de salida en el dispositivo    cl_mem d_outputImage = clCreateImage(configGPU.context, 0, &format, &image_desc, NULL, &status);    chk(status, "clCreateImage");  // Alocacion de la matriz del filtro en el dispositivo    cl_mem d_filter = clCreateBuffer(configGPU.context, 0, filterSize*sizeof(float), NULL, &status);    chk(status, "clCreateBuffer");  // Copiar la imagen de entrada al dispositivo    size_t origin[3] = {0, 0, 0};  // Origen 

Page 69: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

49 Desemborronado de Imágenes con OpenCL

   size_t region[3] = {imageWidth, imageHeight, 1}; // Elementos por dimension    status = clEnqueueWriteImage(configGPU.comQueue, d_inputImage, CL_FALSE, origin, region, 0, 0, inputImage, 0, NULL, NULL);    chk(status, "clEnqueueWriteImage");      // Copiar la matriz de filtro al dispositivo    status = clEnqueueWriteBuffer(configGPU.comQueue, d_filter, CL_FALSE, 0, filterSize*sizeof(float), filter, 0, NULL, NULL);    chk(status, "clEnqueueWriteBuffer");  // Crear el sampler de la imagen    cl_sampler sampler = clCreateSampler(configGPU.context, CL_FALSE, CL_ADDRESS_CLAMP_TO_EDGE, CL_FILTER_NEAREST, &status);    chk(status, "clCreateSampler");     const char* source = readSource(CAMINO_AL_KERNEL);  // Crear objeto del programa y compilarlo desde una fuente    cl_program program;    program = clCreateProgramWithSource(configGPU.context, 1, &source, NULL, NULL);    chk(status, "clCreateProgramWithSource");    status = clBuildProgram(program, 1, &(configGPU.device), NULL, NULL, NULL);    //chk(status, "clBuildProgram");        if (status != CL_SUCCESS)   {     cl_build_status bufBuild;     clGetProgramBuildInfo (program, configGPU.device, CL_PROGRAM_BUILD_STATUS, sizeof (cl_build_status), &bufBuild, NULL);     if (bufBuild == CL_BUILD_ERROR)      {       printf("ERROR: No se pudo compilar/linkar objeto programa para el disp.\n ");       size_t logSize;       clGetProgramBuildInfo (program, configGPU.device, CL_PROGRAM_BUILD_LOG, 0, NULL, &logSize);       char *programLog = new char [logSize+1];       clGetProgramBuildInfo (program, configGPU.device, CL_PROGRAM_BUILD_LOG, logSize+1, programLog, NULL);       printf("Informe de compilacion: %s\n",programLog);       delete [] programLog;     }     getchar();     return 0;   }   else printf("program1: Compilado/linkado completado con exito.\n");    // Crear objeto del Kernel    cl_kernel kernel;    kernel = clCreateKernel(program, "convolution", &status);    chk(status, "clCreateKernel");  // Configuracion de los parametros hacia el Kernel    status  = clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_inputImage);    status |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_outputImage);    status |= clSetKernelArg(kernel, 2, sizeof(int), &imageHeight);    status |= clSetKernelArg(kernel, 3, sizeof(int), &imageWidth);    status |= clSetKernelArg(kernel, 4, sizeof(cl_mem), &d_filter);    status |= clSetKernelArg(kernel, 5, sizeof(int), &filterWidth);    status |= clSetKernelArg(kernel, 6, sizeof(cl_sampler), &sampler);    chk(status, "clSetKernelArg");  // Configurar las dimensiones del work item 

Page 70: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

ANEXO A. Código aplicación convolución

50 

   size_t globalSize[2] = {imageWidth, imageHeight};    status = clEnqueueNDRangeKernel(configGPU.comQueue, kernel, 2, NULL, globalSize, NULL, 0, NULL, NULL);    chk(status, "clEnqueueNDRange");  // Bajar los resultados en la imagen de salida    status = clEnqueueReadImage(configGPU.comQueue, d_outputImage, CL_TRUE, origin, region, 0, 0, outputImage, 0, NULL, NULL);     chk(status, "clEnqueueReadImage");  // Escribir la imagen de salida en un archivo    storeImage(outputImage, outputFile, imageHeight, imageWidth, inputFile);        getchar();    return 0; 

 

2. Kernel convolution.cl

__kernel void convolution(    __read_only  image2d_t  sourceImage,    __write_only image2d_t  outputImage,     int rows,    int cols,    __constant float* filter,     int filterWidth,    sampler_t sampler)  {    // Store each work‐item’s unique row and column    int column = get_global_id(0);    int row    = get_global_id(1);     // Copy the data to the output image if the    // work‐item is in bounds    if(row < rows && column < cols) {       coords.x = column;       coords.y = row;     // Half the width of the filter is needed for indexing     // memory later    int halfWidth = (int)(filterWidth/2);     // All accesses to images return data as four‐element vector     // (i.e., float4), although only the 'x' component will contain     // meaningful data in this code    float4 sum = {0.0f, 0.0f, 0.0f, 0.0f};    float4 pixel;         // Iterator for the filter    int filterIdx = 0;         // Each work‐item iterates around its local area based on the     // size of the filter    int2 coords;  // Coordinates for accessing the image    // Iterate the filter rows    for(int i = ‐halfWidth; i <= halfWidth; i++) {       coords.y = row + i;        // Iterate over the filter columns 

Page 71: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

51 Desemborronado de Imágenes con OpenCL

      for(int j = ‐halfWidth; j <= halfWidth; j++) {          coords.x = column + j;           float4 pixel = {0.0f, 0.0f, 0.0f, 0.0f};          // Read a pixel from the image.  A single channel image           // stores the pixel in the 'x' coordinate of the returned          // vector.          pixel = read_imagef(sourceImage, sampler, coords);      sum.x += pixel.x * filter[filterIdx++];       }    }         write_imagef(outputImage, coords, sum);    }  

Page 72: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

ANEXO A. Código aplicación convolución

52 

Page 73: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

53

ANEXO B: CÓDIGO APLICACIÓN DE DECONVOLUCIÓN

1. Aplicación del host

/******************************************************************** Aplicacion del host para el problema de desemborronado de imagenes. Jesus Aires Barea ‐‐ [email protected] ********************************************************************/ // Librerias estandares: #include <stdio.h> #include <stdlib.h> #include <CL/cl.h> #include <Windows.h> #include <filesystem>  #define CAMINO_AL_KERNEL1 "./deblur1.cl" #define CAMINO_AL_KERNEL2 "./deblur2.cl"  // Configuracion de los dispositivos: typedef struct {   cl_platform_id platform;  // Identificador de la plataforma   cl_device_id device;   // Identificador del dispositivo   cl_context context;      // Identificador del contexto   cl_command_queue comQueue; // Identificador de la cola de com.   cl_program program;      // Identificador del programa   cl_uint maxCompUnits;   cl_ulong localMemSize; } cl_config;  typedef unsigned char uchar;  char* readSource(char* kernelPath) {     cl_int status;    FILE *fp;    char *source;    long int size;     printf("Program file is: %s\n", kernelPath);     fp = fopen(kernelPath, "rb");    if(!fp) {       printf("Could not open kernel file\n");     system("PAUSE");       exit(‐1);    }    status = fseek(fp, 0, SEEK_END);    if(status != 0) {       printf("Error seeking to end of file\n");     system("PAUSE");       exit(‐1);    }    size = ftell(fp);    if(size < 0) {       printf("Error getting file position\n");     system("PAUSE"); 

Page 74: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

54 

      exit(‐1);    }     rewind(fp);     source = (char *)malloc(size + 1);     int i;    for (i = 0; i < size+1; i++) {       source[i]='\0';    }     if(source == NULL) {       printf("Error allocating space for the kernel source\n");     system("PAUSE");       exit(‐1);    }     fread(source, 1, size, fp);    source[size] = '\0';     return source; }  void chk(cl_int status, const char* cmd) {     if(status != CL_SUCCESS) {       printf("%s failed (%d)\n", cmd, status);     system("PAUSE");       exit(‐1);    } }  void storeImage(float *imageOut, const char *filename, int rows, int cols, const char* refFilename) {     FILE *ifp, *ofp;    unsigned char tmp;    int offset;    unsigned char *buffer;    int i, j;     int bytes;     int height, width;     ifp = fopen(refFilename, "rb");    if(ifp == NULL) {       perror(filename);     system("PAUSE");       exit(‐1);    }     fseek(ifp, 10, SEEK_SET);    fread(&offset, 4, 1, ifp);     fseek(ifp, 18, SEEK_SET);    fread(&width, 4, 1, ifp);    fread(&height, 4, 1, ifp);     fseek(ifp, 0, SEEK_SET);     buffer = (unsigned char *)malloc(offset); 

Page 75: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

55 Desemborronado de Imágenes con OpenCL

   if(buffer == NULL) {       perror("malloc");     system("PAUSE");       exit(‐1);    }     fread(buffer, 1, offset, ifp);     printf("Writing output image to %s\n", filename);    ofp = fopen(filename, "wb");    if(ofp == NULL) {       perror("opening output file");     system("PAUSE");       exit(‐1);    }    bytes = fwrite(buffer, 1, offset, ofp);    if(bytes != offset) {       printf("error writing header!\n");     system("PAUSE");       exit(‐1);    }     // NOTE bmp formats store data in reverse raster order (see comment in    // readImage function), so we need to flip it upside down here.      int mod = width % 4;    if(mod != 0) {       mod = 4 ‐ mod;    }    //   printf("mod = %d\n", mod);    for(i = height‐1; i >= 0; i‐‐) {       for(j = 0; j < width; j++) {          tmp = (unsigned char)imageOut[i*cols+j];          fwrite(&tmp, sizeof(char), 1, ofp);       }       // In bmp format, rows must be a multiple of 4‐bytes.         // So if we're not at a multiple of 4, add junk padding.       for(j = 0; j < mod; j++) {          fwrite(&tmp, sizeof(char), 1, ofp);       }    }      fclose(ofp);    fclose(ifp);     free(buffer); }  float* readImage(const char *filename, int* widthOut, int* heightOut) {     uchar* imageData;     int height, width;    uchar tmp;    int offset;    int i, j;     printf("Leer imagen de entrada desde %s\n", filename);    FILE *fp = fopen(filename, "rb");    if(fp == NULL) {        perror(filename);      system("PAUSE");        exit(‐1);    } 

Page 76: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

56 

    fseek(fp, 10, SEEK_SET);    fread(&offset, 4, 1, fp);     fseek(fp, 18, SEEK_SET);    fread(&width, 4, 1, fp);    fread(&height, 4, 1, fp);     printf("Anchura = %d\n", width);    printf("Altura = %d\n", height);     *widthOut = width;    *heightOut = height;         imageData = (uchar*)malloc(width*height);    if(imageData == NULL) {        perror("malloc");      system("PAUSE");        exit(‐1);    }     fseek(fp, offset, SEEK_SET);    fflush(NULL);     int mod = width % 4;    if(mod != 0) {        mod = 4 ‐ mod;    }     // NOTE bitmaps are stored in upside‐down raster order.  So we begin    // reading from the bottom left pixel, then going from left‐to‐right,     // read from the bottom to the top of the image.  For image analysis,     // we want the image to be right‐side up, so we'll modify it here.     // First we read the image in upside‐down     // Read in the actual image    for(i = 0; i < height; i++) {        // add actual data to the image       for(j = 0; j < width; j++) {          fread(&tmp, sizeof(char), 1, fp);          imageData[i*width + j] = tmp;       }       // For the bmp format, each row has to be a multiple of 4,        // so I need to read in the junk data and throw it away       for(j = 0; j < mod; j++) {          fread(&tmp, sizeof(char), 1, fp);       }    }     // Then we flip it over    int flipRow;    for(i = 0; i < height/2; i++) {       flipRow = height ‐ (i+1);       for(j = 0; j < width; j++) {          tmp = imageData[i*width+j];          imageData[i*width+j] = imageData[flipRow*width+j];          imageData[flipRow*width+j] = tmp;       }    }     fclose(fp); 

Page 77: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

57 Desemborronado de Imágenes con OpenCL

    // Input image on the host    float* floatImage = NULL;    floatImage = (float*)malloc(sizeof(float)*width*height);    if(floatImage == NULL) {       perror("malloc");     system("PAUSE");       exit(‐1);    }     // Convert the BMP image to float (not required)    for(i = 0; i < height; i++) {       for(j = 0; j < width; j++) {          floatImage[i*width+j] = (float)imageData[i*width+j];       }    }     free(imageData);    return floatImage; }  cl_int main (int argc, char* argv[]) {  // Filas y columnas de la imagen de entrada    int imageHeight;    int imageWidth;     const char* inputFile = "borrosa.bmp";    const char* outputFile = "borrosa_o.bmp";  // Lectura de imagen en BMP    float* inputImage = readImage(inputFile, &imageWidth, &imageHeight);  // Tamaño de las imagenes de entrada y salida en el host    int dataSize = imageHeight*imageWidth*sizeof(float);   // Imagen intermedia    float* auxImage = NULL;    auxImage = (float*)malloc(dataSize);        // Imagen de salida en el host    float* outputImage = NULL;    outputImage = (float*)malloc(dataSize);  // Filtro a aplicar   float filter[25] =   {  36.f/100,    18.f/100,    6.f/100,    0.f/100,   0.f/100,           18.f/100,    9.f/100,    3.f/100,    0.f/100,   0.f/100,           6.f/100,    3.f/100,    1.f/100,    0.f/100,   0.f/100,           0.f/100,    0.f/100,    0.f/100,    0.f/100,   0.f/100,     0.f/100,    0.f/100,    0.f/100,    0.f/100,   0.f/100  };    // El filtro de convolucion es 3x3   int filterWidth = 5;     int filterSize  = filterWidth*filterWidth;  

Page 78: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

58 

      // Condigurar entorno OpenCL    cl_int status;   cl_config configGPU;      //  Deteccion de la plataforma.    cl_uint numPlatforms;   status = clGetPlatformIDs (0, NULL, &numPlatforms);   chk(status, "clGetPlatformIDs_1");   cl_platform_id *platforms = new cl_platform_id [numPlatforms];   printf("Numero de plataformas: %d\n", numPlatforms);   status = clGetPlatformIDs (numPlatforms, platforms, NULL);   chk(status, "clGetPlatformIDs_2");   configGPU.platform = platforms[0];    char* buffer = new char [10240];   clGetPlatformInfo (configGPU.platform, CL_PLATFORM_NAME, 10240, buffer, NULL);   printf("Plataforma: %s\n", buffer);   delete [] platforms;       //  Deteccion de los dispositivos.    status = clGetDeviceIDs (configGPU.platform, CL_DEVICE_TYPE_GPU, 1, &(configGPU.device), NULL);   chk(status, "clGetDeviceIDs");    clGetDeviceInfo(configGPU.device, CL_DEVICE_NAME, 10240, buffer, NULL);   printf("Dispositivo: %s\n", buffer);   delete [] buffer;    clGetDeviceInfo (configGPU.device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof (cl_uint), &(configGPU.maxCompUnits), NULL);         //  Creacion del contexto.      cl_context_properties props[3] = {CL_CONTEXT_PLATFORM, (cl_context_properties)(configGPU.platform), 0};   configGPU.context = clCreateContext (props, 1, &(configGPU.device), NULL, NULL, &status);   chk(status, "clCreateContext");     

Page 79: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

59 Desemborronado de Imágenes con OpenCL

//  Creacion de la cola de comandos.    configGPU.comQueue = clCreateCommandQueue (configGPU.context, configGPU.device, CL_QUEUE_PROFILING_ENABLE, &status);   chk(status, "clCreateCommandQueue");      // El formato de la imagen describe cómo los datos seran guardados en memoria    cl_image_format format;   format.image_channel_order     = CL_R;     // single channel   format.image_channel_data_type = CL_FLOAT; // float data type     // Descriptor de la imagen   cl_image_desc image_desc;   image_desc.image_type = CL_MEM_OBJECT_IMAGE2D;   image_desc.image_width = imageWidth;   image_desc.image_height = imageHeight;   image_desc.image_depth = 1;   image_desc.image_array_size = 1;   image_desc.image_row_pitch = 0;   image_desc.image_slice_pitch = 0;   image_desc.num_mip_levels = 0;   image_desc.num_samples = 0;   image_desc.buffer= NULL;     // Alocacion de la imagen de entrada en el dispositivo    cl_mem d_inputImage = clCreateImage(configGPU.context, 0, &format, &image_desc, NULL, &status);    chk(status, "clCreateImage");  // Alocacion de la imagen de salida en el dispositivo     cl_mem d_outputImage = clCreateImage(configGPU.context, 0, &format, &image_desc, NULL, &status);    chk(status, "clCreateImage");  // Alocacion de la imagen auxiliar en el dispositivo     cl_mem d_auxImage = clCreateImage(configGPU.context, 0, &format, &image_desc, NULL, &status);    chk(status, "clCreateImage");  // Alocacion de la matriz del filtro en el dispositivo     cl_mem d_filter = clCreateBuffer(configGPU.context, 0, filterSize*sizeof(float), NULL, &status);    chk(status, "clCreateBuffer");      

Page 80: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

60 

// Copiar la imagen de entrada al dispositivo     size_t origin[3] = {0, 0, 0};  // Origen    size_t region[3] = {imageWidth, imageHeight, 1}; // Elementos por dimension    status = clEnqueueWriteImage(configGPU.comQueue, d_inputImage, CL_FALSE, origin, region, 0, 0, inputImage, 0, NULL, NULL);    chk(status, "clEnqueueWriteImage");  // Copiar la imagen auxiliar al dispositivo    status = clEnqueueWriteImage(configGPU.comQueue, d_auxImage, CL_FALSE, origin, region, 0, 0, auxImage, 0, NULL, NULL);    chk(status, "clEnqueueWriteImage");         // Copiar la matriz de filtro al dispositivo     status = clEnqueueWriteBuffer(configGPU.comQueue, d_filter, CL_FALSE, 0, filterSize*sizeof(float), filter, 0, NULL, NULL);    chk(status, "clEnqueueWriteBuffer");      // Crear el sampler de la imagen     cl_sampler sampler = clCreateSampler(configGPU.context, CL_FALSE, CL_ADDRESS_CLAMP_TO_EDGE, CL_FILTER_NEAREST, &status);    chk(status, "clCreateSampler");                const char* source1 = readSource(CAMINO_AL_KERNEL1);    const char* source2 = readSource(CAMINO_AL_KERNEL2);     // Crear objeto del programa y compilarlo desde una fuente     cl_program program1;    program1 = clCreateProgramWithSource(configGPU.context, 1, &source1, NULL, NULL);    chk(status, "clCreateProgramWithSource");     cl_program program2;    program2 = clCreateProgramWithSource(configGPU.context, 1, &source2, NULL, NULL);    chk(status, "clCreateProgramWithSource");     status = clBuildProgram(program1, 1, &(configGPU.device), NULL, NULL, NULL);    //chk(status, "clBuildProgram");     if (status != CL_SUCCESS)   {     cl_build_status bufBuild;     clGetProgramBuildInfo (program1, configGPU.device, CL_PROGRAM_BUILD_STATUS, sizeof (cl_build_status), &bufBuild, NULL);     if (bufBuild == CL_BUILD_ERROR)      {       printf("ERROR: No se pudo compilar/linkar objeto programa para el disp.\n ");       size_t logSize;       clGetProgramBuildInfo (program1, configGPU.device, CL_PROGRAM_BUILD_LOG, 0, NULL, &logSize); 

Page 81: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

61 Desemborronado de Imágenes con OpenCL

      char *programLog = new char [logSize+1];       clGetProgramBuildInfo (program1, configGPU.device, CL_PROGRAM_BUILD_LOG, logSize+1, programLog, NULL);       printf("Informe de compilacion: %s\n",programLog);       delete [] programLog;     }     getchar();     return 0;   }   else printf("program1: Compilado/linkado completado con exito.\n");     status = clBuildProgram(program2, 1, &(configGPU.device), NULL, NULL, NULL);    //chk(status, "clBuildProgram");           if (status != CL_SUCCESS)   {     cl_build_status bufBuild;     clGetProgramBuildInfo (program2, configGPU.device, CL_PROGRAM_BUILD_STATUS, sizeof (cl_build_status), &bufBuild, NULL);     if (bufBuild == CL_BUILD_ERROR)      {       printf("ERROR: No se pudo compilar/linkar objeto programa para el disp.\n ");       size_t logSize;       clGetProgramBuildInfo (program2, configGPU.device, CL_PROGRAM_BUILD_LOG, 0, NULL, &logSize);       char *programLog = new char [logSize+1];       clGetProgramBuildInfo (program2, configGPU.device, CL_PROGRAM_BUILD_LOG, logSize+1, programLog, NULL);       printf("Informe de compilacion: %s\n",programLog);       delete [] programLog;     }     getchar();     return 0;   }   else printf("program2: Compilado/linkado completado con exito.\n");     // Crear objeto del Kernel     cl_kernel kernel[2];    kernel[0] = clCreateKernel(program1, "deblur1", &status);    chk(status, "clCreateKernel1");     kernel[1] = clCreateKernel(program2, "deblur2", &status);    chk(status, "clCreateKernel2");  // Configuracion de los parametros hacia el Kernel     status  = clSetKernelArg(kernel[0], 0, sizeof(cl_mem), &d_inputImage);    status |= clSetKernelArg(kernel[0], 1, sizeof(cl_mem), &d_auxImage);    status |= clSetKernelArg(kernel[0], 2, sizeof(int), &imageHeight);    status |= clSetKernelArg(kernel[0], 3, sizeof(int), &imageWidth);    status |= clSetKernelArg(kernel[0], 4, sizeof(cl_mem), &d_filter);    status |= clSetKernelArg(kernel[0], 5, sizeof(int), &filterWidth);    status |= clSetKernelArg(kernel[0], 6, sizeof(cl_sampler), &sampler);    chk(status, "clSetKernelArg");     status  = clSetKernelArg(kernel[1], 0, sizeof(cl_mem), &d_auxImage);    status |= clSetKernelArg(kernel[1], 1, sizeof(cl_mem), &d_outputImage);    status |= clSetKernelArg(kernel[1], 2, sizeof(int), &imageHeight);    status |= clSetKernelArg(kernel[1], 3, sizeof(int), &imageWidth); 

Page 82: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

62 

   status |= clSetKernelArg(kernel[1], 4, sizeof(cl_sampler), &sampler);    chk(status, "clSetKernelArg");     size_t localSize[2] = {16,16};        int WGx = imageWidth/localSize[0];    if ( WGx*localSize[0] < imageWidth ) WGx++;     int WGy = imageHeight/localSize[1];    if ( WGy*localSize[1] < imageHeight ) WGy++;        size_t globalSize[2] = {WGx*localSize[0], WGy*localSize[1]};  // Registro del tiempo de inicio del trabajo    SYSTEMTIME rawtimei;   GetLocalTime(&rawtimei);   printf("\nTiempo de inicio: %02d/%02d/%4d ‐ %02d:%02d:%02d:%03d\n", rawtimei.wDay, rawtimei.wMonth, rawtimei.wYear, rawtimei.wHour, rawtimei.wMinute, rawtimei.wSecond, rawtimei.wMilliseconds);          // Arrancar kernel 1     status = clEnqueueNDRangeKernel(configGPU.comQueue, kernel[0], 2, NULL, globalSize, NULL , 0, NULL, NULL);    chk(status, "clEnqueueNDRange");  // Bajar los resultados en la imagen auxiliar     status = clEnqueueReadImage(configGPU.comQueue, d_auxImage, CL_TRUE, origin, region, 0, 0, auxImage, 0, NULL, NULL);     chk(status, "clEnqueueReadImage");  // Arrancar kernel 2     status = clEnqueueNDRangeKernel(configGPU.comQueue, kernel[1], 2, NULL, globalSize, NULL , 0, NULL, NULL);    chk(status, "clEnqueueNDRange");  // Bajar los resultados en la imagen de salida     status = clEnqueueReadImage(configGPU.comQueue, d_outputImage, CL_TRUE, origin, region, 0, 0, outputImage, 0, NULL, NULL);     chk(status, "clEnqueueReadImage");  // Escribir la imagen de salida en un archivo     storeImage(outputImage, outputFile, imageHeight, imageWidth, inputFile);  // Registro tiempo de acabado    SYSTEMTIME rawtimef;      GetLocalTime(&rawtimef);     printf("\nTiempo de acabado: %02d/%02d/%4d ‐ %02d:%02d:%02d:%03d\n", rawtimef.wDay, rawtimef.wMonth, rawtimef.wYear, rawtimef.wHour, rawtimef.wMinute, rawtimef.wSecond, rawtimef.wMilliseconds);     if(rawtimef.wMinute‐rawtimei.wMinute < 0) {rawtimef.wHour‐‐; rawtimef.wMinute+=60;}    if(rawtimef.wSecond‐rawtimei.wSecond < 0) {rawtimef.wMinute‐‐; rawtimef.wSecond+=60;}    if(rawtimef.wMilliseconds‐rawtimei.wMilliseconds < 0) {rawtimef.wSecond‐‐; rawtimef.wMilliseconds+=1000;}  

Page 83: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

63 Desemborronado de Imágenes con OpenCL

   printf("\nIntervalo de tiempo: %02d:%02d:%02d:%03d\n", rawtimef.wHour ‐ rawtimei.wHour, rawtimef.wMinute ‐ rawtimei.wMinute, rawtimef.wSecond ‐ rawtimei.wSecond, rawtimef.wMilliseconds ‐ rawtimei.wMilliseconds);        clReleaseKernel (kernel[0]);   clReleaseKernel (kernel[1]);   clReleaseMemObject (d_inputImage);   clReleaseMemObject (d_auxImage);   clReleaseMemObject (d_outputImage);   clReleaseMemObject (d_filter);   clReleaseProgram (program1);   clReleaseProgram (program2);   clReleaseCommandQueue (configGPU.comQueue);   clReleaseContext (configGPU.context);      getchar();    return 0;  }  

2. Kernel deblur1.cl

#define PI 3.1415926536  __kernel void deblur1(    __read_only  image2d_t  sourceImage,    __write_only image2d_t  outputImage,     int rows,    int cols,    __constant float* PSF,     int filterWidth,    sampler_t sampler)  {    // Almaceno coordenadas alrededor de las que trabajo        int row    = get_global_id(1);    int column = get_global_id(0);     // Si estoy dentro de la imagen...     if(row < rows && column < cols) {     // Coordenadas de bucles    int m, n;     // B ‐> lectura de imagen    // S ‐> lectura de filtro     float B;    float S;     // Sumatorios     float S_S = 0;    float S_e = 0;    float S_B = 0;   

Page 84: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

64 

   // alfas     float ai, aj;     // Valor de la funcion del coseno en el sumatorio     float C;     // Coordenadas para acceder a la imagen y pixel de salida     int2 coords;     float4 pixel = {0.0f, 0.0f, 0.0f, 0.0f};  // S=dct2(S)./dct2(e); B=dct2(B);        for(int n = 0; n < cols; n++)    {   for(int m = 0; m < rows; m++)    {                  C = cos(PI*(2*m+1)*row/(2*rows))*cos(PI*(2*n+1)*column/(2*cols));      if(m < filterWidth && n < filterWidth)    S_S += PSF[m*filterWidth+n]*C;                      if(!m && !n)            S_e += C;                          coords.x = n;     coords.y = m;     pixel = read_imagef(sourceImage, sampler, coords);      S_B += (pixel.x)*C;            }   }           if(row == 0)      ai = sqrt(((float)1/rows));    else        ai = sqrt(((float)2/rows));     if(column == 0)    aj = sqrt(((float)1/cols));    else        aj = sqrt(((float)2/cols));     S = (ai*aj*S_S)/(ai*aj*S_e);    B = ai*aj*S_B;    S_S = 0;   S_e = 0;   S_B = 0;    // B = B./S;    if(fabs(S) > 0.1)      B = B/S;     else            B = 0;           pixel.x = B;    coords.x =   column;   coords.y =  row;    write_imagef(outputImage, coords, pixel);    }  

}

Page 85: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

65 Desemborronado de Imágenes con OpenCL

3. Kernel deblur2.cl

#define PI 3.1415926536  __kernel void deblur2(    __read_only  image2d_t  sourceImage,    __write_only image2d_t  outputImage,     int rows,    int cols,    sampler_t sampler)  {     // Almaceno coordenadas alrededor de las que trabajo     int row    = get_global_id(1);    int column = get_global_id(0);     // Si estoy dentro de la imagen...     if(row < rows && column < cols) {     // Sumatorio     float S_B = 0;     // alfas     float ap, aq;     // Valor de la funcion del coseno en el sumatorio     float C;     // Coordenadas para acceder a la imagen y pixel de salida     int2 coords;     float4 pixel = {0.0f, 0.0f, 0.0f, 0.0f};  // X=idct2(B*S);        for(int p = 0; p < rows; p++)    {   for(int q = 0; q < cols; q++)    {                  C = cos(PI*(2*row+1)*p/(2*rows))*cos(PI*(2*column+1)*q/(2*cols));              if(p == 0)      ap = sqrt(((float)1/(float)rows));       else        ap = sqrt(((float)2/(float)rows));        if(q == 0)      aq = sqrt(((float)1/(float)cols));       else        aq = sqrt(((float)2/(float)cols));        coords.x = q;     coords.y = p;      pixel = read_imagef(sourceImage, sampler, coords);      S_B += (pixel.x)*C*ap*aq;                      }     }    //pixel.x = fabs(S_B); 

Page 86: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

Anexo B: Código aplicación de deconvolución

66 

   if(S_B < 0) S_B=0;    pixel.x = S_B;    coords.x =   column;   coords.y =  row;    write_imagef(outputImage, coords, pixel);    }  

   

Page 87: Trabajo Fin de Grado Grado en Ingeniería de las …bibing.us.es/proyectos/abreproy/90379/fichero/TFG...Equation Chapter 1 Section 1 Trabajo Fin de Grado Grado en Ingeniería de las

67 Desemborronado de Imágenes con OpenCL