Archivo

Entradas Etiquetadas ‘imagen’

Salvando archivos de imagen BMP en C

Jueves, 21 de Julio de 2011 Gaspar Fernández Sin comentarios

linux_detergenteHace tiempo hablé de la lectura de archivos BMP en C y puse algún ejemplo. Pero falta lo más importante, poder guardar de nuevo las imágenes, tras aplicar un filtro o generar una imagen desde cero y exportarla. Para ello he creado la siguiente función:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void SaveBMP(char *filename, bmpInfoHeader *info, unsigned char *imgdata)
{
  bmpFileHeader header;
  FILE *f;
  uint16_t type;
 
  f=fopen(filename, "w+");
  header.size=info->imgsize+sizeof(bmpFileHeader)+sizeof(bmpInfoHeader);
  /* header.resv1=0; */
  /* header.resv2=1; */
  /* El offset será el tamaño de las dos cabeceras + 2 (información de fichero)*/
  header.offset=sizeof(bmpFileHeader)+sizeof(bmpInfoHeader)+2;
  /* Escribimos la identificación del archivo */
  type=0x4D42;
  fwrite(&type, sizeof(type),1,f);
  /* Escribimos la cabecera de fichero */
  fwrite(&header, sizeof(bmpFileHeader),1,f);
  /* Escribimos la información básica de la imagen */
  fwrite(info, sizeof(bmpInfoHeader),1,f);
  /* Escribimos la imagen */
  fwrite(imgdata, info->imgsize, 1, f);
  fclose(f);
}

En el siguiente ejemplo, utilizando la carga de un archivo de imagen (por ejemplo la que ilustra el post convertida a BMP), podemos hacer un sencillo algoritmo para pasarla a blanco y negro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>       /* round() */

typedef struct bmpFileHeader
{
  uint32_t size;
  uint16_t resv1;
  uint16_t resv2;
  uint32_t offset;
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* DIB header size */
  uint32_t width;
  uint32_t height;
  uint16_t planes;         /* color planes */
  uint16_t bpp;            /* bits per pixel */
  uint32_t compress;
  uint32_t imgsize;    
  uint32_t bpmx;        /* X bits per meter */
  uint32_t bpmy;        /* Y bits per meter */
  uint32_t colors;      /* colors used */
  uint32_t imxtcolors;      /* important colors */
} bmpInfoHeader;

void SaveBMP(char *filename, bmpInfoHeader *info, unsigned char *imgdata);
unsigned char calculaColorMedio(unsigned char *pixel);
unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader);
bmpInfoHeader *createInfoHeader(unsigned w, unsigned h, unsigned ppp);

int main()
{
  bmpInfoHeader info;  
  unsigned char *img;
  unsigned char color[3];
  unsigned char media;
  int i, j;
  img=LoadBMP("Linux_Detergente.bmp", &info);

  for (i=0; i<info.height; i++)
    {
      for (j=0; j<info.width; j++)
    {
      media=calculaColorMedio(&img[3*(j+i*info.width)]);
      img[3*(j+i*info.width)]=media;
      img[3*(j+i*info.width)+1]=media;
      img[3*(j+i*info.width)+2]=media;

    }
    }
  SaveBMP("res3.bmp", &info, img);

  free(img);
 
}

unsigned char calculaColorMedio(unsigned char *pixel)
{
  unsigned media = (*pixel + *(pixel+1) + *(pixel+2)) / 3;

  return (unsigned char) media;
}

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;
  unsigned char *imgdata;
  uint16_t type;
  f=fopen (filename, "r");
  /* handle open error */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)
    {
      fclose(f);
      return NULL;
    }
  fread(&header, sizeof(bmpFileHeader), 1, f);

  printf ("size: %u\n", header.size);
  printf ("offs: %u\n", header.offset);
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);
  printf ("header size:      %d\n", bInfoHeader->headersize);
  printf ("image width:      %d\n", bInfoHeader->width);
  printf ("image height:     %d\n", bInfoHeader->height);
  printf ("colour planes:    %d\n", bInfoHeader->planes);
  printf ("bpp:              %d\n", bInfoHeader->bpp);
  printf ("compress:         %d\n", bInfoHeader->compress);
  printf ("imgage size:      %d\n", bInfoHeader->imgsize);
  printf ("bpmx:             %d\n", bInfoHeader->bpmx);
  printf ("bpmy:             %d\n", bInfoHeader->bpmy);
  printf ("colors:           %d\n", bInfoHeader->colors);
  printf ("important colors: %d\n", bInfoHeader->imxtcolors);
  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);
  fseek(f, header.offset, SEEK_SET);
  printf("leido: %d\n", fread(imgdata, bInfoHeader->imgsize,1, f));
  fclose(f);

  return imgdata;
}

bmpInfoHeader *createInfoHeader(unsigned w, unsigned h, unsigned ppp)
{
  bmpInfoHeader *ih = malloc(sizeof(bmpInfoHeader));

  ih->headersize=sizeof(bmpInfoHeader);
  ih->width=w;
  ih->height=h;
  ih->planes=1;
  ih->bpp=24;
  ih->compress=0;
  ih->imgsize=w*h*3;        /* 3 bytes por pixel w*h pixels */
  ih->bpmx=(unsigned)round((double)ppp*100/2.54);
  ih->bpmy=ih->bpmx;        /* Misma resolución vertical y horiontal */
  ih->colors=0;
  ih->imxtcolors=0;

  return ih;
}

void SaveBMP(char *filename, bmpInfoHeader *info, unsigned char *imgdata)
{
  bmpFileHeader header;
  FILE *f;
  uint16_t type;
 
  f=fopen(filename, "w+");
  header.size=info->imgsize+sizeof(bmpFileHeader)+sizeof(bmpInfoHeader);
  /* header.resv1=0; */
  /* header.resv2=1; */
  /* El offset será el tamaño de las dos cabeceras + 2 (información de fichero)*/
  header.offset=sizeof(bmpFileHeader)+sizeof(bmpInfoHeader)+2;
  /* Escribimos la identificación del archivo */
  type=0x4D42;
  fwrite(&type, sizeof(type),1,f);
  /* Escribimos la cabecera de fichero */
  fwrite(&header, sizeof(bmpFileHeader),1,f);
  /* Escribimos la información básica de la imagen */
  fwrite(info, sizeof(bmpInfoHeader),1,f);
  /* Escribimos la imagen */
  fwrite(imgdata, info->imgsize, 1, f);
  fclose(f);
}

Tendremos que compilarlo con -lm para incluir la biblioteca matemática (usada para hacer round() con los colores), debe generar un fichero res.bmp con la imagen en blanco y negro.

Nota: El algoritmo utilizado para hacer la imagen en blanco y negro es la media de las 3 componentes, hay muchos algoritmos para hacer esto, ya iré mostrando algunos en el futuro.

Nota2: He incluido la etiqueta #tuentiContest aquí aunque no tenga mucho que ver por el reto 14, donde teníamos que leer un archivo BMP.

Foto: Jacob Köhler

Recopilación de soluciones para los retos de #tuentiContest . Challenge #18

Jueves, 30 de Junio de 2011 Gaspar Fernández Sin comentarios

Últimamente he hablado acerca del I concurso de programación de Tuenti. Un concurso de programación Online que se llevó acabo durante la semana pasada (del 13 al 20 de Junio, muy mala fecha).

Podéis ver los enunciados de todos los problemas, con ejemplos sobre la entrada y salida (aunque a veces no hay que hacerles mucho caso) en la web oficial del concurso, pero en Vidas Concurrentes lo encontramos todo en español.

Challenge #18 : The Riddle

Nos mandan una imagen en base64 por la stdin (ya lo toman como costumbre) y nos dan además, una adivinanza. Se trataba de extraer texto de la imagen, pero sólo de ciertas partes, es decir, teníamos que extraer la información por columnas, siempre que éstas no fueran verdes.
Soluciones:

Si no estás en la lista y quieres plantear tu solución, deja un comentario con tu link !

Actualización 2011/07/03 01:49 : Añadida solución de @frisco82
Actualización 2011/07/03 13:52 : Añadida solución de @Rosapolis

Recopilación de soluciones para los retos de #tuentiContest . Challenge #14

Lunes, 27 de Junio de 2011 Gaspar Fernández 2 comentarios

Últimamente he hablado acerca del I concurso de programación de Tuenti. Un concurso de programación Online que se llevó acabo durante la semana pasada (del 13 al 20 de Junio, muy mala fecha).

Podéis ver los enunciados de todos los problemas, con ejemplos sobre la entrada y salida (aunque a veces no hay que haerles mucho caso) en la web oficial del concurso, pero en Vidas Concurrentes lo encontramos todo en español.

Challenge #14 : Colors are beautiful

Nos dan una imagen para que la carguemos, y luego nos dirán una componente y un número de línea, para que hagamos la suma de esa componente a lo largo de toda la línea
Soluciones:

Si no estás en la lista y quieres plantear tu solución, deja un comentario con tu link !

Actualización 2011/06/28 08:30 : Añadida solución de @theom3ga
Actualización 2011/06/28 08:35 : Añadida solución de @ricardclau
Actualización 2011/07/03 01:49 : Añadida solución de @frisco82
Actualización 2011/07/03 13:45 : Añadida solución de @Rosapolis
Actualización 2011/07/14 14:55 : Añadida solución de @lagunex

Leyendo archivos de imagen en formato BMP en C

Domingo, 26 de Junio de 2011 Gaspar Fernández Sin comentarios

n1214332197_30461081_3311160

Hoy vamos a practicar a leer una imagen desde un archivo BMP desde C. Aunque existen muchas APIs disponibles que son capaces de hacerlo, y mucho mejor que lo que voy a plantear (puesto que nos limitaremos a BMPs sin compresión y a 24bits por pixel), es un buen ejercicio para leer archivos con un formato especificado y documentado.

Para este tipo de archivos, tendremos dos cabeceras disponibles, la primera será la cabecera de fichero, y la segunda, la cabecera de información de imagen, que las definimos aquí:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct bmpFileHeader
{
  /* 2 bytes de identificación */
  uint32_t size;        /* Tamaño del archivo */
  uint16_t resv1;       /* Reservado */
  uint16_t resv2;       /* Reservado */
  uint32_t offset;      /* Offset hasta hasta los datos de imagen */
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* Tamaño de la cabecera */
  uint32_t width;               /* Ancho */
  uint32_t height;          /* Alto */
  uint16_t planes;                  /* Planos de color (Siempre 1) */
  uint16_t bpp;             /* bits por pixel */
  uint32_t compress;        /* compresión */
  uint32_t imgsize;     /* tamaño de los datos de imagen */
  uint32_t bpmx;                /* Resolución X en bits por metro */
  uint32_t bpmy;                /* Resolución Y en bits por metro */
  uint32_t colors;              /* colors used en la paleta */
  uint32_t imxtcolors;      /* Colores importantes. 0 si son todos */
} bmpInfoHeader;

Todos los campos tienen un comentario que los explica, pero los detallaré algo más:

  • De primeras nos encontramos dos bytes de identificación que no he introducido en el registro, y es así por la alineación de variables. Es mejor leerlos a parte y luego leer el struct que os presento.
  • size (en FileHeader) es el tamaño del archivo completo, lo podremos usar para comprobar si hay más datos al final del archivo, o para ver si el archivo está cortado, es decir, como comprobación de errores
  • offset (en FileHeader), es la distancia en bytes desde que termina la cabecera de información hasta que empiezan los datos. No le vamos a hacer mucho caso a esto, sólo que puede ser que el archivo tenga más información entre la cabecera y los datos.
  • headersize (en InfoHeader), será el tamaño de la cabecera, dado que hay varias versiones del formato, ésta puede ser mucho más grande, aunque nosotros sólo leeremos siempre 40bytes.
  • planes (en InfoHeader), son los planos de color. Sólo está documentado el valor 1 de este campo, por tanto será el único que tratemos.
  • bpp (en InfoHeader), son los bits por pixel de la imagen, para el ejemplo, siempre serán 24, ya que no soportamos imágenes con otra profundidad de color.
  • compress (en InfoHeader), para este ejemplo será siempre 0, ya que no trataremos compresión.
  • imgsize (en InfoHeader), será el tamaño de los datos de imagen, al igual que size nos puede servir para control de errores, y para ver cuánta memoria vamos a reservar, aunque este tamaño, para el ejemplo será ancho*alto*3 (24bits por pixel)
  • bpmx, bpmy en (InfoHeader), será la resolución de la imagen en pixels por metro.
  • colors, imxtcolors en (InfoHeader), valdrán 0 en este ejemplo, ya que no usaremos imágenes con paleta de colores.

Ya estamos preparados para leer el archivo de imagen con la siguiente función:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;     /* cabecera */
  unsigned char *imgdata;   /* datos de imagen */
  uint16_t type;        /* 2 bytes identificativos */

  f=fopen (filename, "r");
  if (!f)
    return NULL;        /* Si no podemos leer, no hay imagen*/

  /* Leemos los dos primeros bytes */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)        /* Comprobamos el formato */
    {
      fclose(f);
      return NULL;
    }

  /* Leemos la cabecera de fichero completa */
  fread(&header, sizeof(bmpFileHeader), 1, f);

  /* Leemos la cabecera de información completa */
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);

  /* Reservamos memoria para la imagen, ¿cuánta?
     Tanto como indique imgsize */

  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);

  /* Nos situamos en el sitio donde empiezan los datos de imagen,
   nos lo indica el offset de la cabecera de fichero*/

  fseek(f, header.offset, SEEK_SET);

  /* Leemos los datos de imagen, tantos bytes como imgsize */
  fread(imgdata, bInfoHeader->imgsize,1, f);

  /* Cerramos */
  fclose(f);

  /* Devolvemos la imagen */
  return imgdata;
}

Esta función devolverá los datos de la imagen como resultado de la función, y a través de un parámetro de entrada/salida, devolveremos también el infoHeader.
Lo que hacemos es simplemente leer las cabeceras, aunque para la primera cabecera, como dijimos antes, leemos primero los 2 bytes identificativos del formato de archivo, éstos tienen que valer 0×4D42 (MB), aunque como los datos están en little-endian (el byte menos significativo primero), lo leeremos al revés (BM), que corresponde a uno de los formatos de imagen que podemos alojar dentro de un BMP.

Por último, vamos a probar todo esto con un ejemplo, para no complicar las cosas con APIs gráficas, o cálculos de histogramas, se me ocurrió hacer una pequeña representación de la imagen en la consola. Para este código, creé una imagen con ImageMagick de la siguiente manera:

$ convert -background red -fill blue -font /usr/share/fonts/truetype/ttf-liberation/LiberationMono-Bold.ttf -pointsize 72 “label:Poesía Binaria” +matte poesia.bmp

+matte nos servirá para eliminar el canal alfa por defecto de la imagen.

Ahora, en nuestro código incluimos los struct y la función anteriores con un par de cosas más:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct bmpFileHeader
{
  /* 2 bytes de identificación */
  uint32_t size;        /* Tamaño del archivo */
  uint16_t resv1;       /* Reservado */
  uint16_t resv2;       /* Reservado */
  uint32_t offset;      /* Offset hasta hasta los datos de imagen */
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* Tamaño de la cabecera */
  uint32_t width;       /* Ancho */
  uint32_t height;      /* Alto */
  uint16_t planes;          /* Planos de color (Siempre 1) */
  uint16_t bpp;             /* bits por pixel */
  uint32_t compress;        /* compresión */
  uint32_t imgsize;     /* tamaño de los datos de imagen */
  uint32_t bpmx;        /* Resolución X en bits por metro */
  uint32_t bpmy;        /* Resolución Y en bits por metro */
  uint32_t colors;      /* colors used en la paleta */
  uint32_t imxtcolors;      /* Colores importantes. 0 si son todos */
} bmpInfoHeader;

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader);
void DisplayInfo(bmpInfoHeader *info);
void TextDisplay(bmpInfoHeader *info, unsigned char *img);

int main()
{
  bmpInfoHeader info;
  unsigned char *img;

  img=LoadBMP("poesia.bmp", &info);
  DisplayInfo(&info);
  TextDisplay(&info, img);

  return 0;
}

void TextDisplay(bmpInfoHeader *info, unsigned char *img)
{
  int x, y;
  /* Reducimos la resolución vertical y horizontal para que la imagen entre en pantalla */
  static const int reduccionX=6, reduccionY=4;
  /* Si la componente supera el umbral, el color se marcará como 1. */
  static const int umbral=90;
  /* Asignamos caracteres a los colores en pantalla */
  static unsigned char colores[9]=" bgfrRGB";
  int r,g,b;

  /* Dibujamos la imagen */
  for (y=info->height; y>0; y-=reduccionY)
    {
      for (x=0; x<info->width; x+=reduccionX)
    {
      b=(img[3*(x+y*info->width)]>umbral);
      g=(img[3*(x+y*info->width)+1]>umbral);
      r=(img[3*(x+y*info->width)+2]>umbral);

      printf("%c", colores[b+g*2+r*4]);
    }
      printf("\n");
    }
}

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;     /* cabecera */
  unsigned char *imgdata;   /* datos de imagen */
  uint16_t type;        /* 2 bytes identificativos */

  f=fopen (filename, "r");
  if (!f)
    return NULL;        /* Si no podemos leer, no hay imagen*/

  /* Leemos los dos primeros bytes */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)        /* Comprobamos el formato */
    {
      fclose(f);
      return NULL;
    }

  /* Leemos la cabecera de fichero completa */
  fread(&header, sizeof(bmpFileHeader), 1, f);

  /* Leemos la cabecera de información completa */
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);

  /* Reservamos memoria para la imagen, ¿cuánta?
     Tanto como indique imgsize */

  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);

  /* Nos situamos en el sitio donde empiezan los datos de imagen,
   nos lo indica el offset de la cabecera de fichero*/

  fseek(f, header.offset, SEEK_SET);

  /* Leemos los datos de imagen, tantos bytes como imgsize */
  fread(imgdata, bInfoHeader->imgsize,1, f);

  /* Cerramos */
  fclose(f);

  /* Devolvemos la imagen */
  return imgdata;
}

void DisplayInfo(bmpInfoHeader *info)
{
  printf("Tamaño de la cabecera: %u\n", info->headersize);
  printf("Anchura: %d\n", info->width);
  printf("Altura: %d\n", info->height);
  printf("Planos (1): %d\n", info->planes);
  printf("Bits por pixel: %d\n", info->bpp);
  printf("Compresión: %d\n", info->compress);
  printf("Tamaño de datos de imagen: %u\n", info->imgsize);
  printf("Resolucón horizontal: %u\n", info->bpmx);
  printf("Resolucón vertical: %u\n", info->bpmy);
  printf("Colores en paleta: %d\n", info->colors);
  printf("Colores importantes: %d\n", info->imxtcolors);
}

Compilamos el código anterior y nos queda algo como esto:
pbinaria

Lo más raro es la forma de acceder a cada pixel (podremos simplificar más adelante), y es que la imagen está almacenada boca abajo, por lo que la primera línea que leamos será la última que se debe mostrar, por otra parte, los colores están en formato BGR, y, generalmente para acceder a cada pixel lo podremos hacer con:

img[3*(x+y*info->width)+comp]

Teniendo en cuenta, como ya dijimos que la coordenada y va desde la última línea a la primera, y que:

  • Si queremos leer la componente azul, comp=0
  • Si queremos leer la componente verde, comp=1
  • Si queremos leer la componente roja, comp=2

Más adelante, iré publicando algunas aplicaciones prácticas de todo esto, y pronto, una función parecida a LoadBMP que se encargue de salvar los archivos y así empezar a probar filtros gráficos.

Actualizado: 2011/06/26 13:00 : Añadida la información de las componentes de color al final

Limitando recursos en ImageMagick

Jueves, 21 de Abril de 2011 Gaspar Fernández 2 comentarios

A veces, utilizar ImageMagick puede ser horrible, sobre todo porque en ocasiones, se come todos los recursos de nuestro sistema, al procesar imágenes grandes, múltiples imágenes, vídeo, etc… en ocasiones puede que el OOM Killer de Linux detenga la tarea, y puede que mientras procesamos las imágenes, necesitemos el ordenador para algo.

NOTA: Utilizaré convert para poner ejemplos de cómo utilizar esta herramienta, pero estos consejos valen para todos los programas de tratamiento de imagen que componen imagemagick: convert, mogrify, montage…

Bien, ImageMagick tiene opciones para modificar cuánta memoria va a acaparar. Si sabemos que es una tarea muy pesada para la memoria (una imagen de 5000×5000 ocupará cerca de 70Mb, una imagen de 10000×10000 cerca de 300Mb, sin contar la memoria utilizada para el tratamiento de la imagen) podemos decir a ImageMagick que utilice como mucho 20Mb de RAM (el resto de memoria que necesite la cogerá del disco duro, de nuestro directorio temporal).
Para esto, podemos utilizar lo siguiente:

$ convert imagenA.jpg -limit memory 20MiB -resize 50% imagenB.png

Si durante el proceso, ejecutamos top podremos ver la diferencia de memoria utilizada.
Podemos ver los recursos que podemos limitar con:

$ identify -list resource
File Area Memory Map Disk Thread Time
——————————————————————————-
768 4.2324GB 2.9563GiB 7.8835GiB 18.446744EB 1 unlimited

Las peticiones de memoria que hace ImageMagick como vemos aquí son las siguientes:
Se intenta reservar memoria para los pixels, pero si la memoria excede el límite de memory, o falla (puede que el sistema no disponga de memoria suficiente), lo escribirá en disco y lo mapeará en memoria, pero si se excede la memoria del mapa de memoria, todo va a disco, si ya no tenemos disco duro suficiente, o hemos limitado el disco disponible fallará.
Tenemos que tener en cuenta que hacemos distinción entre MiB (MebiBytes) (1MiB=1.048.576 bytes) y MB (MegaByte) (1MB = 1.000.000 bytes)

Así que también podemos limitar la memoria de los mapas de memoria, de disco, los threads a utilizar (o podemos darle más, si queremos), por ejemplo, si queremos insertar imágenes en un pdf:

$ convert *.jpg -limit memory 200MiB -limit map 200MiB resultado.pdf

También es muy útil el parámetro de area, este vale para limitar la memoria de forma absoluta, es decir, para que ImageMagick no intente hacer nada en memoria si excedemos esta cantidad de memoria (a veces es mejor limitar la memoria, antes de que intente encajar las reservas de memoria en los otros dos parámetros, y tenemos más control sobre la memoria utilizada):

$ convert *.jpg -limit area 300M resultado.pdf

Todas estas opciones de ImageMagick pueden ser variables de entorno: MAGICK_AREA_LIMIT, MAGICK_DISK_LIMIT, MAGICK_FILE_LIMIT, MAGICK_MEMORY_LIMIT, MAGICK_MAP_LIMIT, MAGICK_THREAD_LIMIT, MAGICK_TIME_LIMIT. Podemos hacer lo siguiente para ver que los cambios se aplican correctamente:

$ export MAGICK_AREA_LIMIT=50M
$ export MAGICK_MEMORY_LIMIT=10MiB
$ export MAGICK_MAP_LIMIT=30MiB
$ convert -list resource
File Area Memory Map Disk Thread Time
——————————————————————————-
768 50MB 10MiB 30MiB 18.446744EB 1 unlimited

Como habréis podido comprobar, también se puede limitar por tiempo, incluyendo -limit time [segundos] o con la variable de entorno MAGICK_TIME_LIMIT.

Pero no acaba aquí, podemos hacer que el procesado de imagen sea un poco más lento, pero nos deje el ordenador libre para otros usos, esto es con la variable de entorno MAGICK_THROTTLE, ImageMagick periodicamente dejará respirar el procesador tantos milisegundos como le hayamos asignado a la variable:

export MAGICK_THROTTLE=100

Dejará de procesar 100ms en varias ocasiones durante el proceso, lo podremos usar si vemos que el proceso será largo, no nos importa la velocidada y va a utilizar toda nuestra CPU.

También podemos acompañar todas estas opciones de ImageMagick con nice e ionice. Por ejemplo, con ionice podremos retrasar o priorizar los accesos a disco y con nice la prioridad del proceso. Si de verdad no nos importa cuánto tarde la conversión podemos hacer lo siguiente:

nice -n 20 ionice -c3 convert *.jpg -limit area 200M resultado.pdf

Con lo que asignaremos la menor prioridad al proceso, y además sólo accederá a disco cuando nadie más lo haga (si otro proceso quiere escribir en disco, convert tendrá que esperar)

Vídeo digital: Secuencias de imágenes

Jueves, 16 de Septiembre de 2010 Gaspar Fernández Sin comentarios

Esta vez voy a presentar algunos ejemplos de uso de ffmpeg con secuencias de imágenes. Para empezar a trabajar con vídeo digital desde GNU/Linux.

Crear vídeo a partir de una secuencia de imágenes

Es una buena técnica el hecho de trabajar con secuencias de imágenes para crear un vídeo. Podemos, por ejemplo utilizar la potencia de Imagemagick (I, II, III, IV) para modificar el color de la imagen, etiquetarlas o darle algún tratamiento. O incluso para manejar transparencias en vídeo de una forma controlada. Para las secuencias de imágenes es conveniente utilizar un formato de imagen sin pérdidas como png, pnm, o jpg sin pérdidas (ojo, no jpg normal).

Para hacer la conversión podemos utilizar esta orden:

$ ffmpeg -f image2 -i directorio/imag%d.png -r 25 -vcodec mpeg4 video.avi

Elegimos como entrada un formato de imágenes y las exportaremos a 25 fps, con el codec mpeg4 y el archivo se llamará video.avi. Todas las imágenes estarán dentro de directorio y se llamarán imag0.png, imag1.png, imag2.png, …

Siempre depende el codec que vayamos a utilizar, si queremos enviarlo por Internet, o grabarlo en algún soporte como montaje definitivo podemos utilizar el formato mpeg4, theora, h264, vp8, etc; si queremos grabarlo en DVD podemos escoger mpeg2, aunque si vamos a utilizarlo como parte de nuestro proceso de montaje podemos usar dv o algún codec sin pérdidas como ljpeg, jpegls, v210, etc; el objetivo, para montaje es utilizar un formato sin comprimir o comprimido sin pérdidas.

Creando un vídeo sólo con una imagen

En ocasiones, puede ser necesario crear un archivo de vídeo que sólo contenga una imagen. ¿ Por qué ? Podemos crear algún script que haga algún efecto con ese vídeo, una transición, o podemos unirlo a un vídeo, para hacer de cortinilla de inicio o cierre.

Para ello hacemos lo siguiente:

$ ffmpeg -loop_input -f image2 -i imagen.png -vframes 50 -r 10 -vcodec libtheora video.avi

Donde grabaremos 50 frames con la imagen imagen.png a 10 fotogramas por segundo y con el codec theora. Aunque, en lugar de especificar los fps podemos especificar el tiempo que queremos que dure (en hh:mm:ss.[xxx], es decir, horas, minutos, seguntos, centésimas):

$ ffmpeg -loop_input -f image2 -i imagen.png -t 00:00:5.50 -r 10 -vcodec libtheora video.avi

Donde grabaremos un vídeo de 5 segundos y medio con la misma imagen.

Pasando un vídeo a secuencia de imágenes

Ahora tratamos de hacer la primera conversión, de vídeo a imágenes, intentando escoger el codec adecuado. Aunque podemos utilizar jpg, vamos a exportar a png para dar más calidad a las imágenes:

$ ffmpeg -i video.avi -f image2 -vcodec png video/frame_%d.png

Grabaremos los distintos fotogramas del video (video.avi) en el directorio video y con archivos llamados frame_1.png, frame_2.png, frame_3.png…

Sed… de venganza (1): Sustituyendo cadenas en múltiples archivos

Lunes, 28 de Junio de 2010 Gaspar Fernández 1 comentario

3846929292_60721fb24e

Es uno de los grandes desconocidos y tan temidos comandos de que disponemos. Y es cierto que a veces da pereza mirarse el manual cuando queremos hacer algo que sed podría hacer rápidamente.

Lo que cuento hoy es su uso más popular (porque sed se puede usar para muuuuuchas cosas) y es muy simple, sustituir en un stream un texto por otro (Donde dije digo, digo Diego).

Imaginemos un fichero de texto, para ser originales llamémosle README, y en el texto queremos cambiar la palabra “Ireland” por “Spain”. Podemos hacer lo siguiente:

$ sed ’s/Ireland/Spain/g’ README

y veremos en pantalla el texto. Para guardarlo en el mismo archivo, si buscamos por Internet, veremos cómo la gente se complica la vida (que no digo que sea malo, yo también me la complico un poco más abajo), pero podemos usar el modificador -i (que tiene algunas funciones curiosas).

Si ahora hacemos:

$ sed -i ’s/Ireland/Spain/g’ README

Los cambios se guardarán automáticamente en el fichero README. Pero si investigamos un poco más, y queremos rematar la faena podemos hacer:

$ sed -i~ ’s/Ireland/Spain/g’ README

En este caso, guardaremos el cambio en el fichero README y grabaremos en README~ una copia de seguridad del fichero antiguo. La extensión de la copia de seguridad podemos cambiarla haciendo por ejemplo:

$ sed -i.bak ’s/Ireland/Spain/g’ README

Para que la copia sea README.bak

Pero ahora viene algo interesante, con el parámetro -i podemos modificar todos los archivos que queramos, sed acepta en la entrada múltiples archivos, por lo que si en un directorio con muchos archivos queremos cambiar un texto (imaginad que habéis hecho un proyecto relativamente grande, y hay una función con un nombre un poco ridículo, curiosamente es la que más veces llamáis, y como vamos a enseñar el código fuente, no queremos que nadie lea eso), podremos hacer:

$ sed -i ’s/nombre_ridiculo/nombre_elegante/g’ *

Ahora bien, si el proyecto está en múltiples directorios, siempre podemos usar find para localizar los archivos de la siguiente forma:

find -name ‘*.c’ -exec sed -i ’s/nombre_ridiculo/nombre_elegante/g’ {} \;

Con toda esta línea, buscaremos todos los archivos con extensión .c dentro del directorio actual y subdirectorios y se los pasaremos a sed, con el modificador -exec de find, ejecutaremos el comando que especificamos, donde {} indica el nombre de archivo (que nos lo da find) y con \; indicamos el fin del comando y sus parametros.

Pero esto no termina aquí, sed soporta expresiones regulares, y si por ejemplo queremos coger todas las imágenes de un fichero html, cambiarlas de directorio y añadirles un class (inicialmente encontramos <img src=”foto.jpg” alt=”" /> , y queremos que salga <img class=”imagen” src=”/static/foto.jpg” alt=”" />) podemos hacer lo siguiente:

sed ’s/src=”\(.[a-zA-Z\.\_\/]*\)”/src=”\/static\/\1” class=”imagen”/g’ fichero.html

Lo que hay en negrita, corresponde a la expresión regular que determina el nombre del archivo (caracteres de la a a la z, de la A a la Z, puntos, guiones bajos, y barras (en la cadena de origen), en la cadena de destino escribimos \1 donde queremos que coloque el texto correspondiente a la expresión anterior, es decir donde queremos que coloque el nombre del archivo.

Ni que decir tiene que podemos hacer una mezcla de todo lo dicho en este post ( expresiones regulares, y sustitución en múltiples archivos dentro de múltiples subdirectorios y guardando backups ), y estaremos delante de una potente herramienta.

Lo malo de ejecutar sed, es que tenemos que escapar muchos caracteres,por lo menos ), (, ., \, / y seguro que encontramos alguno más; para ello, siempre que queramos introducir un carácter de esos, debemos poner una contra barra (\) delante.

Foto: albertopveiga (Flickr)

Historia de Lenna

Viernes, 19 de Marzo de 2010 Gaspar Fernández Sin comentarios

lenna_mainEste post lo he rescatado de un antiguo blog que tenía allá por 2007, y es que en la Universidad estuve estudiando una asignatura, Tratamiento Digital de Imagen y había una foto que se repetía hasta la saciedad.

Todo se remonta a Julio del 1973 cuando en el Instituto de Procesamiento de Señal e Imagen del USC (California) estaban buscando una imagen para una conferencia. La imagen debía tener un gran margen dinámico. Estaban pensando en una cara, y por supuesto, qué mejor que sacarla de la revista Playboy… y como eran tontillos… Playmate de Noviembre del 1972, no, si la muchacha es guapa, aunque en la foto tenía 21 años. Y la foto no está entera, como os podéis imaginar… pero si queréis os dejo unos enlaces al final :)

Tras la conferencia mucha gente estuvo interesada en la foto, para sus investigaciones con filtros y compresión de imagen. imaginaos que sin Lenna, el JPEG no sería lo que conocemos actualmente; y bueno, que había pocas chicas en la investigación de imagen digital, todo sea dicho.

Con respecto al nombre, es Lena Söderberg, de origen sueco; pero como siempre la manía de cambiar los nombres, en la revista se llamba Lenna Sjööblom (con dos N para que los anglosajones lo pronunciaran bien). Fue el número más vendido de la historia de la revista. Aquí tenéis la ficha de Lenna.

La imagen de Lenna se ha convertido con los años en un estándar de facto de  imagen de prueba de miles de algoritmos relacionados con este campo y muy importante para la comunidad científica, tanto es así que hasta Playboy autorizó su publicación en estos círculos (normal ellos también han terminado utilizando la tecnología desarrollada por ese hecho), no denunciando ni a la revista Optical Engineering(01/1992) ni a IEEE Transactions on Image Processing (1996), ya que la foto tiene derechos. David C. Munson, editor de ésta última reista escribió una nota explicando las razones de por qué se utiliza la foto de Lenna. Más o menos viene a decir dos razones:

* Es una buena imagen de prueba, tiene regiones planas, sombras y texturas lo que es bueno para probar algoritmos de procesamiento de imágenes.

* Segundo, es una chica atractiva, y es normal que esta comunidad de investigación (casi todo hombres) giren en torno a una imagen que encuentran atractiva.

Además la imagen se ha utilizado en multitud de libros para demostrar ciertos algoritmos. Por ejemplo el que estoy leyendo ahora para la asignatura Digital Image Processing de Rafael C. Gonzalez y Richard E. Woods (muy gordo por cierto).

Ahora tenemos la otra cara de la moneda, la cara de un mundo que se empeña en criticar todo lo que se ha creado, porque es incorrecto, les parece mal. A ciertas personas les molesta que sea una mujer la que esté posando, pero les molesta aún más que provenga de una portada de Playboy, al menos está recortada, les molesta que haya salido en revistas, en libros, en miles de pruebas del desarrollo de JPEG.

Lo mejor de todo es que hasta el 1988 ella no se enteró de la repercusión que tenía, hasta que la entrevistaron de una revista de informática sueca. Y en mayo de 1997, fue invitada a la la 50th Annual Conference of the Society for Imaging Science in Technology, como parte de de la historia de la imagen digital. Y bueno, casi casi pudo montar un club de fans, todo el mundo pidiéndole autógrafos, yo creo que le hicieron más caso a ella que a todo lo demás… tantas horas delante de un ordenador mirándola fijamente, unen mucho :) Y una de sus frases fue: deben estar tan cansados de mí … ¡mirando la misma foto por todos estos años!

lenna_oldHay muy poca información sobre ella actualmente. Está casada, tiene tres hijos y trabaja para el estado. Cuando la invitaron para la conferencia que he citado antes trabajaba ayudando a discapacitados a trabajar con ordenadores.

Por cierto, el póster también sale en la película El dormilón (Original: Sleeper) de Woody Allen (1973). Lenna en Sleeper.

Aquí os dejo unos cuántos enlaces si queréis más información:

May 2001 Newsletter of the IEEE Professional Communication Society (PDF)
La Historia de Lenna
Lenna - Wikipedia
The Rest of The Lenna Story

Visita otras webs de la red