TEXTIO を使用して読み取った BMP ファイルのビットマップ イメージ
画像ファイルをビットマップ形式に変換すると、VHDL を使用して画像を読み取る最も簡単な方法になります。 BMP ラスター グラフィック イメージ ファイル形式のサポートは、Microsoft Windows オペレーティング システムに組み込まれています。これにより、BMP は VHDL テストベンチで使用する写真を保存するのに適した画像形式になります。
この記事では、BMP などのバイナリ イメージ ファイルを読み取り、そのデータをシミュレーターの動的メモリに格納する方法を学習します。画像をグレースケールに変換するための画像処理モジュールの例を使用します。これが被試験デバイス (DUT) になります。最後に、DUT からの出力を、元の画像と視覚的に比較できる新しい画像に書き込みます。
このブログ投稿は、VHDL での TEXTIO ライブラリの使用に関するシリーズの一部です。ここで他の記事を読む:
TEXTIO を使用してファイルから RAM を初期化する方法
TEXTIO を使用してテストベンチで読み込まれた刺激ファイル
ビットマップが VHDL に最適なフォーマットである理由
インターネット上で最も一般的な画像ファイル形式は、JPEG と PNG です。どちらも圧縮を使用し、JPEG は非可逆ですが、PNG は可逆です。ほとんどの形式では、画像のストレージ サイズを大幅に削減できるため、何らかの形式の圧縮が提供されています。これは通常の使用では問題ありませんが、VHDL テストベンチでの読み取りには適していません。
ソフトウェアまたはハードウェアで画像を処理できるようにするには、アプリケーション内の生のピクセル データにアクセスできる必要があります。色と輝度のデータをバイトのマトリックスに格納したい場合、これはビットマップまたはラスター グラフィックスと呼ばれます。
Photoshop や GIMP などの有名な画像エディタのほとんどは、ラスター ベースです。さまざまな画像形式を開くことができますが、それらはすべてエディター内部でラスター グラフィックに変換されます。
これは VHDL で行うこともできますが、圧縮された画像をデコードする既製のソリューションがないため、かなりのコーディング作業が必要になります。より良い解決策は、テスト入力画像を BMP のようなビットマップ形式に手動で変換するか、テストベンチを起動するスクリプトに組み込むことです。
BMP 画像ファイル形式
BMP ファイル形式は、ウィキペディアで詳しく説明されています。この形式にはさまざまなバリエーションがありますが、いくつかの特定の設定に同意することで、より簡単になります。入力画像を作成するには、Windows にプリインストールされている Microsoft Paint で開きます。次に、[ファイル] → [名前を付けて保存] をクリックします 、ファイルの種類:24 ビット ビットマップ (*bmp; *.dib) を選択します .ファイルに .bmp サフィックスで終わる名前を付けて、[保存] をクリックします。
ファイルがこのように作成されていることを確認することで、ヘッダーは常に 54 バイト長の BITMAPINFOHEADER バリアントであり、Wikipedia ページで言及されているピクセル形式 RGB24 であると想定できます。さらに、ヘッダー内のいくつかの選択されたフィールドのみを気にします。以下の表は、これから読み取るヘッダー フィールドを示しています。
オフセット (12 月) | サイズ (B) | 予想 (16 進数) | 説明 |
---|---|---|---|
0 | 2 | 「BM」(42 4D) | ID フィールド |
10 | 4 | 54 (36 00 00 00) | ピクセル配列オフセット |
14 | 4 | 40 (28 00 00 00) | ヘッダー サイズ |
18 | 4 | 読み取り値 | 画像の幅 (ピクセル単位) |
22 | 4 | 読み取り値 | ピクセル単位の画像の高さ |
26 | 1 | 1 (01) | 色平面の数 |
28 | 1 | 24 (18) | 1 ピクセルあたりのビット数 |
緑色でマークされた値は、他のヘッダー フィールドで期待される値を知っているため、実際に確認する必要がある唯一の値です。事前定義された固定サイズの画像のみを毎回使用することに同意した場合は、ヘッダー全体をスキップして、BMP ファイル内のバイト オフセット番号 54 から読み取りを開始できます。ここにピクセル データが見つかります。
それにもかかわらず、リストされている他の値が期待どおりであることを確認します。すでにヘッダーを読んでいるので、難しくありません。また、将来いつでも、あなたまたはあなたの同僚の 1 人が間違ったエンコーディングのイメージをテストベンチに提供した場合に、ユーザー エラーに対する保護を提供します。
テスト ケース
このブログ投稿は、VHDL テストベンチでファイルから画像を読み取る方法についてですが、完全を期すために DUT の例を含めました。画像を読み取るときに、DUT を介してピクセル データをストリーミングします。最後に、結果を別の出力 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;
上記のコードは、DUT のエンティティを示しています。グレースケール モジュールは、1 ピクセルの 24 ビット RGB データを入力として受け取り、それを出力に表示されるグレースケール表現に変換します。出力ピクセルはまだ RGB 色空間内のグレーの色合いを表していることに注意してください。BMP を別の形式であるグレースケール BMP に変換していません。
モジュールは純粋に組み合わせであり、クロックまたはリセット入力はありません。何かが入力に割り当てられると、結果はすぐに出力に表示されます。簡単にするために、グレースケールへの変換では、ITU-R BT.2100 RGB から輝度へのコーディング システムに従って、輝度 (明るさ) 値の固定小数点近似を使用します。
以下のフォームを使用して、グレースケール モジュールとプロジェクト全体のコードをダウンロードできます。
下に表示されているボーイング 747 の写真は、サンプルの入力画像になります。つまり、このブログ投稿に埋め込まれているのは実際の BMP 画像ではありません。これは、テストベンチで読み取る BMP 画像の JPEG 表現です。上記のフォームにメール アドレスを入力して、元の BMP 画像をリクエストすると、すぐに受信トレイに届きます。

テスト画像は 1000 x 1000 ピクセルの大きさです。ただし、この記事で紹介するコードは、BITMAPINFOHEADER 24 ビット BMP 形式である限り、どの画像サイズでも機能するはずです。ただし、ほとんどの VHDL シミュレーターではファイル アクセスが遅いため、大きなイメージを読み込むと、シミュレーションに多くの時間がかかります。この画像は 2930 kB で、ModelSim に読み込むのに数秒かかります。
TEXTIO ライブラリをインポート
VHDL でファイルを読み書きするには、TEXTIO ライブラリをインポートする必要があります。以下のリストの行を VHDL ファイルの先頭に必ず含めてください。 finish
もインポートする必要があります 標準パッケージのキーワードを使用して、すべてのテストが完了したときにシミュレーションを停止します。
use std.textio.all; use std.env.finish;
上記のステートメントでは、VHDL-2008 以降を使用する必要があります。
カスタム型宣言
テストベンチの宣言領域の先頭で、いくつかのカスタム タイプを宣言します。ピクセル データを格納するためのデータ構造の形式は、DUT が想定する入力の種類によって異なります。グレースケール モジュールは、赤、緑、青のいずれかの色成分を表す 3 バイトを想定しています。一度に 1 つのピクセルで動作するため、必要に応じてピクセル セットを自由に保存できます。
以下のコードからわかるように、最初に 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;
2 番目のステートメントは、pixel_type
という名前のレコードを宣言します。 .このカスタム タイプは、1 ピクセルの RGB データのコンテナーとして機能します。
最後に、すべてのピクセルを格納するための動的データ構造が宣言されます。 row_type
の間 pixel_type
の制約のない配列です 、row_pointer
それへのアクセス タイプ、VHDL ポインターです。同様に、制約のない image_type
を構築します ピクセルのすべての行を格納する配列
したがって、image_pointer
type は、動的に割り当てられたメモリ内のフル イメージへのハンドルとして機能します。
DUT のインスタンス化
以下に示すように、宣言領域の最後で、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 );
プロセス変数とファイル ハンドル
すべてのファイルの読み取りと書き込みを含む単一のテストベンチ プロセスを作成します。プロセスの宣言領域を以下に示します。新しい char_file
を宣言することから始めます type を使用して、入力画像ファイルから読み取りたいデータ型を定義します。 BMP ファイルはバイナリ エンコードされています。したがって、バイト、character
で操作したい VHDL で入力します。次の 2 行では、type を使用して入力ファイルと出力ファイルを開きます。
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
次に、ヘッダー データを格納する変数と、画像の幅と高さを保持する 2 つの整数変数を宣言します。その後、 row
を宣言します ポインターと image
ポインター。後者は、ファイルから読み取られた後の完全なイメージへのハンドルになります。
最後に、2 つの便利な変数を宣言します。 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
を持つ assert ステートメントを示しています。 エラーと 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;
次に、ヘッダーから画像の幅と高さのフィールドを読み取ります。これらは、実際に使用する 2 つの値のみです。したがって、それらを image_width
に割り当てます と image_height
変数。以下のコードからわかるように、4 バイトのヘッダー フィールドを適切な整数値に変換するために、後続のバイトに重み付けされた 2 の累乗を掛ける必要があります。
-- 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 形式では、ピクセルの各行を 4 バイトの倍数にパディングする必要があります。以下のコードでは、画像の幅にモジュロ演算子を使用するワンライナー式でこれを処理しています。
-- Number of bytes needed to pad each row to 32 bits padding := (4 - image_width*3 mod 4) mod 4;
また、読み取るピクセル データのすべての行のスペースを予約する必要があります。 image
variable はアクセス タイプ、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 画像全体のピクセル データが含まれている必要があります。
DUT のテスト
グレースケール モジュールは組み合わせロジックのみを使用するため、クロックやリセット信号について心配する必要はありません。以下のコードは、RGB 値を DUT 入力に書き込みながら、すべての行のすべてのピクセルを処理します。入力値を割り当てた後、10 ナノ秒待機して、DUT 内のすべてのデルタ サイクル遅延を巻き戻します。 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;
プログラムが待機ステートメントを終了すると、DUT 出力には、このピクセルのグレースケール カラーの RGB 値が含まれている必要があります。ループの最後で、入力 BMP ファイルから読み取ったピクセル値を DUT 出力に置き換えます。
出力 BMP ファイルの書き込み
この時点で、image
内のすべてのピクセルが 変数は DUT によって操作されているはずです。画像データを out_file
に書き込む時が来ました このオブジェクトは、「out.bmp」という名前のローカル ファイルを指します。以下のコードでは、入力 BMP ファイルから保存したヘッダー バイトのすべてのピクセルを実行し、それらを出力ファイルに書き込みます。
for i in header_type'range loop write(out_file, header(i)); end loop;
ヘッダーの後に、入力ファイルから読み取った順序でピクセルを書き込む必要があります。以下のリストにある 2 つのネストされた 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」ファイルの様子を示しています。 BMP は Web ページへの埋め込みに適していないため、このブログ投稿に示されている実際のファイルは JPEG ですが、「boeing.bmp」ファイルを含む完全なプロジェクトを含む zip を取得するには、上記のフォームに電子メール アドレスを残すことができます。 /P>
FPGA での画像処理では、RGB の代わりに YUV カラー エンコード スキームがよく使用されます。 YUV では、輝度 (ルミナンス) コンポーネントである Y は、色情報とは別に保持されます。 YUV 形式は、人間の視覚により密接にマッピングされています。幸いなことに、RGB と YUV の間の変換は簡単です。
RGB から CMYK への変換は、1 対 1 のピクセル式がないため、もう少し複雑です。
このようなエキゾチックなエンコーディング スキームを使用する場合の別の方法は、独自の画像ファイル形式を発明することです。ピクセル配列を「.yuv」または「.cmyk」の接尾辞が付いたカスタム ファイル形式で保存するだけです。ピクセルの画像形式がわかっている場合は、ヘッダーは必要ありません。テストベンチでそれを読み取るだけです。
ソフトウェア変換はいつでもデザイン フローに組み込むことができます。たとえば、シミュレーションを開始する前に、標準のコマンドライン画像変換ソフトウェアを使用して、PNG 画像を BMP 形式に自動的に変換します。次に、この記事で学んだように、VHDL を使用してテストベンチで読み取ります。 フィギュア>
最後の発言
VHDL