ビジョンライブラリを使った高位合成の方法【(1) Csim】

ビジョンライブラリ
スポンサーリンク

本記事の概要

概要

Vitisビジョンライブラリでテストベンチ関数を使ったCシミュレーションの方法を解説。
用例のcustomconvに含まれるFilter2D_accelを例にラプラシアンフィルタの高位合成と実装をめざす(本記事はCシミュレーションのみ)

Xilinxの統合ソフトウェア開発環境では、Vitisビジョンライブラリ(以下、ビジョンライブラリ)をインクルードすることによって、画像処理機能を高速化(アクセラレーション)することが可能です。例えば、OpenCVのフィルタ処理などを、FPGAに実装可能なロジックへと高位合成することができます。

ビジョンライブラリのインクルードの方法は以下の記事にまとめています。

本サイトでは実際にVitisビジョンライブラリのFilter2D_accel関数を例に、ラプラシアンフィルタを行う画像処理IPのCシミュレーション、高位合成、C/RTL協調シミュレーション、そして実装を進めていこうと思います。最終的にラプラシアンフィルタをZynqに実装し、空間フィルタ処理した画像をディスプレイに表示させてみようと思います。

まず、本記事では、Filter2D関数の動作をテストベンチでシミュレーションしす。シミュレーションは、C/C++で記述されたソースコードが所望の機能(ラプラシアンフィルタ)を満足するかどうかを確認するCシミュレーションです。

まず、テストベンチ関数の処理の内容を理解し、そのうえで動作を確認できればと思います。

この記事の対象読者
  • Vitisビジョンライブラリを初めて扱う
  • ビジョンライブラリのテストベンチの方法を知りたい
  • ビジョンライブラリの各パラメータの設定方法を知りたい

Filter2D_accelについて

まず、今回用例に採用するFilter2D_accel関数について簡単に述べておきます。

Filter2Dは、入力画像に対し線形フィルタによる空間フィルタリング処理を行う関数で、OpenCVにも実装されています。

空間フィルタリングとは、入力画像の対応する画素値だけでなく周囲の画素も含めた特定の領域内の画素値を用いて計算する画像処理のことを言います。空間フィルタは線形フィルタと非線形フィルタに分類され、周囲の画素を用いた演算が次の式のように線形変換で与えられる空間フィルタのことを、線形フィルタといいます。

線形フィルタを用いた空間フィルタリング処理

$$ I_{\rm output}(x_i,y_j) = \sum_{n=-W}^{W} \sum_{m=-W}^W I_{\rm input}(x_{i+m}, y_{j + n}) K(m, n) $$

ただし、

  • \( I_{\rm output}(x_i,y_j) \):入力画像
  • \( I_{\rm input}(x_i,y_j) \):出力画像
  • \( K(m, n) \):カーネルと呼ばれる線形フィルタの係数を表す行列。カーネルの大きさは\((2W+1) \times (2W+1)\)。

要するに、Filter2Dは、入力画像に対してカーネル行列を使った畳み込み(convolution)処理を行う関数です。そのため、ビジョンライブラリの用例は、customconv(custom convolutionの略か?)という名前でディレクトリが用意されています。

線形フィルタを用いた空間フィルタリング処理では、以下のような画像処理を施すことができます。

線形フィルタを用いた空間フィルタリング処理の例
  1. 平滑化(smoothing):周囲の画素との平均をとる。ノイズを軽減することが可能
  2. エッジ抽出(edge extraction):周囲との画素との差分をとる。画素値の急峻な変化を抽出することが可能。
  3. 鮮鋭化(sharpening):入力画像と平滑化画像との差分をとる。元の画像を維持しつつ、エッジを強調する

今回は、エッジ抽出の一つである4近傍ラプラシアンフィルタを例にしてみましょう。カーネルが

$$ K =
\begin{pmatrix}
0 & 1 & 0 \\
1 & -4 & 1 \\
0 & 1 & 0
\end{pmatrix}
$$

で与えられる横方向と縦方向の2次微分の和を出力する線形フィルタです。

ラプラシアンフィルタ

$$ I_{\rm output} (x_i,y_j) = \Delta I_{\rm input} (x_i, y_j) = \left(\frac{\partial^2}{\partial x^2} + \frac{\partial^2}{\partial y^2} \right) I_{\rm input} (x_i,y_j) $$

※実際の画像は離散的なので微分ではなく差分です。

おすすめ書籍

画像処理は、まず次の教科書がおすすめです。CG-ARTS協会が監修で、基本事項が網羅的にまとめられていますし、何より図解が多くわかりやすいです。画像処理だけでなく、必要となる光学の基礎や、カメラやディスプレイの動作原理、規格など、ハードウェアに関する最低限の知識が記載されているのもよかったです。

ある程度読んでから、力試しに画像処理エンジニア検定の問題集を解いてみると理解が深まります。いっそ受験してみて資格を取ってしまうのもよいかもしれませんね。

テストベンチを読み込む

まずは、ビジョンライブラリからFilter2D関数を読み込みましょう。読み込む方法は以下の記事を参考にしてください。

Vitis HLSを立ち上げ、空のプロジェクトを作成します。

ビジョンライブラリにおけるL1ディレクトリの用例customconvからテストベンチと高位合成対象のソースコードをそれぞれプロジェクト内にコピーします。

customconvのパス

LIBRARY_ROOT \vision\L1\examples\customconv から

  • xf_custom_convolution_accel.cpp : 高位合成の対象となる関数
  • xf_custom_convolution_config.h : xf_custom_convolution_accel.cppのヘッダファイル
  • xf_custom_convolution_tb.cpp : テストベンチ関数

LIBRARY_ROOT \vision\L1\examples\customconv\build から

  • xf_config_params.h : 各種パラメータを指定したヘッダファイル

ここで、 LIBRARY_ROOT はVitisライブラリのパスを指します。例えば、私の環境では [LIBRARY_ROOT = C:\Xilinx\Vitis_Libraries-master]です。

処理する入力画像もプロジェクトにコピーしておきましょう。今回は、解像度640×480のディスプレイに投影するため、解像度160×120の次のビットマップ画像を用意しました。640×480の画像でもよいのですが、高位合成後のロジック規模が大きくなりすぎたので、解像度を下げています。

テストベンチ関数xf_custom_convolution_tb.cppをTest Benchに、高位合成の対象となるxf_custom_convolution_accel.cppをSourceに登録します。

次に、Project>Project Settingと順に選択し、SimulationとSynthesisの設定タブでそれぞれFlagを設定し、ビジョンライブラリとOpenCVライブラリをインクルード、そしてリンクするためのフラグを設定します。

Vitis HLSのFlagの設定方法

(A) Simulationの設定(Cシミュレーションのための設定)

①まず、[Edit CFLAGS…]をクリックし、立ち上がったダイアログに
[-I LIBRARY_ROOT \vision\L1\include -I OPENCV_BUILD_PATH \install\include -std=c++14 -Wno-unknown-pragmas]
を入力します。
例えば、私の場合は、 LIBRARY_ROOT = C:\Xilinx\Vitis_Libraries-master、 OPENCV_BUILD_PATH = C:\opencv_hls\buid_mingw としたので、

-IC:\Xilinx\Vitis_Libraries-master\vision\L1\include -IC:\opencv_hls\opencv\build_mingw\install\include -std=c++14 -Wno-unknown-pragmas

と入力しました。

-std=の後は、Xilinxのドキュメンテーションではc++14ではなくc++0xが指定されています。

stdオプションはC++のコンパイラを利用するときに、明示的にどのバージョンのC++の機能を使うのかを示すものです。c++0xは09年以前のリリースをめざしていた名残で0xとつけられています。実際は、計画が変更され2011年にリリースされたのでC++11のことを指します(参照)

私のビルド環境では、c++0x でCシミュレーションを進めると大量にWarningが出てしまったので、c++14に変更しました。

②次に、[Linker Flags]に、
[-L OPENCV_BUILD_PATH \install\x64\mingw\lib -lopencv_core440 -lopencv_imgcodecs440 -lopencv_imgproc440]
を入力します。例えば、私の場合は、

-LC:\opencv\build_ming\install\x64\mingw\lib -lopencv_core440 -lopencv_imgcodecs440 -lopencv_imgproc440

と入力します。

③Input Argumentsには入力画像のパスを設定しました。

(B) Synthesisの設定(高位合成のための設定)

①Top Functionsに、右の[Browse…]をクリックし、高位合成の対象となる関数名を選択します。例えば今回は、”Filter2D_accel”となります。

②[Edit CFLAGS…]をクリックし、立ち上がったダイアログに
[-I LIBRARY_ROOT \vision\L1\include -std=c++14]
を入力します。例えば、私の場合は、

-IC:\Xilinx\Vitis_Libraries-master\vision\L1\include -std=c++14

と入力しました。 Synthesisの方にはOpenCVのパスを含めないように注意しましょう。

以上で、Vitis HLSへの関数の読み込みは終了です。

テストベンチの構成

パラメータ設定

今回の解像度の画像に対しラプラシアンフィルタの処理を行うため、ヘッダーファイルとテストベンチ関数のパラメータを変更しました。

xf_config_params.h

まず、xf_config_params.hから。次のようにパラメータを設定します。

#define FILTER_HEIGHT 3
#define FILTER_WIDTH 3

/*  set the optimization type  */

#define NO 1 // Normal Operation
#define RO 0 // Resource Optimized

/* set the output type */

#define OUT_8U 1
#define OUT_16S 0

#define GRAY 0
#define RGBA 1

それぞれのパラメータの意味は以下の通り。

パラメータ説明今回
FILTER_HIGHT
FILTER_WIDTH
カーネルの要素数4近傍ラプラシアンフィルタでは3×3行列とする
Optimization TypeNO:NPPCを1にする
RO:NPPCを8にする
静止画出力でレイテンシーには余裕があるので、NPPC(1クロック当たりのピクセル数)を1とするNOを選択
参考:Matクラスについて
Output Type出力画像のピクセルのTypeを選択入力画像は符号なし8it分解能のビットマップ。出力も同様のビットマップとするためOUT_8Uを選択
ChannelGRAY:グレースケール
RGBA:RGB3色
入力画像はカラー画像のため、RGBAを選択

xf_custom_convolution_config.h

xf_config_params.hを上の表のように設定すると、自動的に#if-#else-#endif構文により入力画像と出力画像を格納するMatオブジェクトのTypeや配列のデータ幅が xf_custom_convolution_config.h で設定されます。

ビジョンライブラリにおけるxf::cv::Mat型の使い方については、次の記事をご覧ください。

xf_custom_convolution_config.h で、ユーザーが変更しなくてはならないのは、HEIGHTとWIDTHとSHIFTの3つです。

このうちHEIGHTとWIDTHは、入力画像と出力画像を格納するMatオブジェクトのテンプレート引数そのものなので、特に上記の記事をご覧いただければ説明は不要でしょう。

SHIFTは、空間フィルタリングのカーネルを定義するときのビット右シフトの回数を表します。

例えば、カーネルに次の平滑化フィルタを設定する場合を考えましょう(customconvはデフォルトでこのフィルタが設定されています)。

$$ K =
\begin{pmatrix}
1/9 & 1/9 & 1/9 \\
1/9 & 1/9 & 1/9 \\
1/9 & 1/9 & 1/9
\end{pmatrix} =
\begin{pmatrix}
0.1111 & 0.1111 & 0.1111 \\
0.1111 & 0.1111 & 0.1111 \\
0.1111 & 0.1111 & 0.1111
\end{pmatrix}
$$

Filter_2D_accelでは、カーネルを表す配列のデータ型がshort int、すなわち符号付き2byte整数型です。しかし、平滑化フィルタでは小数を入力しなければ正しく計算できません。

そこで、用いるのが右シフトです。カーネルを0.1111ではなく、3640に設定し、SHIFTを15とします。そうすると、

$$ 3640>>15 = 3640 / 2^{15} \simeq 0.1111 $$

なので、小数を表現することができます。

ラプラシアンフィルタの場合、整数なのでビットシフトを設ける必要はありませんが、次のように8ビット分シフトさせて小数も代入できるようにしました。

#define HEIGHT 120
#define WIDTH 160

/*  specify the shift parameter */
#define SHIFT 8
    filter_ptr[0] = 0; //0;
    filter_ptr[1] = 1 << SHIFT;
    filter_ptr[2] = 0; //0;

    filter_ptr[3] = 1 << SHIFT;
    filter_ptr[4] = -4 << SHIFT;
    filter_ptr[5] = 1 << SHIFT;

    filter_ptr[6] = 0; //0;
    filter_ptr[7] = 1 << SHIFT;
    filter_ptr[8] = 0; //0;

一連の処理の流れ

次に、具体的にテストベンチの処理を確認しましょう。
テストベンチxf_custom_convolution_tb.cppのメイン関数は以下の順に処理がなされています。

xf_custom_convolution_tb.cppにおける処理
処理説明
①入力画像の読み込みcv::Matクラスのオブジェクトin_imgに入力画像を代入
②カーネルの生成(OpenCV用)OpenCVのfilter2D関数で用いるカーネルを
cv::Matクラスのオブジェクトfilterに代入
③参照画像の算出OpenCVのfilter2D関数を呼び出し、参照画像を算出
cv::Matクラスのオブジェクトocv_refに参照画像を代入。
ref_img.jpgというファイルに参照画像を出力。
④カーネルの生成(ビジョンライブラリ用)ビジョンライブラリのFilter2D_accel関数で用いる
カーネルを配列filter_ptrに代入
⑤出力画像の算出ビジョンライブラリのFilter2D_accelを呼び出し、
出力画像を算出

cv::Matクラスのオブジェクトout_imgに出力画像を代入。
out_img.jpgというファイルに出力画像を出力。
⑤差分画像の取得と比較・判定out_imgとref_imgの差分を計算し、差分画像diffを
diff_img.jpgというファイルに出力。
差分画像の最小・最大誤差と、閾値を上回る誤差をもつピクセル数を計算・出力し、
正しくシミュレーションできているか判定

OpenCVのcv::Matクラスとのデータの受け渡し

「⑤出力画像の算出」でOpenCVのcv::Matクラスのオブジェクトimg_inから、ビジョンライブラリのxf::cv::MatクラスのオブジェクトimgInputにデータを移すための受け渡し方法をまとめておきます。

Filter2D_accel関数を呼び出すとき、OpenCVのcv::Matクラスのオブジェクトを引数にとれず、配列しか渡すことができません。そこで、テストベンチ関数と高位合成対象の関数では、次のようにして画像を渡しています。

高位合成を行う対象のソースファイル内では、OpenCVのライブラリを参照することはできません。ビジョンライブラリの関数でないと高位合成できないためです。

そのため、テストベンチでは、cv::Matのオブジェクトにおけるdata配列(のポインタ)を高位合成する対象の関数に渡して、関数xf::cv::Array2xfMatを用いて配列をxf::cv::Matのオブジェクトへと変換します。

xf::cv::Matクラスのオブジェクトへと変換したら、空間フィルタリングを行う関数xf::cv::filter2Dに代入します。

高位合成対象の関数内で所望の機能の処理を行ったのち、関数 xf::cv::xfMat2ArrayでMatオブジェクトを配列に変換してから、テストベンチ関数に戻ります。

Cシミュレーションの結果

では、実際にCシミュレーションを実行してみましょう。

実行の結果は、無事誤差なく出力できていました。

Filter2D_accel関数の出力であるout_img.jpgも差分処理した画像が出力されていました。
ラプラシアンフィルタを用いることによって、画素値が大きく変わるエッジ部分のみが強調されています。

誤差がないので当然ですが、参照画像との差分も問題なく真っ黒の画像が得られていました。

まとめ

Filter2D_accel関数のCシミュレーションを行い、無事に所望の機能が得られることが確認できました。
引き続き、次の記事では高位合成を行った結果や協調シミュレーションの結果を示していきたいと思います。

最後までご覧いただきありがとうございました!

スポンサーリンク


参考:開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vitis コア開発キット – 2021.2

次回の記事のリンク

コメント