日曜技術者のメモ

趣味でやった事のメモ書きです。

VivadoHLSで射影変換を実装してみた-2-

前回の続き

se.hatenablog.jp

Vivado HLS

射影変換は以下図のように2モジュールに分けて実装をした。
f:id:ginnyu-tei:20170708185251j:plain:w200

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でシミュレーションしてみると毎サイクル出てくるのが確認できる。
f:id:ginnyu-tei:20170708190530j:plain:w200

そして回路規模
f:id:ginnyu-tei:20170708203559j:plain
積和除算があるので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リード・ライト共にシングルアクセスである。
f:id:ginnyu-tei:20170708214451j:plain:w200

↓回路規模
f:id:ginnyu-tei:20170708214833j:plain

Vivado

これでモジュールが完成したので実機で試す。
TOPの配線は全てIP Integratorで済ます。
↓が全て配線したTOP
f:id:ginnyu-tei:20170708215611j:plain:w200

↓概略図
f:id:ginnyu-tei:20170708221823j:plain:w200

まず、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で動かした結果が↓
f:id:ginnyu-tei:20170705210623j:plain:w200
前回のソフトで動作させた結果とほぼ同じ形に変形している。

この状態でフレームレートを測定した。
フレームレートの測定はhomography_dmaのap_doneを
Pmodに出力しオシロスコープで間隔を測定した。
↓がオシロスコープの画面
f:id:ginnyu-tei:20170708225509j:plain:w200
ap_doneは1パルスしか出なくオシロで取り逃す可能性がある
そこでap_done毎に信号を反転するモジュールを入れている。
その結果、約24fps出ている事が分かった。
Twitterでは約11fpsとツイートしたが(↓)あれは測定ミスでした。)

以上で射影変換の実装&実機テストは完了である。
今回は射影変換をお手軽に実装するという脳内テーマで
スタートしたので速度や規模はあまり気にしていなかった。
(無事動いたのでOKとする)
とは言え、homography_dmaは150MHz(ピクセルクロックの3倍)で動作させているのに
24フーレムしか出ていないので、次回は原因についてと高速化について言及する。