manejo de imagenes.

Los archivos comienzan (cabecera o header) con las letras 'BM' (0x42 0x4D), ... cabecera de un archivo BMP y extraer la información necesaria de la imagen ...
941KB Größe 6 Downloads 59 vistas
Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. -----------------------------------------------------------------------------------------------------------------------

MANEJO DE IMAGENES. Archivos BMP. Los archivos de mapas de bits se componen de direcciones asociadas a códigos de color, uno para cada cuadro en una matriz de píxeles tal como se esquematizaría un dibujo de "colorea los cuadros" para niños pequeños. Normalmente, se caracterizan por ser muy poco eficientes en su uso de espacio en disco, pero pueden mostrar un buen nivel de calidad. A diferencia de los gráficos vectoriales, al ser reescalados a un tamaño mayor, pierden calidad. Otra desventaja de los archivos BMP es que no son utilizables en páginas web debido a su gran tamaño en relación a su resolución. Dependiendo de la profundidad de color que tenga la imagen cada píxel puede ocupar 1 o varios bytes. Generalmente se suelen transformar en otros formatos, como JPEG (fotografías), GIF o PNG (dibujos y esquemas), los cuales utilizan otros algoritmos para conseguir una mayor compresión (menor tamaño del archivo). Los archivos comienzan (cabecera o header) con las letras 'BM' (0x42 0x4D), que lo identifica con el programa de visualización o edición. En la cabecera también se indica el tamaño de la imagen y con cuántos bytes se representa el color de cada píxel. A continuación se detalla la estructura de la cabecera de un fichero .BMP

CLASE DE TEORÍA Nro 7

Página 1 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------Basándonos en estos datos vamos a implementar un programa que nos permita leer la cabecera de un archivo BMP y extraer la información necesaria de la imagen para luego poder operar sobre ella:

/* --- TALLER DE LENGUAJES I LeerBMP_02.cpp

2014

Este programa lee la cabecera de un archivo BMP y muestra por pantalla los puntos principales para el acceso y manejo de dichas imágenes. Para ello se han utilizado un conjunto de apuntadores tipificados. También podría haberse resuelto con una estructura que englobe todos los datos de la cabecera (header) del archivo grafico. --------------------------------------------------------------------------------------------------------------- */ #include #include #include typedef short ent; int FileExiste ( char * ); // --------------------------------------------------------------------------------------void main() { FILE *FuenteBMP; char NombBMP[128] = "G:\\Carretilla Canal Gris.BMP"; char Cabec[64]; int *TamBMP; int *InicioDatos; int *AnchoPx; int *AltoPx; int *TamImag; int *TamTablaCol; int Leidos; ent *NroPlanos; ent *TamPts; clrscr(); if(FileExiste(NombBMP)) FuenteBMP=fopen(NombBMP,"rb");

Leidos=fread(Cabec,1,54,FuenteBMP); TamBMP InicioDatos AnchoPx

= (int *)&Cabec[2]; = (int *)&Cabec[10]; = (int *)&Cabec[18];

CLASE DE TEORÍA Nro 7

Página 2 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------AltoPx = (int *)&Cabec[22]; TamPts = (ent *)&Cabec[28]; TamImag = (int *)&Cabec[34]; TamTablaCol = (int *)&Cabec[46]; NroPlanos = (ent *)&Cabec[26]; cprintf ("Leidos = %d\r\n", Leidos ); cprintf ("Tamanio del BMP = %d\r\n", *TamBMP ); cprintf ("InicioDatos = %d\r\n", *InicioDatos ); cprintf ("Ancho en Px = %d\r\n", *AnchoPx ); cprintf ("Alto en Px = %d\r\n", *AltoPx ); cprintf ("Tamano de pts = %d\r\n", cprintf ("Tamanio de Img = %d\r\n", cprintf ("Tamanio de Tabla = %d\r\n", cprintf ("Numero de planos = %d\r\n",

*TamPts ); *TamImag ); *TamTablaCol ); NroPlanos );

getch( ); fclose(FuenteBMP); } // --------------------------------------------------------------------------------------int FileExiste(char *Nombre) { FILE *Aux; if((Aux=fopen(Nombre,"rb"))==NULL) return(0); fclose(Aux); return(1); } Al comienzo del programa nos hemos visto forzados a redefinir: typedef short ent; debido a que aquí (en el BCC32) los enteros son de 4 bytes y no de 2 como se definieron para la cabecera del BMP. Sin embargo no hemos mencionado algo sumamente importante: El tamaño de cada fila de pixeles en el archivo no es solamente igual a (*AnchoPx).sizeof(TPixel) sino que DEBE SER un múltiplo de 4 (así funcionan los BMP!!). Algunas veces el ancho de la imagen ya resulta un múltiplo de 4 y el hecho se nos pasa por alto. CLASE DE TEORÍA Nro 7

Página 3 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------Por eso para asegurarnos conviene chequear de la siguiente manera: while((((*AnchoPx)*sizeof(TPixel)+Mult4)%4)!=0) Mult4++; Completar4=i; O sea si el ancho de la figura multiplicado por la cantidad de bytes que utiliza por color no resulta ser un múltiplo de 4, le vamos agregando bytes hasta que ello se cumpla. Pero aquí viene otra circunstancia especial: Significa que DEBEREMOS LEER el archivo original con ese adicional: Leidos=fread(pFila,1,(*AnchoPx) *sizeof(TPixel)+Completar4,FuenteBMP); al realizar las modificaciones que deseemos hacer en cada pixel de la imagen, grabamos la fila en el archivo destino de la siguiente manera: fwrite(pFila,1,Leidos,DestinoBMP);

Ahora bien, supongamos que el archivo abierto y leído más arriba ha sufrido modificaciones en cada uno de sus pixeles y que deseamos salvarlo con otro nombre:

/* --- TALLER DE LENGUAJES I Replicar archivo BMP.cpp

2014

Este programa en lugar de realizar una réplica binaria como habíamos visto en la clase pasada, lee la cabecera de un archivo BMP y realiza la replicación basándose en los datos de dicha cabecera. Con respecto a esto, normalmente toda imagen en codificación RGB, posee el inicio de los datos en la posición 54. Sin embargo si la imagen se halla codificada en canal de gris a 8 bit, el inicio de los datos comienza en la posición 1078, como en el archivo elegido. En este caso el espacio entre la posición 54 y la 1078 contiene una Tabla de Grises de la forma: 0 0 0 1 1 1 2 2 2 ... 255 255

0 0 0 255 0

y que posee una extensión de 1024 bytes. Podemos entonces, ya sea detectar el tamaño del punto: 8 bits, o bien el inicio de los datos a partir de 1078 y tomar en cuenta esos 1024 bytes. ---------------------------------------------------------------------------------------------------------------- */ CLASE DE TEORÍA Nro 7

Página 4 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------#include #include #include typedef short ent; int FileExiste ( char * ); // -------------------------------------------------------------------------void main() { FILE *FuenteBMP; FILE *ReplicaBMP; char Buff[4096]; char NombBMP[128] = "D:\\DISCO F\\Leng214\\Carretilla Canal Gris.BMP"; char NombReplicaBMP[128] = "D:\\DISCO F\\Leng214\\Replica Carretilla Canal Gris.BMP"; char Cabec[64]; int *TamBMP; int *InicioDatos; int *AnchoPx; int *AltoPx; int *TamImag; int *TamTablaCol; int Leidos; ent *NroPlanos; ent *TamPts;

clrscr( ); if(FileExiste(NombBMP)) FuenteBMP=fopen(NombBMP,"rb"); if((ReplicaBMP=fopen(NombReplicaBMP,"wb"))==NULL) { cprintf("NO PUDO CREAR ARCHIVO DESTINO"); fclose(FuenteBMP); getch( ); return; } Leidos=fread(Cabec,1,54,FuenteBMP); fwrite(Cabec,1,Leidos,ReplicaBMP); InicioDatos=(int *)&Cabec[10]; TamPts=(ent *)&Cabec[28]; // ... verificacion importante ---if(*TamPts == 8) { fread (Buff,1,1024,FuenteBMP ); fwrite(Buff,1,1024,ReplicaBMP); } CLASE DE TEORÍA Nro 7

Página 5 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------fseek(FuenteBMP, *InicioDatos,SEEK_SET); while((Leidos=fread(Buff,1,4096,FuenteBMP))!=0) fwrite(Buff,1,Leidos,ReplicaBMP); fclose(FuenteBMP); fclose(ReplicaBMP); cprintf("ARCHIVO BMP EXITOSAMENTE REPLICADO"); getch( ); } // --------------------------------------------------------------------------------------int FileExiste(char *Nombre) { FILE *Aux; if((Aux=fopen(Nombre,"rb"))==NULL) return(0); fclose(Aux); return(1); }

Para poder modificar los pixeles de una imagen, vamos a analizar algunos conceptos básicos. Para sistemas emisores de luz como los monitores, se utiliza normalmente el sistema de colores RGB que establece que la mayoría de los colores visibles se obtienen por combinación, en distintas proporciones, de los colores primarios Red, Green y Blue:

CLASE DE TEORÍA Nro 7

Página 6 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. -----------------------------------------------------------------------------------------------------------------------

Con este sistema podemos formar un cubo de colores en cada uno de cuyos ejes tendríamos uno de los colores primarios. La cantidad de tonos para cada color primario tiene que ver con lo que se denomina "profundidad de color". Normalmente se tiene 8 bits/color, o sea 256 tonos posibles. Ahora bien, si tomamos en cuenta que cada pixel o punto luminoso de una imagen está compuesto por 3 colores, tendríamos 24 bits/pixel, lo cual equivale a decir que cada punto de la imagen costaría 3 bytes de memoria RAM. La cantidad de colores posibles para cada pixel sería de (256)3 = 16.777.216. Las cámaras fotográficas digitales modernas poseen opciones para tomar cuadros en 16 bits/color, o sea 48 bits/pixel (formato RAW), lo cual hacen un total de: 65536 tonos para c/color y 281.474.976.71.656 colores/pixel. Ahora cada punto de la imagen necesita 6 bytes de memoria RAM. Ello quiere decir que una fotografía de 3648 x 2736 pixels en modo de 8 bits/color, requiere: 3648 x 2736 x 3 = 29.942.784 bytes (sin compresión). 3648 x 2736 x 6 = 59.885.568 bytes (sin compresión). Normalmente los archivos de imágenes son jpg, o sea tienen una cierta compresión admisible con la pérdida de calidad. Para el ejemplo mostrado se tiene 4.791.341 bytes (en el modo de 8 bits/color). Cuanto más relación de compresión se aplica, menos calidad posee la imagen puesto que el proceso de compresión jpg genera pérdida de información.

CLASE DE TEORÍA Nro 7

Página 7 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------Otros concepto asociado con imágenes es lo que se denomina "resolución de la imagen": la cantidad de pixeles que la componen. De esta manera se tienen imágenes de 3, 5, 7, 10, 14, etc. Megapíxel. Hoy en día los celulares de tipo smartphones poseen incorporada cámaras de alta resolución: 14 Mpx. ¿Cuál sería la ventaja de disponer de más píxeles para una misma imagen?: que podríamos captar una mayor cantidad de detalles, o dicho de otra manera, detalles más finos. ¿Y cuál sería la ventaja de tener mayor profundidad de color?: que captaríamos mayor cantidad de matices, o matices intermedios. Esto último se denomina también "gama tonal".

Pero volvamos a nuestro gráfico RGB o cubo de colores. En cada eje (para 8 bits/color), se pueden generar 256 tonos diferentes. Si nos movemos por uno sólo de sus ejes, por ejemplo el verde (los otros dos componentes: el Rojo y el Azul valdrían cero), sólo obtendríamos tonos de verde. Nótese que en el centro de coordenadas (0, 0, 0) todos los colores desaparecen y tenemos el Negro, o sea que un extremo de cualquiera de los colores primarios sería siempre Negro y en el otro se tendría la máxima potencia o luminosidad. Así, el Verde más intenso que podría obtenerse sería de valor 255. Los mismo si nos movemos por el canal Rojo o el Azul.

Cuando comenzamos a combinar distintos valores de los canales primarios, comenzamos a obtener el resto de la gama de colores. Por ejemplo: Rojo = 255 y Verde = 255 (con el Azul en cero), dan el color Amarillo. Si en cambio fuera Rojo = 255 y Azul = 255 (con el Verde en cero), tendríamos el color Magenta. Esto concuerda con el diagrama de los tres discos que se intersectan.

Una cosa muy interesante es que cuando cada canal posee un mismo valor, se obtiene un tono de gris. Por ejemplo R=120 G=120 B=120 generan un tono determinado de Gris. R=195 G=195 B=195 dan otro tono distinto de Gris, y así sucesivamente. Esto se evidencia en la diagonal principal del cubo que va desde el Negro en el origen, hasta el Blanco en el otro extremo. Entonces ¿cuántos tonos de grises tendríamos para un sistema con 8 bits/color? También 256 tonos. Surge rápidamente entonces la idea de tomar una imagen color y convertirla en tonos de grises. Nada más sencillo: sólo tenemos que igualar los valores de los 3 canales. Pero aquí surge una pregunta ¿y qué valor le damos? ¿El del máximo? ¿El del mínimo? ¿El promedio? Cada uno de ellos podría ser un criterio y obtendríamos una imagen legible más oscura o más clara, pero en realidad existe otro criterio más bien técnico: la respuesta del ojo humano a los colores. El ojo posee mejor respuesta a los verdes que a cualquier otro color, por lo tanto la siguiente fórmula: Gris=0.3*R+0.59*G+0.11*B que da énfasis a los verdes, permite obtener imágenes en grises de muy buena calidad. Esta expresión debe aplicarse a cada pixel de la imagen y tomar dicho valor para cada canal. CLASE DE TEORÍA Nro 7

Página 8 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------/* --- TALLER DE LENGUAJES I Convertir a grises.cpp

2014 -

Este programa lee un archivo BMP en colores y lo convierte en su equivalente en tono de grises o a algún otro. IMPORTANTE: el ancho de la imagen en bytes, o sea (*AnchoPx).sizeof(TPixel) debe ser un múltiplo de 4. Si así no fuere hay que leer la línea completa más la cantidad para completar el múltiplo de 4.

---------------------------------------------------------------------------------------------- */

#include #include #include typedef unsigned char byte; typedef short int ent; typedef struct { byte B; byte G; byte R; } TPixel; int FileExiste ( char * ); void *RM ( int ); // --------------------------------------------------------------------------------------void main() { FILE *FuenteBMP; FILE *DestinoBMP; char NombFuenteBMP [128] = "D:\\DISCO F\\Leng214\\Archivos BMP\\Locomotora 03.BMP"; char NombDestinoBMP[128] = "D:\\DISCO F\\Leng214\\Locomotora 03 en grises.BMP"; char Cabec[64]; int *InicioDatos; int *TamArchivo; int *TamCabecera; int *TamImagen; int *TamTablaColores; int *ContColoresImport; ent *TamPt; ent *TotPlanos; int *AnchoPx; int *AltoPx; TPixel *pFila; int i,j;

CLASE DE TEORÍA Nro 7

Página 9 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------byte byte int char int

R,G,B; Gris; Mult4 = 0; Buff[4096]; Leidos;

clrscr( ); if(FileExiste(NombFuenteBMP)) FuenteBMP=fopen(NombFuenteBMP,"rb"); else { cprintf("NO EXISTE ARCHIVO FUENTE"); getch(); return; } if((DestinoBMP=fopen(NombDestinoBMP,"wb"))==NULL){ cprintf("NO PUDO CREAR ARCHIVO DESTINO"); getch(); fclose(FuenteBMP); return; } fread(Cabec,1,54,FuenteBMP); InicioDatos = (int *)&Cabec[10]; AnchoPx = (int *)&Cabec[18]; AltoPx = (int *)&Cabec[22]; TamImagen = (int *)&Cabec[34]; TamArchivo = (int *)&Cabec[2]; TamCabecera = (int *)&Cabec[14]; TamTablaColores = (int *)&Cabec[46]; ContColoresImport = (int *)&Cabec[50]; TamPt = (ent *)&Cabec[28]; TotPlanos = (ent *)&Cabec[26]; /* ----------------------------------------------------------------------------------cprintf("InicioDatos = %d\r\n",*InicioDatos ); cprintf("AnchoPx = %d\r\n",*AnchoPx ); cprintf("AltoPx = %d\r\n",*AltoPx ); cprintf("TamArchivo = %d\r\n",*TamArchivo ); cprintf("TamImagen = %d\r\n",*TamImagen ); cprintf("TamCabecera = %d\r\n",*TamCabecera ); cprintf("TamTablaColores = %d\r\n",*TamTablaColores ); cprintf("TamPt = %d\r\n",*TamPt ); cprintf("ContColoresImport = %d\r\n",*ContColoresImport ); cprintf("Total de planos = %d\r\n",*TotPlanos ); ---------------------------------------------------------------------------------------------- */

CLASE DE TEORÍA Nro 7

Página 10 de 30

Carrera de Programador Universitario & Licenciatura en Informática TALLER DE LENGUAJES I 2015 - Dictado: Ing. Juan M. Conti FACET de la Universidad Nacional de Tucumán. ----------------------------------------------------------------------------------------------------------------------while((((*AnchoPx)*sizeof(TPixel)+Mult4)%4)!=0) Mult4++; fwrite(Cabec,1,54,DestinoBMP); pFila=(TPixel *)RM((*AnchoPx)*sizeof(TPixel)+Mult4); fseek(FuenteBMP,*InicioDatos,SEEK_SET); for(i=0;i