Tema 2: Procesamiento avanzado de imágenes digitales.

Anuncio
TRATAMIENTO RADIOMÉTRICO
1.1.− Introducción al manejo de imágenes con Visual Basic.NET
Las imágenes en .NET se gestionan a través de GDI+ (Graphics Device Interface). Esto amplia las
características de ediciones anteriores de VB, porque añade funciones gráficas que anteriormente no estaban
disponibles. Con GDI+, Visual Basic proporciona capacidades gráficas en todos los sentidos, desde el
manejo de gráficos vectoriales hasta la gestión de la impresión, pasando por el tratamiento de imágenes que,
en definitiva, es la cuestión que nos interesa en esta parte de la asignatura. Todas estas funcionalidades se
proporcionan dentro de un NameSpace llamado System.Drawing.
1.1.1.- Creación de una aplicación
Abre un proyecto nuevo de VB, del tipo Aplicación para Windows, y guárdalo como ProcesadorImagen.
Cuando terminemos de definir los controles que insertaremos en este formulario, finalmente tendrá el
siguiente aspecto:
Figura 1.1.- Aspecto final de la aplicación ProcesadorImagen.
1
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
HERRAMIENTAS INFORMÁTICAS
PARA EL
GEOPROCESADO
Como vemos, hay dos contenedores de imagen con barras de desplazamiento. En el izquierdo mostraremos
la imagen original mientras que en el derecho la misma imagen tras haber hecho alguna operación. Además,
tenemos un menú principal en la parte superior del formulario, donde podremos seleccionar las acciones a
realizar, y en la parte inferior una barra de estado donde se mostrarán mensajes de estado, una barra de
progreso y coordenadas de las imágenes.
En primer lugar inserta un menú (MenuStrip) y una barra de estado (StatusStrip). Dentro del menú coloca los
elementos principales: Archivo, Operaciones y Transformaciones. Dentro del primero coloca las opciones
clásicas Abrir, Guardar, Cerrar, y Salir.
Figura 1.2.- Ítems del menú Archivo
Hay varias maneras de mostrar imágenes: en el fondo de un formulario, dentro de un control PictureBox, ... .
La primera es la más cómoda y proporciona las barras de desplazamiento de forma automática, pero puede
generar problemas si en el formulario se introducen elementos (menús y barras de herramientas) que pueden
ser desplazados por el usuario en tiempo de ejecución. Por otro lado, cargar la imagen directamente en un
contenedor de imagen como PictureBox no ofrece la posibilidad de utilizar las barras de desplazamiento
(scroll) de forma automática.
Una solución pasa por utilizar un control de tipo Panel¸ que proporciona las barras de desplazamiento, e
introducir dentro un control PictureBox donde se cargará la imagen. Este control PictureBox se debe acoplar
en el contenedor principal (que en este caso corresponde con el control Panel), y tener la propiedad
SizeMode como Autosize.
Bien, pues inserta dos contenedores de imagen (Panel+PictureBox) como se ha indicado en el párrafo
anterior.
1.1.2.- Visualización de imágenes
Ahora vamos a empezar a darle funcionalidad a la aplicación y empezaremos gestionando la apertura y cierre
de imágenes. Para ello introduce en primer lugar un control del tipo OpenFileDialog, y luego introduce el
siguiente código en el evento Click de la opción Abrir del menú Archivo:
2
TRATAMIENTO RADIOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
With OpenFileDialog1
.Filter = "Ficheros BMP|*.bmp" & _
"|Ficheros GIF|*.gif" & _
"|Ficheros JPG o JPEG|*.jpg;*.jpeg" & _
"|Ficheros PNG|*.png" & _
"|Ficheros TIFF|*.tif"
.FilterIndex = 3
If (.ShowDialog() = Windows.Forms.DialogResult.OK) Then
PictureBox1.Image = Image.FromFile(.FileName)
Panel1.AutoScrollMinSize = PictureBox1.Image.Size
End If
End With
Se necesita inicializar la propiedad AutoScrollMinSize del control Panel1 con el tamaño de la imagen para
poder mostrar las barras de desplazamiento.
Para cerrar la imagen deberemos incluir en la opción Cerrar del menú Archivo el siguiente código:
PictureBox1.Image = Nothing
Panel1.AutoScroll = False
Por último, en la opción Salir del menú Archivo introducimos simplemente End, para que la aplicación
finalice al seleccionar esta opción.
1.1.3.- Acceso a los niveles digitales de la imagen
Ya hemos visto cómo cargar y visualizar imágenes, ahora vamos a ver cómo podemos acceder a los colores
correspondientes a los píxeles de la imagen cargada de una forma intuitiva. En este tema se van a volcar los
colores a una matriz bidimensional con las mismas dimensiones que la imagen.
Los colores se leen en un formato específico, System.Drawing.Color, que contiene los valores de rojo, verde
y azul.
Como la matriz bidimensional la queremos poder utilizar en cualquier lugar de la aplicación, lo mejor es
definirla sin dimensiones en la sección de Declaraciones del formulario.
3
CURSO 2009-2010
HERRAMIENTAS INFORMÁTICAS
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
PARA EL
GEOPROCESADO
Public Matriz(,) As System.Drawing.Color
Utilizando Image del control PictureBox podemos acceder a diferentes propiedades de la imagen cargada,
como son el ancho (Width) y el alto (Height), que nos van a hacer falta para dimensionar la matriz. Así, una
vez cargada la imagen ya podremos redimensionar la matriz para que tenga capacidad de almacenar la
imagen.
En el menú Operaciones añade un elemento llamado Cargar Matriz y escribe dentro el siguiente código:
Dim i, j As Long
ReDim Matriz(PictureBox1.Image.Width - 1, PictureBox1.Image.Height - 1)
Dim bmp As New Bitmap(PictureBox1.Image)
For i = 0 To PictureBox1.Image.Width - 1
For j = 0 To PictureBox1.Image.Height - 1
Matriz(i, j) = bmp.GetPixel(i, j)
Next
Next
En primer lugar fíjate que establecemos el ancho y el alto, pero descontamos la unidad. Esto es debido a que
el mínimo valor de la matriz es 0, mientras que el alto y el ancho comienzan en 1.
Después definimos un objeto de tipo Bitmap, que nos va a permitir acceder a los colores de la imagen
visualizada.
Posteriormente hemos implementado un doble bucle For, que va a recorrer todos y cada uno de los píxeles
de la imagen. Los colores para cada uno de los píxeles (i,j) se van obtener con el método GetPixel del
Bitmap antes definido. Cuando concluya este doble bucle tendremos en Matriz los colores de la imagen que
hemos cargado, y tenemos la misma estructura bidimensional que la propia imagen.
1.1.4.- Pintar una imagen
Para dibujar la imagen que acabamos de cargar en la matriz, genera una nueva opción en Operaciones, que
se llame Pintar Matriz. Vamos a ver qué código hace falta para generar una imagen nueva.
4
TRATAMIENTO RADIOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
Dim i, j As Long
Dim bmp As New Bitmap(Matriz.GetUpperBound(0) + 1, Matriz.GetUpperBound(1) + 1)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
PictureBox2.Refresh()
For i = 0 To Matriz.GetUpperBound(0)
For j = 0 To Matriz.GetUpperBound(1)
bmp.SetPixel(i, j, Matriz(i, j))
Next
Next
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
Hay que crear un nuevo Bitmap, mientras que Matriz va de 0 a alto-1 (ancho-1), el tamaño de la imagen va
de 1 a alto (ancho).
5
CURSO 2009-2010
HERRAMIENTAS INFORMÁTICAS
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
PARA EL
GEOPROCESADO
1.1.5.- Guardar una imagen
Las imágenes se pueden salvar en diferentes formatos, característica proporcionada por .NET de forma tan
sencilla como especificar el formato. Inserta un control de tipo SaveFileDialog e implementa el siguiente
código en la opción Guardar del menú Archivo:
With SaveFileDialog1
.Filter = "Ficheros BMP|*.bmp" & _
"|Ficheros GIF|*.gif" & _
"|Ficheros JPG o JPEG|*.jpg;*.jpeg" & _
"|Ficheros PNG|*.png" & _
"|Ficheros TIFF|*.tif"
.FilterIndex = 3
If (.ShowDialog() = Windows.Forms.DialogResult.OK) Then
If SaveFileDialog1.FileName <> "" Then
Dim fs As System.IO.FileStream = CType(SaveFileDialog1.OpenFile(),
System.IO.FileStream)
Select Case SaveFileDialog1.FilterIndex
Case 1
PictureBox2.Image.Save(fs, _
System.Drawing.Imaging.ImageFormat.Bmp)
Case 2
PictureBox2.Image.Save(fs, _
System.Drawing.Imaging.ImageFormat.Gif)
Case 3
PictureBox2.Image.Save(fs, _
System.Drawing.Imaging.ImageFormat.Jpeg)
Case 4
PictureBox2.Image.Save(fs, _
System.Drawing.Imaging.ImageFormat.Png)
Case 5
PictureBox2.Image.Save(fs, _
System.Drawing.Imaging.ImageFormat.Tiff)
End Select
fs.Close()
End If
End If
End With
6
TRATAMIENTO RADIOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
1.2.− Diagrama de flujo
La aplicación que hemos generado sigue el siguiente diagrama de flujo.
Abrir imagen
Cargar Matriz
Pintar Matriz
Guardar imagen
1.3.− Procesamiento de imágenes
En los apartados anteriores hemos visto una serie de tareas para empezar a trabajar con imágenes, y gracias a
ellas ya estamos en disposición de empezar a realizar algún tipo de procesamiento a las imágenes,
intercalando el procesamiento en el diagrama de flujo básico de la aplicación:
Abrir imagen
Cargar Matriz
Procesamiento imagen
Pintar Matriz
Guardar imagen
7
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
HERRAMIENTAS INFORMÁTICAS
PARA EL
GEOPROCESADO
Que lógicamente tiene que ir entre la carga de la imagen y el pintado de la matriz. Si el procesamiento es
sencillo (inversión, binarización, …) se puede implementar directamente en el código correspondiente a
Pintar Matriz. Por otro lado, si el procesamiento es algo más complicado (máscaras) es mejor copiar el
código correspondiente en una nueva opción del menú Operaciones.
1.3.1.- Inversión de los colores de la imagen
Vamos a implementar un procesamiento de imagen sencillo, como es la inversión de los colores de la
imagen, y posteriormente se dejará como tarea la realización de varios tipos de procesamiento. Para ello,
implementa la variable Tipo_Procesado en la sección de Declaraciones del formulario, para que sea pública
y de tipo cadena.
Public Tipo_Procesado As String
A continuación crea la opción Inversión en el menú Operaciones. En el evento Click de dicha opción
incorpora el siguiente código:
Tipo_Procesado = "INVERSION"
Call PintarMatrizToolStripMenuItem_Click(sender, e)
Lo que estamos es indicando que queremos hacer un procesado de tipo negativo y a continuación se llama al
evento Click de la opción Pintar Matriz del menú Operaciones. El código de este evento lo introducimos
anteriormente, pero es necesario realizar una serie de cambios, dado que no podremos realizar ningún
cambio en el color porque la estructura System.Drawing.Color es de sólo lectura. Por ello, lo más adecuado
es declarar tres variables de tipo Byte, que se pueden llamar Rojo, Verde y Azul y utilizarlas para hacer los
cambios. Además, introducimos en el doble bucle For un Select Case que determinará qué tipo de
procesamiento deseamos realizar (en este caso “INVERSION”). Por último, en SetPixel definimos el color
mediante el método FromArgb.
8
TRATAMIENTO RADIOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
Dim Rojo, Verde, Azul As Byte
For i = 0 To Matriz.GetUpperBound(0)
For j = 0 To Matriz.GetUpperBound(1)
Rojo = Matriz(i, j).R
Verde = Matriz(i, j).G
Azul = Matriz(i, j).B
Select Case Tipo_Procesado
Case Is = "INVERSION"
Rojo = 255 - Rojo
Verde = 255 - Verde
Azul = 255 - Azul
End Select
bmp.SetPixel(i, j, Color.FromArgb(Rojo, Verde, Azul))
Next
Next
Así, cuando queramos añadir un nuevo procesamiento deberemos seguir estos pasos:
1. Añadir una nueva opción en el menú Operaciones.
2. En el código de esta opción, definir Tipo_Procesado e invocar al evento Click de la opción Pintar
Matriz.
3. En el doble bucle For del código de este evento, añadir en el Select Case el tipo de procesado con las
nuevas fórmulas para las variables Rojo, Verde y Azul.
Generalmente se realizan los procesamientos a imágenes en niveles de gris. Los colores rojo, verde y azul de
las imágenes en niveles de gris tienen el mismo valor para cada píxel.
1.3.2.- Máscaras
El procesamiento con máscaras implica trabajar con un entorno de un punto, representado por una matriz
generalmente cuadrada y con número impar de píxeles (3x3, 5x5, etc…). Es decir, en lugar de considerar
9
CURSO 2009-2010
HERRAMIENTAS INFORMÁTICAS
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
PARA EL
GEOPROCESADO
únicamente los niveles digitales (colores) de cada píxel, se consideran además los niveles digitales de los
píxeles adyacentes.
Esto implica que el doble bucle For con el que estamos trabajando no puede empezar en la primera fila (ni
en la primera columna) de la imagen, ni puede terminar en la última fila (última fila) de la imagen. Por
ejemplo, si la máscara es 3x3, el doble bucle debe ser:
For i = 1 To (Matriz.GetUpperBound(0)-1)
For j = 1 To (Matriz.GetUpperBound(1)-1)
...
Next
Next
En caso de ser de 5x5, irá desde 2 hasta el límite -2.
A modo de ejemplo vamos a implementar la máscara correspondiente a Sobel:
1
4
1
0
-1
2
0
-2
1
0
-1
Declaramos una matriz de tipo Byte en la sección de Declaraciones del formulario para poder acceder a ella
desde donde nos interese, y la llamamos Mascara.
Public Mascara(,) as Integer
10
TRATAMIENTO RADIOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
Generamos la opción Sobel dentro del menú Operaciones con el siguiente código:
Dim i, j As Long
Dim mi, mj As Long
Dim SumaRojo, SumaMascara As Long
Dim factor, desviacion As Double
Dim Rojo, Verde, Azul As Integer
Dim bmp As New Bitmap(Matriz.GetUpperBound(0) + 1, Matriz.GetUpperBound(1) + 1)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
PictureBox2.Refresh()
ReDim Mascara(2, 2)
Mascara(0, 0) = 1 : Mascara(0, 1) = 0 : Mascara(0, 2) = -1
Mascara(1, 0) = 2 : Mascara(1, 1) = 0 : Mascara(1, 2) = -2
Mascara(2, 0) = 1 : Mascara(2, 1) = 0 : Mascara(2, 2) = -1
For mi = -1 To 1
For mj = -1 To 1
SumaMascara = SumaMascara + Mascara(mi + 1, mj + 1)
Next
Next
If SumaMascara = 0 Then SumaMascara = 1
factor = 1/4
desviacion = 0
11
CURSO 2009-2010
HERRAMIENTAS INFORMÁTICAS
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
PARA EL
GEOPROCESADO
'Con este cuadruple bucle se recorre la máscara y la matriz
For i = 1 To Matriz.GetUpperBound(0) - 1
For j = 1 To Matriz.GetUpperBound(1) - 1
SumaRojo = 0
For mi = -1 To 1
For mj = -1 To 1
SumaRojo = SumaRojo + Matriz(i + mi, j + mj).R * Mascara(mi + 1, mj +
1)
Next
Next
Rojo = Math.Abs(SumaRojo / SumaMascara)
Rojo = Math.Abs(Rojo / factor + desviacion)
If Rojo > 255 Then Rojo = 255
bmp.SetPixel(i, j, Color.FromArgb(Rojo, Rojo, Rojo))
Next
'Esta aquí que se refresque línea a línea
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
Next
Donde hemos insertado el código de Pintar Matriz necesario para poder pintar en la imagen. Esto es más
sencillo en este caso que añadir nuevos casos en el doble bucle For de Pintar Matriz, como hemos hecho
para los procesamientos sencillos.
En el código se está considerando el uso de imágenes en niveles de gris, por ello se trabaja solamente con
Rojo.
12
TRATAMIENTO RADIOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
1.4.− Información en la posición del ratón
Para terminar con este tema, vamos a ver cómo podemos acceder a las coordenadas y al nivel digital (color),
y cómo cambiar el puntero de Windows cuando pase por encima de la imagen. Cuando generamos la
aplicación, incluimos una barra de estado (StatusStrip), y en ella se irán incluyendo los elementos necesarios
para poder visualizar esta información.
Para ello incluimos los siguientes objetos en la sección de Declaraciones del formulario:
Friend WithEvents EtiquetaCoord_StatusStrip As ToolStripStatusLabel
Friend WithEvents EtiquetaND_StatusStrip As ToolStripStatusLabel
En el método Load del formulario (cuidado con las llaves y los paréntesis):
Me.EtiquetaCoord_StatusStrip = New ToolStripStatusLabel
Me.StatusStrip1.Items.AddRange(New ToolStripItem() {Me.EtiquetaCoord_StatusStrip})
Me.EtiquetaND_StatusStrip = New ToolStripStatusLabel
Me.StatusStrip1.Items.AddRange(New ToolStripItem() {Me.EtiquetaND_StatusStrip})
Y en el evento MouseMove del PictureBox1:
EtiquetaCoord_StatusStrip.Text = "COORDENADAS
X: " & CStr(e.X) & " ; " & _
"Y: " & CStr(e.Y)
'X e Y empiezan en 0
If Not (Matriz Is Nothing) Then
EtiquetaND_StatusStrip.Text = "COLOR
R:" & CStr(Matriz(e.X, e.Y).R) & " ; " & _
"G:" & CStr(Matriz(e.X, e.Y).G) & " ; " & _
"B:" & CStr(Matriz(e.X, e.Y).B)
End If
13
CURSO 2009-2010
HERRAMIENTAS INFORMÁTICAS
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
PARA EL
GEOPROCESADO
Por último, para que el puntero del ratón se visualice como una cruz al pasar por encima de las imágenes,
simplemente debemos incluir en el método Load del formulario las siguientes sentencias:
PictureBox1.Cursor = Cursors.Cross
PictureBox2.Cursor = Cursors.Cross
Índice del tema
1.1.- Introducción al manejo de imágenes con Visual Basic.NET...................................................................1
1.1.1.- Creación de una aplicación...............................................................................................................1
1.1.2.- Visualización de imágenes................................................................................................................2
1.1.3.- Acceso a los niveles digitales de la imagen.......................................................................................3
1.1.4.- Pintar una imagen.............................................................................................................................5
1.1.5.- Guardar una imagen..........................................................................................................................6
1.2.- Diagrama de flujo....................................................................................................................................7
1.3.- Procesamiento de imágenes.....................................................................................................................7
1.3.1.- Inversión de los colores de la imagen...............................................................................................8
1.3.2.- Máscaras...........................................................................................................................................9
1.4.- Información en la posición del ratón.....................................................................................................13
14
TRATAMIENTO GEOMÉTRICO
2.1.
Introducción
En este tema se van a tratar las transformaciones geométricas píxel a píxel. Para ello, en los ejemplos que se
exponen a continuación, se parte siempre de la situación en la que se encuentre la imagen abierta y cargada
en una matriz. Cada uno de ellos se puede implementar como una nueva opción dentro de submenú llamado
Transformaciones.
1
Curso 2009-2010
Herramientas Informáticas
Geotecnologías Cartográficas en Ingeniería y Arquitectura
2.2.
Translación
'Declaración de variables auxiliares
Dim i, j As Long
Dim Rojo, Verde, Azul As Byte
Dim Xo, Yo As Double
'Parámetros
Xo = 100
Yo = 50
'Xo = CInt(InputBox("Introduce la translación en X", , 0))
'Yo = CInt(InputBox("Introduce la translación en Y", , 0))
'Cálculo dimensiones nueva imagen
Dim Ancho, Alto As Integer
Ancho = Matriz.GetUpperBound(0) + 1 + Xo
Alto = Matriz.GetUpperBound(1) + 1 + Yo
'Declaración del mapa de bits
Dim bmp As New Bitmap(Ancho, Alto)
'Vinculación bitmap con image
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
'Activación de las barras de desplazamiento
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
PictureBox2.Refresh()
'Doble bucle para efectuar la traslación
For i = 0 To Matriz.GetUpperBound(0)
For j = 0 To Matriz.GetUpperBound(1)
Rojo = Matriz(i, j).R
Verde = Matriz(i, j).G
Azul = Matriz(i, j).B
2
para el Geoprocesado
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
bmp.SetPixel(Convert.ToInt32(i + Xo), Convert.ToInt32(j + Yo), Color.FromArgb(Rojo,
Verde, Azul))
Next
'Esto es solo para que se refresque línea a línea
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
Next
Me.Refresh()
2.3.
Escala
'Declaración de variables auxiliares
Dim i, j As Long
Dim Rojo, Verde, Azul As Byte
Dim Escala As Double
'Parámetros
Escala = 1.5
'Escala = CDbl(InputBox("Introduce el factor de Escala", , 1))
'Cálculo dimensiones nueva imagen
Dim Ancho, Alto As Integer
Ancho = (Matriz.GetUpperBound(0) + 1) * Escala
Alto = (Matriz.GetUpperBound(1) + 1) * Escala
'Declaración del mapa de bits
Dim bmp As New Bitmap(Ancho, Alto)
'Vinculación bitmap con image
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
'Activación de las barras de desplazamiento
3
Curso 2009-2010
Herramientas Informáticas
Geotecnologías Cartográficas en Ingeniería y Arquitectura
para el Geoprocesado
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
PictureBox2.Refresh()
'Doble bucle para efectuar el escalado
For i = 0 To Matriz.GetUpperBound(0)
For j = 0 To Matriz.GetUpperBound(1)
Rojo = Matriz(i, j).R
Verde = Matriz(i, j).G
Azul = Matriz(i, j).B
bmp.SetPixel(Convert.ToInt32(i * Escala), Convert.ToInt32(j * Escala),
Color.FromArgb(Rojo, Verde, Azul))
Next
'Esto es solo para que se refresque línea a línea
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
Next
Me.Refresh()
2.4.
Sentido Inverso
Habrás visto el problema de huecos en la imagen que se genera con el código anterior. Esto se soluciona si se
remuestrea a la inversa, es decir, se recorre la imagen destino y se busca el color en la imagen original.
'Declaración de variables auxiliares
Dim i, j As Long
Dim Rojo, Verde, Azul As Byte
Dim Escala As Double
'Parámetros
'Escala = 1.5
Escala = CDbl(InputBox("Introduce el factor de Escala", , 1))
4
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
'Cálculo dimensiones nueva imagen
Dim Ancho, Alto As Integer
Ancho = (Matriz.GetUpperBound(0) + 1) * Escala
Alto = (Matriz.GetUpperBound(1) + 1) * Escala
'Declaración del mapa de bits
Dim bmp As New Bitmap(Ancho, Alto)
'Vinculación bitmap con image
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
'Activación de las barras de desplazamiento
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
PictureBox2.Refresh()
'Doble bucle para efectuar el escalado
For i = 0 To Convert.ToInt32(Ancho - (1 * Escala))
For j = 0 To Convert.ToInt32(Alto - (1 * Escala))
Rojo = Matriz(Convert.ToInt32(i / Escala), Convert.ToInt32(j / Escala)).R
Verde = Matriz(Convert.ToInt32(i / Escala), Convert.ToInt32(j / Escala)).G
Azul = Matriz(Convert.ToInt32(i / Escala), Convert.ToInt32(j / Escala)).B
bmp.SetPixel(i, j, Color.FromArgb(Rojo, Verde, Azul))
Next
'Esto es solo para que se refresque línea a línea
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
Next
Me.Refresh()
5
Curso 2009-2010
Herramientas Informáticas
Geotecnologías Cartográficas en Ingeniería y Arquitectura
2.5.
para el Geoprocesado
Transformación proyectiva
Ahora vamos a implementar la transformación proyectiva, tanto el cálculo de los parámetros como su
aplicación en el remuestreo de una imagen. Para ello vamos a implementar una serie de operaciones
matriciales necesarias para el cálculo.
Tanto en la asignatura Cámaras como en Procesamiento avanzado de imágenes digitales se dan los
fundamentos necesarios para el uso de la transformación proyectiva.
El sistema de observación correspondiente expresado de forma matricial es:
X
0

Y 1
0 0
0
X
0 0 − XX ' − YX '
∗
Y 1 − XY ' − YY ' 
 a
 b
 
 c
 
 d  −  X ' = [V ]
 e   Y ' 
 
 f
 g
 
 h 
A∗ X − L = V
El cálculo de parámetros queda resuelto mediante (caso sobredeterminado):
X = N − 1 AT L
donde N = A ∗ AT
6
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
2.5.1.- Cálculos matriciales
Para implementar las operaciones matriciales inserta en el proyecto un módulo, y dentro de él inserta el
código correspondiente a las funciones que se detallan a continuación. De esta manera podemos llamar a las
funciones desde el código del formulario.
2.5.1.1.- Transpuesta de una matriz
Public Function Transpuesta(ByVal Matriz1(,) As Double) As Double(,)
'Creamos la matriz transpuesta
Dim MatrizTranspuesta(Matriz1.GetUpperBound(1), Matriz1.GetUpperBound(0)) As Double
'Declaración de variables auxiliares
Dim i, j As Short
'Doble bucle para efectuar la transposición
For i = 0 To Matriz1.GetUpperBound(0)
For j = 0 To Matriz1.GetUpperBound(1)
MatrizTranspuesta(j, i) = Matriz1(i, j)
Next
Next
'Es necesario esta sentencia para que la función devuelva la matriz calculada
Return MatrizTranspuesta
End Function
Ejemplo de uso de esta función. Escribe el siguiente código en el evento Load del formulario principal,
introduce puntos de ruptura y agrega inspecciones para evaluar las variables en tiempo de ejecución:
Dim A(2, 1) As Double
A(0, 0) = 2 : A(0, 1) = 3
A(1, 0) = 4 : A(1, 1) = 1
A(2, 0) = 9 : A(2, 1) = 8
Dim R(,) As Double = Transpuesta(A)
7
Curso 2009-2010
Herramientas Informáticas
Geotecnologías Cartográficas en Ingeniería y Arquitectura
para el Geoprocesado
2.5.1.2.- Suma de matrices
Public Function Suma(ByVal Matriz1(,) As Double, ByVal Matriz2(,) As Double) As Double(,)
'Comprobación de que las dos matrices son del mismo orden,
If Not Matriz1.GetUpperBound(0) = Matriz2.GetUpperBound(0) AndAlso _
Matriz1.GetUpperBound(1) = Matriz2.GetUpperBound(1) Then Exit Function
'Creamos la matriz suma
Dim MatrizSuma(Matriz1.GetUpperBound(0), Matriz1.GetUpperBound(1)) As Double
'Declaración de variables auxiliares
Dim i, j As Short
'Doble bucle para efectuar la suma
For i = 0 To Matriz1.GetUpperBound(0)
For j = 0 To Matriz1.GetUpperBound(1)
MatrizSuma(i, j) = Matriz1(i, j) + Matriz2(i, j)
Next
Next
'Es necesario esta sentencia para que la función devuelva la matriz calculada
Return MatrizSuma
End Function
Ejemplo de uso:
Dim A(1, 1), B(1, 1) As Double
A(0, 0) = 2 : A(0, 1) = 3
A(1, 0) = 4 : A(1, 1) = 1
B(0, 0) = 2 : B(0, 1) = 7
B(1, 0) = 5 : B(1, 1) = 3
Dim R As Double(,) = Suma(A, B)
8
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
2.5.1.3.- Producto de dos matrices
Public Function Producto(ByVal Matriz1(,) As Double, ByVal Matriz2(,) As Double) As Double(,)
'Comprobación de que las dos matrices son multiplicables
If Not Matriz1.GetUpperBound(1) = Matriz2.GetUpperBound(0) Then Exit Function
'Creamos la matriz producto
Dim MatrizProducto(Matriz1.GetUpperBound(0), Matriz2.GetUpperBound(1)) As Double
'Declaración de variables auxiliares
Dim i, j, k As Short
'Triple bucle para efectuar el producto
For i = 0 To MatrizProducto.GetUpperBound(0)
For j = 0 To MatrizProducto.GetUpperBound(1)
For k = 0 To Matriz1.GetUpperBound(1)
MatrizProducto(i, j) = MatrizProducto(i, j) + _
Matriz1(i, k) * Matriz2(k, j)
Next
Next
Next
'Es necesario esta sentencia para que la función devuelva la matriz calculada
Return MatrizProducto
End Function
Ejemplo de uso:
Dim A(1, 2), B(2, 1) As Double
A(0, 0) = 2 : A(0, 1) = 3 : A(0, 2) = 2
9
Curso 2009-2010
Herramientas Informáticas
Geotecnologías Cartográficas en Ingeniería y Arquitectura
para el Geoprocesado
A(1, 0) = 4 : A(1, 1) = 1 : A(1, 2) = 5
B(0, 0) = 2 : B(0, 1) = 7
B(1, 0) = 5 : B(1, 1) = 3
B(2, 0) = 1 : B(2, 1) = 6
Dim R As Double(,) = Producto(A, B)
2.5.1.4.- Inversa de una matriz
Public Function Inversa(ByVal Matriz3(,) As Double) As Double(,)
'Método de inversión: matriz de paso
'Comprobación de que la matriz es cuadrada
If Not Matriz3.GetUpperBound(0) = Matriz3.GetUpperBound(1) Then Exit Function
'Creamos dos matrices auxiliares
Dim Matriz1(Matriz3.GetUpperBound(0), Matriz3.GetUpperBound(1)) As Double
Dim Matriz2(Matriz3.GetUpperBound(0), Matriz3.GetUpperBound(1)) As Double
'Creamos la matriz inversa
Dim MatrizInversa(Matriz1.GetUpperBound(0), Matriz1.GetUpperBound(1)) As Double
'Declaración de variables auxiliares
Dim i, j, k As Short
Dim factor As Double
'Copia de la matriz original
For i = 0 To Matriz1.GetUpperBound(0)
For j = 0 To Matriz1.GetUpperBound(0)
Matriz1(i, j) = Matriz3(i, j)
10
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
Next
Next
'Matriz de Paso inicial = Matriz Identidad (todo ceros excepto la diagonal formada por
"unos")
For i = 0 To MatrizInversa.GetUpperBound(0)
Matriz2(i, i) = 1
Next
'Se diagonaliza la matriz original y se aplican las mismas operaciones a la matriz de paso
For j = 0 To Matriz1.GetUpperBound(0)
For i = 0 To Matriz1.GetUpperBound(0)
If i <> j Then
If Matriz1(i, j) = 0 Then GoTo CUIDADO
factor = Matriz1(j, j) / Matriz1(i, j)
For k = 0 To Matriz2.GetUpperBound(0)
Matriz1(i, k) = Matriz1(j, k) - factor * Matriz1(i, k)
Matriz2(i, k) = Matriz2(j, k) - factor * Matriz2(i, k)
Next
CUIDADO:
End If
Next
Next
'Se divide cada elemento de la diagonal por si mismo y se consigue:
'
* En la matriz original la Matriz Identidad
'
* A la derecha la matriz de Paso = Inversa de la Matriz normal
For i = 0 To Matriz1.GetUpperBound(0)
factor = Matriz1(i, i)
'En la matriz original sólo se divide la diagonal porque el resto es cero (ya es
diagonal)
'Si quieres ver cómo se modifica la matriz original activa la siguiente línea
Matriz1(i, i) = Matriz1(i, i) / factor
For j = 0 To Matriz1.GetUpperBound(0)
MatrizInversa(i, j) = Matriz2(i, j) / factor
Next
11
Curso 2009-2010
Geotecnologías Cartográficas en Ingeniería y Arquitectura
Herramientas Informáticas
para el Geoprocesado
Next
'Es necesario esta sentencia para que la función devuelva la matriz calculada
Return MatrizInversa
End Function
Ejemplo de uso:
Dim A(2, 2) As Double
A(0, 0) = 2 : A(0, 1) = 3 : A(0, 2) = 2
A(1, 0) = 4 : A(1, 1) = 1 : A(1, 2) = 5
A(2, 0) = 3 : A(2, 1) = 3 : A(2, 2) = 1
Dim R_Inv As Double(,) = Inversa(A)
Dim Identidad As Double(,) = Producto(A, R_Inv)
2.5.2.- Cálculo de los parámetros y remuestreo
En primer lugar debes incluir en la sección de Declaraciones del formulario la definición de la
estructura Punto:
Public Structure Punto
Dim X As Double
Dim Y As Double
End Structure
Inserta el siguiente código en una nueva opción del menú Transformaciones llamada Proyectiva. Utiliza para
probarlo la imagen Malla.bmp. Recuerda que debes abrir la imagen y luego cargar la matriz.
12
BENJAMÍN ARIAS PÉREZ
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
Dim Origen(3), Destino(3) As Punto
Origen(0).X = 50
Origen(0).Y = 50
Origen(1).X = 390
Origen(1).Y = 50
Origen(2).X = 390
Origen(2).Y = 440
Origen(3).X = 50
Origen(3).Y = 440
Destino(0).X = 64
Destino(0).Y = 66
Destino(1).X = 296
Destino(1).Y = 120
Destino(2).X = 401
Destino(2).Y = 441
Destino(3).X = 27
Destino(3).Y = 462
'Declaración de matrices
Dim A(Origen.GetUpperBound(0) * 2 + 1, Origen.GetUpperBound(0) * 2 + 1) As Double
Dim L(Origen.GetUpperBound(0) * 2 + 1, 0) As Double
'Declaración de variables auxiliares
Dim i, j As Short
'Matriz de diseño
For i = 0 To Origen.GetUpperBound(0)
'Primera fila
A(2 * i, 0) = (Origen(i).X)
13
Curso 2009-2010
Geotecnologías Cartográficas en Ingeniería y Arquitectura
Herramientas Informáticas
para el Geoprocesado
A(2 * i, 1) = (Origen(i).Y)
A(2 * i, 2) = 1
A(2 * i, 3) = 0
A(2 * i, 4) = 0
A(2 * i, 5) = 0
A(2 * i, 6) = -(Origen(i).X) * (Destino(i).X)
A(2 * i, 7) = -(Origen(i).Y) * (Destino(i).X)
'Segunda fila
A(2 * i + 1, 0) = 0
A(2 * i + 1, 1) = 0
A(2 * i + 1, 2) = 0
A(2 * i + 1, 3) = (Origen(i).X)
A(2 * i + 1, 4) = (Origen(i).Y)
A(2 * i + 1, 5) = 1
A(2 * i + 1, 6) = -(Origen(i).X) * (Destino(i).Y)
A(2 * i + 1, 7) = -(Origen(i).Y) * (Destino(i).Y)
Next
'Matriz de términos independientes
For i = 0 To Origen.GetUpperBound(0)
L(2 * i, 0) = Destino(i).X
L(2 * i + 1, 0) = Destino(i).Y
Next
'Cálculo de parámetros
Dim N As Double(,) = Producto(Transpuesta(A), A)
Dim N_inv As Double(,) = Inversa(N)
Dim T As Double(,) = Producto(Transpuesta(A), L)
Dim X As Double(,) = Producto(N_inv, T)
Dim Rojo, Verde, Azul As Byte
Dim bmp As New Bitmap(Matriz.GetUpperBound(0) * 2, Matriz.GetUpperBound(1) * 2)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
14
TRATAMIENTO GEOMÉTRICO
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
PictureBox2.Refresh()
For i = 0 To Matriz.GetUpperBound(0)
For j = 0 To Matriz.GetUpperBound(1)
Rojo = Matriz(i, j).R
Verde = Matriz(i, j).G
Azul = Matriz(i, j).B
Dim X_Dest, Y_Dest As Short
X_Dest = (X(0, 0) * i + X(1, 0) * j + X(2, 0)) / (X(6, 0) * i + X(7, 0) * j + 1)
Y_Dest = (X(3, 0) * i + X(4, 0) * j + X(5, 0)) / (X(6, 0) * i + X(7, 0) * j + 1)
bmp.SetPixel(X_Dest + 100, Y_Dest + 100, Color.FromArgb(Rojo, Verde, Azul))
Next
'Esto es solo para que se refresque línea a línea
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
Next
Me.Refresh()
15
Curso 2009-2010
Geotecnologías Cartográficas en Ingeniería y Arquitectura
Herramientas Informáticas
para el Geoprocesado
Índice del tema
2.1. Introducción...............................................................................................................................................1
2.2. Translación................................................................................................................................................2
2.3. Escala.........................................................................................................................................................3
2.4. Sentido Inverso..........................................................................................................................................4
2.5. Transformación proyectiva........................................................................................................................6
16
TRABAJO CON MAPAS DE BITS
3.1. - Introducción
Hasta ahora hemos trabajado con los píxeles como unidad de procesamiento. En este tema vamos a utilizar
los mapas de bits como unidad de procesamiento geométrico (y radiométrico) de las imágenes.
Para todas las funciones que veamos en este tema debes implementar una nueva opción en el menú principal
y llámala Mapa de bits.
Las funciones que vayas generando en este tema deben estar dentro nuevos submenús de Mapa de bits.
Por otro lado, las funciones de este tema no van a utilizar la matriz empleada en los temas anteriores, luego
sólo será necesario abrir una imagen para poder aplicarlas, no siendo necesario cargar la matriz.
3.2.- Rotación con la clase Image
En primer lugar vamos a efectuar la única transformación que tiene la clase Image. Se trata de la rotación y
se accede a ella a través de la función RotateFlip. Un ejemplo de llamada a esta función puede ser:
PictureBox1.Image.RotateFlip(RotateFlipType.Rotate180FlipX)
PictureBox1.Refresh()
La enumeración RotateFlipType admite los siguientes miembros:
Nombre de Miembro
RotateNoneFlipNone
Rotate90FlipNone
Rotate180FlipNone
Rotate270FlipNone
Descripción
Indica que no hay ni giro ni volteo.
Indica un giro de 90 grados sin volteo.
Indica un giro de 180 grados sin volteo.
Indica un giro de 270 grados sin volteo.
1
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA PARA EL GEOPROCESADO
RotateNoneFlipX
Rotate90FlipX
Rotate180FlipX
Rotate270FlipX
RotateNoneFlipY
Rotate90FlipY
Rotate180FlipY
Rotate270FlipY
RotateNoneFlipXY
Rotate90FlipXY
Rotate180FlipXY
Rotate270FlipXY
HERRAMIENTAS INFORMÁTICAS
Indica que no hay giro, seguido por un volteo horizontal.
Indica un giro de 90 grados seguido por un volteo horizontal.
Indica un giro de 180 grados seguido por un volteo horizontal.
Indica un giro de 270 grados seguido por un volteo horizontal.
Indica que no hay giro, seguido por un volteo vertical.
Indica un giro de 90 grados seguido por un volteo vertical.
Indica un giro de 180 grados seguido por un volteo vertical.
Indica un giro de 270 grados seguido por un volteo vertical.
Indica que no hay giro, seguido por un volteo horizontal y vertical.
Indica un giro de 90 grados seguido por un volteo horizontal y vertical.
Indica un giro de 180 grados seguido por un volteo horizontal y vertical.
Indica un giro de 270 grados seguido por un volteo horizontal y vertical.
3.3.- El objeto Graphics
Vamos a utilizar una serie de funciones implementadas en el objeto Graphics y aplicar su método
DrawImage para realizar las transformaciones.
3.3.1.- Translación
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
G.TranslateTransform(50, 50)
G.DrawImage(PictureBox1.Image, New PointF(0, 0))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
3.3.2.- Escala
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
2
TRABAJO CON MAPAS DE BITS
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
G.ScaleTransform(4, 4)
G.DrawImage(PictureBox1.Image, New PointF(0, 0))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
3.3.3.- Giro
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
G.RotateTransform(45)
G.DrawImage(PictureBox1.Image, New PointF(150, 50))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
3
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
HERRAMIENTAS INFORMÁTICAS
PARA EL
GEOPROCESADO
3.3.4.-Combinación de operaciones geométricas
Se pueden combinar las operaciones anteriores de la siguiente forma:
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
G.TranslateTransform(50, 50)
G.ScaleTransform(4, 4)
G.RotateTransform(45)
G.DrawImage(PictureBox1.Image, New PointF(150, 50))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
3.4.-Drawing2D.Matrix
Ahora vamos a ver las posibilidades que ofrece Drawing2D.Matrix. La Transformación Afín implica
geométricamente un giro, dos factores de escala (uno para cada eje de coordenadas) y una translación. Estos
cinco parámetros se pueden agrupar en una matriz que proporciona un namespace llamado Drawing2D, que
precisamente recibe el nombre de Matrix. Según la documentación proporcionada por MSDN corresponde a:
 m11

 m21
 OffsetX

m12
m22
OffsetY
0

0
1 
Los términos de esta matriz corresponde a:
•
•
•
4
Los términos mij engloban el giro y los dos factores de escala.
OffsetX y OffsetY, representan los valores de traslación.
La última columna (0,0,1)T corresponde con la Transformación Afín (ver contenido de la asignatura
Procesamiento Avanzado de Imágenes Digitales). Si los dos elementos nulos no lo fueran estaríamos
ante una Transformación Proyectiva.
TRABAJO CON MAPAS DE BITS
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
3.4.1.-Translación
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
Dim TAfin As New Drawing2D.Matrix
TAfin.Translate(50, 25)
G.Transform = TAfin
G.DrawImage(PictureBox1.Image, New PointF(0, 0))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
3.4.2.-Escala
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
Dim TAfin As New Drawing2D.Matrix
TAfin.Scale(2, 3)
G.Transform = TAfin
G.DrawImage(PictureBox1.Image, New PointF(0, 0))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
5
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
HERRAMIENTAS INFORMÁTICAS
PARA EL
GEOPROCESADO
3.4.3.-Giro
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
Dim TAfin As New Drawing2D.Matrix
TAfin.Rotate(30)
G.Transform = TAfin
G.DrawImage(PictureBox1.Image, New PointF(0, 0))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
3.4.4.-Transformación Afín
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
G = Graphics.FromImage(bmp)
'Borra la Matriz de transformación
G.ResetTransform()
Dim TAfin As New Drawing2D.Matrix
TAfin.Translate(50, 25)
TAfin.Scale(2, 3)
TAfin.Rotate(30)
G.Transform = TAfin
G.DrawImage(PictureBox1.Image, New PointF(0, 0))
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
6
TRABAJO CON MAPAS DE BITS
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
3.5.-Interpolación
El namespace Drawing2D ofrece varias formas de interpolación, que corresponden con la enumeración
InterpolationMode.
Nombre de miembro Descripción
Bicubic
Especifica interpolación bicúbica. No se ha aplicado ningún filtro previo. Este
modo no es adecuado para comprimir una imagen hasta por debajo de un 25%
de su tamaño original.
Bilinear
Especifica interpolación bilineal. No se ha aplicado ningún filtro previo. Este
modo no es adecuado para comprimir una imagen hasta por debajo de un 50%
de su tamaño original.
Default
Especifica el modo predeterminado.
High
Especifica interpolación de calidad alta.
HighQualityBicubic
Especifica una interpolación bicúbica de gran calidad. Se aplica un filtro previo
para garantizar una compresión de gran calidad. Este modo genera imágenes
transformadas de la más alta calidad.
HighQualityBilinear Especifica una interpolación bilineal de gran calidad. Se aplica un filtro previo
para garantizar una compresión de gran calidad.
Invalid
Equivalente al elemento Invalid de la enumeración QualityMode.
Low
Especifica interpolación de calidad baja.
NearestNeighbor
Especifica interpolación de elemento más cercano.
Un ejemplo de uso de esta función (inserta el código antes de llamar a la función DrawImage):
G.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
7
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
HERRAMIENTAS INFORMÁTICAS
PARA EL
GEOPROCESADO
3.6.-ColorMatrix
Es posible manejar radiométricamente un mapa de bits a través de ColorMatrix. Se trata de una matriz de
5x5 que contiene los pesos para tratar los colores Rojo, Verde, Azul, Transparencia y un valor w (por este
orden). Con los siguientes ejemplos queda bastante claro su uso.
3.6.1.-Transparencia
With OpenFileDialog1
.Filter = "Ficheros BMP|*.bmp" & _
"|Ficheros GIF|*.gif" & _
"|Ficheros JPG o JPEG|*.jpg;*.jpeg" & _
"|Ficheros PNG|*.png" & _
"|Ficheros TIFF|*.tif"
.FilterIndex = 1
If (.ShowDialog() = Windows.Forms.DialogResult.OK) Then
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
'Esto asigna el Image pero no dibuja la imagen
Dim imgOriginal As Image = Image.FromFile(.FileName)
G = Graphics.FromImage(bmp)
Dim rect As New Rectangle(0, 0, 1000, 1000)
Dim imgAtr As New Imaging.ImageAttributes
'Transparencia
Dim clMatriz As Imaging.ColorMatrix =
{New Single() {1, 0, 0,
New Single() {0, 1, 0,
New Single() {0, 0, 1,
New Single() {0, 0, 0,
New Single() {0, 0, 0,
New Imaging.ColorMatrix(New Single()() _
0, 0}, _
0, 0}, _
0, 0}, _
0.5, 0}, _
0, 1}})
imgAtr.SetColorMatrix(clmatriz)
G.DrawImage(imgOriginal, rect, 0, 0, 1000, 1000, GraphicsUnit.Pixel, imgAtr)
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la
imagen para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
End If
End With
8
TRABAJO CON MAPAS DE BITS
PROCESAMIENTO DIGITAL DE IMÁGENES
BENJAMÍN ARIAS PÉREZ
3.6.2.-De color a niveles de gris
With OpenFileDialog1
.Filter = "Ficheros BMP|*.bmp" & _
"|Ficheros GIF|*.gif" & _
"|Ficheros JPG o JPEG|*.jpg;*.jpeg" & _
"|Ficheros PNG|*.png" & _
"|Ficheros TIFF|*.tif"
.FilterIndex = 1
If (.ShowDialog() = Windows.Forms.DialogResult.OK) Then
Dim G As Graphics
Dim bmp As New Bitmap(1000, 1000)
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
'Esto asigna el Image pero no dibuja la imagen
Dim imgOriginal As Image = Image.FromFile(.FileName)
G = Graphics.FromImage(bmp)
Dim rect As New Rectangle(0, 0, 1000, 1000)
Dim imgAtr As New Imaging.ImageAttributes
'Conversión a escala de gris
Dim clmatriz As Imaging.ColorMatrix = New Imaging.ColorMatrix(New Single()() _
{New Single() {0.299, 0.299, 0.299, 0, 0}, _
New Single() {0.587, 0.587, 0.587, 0, 0}, _
New Single() {0.114, 0.114, 0.114, 0, 0}, _
New Single() {0, 0, 0, 1, 0}, _
New Single() {0, 0, 0, 0, 1}})
imgAtr.SetColorMatrix(clmatriz)
G.DrawImage(imgOriginal, rect, 0, 0, 1000, 1000, GraphicsUnit.Pixel, imgAtr)
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la
imagen para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
End If
End With
9
CURSO 2009-2010
GEOTECNOLOGÍAS CARTOGRÁFICAS EN INGENIERÍA Y ARQUITECTURA
HERRAMIENTAS INFORMÁTICAS
PARA EL
GEOPROCESADO
3.7.-Scan0
Para terminar con la implementación de código, vamos a ver una última forma de trabajar con imágenes en
VB.NET a través de funciones que gestionan punteros y bloques de memoria. Es sólo un primer paso que os
permitirá, si continuáis por esta línea, trabajar de forma muy eficiente con imágenes de gran tamaño.
'Crea un nuevo bitmap a partir de la imagen cargada en PictureBox1
Dim bmp As New Bitmap(PictureBox1.Image)
'Define una estructura de tipo Rectangle
Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
'Define bmpData para acceder a los datos del mapa de bits
'y bloquea el mapa de bits
Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(rect, _
Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat)
'Obtiene la dirección en memoria del mapa de bits
Dim ptr As IntPtr = bmpData.Scan0
'Declara un array de bytes
'Este código es específico para un bitmap de 24 bits per pixels.
Dim bytes As Integer = bmpData.Stride * bmp.Height
Dim rgbValues(bytes - 1) As Byte
'Copia los valores RGB en el array
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)
'Colocamos a 100 el cuarto componente (transparencia)
For i As Integer = 3 To rgbValues.Length - 1 Step 4
rgbValues(i) = 100
Next
'Copia el array en la dirección de memoria
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes)
'Desbloquea el mapa de bits
bmp.UnlockBits(bmpData)
'Dibuja la imagen
Dim img As Image
img = CType(bmp, Image)
PictureBox2.Image = img
PictureBox2.Refresh()
'Se necesita inicializar la propiedad AutoScrollMinSize con el tamaño de la imagen
para poder mostrar los Scrolls
Panel2.AutoScrollMinSize = PictureBox2.Image.Size
10
BENJAMÍN ARIAS PÉREZ
TRABAJO CON MAPAS DE BITS
PROCESAMIENTO DIGITAL DE IMÁGENES
Índice del tema
3.1. - Introducción............................................................................................................................................1
3.2.- Rotación con la clase Image....................................................................................................................1
3.3.- El objeto Graphics...................................................................................................................................2
3.4.-Drawing2D.Matrix...................................................................................................................................4
3.5.-Interpolación............................................................................................................................................7
3.6.-ColorMatrix.............................................................................................................................................8
3.7.-Scan0.....................................................................................................................................................10
11
Empleo de Imágenes Piramidales
Benjamín Arias Pérez
Ingeniero Técnico en Topografía
Ingeniero en Geodesia y Cartografía
e-mail: [email protected]
Resumen
En el presente artículo se expone un análisis acerca de la gestión y uso de imágenes piramidales.
Para ello se define un formato de imagen propio, Mapa de Bits Jerárquico, y asociado al mismo se desarrollan
dos controles ActiveX: uno para la creación de las imágenes con el nuevo formato, y otro para la explotación
fotogramétrica de dichas imágenes.
1.- Introducción
Este artículo abarca parte de los
conocimientos adquiridos y aplicados durante la
generación del Proyecto Fin de Carrera de la
Ingeniería Superior en Geodesia y Cartografía,
Desarrollo de Software para Fotogrametría Digital,
presentado en la Escuela Politécnica Superior de
Ávila, perteneciente a la Universidad de
Salamanca.
El gran impedimento a la hora de trabajar
en Fotogrametría Digital y la implementación de
algoritmos aplicados, está derivado por un lado de
la necesidad de empleo de imágenes que ocupan
mucho espacio de almacenamiento, y por otro
lado del manejo en memoria de estas imágenes.
Sobre cualquier lenguaje de programación
pensado para desarrollo de software sobre
Windows, se abre una imagen de un gran tamaño
con los métodos que se ponen a disposición del
usuario, el proceso se puede llegar a ralentizar
tanto, que es posible que se termine bloqueando
el ordenador.
Entonces, el interés está en cómo
manejar estas imágenes en el caso de que
ocupen tanto en memoria que al ordenador le
cuesta mover la imagen en pantalla. Por ello, se
decidió la creación de un nuevo formato, un
formato propio, o si se quiere interno, para con él
manejar grandes archivos de imagen.
1.1.- Objetivos
Los objetivos que se pretenden con la
definición de un nuevo formato de imagen son:

Acceso rápido a la imagen: que se pueda
abrir de forma rápida (o al menos que al
usuario no le parezca una eternidad).

Navegabilidad: que se
roaming con la imagen.

Imagen índice: que haya una imagen que
nos muestre en todo momento que parte
pueda
realizar
de la imagen completa es la que se está
visualizando en pantalla (Overview).

Acceso fotogramétrico: esto implica que se
puedan conseguir aspectos necesarios en
el manejo de una imagen dentro de la
disciplina de la Fotogrametría. Se refiere,
e.g., al posible acceso a las coordenadas
píxel del puntero del ratón en un
determinado momento, cómo obtener el
valor del nivel de gris. También hace
referencia a la posibilidad de manejar dos
imágenes a la vez para ayudar a conseguir
la visión estereoscópica. O tener más de
dos imágenes abiertas en caso de
Aerotriangulación.

Acceso al usuario: el posible usuario de
este nuevo formato de imagen debe tener
acceso fácil a la imagen, su percepción del
código debe ser totalmente opaca y debe
limitarse a utilizar el formato como una
herramienta fotogramétrica más. Ello no
excluye de la posibilidad de que ciertos
usuarios puedan utilizar de forma un poco
más cercana este formato propio de
imagen, incluyéndolo en sus posibles
aplicaciones, utilizando el código ya
generado, creando algoritmos alrededor de
él, i.e, que haya una interacción entre el
programa a generar por el usuario y el
nuevo formato de imagen, pero sin que
aquel pueda modificar el contenido del
formato, limitándose a emplearlo. Esta
última alternativa se ha empleado con éxito
en dos proyectos fin de carrera.
2.- Definición del nuevo formato de imagen
El formato de imagen que se describe a
continuación consiste, en lo que se refiere a la
representación interna de la imagen, en el formato
de mapas de bits de Windows. Este proceder se
sustenta por el hecho de que generalmente se
trabaja con sistemas operativos Windows, y estos
sistemas tienen dicho formato gráfico. Por lo tanto,
Windows proporciona un juego de funciones
gráficas para emplear con su formato de imagen,
BMP, que hacen posible manejar las imágenes de
una manera eficiente, aunque no por ello sencilla.
El nuevo formato de imagen creado
contiene varios mapas de bits, que son copias de
la imagen original a diferente tamaño, y por último
un bitmap que servirá de overview. Por lo tanto, se
trata de una pirámide de imágenes, y por ello este
formato propio de imágenes recibe el nombre de
Mapa de Bits Jerárquico, y sus archivos llevarán
la extensión BMJ. Además, se implementa sólo
bajo una profundidad de color de 8 bits, i.e., 256
colores, por lo tanto se emplea una paleta de
colores para su visualización en pantalla.
Luego el formato BMJ está formado por
las siguientes partes:

La imagen original: se copia la imagen
original al nuevo archivo, y se copia tal
cual, con la información de cabecera de
archivo, cabecera de bitmap y paleta de
colores, y el mapa de bits.

Estructura BMJ: datos acerca de la
estructura de este archivo, como pueden
ser el número de imágenes en la pirámide,
posición en el archivo de las mismas, etc.
Es una pieza fundamental dentro de este
archivo.

Las sucesivas imágenes a diferente
tamaño: son copias a distinto tamaño de la
imagen original, dividiendo sucesivamente
su alto y su ancho entre 2, hasta llegar a un
tamaño mínimo especificado en el código.
Estas imágenes se almacenan en el
archivo con la información de cabecera de
archivo, cabecera de bitmap y paleta y
luego el bitmap, tal y como si se trata de un
archivo de mapa de bits independiente. De
esta manera, el algoritmo que se encargue
de la visualización tiene los datos a su
disposición y no necesita del cálculo de sus
datos, aspecto éste esencial porque ahorra
tiempo de cálculo en la visualización.

Imagen índice: es una imagen que servirá
de Overview, de apoyo en la navegación
por la imagen. También se incluyen en el
archivo BMJ la cabeceras, paleta y el
correspondiente bitmap.

Al final del archivo se puede incluir datos
acerca de la imagen, como puede ser
datos de la Orientación Interna, o de la
Orientación Externa.
Lo más lógico es que la información de la
estructura BMJ esté al inicio del archivo, así el
código que se encargara de la visualización de la
imagen empieza por leerla y actuar en
consecuencia. Esto no es así debido a dos
causas:

La primera es debida a la implementación
del código para realizar el nuevo formato a
partir de un mapa de bits, pues existen una
serie de inconvenientes prácticos para
colocar primero la estructura BMJ y luego
la imagen original tal cual.

La segunda es una causa de carácter
práctico, pues si al archivo generado con
extensión BMJ se le cambia dicha
extensión por BMP se podrá visualizar la
imagen original, aunque el archivo
contenga más bytes. Esto ocurre porque
para visualizar un mapa de bits, el
programa que se encargue de hacerlo, lee
la cabecera del archivo y la cabecera del
bitmap, donde trae información acerca del
tamaño, resolución, profundidad de color,
etc, luego carga el bitmap en memoria y
posteriormente se produce la visualización
del archivo en pantalla. Luego con el resto
de la información del archivo BMJ, como no
se hace referencia en la cabecera del
bitmap a ellos, porque no pertenece al
mapa de bits original, el programa no los
lee ni sabe que están ahí.
El formato creado se especifica para
operaciones de lectura/escritura en binario, y la
estructura se puede resumir en la siguiente figura:
Mapa de Bits Jerárquico
Imagen Original
Estructura BMJ
Imagen 1
Imagen 2
...
Imagen n
Imagen Índice
Otros Datos (OI, OE, ...)
Figura 1.- Estructura de archivo Mapa de Bits Jerárquico.
La imagen Original, sus correspondientes
imágenes piramidales, y la imagen índice, tienen
cada una la estructura de archivo de mapa de bits.
simplemente se trata del nombre del archivo,
directorio o camino (path) inclusive, que se desea
convertir al formato Mapa de Bits Jerárquico.
Luego el código de escritura del formato
de imagen Mapa de Bits Jerárquico consta de las
siguientes partes:
3.- Escritura
Jerárquico
del
formato
Mapa
de
Bits
3.1.- Desarrollo del código de escritura
Para la escritura de archivos en el
formato diseñado se crea un control ActiveX, que
se corresponde con la creación de una clase, a
modo de objeto, que recibe el nombre de
Convierte. Esta clase, u objeto, tiene un miembro
o función externa principal, Carga, que es la única
relación que existe entre el control y el hipotético
usuario. También existen otra serie de funciones y
procedimientos, pero éstos son de carácter interno
y son de uso exclusivo del control Convierte, por lo
que el usuario de este control no tiene acceso a
ellos.
La función Carga de la clase Convierte
tiene tan sólo un parámetro, y además de carácter
opcional. El funcionamiento externo de esta
función es bastante sencillo y tan sólo debe
especificarse un parámetro, si se desea, o
ninguno en otro caso. Este parámetro
1.
Gestión de archivos: tanto del archivo de
entrada o lectura, como del archivo de
salida o escritura.
2.
Lectura del archivo de mapa de bits que se
desea transformar a BMJ. En concreto se
lee la cabecera de archivo y la cabecera
del mapa de bits.
3.
Chequeo del tipo de mapa de bits, de la
profundidad de color, y de que no se trate
de una imagen comprimida. Esto es así
porque sólo se trabaja con un determinado
tipo de BMP.
4.
Obtención de información de la memoria
disponible del ordenador, para actuar en
consecuencia.
5.
Escritura del archivo BMJ de forma
simultánea a la lectura del archivo BMP.
6.
Visualización de un indicador del avance
en la escritura del archivo BMJ.
3.2.- Alternativas
Para la selección de píxeles se estudian
varias alternativas para el ahorro de tiempo, o si
se quiere, para la disminución del número de
operaciones a realizar. En concreto 3 alternativas,
que son las siguientes:
1º. Lectura del valor del píxel en la imagen
original, uno a uno, y escritura de dichos
valores en el mapa de bits correspondiente
a la imagen n-ésima.
2º. Lectura de ciertas filas completas de la
imagen original, escritura de dichas líneas
en el mapa de bits correspondiente a la
imagen n-ésima. Se completa esta técnica
con la visualización escalada de estas
líneas.
que tiene 8x8 píxeles, i.e, 64 píxeles. La
siguiente imagen de la pirámide se forma a partir
de la original tomando un píxel de cada 4:
3º. Lectura de ciertas filas completas de la
imagen original, y escritura seleccionada
de píxeles en el mapa de bits
correspondiente a la imagen n-ésima.
La primera alternativa es la opción
intuitiva, la más lógica. Se trata simplemente de ir
leyendo en el mapa de bits de la imagen original
los valores de los píxeles que se deseen, y su
posterior escritura en el mapa de bits de la imagen
n-ésima. La cadencia en la lectura viene
determinada por la división n-ésima de la imagen
original. De esta forma, la primera imagen se
genera con uno de cada dos píxeles de la imagen
original en la primera línea, luego salta a la 3ª
línea para coger 1 de cada dos líneas, etc...De
n
forma generalizada, se toma un píxel de cada 2
píxeles de la imagen original en el n-esimo
escalón de la pirámide. A continuación se muestra
un ejemplo de forma gráfica. Se supone la imagen
original formada por 16x16 píxeles, i.e, 256
píxeles.
luego la imagen resultante es:
con 4x4 píxeles, i.e., 16 píxeles.
Esta primera alternativa implica muchas
operaciones de lectura y escritura, lo que a su vez
implica mucho tiempo. Por ello se piensa en otras
alternativas, que se explican más adelante.
En la Figura 2 se muestra una pirámide
imágenes así obtenida. La imagen inferior es la
imagen original, la imagen situada en el medio es
la primera imagen generada por la primera división
de la original, y la superior es la imagen generada
por la segunda división de la original.
La primera imagen de la pirámide se
forma tomando un píxel de cada dos, que como se
puede ver en la imagen superior, son los píxeles
marcados con X. La imagen así formada es:
escritura. Entonces la primera imagen que se
almacena es:
La segunda imagen se genera con una
de cada 4 filas de la imagen original:
Figura 2.- Ejemplo de imagen piramidal según la
primera alternativa.
La estrategia de la segunda alternativa es
leer una fila completa de la imagen original en
cada operación de lectura, y escribir dicha fila en
una sola operación de escritura en el mapa de bits
n
de la imagen n-ésima. Se leen sólo cada 2 filas
para la generación de la imagen n-ésima. Con el
mismo ejemplo anterior, como se puede ver en la
siguiente figura:
La segunda imagen generada es:
Para visualizar las imágenes así
generadas se requiere el escalado de las
imágenes dentro del código de visualización
(control Imagen). A continuación se muestra una
pirámide de imágenes así generada:
una imagen de 16x16 píxeles, de la que se leen
las filas completas, sólo una de cada dos filas. Las
filas se leen cada una en una sola operación de
lectura, y se escriben en una sola operación de
El rendimiento de esta segunda
alternativa es realmente espectacular, en términos
tanto de número de operaciones como de tiempo
necesario para convertir una imagen en formato
de mapa de bits a una imagen en formato de
Mapa de Bits Jerárquico. Sin embargo, no es la
alternativa más adecuada debido al efecto de
huecos, píxeles negros, que se produce al escalar
la imagen para su visualización. En la Figura 4 se
puede comprobar este efecto. La imagen situada a
la izquierda es la imagen n-ésima generada
mediante la primera alternativa, y la imagen
situada a la derecha es la imagen n-ésima
generada por la segunda alternativa.
Debido a este inconveniente se rechaza
esta alternativa, porque no ofrece garantías en la
visualización debido a la función de escalado,
aunque tiene un reducido coste temporal.
Figura 3.- Ejemplo de imagen piramidal según la
segunda alternativa.
Figura 4.- Efecto de huecos debidos a la función de escalado.
Por lo tanto, se piensa en una tercera
alternativa, que en realidad es un híbrido de las
otras dos anteriores, tomando de cada una ellas
sus aspectos más positivos. En primer lugar, se
lee cada fila completa en una sola operación de
lectura y queda cargada en una matriz. Esto
ofrece ya una cierta mejora respecto a la primera
alternativa al ahorrar operaciones de lectura. La
escritura en el archivo no se realiza con la fila
n
completa, sino un píxel de cada 2 píxeles. No se
reduce el número de operaciones de escritura,
pero se consigue una ventaja, porque se obtienen
las imágenes piramidales correctas y no es
necesario un escalado posterior de la imagen.
con lo que la imagen resultante es de 4x4
píxeles:
Utilizando el mismo ejemplo que se ha
usado en las otras dos alternativas, se explica a
continuación esta tercera y definitiva. En primer
lugar, se parte de la imagen original, y para crear
la primera imagen de la pirámide se leen una de
cada dos filas de forma completa:
Esta es la alternativa implementada en el
control Convierte.
A continuación se muestra un estudio de
los tiempos y tamaños obtenidos para poder hacer
una comparación entre alternativas. Los tiempos
indicados corresponden a un ordenador Pentium a
166 MHz, con 64 MB de RAM., que si bien se
puede considerar hoy día como desfasado,
precisamente por ello resulta más adecuado para
este tipo de estudios, pues acentúa las diferencias
entre alternativas.
de cada fila leída, y que está cargada en una
matriz unidimensional, se seleccionan uno de
cada dos píxeles para escribirlo en el mapa de bits
de la primera imagen original.
y la imagen generada es la siguiente, de
8x8 píxeles:
la siguiente imagen piramidal se hace leyendo
cada fila de forma completa, una de cada 4 filas, y
para la escritura se selecciona uno de cada 4
píxeles:
Nombre Imagen
1ª Alternativa
(segundos)
2ª Alternativa
(segundos)
3ª Alternativa
(segundos)
%(1ª/2ª)
%(1ª/3ª)
Edif.bmp
17
0,6
7
2.733,33
142,86
tetraedro_d.bmp
44
1,5
18
2.833,33
144,44
Fot209.bmp
162
6
64
2.600,00
153,13
Catast188208.bmp
1.875
136
723
1.278,68
159,34
medida, en más de la mitad, e incluso la relación
mejora con el tamaño de la imagen. Los tamaños
van de menor tamaño, Edif.bmp, a mayor tamaño,
Catast188208.bmp. El siguiente gráfico ayuda a
ver las diferencias temporales:
Como se puede observar hay un gran
rendimiento temporal con la segunda alternativa.
Aún cuando con la imagen más grande de todas,
Catast188208.bmp, baja el rendimiento, sigue
siendo una mejora espectacular. Con la alternativa
definitiva, la tercera, se mejora el tiempo en buena
2000
1800
Tiempo en segundos
1600
1400
1200
1ª Alternativa
1000
2ª Alternativa
3ª Alternativa
800
600
400
200
0
1
2
3
4
Figura 6.- Diagrama de barras con estudio comparativo de tiempos.
donde los números se corresponden con la
numeración de la siguiente forma:

Edif.bmp: se corresponde con 1.

tetraedro_d.bmp: se corresponde con 2.

Foto209.bmp: se corresponde con 3.

Catast188208.bmp: se corresponde con 4.
Una vez elegida la alternativa, se hizo un
estudio de tamaños entre las imágenes originales
los archivos BMJ obtenidos. Los datos se
muestran en la siguiente tabla:
Nombre Imagen
Tamaño Imagen
% Aumento
Tamaño Imagen
BMP (Bytes)
BMJ (Bytes)
692.224
958.464
38,46
tetraedro_d.bmp
2.109.440
2.801.664
32,82
Fot209.bmp
7.114.752
108.234.752
9.486.336
33,33
144.324.608
33,34
Edif.bmp
Catast188208.bmp
Como se puede observar la relación de
aumento entre ambos archivos se mantiene en
torno a un 33-34%, excepto el primero que es de
38.46%.
160.000.000
140.000.000
Bytes Almacenamiento
120.000.000
100.000.000
80.000.000
Tamaño BMP
Tamaño BMJ
60.000.000
40.000.000
20.000.000
0
1
2
3
4
Figura 8.- Diagrama de barras con estudio comparativo de tamaños
4.- Desarrollo del código de lectura
El código de lectura se implementa sobre
un Control ActiveX, en forma de Control de
Usuario, y que recibe el nombre de Imagen. Por lo
tanto, las características iniciales son similares al
control Convierte, que es el control generado para
la escritura del formato BMJ. Sin embargo, el
control Imagen presenta más propiedades,
métodos y elementos que el control Convierte.
4.1.- Gestión de archivos
La función del control Imagen para
visualizar los archivos BMJ recibe el nombre de
Carga, y se puede llamar de forma externa al
control Imagen. Esta función puede recibir dos
parámetros:

nImagen: un número identificador de la
imagen. Se define como de tipo Byte, luego
su rango de valores se encuentra en 0 y
255 en números enteros.

NombreArchivo: el nombre del archivo que
contiene el archivo BMJ. Este parámetro es
opcional, por lo que su omisión ocasiona
que aparezca una ventana para que el
usuario elija el archivo BMJ que desea
visualizar.
El parámetro nImagen es el que sirve de
identificador en todo el código, por lo tanto es un
elemento indispensable dentro del código del
control Imagen. Tanto es así, que todas las
propiedades, métodos y elementos hacen
referencia a este identificador, por lo que la
primera operación en aquellos es obtener el
identificador de la imagen.
El parámetro NombreArchivo es el
nombre del archivo más la ruta de directorios
donde se encuentra. Es opcional, por lo que su
omisión genera la presentación en pantalla de una
ventana a modo de cuadro de diálogo, donde el
usuario puede elegir el archivo de tipo Mapa de
Bits Jerárquico que desea abrir. En el caso de que
el usuario pulse Cancelar en esta ventana, se
muestra un mensaje indicando que el usuario ha
decidido Cancelar la acción, por lo que no se
abrirá ninguna imagen.
La función Carga del control Imagen
devuelve una variable boolena, y de esta forma se
puede controlar si se ha producido un error en la
visualización de la imagen. Si la función devuelve
Verdadero se considera que no se producido
ningún error en la visualización de la imagen. En
cambio, si la función, una vez ejecutada, devuelve
Falso, significa que ha habido algún error en la
visualización.
Como ya se ha explicado anteriormente,
el formato de archivo BMJ se fundamenta en el
formato BMP, y éste está pensado para facilitar su
visualización en Windows. Es por ello que las
funciones que se utilizan para mostrar una imagen
en pantalla admiten como parámetros las
estructuras que existen dentro del formato BMP,
éstas están contenidas en el archivo del formato
BMP, y también están contenidas dentro del
archivo de extensión BMJ.
Por lo tanto en la lectura de un archivo
BMJ se pueden hablar de dos tipos de acciones,
independientemente de si se trata de la lectura
inicial o de la lectura en tiempo de ejecución:

Lectura propiamente dicha: se leen los
valores correspondientes al formato y se
almacenan en las variables adecuadas.

Preparación de la visualización: para ello
se cargan los valores necesarios en
variables que sean acordes con:
4.2.- Lectura del archivo BMJ
Se pueden distinguir dos tipos de lecturas
del archivo BMJ, dependiendo de cuándo se
realice la lectura:

Lectura inicial: se realiza mediante la
función Carga, de forma externa al Control
Imagen. Esta lectura está gestionada por
dicha función.

Lectura en tiempo de ejecución: se realiza
de forma interna desde el Control
Convierte, en respuesta a los eventos que
pueda generar el usuario. Esta lectura no
está gestionada por la función Carga.
La lectura inicial, gestionada por la
función Carga, sigue los siguientes pasos:
1.
Lectura de la cabecera del archivo y de la
cabecera del mapa de bits.
2.
Redimensionamiento de una matriz que
almacenará los valores del mapa de bits
que se van a cargar.
-
las estructuras admitidas por las
funciones de visualización
-
la parte de la imagen que se desea
mostrar
-
la imagen que corresponda dentro
de la pirámide de imágenes.
Las estructuras adecuadas para la
visualización son la cabecera de mapa de bits
(BITMAPINFOHEADER),
la
paleta
de
colores(RGBQUAD), y el mapa de bits
propiamente dicho.
4.3- Pasos para mostrar un mapa de bits
Los pasos que hay que realizar para
poder presentar un mapa de bits en pantalla, o si
se quiere en cualquier otro dispositivo son los
siguientes:
1.
Obtención del bitmap
2.
Creación de un contexto de dispositivo de
memoria
3.
Selección del bitmap
contexto de dispositivo.
4.
Copia desde dicho contexto al dispositivo
que se vaya a utilizar para su visualización.
en
este
último
3.
Almacenamiento del mapa de bits en la
matriz redimensionada.
5.
Eliminación del contexto de dispositivo
creado en el paso 2.
4.
Preparación de las estructuras adecuadas
para que puedan ser admitidas por las
funciones de visualización.
6.
Eliminación del mapa de bits cuando se
hay finalizado con él.
5.
Envío de las estructuras a las funciones de
visualización.
4.4.- Visualización de una parte de la imagen
La zona de la imagen que se desea
mostrar está determinada en todo momento por
las acciones que el usuario considere conveniente
realizar, por lo que la navegación por la imagen
es controlada por el usuario. La zona de la imagen
que se desea mostrar corresponde entonces con:

las coordenadas X, Y, del origen de la zona
de la imagen.

el ancho y el alto de la ventana de
visualización.

la imagen correspondiente dentro de la
pirámide de imágenes.

el identificador de la imagen BMJ.
Estos valores son los que se determinan
que zona de la imagen se visualiza, y estos
valores son determinados en todo momento.
Conviene recordar la estructura del
archivo BMJ, que por este orden tiene los
siguientes elementos:
mapa de bits correspondiente al primer
píxel.
4.
Mediante el ancho y el alto de la zona a
visualizar se leen los valores del mapa de
bits correspondientes.
5.
Se siguen los pasos necesarios para
mostrar un mapa de bits en pantalla.
Los tres primeros pasos son de gestión
de archivos. El primer paso se trata de saber
sobre que archivo hay que efectuar la lectura. El
segundo y tercer paso, acceden al primer píxel a
visualizar, teniendo para ello en cuenta los valores
almacenados en la estructura BMJ y las
coordenadas del origen.
La estructura BMJ es:
Public Type BitMapJerarquico

Imagen Original.

Estructura BMJ.
Tipo As Byte

Pirámide de imágenes.
nBitmap As Byte

Imagen Índice.
posBitmap() As Long

Datos relativos a Orientaciones.
También conviene recordar la forma de
un archivo BMP, que por orden está formado por:

Cabecera de archivo.

Cabecera del mapa de bits.

Paleta de colores.

Mapa de bits.
Por último recordar los conceptos de
scan line, número de bytes necesario para
representar una línea, e ídem del mapa de bits
completo. Y sobre todo, es interesante refrescar
ahora la estructura secuencial del mapa de bits.
La pregunta ahora es: ¿cómo se visualiza
una zona de la imagen? La respuesta a esta
cuestión es la pieza clave dentro de este Proyecto
Fin de Carrera. La visualización de una zona de la
imagen tiene varios pasos, y éstos son los
siguientes:
1.
En primer lugar, mediante el identificador
de la imagen BMJ, se accede a la imagen
BMJ correspondiente de las varias que es
posible tener abiertas.
2.
Después, se accede a la imagen n-ésima
dentro de la pirámide.
3.
Con las coordenadas X, Y del origen de la
zona de la imagen, se accede dentro del
posDatos As Long
End Type
El miembro posBitmap(n) permite, dentro
del archivo BMJ, acceder a la posición de inicio de
la imagen n-ésima. A continuación se accede al
inicio del mapa de bits de dicha imagen mediante
el
miembro
bfOffBits
de
la
estructura
BITMAPFILEHEADER.
La siguiente acción será acceder a la
píxel de inicio de la zona a visualizar mediante las
coordenadas del origen. Sin embargo, debido a la
configuración bottom-up del mapa de bits, por la
que la imagen aparece invertida en el orden de
sus filas, realmente se accede a la esquina inferior
izquierda, en lugar de a la esquina superior
izquierda, donde es norma común colocar el
origen de coordenadas en una imagen digital.
Una vez que se accede a la esquina
inferior izquierda, se lee por filas sólo el ancho a
visualizar, con lo que la imagen que se carga en
memoria es exclusivamente la que se desea
visualizar. Para aclarar esta idea conviene
observar la figura bajo estas líneas. Representa la
imagen completa, y los píxeles marcados con una
X son los correspondientes a la zona a visualizar.
Pues la estrategia se basa en que se carga en
memoria sólo la zona de la imagen a visualizar, y
no toda la imagen.
Para visualizar una zona de la pantalla,
se deben asignar valores a las estructuras del
formato BMP para poder pasadas a las funciones
de visualización. Por lo tanto, para cada zona de
la imagen que se presente en pantalla es
necesario modificar los siguientes miembros de la
estructura BITMAPINFOHEADER:

biWidth: es el ancho de la zona a mostrar.

biHeight: es el alto de la zona a mostrar.

biSizeImage: tamaño del mapa de bits en
bytes.

Zoom -: si en el momento de ejecutar este
comando la zona de la imagen mostrada
corresponde a la imagen n-ésima, visualiza
la zona de la imagen correspondiente a la
imagen inmediatamente inferior en cuanto
a tamaño, i.e., la imagen (n+1)-ésima de la
pirámide. Si no hay imagen inferior se
muestra el siguiente mensaje:

Hacer Zoom: permite realizar un Zoom de
la imagen

Paleta: permite modificar la tabla de
colores.

Anaglifo: permite mostrar el anaglifo de dos
imágenes.

Roaming: Permite realizar el denominado
Roaming, y además de dos formas, bien
por medio de movimientos del ratón o bien
por acción de la rueda superior del ratón.

Visión Estereoscópica: se consigue con la
ayuda de un estereóscopo. Además,
permite visualizar las marcas flotantes,
moverlas
de
forma
conjunta
o
independiente,
y
otras
aplicaciones
fotogramétricas.

Coordenadas Píxel: permite ver las
Coordenadas Instrumentales en tiempo
real. Se visualizan al lado del puntero del
ratón, como se puede ver:
5.- Descripción de funcionalidades
En este apartado se describen de forma
breve algunas de las funcionalidades del control
Imagen, entendidas como propiedades, métodos y
elementos que articulan las acciones del usuario
hacia el control Imagen en tiempo de ejecución
Las restantes funcionalidades se explican
en los capítulos posteriores, pues necesitan de
explicaciones adicionales
Junto con la ventana que muestra la
visualización en pantalla de la imagen también se
han diseñado las siguientes ventanas:

Imagen Índice.

Zoom.

Factor de Zoom.

Paleta.

Histograma.
A continuación se describen estas
ventanas, para qué sirven y cómo se
implementan. Sin embargo, antes se detallarán las
funciones de la ventana de Visualización.
Las opciones que se permiten son las siguientes:

Imagen Índice: visualiza la Imagen Índice.

Zoom +: si en el momento de ejecutar este
comando la zona de la imagen mostrada
corresponde a la imagen n-ésima de la
pirámide, visualiza la zona de la imagen
correspondiente
a
la
imagen
inmediatamente superior en cuanto a
tamaño, i.e., la imagen (n-1)-ésima. Si no
hay imagen superior se muestra el
siguiente
mensaje,
y
para
seguir
aumentando en tamaño es necesario hacer
uso del comando Hacer Zoom.


Fotocoordenadas:
permite
ver
las
Fotocoordenadas en tiempo real. Se
visualizan al lado del puntero del ratón.
Nivel Digital: permite ver el Nivel Digital del
píxel sobre el que se sitúa el puntero del
ratón.

Desarrollo de software específico para
poder manejar el formato de imagen a
crear.

Desarrollo propio del formato, y de su
implementación algorítmica.

Se deberá incorporar un paso previo en las
aplicaciones que utilicen este formato para
poder realizar una transformación del
formato original al formato que se pretende
realizar.

Otro formato gráfico más: esto enlaza con
la idea de caos que se puede percibir en el
mundo la informática gráfica, donde cada
nuevo programa tiene su propio formato.
6.- Bibliografía
CHARTE, F., CLAVIJO, J.A., DE ANTONIO, A.,
PASCUAL,
J.,
SEGARRA,
M.J.,
Programación avanzada en Windows 2000,
Ed. McGraw-Hill, 2000, Madrid.

Histograma:
permite
histograma de la imagen

Orientación Interna: permite ver, introducir
y modificar los parámetros de Orientación
Interna de la imagen.
GONZÁLEZ, R., WOODS, R., Digital image
processing, Addison-Wesley Publishing
Company, 1992. (Edición en castellano).

Orientación Externa: permite ver, introducir
y modificar los parámetros de Orientación
Externa de la imagen.
KRAUS, K., Photogrammetry, Ferd. Dümmlers
Verlag.Bonn.

Focal cámara: permite ver, introducir y
modificar la distancia focal de la cámara
con la que se ha obtenido la imagen.
visualizar
el
6.- Conclusiones
Las ventajas de un desarrollo de formato
propio de imagen para poder así manejar la
imagen se pueden determinar como las
siguientes:

Acceso a la imagen: en varias de sus
vertientes. Se puede decir acceso directo a
los píxeles que forman la imagen, a nivel
de posición (geométrico) y a nivel de
colores (radiométrico). También acceso a
su tamaño, a su resolución, etc...

Manejabilidad de la imagen: si se puede
mostrar en pantalla sólo partes de la
imagen, si se puede hacer roaming con
ella, etc..
Los inconvenientes de desarrollar un
formato propio de imagen se podría decir que son
los menos, pero no por ello carecen de
importancia:
Volumen I: Fundamentals an Standard
Processes, 4ª ed., 1993.
Volumen II: Advanced Methods and
Applications, 4ª ed., 1997.
RICHTER, J., Programación avanzada en
Windows, Ed. McGraw-Hill, 1997, Madrid.
WOLF, P. R., Elements of Photogrammetry, with
air photo interpretation and remote sensing,
2ª ed., McGraw-Hill, 1983, New York.
5.- LECTURA Y ESCRITURA DEL FORMATO
MAPA DE BITS JERÁRQUICO
5.1.- Introducción
En los capítulos anteriores se expusieron las razones de la conveniencia del
diseño de un nuevo formato propio de imagen, y además la forma de hacerlo. En este
capítulo se trata de explicar la implementación del código de lectura y escritura de dicho
formato. Por lo tanto, según se desprende de lo anterior, se puede subdividir el trabajo
en dos partes diferenciadas: la escritura por un lado, y la lectura por otro. Y además, el
orden lógico es así, primero la escritura del archivo según el nuevo formato diseñado, y
posteriormente la lectura del archivo escrito.
Aunque se explique por separado el desarrollo del código de escritura y de
lectura del formato Mapa de Bits Jerárquico, conceptualmente no son dos elementos
inseparables, dado que según sea la escritura del formato, así será su lectura, i.e., el
desarrollo real de ambos códigos se hizo de forma simultánea, y las distintas
alternativas de escritura que se explican más adelante, tienen su réplica en el código
homólogo correspondiente a la lectura del formato.
De los diferentes formatos de BMP que existen, sólo se trabaja con uno, y
además con una sola profundidad de color. El formato aceptado se corresponde con la
estructura de cabecera BITMAPINFOHEADER, y la profundidad de color es 8 bits, i.e,
256 colores. Se utilizan sólo estas especificaciones por varias razones:

La estructura de cabecera BITMAPINFOHEADER es la más utilizada,
estando garantizada su compatibilidad por Windows1.

La profundidad de color, 8 bits, es también ampliamente empleada.
Una profundidad de color menor, de 1 o de 4 bits, puede no satisfacer
las necesidades de niveles de gris, mientras que con una profundidad
mayor es excesivo el número de niveles de gris a emplear. Se puede
decir que con 256 valores de niveles de gris quedan satisfechas las
necesidades.
La profundidad de color de 8 bits implica el uso de una paleta, y el mapa de bits
contiene direcciones a dicha paleta, mientras que una profundidad de color superior
implica no utilizar una paleta, ya que ésta sería muy grande, y entonces es el propio
mapa de bits quien almacena los valores correspondientes a los colores. Luego utilizar
una profundidad de 8 bits nos garantiza el uso de una paleta, y este hecho es de una
importancia vital, al poder emplear esta paleta como una Look-Up Table (LUT).
1
N. del A.- Según las especificaciones del formato, no sucediendo lo mismo con la estructura de
cabecera precedente, BITMAPCOREHEADER.
79
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
5.2.- Escritura del formato Mapa de Bits Jerárquico
5.2.1.- Desarrollo del código de escritura
Para la escritura de archivos en el formato diseñado se ha creado un control en
Visual Basic, que se corresponde con la creación de una clase, a modo de objeto, que
recibe el nombre de Convierte. Esta clase, u objeto, tiene un miembro o función externa
principal, Carga, que es la única relación que existe entre el control, una vez compilado,
y el hipotético usuario. También existen otra serie de funciones y procedimientos, pero
éstos son de carácter interno y son de uso exclusivo del control Convierte, por lo que el
usuario de este control no tiene acceso a ellos2.
La función Carga de la clase Convierte tiene tan sólo un parámetro, y además de
carácter opcional. El funcionamiento externo de esta función es bastante sencillo y tan
sólo debe especificarse un parámetro, si se desea, o ninguno en otro caso. Este
parámetro simplemente se trata del nombre del archivo, directorio o camino (path)
inclusive, que se desea convertir al formato Mapa de Bits Jerárquico (BMJ).
Luego el código de escritura del formato de imagen Mapa de Bits Jerárquico
consta de las siguientes partes:
1) Gestión de archivos: tanto del archivo de entrada o lectura,
como del archivo de salida o escritura.
2) Lectura del archivo de mapa de bits que se desea transformar a
BMJ. En concreto se lee la cabecera de archivo y la cabecera
del mapa de bits.
3) Chequeo del tipo de mapa de bits, de la profundidad de color, y
de que no se trate de una imagen comprimida. Esto es así
porque sólo se trabaja con un determinado tipo de BMP, como
ya se ha explicado anteriormente.
4) Obtención de información de la memoria disponible del
ordenador, para actuar en consecuencia.
5) Escritura del archivo BMJ de forma simultánea a la lectura del
archivo BMP.
6) Visualización de un indicador del avance en la escritura del
archivo BMJ.
5.2.1.1.- Gestión de archivos
La función Carga de la clase Convierte admite un único parámetro, y además
tiene el carácter de opcional. Se trata del nombre del archivo con extensión BMP que se
desea transformar al formato BMJ. Entonces, si no se pasa el nombre del archivo a
convertir, aparece un cuadro de diálogo de tipo Abrir.
2
N. del A.- Es lo que se conoce como Encapsulamiento
80
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
En caso de pasar el nombre en la función Carga, este cuadro de diálogo no
aparece porque no es necesario. Sin embargo, independientemente de si el parámetro se
incluye o no en la función Carga, el cuadro de diálogo correspondiente al archivo a
guardar como BMJ siempre aparece.
5.2.1.2.- Lectura del archivo BMP
Esta parte del código está referida a la lectura de las cabeceras, tanto de archivo
como del mapa de bits, con varios objetivos:

Obtener la información necesaria para convertir después a formato de
imagen BMJ.

Poder efectuar el chequeo del tipo de mapa de bits.
La lectura del archivo se completa, una vez chequeado el tipo de mapa de bits,
con la lectura de los datos del mapa de bits propiamente dicho. Esta parte de la lectura
es simultánea a la escritura del formato BMJ.
5.2.1.3.- Chequeo del archivo BMP
Se efectúa el chequeo del formato de imagen para no seguir ejecutando el
programa en caso de que no se cumpla con las especificaciones requeridas, que son las
siguientes3:

Profundidad de color de 8 bits: se comprueba que el miembro
biBitCount de la estructura BITMAPINFOHEADER sea 8. En caso
de que sea distinto de 8 se detiene el programa.

Tipo de estructura de cabecera de bitmaps: se comprueba que
efectivamente sea la estructura BITMAPINFOHEADER. Para ello el
valor del miembro biSize de dicha estructura debe ser 40.

Tipo de compresión de la imagen: se comprueba que la imagen no
está en ningún formato de compresión, para lo que el miembro
biCompression de la estructura BITMAPINFOHEADER debe ser 0.
Si la imagen que se desea convertir a BMJ no cumple estas especificaciones, se
envía información al usuario, mediante mensajes en pantalla, del motivo por el cual no
se realiza la conversión.
Si la imagen a transformar al formato BMJ cumple las especificaciones
anteriores, el código sigue ejecutándose, siendo el siguiente paso la obtención de
información sobre la memoria disponible.
3
N. del A.- Se recomienda observar las definiciones de las estructuras en el capítulo 3.
81
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
5.2.1.4.- Obtención de información de la memoria disponible
La primera parte del archivo BMJ contiene el archivo en formato BMP original,
y la copia de éste sobre el primero se puede realizar de forma simple, byte a byte. Sin
embargo, si el archivo original contiene 1.000 bytes, e.g., se emplearán 1.000
operaciones de lectura y otras tantas de escritura, i.e., 2.000 operaciones de lectura y
escritura, que a su vez se pueden transformar a tiempo en función del ordenador con el
que se trabaje y en función del dispositivo del almacenamiento. Por lo tanto esto no es
muy práctico a efectos de rendimiento temporal del código. Para paliar este
inconveniente, se puede recurrir a funciones de la API de Windows que permitan
manejar bloques de memoria. Entonces el elemento de lectura/escritura pasa de ser 1
byte a ser un bloque de bytes, con lo que disminuye de forma considerable el número de
operaciones, y por tanto el tiempo empleado.
Los pasos básicos para copiar un bloque de memoria son4:
1.- Asignación de memoria de la pila global.
2.- Bloqueo de la memoria asignada. Una vez bloqueada la memoria, ésta
no se puede mover ni destruir, en cambio se puede copiar.
3.- Copia del contenido de la memoria bloqueada a otro bloque.
4.- Desbloqueo de la memoria bloqueada.
5.- Liberación del bloque de memoria.
Se puede considerar que el paso 4º es el inverso del 2º, que el paso 5º es el
inverso del 1º.
Es necesario salir de Visual Basic para poder realizar estas operaciones, puesto
que las funciones que aporta Visual Basic no permiten manejar bloques de memoria.
Por lo tanto, hay que realizar llamadas a funciones de la API que permiten hacer este
tipo de operaciones.
Con estas operaciones se reduce el tiempo de lectura/escritura del archivo
original sobre el archivo final. Pero cabe plantearse una pregunta, ¿qué ocurre cuando el
bloque de memoria excede de la memoria que Windows maneja?, que implica otra
cuestión, ¿se pueden realizar operaciones con grandes bloques de memoria?. La
respuesta a esta última pregunta es que afirmativa. En cuanto a la primera pregunta,
cuando se quiere asignar un bloque de memoria que excede la capacidad de Windows
para manejarlo, Windows se dedica a ampliar la memoria virtual y su archivo de
paginación, hasta que pueda asignar la memoria. Esto implica tiempo de espera mientras
que Windows reorganiza la memoria virtual, y además este tiempo de espera es
indeterminado, por lo tanto es un cabo suelto que conviene atar. Una posibilidad (hay
varias), es determinar cuánta memoria RAM hay disponible en el ordenador, y realizar
las operaciones de bloques de memoria con bloques de un tamaño inferior a dicha
memoria disponible. De esta forma se realizan varias operaciones de lectura/escritura en
vez de una sola, y el número de operaciones es pequeño si se compara con el número de
operaciones a realizar si el elemento de lectura/escritura se trata de 1 byte.
4
PRETOUTSOS, E., La Biblia de Visual Basic 5, pág. 620.
82
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
Para obtener información de la memoria de Windows y del ordenador es
necesario nuevamente realizar llamadas a la API. La información que se puede
conseguir es la siguiente:

Porcentaje de la memoria usada.

Número total de bytes de la memoria física.

Número de bytes disponibles de la memoria física.

Número total de bytes en el archivo de paginación.

Número de bytes disponibles en el archivo de paginación.

Número total de bytes virtuales.

Número de bytes virtuales libres.
En el código implementado se opta por utilizar la cuarta parte del número de
bytes libres de la memoria física, que es la memoria RAM que no está siendo utilizada.
No se utiliza toda la memoria disponible para dejar margen a Windows y para no agotar
la memoria física disponible.
También hay que añadir que las operaciones de lectura/escritura del contenido
de un bloque de memoria sobre un archivo no se pueden realizar con las funciones que
proporciona Visual Basic, y se hacen con llamadas a funciones de la API de Windows.
5.2.1.5.- Scan line
Antes de continuar con la explicación de la escritura de la pirámide de imágenes
es pertinente en este punto explicar el concepto scan line. El término scan line hace
referencia (pero no se corresponde) a los conceptos de línea y columna en una imagen
digital, y además está íntimamente relacionado con la profundidad de color de la
imagen, y con la manera característica de Windows de escribir y visualizar los mapas
de bits.
En el tercer capítulo de este trabajo se explicaron los tipos de archivos BMP y
las diferentes estructuras que pueden formar parte de ellos. Los mapas de bits están
escritos de forma secuencial, de tal forma que se recorre la imagen píxel a píxel, y no
hay bytes que marquen el final de una línea. Los mapas de bits están estructurados de la
siguiente forma (Línea, Columna):
(1,1)(1,2)...(1,n)(2,1)(2,2)...(2,n)...(m,1)(m,2)...(m,n)
Windows entiende los mapas de bits en scan lines de 4 bytes, con
independiencia del número de columnas reales que formen cada línea de la imagen. En
el caso de que el número de columnas de la imagen no sea un múltiplo de 4 bytes,
simplemente se rellena hasta completar los 4 bytes el resto de la scan line con valores
nulos (cero).
El concepto múltiplo de 4 bytes está relacionado con la profundidad de color,
i.e., con el número de bits empleado para representar el color de un píxel. Si se trata de
83
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
profundidad de color de 1, 4, u 8 bits, el mapa de bits contiene direcciones a la paleta. Si
se trata de 16, 24 o 32 bits, el mapa de bits realmente contiene el color del píxel. En el
caso de que se trate de 1 bit por color, una scan line, con 4 bytes, serán 32 bits, i.e., se
podrán representar en cada scan line 32 píxeles. Si una imagen tiene un ancho de 30
píxeles, el mapa de bits secuencial tendrá un aspecto similar al siguiente:
(1,1)...(1,30)(Nulo)(Nulo)(2,1)...(2,30)(Nulo)(Nulo)...(m,1)...
...(m,30)(Nulo)(Nulo)
donde cada paréntesis corresponde a 1 píxel, dado que en este caso se trata de
profundidad de color de 1 bit. Para aclarar más las cosas, la primera scan line es:
(1,1)...(1,30)(Nulo)(Nulo)
que corresponden a 32 bits o 4 bytes. Si el ancho de la imagen fuese mayor de 32
píxeles, e.g., 45 píxeles, con la profundidad de color de 1 bits son 2 scan lines, y se
corresponden con una sola línea de la imagen. En este ejemplo, la primera scan line está
completa, y la segunda sólo los 13 primeros bits (o píxeles en este caso) tienen valores,
i.e, 32 + 13 = 45. Sin embargo, debido a la configuración scan line, se completan hasta
2*32 = 64 píxeles (o bits en este caso) con 19 valores nulos, i.e., 64 - 45 = 19. A
continuación se expresa la primera línea de esta imagen de 45 píxeles de ancho:
(1,1)...(1,32)(1,33)...(1,45)(Nulo)...(Nulo)
donde la primera scan line es:
(1,1)...(1,32)
y la segunda scan line es
(1,33)...(1,45)(Nulo)...(Nulo)
Se puede decir que cada línea de la imagen original está representada en el mapa
de bits bien por una scan line, si al ancho en píxeles es igual o menor de 32, o bien por
un grupo de scan lines, si dicho ancho supera los 32 píxeles. Lógicamente, con cada
profundidad de color varía el número de scan lines para representar una línea con el
mismo número de píxeles, por lo que se debe hacer un estudio para cada tipo de
profundidad de bits si se quiere acabar de entender este concepto. Por ejemplo, si se
toman los mismos ejemplos pero con una profundidad de color de 8 bits. Cada píxel
necesita de 1 byte de la scan line para poder ser representado, luego cada scan line
recoge con esta profundidad de color 4 píxeles. En caso de que el ancho dela imagen no
sea múltiplo de 4 bytes, se completa la última scan line con valores nulos. Hay, por
84
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
tanto, tantas últimas scan line como líneas reales de la imagen. Una imagen con 30
píxeles de lado tiene un aspecto semejante al que se muestra a continuación:
(1,1)...(1,4)(1,5)...(1,8)(1,9)...(1,12)(1,13)...(1,16)(1,17)...
...(1,20)(1,21)...(1,24)(1,25)...(1,28)(1,29)(1,30)(Nulo)(Nulo)
donde cada píxel corresponde con cada paréntesis. La primera scan line es:
(1,1)...(1,4)
y la segunda scan line es:
(1,5)...(1,8)
y la última scan line:
(1,29)(1,30)(Nulo)(Nulo)
Si se trata de una imagen con 45 píxeles de ancho, una línea de la imagen
original es así:
(1,1)...(1,4)(1,5)...(1,8)(1,9)...(1,12)(1,13)...(1,16)(1,17)...
...(1,20)(1,21)...(1,24)(1,25)...(1,28)(1,29)...(1,32)(1,33)....
...(1,36)(1,37)...(1,40)(1,41)...(1,44)(1,45)(Nulo)(Nulo)(Nulo)
i.e., se necesitan 3 valores nulos para completar los 45 píxeles reales. La última scan
line es:
(1,45)(Nulo)(Nulo)(Nulo)
En el caso de una profundidad de color de 32 bits cada píxel necesita de los 4
bytes de la scan line, luego en este caso el número de scan line corresponderá
directamente con el número de columnas de la imagen original, i.e., con el ancho en
píxeles de la imagen, y por lo tanto no es necesario rellenar con valores nulos para
completar cada scan line.
Se ha explicado la forma secuencial de los mapas de bits para los de tipo topdown, y aunque éstos no son los más abundantes, si son los más intuitivos para explicar
mejor el concepto de scan line. Los mapas de bits de tipo bottom-up difieren de estos en
que invierten el orden de las filas de la imagen original, no así el de las columnas.
Luego, el último ejemplo expuesto, con 45 píxeles de ancho y una profundidad de 8
bits, si se considera ahora que es del tipo bottom-up, la primera scan line que aparece
para representar a la última fila, fila m, de la imagen original tiene el siguiente aspecto:
85
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
(m,1)...(m,4)(m,5)...(m,8)(m,9)...(m,12)(m,13)...(m,16)(m,17)...
...(m,20)(m,21)...(m,24)(m,25)...(m,28)(m,29)...(m,32)(m,33)....
...(m,36)(m,37)...(m,40)(m,41)...(m,44)(m,45)(Nulo)(Nulo)(Nulo)
No hay más diferencias con respecto a los mapas de bits de tipo top-down que
esta inversión de las filas, no afectando a las columnas este cambio en el orden de las
filas.
De lo arriba explicado se deduce que un mapa de bits no tendrá el mismo
número de scan lines que líneas en la imagen original, y que además el número de scan
lines depende de dos factores:

Profundidad de color, i.e., número de bits necesarios para representar
el color de un píxel.

Número de columnas de la imagen original.
El número de bytes está directamente relacionado con el número de scan lines,
dado que cada una de estas contiene invariablemente 4 bytes. Luego la relación entre el
número de bytes necesario para representar una línea de la imagen y el número de scan
lines es directa:
nBytes = nScanLine * 4
A efectos de implementación, el mapa de bits a visualizar se almacena en una
matriz unidimensional de bytes, lo que concuerda con la naturaleza secuencial del mapa
de bits en el archivo de imagen, y concuerda también con las especificaciones de
Windows para mostrar una imagen en un dispositivo de visualización. El tamaño de esta
matriz está en función del número de scan lines necesarias para representar cada línea
de la imagen, y del número de líneas de la imagen. Se puede decir que el número de
scan lines se corresponde con el número de las columnas de la imagen original, siendo
redondeado por exceso a múltiplos de 4. Sin embargo, la matriz unidimensional que
contiene los datos del mapa de bits está formada por bytes, no por scan lines, luego hay
que tener en cuenta el número de bytes necesario para representar una línea de la
imagen original.
A continuación se lista el código que permite calcular el número de bytes para
representar una línea de la imagen, nBytes, que corresponde a una scan line, en el caso
de profundidad de color de 8 bits,
B = AnchoImagen * 8
If (B Mod 32) <> 0 Then
nBytes = ((B + 32 - (B Mod 32)) / 8)
Else
nBytes = B / 8
86
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
End If
donde Mod es un operador matemático que devuelve el resto de la división entre B y 32.
La siguiente expresión:
nBytes = ((B + 32 - (B Mod 32)) / 8)
corresponde con el redondeo por exceso a múltiplos de 4 del número de columnas de la
imagen original, o si se prefiere, del ancho en píxeles de la imagen original. Así, la
matriz unidimensional se redimensiona en función a nBytes y al número de filas de la
imagen original (alto en píxeles):
Redimensiona MatrizImagen(nBytes * AltoImagen)
Si en uno de los ejemplos anteriores, profundidad de color 8 bits y un ancho de
imagen de 30 píxeles, se asignan 25 píxeles de alto. Las operaciones a realizar para
redimensionar la matriz unidimensional que va a contener los datos del mapa de bits
son:
B = 30 * 8 = 240
B Mod 32 = 16
como se puede ver el resto es distinto de cero, luego se aplica la siguiente fórmula:
nBytes = ((B + 32 - (B Mod 32)) / 8)
nBytes = ((240 + 32 - (16)) / 8) = 32
Luego hacen falta 32 bytes para representar cada línea de 30 píxeles de la
imagen, y como la imagen tiene 25 filas:
32 * 25 = 800
Redimensiona MatrizImagen(800)
En el caso de 45 píxeles de ancho, 25 filas de alto y profundidad de 8 bits:
B = 45 * 8 = 360
B Mod 32 = 8
87
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
nBytes = ((B + 32 - (B Mod 32)) / 8)
nBytes = ((360 + 32 - (8)) / 8) = 48
48 * 25 = 1200
Redimensiona MatrizImagen(1200)
Esto se corresponde con lo anteriormente explicado, 48 bytes por cada línea.
Para saber el número de scan lines necesario para representar una línea, simplemente se
trata de dividir entre 4 del número de bytes calculado para representar una línea.
El número preciso de scan lines, o de bytes si se prefiere, para representar una
línea varía también en función del número de bits necesario para representar el color de
un píxel. En el caso de profundidad de color de 1 bit, el cálculo de bytes se realiza de
manera semejante a la anterior:
B = AnchoImagen * 1
If (B Mod 32) <> 0 Then
nBytes = ((B + 32 - (B Mod 32)) / 8)
Else
nBytes = B / 8
End If
Luego, en los ejemplos anteriores, harán falta 4 bytes para almacenar una línea a
de 30 píxeles, y 8 bytes para representar un línea de 45 píxeles de ancho, si la
profundidad de color es de 1 bit.
En el caso de profundidad de color de 32 bits:
B = AnchoImagen * 32
If (B Mod 32) <> 0 Then
nBytes = ((B + 32 - (B Mod 32)) / 8)
Else
nBytes = B / 8
End If
por lo que la línea de 30 píxeles necesita de 120 bytes para ser almacenada. El código
generalizado para cualquier profundidad de color, nBitColor, es:
B = AnchoImagen * nBitColor
If (B Mod 32) <> 0 Then
nBytes = ((B + 32 - (B Mod 32)) / 8)
88
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
Else
nBytes = B / 8
End If
Este código simplemente relaciona dos unidades, bits y bytes, con el redondeo
por exceso a 4 bytes, o a 32 bits. La unidad bit está implicada por ser la unidad
empleada para representar la profundidad de color. La unidad byte está implicada por
ser la unidad con que se redimensiona la matriz unidimensional que contiene los datos
del mapa de bits. Y el redondeo a 32 bits, o a 4 bytes, está ligado a el concepto de scan
line. El código anterior, si se quiere, también está relacionando el ancho de la imagen,
en píxeles, y con la profundidad de color, en bits, para obtener el número de bytes
necesario para almacenar una línea.
Como se puede comprobar, comprender el concepto de scan line es esencial para
manejar imágenes con Windows. En base a lo explicado se fundamenta la
implementación del código tanto en el control Convierte, que se encarga de la
transformación al formato BMJ, como en el control Imagen, que se encarga de la
visualización de la pirámide de imágenes contenida en un archivo con formato Mapa de
Bits Jerárquico.
5.2.1.6.- Escritura del archivo BMJ
En este apartado se detalla la escritura del archivo BMJ, y como ya se ha
comentado, está relacionada con la lectura previa a la visualización que realiza el
control Imagen, que se describe en este mismo capítulo más adelante.
Para retomar el hilo de la explicación de la escritura del archivo BMJ es
conveniente recordar la estructura en forma secuencial del archivo BMJ: en primer lugar
se encuentra la imagen original, después la estructura de datos BMJ, a continuación la
pirámide de imágenes (de mayor a menor tamaño), después la imagen Índice, y por
último, datos relativos a las Orientaciones Interna y Externa de la imagen.
La escritura en el archivo BMJ de la imagen original está ya explicada en el
apartado 5.2.1.4. La siguiente parte del archivo BMJ es la estructura BMJ:
Public Type BitMapJerarquico '6 + nBitmap*4 bytes
Tipo As Byte '1 byte
nBitmap As Byte '1 byte
posBitmap() As Long 'nBitmap*4 bytes
posDatos As Long '4 bytes
End Type
89
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
A los elementos de la estructura BMJ se le asignan los siguientes valores antes
de su escritura en el archivo:

Tipo: este elemento vale 1 para archivos BMJ que contienen imágenes
en formato de mapa de bits.

nBitmap: indica el número de imágenes que hay en la pirámide de
imágenes. Esto se calcula en función del tamaño de la imagen Índice.
Cada imagen en la pirámide será la cuarta parte de la inmediatamente
anterior, porque se divide entre dos el ancho y el alto de la imagen. Esta
sucesión termina cuando la división por dos del ancho o alto sea inferior
al ancho o alto predeterminado, respectivamente, de la imagen Índice.

posBitmap(i): indica la posición dentro del archivo BMJ donde
comienza la imagen i-ésima. Esto no se calcula, sino que se trata del byte
en el que se comienza a escribir la imagen i-ésima. La razón por la que se
anota es para permitir al código de visualización (correspondiente al
control Imagen), subir y bajar en la pirámide de imágenes.

posDatos: posición dentro del archivo de formato BMJ donde se
encuentran los datos de Orientación Interna y Orientación Externa.
Tampoco se calcula, pero se anota para poder acceder a estos datos con
el control Imagen. Se trata del byte hacia el que apunta la función de
escritura cuando se terminan de escribir las imágenes.
La siguiente parte dentro del archivo BMJ es la pirámide de imágenes.
Básicamente se trata de ir dividiendo sucesivamente el ancho y el alto de la imagen
original entre potencias de dos, lo que hace que una imagen de la pirámide sea 4 veces
menor que la anterior. Esta iteración de divisiones se realiza hasta que se llega a un
límite, determinado por el tamaño de la imagen Índice. De esta forma la imagen Índice
corresponde a la imagen más pequeña de la pirámide. La manera de realizar esta
pirámide de imágenes se relata en el siguiente apartado (5.2.1.7.).
Una vez finalizada la creación y escritura de las imágenes de la pirámide, se crea
y escribe la imagen Índice. Se crean las cabeceras y el mapa de bits correspondiente, y
se escriben el archivo BMJ.
Y por último, una vez escritas todas las imágenes, se aprovecha que el byte de
escritura del archivo BMJ queda señalando el último byte escrito, para escribir dicho
byte en el archivo BMJ, que corresponde a la posición de los datos de la Orientación
Interna.
90
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
5.2.1.7.- Creación de la pirámide de imágenes
La creación de la pirámide de imágenes puede explicarse por medio de la
escritura de una de sus imágenes, dado que la única diferencia entre las imágenes que
conforman la pirámide es su tamaño, y no el modo en que es creada. Por lo tanto, en lo
siguiente se supone la división n-ésima de la imagen original, y la imagen a considerar
es la imagen n-ésima.
El almacenamiento de la imagen n-ésima en al archivo BMJ se hace de forma
completa, i.e., como si fuera un archivo en formato BMP incrustado en una determinada
posición del archivo BMJ. Por lo tanto, se escriben la estructura BITMAPFILEHEADER
(cabecera del archivo), la estructura BITMAPINFO y el mapa de bits. La estructura
BITMAPINFO contiene a la estructura BITMAPINFOHEADER (cabecera del mapa de bits)
y a la matriz de estructuras RGBQUAD (paleta de colores).
La cabecera del archivo no es indispensable para la posterior lectura con fines de
visualización, por lo que se podría obviar su inclusión el archivo BMJ. Sin embargo,
dado que la estructura que la contiene no es grande, 14 bytes, y posibilita la extracción
de la imagen n-ésima a un archivo independiente en formato BMP, se escriben en el
archivo BMJ.
Los pasos básicos a considerar para la escritura de la imagen n-ésima en un
archivo BMJ son:
1.- Cálculo del ancho y del alto de la imagen n-ésima.
2.- Cálculo del número de bytes necesario para representar una línea de la
imagen n-ésima.
3.- Cálculo y asignación de valores a los miembros de las distintas
estructuras.
4.- Escritura de las estructuras de la imagen n-ésima en el archivo BMJ.
5.- Lectura del archivo original y escritura en el archivo BMJ del mapa
de bits de la imagen n-ésima.
La imagen n-ésima tiene definido su alto y su ancho por la división del ancho y
del alto, respectivamente, de la imagen original por la n-ésima potencia de 2. Este
cálculo es el primer paso.
Después, en el segundo paso, se calcula el número de bytes necesario para
representar una línea de la imagen n-ésima, del modo en que se explica arriba, en el
apartado 5.2.1.6. Para ello es necesario el ancho de la imagen n-ésima, determinado en
el primer paso.
En el tercer paso, se calculan y asignan valores a las estructuras tanto de
cabecera de archivo, como de cabecera del mapa de bits. Los cálculos son:

Cálculo del tamaño del mapa de bits en bytes: se realiza como se ha
explicado en el apartado 5.2.1.6, mediante el producto del número de
bytes necesario para representar una línea de la imagen n-ésima
(calculado en el paso anterior) por el número de líneas de la imagen
n-ésima.
91
BENJAMÍN ARIAS PÉREZ

FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
Cálculo del tamaño de archivo: mediante la suma del miembro
bfOffBits de la estructura BITMAPFILEHEADER más el tamaño
del mapa de bits en bytes. El miembro bfOffBits especifica la
distancia, en bytes, desde la estructura BITMAPFILEHEADER hasta
los bits del mapa de bits. Esto es, desde el byte inicial del archivo
hasta el comienzo del mapa de bits. Este miembro, bfOffBits, no
es necesario calcularlo porque es el mismo que el del fichero original,
y el mismo que el de todas las imágenes, al ser todas del mismo tipo.
La estructura BITMAPFILEHEADER, que corresponde a la cabecera de archivo,
es la siguiente:
Type BITMAPFILEHEADER
bfType As Integer
bfSize As Long
bfReserved1 As Integer
bfReserved2 As Integer
bfOffBits As Long
End Type
A los miembros de esta estructura se les asignan los siguientes valores:
 bfType: Son los caracteres BM.
 bfSize: es la suma del miembro bfOffBits
de la estructura
BITMAPFILEHEADER más el tamaño del mapa de bits en bytes..

bfReserved1: reservado, debe ser cero.

bfReserved2: reservado, debe ser cero.

bfOffBits: no se calcula, y se asigna el mismo que el del archivo
original.
La estructura BITMAPINFOHEADER también es necesario rellenarla:
Type BITMAPINFOHEADER
biSize As Long
biWidth As Long
biHeight As Long
biPlanes As Integer
biBitCount As Integer
biCompression As Long
biSizeImage As Long
biXPelsPerMeter As Long
biYPelsPerMeter As Long
92
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
biClrUsed As Long
biClrImportant As Long
End Type
Los miembros de la estructura BITMAPINFOHEADER toman los siguientes
valores:
 biSize: es el tamaño de la estructura BITMAPINFOHEADER. Son 40
bytes
 biWidth: es el ancho de la imagen n-ésima.

biHeight: es el alto de la imagen n-ésima.

biPlanes: siempre es 1.

biBitCount: la profundidad de color es la misma que la de la imagen

original. En el caso de las imágenes que se tratan en este trabajo su
valor es siempre 8.
biCompression: ningún tipo de compresión, por lo tanto siempre es 0.

biSizeImage: tamaño del mapa de bits en bytes.

biXPelsPerMeter: resolución en X de la imagen n-ésima, dado que no

se cambia el tamaño del píxel, sino el número de píxeles, es la misma
resolución que la de la imagen original.
biYPelsPerMeter: ídem.
biClrUsed: este valor siempre es cero.
biClrImportant: este valor siempre es cero.


La paleta de colores, considerada como la matriz de estructuras RGBQUAD, es
simplemente una copia de la paleta de la imagen original, porque no se trata de una
modificación de colores, sino de una selección de píxeles.
El cuarto paso consiste en la escritura en el archivo BMJ de las estructuras
anteriores con los valores ya reseñados.
El quinto paso es la selección de píxeles de la imagen original para formar la
imagen más pequeña. Dado que se estudiaron varias alternativas para ello, en función de
tiempo y número de operaciones, se explican en el siguiente apartado.
5.2.2.- Alternativas
Para la selección de píxeles se estudiaron varias alternativas para el ahorro de
tiempo, o si se quiere, para la disminución del número de operaciones a realizar. En
concreto 3 alternativas, que son las siguientes:
1ª.- Lectura del valor del píxel en la imagen original, uno a uno, y
escritura de dichos valores en el mapa de bits correspondiente a la
imagen n-ésima.
93
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
2ª.- Lectura de ciertas filas completas de la imagen original, escritura de
dichas líneas en el mapa de bits correspondiente a la imagen n-ésima. Se
completa esta técnica con la visualización escalada de estas líneas.
3ª.- Lectura de ciertas filas completas de la imagen original, y escritura
seleccionada de píxeles en el mapa de bits correspondiente a la imagen
n-ésima.
La primera alternativa es la opción intuitiva, la más lógica. Se trata simplemente
de ir leyendo en el mapa de bits de la imagen original los valores de los píxeles que se
deseen, y su posterior escritura en el mapa de bits de la imagen n-ésima. La cadencia en
la lectura viene determinada por la división n-ésima de la imagen original. De esta
forma, la primera imagen se genera con uno de cada dos píxeles de la imagen original
en la primera línea, luego salta a la 3ª línea para coger 1 de cada dos líneas, etc...De
forma generalizada, se toma un píxel de cada 2n píxeles de la imagen original en el
n-esimo escalón de la pirámide. A continuación se muestra un ejemplo de forma gráfica.
Se supone la imagen original formada por 16x16 píxeles, i.e, 256 píxeles.
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
La primera imagen de la pirámide se forma tomando un píxel de cada dos, que
como se puede ver en la imagen superior, son los píxeles marcados con X. La imagen
así formada es:
X X X X X X X X
X X X X X X X X
X X X X X X X X
X X X X X X X X
X X X X X X X X
X X X X X X X X
X X X X X X X X
X X X X X X X X
94
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
que tiene 8x8 píxeles, i.e, 64 píxeles. La siguiente imagen de la pirámide se forma a
partir de la original tomando un píxel de cada 4:
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
luego la imagen resultante es:
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
con 4x4 píxeles, i.e., 16 píxeles.
Esta primera alternativa implica muchas operaciones de lectura y escritura, lo
que a su vez implica mucho tiempo. Por ello se piensa en otras alternativas, que se
explican más adelante.
En la página siguiente se muestra una pirámide imágenes así obtenida. La
imagen inferior es la imagen original, la imagen situada en el medio es la primera
imagen generada por la primera división de la original, y la superior es la imagen
generada por la segunda división de la original.
95
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
La estrategia de la segunda alternativa es leer una fila completa de la imagen
original en cada operación de lectura, y escribir dicha fila en una sola operación de
escritura en el mapa de bits de la imagen n-ésima. Se leen sólo cada 2n filas para la
generación de la imagen n-ésima. Con el mismo ejemplo anterior, como se puede ver en
la siguiente figura:
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
96
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
una imagen de 16x16 píxeles, de la que se leen las filas completas, sólo una de cada dos
filas. Las filas se leen cada una en una sola operación de lectura, y se escriben en una
sola operación de escritura. Entonces la primera imagen que se almacena es:
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
La segunda imagen se genera con una de cada 4 filas de la imagen original:
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
La segunda imagen generada es:
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
97
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
Para visualizar las imágenes así generadas se requiere el escalado de las
imágenes dentro del código de visualización (control Imagen). A continuación se
muestra una pirámide de imágenes así generada:
El rendimiento de esta segunda alternativa es realmente espectacular, en
términos tanto de número de operaciones como de tiempo necesario para convertir una
imagen en formato de mapa de bits a una imagen en formato de Mapa de Bits
Jerárquico. Sin embargo, no es la alternativa más adecuada debido al efecto de huecos,
píxeles negros, que se produce al escalar la imagen para su visualización. En la imagen
situada en la página siguiente se puede comprobar este efecto. La imagen situada a la
izquierda es la imagen n-ésima generada mediante la primera alternativa, y la imagen
situada a la derecha es la imagen n-ésima generada por la segunda alternativa.
Debido a este inconveniente se rechaza esta alternativa, que aunque resulta
bastante llamativa por el reducido coste temporal, no ofrece garantías en la
visualización debido a la función de escalado.
98
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
Por lo tanto, se piensa en una tercera alternativa, que en realidad es un híbrido de
las otras dos alternativas, tomando de cada una ellas sus aspectos más positivos. En
primer lugar, se lee cada fila completa en una sola operación de lectura y queda cargada
en una matriz. Esto ofrece ya una cierta mejora respecto a la primera alternativa al
ahorrar operaciones de lectura. La escritura en el archivo no se realiza con la fila
completa, sino un píxel de cada 2n píxeles. No se reduce el número de operaciones de
escritura, pero se consigue una ventaja, porque se obtienen las imágenes piramidales
correctas y no es necesario un escalado posterior de la imagen.
Con el ejemplo con el que se han explicado las otras dos alternativas, se explica
a continuación esta tercera y definitiva. En primer lugar, se parte de la imagen original,
y para crear la primera imagen de la pirámide se leen una de cada dos filas de forma
completa:
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X
99
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
de cada fila leída, y que está cargada en una matriz unidimensional, se seleccionan uno
de cada dos píxeles para escribirlo en el mapa de bits de la primera imagen original.
X
X
X
X
X
X
X
X
y la imagen generada es la siguiente, de 8x8 píxeles:
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
la siguiente imagen piramidal se hace leyendo cada fila de forma completa, una de cada
4 filas, y para la escritura se selecciona uno de cada 4 píxeles:
X
X
X
X
con lo que la imagen resultante es de 4x4 píxeles:
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
Esta es la alternativa implementada en el control Convierte.
A continuación se muestra un estudio de los tiempos y tamaños obtenidos para
poder hacer una comparación entre alternativas. Los tiempos indicados corresponden a
un ordenador Pentium a 166 MHz, con 64 MB de RAM.
100
DESARROLLO DE SOFTWARE.
Nombre Imagen
Edif.bmp
tetraedro_d.bmp
Fot209.bmp
Catast188208.bmp
LECTURA Y ESCRITURA DEL FORMATO PROPIO
1ª Alternativa
(segundos)
17
44
162
2ª Alternativa
(segundos)
3ª Alternativa
(segundos)
0,6
1,5
6
7
18
64
1.875
136
723
%(1ª/2ª)
%(1ª/3ª)
2.733,33
2.833,33
142,86
144,44
2.600,00
1.278,68
153,13
159,34
Como se puede observar hay un gran rendimiento temporal con la segunda
alternativa. Aún cuando con la imagen más grande de todas, Catast188208.bmp, baja el
rendimiento, sigue siendo una mejora espectacular. Con la alternativa definitiva, la
tercera, se mejora el tiempo en buena medida, en más de la mitad, e incluso la relación
mejora con el tamaño de la imagen. Los tamaños van de menor tamaño, Edif.bmp, a
mayor tamaño, Catast188208.bmp. El siguiente gráfico ayuda a ver las diferencias
temporales:
2000
1800
Tiempo en segundos
1600
1400
1200
1ª Alternativa
1000
2ª Alternativa
800
3ª Alternativa
600
400
200
0
1
2
3
4
donde los números se corresponden con la numeración de la siguiente forma:

Edif.bmp: se corresponde con 1.

tetraedro_d.bmp: se corresponde con 2.

Foto209.bmp: se corresponde con 3.

Catast188208.bmp: se corresponde con 4.
101
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
Una vez elegida la alternativa, se hizo un estudio de tamaños entre las imágenes
originales y los archivos BMJ obtenidos. Los datos se muestran en la siguiente tabla:
Nombre Imagen
Tamaño Imagen
BMP (Bytes)
Tamaño Imagen
BMJ (Bytes)
Edif.bmp
tetraedro_d.bmp
Fot209.bmp
Catast188208.bmp
692.224
2.109.440
7.114.752
108.234.752
958.464
% Aumento
2.801.664
9.486.336
144.324.608
Como se puede observar la relación de aumento entre ambos archivos se
mantiene en torno a un 33-34%, excepto el primero que es de 38.46%.
160.000.000
Bytes Almacenamiento
140.000.000
120.000.000
100.000.000
80.000.000
60.000.000
Tamaño BMP
Tamaño BMJ
40.000.000
20.000.000
0
1
2
3
4
5.2.3.- Gestión de errores
La función Carga, para el chequeo de errores que se puedan producir, incorpora
como respuesta a la llamada a la función una variable booleana, de tal forma que en
caso de que se produzca algún error interno la variable tenga el valor Falso, y en caso
contrario, i.e., en caso de que no se produzca ningún error tenga el valor Verdadero.
Esto es conveniente hacerlo así para que el programa que llame a la función Carga del
102
38,46
32,82
33,33
33,34
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
objeto Convierte sepa si se ha producido algún error durante la ejecución de dicha
llamada, y por lo tanto, de esta manera sea fácil implementar el código correspondiente
al producirse un error en la llamada a un procedimiento externo.
La gestión de los errores es, por tanto, una cuestión intrínseca al objeto
Convierte. Cuando se genera un error, el procedimiento que se sigue es el siguiente:
1. Se aborta la ejecución de la función Carga, generándose un código de
error.
2. En base a dicho código de error se envía un mensaje al usuario para
comunicarle la causa del error.
3. Se asigna Falso a la variable de repuesta de la función Carga.
4. Finaliza la ejecución de la función Carga.
5.3.- Lectura del formato Mapa de Bits Jerárquico
5.3.1.- Desarrollo del código de lectura
El código de lectura se implementa sobre un Control ActiveX, en forma de
Control de Usuario, y que recibe el nombre de Imagen. Por lo tanto, las características
iniciales son similares al control Convierte, que es el control generado para la escritura
del formato BMJ. Sin embargo, el control Imagen presenta más propiedades, métodos y
elementos que el control Convierte.
5.3.1.1.- Gestión de archivos
La función del control Imagen para visualizar los archivos BMJ recibe el
nombre de Carga, y se puede llamar de forma externa al control Imagen. Esta función
puede recibir dos parámetros:

nImagen: un número identificador de la imagen. Se define como de
tipo Byte, luego su rango de valores se encuentra en 0 y 255 en
números enteros.

NombreArchivo: el nombre del archivo que contiene el archivo BMJ.
Este parámetro es opcional, por lo que su omisión ocasiona que
aparezca una ventana para que el usuario elija el archivo BMJ que
desea visualizar.
El parámetro nImagen es el que sirve de identificador en todo el código, por lo
tanto es un elemento indispensable dentro del código del control Imagen. Tanto es así,
que todas las propiedades, métodos y elementos hacen referencia a este identificador,
por lo que la primera operación en aquellos es obtener el identificador de la imagen.
103
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
El parámetro NombreArchivo es el nombre del archivo más la ruta de
directorios donde se encuentra. Es opcional, por lo que su omisión genera la
presentación en pantalla de una ventana a modo de cuadro de diálogo, donde el usuario
puede elegir el archivo de tipo Mapa de Bits Jerárquico que desea abrir. En el caso de
que el usuario pulse Cancelar en esta ventana, se muestra un mensaje indicando que el
usuario ha decidido Cancelar la acción, por lo que no se abrirá ninguna imagen.
La función Carga del control Imagen devuelve una variable boolena, y de esta
forma se puede controlar si se ha producido un error en la visualización de la imagen. Si
la función devuelve Verdadero se considera que no se producido ningún error en la
visualización de la imagen. En cambio, si la función, una vez ejecutada, devuelve Falso,
significa que ha habido algún error en la visualización.
5.3.1.2.- Lectura del archivo BMJ
Se pueden distinguir dos tipos de lecturas del archivo BMJ, dependiendo de
cuándo se realice la lectura:

Lectura inicial: se realiza mediante la función Carga, de forma
externa al Control Imagen. Esta lectura está gestionada por dicha
función.

Lectura en tiempo de ejecución: se realiza de forma interna desde el
Control Convierte, en respuesta a los eventos que pueda generar el
usuario. Esta lectura no está gestionada por la función Carga.
La lectura inicial, gestionada por la función Carga, sigue los siguientes pasos:
1.- Lectura de la cabecera del archivo y de la cabecera del mapa de bits.
2.- Redimensionamiento de una matriz que almacenará los valores del
mapa de bits que se van a cargar.
3.- Almacenamiento del mapa de bits en la matriz redimensionada.
4.- Preparación de las estructuras adecuadas para que puedan ser
admitidas por las funciones de visualización.
5.- Envío de las estructuras a las funciones de visualización.
Como ya se ha explicado anteriormente, el formato de archivo BMJ se
fundamenta en el formato BMP, y éste está pensado para facilitar su visualización en
Windows. Es por ello que las funciones que se utilizan para mostrar una imagen en
pantalla admiten como parámetros las estructuras que existen dentro del formato BMP,
éstas están contenidas en el archivo del formato BMP, y también están contenidas
dentro del archivo de extensión BMJ.
Por lo tanto en la lectura de un archivo BMJ se pueden hablar de dos tipos de
acciones, independientemente de si se trata de la lectura inicial o de la lectura en tiempo
de ejecución:
104
DESARROLLO DE SOFTWARE.


LECTURA Y ESCRITURA DEL FORMATO PROPIO
Lectura propiamente dicha: se leen los valores correspondientes al
formato y se almacenan en las variables adecuadas.
Preparación de la visualización: para ello se cargan los valores
necesarios en variables que sean acordes con:
- las estructuras admitidas por las funciones de visualización
- la parte de la imagen que se desea mostrar
- la imagen que corresponda dentro de la pirámide de
imágenes.
Las estructuras adecuadas para la visualización son la cabecera de mapa de bits
(BITMAPINFOHEADER), la paleta de colores(RGBQUAD), y el mapa de bits propiamente
dicho.
5.3.1.3- Pasos para mostrar un mapa de bits
Los pasos que hay que realizar para poder presentar un mapa de bits en pantalla,
o si se quiere en cualquier otro dispositivo son los siguientes5:
1.- Obtención del bitmap
2.- Creación de un contexto de dispositivo de memoria
3.- Selección del bitmap en este último contexto de dispositivo.
4.- Copia desde dicho contexto al dispositivo que se vaya a utilizar para
su visualización.
5.- Eliminación del contexto de dispositivo creado en el paso 2.
6.- Eliminación del mapa de bits cuando se hay finalizado con él.
5.3.1.4.- Visualización de una parte de la imagen
La zona de la imagen que se desea mostrar está determinada en todo momento
por las acciones que el usuario considere conveniente realizar, por lo que la navegación
por la imagen es controlada por el usuario. La zona de la imagen que se desea mostrar
corresponde entonces con:

las coordenadas X, Y, del origen de la zona de la imagen.

el ancho y el alto de la ventana de visualización.

la imagen correspondiente dentro de la pirámide de imágenes.

el identificador de la imagen BMJ.
5
CHARTE, F., et al., Programación avanzada en Windows 2000, pág. 198.
105
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
Estos valores son los que se determinan que zona de la imagen se visualiza, y
estos valores son determinados en todo momento.
Conviene recordar la estructura del archivo BMJ, que por este orden tiene los
siguientes elementos:

Imagen Original.

Estructura BMJ.

Pirámide de imágenes.

Imagen Índice.

Datos relativos a Orientaciones.
También conviene recordar la forma de un archivo BMP, que por orden está
formado por:

Cabecera de archivo.

Cabecera del mapa de bits.

Paleta de colores.

Mapa de bits.
Por último recordar los conceptos de scan line, número de bytes necesario para
representar una línea, e ídem del mapa de bits completo. Y sobre todo, es interesante
refrescar ahora la estructura secuencial del mapa de bits.
La pregunta ahora es: ¿cómo se visualiza una zona de la imagen? La respuesta a
esta cuestión es la pieza clave dentro de este trabajo. La visualización de una zona de la
imagen tiene varios pasos, y éstos son los siguientes:
1.- En primer lugar, mediante el identificador de la imagen BMJ, se
accede a la imagen BMJ correspondiente de las varias que es posible
tener abiertas.
2.- Después, se accede a la imagen n-ésima dentro de la pirámide.
3.- Con las coordenadas X, Y del origen de la zona de la imagen, se
accede dentro del mapa de bits correspondiente al primer píxel.
4.- Mediante el ancho y el alto de la zona a visualizar se leen los valores
del mapa de bits correspondientes.
5.- Se siguen los pasos necesarios para mostrar un mapa de bits en
pantalla.
Los tres primeros pasos son de gestión de archivos. El primer paso se trata de
saber sobre que archivo hay que efectuar la lectura. El segundo y tercer paso, acceden al
primer píxel a visualizar, teniendo para ello en cuenta los valores almacenados en la
estructura BMJ y las coordenadas del origen.
106
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
La estructura BMJ es:
Public Type BitMapJerarquico '6 + nBitmap*4 bytes
Tipo As Byte '1 byte
nBitmap As Byte '1 byte
posBitmap() As Long 'nBitmap*4 bytes
posDatos As Long '4 bytes
End Type
El miembro posBitmap(n) permite, dentro del archivo BMJ, acceder a la
posición de inicio de la imagen n-ésima. A continuación se accede al inicio del mapa de
bits de dicha imagen mediante el miembro bfOffBits de la estructura
BITMAPFILEHEADER.
La siguiente acción será acceder a la píxel de inicio de la zona a visualizar
mediante las coordenadas del origen. Sin embargo, debido a la configuración bottom-up
del mapa de bits, por la que la imagen aparece invertida en el orden de sus filas,
realmente se accede a la esquina inferior izquierda, en lugar de a la esquina superior
izquierda, donde es norma común colocar el origen de coordenadas en una imagen
digital.
Una vez que se accede a la esquina inferior izquierda, se lee por filas sólo el
ancho a visualizar, con lo que la imagen que se carga en memoria es exclusivamente la
que se desea visualizar. Para aclarar esta idea conviene observar la figura bajo estas
líneas. Representa la imagen completa, y los píxeles marcados con una X son los
correspondientes a la zona a visualizar. Pues la estrategia se basa en que se carga en
memoria sólo la zona de la imagen a visualizar, y no toda la imagen.
X X X X
X X X X
X X X X
107
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
Para visualizar una zona de la pantalla, se deben asignar valores a las estructuras
del formato BMP para poder pasadas a las funciones de visualización. Por lo tanto, para
cada zona de la imagen que se presente en pantalla es necesario modificar los siguientes
miembros de la estructura BITMAPINFOHEADER:

biWidth: es el ancho de la zona a mostrar.

biHeight: es el alto de la zona a mostrar.

biSizeImage: tamaño del mapa de bits en bytes. Se calcula tal y como
ya se ha explicado en el apartado 5.2.1.6.
5.3.3.- Descripción de funcionalidades
En este apartado se describen de forma exhaustiva algunas de las
funcionalidades del control Imagen, entendidas como propiedades, métodos y elementos
que articulan las acciones del usuario hacia el control Imagen en tiempo de ejecución
Las restantes funcionalidades se explican en los capítulos posteriores, pues
necesitan de explicaciones adicionales
Junto con la ventana que muestra la visualización en pantalla de la imagen
también se han diseñado las siguientes ventanas:

Imagen Índice.

Zoom.

Factor de Zoom.

Paleta.

Histograma.
A continuación se describen estas ventanas, para qué sirven y cómo se
implementan. Sin embargo, antes se detallarán las funciones de la ventana de
Visualización.
5.3.3.1.- Ventana de Visualización
En esta ventana es donde se visualiza la zona de la imagen. Es la ventana
principal y desde ella se puede llamar a las restantes. Presenta el aspecto que se muestra
en la siguiente página.
108
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
La ventana muestra la zona de la imagen que se desea mostrar y además las
siguientes características:

En la parte superior izquierda se muestra el número de imagen, que
corresponde con el identificador de imagen en caso de que haya varias
imágenes abiertas.

En la parte superior derecha se encuentran los iconos de manejo de la
ventana. Efectúan tareas clásicas en Windows, que de izquierda a
derecha son:
- Minimizado de la ventana.
- Maximizado de la ventana.
- Cerrar la ventana.

En la parte inferior y derecha de la ventana se encuentran las barras de
desplazamiento horizontal y vertical respectivamente.
La acción más frecuente, además de la navegación por medio de las barras de
desplazamiento, es pulsar sobre la imagen con el botón derecho, con lo que aparece un
menú emergente (popup menu), donde están todas las opciones que permiten manejar la
imagen, como se puede ver en la imagen de la siguiente página.
109
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
Las opciones que se permiten son las siguientes:

Imagen Índice: visualiza la Imagen Índice. El funcionamiento de esta
imagen se explica en el apartado 5.3.3.2.

Zoom +: si en el momento de ejecutar este comando la zona de la imagen
mostrada corresponde a la imagen n-ésima de la pirámide, visualiza la
zona de la imagen correspondiente a la imagen inmediatamente superior
en cuanto a tamaño, i.e., la imagen (n-1)-ésima. Si no hay imagen
superior se muestra el siguiente mensaje, y para seguir aumentando en
tamaño es necesario hacer uso del comando Hacer Zoom.

110
Zoom -: si en el momento de ejecutar este comando la zona de la imagen
mostrada corresponde a la imagen n-ésima, visualiza la zona de la
imagen correspondiente a la imagen inmediatamente inferior en cuanto a
tamaño, i.e., la imagen (n+1)-ésima de la pirámide. Si no hay imagen
inferior se muestra el siguiente mensaje:
DESARROLLO DE SOFTWARE.






LECTURA Y ESCRITURA DEL FORMATO PROPIO
Hacer Zoom: permite realizar un Zoom de la imagen. La utilización de
este comando se explica más adelante en el apartado 5.3.3.3.
Paleta: permite modificar la tabla de colores. Ver apartado 5.3.3.4.
Anaglifo: permite mostrar el anaglifo de dos imágenes. Se explica en el
capítulo 9 de esta publicación.
Roaming: Permite realizar el denominado Roaming, y además de dos
formas, bien por medio de movimientos del ratón o bien por acción de la
rueda superior del ratón. Se explica en el siguiente capítulo de esta
publicación.
Visión Estereoscópica: permite conseguir la visión estereoscópica con la
ayuda de un estereóscopo. Además, permite visualizar las marcas
flotantes, moverlas de forma conjunta o independiente, y otras
aplicaciones fotogramétricas. Se explica en el capítulo 9 de esta
publicación.
Coordenadas Píxel: permite ver las Coordenadas Instrumentales en
tiempo real. Se visualizan al lado del puntero del ratón, como se puede
ver:
111
BENJAMÍN ARIAS PÉREZ
112
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN

Fotocoordenadas: permite ver las Fotocoordenadas en tiempo real. Se
visualizan al lado del puntero del ratón, como se puede ver en la
siguiente imagen:

Nivel Digital: permite ver el Nivel Digital del píxel sobre el que se sitúa
el puntero del ratón, tal y como muestra la imagen:
DESARROLLO DE SOFTWARE.




LECTURA Y ESCRITURA DEL FORMATO PROPIO
Histograma: permite visualizar el histograma de la imagen. Se explica en
el apartado 6.3.3.4.
Orientación Interna: permite ver, introducir y modificar los parámetros
de Orientación Interna de la imagen. Se explica en el capítulo 9.
Orientación Enterna: permite ver, introducir y modificar los parámetros
de Orientación Enterna de la imagen. Se explica en el capítulo 9.
Focal cámara: permite ver, introducir y modificar la distancia focal de la
cámara con la que se ha obtenido la imagen. Se explica en el capítulo 9.
5.3.3.2.- Ventana de Imagen Índice
La ventana de Imagen Índice nos muestra la imagen completa a modo de
Overview. En todo momento muestra un rectángulo de borde blanco e interior hueco,
que nos permite saber en que posición de la imagen completa se encuentra la zona
visualizada en la ventana de Visualización. Presenta el siguiente aspecto:
Además, el tamaño del rectángulo blanco ayuda a hacerse una idea del tamaño
de la zona visualizada con respecto a la imagen completa. A continuación se muestra
una imagen en un determinado escalón, y en la página siguiente se puede ver el tamaño
del rectángulo en la imagen índice:
113
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
y si se compara con la siguiente imagen se puede ver la diferencia, tanto en la imagen
visualizada como en el rectángulo de la imagen Índice:
114
DESARROLLO DE SOFTWARE.
LECTURA Y ESCRITURA DEL FORMATO PROPIO
También se puede navegar por la imagen a través de la imagen Índice. Si se
mueve el ratón por encima de la imagen Índice, cambia el puntero del ratón a un
puntero de cuatro flechas. Se puede entonces pinchar sobre la zona de la imagen Índice
hacia la que se quiere ir, y se verá en la ventana de Visualización la zona de la imagen
hacia la que se ha ido. Esta forma de navegar es muy útil cuando se trata de imágenes
muy grandes.
5.3.3.3.- Ventana de Zoom y de Factor de Zoom
Se utiliza para ampliar la visualización de la imagen, lo que se conoce como
Hacer Zoom. Si se elige este comando se visualiza la siguiente ventana:
Se elige un factor de ampliación, por ejemplo 2, y se pulsa Aceptar. Entonces
puede marcar sobre la ventana de visualización un rectángulo con ratón, mateniendo
para ello pulsado el botón izquierdo del ratón. Al soltar el botón se visualiza la ventana
de Zoom:
y en la ventana de visualización se pinta un rectángulo con la zona que ha sido
ampliada, como se puede observar en la siguiente página.
115
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
De la ventana ImagenZoom se puede obtener la siguiente información:

Coordenadas Píxel: si se han seleccionado sobre la ventana de
Visualización.

116
Fotocoordenadas: ídem.
DESARROLLO DE SOFTWARE.

LECTURA Y ESCRITURA DEL FORMATO PROPIO
Nivel Digital: ídem.
5.3.3.4.- Ventana de Paleta
Permite actuar sobre la paleta de colores del mapa de bits. Sólo está
implementado un sesgo en los valores de la paleta, pero la verdadera funcionalidad de la
manipulación de la paleta es que permite considerarla como una Look-Up Table (LUT)
con resultados en tiempo real.
Si se selecciona esta opción se visualiza la siguiente ventana:
y si se desplaza se puede ver en la imagen el resultado del desplazamiento de los valores
de la paleta. La imagen sin manipulación en la paleta es:
117
BENJAMÍN ARIAS PÉREZ
FOTOGRAMETRÍA DIGITAL Y PROGRAMACIÓN
si manipulamos la paleta:
el aspecto de la imagen cambia en tiempo real:
5.3.3.5.- Ventana de Histograma
Esta ventana muestra el histograma de la imagen Índice, no es el histograma de
la imagen original. Esto es debido a que el histograma se presenta mediante un control
propio de Visual Basic, MSChart, y a éste se le pasa una matriz de valores y él se
encarga de representar los datos en un gráfico. Y una matriz tan grande como puede
suponer un imagen de 10.000x10.000 píxeles no la acepta. Sin embargo, si acepta una
matriz como mucho de 200x200 píxeles como es el caso de la imagen Índice. El
histograma resultante no es el de la imagen original, por lo que sólo es orientativo.
118
Operadores Morfológicos para Detección de Esquinas
Benjamín Arias Pérez
Ingeniero Técnico en Topografía
Ingeniero en Geodesia y Cartografía
Profesor Asociado.
Departamento de Ingeniería Cartográfica y del Terreno.
Escuela Politécnica Superior de Ávila
Universidad de Salamanca
e-mail: [email protected]
Resumen
En el presente artículo se expone un análisis acerca de la morfología matemática y su
aplicación para extracción de información de una imagen. Se introducen conceptos de
morfología matemática y se explican los principales operadores morfológicos, haciendo
especial énfasis en su utilidad. Por último, se define un operador morfológico que permite la
detección de esquinas, mostrando los resultados alcanzados mediante su empleo.
1.- Introducción
La morfología matemática puede ser útil
en la extracción de información de las
imágenes. Para ello se debe partir de la
premisa inicial de que las imágenes reales
pueden ser modeladas utilizando conjuntos
de puntos de cualquier dimensión. En el
caso de las imágenes digitales, es el
espacio Euclídeo de 2 dimensiones (E2) el
espacio adecuado para su aplicación, al
tratarse de formas planas.
Normalmente, la morfología matemática
se emplea en imágenes binarias, y se
extiende su utilización a escala de grises y
a imágenes de color.
2.- Morfología matemática en imágenes
binarias
Una imagen binaria puede representarse
como un conjunto de puntos 2D. Los
píxeles correspondientes a los objetos de la
imagen tienen el valor 1, y forman un
conjunto denominado, e.g., X. El resto de
puntos de la imagen forman el conjunto
complementario, XC , valen 0 y
corresponden al fondo de la imagen.
Cualquier punto x de una imagen X se
considera un vector respecto del origen, el
cual normalmente se sitúa en el punto
(0,0).
Un ejemplo de imagen binaria puede ser:
0
0

0

0
0
0
1
1
0
0
0
1
0
0
0
0
1
0
0
0
0
0
0

0
0
donde el conjunto de puntos que forman
el objeto de imagen es:
X = {(1,1), (1,2), (1,3), (2,1)}
considerando (fila, columna), y origen
(0,0).
Una transformación morfológica se
define por la relación del conjunto de
puntos del objeto, X, con otro conjunto de
puntos, denominado elemento estructural.
Éste se expresa con respecto a un origen
local, denominado punto representativo.
Dicho origen puede no formar parte del
conjunto de puntos que conforman el
elemento estructural.
La forma de aplicar una transformación
morfológica es desplazando el elemento
estructural por toda la imagen, y el
resultado de la relación, 0 o 1, se almacena
en el píxel actual de la imagen.
2.1.- Definiciones elementales
2.1.1.- Dualidad
La Dualidad de operaciones morfológicas
se deduce de la existencia del conjunto
complementario; de modo que para cada
transformación morfológica φ(X) existe una
transformación dual φ*(XC) tal que:
φ*(XC) = (φ(X))C
2.1.2.- Traslación
La traslación del conjunto de puntos X
por el vector h se denota por Xh y se define
por:
{
}
Xh = d ∈ E2 : d = x + h ∀x∈ X
2.1.3.- Complemento de un conjunto
El complemento de un conjunto es:
X C = {x | x ∉ X }
2.1.4.- Diferencia de dos conjuntos
La diferencia de dos conjuntos X e Y es:
X − Y = {x | x ∈ X , x ∉ Y }
2.2.- Transformaciones
cuantitativas
morfológicas
Una transformación morfológica se
denomina cuantitativa si y solo si satisface
cuatro principios morfológicos básicos:
a) Compatibilidad con la traslación:
sea la transformación φ que
depende de la posición del origen
O del sistema de coordenadas, y
denotemos dicha transformación
como φO. Si todos los puntos son
trasladados por el vector –h se
expresa como φ-h. El principio de
compatibilidad con la traslación
está dado por:
φO ( X h ) = (φ− h ( X ) )h
Si φ no depende de la posición del
origen O entonces la compatibilidad
con el principio de traslación se reduce
a la invarianza bajo traslación:
φ ( X h ) = (φ ( X ) )h
b) Compatibilidad con el cambio de
escala: supongamos que λX es la
homotecia de un conjunto de
puntos
X,
por
tanto
las
coordenadas de cada punto del
conjunto se multiplican por alguna
constante positiva λ. Esto es
equivalente a cambiar la escala
con respecto a algún origen. Sea
φλ la transformación que depende
del parámetro positivo λ (cambio de
escala). La compatibilidad con el
cambio de escala está dada por:
1 
X
λ 
φλ ( X ) = λφ 
Si φ no depende de la escala λ,
entonces la compatibilidad con el
cambio de escala se reduce a la
invarianza al cambio de escala:
φ (λX ) = λφ ( X )
c) Conocimiento local: el principio del
conocimiento local considera la
situación en la cual sólo una parte
de una determinada estructura
puede examinarse.Éste es siempre
el caso
d) Continuidad semi-superior.
2.3.- Dilatación
La transformación morfológica de la
dilatación combina dos conjuntos utilizando
la adición de conjuntos de Minkowski:
{
2
δB (X ) = X ⊕ B = d ∈ E : d = x + b
para cada x ∈ X y b ∈ B
}
Algunas propiedades de la dilatación
resultan
interesantes
para
su
implementación:
2.5.- Relación entre dilatación y erosión
La relación entre ambas propiedades se
muestra en las siguientes propiedades:
a) Conmutativa:
a) La dilatación y la erosión
transformaciones duales:
X ⊕B = B⊕ X
son
(
( X ⊗ Y )C = X C ⊕ Y
b) Asociativa:
(
donde Y = {− y : y ∈ Y }
X ⊕ (B ⊕ D ) = ( X ⊕ B ) ⊕ D
b) La erosión no es conmutativa,
mientras que la dilatación sí lo es:
c) Invariante a la traslación:
X h ⊕ B = ( X ⊕ B )h
X ⊗B ≠ B⊗ X
d) Es una transformación creciente:
Si X ⊆ Y entonces X ⊕ B ⊆ Y ⊕ B
2.4.- Erosión
La transformación morfológica de la
erosión combina dos conjuntos utilizando la
substracción de vectores:
{
}
ε B ( X ) = X ⊗ B = d∈E 2: d +b∈X para cada b∈B
Algunas propiedades de la erosión son:
a) Si el punto representativo es un
miembro del elemento estructural, la
erosión es una transformación antiextensiva:
Si (0,0) ∈ B entonces X ⊕ B ⊆ X
b) Es invariante ante la traslación:
c) La dilatación y la intersección no
pueden intercambiarse: la dilatación
de la intersección de dos imágenes
está contenida en la intersección de
sus dilataciones:
d) El
orden
de
la
erosión
(respectivamente dilatación) puede
intercambiarse con la unión de
conjuntos. Este hecho permite que los
elementos estructurales puedan ser
descompuestos en una unión de
elementos estructurales más simples:
e) La dilatación (erosión) de la imagen X
primero por el elemento estructural B
seguida del elemento estructural D es
equivalente a la dilatación (erosión)
de la imagen X por el resultado de la
dilatación de B por D:
f) Las transformaciones morfológicas
básicas pueden utilizarse para
encontrar los contornos de los
objetos. Esto puede lograrse, e.g.,
mediante la operación lógica XOR de
la imagen original con la imagen
dilatada (o erosionada).
X h ⊗ B = ( X ⊗ B) h
c) Es una transformación creciente:
Si X ⊆ Y entonces X ⊗ B ⊆ Y ⊗ B
d) Si B y D son elementos estructurales
y D está contenido en B, la erosión
por B es más agresiva que por D:
Si D ⊆ B entonces X ⊗ B ⊆ X ⊕ D
2.6.- Apertura
Se trata de una erosión seguida de una
dilatación, ambas operaciones con el
mismo elemento estructurante:
γ B ( X ) = X o B = ( X ⊗ B) ⊕ B
Si una imagen X permanece invariable
por apertura con respecto al elemento
estructural B se dice que es abierta con
respecto a B.
2.7.- Cierre
Se trata de una dilatación seguida de
una erosión, ambas considerando el mismo
elemento estructurante:
ϕ B ( X ) = X • B = ( X ⊕ B) ⊗ B
Si una imagen X permanece invariable
por cierre con respecto al elemento
estructural B se dice que es cerrada con
respecto a B.
2.8.- Propiedades de la apertura y el
cierre
a) La apertura y el cierre son
invariantes a la traslación del
elemento estructural.
b) Dado que la dilatación y la erosión
son transformaciones crecientes,
implica que la apertura y el cierre
también lo son.
c) La apertura es anti-extensiva y el
cierre es extensivo.
d) La apertura y el cierre son
operaciones duales.
e) La apertura y el cierre utilizadas
iterativamente son idempotentes, lo
que implica que la reaplicación de
esas transformaciones no cambia el
resultado previo.
2.9.- Cierre asimétrico
La apertura y el cierre pueden ser
utilizados para detectar esquinas. Sin
embargo,
presentan
los
siguientes
inconvenientes:
a) La apertura afecta a esquinas claras
sobre fondo oscuro, mientras que la
erosión detecta mejor las esquinas
oscuras sobre fondo claros.
b) Estructuras de la imagen de
dimensión reducida (ruido) son
detectados
erróneamente
como
esquinas.
c) No son invariantes a la rotación.
Por ello se define el cierre asimétrico:
una apertura de una imagen por un
elemento estructurante, seguida de una
erosión por otro elemento estructurante:
ϕ + ,◊ ( X ) = ( X ⊕ +) ⊗ ◊ = ε ◊(δ + ( X ))
El objetivo que se persigue con ello es
conseguir que la dilatación y la erosión
sean complementarias en términos de las
esquinas que detectan (claros/oscuros).
Esto se consigue utilizando una cruz en la
dilatación, y un rombo en la erosión. La
presencia de esquinas en una imagen se
define entonces por el siguiente operador:
φ+ ( X ) = X − ϕ + , ◊ ( X )
Pero, con el operador anterior no se
resuelve el problema de invariancia a la
rotación y sensibilidad a las estructuras de
dimensión reducida. Por ello, se define un
nuevo
operador,
cuyos
elementos
estructurantes están girados 45º respecto
del anterior:
φ× ( X ) = X − ϕ×,Π ( X )
Luego una combinación de ambos
operadores puede considerarse suficiente
para detectar esquinas en prácticamente
todas las orientaciones e insensible a
estructuras de dimensión reducida:
φ + ,× ( X ) = ϕ + , ◊ ( X ) − ϕ × , Π ( X )
Este operador, propuesto por Laganière,
debe complementarse con una adecuada
umbralización del resultado, con el fin de
escoger las esquinas correctas.
3.- Morfología matemática en imágenes
en niveles de gris
Puede extenderse lo anteriormente
expuesto a las imágenes en niveles de
gris, considerando para ello que la imagen
ahora se representa por una función
discreta
bimensional
f(x,y),
con
dimensiones M y N. El elemento
estructurante también es bidimensional y
discreto, b(i,j), con dimensiones m y n.
La dilatación en imágenes de niveles de
gris se define como:
f ⊕ b( x, y ) = max { f ( x − i, y − j ) + b(i, j )}
0 ≤i ≤ m −1
0 ≤ j ≤ n −1
La erosión en imágenes de niveles de
gris se define como:
f ⊗ b( x, y ) = min { f ( x + i, y + j ) + b(i, j )}
0≤ i ≤ m −1
0≤ j ≤ n −1
4.- Aplicaciones de los operadores
morfológicos
4.1.- Eliminación de ruido
Este efecto se consigue realizando
primero una erosión y después una
dilatación por un elemento estructurante
cuadrado de lado 1.
El proceso iterativo finaliza cuando:
X K = X k −1
De izquierda a derecha: imagen original,
erosión y dilatación de la imagen
erosionada.
4.2.- Extracción de contornos
Para conseguir el contorno de una región
se realiza una erosión a la imagen por un
elemento estructurante en forma de
cuadrado de lado 1, y a continuación se
halla la diferencia de dicha imagen
erosionada con la original:
A la izquierda se muestra la imagen
original, en el centro la imagen erosionada
y a la derecha los contornos extraídos.
4.3.- Rellenado de regiones
Para conseguir el rellenado de regiones
se precisa como requisito indispensable
conocer las coordenadas de un punto del
interior de la región a rellenar. Conocido
éste, se entra en un proceso iterativo:
X K = ( X K −1 ⊕ B ) I AC
donde:
•
el subíndice K define la iteración K,
•
B es el elemento estructurante, que
debe ser en forma de cruz,
•
A representa a la imagen original
La imagen original está situada en la
parte superior izquierda, y a su derecha se
ve el resultado del algoritmo en la 1ª
iteración. En la parte inferior izquierda
muestra la el proceso en la iteración nº 100,
y a su derecha el resultado final, en la
iteración nº 218.
4.4.- Detección de esquinas
Se muestran a continuación los
resultados conseguidos aplicando distintos
operadores morfológicos a una imagen con
diferentes formas para el estudio de los
diferentes operadores morfológicos. Las
posiciones que se consideran como
esquinas se remarcan en color rojo.
Imagen Original
Apertura por una cruz
Dilatación por una Cruz
Cierre por una cruz
Erosión por una cruz
Dilatación por cruz y erosión por un rombo:
Dilatación por un aspa y erosión por un
cuadrado:
4.5.- Pruebas con una Marca Fiducial
Como aplicación práctica del operador
Laganière, se muestran a continuación
pruebas realizadas a marcas fiduciales,
para de esta forma extraer puntos que
permitan automatizar la Orientación
Interna.
En primer lugar, se aplica una apertura
con un elemento estructurante en forma de
cruz. En la parte superior se muestra la
imagen original de interés, y a su derecha
la imagen diferencia entre la imagen
original y la apertura con una cruz, con
umbral 10. En la parte inferior, idéntica
prueba con umbral 26 en la izquierda, y
100 en la derecha.
Aplicando el
Laganière:
operador
propuesto
por
Se puede observar en la última imagen la
alta efectividad del operador de Laganière,
en comparación con los restantes
operadores morfológicos expuestos
Como puede observarse, los resultados
conseguidos son satisfactorios. Ahora bien,
los resultados dependen en gran medida
del documento original. A tal efecto, al
realizar las mismas aperturas a una imagen
con peor contraste:
4.6.- Estrategia piramidal
La utilización de estos operadores se
puede ampliar utilizando una imagen con la
estructura piramidal del archivo BMJ. En la
imagen que se muestra a continuación se
muestra el último nivel de la pirámide, el
más alto, y se pueden comparar los
resultados de aplicar todos los operadores
morfológicos por una cruz, y luego el
operador de Laganière.
los resultados
distintos.
son
completamente
Aplicando el operador propuesto por
Laganière, con umbrales 10 (superior
derecha), 26 y 100 (inferior izquierda y
derecha respectivamente), se obtienen los
siguientes resultados:
Hay que destacar que el número de
detecciones, D, en el caso de umbral 100
ha sido de 2.500, mientras que para la
misma imagen pero aplicando una Apertura
con una cruz D=11.025.
.
De izquierda a derecha y de arriba a
abajo son: la imagen original y dilatación
por una cruz (D=179.900), erosión por una
cruz (D=106.800), apertura por una cruz
(D=38.255),
cierre
por
una
cruz
(D=25.600), cierre asimétrico por cruz y
rombo (D=25.175), cierre asimétrico por un
aspa y cuadrado (D=22.275), y operador de
Laganiére (D=425).
Las posibilidades de estos operadores
son varias, en función de las aplicaciones
que
se
deseen.
Estos
resultados
demuestran que se pueden utilizar en
estrategias piramidales de búsquedas, en
toda su amplitud. En concreto, el operador
propuesto por Laganière proporciona
resultados altamente efectivos.
Bibliografía
ARIAS PÉREZ, B., Empleo de imágenes
piramidales, Topografía y Cartografía, núm.
118-119, Vol. XX, Madrid, 2003.
ARIAS PÉREZ, B., La programación
orientada a objetos en Fotogrametría,
Datum XXI, núm. 2, Año I, Madrid, 2002.
PAJARES MARTINSANZ, G., DE LA CRUZ
GARCÍA, J.M., Visión por computador.
Imágenes digitales y aplicaciones, RA-MA
Editorial, 2001, Madrid.
ROCHA, A., FERREIRA, R., Mosaico de
imagens, Faculdade de Engenharia da
Universidade do Porto, Licenciatura em
Engenharia
Electrotécnica
e
de
Computadores, 2000.
SERRA, J., Curso sobre Morfología
Matemática, Centre de Morphologie
Mathématique, Ecole des Mines de Paris,
2000.
Descargar