Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Industrial programming >> VHDL

Imagen de mapa de bits de archivo BMP leída usando TEXTIO

Convertir el archivo de imagen a un formato de mapa de bits es la forma más fácil de leer una imagen usando VHDL. La compatibilidad con el formato de archivo de imagen de gráficos de trama BMP está integrada en el sistema operativo Microsoft Windows. Eso hace que BMP sea un formato de imagen adecuado para almacenar fotos para su uso en bancos de pruebas VHDL.

En este artículo, aprenderá cómo leer un archivo de imagen binario como BMP y almacenar los datos en la memoria dinámica del simulador. Usaremos un módulo de procesamiento de imágenes de ejemplo para convertir la imagen a escala de grises, este será nuestro dispositivo bajo prueba (DUT). Finalmente, escribimos la salida del DUT en una nueva imagen que podemos comparar visualmente con la original.

Esta publicación de blog es parte de una serie sobre el uso de la biblioteca TEXTIO en VHDL. Lea los otros artículos aquí:

Cómo inicializar RAM desde un archivo usando TEXTIO

Archivo de estímulo leído en el banco de pruebas usando TEXTIO

Por qué el mapa de bits es el mejor formato para VHDL

Los formatos de archivo de imagen más comunes en Internet son JPEG y PNG. Ambos usan compresión, JPEG tiene pérdida mientras que PNG no tiene pérdida. La mayoría de los formatos ofrecen algún tipo de compresión porque esto puede reducir drásticamente el tamaño de almacenamiento de la imagen. Si bien esto está bien para un uso normal, no es ideal para leer en un banco de pruebas VHDL.

Para poder procesar una imagen en software o hardware, debe tener acceso a los datos de píxeles sin procesar dentro de su aplicación. Desea tener los datos de color y luminancia almacenados en una matriz de bytes, esto se denomina mapa de bits o gráficos de trama.

La mayoría de los editores de imágenes más conocidos, como Photoshop o GIMP, se basan en tramas. Pueden abrir una amplia gama de formatos de imagen, pero todos se convierten a gráficos de trama internamente en el editor.

También puede hacer esto en VHDL, pero eso requeriría un esfuerzo de codificación considerable porque no hay soluciones preparadas para decodificar imágenes comprimidas. Una mejor solución es convertir las imágenes de entrada de prueba a un formato de mapa de bits como BMP manualmente o incorporándolo al script que inicia su banco de pruebas.

El formato de archivo de imagen BMP

El formato de archivo BMP está bien documentado en Wikipedia. Este formato tiene muchas variantes diferentes, pero vamos a acordar unos ajustes concretos que nos lo van a facilitar mucho. Para crear nuestras imágenes de entrada, las abrimos en Microsoft Paint, que viene preinstalado con Windows. Luego, hacemos clic en Archivo→Guardar como , seleccione Guardar como tipo:mapa de bits de 24 bits (*bmp; *.dib) . Asigne un nombre al archivo que termine con el sufijo .bmp y haga clic en guardar.

Al asegurarnos de que el archivo se crea de esta manera, podemos suponer que el encabezado siempre es la variante BITMAPINFOHEADER de 54 bytes con formato de píxel RGB24 mencionado en la página de Wikipedia. Además, solo nos preocuparemos por unos pocos campos seleccionados dentro del encabezado. La siguiente tabla muestra los campos de encabezado que vamos a leer.

Compensación (diciembre) Tamaño (B) Esperado (Hex) Descripción
0 2 “BM” (42 4D) campo de identificación
10 4 54 (36 00 00 00) Desplazamiento de matriz de píxeles
14 4 40 (28 00 00 00) Tamaño del encabezado
18 4 Leer valor Ancho de la imagen en píxeles
22 4 Leer valor Altura de la imagen en píxeles
26 1 1 (01) Número de planos de color
28 1 24 (18) Número de bits por píxel

Los valores marcados en verde son los únicos que realmente necesitamos mirar porque sabemos qué valores esperar en los otros campos de encabezado. Si acordó usar solo imágenes de dimensiones fijas predefinidas cada vez, puede omitir todo el encabezado y comenzar a leer en el número de desplazamiento de bytes 54 dentro del archivo BMP, ahí es donde se encontrarán los datos de píxeles.

No obstante, comprobaremos que los demás valores enumerados son los esperados. No es difícil de hacer ya que ya estamos leyendo el encabezado. También proporciona una protección contra los errores del usuario, en caso de que usted o uno de sus colegas proporcione una imagen de la codificación incorrecta al banco de pruebas en cualquier momento en el futuro.

El caso de prueba

Esta publicación de blog trata sobre cómo leer una imagen de un archivo en un banco de pruebas VHDL, pero para completar, he incluido un DUT de ejemplo. Transmitiremos los datos de píxeles a través del DUT mientras leemos la imagen. Finalmente, escribimos los resultados en otro archivo BMP de salida que se puede examinar en su visor de imágenes favorito.

entity grayscale is
  port (
    -- RGB input
    r_in : in std_logic_vector(7 downto 0);
    g_in : in std_logic_vector(7 downto 0);
    b_in : in std_logic_vector(7 downto 0);

    -- RGB output
    r_out : out std_logic_vector(7 downto 0);
    g_out : out std_logic_vector(7 downto 0);
    b_out : out std_logic_vector(7 downto 0)
  );
end grayscale; 

El código anterior muestra la entidad de nuestro DUT. El módulo de escala de grises toma los datos RGB de 24 bits de un píxel como entrada y los convierte en una representación en escala de grises que se presenta en la salida. Tenga en cuenta que el píxel de salida representa un tono de gris aún dentro del espacio de color RGB, no estamos convirtiendo el BMP en un BMP en escala de grises, que es un formato diferente.

El módulo es puramente combinacional, no hay reloj ni entrada de reinicio. El resultado aparece inmediatamente en la salida cuando se asigna algo a la entrada. Para simplificar, la conversión a escala de grises utiliza una aproximación de punto fijo del valor de luma (brillo) de acuerdo con el sistema de codificación ITU-R BT.2100 RGB to luma.

Puede descargar el código para el módulo de escala de grises y todo el proyecto utilizando el siguiente formulario.

La imagen del Boeing 747 que está viendo a continuación será nuestra imagen de entrada de ejemplo. Es decir, no es la imagen BMP real la que está incrustada en esta publicación de blog, eso no sería posible. Es una representación JPEG de la imagen BMP que vamos a leer en nuestro banco de pruebas. Puede solicitar la imagen BMP original dejando su dirección de correo electrónico en el formulario de arriba y la recibirá de inmediato en su bandeja de entrada.

La imagen de prueba tiene un tamaño de 1000 x 1000 píxeles. Sin embargo, el código presentado en este artículo debería funcionar con cualquier tamaño de imagen siempre que esté en el formato BMP de 24 bits BITMAPINFOHEADER. Sin embargo, la lectura de una imagen grande requerirá mucho tiempo de simulación porque el acceso a los archivos en la mayoría de los simuladores VHDL es lento. Esta imagen tiene 2930 kB y tarda unos segundos en cargarse en ModelSim.

Importar la biblioteca TEXTIO

Para leer o escribir en archivos en VHDL, debe importar la biblioteca TEXTIO. Asegúrese de incluir las líneas de la lista a continuación en la parte superior de su archivo VHDL. También necesitamos importar el finish palabra clave del paquete estándar para detener la simulación cuando se hayan completado todas las pruebas.

use std.textio.all;
use std.env.finish;

Las declaraciones anteriores requieren el uso de VHDL-2008 o posterior.

Declaraciones de tipos personalizados

Declararemos algunos tipos personalizados al comienzo de la región declarativa de nuestro banco de pruebas. El formato de las estructuras de datos para almacenar datos de píxeles depende del tipo de entrada que espera el DUT. El módulo de escala de grises espera tres bytes, cada uno de los cuales representa uno de los componentes de color rojo, verde y azul. Debido a que opera en un píxel a la vez, somos libres de almacenar el conjunto de píxeles como queramos.

Como podemos ver en el siguiente código, primero declaramos un header_type matriz que usaremos para almacenar todos los datos del encabezado. Examinaremos algunos campos dentro del encabezado, pero también debemos almacenarlo porque vamos a escribir los datos de la imagen procesada en un nuevo archivo al final del banco de pruebas. Luego, debemos incluir el encabezado original en la imagen de salida.

type header_type  is array (0 to 53) of character;

type pixel_type is record
  red : std_logic_vector(7 downto 0);
  green : std_logic_vector(7 downto 0);
  blue : std_logic_vector(7 downto 0);
end record;

type row_type is array (integer range <>) of pixel_type;
type row_pointer is access row_type;
type image_type is array (integer range <>) of row_pointer;
type image_pointer is access image_type;

La segunda declaración declara un registro llamado pixel_type . Este tipo personalizado actuará como un contenedor para los datos RGB de un píxel.

Finalmente, se declaran las estructuras de datos dinámicas para almacenar todos los píxeles. Mientras que row_type es una matriz sin restricciones de pixel_type , el row_pointer es un tipo de acceso a él, un puntero VHDL. De manera similar, construimos un image_type sin restricciones matriz para almacenar todas las filas de píxeles.

Así, el image_pointer type funcionará como identificador de la imagen completa en la memoria asignada dinámicamente.

Instanciación del DUT

Al final de la región declarativa, declaramos las señales de interfaz para el DUT, como se muestra a continuación. Las señales de entrada tienen el postfijo _in y las señales de salida con _out . Esto nos permite identificarlos fácilmente tanto en el código como en la forma de onda. El DUT se instancia al inicio de la arquitectura con las señales asignadas a través del mapa de puertos.

signal r_in : std_logic_vector(7 downto 0);
signal g_in : std_logic_vector(7 downto 0);
signal b_in : std_logic_vector(7 downto 0);
signal r_out : std_logic_vector(7 downto 0);
signal g_out : std_logic_vector(7 downto 0);
signal b_out : std_logic_vector(7 downto 0);

begin

DUT :entity work.grayscale(rtl)
port map (
  r_in => r_in,
  g_in => g_in,
  b_in => b_in,
  r_out => r_out,
  g_out => g_out,
  b_out => b_out
);

Variables de proceso y identificadores de archivos

Crearemos un solo proceso de banco de pruebas para contener toda la lectura y escritura de archivos. La región declarativa del proceso se muestra a continuación. Empezamos declarando un nuevo char_file type para definir el tipo de datos que deseamos leer del archivo de imagen de entrada. El archivo BMP está codificado en binario; por lo tanto queremos operar en bytes, el character escriba en VHDL. En las siguientes dos líneas, usamos el tipo para abrir un archivo de entrada y uno de salida.

process
  type char_file is file of character;
  file bmp_file : char_file open read_mode is "boeing.bmp";
  file out_file : char_file open write_mode is "out.bmp";
  variable header : header_type;
  variable image_width : integer;
  variable image_height : integer;
  variable row : row_pointer;
  variable image : image_pointer;
  variable padding : integer;
  variable char : character;
begin

A continuación, declaramos una variable para contener los datos del encabezado, así como dos variables enteras para contener el ancho y el alto de la imagen. Después de eso, declaramos un row puntero y un image puntero. Este último será nuestro identificador de la imagen completa una vez que se haya leído del archivo.

Finalmente, declaramos dos variables de conveniencia; padding de tipo integer y char de tipo character . Los usaremos para almacenar valores que leemos del archivo temporalmente.

Lectura del encabezado BMP

Al comienzo del cuerpo del proceso, leemos el encabezado completo del archivo BMP en el header variable, como se muestra en el siguiente código. El encabezado tiene una longitud de 54 bytes, pero en lugar de usar el valor codificado, obtenemos el rango para iterar haciendo referencia a header_type'range atributo. Siempre debe usar atributos cuando pueda para mantener los valores constantes definidos en la menor cantidad de lugares posible.

  for i in header_type'range loop
    read(bmp_file, header(i));
  end loop;

Luego siguen algunas afirmaciones en las que verificamos que algunos de los campos del encabezado sean los esperados. Esta es una protección contra el error del usuario, ya que no usamos los valores de lectura para nada, solo verificamos que sean los esperados. Los valores esperados son los que se enumeran en esta tabla, que se muestran anteriormente en el artículo.

El siguiente código muestra las declaraciones de afirmación, cada una con un report declaración que describe el error y un severity failure instrucción para detener la simulación si la expresión afirmada es false . Necesitamos usar un nivel de gravedad elevado porque al menos con la configuración predeterminada en ModelSim, solo imprimirá un mensaje de error y continuará con la simulación.

  -- Check ID field
  assert header(0) = 'B' and header(1) = 'M'
    report "First two bytes are not ""BM"". This is not a BMP file"
    severity failure;

  -- Check that the pixel array offset is as expected
  assert character'pos(header(10)) = 54 and
    character'pos(header(11)) = 0 and
    character'pos(header(12)) = 0 and
    character'pos(header(13)) = 0
    report "Pixel array offset in header is not 54 bytes"
    severity failure;

  -- Check that DIB header size is 40 bytes,
  -- meaning that the BMP is of type BITMAPINFOHEADER
  assert character'pos(header(14)) = 40 and
    character'pos(header(15)) = 0 and
    character'pos(header(16)) = 0 and
    character'pos(header(17)) = 0
    report "DIB headers size is not 40 bytes, is this a Windows BMP?"
    severity failure;

  -- Check that the number of color planes is 1
  assert character'pos(header(26)) = 1 and
    character'pos(header(27)) = 0
    report "Color planes is not 1" severity failure;

  -- Check that the number of bits per pixel is 24
  assert character'pos(header(28)) = 24 and
    character'pos(header(29)) = 0
    report "Bits per pixel is not 24" severity failure;

Luego leemos los campos de ancho y alto de la imagen del encabezado. Estos son los dos únicos valores que realmente vamos a utilizar. Por lo tanto, los asignamos al image_width y image_height variables Como podemos ver en el siguiente código, tenemos que multiplicar los bytes subsiguientes con la potencia ponderada de dos valores para convertir los campos de encabezado de cuatro bytes en valores enteros adecuados.

  -- Read image width
  image_width := character'pos(header(18)) +
    character'pos(header(19)) * 2**8 +
    character'pos(header(20)) * 2**16 +
    character'pos(header(21)) * 2**24;

  -- Read image height
  image_height := character'pos(header(22)) +
    character'pos(header(23)) * 2**8 +
    character'pos(header(24)) * 2**16 +
    character'pos(header(25)) * 2**24;

  report "image_width: " & integer'image(image_width) &
    ", image_height: " & integer'image(image_height);

Finalmente, imprimimos la altura y el ancho de lectura en la consola del simulador usando el report declaración.

Lectura de los datos de píxeles

Necesitamos averiguar cuántos bytes de relleno habrá en cada línea antes de que podamos comenzar a leer los datos de píxeles. El formato BMP requiere que cada fila de píxeles se rellene a un múltiplo de cuatro bytes. En el siguiente código, nos ocupamos de esto con una fórmula de una sola línea usando el operador de módulo en el ancho de la imagen.

  -- Number of bytes needed to pad each row to 32 bits
  padding := (4 - image_width*3 mod 4) mod 4;

También tenemos que reservar espacio para todas las filas de datos de píxeles que vamos a leer. El image variable es un tipo de acceso, un puntero VHDL. Para que apunte a un espacio de memoria grabable usamos el new palabra clave para reservar espacio para image_height número de filas en la memoria dinámica, como se muestra a continuación.

  -- Create a new image type in dynamic memory
  image := new image_type(0 to image_height - 1);

Ahora es el momento de leer los datos de la imagen. La siguiente lista muestra el bucle for que lee la matriz de píxeles, fila por fila. Para cada fila, reservamos espacio para un nuevo row_type objeto, apuntado por el row variable. Luego, leemos el número esperado de píxeles, primero el color azul, luego el verde y finalmente el color rojo. Este es el orden según el estándar BMP de 24 bits.

  for row_i in 0 to image_height - 1 loop

    -- Create a new row type in dynamic memory
    row := new row_type(0 to image_width - 1);

    for col_i in 0 to image_width - 1 loop

      -- Read blue pixel
      read(bmp_file, char);
      row(col_i).blue :=
        std_logic_vector(to_unsigned(character'pos(char), 8));

      -- Read green pixel
      read(bmp_file, char);
      row(col_i).green :=
        std_logic_vector(to_unsigned(character'pos(char), 8));

      -- Read red pixel
      read(bmp_file, char);
      row(col_i).red :=
        std_logic_vector(to_unsigned(character'pos(char), 8));

    end loop;

    -- Read and discard padding
    for i in 1 to padding loop
      read(bmp_file, char);
    end loop;

    -- Assign the row pointer to the image vector of rows
    image(row_i) := row;

  end loop;

Después de leer la carga útil de cada línea, leemos y descartamos los bytes de relleno adicionales (si los hay). Finalmente, al final del ciclo, asignamos la nueva fila dinámica de píxeles a la ranura correcta del image formación. Cuando el ciclo for termina el image La variable debe contener datos de píxeles para toda la imagen BMP.

Prueba del dispositivo bajo prueba

El módulo de escala de grises usa solo lógica combinacional, por lo que no tenemos que preocuparnos por ningún reloj o señales de reinicio. El siguiente código pasa por cada píxel en cada fila mientras escribe los valores RGB en las entradas DUT. Después de asignar los valores de entrada, esperamos 10 nanosegundos para permitir que todos los retrasos del ciclo delta dentro del DUT se resuelvan. Siempre que funcionen valores mayores que 0, o incluso wait for 0 ns; repetido suficientes veces.

  for row_i in 0 to image_height - 1 loop
    row := image(row_i);

    for col_i in 0 to image_width - 1 loop

      r_in <= row(col_i).red;
      g_in <= row(col_i).green;
      b_in <= row(col_i).blue;
      wait for 10 ns;

      row(col_i).red := r_out;
      row(col_i).green := g_out;
      row(col_i).blue := b_out;

    end loop;
  end loop;

Cuando el programa sale de la declaración de espera, las salidas del DUT deben contener los valores RGB para el color de la escala de grises para este píxel. Al final del bucle, dejamos que la salida DUT reemplace los valores de píxel que leemos del archivo BMP de entrada.

Escribir el archivo BMP de salida

En este punto, todos los píxeles en el image La variable debería haber sido manipulada por el DUT. Es hora de escribir los datos de la imagen en el out_file objeto, que apunta a un archivo local llamado "out.bmp". En el siguiente código, revisamos cada píxel en los bytes del encabezado que hemos almacenado desde el archivo BMP de entrada y los escribimos en el archivo de salida.

  for i in header_type'range loop
    write(out_file, header(i));
  end loop;

Después del encabezado, debemos escribir los píxeles en el orden en que los leemos del archivo de entrada. Los dos bucles for anidados en la lista a continuación se encargan de eso. Tenga en cuenta que después de cada fila usamos el deallocate palabra clave para liberar la memoria asignada dinámicamente para cada fila. La recolección de basura solo se incluye en VHDL-2019, en versiones anteriores de VHDL puede esperar pérdidas de memoria si omite esta línea. Al final del ciclo for, escribimos bytes de relleno si es necesario para llevar la longitud de la fila a un múltiplo de 4 bytes.

  for row_i in 0 to image_height - 1 loop
    row := image(row_i);

    for col_i in 0 to image_width - 1 loop

      -- Write blue pixel
      write(out_file,
        character'val(to_integer(unsigned(row(col_i).blue))));

      -- Write green pixel
      write(out_file,
        character'val(to_integer(unsigned(row(col_i).green))));

      -- Write red pixel
      write(out_file,
        character'val(to_integer(unsigned(row(col_i).red))));

    end loop;

    deallocate(row);

    -- Write padding
    for i in 1 to padding loop
      write(out_file, character'val(0));
    end loop;

  end loop;

Después de que el ciclo haya terminado, desasignamos el espacio de memoria para el image variables, como se muestra a continuación. Luego cerramos los archivos llamando a file_close en los identificadores de archivos. Esto no es estrictamente necesario en la mayoría de los simuladores porque el archivo se cierra implícitamente cuando finaliza el subprograma o el proceso. Sin embargo, nunca está mal cerrar los archivos cuando haya terminado con ellos.

  deallocate(image);

  file_close(bmp_file);
  file_close(out_file);

  report "Simulation done. Check ""out.bmp"" image.";
  finish;
end process;

Al final del proceso del banco de pruebas, imprimimos un mensaje en la consola ModelSim de que la simulación ha terminado, con una pista de dónde se puede encontrar la imagen de salida. El finish La palabra clave requiere VHDL-2008, es una forma elegante de detener el simulador después de que se hayan completado todas las pruebas.

La imagen BMP de salida

La siguiente imagen muestra cómo se ve el archivo "out.bmp" después de que se haya completado el banco de pruebas. El archivo real que se muestra en esta publicación de blog es un JPEG porque los BMP no son adecuados para incrustarlos en páginas web, pero puede dejar su dirección de correo electrónico en el formulario de arriba para obtener un archivo zip con el proyecto completo, incluido el archivo "boeing.bmp".

Últimos comentarios

Para el procesamiento de imágenes en FPGA, el esquema de codificación de color YUV se usa a menudo en lugar de RGB. En YUV, el componente de luma (luminancia), Y, se mantiene separado de la información de color. El formato YUV está más relacionado con la percepción visual humana. Afortunadamente, es fácil convertir entre RGB y YUV.

Convertir RGB a CMYK es un poco más complicado porque no existe una fórmula de píxel uno a uno.

Otra alternativa cuando se utilizan esquemas de codificación tan exóticos es inventar su propio formato de archivo de imagen. Simplemente almacene las matrices de píxeles en un formato de archivo personalizado con el sufijo ".yuv" o ".cmyk". No hay necesidad de un encabezado cuando sabe qué tipo de formato de imagen tendrán los píxeles, simplemente siga adelante y léalo en su banco de pruebas.

Siempre puede incorporar una conversión de software en su flujo de diseño. Por ejemplo, convierta automáticamente una imagen PNG al formato BMP mediante el uso de un software de conversión de imágenes de línea de comandos estándar antes de que comience la simulación. Luego, léalo en su banco de pruebas usando VHDL como aprendió en este artículo.


VHDL

  1. Holograma
  2. C # usando
  3. Manejo de archivos C
  4. Clase de archivo Java
  5. Uso de firmas digitales para comprobar la integridad de los datos en Linux
  6. Cómo inicializar RAM desde un archivo usando TEXTIO
  7. Java BufferedReader:cómo leer un archivo en Java con un ejemplo
  8. Python JSON:codificar (volcados), decodificar (cargas) y leer archivos JSON
  9. Operaciones de E/S de archivos de Verilog
  10. C - Archivos de encabezado
  11. Cámara plenóptica