Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> VHDL

Растровое изображение файла BMP, прочитанное с помощью TEXTIO

Преобразование файла изображения в формат растрового изображения обеспечивает самый простой способ чтения изображения с использованием VHDL. Поддержка формата файлов растровых графических изображений BMP встроена в операционную систему Microsoft Windows. Это делает BMP подходящим форматом изображения для хранения фотографий для использования в тестовых стендах VHDL.

В этой статье вы узнаете, как читать двоичный файл изображения, такой как BMP, и сохранять данные в динамической памяти симулятора. Мы будем использовать пример модуля обработки изображений для преобразования изображения в оттенки серого, это будет наше тестируемое устройство (DUT). Наконец, мы записываем выходные данные тестируемого устройства в новое изображение, которое можно визуально сравнить с исходным.

Этот пост в блоге является частью серии статей об использовании библиотеки TEXTIO в VHDL. Прочтите другие статьи здесь:

Как инициализировать ОЗУ из файла с помощью TEXTIO

Файл стимула считывается в тестовом стенде с помощью TEXTIO

Почему растровый формат является лучшим форматом для VHDL

Наиболее распространенными форматами файлов изображений в Интернете являются JPEG и PNG. Оба они используют сжатие, JPEG с потерями, а PNG без потерь. Большинство форматов предлагают ту или иную форму сжатия, потому что это может значительно уменьшить размер хранения изображения. Это нормально для обычного использования, но не идеально для чтения на тестовом стенде VHDL.

Чтобы иметь возможность обрабатывать изображение в программном или аппаратном обеспечении, вам необходимо иметь доступ к необработанным пиксельным данным в вашем приложении. Вы хотите, чтобы данные о цвете и яркости хранились в матрице байтов, это называется растровой или растровой графикой.

Большинство известных графических редакторов, таких как Photoshop или GIMP, основаны на растровом изображении. Они могут открывать широкий спектр форматов изображений, но все они конвертируются в растровую графику внутри редактора.

Вы можете сделать это и на VHDL, но это потребует значительных усилий по кодированию, потому что не существует готовых решений для декодирования сжатых изображений. Лучшим решением будет преобразование тестовых входных изображений в растровый формат, такой как BMP, вручную или путем включения его в скрипт, который запускает ваш тестовый стенд.

Формат файла изображения BMP

Формат файла BMP хорошо задокументирован в Википедии. У этого формата есть много разных вариантов, но мы собираемся согласовать некоторые конкретные настройки, которые сделают его намного проще для нас. Чтобы создать наши входные изображения, мы открываем их в Microsoft Paint, который предустановлен в Windows. Затем мы нажимаем Файл→Сохранить как. , выберите Тип файла:24-битное растровое изображение (*bmp; *.dib). . Назовите файл с расширением .bmp и нажмите «Сохранить».

Убедившись, что файл создан таким образом, мы можем предположить, что заголовок всегда представляет собой вариант BITMAPINFOHEADER длиной 54 байта с форматом пикселей RGB24, упомянутый на странице Википедии. Кроме того, нас будут интересовать только несколько выбранных полей в заголовке. В таблице ниже показаны поля заголовков, которые мы собираемся прочитать.

<тд>2 <тд>4 <тд>4 <тд>4 <тд>4 <тд>1 <тд>1
Смещение (декабрь) Размер (B) Ожидается (Hex) Описание
0 «БМ» (42 4D) Поле идентификатора
10 54 (36 00 00 00) Смещение массива пикселей
14 40 (28 00 00 00) Размер заголовка
18 Читать значение Ширина изображения в пикселях
22 Читать значение Высота изображения в пикселях
26 1 (01) Количество цветовых плоскостей
28 24 (18) Количество бит на пиксель

Значения, отмеченные зеленым, — это единственные, на которые нам действительно нужно смотреть, потому что мы знаем, какие значения ожидать в других полях заголовка. Если вы согласились каждый раз использовать только изображения предопределенных фиксированных размеров, вы можете пропустить весь заголовок и начать чтение со смещения в байтах 54 в файле BMP, где будут найдены данные о пикселях.

Тем не менее, мы проверим, что другие перечисленные значения соответствуют ожиданиям. Это не сложно сделать, так как мы уже читаем шапку. Это также обеспечивает защиту от пользовательских ошибок, если вы или один из ваших коллег предоставите тестовому стенду образ с неправильной кодировкой в ​​будущем.

Тестовый пример

Этот пост в блоге посвящен тому, как читать изображение из файла в тестовом стенде VHDL, но для полноты картины я включил пример тестируемого устройства. Мы будем передавать пиксельные данные через тестируемое устройство по мере считывания изображения. Наконец, мы записываем результаты в другой выходной BMP-файл, который можно просмотреть в вашей любимой программе просмотра изображений.

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; 

Приведенный выше код показывает сущность нашего тестируемого устройства. Модуль оттенков серого принимает 24-битные данные RGB для одного пикселя в качестве входных данных и преобразует их в представление в оттенках серого, которое представляется на выходе. Обратите внимание, что выходной пиксель представляет собой оттенок серого, который все еще находится в цветовом пространстве RGB. Мы не преобразовываем BMP в BMP в оттенках серого, который является другим форматом.

Модуль чисто комбинационный, нет ни часов, ни входа сброса. Результат сразу появляется на выходе, когда что-то назначается на вход. Для простоты преобразование в оттенки серого использует аппроксимацию с фиксированной точкой значения яркости (яркости) в соответствии с системой кодирования RGB ITU-R BT.2100 для яркости.

Вы можете загрузить код модуля оттенков серого и всего проекта, используя форму ниже.

Изображение Boeing 747, которое вы видите ниже, будет нашим примером входного изображения. То есть это не фактическое изображение BMP, встроенное в этот пост в блоге, это было бы невозможно. Это JPEG-представление BMP-изображения, которое мы собираемся прочитать на нашем тестовом стенде. Вы можете запросить исходное изображение BMP, оставив свой адрес электронной почты в форме выше, и вы сразу получите его в свой почтовый ящик.

Размер тестового изображения составляет 1000 x 1000 пикселей. Тем не менее, код, представленный в этой статье, должен работать с любым размером изображения, если он находится в 24-битном формате BMP BITMAPINFOHEADER. Однако чтение большого изображения займет много времени на симуляцию, потому что доступ к файлам в большинстве симуляторов VHDL медленный. Это изображение имеет размер 2930 КБ, и его загрузка в ModelSim занимает несколько секунд.

Импорт библиотеки TEXTIO

Чтобы читать или записывать файлы в VHDL, вам нужно импортировать библиотеку TEXTIO. Убедитесь, что вы включили строки из приведенного ниже списка в начало файла VHDL. Нам также нужно импортировать finish ключевое слово из стандартного пакета, чтобы остановить моделирование после завершения всех тестов.

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

Приведенные выше утверждения требуют использования VHDL-2008 или более поздней версии.

Объявления пользовательских типов

Мы объявим несколько пользовательских типов в начале декларативной области нашего тестового стенда. Формат структур данных для хранения пиксельных данных зависит от того, какие входные данные ожидает ИУ. Модуль оттенков серого ожидает три байта, каждый из которых представляет один из цветовых компонентов:красный, зеленый и синий. Поскольку он работает с одним пикселем за раз, мы можем хранить набор пикселей по своему усмотрению.

Как видно из приведенного ниже кода, мы сначала объявляем header_type массив, который мы будем использовать для хранения всех данных заголовка. Мы рассмотрим некоторые поля в заголовке, но нам также необходимо сохранить его, потому что мы собираемся записать обработанные данные изображения в новый файл в конце тестового стенда. Затем нам нужно включить исходный заголовок в выходное изображение.

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;

Второй оператор объявляет запись с именем pixel_type. . Этот пользовательский тип будет действовать как контейнер для данных RGB для одного пикселя.

Наконец, объявляются динамические структуры данных для хранения всех пикселей. Пока row_type представляет собой неограниченный массив pixel_type , row_pointer является типом доступа к нему, указателем VHDL. Точно так же мы создаем неограниченный image_type массив для хранения всех строк пикселей.

Таким образом, image_pointer type будет функционировать как дескриптор полного изображения в динамически выделенной памяти.

Создание экземпляра DUT

В конце декларативной области мы объявляем интерфейсные сигналы для ИУ, как показано ниже. Входные сигналы имеют постфикс с _in и выходные сигналы с _out . Это позволяет нам легко идентифицировать их как в коде, так и в форме сигнала. DUT создается в начале архитектуры с сигналами, назначенными через карту портов.

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
);

Переменные процесса и дескрипторы файлов

Мы создадим один единственный процесс testbench, содержащий все операции чтения и записи файлов. Декларативная область процесса показана ниже. Начнем с объявления нового char_file type для определения типа данных, которые мы хотим прочитать из входного файла изображения. Файл BMP имеет двоичную кодировку; поэтому мы хотим работать с байтами, character введите VHDL. В следующих двух строках мы используем тип для открытия входного и выходного файлов.

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

Затем мы объявляем переменную, содержащую данные заголовка, а также две целочисленные переменные для хранения ширины и высоты изображения. После этого мы объявляем row указатель и image указатель. Последний будет нашим дескриптором полного образа, как только он будет прочитан из файла.

Наконец, мы объявляем две удобные переменные; padding типа integer и char типа character . Мы будем использовать их для временного хранения значений, считанных из файла.

Чтение заголовка BMP

В начале тела процесса мы читаем весь заголовок из BMP-файла в header переменная, как показано в коде ниже. Заголовок имеет длину 54 байта, но вместо использования жестко закодированного значения мы получаем диапазон для итерации, ссылаясь на header_type'range атрибут. Вы всегда должны использовать атрибуты, когда это возможно, чтобы константные значения были определены в как можно меньшем количестве мест.

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

Затем следует несколько утверждений, в которых мы проверяем, что некоторые поля заголовка соответствуют ожиданиям. Это защита от ошибок пользователя, поскольку мы ни для чего не используем прочитанные значения, мы просто проверяем, что они соответствуют ожиданиям. Ожидаемые значения перечислены в этой таблице, показанной ранее в статье.

В приведенном ниже коде показаны операторы assert, каждый из которых имеет report. оператор с описанием ошибки и severity failure оператор для остановки симуляции, если утвержденное выражение равно false . Нам нужно использовать повышенный уровень серьезности, потому что, по крайней мере, с настройками по умолчанию в ModelSim, он просто напечатает сообщение об ошибке и продолжит моделирование.

  -- 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;

Затем мы считываем поля ширины и высоты изображения из заголовка. Это единственные два значения, которые мы собираемся использовать. Поэтому мы назначаем их на image_width и image_height переменные. Как видно из приведенного ниже кода, мы должны умножить последующие байты на взвешенную степень двух значений, чтобы преобразовать четырехбайтовые поля заголовка в правильные целочисленные значения.

  -- 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);

Наконец, мы печатаем высоту и ширину чтения в консоли симулятора, используя report заявление.

Чтение данных пикселей

Нам нужно выяснить, сколько байтов заполнения будет в каждой строке, прежде чем мы сможем начать чтение данных пикселей. Формат BMP требует, чтобы каждая строка пикселей была дополнена числом, кратным четырем байтам. В приведенном ниже коде мы позаботимся об этом с помощью однострочной формулы, используя оператор модуля по ширине изображения.

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

Мы также должны зарезервировать место для всех строк пиксельных данных, которые мы собираемся считывать. image переменная — это тип доступа, указатель VHDL. Чтобы указать на область памяти, доступную для записи, мы используем new ключевое слово для резервирования места для image_height количество строк в динамической памяти, как показано ниже.

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

Теперь пришло время прочитать данные изображения. В приведенном ниже листинге показан цикл for, который считывает массив пикселей построчно. Для каждой строки мы резервируем место для нового row_type объект, на который указывает row переменная. Затем мы считываем ожидаемое количество пикселей, сначала синего, затем зеленого и, наконец, красного цвета. Это порядок согласно 24-битному стандарту BMP.

  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;

После чтения полезной нагрузки для каждой строки мы читаем и отбрасываем лишние байты заполнения (если они есть). Наконец, в конце цикла мы назначаем новую динамическую строку пикселей правильному слоту image. множество. Когда цикл for завершает image переменная должна содержать пиксельные данные для всего изображения BMP.

Тестирование тестируемого устройства

Модуль оттенков серого использует только комбинационную логику, поэтому нам не нужно беспокоиться о каких-либо тактовых сигналах или сигналах сброса. Приведенный ниже код просматривает каждый пиксель в каждой строке, записывая значения RGB на входы ИУ. После присвоения входных значений мы ждем 10 наносекунд, чтобы позволить всем задержкам дельта-циклов в тестируемом устройстве раскрутиться. Будут работать любые значения времени больше 0 или даже wait for 0 ns; повторяется достаточное количество раз.

  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;

Когда программа выходит из оператора ожидания, выходные данные тестируемого устройства должны содержать значения RGB для оттенка серого для этого пикселя. В конце цикла мы позволяем выходу DUT заменить значения пикселей, которые мы считываем из входного BMP-файла.

Запись выходного файла BMP

В этот момент все пиксели в image переменная должна была управляться тестируемым устройством. Пришло время записать данные изображения в out_file объект, который указывает на локальный файл с именем «out.bmp». В приведенном ниже коде мы просматриваем каждый пиксель в байтах заголовка, которые мы сохранили из входного файла BMP, и записываем их в выходной файл.

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

После заголовка нам нужно записать пиксели в том порядке, в котором мы их считывали из входного файла. Об этом позаботятся два вложенных цикла for в листинге ниже. Обратите внимание, что после каждой строки мы используем deallocate ключевое слово для освобождения динамически выделенной памяти для каждой строки. Сборка мусора включена только в VHDL-2019, в предыдущих версиях VHDL вы можете ожидать утечки памяти, если пропустите эту строку. В конце цикла for мы записываем байты заполнения, если это необходимо, чтобы длина строки стала кратной 4 байтам.

  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;

После завершения цикла мы освобождаем место в памяти для image переменная, как показано ниже. Затем мы закрываем файлы, вызывая file_close на файловых дескрипторах. В большинстве симуляторов это не является строго необходимым, поскольку файл неявно закрывается при завершении подпрограммы или процесса. Тем не менее никогда не будет ошибкой закрыть файлы, когда вы закончите с ними работать.

  deallocate(image);

  file_close(bmp_file);
  file_close(out_file);

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

В конце процесса тестового стенда мы выводим на консоль ModelSim сообщение о том, что симуляция окончена, с подсказкой, где можно найти выходное изображение. finish для ключевого слова требуется VHDL-2008, это элегантный способ остановить симулятор после завершения всех тестов.

Выходное изображение BMP

На изображении ниже показано, как выглядит файл «out.bmp» после завершения тестового стенда. Фактический файл, показанный в этом сообщении блога, представляет собой файл JPEG, поскольку файлы BMP не подходят для встраивания на веб-страницы, но вы можете оставить свой адрес электронной почты в форме выше, чтобы получить zip-архив с полным проектом, включая файл «boeing.bmp».

Последние замечания

Для обработки изображений в ПЛИС часто используется схема кодирования цвета YUV вместо RGB. В YUV компонент яркости Y хранится отдельно от информации о цвете. Формат YUV более тесно связан с визуальным восприятием человека. К счастью, преобразовать RGB в YUV несложно.

Преобразование RGB в CMYK немного сложнее, потому что не существует однозначной формулы для пикселей.

Другой альтернативой при использовании таких экзотических схем кодирования является изобретение собственного формата файла изображения. Просто сохраните массивы пикселей в пользовательском формате файла с суффиксом «.yuv» или «.cmyk». Нет необходимости в заголовке, когда вы знаете, какой формат изображения будет иметь пиксели, просто прочтите его в своем тестовом стенде.

Вы всегда можете включить преобразование программного обеспечения в процесс проектирования. Например, автоматически конвертируйте изображение PNG в формат BMP с помощью стандартного программного обеспечения для преобразования изображений из командной строки перед началом моделирования. Затем прочитайте его в своей тестовой среде, используя VHDL, как вы узнали из этой статьи.


VHDL

  1. Голограмма
  2. С# с использованием
  3. C Обработка файлов
  4. Класс файла Java
  5. Как инициализировать RAM из файла с помощью TEXTIO
  6. Java BufferedReader:как читать файл в Java с примером
  7. Python JSON:кодировать (дампы), декодировать (загружать) и читать файл JSON
  8. Операции ввода-вывода файлов Verilog
  9. C — заголовочные файлы
  10. Пленоптическая камера