Manufaktur industri
Industri Internet of Things | bahan industri | Pemeliharaan dan Perbaikan Peralatan | Pemrograman industri |
home  MfgRobots >> Manufaktur industri >  >> Industrial programming >> VHDL

Gambar bitmap file BMP dibaca menggunakan TEXTIO

Mengonversi file gambar ke format bitmap membuat cara termudah untuk membaca gambar menggunakan VHDL. Dukungan untuk format file gambar grafik raster BMP dibangun ke dalam sistem operasi Microsoft Windows. Itu membuat BMP menjadi format gambar yang cocok untuk menyimpan foto untuk digunakan di bangku tes VHDL.

Pada artikel ini, Anda akan belajar cara membaca file gambar biner seperti BMP dan menyimpan data dalam memori dinamis di simulator. Kami akan menggunakan contoh modul pemrosesan gambar untuk mengubah gambar menjadi skala abu-abu, ini akan menjadi perangkat kami yang sedang diuji (DUT). Terakhir, kami menulis output dari DUT ke gambar baru yang dapat kami bandingkan secara visual dengan aslinya.

Posting blog ini adalah bagian dari seri tentang penggunaan perpustakaan TEXTIO di VHDL. Baca artikel lainnya di sini:

Cara menginisialisasi RAM dari file menggunakan TEXTIO

File stimulus dibaca di testbench menggunakan TEXTIO

Mengapa bitmap adalah format terbaik untuk VHDL

Format file gambar yang paling umum di internet adalah JPEG dan PNG. Keduanya menggunakan kompresi, JPEG lossy sedangkan PNG lossless. Sebagian besar format menawarkan beberapa bentuk kompresi karena ini dapat secara dramatis mengurangi ukuran penyimpanan gambar. Meskipun ini baik untuk penggunaan normal, ini tidak ideal untuk membaca di bangku tes VHDL.

Untuk dapat memproses gambar dalam perangkat lunak atau perangkat keras, Anda harus memiliki akses ke data piksel mentah dalam aplikasi Anda. Anda ingin memiliki data warna dan luminansi yang disimpan dalam matriks byte, ini disebut grafik bitmap atau raster.

Sebagian besar editor gambar terkenal seperti Photoshop atau GIMP berbasis raster. Mereka dapat membuka berbagai format gambar, tetapi semuanya dikonversi ke grafik raster secara internal di editor.

Anda juga dapat melakukan ini di VHDL, tetapi itu akan membutuhkan upaya pengkodean yang cukup besar karena tidak ada solusi siap pakai untuk mendekode gambar terkompresi. Solusi yang lebih baik adalah mengonversi gambar input pengujian ke format bitmap seperti BMP secara manual atau dengan memasukkannya ke dalam skrip yang meluncurkan testbench Anda.

Format file gambar BMP

Format file BMP didokumentasikan dengan baik di Wikipedia. Format ini memiliki banyak varian yang berbeda, tetapi kami akan menyetujui beberapa pengaturan khusus yang akan membuatnya lebih mudah bagi kami. Untuk membuat gambar input kami, kami membukanya di Microsoft Paint yang sudah diinstal sebelumnya dengan Windows. Kemudian, kita klik File→Save as , pilih Simpan sebagai jenis:Bitmap 24-bit (*bmp; *.dib) . Beri nama file dengan akhiran .bmp dan klik simpan.

Dengan memastikan bahwa file dibuat seperti ini, kita dapat mengasumsikan bahwa header selalu merupakan varian BITMAPINFOHEADER dengan panjang 54 byte dengan format piksel RGB24 yang disebutkan di halaman Wikipedia. Selanjutnya, kami hanya akan peduli tentang beberapa bidang yang dipilih di dalam header. Tabel di bawah ini menunjukkan bidang header yang akan kita baca.

Offset (Des) Ukuran (B) Diharapkan (Hex) Deskripsi
0 2 “BM” (42 4D) bidang ID
10 4 54 (36 00 00 00) Oset array piksel
14 4 40 (28 00 00 00) Ukuran tajuk
18 4 Baca nilai Lebar gambar dalam piksel
22 4 Baca nilai Tinggi gambar dalam piksel
26 1 1 (01) Jumlah bidang warna
28 1 24 (18) Jumlah bit per piksel

Nilai yang ditandai dengan warna hijau adalah satu-satunya yang benar-benar perlu kita lihat karena kita tahu nilai mana yang diharapkan di bidang header lainnya. Jika Anda setuju untuk hanya menggunakan gambar dengan dimensi tetap yang telah ditentukan sebelumnya, Anda dapat melewati seluruh header dan mulai membaca pada byte offset nomor 54 dalam file BMP, di situlah data piksel akan ditemukan.

Namun demikian, kami akan memeriksa bahwa nilai-nilai lain yang terdaftar seperti yang diharapkan. Tidak sulit untuk melakukannya karena kita sudah membaca headernya. Ini juga memberikan perlindungan terhadap kesalahan pengguna, jika Anda atau salah satu kolega Anda memberikan gambar pengkodean yang salah ke testbench kapan saja di masa mendatang.

Kasus uji

Posting blog ini adalah tentang cara membaca gambar dari file di testbench VHDL, tetapi untuk kelengkapannya, saya telah menyertakan contoh DUT. Kami akan mengalirkan data piksel melalui DUT saat kami membaca gambar. Terakhir, kami menulis hasilnya ke file BMP keluaran lain yang dapat diperiksa di penampil gambar favorit Anda.

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; 

Kode di atas menunjukkan entitas DUT kami. Modul skala abu-abu mengambil data RGB 24-bit untuk satu piksel sebagai input dan mengubahnya menjadi representasi skala abu-abu yang disajikan pada output. Perhatikan bahwa piksel keluaran mewakili bayangan abu-abu yang masih berada dalam ruang warna RGB, kami tidak mengonversi BMP menjadi BMP skala abu-abu yang merupakan format berbeda.

Modul ini murni kombinasional, tidak ada input clock atau reset. Hasilnya segera muncul pada output ketika sesuatu ditugaskan ke input. Untuk mempermudah, konversi ke skala abu-abu menggunakan perkiraan titik tetap dari nilai luma (kecerahan) menurut sistem pengkodean ITU-R BT.2100 RGB ke luma.

Anda dapat mengunduh kode untuk modul skala abu-abu dan seluruh proyek dengan menggunakan formulir di bawah ini.

Gambar Boeing 747 yang Anda lihat di bawah ini akan menjadi contoh gambar input kami. Artinya, itu bukan gambar BMP sebenarnya yang disematkan di posting blog ini, itu tidak mungkin. Ini adalah representasi JPEG dari gambar BMP yang akan kita baca di testbench kita. Anda dapat meminta gambar BMP asli dengan meninggalkan alamat email Anda di formulir di atas dan Anda akan langsung menerimanya di kotak masuk Anda.

Gambar uji berukuran 1000 x 1000 piksel. Meskipun, kode yang disajikan dalam artikel ini harus berfungsi dengan dimensi gambar apa pun asalkan dalam format BMP 24-bit BITMAPINFOHEADER. Namun, membaca dalam gambar besar akan memakan banyak waktu simulasi karena akses file di sebagian besar simulator VHDL lambat. Gambar ini berukuran 2930 kB dan perlu beberapa detik untuk dimuat di ModelSim.

Impor perpustakaan TEXTIO

Untuk membaca dari atau menulis ke file dalam VHDL, Anda perlu mengimpor perpustakaan TEXTIO. Pastikan Anda menyertakan baris dari daftar di bawah ini di bagian atas file VHDL Anda. Kita juga perlu mengimpor finish kata kunci dari paket standar untuk menghentikan simulasi ketika semua pengujian telah selesai.

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

Pernyataan di atas memerlukan VHDL-2008 atau yang lebih baru untuk digunakan.

Deklarasi jenis kustom

Kami akan mendeklarasikan beberapa jenis kustom di awal wilayah deklaratif dari testbench kami. Format struktur data untuk menyimpan data piksel bergantung pada jenis input yang diharapkan DUT. Modul skala abu-abu mengharapkan tiga byte yang masing-masing mewakili salah satu komponen warna merah, hijau, dan biru. Karena beroperasi pada satu piksel pada satu waktu, kami bebas menyimpan kumpulan piksel sesuai keinginan.

Seperti yang dapat kita lihat dari kode di bawah ini, pertama-tama kita mendeklarasikan header_type array yang akan kita gunakan untuk menyimpan semua data header. Kita akan memeriksa beberapa field di dalam header, tetapi kita juga perlu menyimpannya karena kita akan menulis data gambar yang diproses ke file baru di akhir testbench. Kemudian, kita perlu menyertakan header asli dalam gambar keluaran.

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;

Pernyataan kedua mendeklarasikan sebuah record bernama pixel_type . Jenis kustom ini akan bertindak sebagai wadah untuk data RGB untuk satu piksel.

Akhirnya, struktur data dinamis untuk menyimpan semua piksel dideklarasikan. Sedangkan row_type adalah larik tak terbatas dari pixel_type , row_pointer adalah tipe akses ke sana, penunjuk VHDL. Demikian pula, kami membuat image_type . yang tidak dibatasi array untuk menyimpan semua baris piksel.

Jadi, image_pointer type akan berfungsi sebagai pegangan untuk gambar penuh dalam memori yang dialokasikan secara dinamis.

Membuat Instansiasi DUT

Di akhir wilayah deklaratif, kami mendeklarasikan sinyal antarmuka untuk DUT, seperti yang ditunjukkan di bawah ini. Sinyal input di-postfix dengan _in dan sinyal keluaran dengan _out . Hal ini memungkinkan kita untuk dengan mudah mengidentifikasi mereka dalam kode serta dalam bentuk gelombang. DUT diinstansiasi pada awal arsitektur dengan sinyal yang ditetapkan melalui peta port.

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

Variabel proses dan penanganan file

Kami akan membuat satu proses testbench tunggal untuk memuat semua pembacaan dan penulisan file. Daerah deklaratif dari proses ditunjukkan di bawah ini. Kita mulai dengan mendeklarasikan char_file baru type untuk menentukan tipe data yang ingin kita baca dari file gambar input. File BMP dikodekan biner; oleh karena itu kami ingin beroperasi pada byte, character ketik VHDL. Pada dua baris berikutnya, kita menggunakan tipe untuk membuka file input dan output.

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

Selanjutnya, kita mendeklarasikan sebuah variabel untuk memuat data header, serta dua variabel integer untuk menampung lebar dan tinggi gambar. Setelah itu, kami mendeklarasikan row penunjuk dan image penunjuk. Yang terakhir akan menjadi pegangan kita untuk gambar lengkap setelah dibaca dari file.

Akhirnya, kami mendeklarasikan dua variabel kenyamanan; padding bertipe integer dan char bertipe character . Kami akan menggunakan ini untuk menyimpan nilai yang kami baca dari file untuk sementara.

Membaca header BMP

Pada awal badan proses, kita membaca seluruh header dari file BMP ke dalam header variabel, seperti yang ditunjukkan pada kode di bawah ini. Panjang header adalah 54 byte, tetapi alih-alih menggunakan nilai hard-coded, kami mendapatkan rentang untuk diulang dengan merujuk header_type'range atribut. Anda harus selalu menggunakan atribut jika Anda bisa untuk menjaga nilai konstan didefinisikan sesedikit mungkin.

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

Kemudian ikuti beberapa pernyataan tegas di mana kami memeriksa bahwa beberapa bidang tajuk seperti yang diharapkan. Ini adalah perlindungan yang aman terhadap kesalahan pengguna karena kami tidak menggunakan nilai baca untuk apa pun, kami hanya memeriksa apakah mereka seperti yang diharapkan. Nilai yang diharapkan adalah yang tercantum dalam tabel ini, yang ditampilkan sebelumnya di artikel.

Kode di bawah ini menunjukkan pernyataan tegas, masing-masing dengan report pernyataan yang menjelaskan kesalahan dan severity failure pernyataan untuk menghentikan simulasi jika ekspresi yang ditegaskan adalah false . Kita perlu menggunakan tingkat keparahan yang lebih tinggi karena setidaknya dengan pengaturan default di ModelSim, itu hanya akan mencetak pesan kesalahan dan melanjutkan simulasi.

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

Kemudian kita membaca bidang lebar dan tinggi gambar dari header. Ini adalah dua nilai yang sebenarnya akan kita gunakan. Oleh karena itu, kami menetapkannya ke image_width dan image_height variabel. Seperti yang dapat kita lihat dari kode di bawah ini, kita harus mengalikan byte berikutnya dengan kekuatan bobot dua nilai untuk mengubah bidang header empat byte menjadi nilai integer yang tepat.

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

Terakhir, kami mencetak tinggi dan lebar baca ke konsol simulator dengan menggunakan report pernyataan.

Membaca data piksel

Kita perlu mencari tahu berapa byte padding yang akan ada di setiap baris sebelum kita bisa mulai membaca data piksel. Format BMP mengharuskan setiap baris piksel diisi ke kelipatan empat byte. Dalam kode di bawah ini kami menangani ini dengan rumus satu baris menggunakan operator modulo pada lebar gambar.

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

Kami juga harus memesan ruang untuk semua baris data piksel yang akan kami baca. image variabel adalah tipe akses, penunjuk VHDL. Untuk membuatnya menunjuk ke ruang memori yang dapat ditulis, kami menggunakan new kata kunci untuk memesan ruang untuk image_height jumlah baris dalam memori dinamis, seperti yang ditunjukkan di bawah ini.

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

Sekarang saatnya untuk membaca data gambar. Daftar di bawah ini menunjukkan for-loop yang membaca array piksel, baris demi baris. Untuk setiap baris, kami memesan ruang untuk row_type baru objek, ditunjuk oleh row variabel. Kemudian, kita membaca jumlah piksel yang diharapkan, pertama biru, lalu hijau, dan terakhir warna merah. Ini adalah pemesanan menurut standar BMP 24-bit.

  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;

Setelah membaca payload untuk setiap baris, kami membaca dan membuang byte padding tambahan (jika ada). Terakhir, di akhir loop, kami menetapkan baris piksel dinamis baru ke slot image yang benar. Himpunan. Ketika for-loop mengakhiri image variabel harus berisi data piksel untuk seluruh gambar BMP.

Menguji DUT

Modul skala abu-abu hanya menggunakan logika kombinasional, jadi kita tidak perlu khawatir tentang jam atau sinyal reset. Kode di bawah ini melewati setiap piksel di setiap baris saat menulis nilai RGB ke input DUT. Setelah menetapkan nilai input, kami menunggu selama 10 nanodetik untuk membiarkan semua penundaan siklus delta dalam DUT terlepas. Nilai waktu apa pun yang lebih besar dari 0 akan berfungsi, atau bahkan wait for 0 ns; berulang kali.

  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;

Ketika program keluar dari pernyataan tunggu, output DUT harus berisi nilai RGB untuk warna skala abu-abu untuk piksel ini. Di akhir loop, kita membiarkan output DUT menggantikan nilai piksel yang kita baca dari file BMP input.

Menulis file BMP keluaran

Pada titik ini, semua piksel dalam image variabel seharusnya dimanipulasi oleh DUT. Saatnya menulis data gambar ke out_file objek, yang menunjuk ke file lokal bernama "out.bmp". Dalam kode di bawah ini kita menjalankan setiap piksel dalam byte header yang telah kita simpan dari file BMP input, dan menulisnya ke file output.

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

Setelah header, kita perlu menulis piksel agar kita membacanya dari file input. Dua for-loop bersarang dalam daftar di bawah ini menangani hal itu. Perhatikan bahwa setelah setiap baris kita menggunakan deallocate kata kunci untuk membebaskan memori yang dialokasikan secara dinamis untuk setiap baris. Pengumpulan sampah hanya disertakan dalam VHDL-2019, di versi VHDL sebelumnya Anda dapat mengharapkan kebocoran memori jika Anda menghilangkan baris ini. Di akhir for-loop, kami menulis byte padding jika diperlukan untuk membuat panjang baris menjadi kelipatan 4 byte.

  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;

Setelah loop dihentikan, kami membatalkan alokasi ruang memori untuk image variabel, seperti yang ditunjukkan di bawah ini. Kemudian kita tutup file dengan memanggil file_close pada pegangan file. Ini tidak sepenuhnya diperlukan di sebagian besar simulator karena file secara implisit ditutup ketika subprogram atau proses berakhir. Namun demikian, tidak ada salahnya untuk menutup file setelah Anda selesai menggunakannya.

  deallocate(image);

  file_close(bmp_file);
  file_close(out_file);

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

Di akhir proses testbench, kami mencetak pesan ke konsol ModelSim bahwa simulasi telah selesai, dengan petunjuk di mana gambar keluaran dapat ditemukan. finish kata kunci memerlukan VHDL-2008, ini adalah cara yang elegan untuk menghentikan simulator setelah semua pengujian selesai.

Gambar BMP keluaran

Gambar di bawah ini menunjukkan seperti apa file “out.bmp” setelah testbench selesai. File sebenarnya yang ditampilkan dalam posting blog ini adalah JPEG karena BMP tidak cocok untuk disematkan pada halaman web, tetapi Anda dapat meninggalkan alamat email Anda di formulir di atas untuk mendapatkan zip dengan proyek lengkap termasuk file “boeing.bmp”.

Komentar terakhir

Untuk pemrosesan gambar dalam FPGA, skema pengkodean warna YUV sering digunakan sebagai pengganti RGB. Dalam YUV komponen luma (luminance), Y, disimpan terpisah dari informasi warna. Format YUV lebih dekat dengan persepsi visual manusia. Untungnya, mudah untuk mengonversi antara RGB dan YUV.

Mengonversi RGB ke CMYK sedikit lebih rumit karena tidak ada rumus piksel satu-ke-satu.

Alternatif lain saat menggunakan skema pengkodean eksotis seperti itu adalah menciptakan format file gambar Anda sendiri. Cukup simpan susunan piksel dalam format file khusus yang diakhiri dengan ".yuv" atau ".cmyk". Tidak perlu tajuk saat Anda tahu format gambar seperti apa yang akan dimiliki piksel, lanjutkan dan baca di bangku tes Anda.

Anda selalu dapat memasukkan konversi perangkat lunak ke dalam alur desain Anda. Misalnya, secara otomatis mengonversi gambar PNG ke format BMP dengan menggunakan perangkat lunak konversi gambar baris perintah standar sebelum simulasi dimulai. Kemudian, baca di testbench Anda menggunakan VHDL seperti yang telah Anda pelajari dari artikel ini.


VHDL

  1. Hologram
  2. C# menggunakan
  3. C Penanganan Berkas
  4. Kelas File Java
  5. Cara menginisialisasi RAM dari file menggunakan TEXTIO
  6. Java BufferedReader:Cara Membaca File di Java dengan Contoh
  7. Python JSON:Encode(dumps), Decode(loads) &Baca File JSON
  8. Operasi File IO Verilog
  9. C - File Header
  10. Kamera Plenoptik