viernes, 23 de marzo de 2007

Intentando aprender manejo de bitmaps - I

Estoy desarrollando una aplicación para ortodoncia que requiere el procesamiento de imagenes digitalizadas, sobre las mismas se marcan puntos (cefalométricos) y luego se aplican cálculos geométricos. La mayoría de estos cáclulos son relativamente simples: distancia entre 2 puntos, proyección entre puntos y rectas, intersección entre rectas, cálculo de ángulos, y algun otro cálculo que no es mas que una composición de los mencionados.
El manejo por ahora no parece complicado, y lo relacionado con los cálculos geométricos anda bien. Estoy trabajando con bitmaps, ya que es el formato de archivo que me permite mayor manipulación para programar y trabajar sobre imagenes con Delphi, que es el IDE y lenguaje que estoy utilizando.

Como todo, hacerlo ahora que se algo más, no fue complicado, pero llegar a obtener y ordenar toda la información que hay sobre .bmp, y el manejo de imágenes con Delphi, fue una tarea pesada, de muchas lecturas, buscar ayuda en foros, etc.

Mi mayor problema se presentó al querer calcular la escala, esto es la equivalencia entre pixeles por mm real de la imágen, es decir por cada mm real de la imagen sin digitalizar, a cuantos pixeles equivale en la imagen ya digitalizada mediante scanner, cámara digital, etc. Sabiendo poco o menos que ahora, hice lo siguiente:

  1. antes de escanear la imágen, mido una distancia en la misma, llamemosle distmm, la cual será un dato ingresado por el usuario.
  2. el usuario marca los puntos extremos de la imagen
  3. calculo la distancia en pixeles correspondiente, como ya tengo la distancia en mm, mediante una simple regla de 3, se obtiene la relación entre mm y pixeles para la imagen dada.
Esto funciona.

Quise hacerlo mas automático, que el usuario no tenga que ingresar ningun dato, simplemente marcar 2 puntos, y obtener la escala desde la información guardada en el bmp. Esto me llevó a indagar sobre el tema, busqué en san google e internet y preguntando en foros.

Conclusión: no encontré forma de hacerlo, pero aprendí algo más sobre bmps y su información interna que quiero compartir porque creo que se abre a un mundo fascinante y que cada vez gana mas adeptos, el manejo de imágenes. Consideren estos apuntes de primera mano, una referencia introductoria y rápida, pero espero que util. Al menos que sirva para entender los cesudos artículos disponibles por toda la web a partir de esta base, si sirve para eso, me doy por satisfecha.


Bitmaps...poco conocidos

Es el formato más extendido en los sistemas Windows (aparecido en la versión 3.0) y OS/2 , siendo el arhivo BMP donde se almacenan las imágenes. Se almacenan en formato DIB (device independente bitmap, mapa independiente del dispositivo) que permite a Windows visualizar el contenido del mapa de bits en cualquier dispositivo de visualización. Esto se debe a que el mapa de bits especifica el color de un bit de forma independiente del método utilizado para representarlo.
Es un formato simple que tiene varios derivados (los .ico por ejemplo), y aunque teóricamente permite compresión no se usa en la práctica. Su desventaja es el gran tamaño de archivo (por ausencia de compresión), y que necesita una mayor velocidad de carga como consecuencia.

Forma del archivo

Veamos como está organizado:
BITMAPFILEHEADER // contiene datos sobre tipo, tamaño y diseño de un archivo del bitmap.
BITMAPINFOHEADER // cabecera de información especifica dimensiones, tipo de condensación y formato del color para el bitmap
ACOLORS DE RGBQUAD [] // vector de estructuras, contiene tantos elementos como colores haya en el bitmap
BYTE EL ABITMAPBITS []

Los colores del vector RGBQUAD suelen aparecer en orden de importancia para ayudar al driver de visualización a dibujar el bitmap en un dispositivo que no pueda visualizar tantos colores como los que tiene la imagen.

A diferencia de la mayoría de los formatos gráficos este se organiza en 4, y no en 3 partes.

Estos datos pertenecen a la API, hay que incluir la Unit Windows en Delphi para acceder a los mismos.

Me voy a concentrar en la estructura que me interesa para manejar la información de mi problema, BITMAPINFOHEADER, que tiene la forma:

 BITMAPINFOHEADER, de forma:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[ 1 ];
}BITMAPINFO;

typedef struct tagBITMAPINFOHEADER {
DWORD biSize; // tamaño de la información de cabecera
DWORD biWidth; // ancho en pixeles de la imágen
DWORD biHeight; // alto en píxeles
WORD biPlanes; // nro de planos de la imagen
WORD biBitCount; //nro de bits por pixel (puede valer 1, 4, 8 o 24)
DWORD biCompression; // compresion utilizada
DWORD biSizeImage; // tamaño de la imagen en bytes
DWORD biXpelsPerMeter; //pixeles por metro horizontal
DWORD biYPelsPerMeter; // pixeles por metro vertical
DWORD biClrUsed; // nro de colores utilizados
DWORD biClrImportant; // colores mas importantes (vale 0, si todos lo son)
} BITMAPINFOHEADER;

Importante: Cabe aclarar que esta información no está en todo bitmap, ya que dependerá de la manera en que la imagen sea creada: mediante scanner, computadora, o cámara digital. Pero es importante tenerlo presente, ya que dependerá de su origen para determinar si vamos a contar con esta información para su posterior procesamiento.
(Si el origen son imágenes usadas en medicina, es muy probable que sí contemos con estos datos)

De todos modos, no me fue util a mis propósitos por los motivos que ya diré.

Cómo acceder desde Delphi:

procedure GetBitmapInfo(Filename: string;
var
Info: BITMAPINFOHEADER);
begin
FillChar(Info,Sizeof(Info),0);
with TFileStream.Create(Filename,fmOpenRead,
fmShareDenyWrite) do
try
Seek(Sizeof(BITMAPFILEHEADER),soFromBeginning);
ReadBuffer(Info,Sizeof(Info));
finally
Free;
end;
end;
La variable Info devuelve la estructura con los datos mencionados. Veamos como usarla:

// Por ejemplo

var
Info: BITMAPINFOHEADER;
Str: string;
begin
GetBitmapInfo('c:\1.bmp',Info);
Str:=
'Ancho = ' + IntToStr(Info.biWidth) + #13 +
'Alto = ' + IntToStr(Info.biHeight) + #13 +
'Profundidad en bits = ' + IntToStr(Info.biBitCount) + #13 +
'Resolucion horizontal = ' + IntToStr(Info.biXPelsPerMeter) +
' Pixeles por metro' + #13 +
'Resolucion vertical = ' + IntToStr(Info.biYPelsPerMeter) +
' Pixeles por metro' + #13;
case Info.biCompression of
BI_RGB: Str:= Str + 'Compresion = Sin comprimir';
BI_RLE8: Str:= Str + 'Compresion = RL8';
BI_RLE4: Str:= Str + 'Compresion = RLE4';
BI_BITFIELDS: Str:= Str + 'Compresion = BITFIELDS';
end;
ShowMessage(Str);
end;
Pensaba, y estaba en un error, que con los datos
Info.biXPelsPerMeter (pixeles por metro), podría sacar la escala a mm para el eje Ox y aplicarla a mis medidas en pixeles (lo mismo para el eje Oy).
No es asi, desconozco aun en base a qué se establece esa referencia a metros, pero lo cierto es que no se corresponde con las dimensiones reales de la imagen.
Hecho que comprobé empíricamente con las medidas reales de mi imágen antes de escanear. Tampoco se refiere a mm de pantalla, a partir de la escala hallada calcule los mm, y distan de la medida real entre los puntos de pantalla.
(Reeditado)
biXPelsPerMeter y biYPelsPerMeterEstos se refieren a los valores del dispositivo destino, hecho a tener en cuenta si queremos almacenar la imagen que estamos trabajando, conviene encontrar un dispositivo destino lo mas compatible con esas magnitudes para evitar distorciones o mejor definición.

Nota-1: los valores obtenidos en Info, coinciden con los que obtiene Photoshop cuando nos dice la equivalencia entre pixeles y cms.

Nota-2: los valores de
biXPelsPerMeter y biYPelsPerMeter no cambian con la resolución, se mantienen constantes. Por lo que es la información que se guarda en el bitmap al crearse. La misma debe estar relacionada con la resolución y/o configuración del dispositivo que crea la imagen digitalizada. Estos valores, guardan relación con biWidth y biHeight (pixeles), valores que también se pueden obtener con Photoshop, asi como la equivalencia en cms, y esta relación dista de la medida de la imagen real.

Me pareció interesante para compartir, porque entre otras cosas desconozco toda la información que contiene un bmp y las variantes de uso que puede hacerse con ella. Esto es solo un (simple) ejemplo.


Off Topic - Html: usé los tags <pre> y
</pre> desde el modo de edición en HTML de blogger para formatear el código en Object Pascal de Delphi.