VivadoHLSで射影変換を実装してみた-2-
前回の続き
Vivado HLS
射影変換は以下図のように2モジュールに分けて実装をした。
fifo使ってモジュールを小分けにすると
実装が楽になったり速度が出やすくなるので良く使います。
ここら辺の話は需要があればblogかどっかで纏めるかも。
homography
まずはhomographyのC++コード
void homography( int a, int b, int c, int d, int e, int f, int g, int h, unsigned int *addr) { #pragma HLS INTERFACE s_axilite port=a bundle=reg #pragma HLS INTERFACE s_axilite port=b bundle=reg #pragma HLS INTERFACE s_axilite port=c bundle=reg #pragma HLS INTERFACE s_axilite port=d bundle=reg #pragma HLS INTERFACE s_axilite port=e bundle=reg #pragma HLS INTERFACE s_axilite port=f bundle=reg #pragma HLS INTERFACE s_axilite port=g bundle=reg #pragma HLS INTERFACE s_axilite port=h bundle=reg #pragma HLS INTERFACE ap_fifo port=addr unsigned int u,v; for (int y = 0 ; y < 768 ; y++){ for (int x = 0;x < 1024; x++){ #pragma HLS PIPELINE u = (x*a + y*b + c) / (x*g + y*h + 65536); v = (x*d + y*e + f) / (x*g + y*h + 65536); int tmp = v*1024+u; if ((v >= 0) & (v < 768) & (u >= 0) & (u < 1024)){ *addr = tmp; } else { *addr = 786433; //786432 + 1 } } } }
↑の図では書いていなかったがlパラメータa~hはAXI LiteのレジスタにしてCPUから設定。
射影変換の式はC++コードそのままで、変換後座標を画像左上から順に変化させ
変換前座標を計算し、そこからメモリアドレスを計算してFIFOに入れる。
変換後座標値の値によっては範囲外の変換前座標になる事があるので
その時は範囲外という意味で1024*768+1の値をFIFOに入れる。
ここではパイプラインオプションを入れているので毎サイクル計算結果が出力される。
modelsimでシミュレーションしてみると毎サイクル出てくるのが確認できる。
そして回路規模
積和除算があるのでDSPはそこそこ使ってる。
homography_dma
次はhomography_dmaのコード
void homography_dma( unsigned int *i_data, unsigned int *o_data, unsigned int *addr ){ #pragma HLS INTERFACE ap_fifo port=addr #pragma HLS INTERFACE m_axi depth=786432 port=i_data #pragma HLS INTERFACE m_axi depth=786432 port=o_data unsigned int tmp; for (int i = 0 ; i < 1024*768; i++){ #pragma HLS PIPELINE tmp = *addr; if (tmp > 786432){ o_data[i] = 0; } else { o_data[i] = i_data[tmp]; } } }
homographyで計算したアドレスを使ってo_dataから出力するだけ。
元画像の範囲外座標は黒色を出力する。
なお、今回は実装していないがi_data[tmp]だけリードせず、
例えば2x2pixを読み込みしてバイリニアで補完したり、
4x4pixでバイキュービックで補完すると画質が良くなるはずである。
(この際はhomographyから1次元アドレスではなく座標値を送る用変更が必要)
↓がSim結果
AXIリード・ライト共にシングルアクセスである。
↓回路規模
Vivado
これでモジュールが完成したので実機で試す。
TOPの配線は全てIP Integratorで済ます。
↓が全て配線したTOP
↓概略図
まず、0x10000000にCPUから画像データを書き込む。
その後、0x10000000からhomography_dmaモジュールが画像を読みこみ0x11000000に書き出す。
アナログRGBモジュールは0x10000000にある画像データを出力する。
なお、上図には書いていないが書くモジュールのap_startはVIOで制御している。
テスト稼働時はVIOを使うと非常に楽である。
本当は射影変換とアナログRGBモジュールのFPSが異なるので
フレームの同期が取れていないが今回はそのままにしておく。
静止画であれば気にならないと思う。
SDK
以下がPSで動かすプログラムである。
テンプレートにあるHello Worldを改変して使っている。
#include <stdio.h> #include "platform.h" #include "xil_printf.h" #include "test.h" int main() { int i,j; char pixel[3]; unsigned int R,G,B; volatile unsigned int *Addr; volatile unsigned int *reg_base; Addr = (unsigned int*)0x10000000; reg_base = (unsigned int*)0x43C00000; init_platform(); Xil_DCacheDisable(); print("Hello World\n\r"); for (j = 0 ; j < 768;j++){ for (i = 0 ; i < 1024;i++){ HEADER_PIXEL(header_data,pixel); R = pixel[0] & 0xFF; G = pixel[1] & 0xFF; B = pixel[2] & 0xFF; Addr[j*1024+i]= (R << 16) | (G << 8) | (B); //Addr[j*1024+i]=j*1024+i; } } /射影変換パラメータ設定 reg_base[4] = 0x2C6C6; reg_base[6] = 0xF6EE; reg_base[8] = 0xFCD32CF7; reg_base[10] = 0x248A; reg_base[12] = 0x2C492; reg_base[14] = 0xFE1BDF57; reg_base[16] = 0x49; reg_base[18] = 0x2A; cleanup_platform(); return 0; }
ここで画像データをCPUに書き込むが、
その画像データはGIMPで作成している。
これについては別途blogに書く予定。
Zedboardでテスト
Zedboardで動かした結果が↓
前回のソフトで動作させた結果とほぼ同じ形に変形している。
この状態でフレームレートを測定した。
フレームレートの測定はhomography_dmaのap_doneを
Pmodに出力しオシロスコープで間隔を測定した。
↓がオシロスコープの画面
ap_doneは1パルスしか出なくオシロで取り逃す可能性がある
そこでap_done毎に信号を反転するモジュールを入れている。
その結果、約24fps出ている事が分かった。
Twitterでは約11fpsとツイートしたが(↓)あれは測定ミスでした。)
Vivado HLSで射影変換を実装してみた。
— michu (@ginnyu_tei) 2017年7月5日
OpenCV(上)とFPGA出力(下)がほぼ一致。
速度はXGA@11fps位 pic.twitter.com/2izcWD0QPz
以上で射影変換の実装&実機テストは完了である。
今回は射影変換をお手軽に実装するという脳内テーマで
スタートしたので速度や規模はあまり気にしていなかった。
(無事動いたのでOKとする)
とは言え、homography_dmaは150MHz(ピクセルクロックの3倍)で動作させているのに
24フーレムしか出ていないので、次回は原因についてと高速化について言及する。