FC2カウンター FPGAの部屋 2016年03月

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた4(stereo_match_cam.cpp の作成)

OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた3”の続き。

前回で、”OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた”が上手く行った。でも、これは、右目カメラ画像と左目カメラ画像をBMPファイル変換して、そのBMPファイルに対して stereo_match を起動して、disparity を計測した。これだとBMPファイルにする手間があるので、右目カメラ画像と左目カメラ画像をバッファしているフレームバッファから直接、画像を取得して、stereo_match するようにソフトウェアを書き換えた。これを stereo_match_cam.cpp とした。

stereo_match.cpp には、Copyright が書いてあるので、stereo_match_cam.cpp の全文をブログに書くことはせずに変更点だけを載せようと思う。
まずは、インクルード文や、define 文などを追加した。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>

using namespace cv;

#define PIXEL_NUM_OF_BYTES    4
#define NUMBER_OF_WRITE_FRAMES    3 // Note: If not at least 3 or more, the image is not displayed in succession.

#define XGA_HORIZONTAL_PIXELS   1024
#define XGA_VERTICAL_LINES       768
#define XGA_ALL_DISP_ADDRESS    (XGA_HORIZONTAL_PIXELS * XGA_VERTICAL_LINES * PIXEL_NUM_OF_BYTES)
#define XGA_3_PICTURES          (XGA_ALL_DISP_ADDRESS * NUMBER_OF_WRITE_FRAMES) 

#define SVGA_HORIZONTAL_PIXELS  800
#define SVGA_VERTICAL_LINES     600
#define SVGA_ALL_DISP_ADDRESS   (SVGA_HORIZONTAL_PIXELS * SVGA_VERTICAL_LINES * PIXEL_NUM_OF_BYTES)
#define SVGA_3_PICTURES         (SVGA_ALL_DISP_ADDRESS * NUMBER_OF_WRITE_FRAMES)

static void print_help()
{


次に、argc < 3 の時に help を表示する機能を削除して、”-h”オプションでヘルプを表示する機能を追加した。

    /* if(argc < 3)
    {
        print_help();
        return 0;
    } */


    forint i = 1; i < argc; i++ )
    {
        if ( argv[i][0] == '-' && argv[i][1] == 'h')
        {
            print_help();
            return 0;
        }
    }


次に、udmabuf の処理を追加した。

    StereoBM bm;
    StereoSGBM sgbm;
    StereoVar var;

    // Start by marsee
    unsigned char  attr[1024];
    unsigned long  phys_addr;

    // udmabuf0
    int fdf = open("/dev/udmabuf0", O_RDWR | O_SYNC); // frame_buffer, The cache is disabled. 
    if (fdf == -1){
        fprintf(stderr, "/dev/udmabuf0 open error\n");
        exit(-1);
    }
    volatile unsigned *frame_buffer = (volatile unsigned *)mmap(NULL, SVGA_3_PICTURES+XGA_3_PICTURES+SVGA_ALL_DISP_ADDRESS, PROT_READ|PROT_WRITE, MAP_SHARED, fdf, 0);
    if (!frame_buffer){
        fprintf(stderr, "frame_buffer mmap error\n");
        exit(-1);
    }

    // phys_addr of udmabuf0
    int fdp = open("/sys/devices/virtual/udmabuf/udmabuf0/phys_addr", O_RDONLY);
    if (fdp == -1){
        fprintf(stderr, "/sys/devices/virtual/udmabuf/udmabuf0/phys_addr open error\n");
        exit(-1);
    }
    read(fdp, attr, 1024);
    sscanf((const char *)attr, "%lx", &phys_addr);  
    close(fdp);
    printf("phys_addr = %x\n", (unsigned int)phys_addr);
    // End by marsee

    forint i = 1; i < argc; i++ )
    {


img1_filename と img2_filename が無い時の処理を削除した。

    /*if( !img1_filename || !img2_filename )
    {
        printf("Command-line parameter error: both left and right images must be specified\n");
        return -1;
    } */


左と右目のカメラのフレームバッファの画像をMat にコピーした。なお、この方法については、”OpenCVでピクセルにアクセスする方法4つ”の”その1. ポインタを取得してピクセルの値を取得・設定"を参照させて頂いた。
次に、フレームバッファの画像は 800 x 600 ピクセルなので、640 x 480 ピクセルにリサイズした。800 x 600 の画像でやってみたのだが、left , right のポップアップ・ウインドウに画像が表示されなかった。

    int color_mode = alg == STEREO_BM ? 0 : -1;
    //Mat img1 = imread(img1_filename, color_mode);
    //Mat img2 = imread(img2_filename, color_mode);

    // Start by marsee
    Mat imgL_t(Size(800600), CV_8UC3);
    Mat imgR_t(Size(800600), CV_8UC3);

    volatile unsigned int *Leye_addr = frame_buffer; // The Left Camera Image
    volatile unsigned int *Reye_addr = (volatile unsigned int *)((unsigned)frame_buffer+SVGA_3_PICTURES+0x8); // The Right Camera Image

    // Copy "Left Camera Image" to Mat
    for (int y=0; y<imgL_t.rows; y++){
        Vec3b* ptr = imgL_t.ptr<Vec3b>(y);
        for (int x=0; x<imgL_t.cols; x++){
            int blue = Leye_addr[y*imgL_t.cols+x] & 0xff;
            int green = (Leye_addr[y*imgL_t.cols+x] & 0xff00)>>8;
            int red = (Leye_addr[y*imgL_t.cols+x] & 0xff0000)>>16;
            ptr[x] = Vec3b(blue, green, red);
        }
    }
    // Copy "Right Camera Image" to Mat
    for (int y=0; y<imgR_t.rows; y++){
        Vec3b* ptr = imgR_t.ptr<Vec3b>(y);
        for (int x=0; x<imgR_t.cols; x++){
            int blue = Reye_addr[y*imgR_t.cols+x] & 0xff;
            int green = (Reye_addr[y*imgR_t.cols+x] & 0xff00)>>8;
            int red = (Reye_addr[y*imgR_t.cols+x] & 0xff0000)>>16;
            ptr[x] = Vec3b(blue, green, red);
        }
    }

    Mat img1(Size(640480), CV_8UC3);
    Mat img2(Size(640480), CV_8UC3);

    resize(imgL_t, img1, img1.size(), 00, INTER_CUBIC);
    resize(imgR_t, img2, img2.size(), 00, INTER_CUBIC);
    // End by marsee

    if( scale != 1.f )
    {


変更してから、g++_opencv でコンパイルした。(opencv 用のg++ コンパイルコマンドを作ってある)
./stereo_match_cam -i intrinsics.yml -e extrinsics.yml
コマンドで起動した。
stereo_calib_86_160330.png

left, right, disparity ウインドウが開いた。
stereo_calib_87_160330.jpg

上手く行った。これでソフトウェアでは、上手く行ったので、ハードウェアでのアクセラレーション方法を考えよう。
  1. 2016年03月31日 03:17 |
  2. OpenCV
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する9

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する8”の続き。

今回は、AXI4 Stream 版の hls::LineBuffer と hls::Window を使用したラプラシアンフィルタが動作周波数がどこまで行けるかを確かめる。この AXI4 Stream 版のラプラシアンフィルタは、”Vivado HLS の LOOP_TRIPCOUNTディレクティブ”を使用して、高位合成時のLatency や Interval を表示できるようにしている。

solution2 を新規作成して、Solution Setting の Synthesis Settings の Clock Period を 4 (ns) に設定した。
これで、C コードの合成を行った。結果を下に示す。
LineBuffer_Window_114_160329.png

Clock Period が 4 ns で合成しても 6.65 ns の Clock Period になってしまったので、この辺が限界のようだ。
周期が 6.65 ns と言うことは、150 MHz ということになる。
Latency が 3087 クロック、Interval が 3087 クロックになっていた。前回の Clock Period が 10 ns で合成した時と比べて 7 クロック増えている。
更に、solution1 に比べて solution2 のVerilog HDL ファイルの数が増えているのがわかると思う。このように、動作周波数を上げると演算が分割されるからなのか?Verilog HDL ファイルの数が増えて行く。VHDL ファイルも同様の傾向がある。

次にリソース使用量を示す。左が Clock Period が 10 ns で合成した時で、右が、Clock Period が 4 ns で合成した時だ。
LineBuffer_Window_107_160326.pngLineBuffer_Window_115_160329.png

BRAM_18K と DSP48E は変化がないが、FF がかなり増えて、LUT も少し増えているのが分かった。

C/RTL コシミュレーションを行った。Latency は、3108 クロックだった。
LineBuffer_Window_116_160329.png

C/RTL コシミュレーション波形を Vivado で表示した。
LineBuffer_Window_117_160329.png

入力ストリームのTVALID, TREADY を見ても途中でディアサートされる場面が無いし、出力ストリームのTVALID, TREADY を見ても、やはり、途中でディアサートされる場面が無い。1 クロックで 1 ピクセルを処理できるパイプラインが生成されているようだ。

これで、hls::LineBuffer と hls::Window を使用したラプラシアンフィルタは終わりとする。
  1. 2016年03月29日 04:38 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の LOOP_TRIPCOUNTディレクティブ

ループで実行 される反復回数の合計を指定する LOOP_TRIPCOUNT ディレクティブの使い方を紹介する。

AXI4 Stream を使用して、AXI VDMA IP にプロトコルを合わせると、user 信号が画像フレームの始まりに指定されているため、user 信号がアサートされるまで、wait する必要がある。下にその部分を示す。

    do {
    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.user == 0);


このような wait ループがあると、高位合成した際に Latency や Interval が ? になってしまう。
LineBuffer_Window_110_160327.png

そのような場合に do ループに LOOP_TRIPCOUNT ディレクティブを指定すると Latency や Interval に値が出るようだ。
なお、LOOP_TRIPCOUNT ディレクティブを指定しても合成結果に影響はないそうである。
Vivado HLS Directive Editor で LOOP_TRIPCOUNT ディレクティブを挿入するダイアログを下に示す。max=1 だけを設定した。
LineBuffer_Window_111_160327.png

C++ ソースコードに、”#pragma HLS LOOP_TRIPCOUNT max=1”が挿入された。
LineBuffer_Window_112_160327.png

これで高位合成を行った。
LineBuffer_Window_113_160327.png

Latency は 3080 、 Interval は 3081 になった。
  1. 2016年03月27日 20:59 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する8

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する7”の続き。

前回までで、AXI4 Master 版は終了として、今回は、AXI4 Stream 版で hls::LineBufferとhls::Window を使用して実装してみようと思う。
まずは、実装したC++ ソースコードの lap_filter_axis.cpp を下に示す。

//
// lap_filter_axis.cpp
// 2015/05/01
// 2015/06/25 : 修正、ラプラシアンフィルタの値が青だけ担っていたので、RGBに拡張した
// 2016/03/23 : hls::LineBuffer, hls::Window を使用した
//

#include <stdio.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <hls_video.h>

#include "lap_filter_axis.h"

int conv_rgb2y(int rgb);

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs){
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;

    hls::LineBuffer<2, HORIZONTAL_PIXEL_WIDTH, int> linebuf;
    hls::Window<33int> mbuf;

    int gray_pix, val, i, j, x, y;

    const int lap_weight[3][3] = {{-1, -1, -1},{-18, -1},{-1, -1, -1}};

    do {    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.user == 0);

    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            mbuf.shift_left();    // mbuf の列を1ビット左シフト
            mbuf.insert(linebuf(1,x), 22);
            mbuf.insert(linebuf(0,x), 12);
            gray_pix = conv_rgb2y(pix.data);
            mbuf.insert(gray_pix, 02);

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(gray_pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    val += lap_weight[j][i] * mbuf(2-j,i);
                }
            }
            if (val<0// 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            lap.data = (val<<16)+(val<<8)+val;
            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので0とする
                lap.data = 0;

            if (x==0 && y==0// 最初のデータでは、TUSERをアサートする
                lap.user = 1;
            else
                lap.user = 0;
            
            if (x == (HORIZONTAL_PIXEL_WIDTH-1))    // 行の最後で TLAST をアサートする
                lap.last = 1;
            else
                lap.last = 0;

            outs << lap;    // AXI4-Stream へ出力
         }
     }
     return(0);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}


その他のソースコードは、”Vivado HLS勉強会5(AXI4 Stream)を公開しました”を参照のこと。

ZYBO 用の Vivado HLS 2015.4 プロジェクトを作成した。Target は 10 (ns) とした。
LineBuffer_Window_104_160326.png

C シミュレーションを行った。問題なく通った。
LineBuffer_Window_105_160326.png

C++ ソースコードの合成結果を示す。
LineBuffer_Window_106_160326.png

LineBuffer_Window_107_160326.png

やはり、AXI4 Master よりもリソース使用量が少ない。

C/RTL コシミュレーションを行った。
LineBuffer_Window_108_160326.png

3101 クロックかかった。64 x 48 ピクセルは 3072 なので、1 クロックで 1 ピクセルを処理できている。

C/RTL コシミュレーション波形を見た。
LineBuffer_Window_109_160326.png

入力ストリームのTVALID, TREADY を見ても途中でディアサートされる場面が無いし、出力ストリームのTVALID, TREADY を見ても、やはり、途中でディアサートされる場面が無い。1 クロックで 1 ピクセルを処理できるパイプラインが生成されているようだ。
  1. 2016年03月26日 04:43 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

ZYBO ボードの状況

ZYBO ボードの状況を書いておきたいと思います。

今、ZYBO ボードにそれぞれCMOS カメラを付けて、ステレオカメラにしていますが、やはり取り付けが甘く、特に首を降ってしまうと左右カメラの上下位置が狂ってしまいます。次にカメラ・インターフェース基板を作る時は、ステレオカメラにするか?ステレオカメラの取付方法を考えて作るとして、今の上下位置をどうしよう?と考えながら、ジョイフル本田(ホームセンターです)に行くと強力洗濯バサミが売っていたので、買ってきました。
職場のステレオカメラを見ていたら、割り箸と洗濯バサミでやってみようということでやってみたら、なかなか良い感じです。
ZYBO_pictures_1_160325.jpg
見てくれは悪いですが、これで様子を見ようと思います。
ステレオカメラ処理をハードウェア・アクセラレーションするのが目標です。OpenCVのことがわかっていないこともあって、OpenCV の勉強を頑張ろうと思います。

最初に購入したZYBO ボードの電源スイッチ(スライドスイッチ)が壊れてしまいました。スイッチオンしても、電源が入ったり、切れたりを繰り返してしまいます。手応えも軽くなって、寿命のようです。
同じものが手に入らないかな?と思って、Digilentの方に聞いてみたら、Mountain Switch 102-1221 だということでしたが、製造中止だったので、端子ピッチが 4.7 mm で同じな、マルツのMS-12AAP1 を購入しました。
でも疑問なのは、どう見てもスライドスイッチの端子ピッチが 4 mm 位だったんです。 4.7 mm で合うのかな?と半信半疑でした。
電源用のスライドスイッチを取り外して、マルツのMS-12AAP1 を入れてみたら、案の定、端子ピッチが合いません。。。orz
仕方がないので、足を曲げて、上のランドにハンダ付けしました。
ZYBO_pictures_3_160325.jpg

全体像はこんな感じです。手前の4個並んだスライドスイッチと電源スイッチは同じ部品でした。
ZYBO_pictures_2_160325.jpg
電源も正常に入って、Ubuntu も起動したので、良しとします。。。
  1. 2016年03月26日 03:32 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する7

”hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する6”の続き。

前回は、 memcpy() の代わりに for 文で1行分 Read, Write を行う実装を試してみた。今回は、2 つの実装を実際に使用する 800 x 600 ピクセルの解像度に変更して、どの位リソースを使うかと、今までは 100 MHz で合成してきたが、どの位の動作周波数で合成できるかを探ってみた。

最初は、”hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する3”の conv_rgb2y() のインスタンス数を削減したバージョンの内側の for ループに PIPELINE II=1 ディレクティブを挿入した場合とする。
LineBuffer_Window_21_160317.png

ただし、冒頭にも書いたとおりに、800 x 600 ピクセルの画像を処理できるようにラプラシアンフィルタ処理領域を変更してある。
LineBuffer_Window_89_160324.png

HDL への合成結果を示す。
LineBuffer_Window_90_160324.png
Latency は、480022 クロックだった。800 x 600 ピクセルの総ピクセル数は 480000 なので、1 ピクセルを 1 クロックで処理できている。

リソース使用量を示す。左側が 64 x 48 ピクセルの時のリソース使用量で、右側が今回の 800 x 600 ピクセルの時のリソース使用量だ。BRAMは 800 x 600 ピクセルの時に 2 つ使用するようになっている。
LineBuffer_Window_23_160317.pngLineBuffer_Window_91_160324.png

Analysis 表示を示す。
LineBuffer_Window_92_160324.png

次に、動作周波数を 250 MHz ( 4 ns) に設定した。これで、合成を行った。
LineBuffer_Window_93_160324.png
Estimated は 5.28 (ns) だった。動作周波数にすると、189 MHz となった。
Latency は 480033 クロックなので、1 ピクセルを 5.28 ns で処理できるとすると、2.53 ms で処理が完了する。前回のC/RTL コシミュレーションから 2 倍のクロックがかかるだろうということが予測できるので、5.06 ms になる。でも、すべてシングル転送と言うことを考えるとどうだろうか?

リソース使用量を下に示す。Target を 10 (ns) に設定した方を左に、Target を 4 (ns) に設定した方を右に表示する。
LineBuffer_Window_91_160324.pngLineBuffer_Window_94_160324.png
やはり、FFが顕著に増えている。

Analysis 表示を示す。
LineBuffer_Window_95_160324.png
C33 までステートがあった。前はC22 までだと思うが記録が残っていない。。。

リソース表示を示す。
LineBuffer_Window_96_160324.png

次に、単純にmemcpy() を内側の for ループの外に置く、”hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する5”で動作周波数の限界に挑戦してみよう。ただし、こちらも、内側の for ループに PIPELINE II=1 ディレクティブを挿入し、800 x 600 ピクセルの解像度に変更した場合とする。

HDL への合成結果を示す。
LineBuffer_Window_97_160324.png
Latency は、1456201 クロックだった。800 x 600 ピクセルの総ピクセル数は 480000 なので、1 ピクセルの処理に 3 クロックかかっていると思われる。これで 1 クロック当たり、10 ns で計算すると、約 14.56 ms となる。60 fps の 16.67 ms を超えているので、これで十分かもしれない。

リソース使用量を示す。
LineBuffer_Window_98_160324.png
BRAM が 2 から 6 に増えている。

Analysis 表示を示す。
LineBuffer_Window_99_160324.png
C22 までだった。

次に、Target を 2.50 (ns) にしてみた。そうしたところ、Estimated は 3.44 (ns) だった。約 291 MHz で動作する。
LineBuffer_Window_100_160324.png
Latency は、1468201 クロックで増えている。 1468201 クロック x 3.44 ns = 5.05 ms で処理が完了することになる。かなり速い。

リソース使用量を示す。リソース使用量を下に示す。Target を 10 (ns) に設定した方を左に、Target を 2.5 (ns) に設定した方を右に表示する。
LineBuffer_Window_98_160324.pngLineBuffer_Window_101_160324.png
FFが顕著に増えていて、LUT も少し増えていた。

LineBuffer_Window_102_160324.png
ステートもTarget を 10 (ns) に設定した時は、C22 までだったが、Target を 2.5 (ns) に設定した時は、C36 までになった。

結構、hls::LineBufferとhls::Window を使った実装は、結構、速くて実際に 60 fps で動作できるのでは?と思った。
  1. 2016年03月24日 05:19 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する6

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する5”の続き。

前回は、単純にmemcpy() を内側の for ループの外に於いた実装を試した。今回は memcpy() の代わりに for 文で1行分 Read, Write を行う実装を試してみた。

下に今回の lap_filter_axim() のみのC++ソースコードを示す。

int lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb)
{
#pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave bundle=cam_fb
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave bundle=lap_fb

    hls::LineBuffer<2, HORIZONTAL_PIXEL_WIDTH, int> linebuf;
    hls::Window<33int> mbuf;
    int x, y;
    int val;
    int gray_pix;
    int i, j;
    const int lap_weight[3][3] = {{-1, -1, -1},{-18, -1},{-1, -1, -1}};
#pragma HLS ARRAY_PARTITION variable=lap_weight complete dim=0
    int read_line[HORIZONTAL_PIXEL_WIDTH];
    int write_line[HORIZONTAL_PIXEL_WIDTH];

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for(i=0; i<HORIZONTAL_PIXEL_WIDTH; i++){
            read_line[i] = cam_fb[y*(HORIZONTAL_PIXEL_WIDTH)+i];
        }
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            mbuf.shift_left();  // mbuf の列を1ビット左シフト
            //mbuf.insert_left(colbuf); // mbuf の列に colbuf[] を代入
            mbuf.insert(linebuf(1,x), 22);
            mbuf.insert(linebuf(0,x), 12);
            gray_pix = conv_rgb2y(read_line[x]);
            mbuf.insert(gray_pix, 02);

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(gray_pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    val += lap_weight[j][i] * mbuf(2-j,i);
                }
            }
            if (val<0// 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            // ラプラシアンフィルタ・データの書き込み
            if (x>1 && y>1)
                write_line[x] = (val<<16)+(val<<8)+val ;
            else
                // x<2 || y<2 の場合はピクセルデータがまだ揃っていないので0にする
                write_line[x] = 0;
            // printf("x = %d  y = %d", x, y);
        }
        for (i=0; i<HORIZONTAL_PIXEL_WIDTH; i++){
            lap_fb[y*(HORIZONTAL_PIXEL_WIDTH)+i] = write_line[i];
        }
     }
     return(0);
}


これをC++コードの合成を行った。結果を下に示す。
LineBuffer_Window_65_160322.png

LineBuffer_Window_66_160322.png
Latency は 255745 クロックだった。リソースもほとんど前回と変化は無い。

Analysis 表示にしてみた。
LineBuffer_Window_67_160322.png
C21 ステートまでだった。

リソース表示にしてみた。
LineBuffer_Window_68_160322.png

C/RTL コシミュレーションを行った。
LineBuffer_Window_69_160322.png

C/RTL コシミュレーション波形を示す。
LineBuffer_Window_70_160322.png
2.56 ms かかっていた。

LineBuffer_Window_71_160322.png
Read 間隔は 58.41 us だったが、どうも RREADY や WVALID, WREADY の波形がおかしい?

もっと拡大してみた。
LineBuffer_Window_72_160322.png
それらの信号はアサート、ディアサートを繰り返している。なんでそうなるのだろうか?

次に、行の処理を行う内側の for ループと 1行Read するための for ループ、1 行Write するための for ループにPIPELINE II=1 ディレクティブを挿入した。
LineBuffer_Window_73_160322.png 

これで をC++コードの合成を行った。結果を下に示す。
LineBuffer_Window_74_160322.png 
Latency は 10513 クロックになった。

LineBuffer_Window_75_160322.png 
これは、前回の行の処理を行う内側の for ループにPIPELINE II=1 ディレクティブを挿入した場合と同じ回路だった。

Analysis 表示にしてみた。
LineBuffer_Window_76_160322.png
やはり、C22 ステートまでだった。

リソース表示にしてみた。
LineBuffer_Window_77_160322.png

C/RTL コシミュレーションを行った。
LineBuffer_Window_78_160322.png
Latencyは 11203 クロックだった。高位合成時よりも、それほど増えていない。

C/RTL コシミュレーション波形を示す。
LineBuffer_Window_79_160322.png
終了までに 113 us 程度になった。

LineBuffer_Window_80_160322.png
Read 間隔は、 2.33 us だった。これで、行の 64 ピクセルの処理を行っているので、800 x 600 ピクセルの時はどのくらいの時間で処理できるか?計算してみよう。2.33 us / 64 ピクセル x 800 x 600 = 17.4 ms となった。

最後に、今まで挿入したPIPELINE ディレクティブを削除して、一番外側のループにPIPELINE ディレクティブを入れてみた。
LineBuffer_Window_81_160322.png

をC++コードの合成を行った。結果を下に示す。
LineBuffer_Window_82_160322.png
Latency は 6114 クロックになった。

LineBuffer_Window_83_160322.png
使用リソースは、LUT がオーバーフローしている。。。

Analysis 表示を示す。
LineBuffer_Window_84_160322.png
C144 までだった。たくさんステートがある。

リソース表示にした。
LineBuffer_Window_85_160322.png

C/RTL コシミュレーションを行った。
LineBuffer_Window_86_160322.png
Latency は 14263 クロックだった。高位合成の見積からはだいぶ増えていた。

C/RTL コシミュレーション波形を示す。
LineBuffer_Window_87_160322.png

拡大してみると、なんとシングル転送だった。この実装はリソースのLUTがFPGAに内蔵される個数を超えているので、ダメなのだが、動作の点からもあまり良くないようだ。
LineBuffer_Window_88_160322.png
  1. 2016年03月22日 05:24 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する5

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する4”の続き。

前回は、列の処理と、行の処理の2重 for ループの内側の行の処理の for ループに 最初の行のピクセルを処理する時は、if 文で1ライン分 memcpy() で Read して、行の最後のピクセルを処理した後で、if 文で1ライン分のピクセルを memcpy() でWrite する。
今回は、単純にmemcpy() を内側の for ループの外に置くことにする。

下に今回の lap_filter_axim() のみのC++ソースコードを示す。

int lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb)
{
    #pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave bundle=cam_fb
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave bundle=lap_fb

    hls::LineBuffer<2, HORIZONTAL_PIXEL_WIDTH, int> linebuf;
    hls::Window<33int> mbuf;
    int x, y;
    int val;
    int gray_pix;
    int i, j;
    const int lap_weight[3][3] = {{-1, -1, -1},{-18, -1},{-1, -1, -1}};
#pragma HLS ARRAY_PARTITION variable=lap_weight complete dim=0
    int read_line[HORIZONTAL_PIXEL_WIDTH];
    int write_line[HORIZONTAL_PIXEL_WIDTH];

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        // 1 行 Read
        memcpy(read_line, (const int*)&cam_fb[y*(HORIZONTAL_PIXEL_WIDTH)], (HORIZONTAL_PIXEL_WIDTH)*sizeof(int));
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            mbuf.shift_left();  // mbuf の列を1ビット左シフト
            //mbuf.insert_left(colbuf); // mbuf の列に colbuf[] を代入
            mbuf.insert(linebuf(1,x), 22);
            mbuf.insert(linebuf(0,x), 12);
            gray_pix = conv_rgb2y(read_line[x]);
            mbuf.insert(gray_pix, 02);

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(gray_pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    val += lap_weight[j][i] * mbuf(2-j,i);
                }
            }
            if (val<0// 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            // ラプラシアンフィルタ・データの書き込み
            if (x>1 && y>1)
                write_line[x] = (val<<16)+(val<<8)+val ;
            else
                // x<2 || y<2 の場合はピクセルデータがまだ揃っていないので0にする
                write_line[x] = 0;
            // printf("x = %d  y = %d", x, y);
        }
        memcpy((void *)&lap_fb[y*(HORIZONTAL_PIXEL_WIDTH)], (const int*)write_line, (HORIZONTAL_PIXEL_WIDTH)*sizeof(int));
     }
     return(0);
}


前回よりも、単純なコードになっている。

これをC++コードの合成を行った。結果を下に示す。
LineBuffer_Window_42_160320.png

LineBuffer_Window_43_160320.png

246721 クロックかかっているので、前回とそう違いはない。使用リソースも違いはあまり無い。

Analysis 表示にしてみた。
LineBuffer_Window_44_160320.png
C22 ステートまでだった。

リソース表示にしてみた。
LineBuffer_Window_45_160320.png

C/RTL コシミュレーションを行った。
LineBuffer_Window_46_160320.png

C/RTL コシミュレーション波形を示す。
LineBuffer_Window_47_160320.png
2.47 ms 程度かかっている。

拡大してみた。
LineBuffer_Window_48_160320.png
1行ラプラシアンフィルタ処理を行うための経過時間を知るために、Read 間隔を測ってみたところ、51.54 us だった。

次に、内側の for ループに PIPELINE II=1 のディレクティブを追加した。
LineBuffer_Window_49_160320.png

C++コードの合成を行った。結果を下に示す。
LineBuffer_Window_50_160320.png

LineBuffer_Window_51_160320.png

Latency は 10513 クロックになって、20倍程度速くなった。使用リソースはPIPELINEディレクティブを追加する前よりもむしろ少なくなった。これは良い。

C/RTL コシミュレーションを行った。
LineBuffer_Window_52_160320.png

Latencyは 11203 クロックだった。高位合成時よりも、それほど増えていない。

C/RTL コシミュレーション波形を示す。
LineBuffer_Window_53_160320.png
終了までに 113 us 程度になった。

LineBuffer_Window_54_160320.png
Read 間隔は、 2.33 us だった。

最後に、内側の for ループに追加した PIPELINE ディレクティブを削除して、外側の for ループに PIPELINE II=1 ディレクティブを追加した。
LineBuffer_Window_55_160320.png

C++コードの合成を行った。結果を下に示す。
LineBuffer_Window_56_160320.png
Latency は、3092 クロックだった。これだと、1クロックあたり 1 ピクセルの処理を行えている。

使用リソースを示す。
LineBuffer_Window_57_160320.png
DSP48E を 81 %、FF を 38 %、LUT を 85 % 使用している。これは使用リソースを使いすぎている。これでは、ラプラシアンフィルタだけしかZYBO に入らなくなってしまう。Latency は最高に良いが、選択できない感じだ。

Analysis 表示にしてみた。
LineBuffer_Window_58_160320.png
なんと C83 まである。

リソース表示にした。
LineBuffer_Window_59_160320.png

C/RTL コシミュレーションを行った。
LineBuffer_Window_62_160320.png
Latency は、3733 クロックだった。高位合成結果よりもLatency が増えたが、1クロック、1ピクセル出力を維持できているようだ。

C/RTL コシミュレーション波形を示す。
LineBuffer_Window_63_160320.png
37.8 us で終了している。

拡大してみた。
LineBuffer_Window_64_160320.png
Read 間隔は、770 ns だった。見ると、Read と Write が重なっていた。

現在は 64 x 48 ピクセルのラプラシアンフィルタを合成しているので、800 x 600 ピクセルに変更し、このPIPELINE ディレクティブでやってみたが、合成に長時間かかっても合成できなかった。たぶん行の処理を展開してしまっているのだろう?
もし、800 x 600 画面を 1行の Read 間隔の 770 ns で処理できるとしたら、770 ns / 64 pixel x 800 x 600 = 5.775 ms かかる計算になる。
  1. 2016年03月21日 05:01 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

映画『エヴェレスト 神々の山嶺』を見てきました

今日は、映画『エヴェレスト 神々の山嶺』を見てきました。
夢枕獏さんの小説「神々の山嶺」を読んだので、映画も見たかったからです。やはり山々が綺麗でした。岡田准一さんの演技も良かったし、撮影は過酷だったと思います。でも、なんかいまいちな感じでした。なんででしょうか?

予告で、「64」前後編でやるとのことなので、楽しみです。こちらも小説を読んでいます。
  1. 2016年03月19日 20:39 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する4

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する3”の続き。

今回は、ピクセルの読み込み、ピクセルの書き込みで memcpy() を使った実装を試してみた。まずは、if 文を使って、最初に1ライン分ピクセルを読み込んで、最後に1ライン分のラプラシアンフィルタ処理結果を書き出す方法でやってみる。lap_filter_axim() のみのC++ソースコードを示す。

int lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb)
{
#pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave bundle=cam_fb
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave bundle=lap_fb

    hls::LineBuffer<2, HORIZONTAL_PIXEL_WIDTH, int> linebuf;
    hls::Window<3, 3, int> mbuf;
    int x, y;
    int val;
    int gray_pix;
    int i, j;
    const int lap_weight[3][3] = {{-1, -1, -1},{-1, 8, -1},{-1, -1, -1}};
#pragma HLS ARRAY_PARTITION variable=lap_weight complete dim=0
    int read_line[HORIZONTAL_PIXEL_WIDTH];
    int write_line[HORIZONTAL_PIXEL_WIDTH];

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            if (x == 0) // 1 行 Read
                memcpy(read_line, (const int*)&cam_fb[y*(HORIZONTAL_PIXEL_WIDTH)], (HORIZONTAL_PIXEL_WIDTH)*sizeof(int));

            mbuf.shift_left();  // mbuf の列を1ビット左シフト
            mbuf.insert(linebuf(1,x), 2, 2);
            mbuf.insert(linebuf(0,x), 1, 2);
            gray_pix = conv_rgb2y(read_line[x]);
            mbuf.insert(gray_pix, 0, 2);

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(gray_pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    val += lap_weight[j][i] * mbuf(2-j,i);
                }
            }
            if (val<0) // 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            // ラプラシアンフィルタ・データの書き込み
            if (x>1 && y>1)
                write_line[x] = (val<<16)+(val<<8)+val ;
            else
                // x<2 || y<2 の場合はピクセルデータがまだ揃っていないので0にする
                write_line[x] = 0;
            // printf("x = %d  y = %d", x, y);

            if (x == (HORIZONTAL_PIXEL_WIDTH-1))
                memcpy((void *)&lap_fb[y*(HORIZONTAL_PIXEL_WIDTH)], (const int*)write_line, (HORIZONTAL_PIXEL_WIDTH)*sizeof(int));
        }
     }
     return(0);
}


if 文を使って、最初に1ライン分ピクセルを読み込んで、最後に1ライン分のラプラシアンフィルタ処理結果を書き出している。

HDLへ合成してみた。
LineBuffer_Window_27_160318.png

LineBuffer_Window_28_160318.png

Latency は 24587 ~ 688225 クロックだった。BRAM_18Kを2つ使っている。

Analysis表示にしてみた。
LineBuffer_Window_29_160318.png

C26 までで27ステートある。
リソース表示を示す。
LineBuffer_Window_30_160318.png

C/RTL コシミュレーションを行った。
LineBuffer_Window_31_160318.png

Latency は 253456 だった。
C/RTL コシミュレーション波形を示す。
LineBuffer_Window_32_160318.png

拡大した波形を示す。
LineBuffer_Window_33_160318.png

Read間隔を図ってみた。 52 us だった。
64 ピクセルでの値なので、1 ピクセル当たりどの位かかっているかを計算してみよう。52 us / 64 ピクセル = 0.8125 us = 812.5 ns だった。
動作周波数は 100 MHz なので、1週期は 10 ns だ。1 クロックで 1 ピクセル処理できると、10 ns で 1 ピクセルを処理できるはずなので、現在の結果はそれに比べて 81 倍程度、時間がかかっていることになる。

次に、内側の for ループに PIPELINE II=1 ディレクティブを追加してやってみる。
LineBuffer_Window_34_160318.png

HDLへ合成を行った。
LineBuffer_Window_35_160318.png

LineBuffer_Window_36_160318.png

Latency は 200977 に減った。BRAM_18K は 4 に増えた。DSP は何故か 9 から 5 に減った。FF と LUT は増えた。

Analysis 表示を示す。
LineBuffer_Window_37_160318.png

C154 で 155 ステートに激増した。これだと、LUT も FF も増えるはず。。。

リソース表示を示す。
LineBuffer_Window_38_160318.png

C/RTL コシミュレーションの波形を示す。
LineBuffer_Window_40_160318.png

拡大した波形を示す。
LineBuffer_Window_41_160318.png

Read間隔を図ってみた。 42 us だった。
64 ピクセルでの値なので、1 ピクセル当たりどの位かかっているかを計算してみよう。42 us / 64 ピクセル = 0.656 us = 65.6 ns だった。
動作周波数は 100 MHz なので、1週期は 10 ns だ。1 クロックで 1 ピクセル処理できると、最高の性能だと、10 ns で 1 ピクセルを処理できるはずなので、現在の結果はそれに比べて 65.6 倍時間がかかっていることになる。

今回の結果を見ると、このC++ソースコードは遅くて使えないかな?と思う。次回はまた違う実装を試してみよう。
  1. 2016年03月19日 08:17 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた3

OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた2”の続き。

いろいろと失敗してきた”OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた”ですが、ついにうまく行った。
勝因は、チェスボードに黒枠をつけたのと、チェスボードが大きめに画面に写っていたほうが良いみたいだ。

今回は下の図に示す。枠付きのチェスボードを使用した。
stereo_calib_85_160317.png

./RL_capture_bmp で、少し大きめにチェスボード全体が左目カメラ画像にも右目カメラ画像にも入るように撮影した。left0.bmp から right12.bmp まで 13 組の左目カメラ画像と右目カメラ画像のペアのBMPファイルを取得できた。
その内の3ペアを下に示す。それぞれ、最初の画像が左目カメラ画像で、次の画像が右目カメラ画像だ。なお、
./convert_calibf で 800 x 600 ピクセルのカラー画像を 640 x 480 ピクセルのグレースケール画像に変換してある。
画像のファイル名は上から、left0.jpg, right0.jpg, left1.jpg, right1.jpg,left2.jpg, right2.jpg だ。後で、歪みを補正した画像が出てくるので、それと比較して欲しい。
stereo_calib_71_160317.jpg
stereo_calib_72_160317.jpg

stereo_calib_73_160317.jpg
stereo_calib_74_160317.jpg

stereo_calib_75_160317.jpg
stereo_calib_76_160317.jpg

こんな感じで、13ペア撮影した。

./stereo_calib でステレオカメラのキャリブレーションを行った。
stereo_calib_77_160317.png

12 ペアが認識されている。1ペアは認識されなかったみたいだ。
12 ペアの左目カメラ画像、右目カメラ画像のペアが表示された。その内の3つを示す。
stereo_calib_78_160317.jpg

stereo_calib_79_160317.jpg

stereo_calib_80_160317.jpg

きちんと画像が表示されている。やっと上手く行ったようだ。
extrinsics.yml と intrinsics.yml も生成されていた。

次は、stereo_match をやってみよう。
./stereo_match -i intrinsics.yml -e extrinsics.yml left0.jpg right0.jpg
コマンドを実行した。
stereo_calib_81_160317.png

left, right, disparity ウインドウが開いた。
stereo_calib_82_160317.jpg

画像の縁を見ると歪んでいて、カメラの歪が修正されているようだ。上の歪んでいる left0.jpg, right0.jpg と比較して欲しい。
disparity ウインドウで深度情報も表示されている。

さて、もう1つやってみる。
./stereo_match -i intrinsics.yml -e extrinsics.yml left1.jpg right1.jpg
コマンドを実行した。
stereo_calib_83_160317.png

left, right, disparity ウインドウが開いた。
stereo_calib_84_160317.jpg

こちらも画像の縁を見ると歪んでいて、カメラの歪が修正されているようだ。上の歪んでいる left1.jpg, right1.jpg と比較して欲しい。

やっと、自分で撮影した画像でステレオカメラのキャリブレーションが上手く行った。
ステレオカメラの基板がしっかり固定されていないため、少し動くとキャリブレーションがずれてしまうので、固定方法を考えないといけない。固定方法を考えたい。
ステレオカメラによる深度計速も、OpenCVでソフトウェアではできたが、それをVivado HLS でアクセラレーションしてみたいという希望がある。そのためには OpenCV をもっと勉強する必要がある。

最後に、extrinsics.yml と intrinsics.yml を示す。
最初に、extrinsics.yml から下に示す。

%YAML:1.0
R: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 9.9946469358847922e-01, -1.1180576509711158e-02,
       3.0746072578428339e-02, 1.0477412330910473e-02,
       9.9968201221380104e-01, 2.2936832541000249e-02,
       -3.0992742713989885e-02, -2.2602415027520792e-02,
       9.9926401953337052e-01 ]
T: !!opencv-matrix
   rows: 3
   cols: 1
   dt: d
   data: [ -6.4327525053555608e+00, 4.9819569385123899e-02,
       1.5248276562670204e-01 ]
R1: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 9.9980738074161812e-01, -1.8381302616960832e-02,
       6.8796168998461159e-03, 1.8300896985381197e-02,
       9.9976553851253258e-01, 1.1573468463524205e-02,
       -7.0907393207904693e-03, -1.1445336030428873e-02,
       9.9990935874159870e-01 ]
R2: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 9.9968921220119289e-01, -7.7422667869396578e-03,
       -2.3696757448185279e-02, 7.4689840041410986e-03,
       9.9990483003403074e-01, -1.1599360006570166e-02,
       2.3784307568313970e-02, 1.1418764364675654e-02,
       9.9965189867967530e-01 ]
P1: !!opencv-matrix
   rows: 3
   cols: 4
   dt: d
   data: [ 7.8203511447089386e+02, 0., 3.3787803268432617e+02, 0., 0.,
       7.8203511447089386e+02, 2.5315318870544434e+02, 0., 0., 0., 1.,
       0. ]
P2: !!opencv-matrix
   rows: 3
   cols: 4
   dt: d
   data: [ 7.8203511447089386e+02, 0., 3.3787803268432617e+02,
       -5.0322022889612026e+03, 0., 7.8203511447089386e+02,
       2.5315318870544434e+02, 0., 0., 0., 1., 0. ]
Q: !!opencv-matrix
   rows: 4
   cols: 4
   dt: d
   data: [ 1., 0., 0., -3.3787803268432617e+02, 0., 1., 0.,
       -2.5315318870544434e+02, 0., 0., 0., 7.8203511447089386e+02, 0.,
       0., 1.5540613623311419e-01, 0. ]


intrinsics.yml を示す。

%YAML:1.0
M1: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 8.8235697678477902e+02, 0., 3.2955358227551494e+02, 0.,
       8.8235697678477902e+02, 2.5548502946546807e+02, 0., 0., 1. ]
D1: !!opencv-matrix
   rows: 1
   cols: 8
   dt: d
   data: [ -3.2775953128095858e-01, -6.5894028034894281e-01, 0., 0., 0.,
       0., 0., -6.2343465096598827e+00 ]
M2: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 8.8235697678477902e+02, 0., 3.3256199841666762e+02, 0.,
       8.8235697678477902e+02, 2.5216232763526432e+02, 0., 0., 1. ]
D2: !!opencv-matrix
   rows: 1
   cols: 8
   dt: d
   data: [ -3.4032226081273226e-01, -1.3685804150694736e+00, 0., 0., 0.,
       0., 0., -7.5954408258027808e+00 ]

  1. 2016年03月18日 04:12 |
  2. OpenCV
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する3

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する2”の続き。

今回は、conv_rgb2y() のインスタンス数を削減してどうなるか?を見てみたい。
C++ ソースコードをわずかながら書き換えた。lap_filter_axim() だけを示す。すべてのC++ ソースコードは”hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する1”にある。

int lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb)
{
    #pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave bundle=cam_fb
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave bundle=lap_fb

    hls::LineBuffer<2, HORIZONTAL_PIXEL_WIDTH, int> linebuf;
    hls::Window<33int> mbuf;
    int x, y;
    int val;
    int i, j;
    const int lap_weight[3][3] = {{-1, -1, -1},{-18, -1},{-1, -1, -1}};
#pragma HLS ARRAY_PARTITION variable=lap_weight complete dim=0
    int pix, gray_pix;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            // 1ピクセル読み出し
            pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];
            gray_pix = conv_rgb2y(pix);

            mbuf.shift_left();  // mbuf の列を1ビット左シフト
            //mbuf.insert_left(colbuf); // mbuf の列に colbuf[] を代入
            mbuf.insert(linebuf(1,x), 22);
            mbuf.insert(linebuf(0,x), 12);
            mbuf.insert(gray_pix, 02);

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(gray_pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    val += lap_weight[j][i] * mbuf(2-j,i);
                }
            }
            if (val<0// 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            // ラプラシアンフィルタ・データの書き込み
            if (x>1 && y>1)
                lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = (val<<16)+(val<<8)+val ;
            else
                // x<2 || y<2 の場合はピクセルデータがまだ揃っていないので0にする
                lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = 0;
            // printf("x = %d  y = %d", x, y);
        }
     }
     return(0);
}


変更点は、conv_rgb2y() の呼び出し位置をピクセルを読んできた時にしたことだ。これによって、ラインバッファに入るピクセルデータは白黒になる。今までは、ラプラシアンフィルタの演算で conv_rgb2y() を呼びまくっていたので、特にリソースは少なくなるかも?と言うことだ。
これで、HDLへの合成を行った。
LineBuffer_Window_15_160316.png

LineBuffer_Window_16_160316.png

特徴的なのは、BRAM_18K が 0 になった。前回と比較すると、DSP48E が 7 から 9 に増えた。FF は減って、LUT は微増だった。

Analysis 表示を示す。
LineBuffer_Window_17_160316.png

C21 までで、1 つステートが増えている。

C/RTL コシミュレーションを行った。
LineBuffer_Window_18_160316.png

Latency は 274,507 で、前回よりも大きい。

C/RTL コシミュレーションの波形を表示した。
LineBuffer_Window_19_160316.png

LineBuffer_Window_20_160316.png

やはり、前回同様、Read は 64 バーストだが、Write はシングル転送となっている。

次に、PIPELINE II=1 ディレクティブを入れてみた。
LineBuffer_Window_21_160317.png

HDL への合成結果を示す。やはり、Latency と Interval は何故か?前回よりも 1 ずつ多い。
LineBuffer_Window_22_160317.png

LineBuffer_Window_23_160317.png

前回と比較すると、BRAM_18K はやはり 0 で、DSP_48E は 28 が 5 だった。やはり、リソースを節約することができている。FF は 2250 に対して、1736 でだいぶ減っている。LUT は 2076 に対して、2101 で微増だった。

C/RTL コシミュレーションを行った。
LineBuffer_Window_24_160317.png

こちらは、前回と同じ Latency の値だった。

C/RTL コシミュレーション波形を見てみよう。
LineBuffer_Window_25_160317.png

LineBuffer_Window_26_160317.png

こちらも前回同様に、Read、Write 共にシングル転送だった。

やはり、特にパイプラインにした時の使用リソースが顕著に減っていたと思う。
  1. 2016年03月17日 05:21 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する2

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する1”の続き。

前回は、hls::LineBuffer と hls::Window を教えて頂いたので、それを使ってラプラシアンフィルタを実装した。今回は、そのC++ソースコードをディレクティブを追加してチューンアップしていく。

追加するディレクティブは PIPELINE ディレクティブだ。II=1 (Iteration Interval) のオプションを指定する。早速やってみよう。
II=1 の PIPELINE ディレクティブを追加した様子を下図に示す。
LineBuffer_Window_5_160317.png

これで高位合成を行った。結果を下に示す。
LineBuffer_Window_9_160317.png

Latency = 3091, Interval = 3092 だった。
64 x 48 = 3072 ピクセルなので、1 ピクセル当たり 1 クロックで処理していることになる。凄い。これならば、AXI VDMA は要らないかも知れない?

リソースの使用状況を下に示す。
LineBuffer_Window_10_160317.png

前回より、FF は増えたが、LUTは減った。

Analysis 表示にしてみた。
LineBuffer_Window_11_160317.png

C19 までになって、1つ減った。

次に、C/RTL コシミュレーションを行った。
LineBuffer_Window_12_160316.png

高位合成のレポートから Latency は倍以上に増えてしまった。

Open Wave Viewer... ボタンをクリックして、Vivado を起動し、波形を表示した。
LineBuffer_Window_13_160316.png

拡大してみた。
LineBuffer_Window_14_160316.png

なんと、Read、Write 共にシングル転送だった。これでは、Latency が倍に増える訳も分かった。
Read、Write 共にシングル転送だとすると、実機で動作させると、PSのDDRx SDRAMコントローラが要求をマージしてくれるインテリジェントなコントローラでない場合は、実効転送速度は相当に遅くなる場合がある。まあ、バンクをアクティブに状態に保持しておいて、Read, Write を入れれば、それほどは遅くならないかもしれないが、CPU からのアクセスもあるので、いくつバンクを開けて置けるか?が鍵を握りそう。だけど、ハードマクロで実装されたDDRx SDRAMコントローラなので、それほど心配は要らないのだろうか?いずれにせよ。懸念材料であることだけは確かだ。

次は、新たなC++ソースコードをやってみることにする。

最後にテストベンチを貼っておく。

// Testbench of laplacian_filter.c
// lap_filter_tb.c
// BMPデータをハードウェアとソフトウェアで、ラプラシアン・フィルタを掛けて、それを比較する
// m_axi offset=slave version
// 2015/08/26 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "gmp.h"
#include "bmp_header.h"

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rgb2y_soft(int rgb);
int lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb);    // hardware
void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height); // software

int main()
{
    int *s, *h;
    long x, y;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd, *sw_lapd;
    int blue, green, red;
    char blue_c, green_c, red_c;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((hw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }
    if ((sw_lapd =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate sw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    lap_filter_axim((int *)rd_bmp, (int *)hw_lapd);    // ハードウェアのラプラシアン・フィルタ
    laplacian_filter_soft(rd_bmp, sw_lapd, bmpihr.biWidth, bmpihr.biHeight);    // ソフトウェアのラプラシアン・フィルタ

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    for (y=0, h=hw_lapd, s=sw_lapd; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            //if (*s != 0){
            if (*h != *s){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n", x, y, *h, *s);
                //return(1);
            } else {
                h++;
                s++;
            }
        }
    }
    printf("Success HW and SW results match\n");

    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_lap.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (y=0; y<bmpihr.biHeight; y++){
        for (x=0; x<bmpihr.biWidth; x++){
            blue = hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (hw_lapd[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(hw_lapd);
    free(sw_lapd);

    return(0);
}

void laplacian_filter_soft(int *cam_fb, int *lap_fb, long width, long height)
{
    int **line_buf;
    int *lap_buf;
    int x, y, i;
    int lap_fil_val;
    int a, b;
    int fl, sl, tl;

    // line_buf の1次元目の配列をアロケートする
    if ((line_buf =(int **)malloc(sizeof(int *) * 3)) == NULL){
        fprintf(stderr, "Can't allocate line_buf[3][]\n");
        exit(1);
    }

    // メモリをアロケートする
    for (i=0; i<3; i++){
        if ((line_buf[i]=(int *)malloc(sizeof(int) * width)) == NULL){
            fprintf(stderr, "Can't allocate line_buf[%d]\n", i);
            exit(1);
        }
    }

    if ((lap_buf=(int *)malloc(sizeof(int) * (width))) == NULL){
        fprintf(stderr, "Can't allocate lap_buf memory\n");
        exit(1);
    }

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<height; y++){
        for (x=0; x<width; x++){
            // 1つのピクセルを読み込みながらラプラシアン・フィルタを実行する
            line_buf[y%3][x] = cam_fb[(y*width)+x];
            line_buf[y%3][x] = conv_rgb2y_soft(line_buf[y%3][x]);

            if ((y < 2) || (x < 2)){
                lap_fb[(y*width)+x] = 0;
                continue;
            }

            fl = (y+1)%3;    // 最初のライン, y=2 012, y=3 120, y=4 201
            sl = (y+2)%3;    // 2番めのライン
            tl = (y+3)%3;    // 3番目のライン
            lap_fil_val = laplacian_fil_soft(line_buf[fl][x-2], line_buf[fl][x-1], line_buf[fl][x], line_buf[sl][x-2], line_buf[sl][x-1], line_buf[sl][x], line_buf[tl][x-2], line_buf[tl][x-1], line_buf[tl][x]);
            // ラプラシアンフィルタ・データの書き込み
            lap_fb[(y*width)+x] = (lap_fil_val<<16)+(lap_fil_val<<8)+lap_fil_val ;
            // printf("x = %d  y = %d", x, y);
        }
    }
    free(lap_buf);
    for (i=0; i<3; i++)
        free(line_buf[i]);
    free(line_buf);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = 0;
    else if (y>255)
        y = 255;
    return(y);
}

  1. 2016年03月16日 04:31 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

hls::LineBufferとhls::Windowでラプラシアンフィルタを実装する1

あるところから hls::LineBuffer と hls::Window を教えて頂いたので、それを使ってラプラシアンフィルタを実装してみました。

hls::LineBuffer と hls::Window は、HLS ビデオライブラリの1部のようだ。詳細は、”Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2015.4) 2015 年 11 月 24 日”の 192 ページからに書いてある。

ラインバッファや n x n の行列(メモリ・ウインドウ・バッファ)を表すための C++ のクラスのようだ。つまり、今までコードを書いてラインバッファにしてきたが、それ専用のライブラリを使って実装してみよう。
これらは、193ページの”表 2‐7 : LineBuffer 例のデー タ セッ ト”に示される様に、3 行のラインバッファだと、行2が一番上になる。193ページの表 2‐7 を引用する。
LineBuffer_Window_1_160317.png

最初の lap_filter_axim_lb.cpp を示す。ユーザーズガイドが間違っているようだ。該当する列のラインバッファの内容を1つ上にあげて、一番下にデータを入れるメソッドは、shift_down(x) と insert_bottom(pix, x) のようだ。

// lap_filter_axim_lb.cpp
// m_axi offset=slave Version
// 2016/03/12 : hls::LineBuffer, hls::Window を使用した
//

#include <stdio.h>
#include <string.h>
#include <hls_video.h>

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48
//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

int conv_rgb2y(int rgb);

int lap_filter_axim(volatile int *cam_fb, volatile int *lap_fb)
{
    #pragma HLS INTERFACE s_axilite port=return

#pragma HLS INTERFACE m_axi depth=3072 port=cam_fb offset=slave bundle=cam_fb
#pragma HLS INTERFACE m_axi depth=3072 port=lap_fb offset=slave bundle=lap_fb

    hls::LineBuffer<2, HORIZONTAL_PIXEL_WIDTH, int> linebuf;
    hls::Window<33int> mbuf;
    int x, y;
    int val;
    int t_val;
    int i, j;
    const int lap_weight[3][3] = {{-1, -1, -1},{-18, -1},{-1, -1, -1}};
#pragma HLS ARRAY_PARTITION variable=lap_weight complete dim=0
    int pix;

    // RGB値をY(輝度成分)のみに変換し、ラプラシアンフィルタを掛けた。
    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
       for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            // 1ピクセル読み出し
            pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

            mbuf.shift_left();    // mbuf の列を1ビット左シフト
            mbuf.insert(linebuf(1,x), 22);
            mbuf.insert(linebuf(0,x), 12);
            mbuf.insert(pix, 02);

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    t_val = mbuf(2-j,i);
                    val += lap_weight[j][i] * conv_rgb2y(t_val);
                }
            }
            if (val<0// 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            // ラプラシアンフィルタ・データの書き込み
            if (x>1 && y>1)
                lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = (val<<16)+(val<<8)+val ;
            else
                // x<2 || y<2 の場合はピクセルデータがまだ揃っていないので0にする
                lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = 0;
            // printf("x = %d  y = %d", x, y);
        }
     }
     return(0);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}


なお、

int colbuf[3];
#pragma HLS ARRAY_PARTITION variable=colbuf complete dim=1

を定義して、for 文の中を以下の通りに書き換えても同様の動作となった。

    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
            // 1ピクセル読み出し
            pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

            // colbuf[] にラインバッファと読みだしたピクセルを入れる
            colbuf[2] = linebuf(1,x);
            colbuf[1] = linebuf(0,x);
            colbuf[0] = pix;

            mbuf.shift_left();    // mbuf の列を1ビット左シフト
            mbuf.insert_left(colbuf);    // mbuf の列に colbuf[] を代入

            // LineBuffer の更新
            linebuf.shift_down(x);
            linebuf.insert_bottom(pix, x);

            // ラプラシアンフィルタの演算
            for (j=0, val=0; j<3; j++){
                for (i=0; i<3; i++){
                    t_val = mbuf(2-j,i);
                    val += lap_weight[j][i] * conv_rgb2y(t_val);
                }
            }
            if (val<0// 飽和演算
                val = 0;
            else if (val>255)
                val = 255;

            // ラプラシアンフィルタ・データの書き込み
            if (x>1 && y>1)
                lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = (val<<16)+(val<<8)+val ;
            else
                // x<2 || y<2 の場合はピクセルデータがまだ揃っていないので0にする
                lap_fb[y*HORIZONTAL_PIXEL_WIDTH+x] = 0;
            // printf("x = %d  y = %d", x, y);
        }
     }


これをHDLへ合成した時の結果を下に示す。
LineBuffer_Window_2_160317.png
LineBuffer_Window_3_160317.png

Analysys表示を下に示す。
LineBuffer_Window_4_160317.png

C20までステートがあった。

次に、C/RLTコシミュレーションを行った。
LineBuffer_Window_6_160317.png

シミュレーション終了までに、243,790 クロックかかったようだ。

Open Wave Viewer... ボタンをクリックして波形を表示した。
LineBuffer_Window_7_160317.png

拡大してみてみると、cam_fb の Read は64 バーストで Read するが、lap_fb の Write は シングル転送になっていることが分かる。
LineBuffer_Window_8_160317.png

なお、テストベンチは、”Vivado hls勉強会4(axi4 master)”と同様に 64 x 48 ピクセルの 'A' の画像のラプラシアンフィルタ処理をするテストベンチです。但し、ラプラシアンフィルタの処理ルーチンが最初の行、列の2ピクセル分を黒にしているバージョンになっています。
  1. 2016年03月15日 05:03 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

今後の予定と日記

なかなか OpenCVのステレオカメラ・キャリブレーションも上手く行きません。なんかもっと忘れているパラメータがあるんだと思います。サンプルの画像ではステレオカメラ・キャリブレーションが上手く行って、私の撮影した画像ではうまく行かない理由がなんかあるんだと思います。
それとは、別に当初に予定していた、歪みを除かない状態でのカメラ画像の距離測定をやってみようか?と思っています。
本当はOpenCVで上手く行って、それをVivado HLSでハードウェア・アクセラレーションできると良いのですが、OpenCVの知識が足りませんし、どうなることやら。。。

土日はいろいろと家事も忙しく、文章も書いているので、ブログを書く暇がありませんでした。

ディープ・ラーニングの話題も書きたいと思っています。Caffeを少し落ち着いてやってみようと思っています。

Vivado HLSの勉強会も他の機関からお話が来ているので、やりたいと思っています。無料でできる高位合成のVivado HLSを広めたいですね。皆さん、楽しましょう。
  1. 2016年03月14日 05:24 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0

OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた2

”OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた1”の続き。

前回は自分で作ったチェスボードを使ってステレオカメラのキャリブレーションをしようとしたら、チェスボードの左目カメラ画像と右目カメラ画像のペアを認識してくれなかった。今回は、チェスボードを変更してみたら、ペアを認識してくれたが、ステレオカメラのキャリブレーションがまだおかしい。

さて、いろいろとチェスボードの位置や大きさを変更して、stereo_calib を実行したが、上手くペアを認識してくれない。それではチェスボードがおかしいのだろうか?ということで、背景だけの画像ファイルにOpenCV のサンプルのチェスボードだけをコピー&ペーストしたものを混ぜてみたら、見事それはペアと認識されているようだった。

やはり、チェスボードのパターンが重要のようだった。ちなみに、stereo_calib のオプションで -w がチェスボードのコマの横の数、-h が縦の数を指定できるので、指定していたのだが、やはりペアと認識してくれなかった。

OpenCV のサンプルで使用されているチェスボードを作ることにした。
stereo_calib_62_160311.png

それで、チェスボードの画像を撮影した。取りあえず、3ペア撮影した。その内の最初のペアを下に示す。
stereo_calib_63_160311.jpg

グレースケール、640 x 480 ピクセルに変換した。
stereo_calib_64_160311.jpg

./stereo_calib コマンドを実行した。
3ペアを認識してくれた。。。
stereo_calib_65_160311.png

しかし、rectified ウインドウは真っ黒だった。
stereo_calib_66_160311.png

OpenCVのサンプルだと、左目カメラ画像と右目カメラ画像が表示されていた。
stereo_calib_7_160220.png

./stereo_calib コマンドが終了すると、extrinsics.yml と intrinsics.yml ができていた。
stereo_calib_67_160311.png

次に、stereo_match.cpp を使用して、2つのキャリブレーション・ファイルを入力して、左目カメラ画像と右目カメラ画像を補正してみた。(参考URLは、”OpenCV 2.4.10 の stereo_match.cpp をやってみた”と”OpenCV 2.4.10 の stereo_match.cpp をやってみた2”)

./stereo_match -i intrinsics.yml -e extrinsics.yml left0.jpg right0.jpg
を実行した。
stereo_calib_68_160312.png

結果を下に示す。やはりおかしい。
stereo_calib_69_160312.jpg

./stereo_match -i intrinsics.yml -e extrinsics.yml left1.jpg right1.jpg
も実行してみたが、やはりおかしい。
stereo_calib_70_160312.jpg
  1. 2016年03月11日 05:23 |
  2. ステレオカメラによる画像解析
  3. | トラックバック:0
  4. | コメント:0

OpenCV 2.4.10 の stereo_calib.cpp を自分のカメラ画像でやってみた1

OpenCV 2.4.10 の stereo_match.cpp を自分のカメラ画像でやってみた1”の続きなのだが、とりあえずはstereo_calib.cpp をやる必要があるので、タイトルを変更した。

前回は、自分のカメラでカメラ画像を取得できたが、チェスボードの升目の境界辺りがおかしかった。そのバグは修正できたので、もう一度、カメラ画像を取得して stereo_calib.cpp をやってみよう。

チェスボードの升目の境界辺りのバグは、”ZYBO_0_2のビットマップ・ディスプレイ・コントローラのバグフィックス”で修正された。
更に、黒いドットも”ZYBO_0_2のビットマップ・ディスプレイ・コントローラのバグフィックス2”で修正された。

カメラ画像は StereoCam_Alt_Disp で表示させてから、RL_capture_bmp を使用して、BMPファイルに保存する。(”左目カメラ画像と右目カメラ画像をBMPファイルに変換するアプリケーションを作成した”参照)
stereo_calib_47_160309.jpg

3つのチェスボードの画像を保存した。
stereo_calib_48_160309.jpg

stereo_calib_49_160309.jpg

stereo_calib_50_160309.jpg

BMPファイルは800 ピクセル x 600 ラインの大きさなので、640 ピクセル x 480 ラインに縮小して、convert_calibf コマンドで、グレースケールに変換してから、JPGファイルとして保存する。(”左目カメラ画像、右目カメラ画像のBMPファイルをVGAサイズ+グレースケールに変換”参照)
stereo_calib_51_160309.png

stereo_calib_52_160309.png

最初の1ペアのみ表示する。
stereo_calib_53_160309.jpg

JPGファイルだけをWork ディレクトリに移動し、Work ディレクトリに移動した。
stereo_calib_54_160309.png

Work ディレクトリ
stereo_calib_55_160309.png

stereo_calib.xml は 3ペアのみにした。
stereo_calib_56_160309.png

./stereo_calib を実行したところ、1つもペアとして認識してくれなかった。
stereo_calib_57_160309.png

libdc1394 error: Failed to initialize libdc1394
...0 pairs have been successfully detected.
Error: too little pairs to run the calibration



そこで、OpenCV のサンプル用の left01.jpg と right01.jpg を追加して stereo_calib をやってみることにした。
stereo_calib_58_160309.png

stereo_calib.xml にも追加した。
stereo_calib_59_160309.png

結果は1ペアだけ認識できた。
stereo_calib_60_160309.png

つまり、OpenCV のサンプル用の left01.jpg と right01.jpg だけ認識できたということだ。

私の撮った画像と何処が違うのか?と探したのだが、今のところ、私の撮った画像では持ち手が無いので、チェスボードに持ち手がかぶっているが、OpenCV のサンプル用の left01.jpg と right01.jpg は持ち手がかぶっていない。その違いかな?
下に、OpenCV のサンプル用の left01.jpg と right01.jpg を引用する。
stereo_calib_61_160309.jpg

チェスボードを手で持たないようにしてやってみよう。
  1. 2016年03月08日 05:23 |
  2. ステレオカメラによる画像解析
  3. | トラックバック:0
  4. | コメント:3

FPGAの部屋のまとめサイトの更新(2016年3月7日)

FPGAの部屋のまとめサイトを更新しました。

Zynq UltraScale+ MPSoCステレオカメラによる画像解析 を追加して、今日までの記事を各カテゴリに追加しました。
  1. 2016年03月07日 04:15 |
  2. その他のFPGAの話題
  3. | トラックバック:0
  4. | コメント:0

ZYBO_0_2のビットマップ・ディスプレイ・コントローラのバグフィックス2

”ZYBO_0_2のビットマップ・ディスプレイ・コントローラのバグフィックス”の続き。

今回は、右目カメラ画像に出ている黒いドットを消すことができた。

今までは、右目カメラ画像に黒いドットが点々と出てしまっていた。
stereo_calib_46_160301.jpg

これが、こうなった。黒いドットを取り除くことができた。
BMDC_bugfix_26_160306.jpg

原因はビットマップ・ディスプレイ・コントローラの出力の display_enable が 1 クロックずれていたことだった。
bitmap_disp_engine.v の display_enable はこう書いてあった。

    // display_enable 出力
    always @(posedge clk_disp) begin
        if (reset_disp)
            display_enable <= 1'b1;
        else begin
            if (h_count<H_ACTIVE_VIDEO && v_count<V_ACTIVE_VIDEO)
                display_enable <= 1'b1;
            else
                display_enable <= 1'b0;
        end
    end


それをこのように変更した。

    // display_enable 出力
    always @(posedge clk_disp) begin
        if (reset_disp==1'b1 || cs_rdg==idle_rdg || cs_rdg==init_full_mode)
            de <= 1'b0;
        else begin
            if (h_count<H_ACTIVE_VIDEO && v_count<V_ACTIVE_VIDEO)
                de <= 1'b1;
            else
                de <= 1'b0;
        end
    end
    always @(posedge clk_disp) begin
        if(reset_disp==1'b1 || cs_rdg==idle_rdg || cs_rdg==init_full_mode) begin
            de_b1 <= 1'b0;
        end begin
            de_b1 <= de;
        end
    end
    assign display_enable = de_b1;


リセット条件はFIFOが最初にピクセル・データで満タンになるまでは、display_enable を出力しないというように変更したが、あまり影響は無いと思う。
データが有効な期間と display_enable が有効だと示す位置が1クロックずれていたようだ。これで、綺麗な右目カメラ画像を確保することができた。

(2016/03/07:追記) display_enable が1ビットずれていてこのように途中に黒いドットが見えるか?ということは、本来SVGA 解像度の画像をHDMI 送受信IP がSVGA 解像度に対応していない様です(マニュアルには対応していると書いてありますが、いくらやっても送受信できません)。そこで、SVGA 解像度のフレームバッファをXGA 解像度のフレームバッファとしてHDMIで送受信して、SVGA 画像としてフレームバッファを読みだしているからです。つまり、黒いドットはXGA 解像度の 1024 ピクセルごとに発生しています。XGA 解像度で見ると端にいるので、ディスプレイで見ても分かりません。ところが、SVGA 解像度で表示しているので、途中に見えて気になるということになります。
  1. 2016年03月06日 04:06 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:2

StereoCamTestプロジェクトのデバック2

StereoCamTestプロジェクトのデバック1”の続き。

前回は、dvi2rgb IP から出ている出力がSVGA だったというのがショックだった。XGA サイズの画像をHDMI で出力しているのだが。。。そのデバックをしてみる。

dvi2rgb IP は私が少し改造して、上手く行った物を使っているはずなのだが、ダウンロードしてきた dvi2rgb IP に戻してみる。その際には、”ZYBOのHDMI入力をVGA出力に出力する3(バグフィックス?)”の簡単な回路でやってみることにした。

最初に、DigilentInc/vivado-library を Download ZIPした。
dvi2lap2vga_49_150920.png

ダウンロードされてきた vivado-library-master.zip を展開して、 dvi2rgg_v1_5 を取り出してプロジェクトの dvi2rgb と入れ替えた。

dvi2rgb IP をダウンロードしたそのままでは、タイミングでエラーが出てしまうので、”Help With A Zybo Video Design”の dvi2rgb.xdc のコードをコピーして、中身を消去した dvi2rgb.xdc に全て貼り付けた。
dvi2lap2vga_53_150920.png

これで、dvi2rgb を再度IP 化した。
BMDC_bugfix_20_160304.png

BMDC_bugfix_21_160304.png

このプロジェクトのビットストリームの生成を行って確かめてみたが、1024 x 768 のXGA 解像度の画像が出力された。問題ない。

次に、”StereoCamTestプロジェクトのデバック1”で使用した StereoCamTest_154_t フォルダのプロジェクトの dvi2rgb IP を今うまく行ったIP に置き換えてやってみた。
BMDC_bugfix_19_160304.png

今度はきちんと 1024 x 768 のXGA 解像度の画像が VGA ポートから出力されていた。問題ない。
BMDC_bugfix_22_160304.jpg

BMDC_bugfix_23_160304.jpg
  1. 2016年03月05日 06:24 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

StereoCamTestプロジェクトのデバック1

StereoCamTest プロジェクトで、HDMI経由で転送されてきた右目カメラ画像に黒いドットが入っているバグがあるので、修正を試みた。

StereoCamTest プロジェクトで、dvi2rgb IP の後ろに rgb2vga IP を入れて、直接VGAポートに出力を試みた。

dvi_input 階層モジュールで、dvi2rgb IP の後ろに rgb2vga IP を入れて、VGA出力を行った。
BMDC_bugfix_11_160301.png

VGA出力を最上位階層で、VGAポートに出力した。
BMDC_bugfix_12_160302.png

論理合成、インプリメント、ビットストリームの生成を行った。成功した。
BMDC_bugfix_13_160302.png

ハードウェアをエクスポートした。SDKを立ち上げ、ビットストリームをダウンロードし、disp_test アプリケーションを起動した。
BMDC_bugfix_14_160302.png

ZYBO_0_2 と ZYBO_1_XGA_test との接続テスト時の画像のバグのデバック1(HDMI 出力の確認)”で ZYBO_0_2 のUbuntu 14.04 上で作成した./HDMI_output_test を使用して、XGA の画像をHDMI 経由で転送し、StereoCamTest プロジェクト用のZYBO のVGA出力を観察した。
BMDC_bugfix_15_160302.jpg

画像がおかしい。画像のプロパティを表示してみた。
BMDC_bugfix_16_160302.jpg

800 x 600 のSVGA画像になってしまっている。しかも黒いドットが現れる位置が今までの右目カメラ画像のドット位置と同じだ。

もう一度、ZYBO_2_0 プロジェクトのHDMI出力を見てみた。
BMDC_bugfix_17_160302.jpg

BMDC_bugfix_18_160302.jpg

HDMI 出力はきちんと 1024 x 768 のXGA となっていて、問題ないようだ。
ちなみに、1280 x 768 でもやってみたが、やはり、 800 x 600 と認識されてしまうようだ。dvi2rgb IP がおかしいのだろうか?
  1. 2016年03月03日 05:11 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

StereoCamTestのビットマップ・ディスプレイ・コントローラのバグフィックス

ZYBO_0_2のビットマップ・ディスプレイ・コントローラのバグフィックス”で右目カメラ画像をHDMIで送り出すためのZYBO_0_2 プロジェクトのビットマップ・ディスプレイ・コントローラのバグをフィックスした。今度は、右目カメラ画像をHDMI経由で受けて、左目カメラ画像を取り込むStereoCamTest プロジェクトのビットマップ・ディスプレイ・コントローラのバグを同様にフィックスすることにした。

StereoCamTest_154 プロジェクトをVivado 2015.4 で立ち上げて、bitmap_display_cntrler_axi_master を右クリックし、右クリックメニューから Edit in IP Packager を選択して、IP を修正した。

bitmap_display_cntrler_axi_master_v1_0_project で、bitmap_afifo の din のデータの上位32ビットと下位32ビットをひっくり返した。
BMDC_bugfix_6_160301.png

IPを再パッケージして、 StereoCamTest_154 プロジェクトに戻りIP を更新した。
BMDC_bugfix_7_160301.png

論理合成、インプリメント、ビットストリームの生成を行い、成功した。
BMDC_bugfix_8_160301.png

ハードウェアをエクスポートして、SDKを立ち上げ、BOOT.bin を再生成した。
BMDC_bugfix_9_160301.png

出来上がったBOOT.bin を ZYBO 用MicroSDカードにコピーして、ZYBO に実装した。
BMDC_bugfix_10_160301.png

これで、ビットマップ・ディスプレイ・コントローラはすべて修正したはず。。。
  1. 2016年03月02日 20:52 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

ZYBO_0_2のビットマップ・ディスプレイ・コントローラのバグフィックス

ZYBO_0_2 のビットマップ・ディスプレイ・コントローラのワードレーンがおかしいようなので修正を行った。

ZYBO_0_2 については、
ZYBO_0 を変更1(ブロックデザインの修正)
ZYBO_0 を変更2(インプリメント)
ZYBO_0 を変更3(Ubuntu 14.04 LTSのBOOT.binを変更)
を参照のこと。

ZynqのPSのユニットはすべてリトル・エンディアンだったが、ビットマップ・ディスプレイ・コントローラで使用している入力64ビット幅、出力32ビット幅の非同期FIFOはXilinx社の仕様からビック・エンディアンだった。つまり、ワード(32ビット)単位でレーンがひっくり返る訳である。以前はカメラ・インターフェース・モジュールも64ビット幅でカメラ・データを転送していたので、ワード・レーンはビック・エンディアンだったが、問題が無かった。その時は、ARMプロセッサから画像データを読む際には、4 と排他的論理和を取ったアドレスから読んでいた。つまりワード・レーンをひっくり返していた(32ビット単位で隣のアドレスとひっくり返した)わけだ。
カメラ・インターフェース・モジュールが32ビット幅のAXI4 Stream インターフェースになってからは、今回のワード・レーンの問題が出てきたと思う。なお、出力幅の異なる非同期FIFOのデータ出力順については、
Write側とRead側のデータ幅の異なるFIFOのシミュレーション
Write側とRead側のデータ幅の異なるFIFOのシミュレーション2
Write側とRead側のデータ幅の異なるFIFOのシミュレーション3
を参照のこと。

それでは、ZYBO_0_154_2 フォルダにある ZYBO_0_153 プロジェクトのビットマップ・ディスプレイ・コントローラを変更していこう。
ZYBO_0_153 プロジェクトをVivado 2015.4 で立ち上げて、bitmap_display_cntrler_axi_master を右クリックし、右クリックメニューから Edit in IP Packager を選択して、IP を修正する。

bitmap_display_cntrler_axi_master_v1_0_project で、bitmap_afifo の din のデータの上位32ビットと下位32ビットをひっくり返した。
BMDC_bugfix_1_160229.png

IPを再パッケージして、 ZYBO_0_153 プロジェクトに戻りIP を更新した。
BMDC_bugfix_2_160229.png

論理合成、インプリメント、ビットストリームの生成を行い、成功した。
BMDC_bugfix_3_160301.png

ハードウェアをエクスポートして、SDKを立ち上げ、BOOT.bin を再生成した。
BMDC_bugfix_4_160301.png

出来上がったBOOT.bin を ZYBO 用MicroSDカードにコピーして、ZYBO に実装した。
BMDC_bugfix_5_160301.png

OpenCV 2.4.10 の stereo_match.cpp を自分のカメラ画像でやってみた1”と同様に左目カメラ画像、右目カメラ画像をBMPファイルに変換した。
stereo_calib_44_160301.png

左目カメラ画像を拡大。
stereo_calib_45_160301.jpg

右目カメラ画像を拡大しても、境界は問題ないようだ。
stereo_calib_46_160301.jpg
  1. 2016年03月01日 04:25 |
  2. ZYBO
  3. | トラックバック:0
  4. | コメント:0

OpenCV 2.4.10 の stereo_match.cpp を自分のカメラ画像でやってみた1

左目カメラ画像と右目カメラ画像をBMPファイルに変換するアプリケーションを作成した”の RL_capture_bmp アプリケーションを使用して、自分のカメラの画像を右目カメラ画像、左目カメラ画像共に1ペア撮影した。

left0.jpg と right0.jpg だ。
stereo_calib_38_160229.png

stereo_calib_39_160229.png

left0.bmp を表示してみた。
stereo_calib_40_160229.jpg

チェスボードの升目の境界辺りを拡大してみよう。こっちは問題ない。
stereo_calib_42_160229.jpg

どうも前から、カメラ画像を大きな画面で表示させた時におかしいとは思っていたのだが、1ピクセル分、となりのピクセルと入れ替わっているように見える。つまり、64ビット幅のデータパスで32ビット分、上位下位を間違っている感じだ。

カメラ画像をディスプレイに表示したものがおかしく、BMPファイルは正常に見える。
右目カメラ画像は、ビットマップ・ディスプレイ・コントローラからの出力をHDMI経由で送付されているので、マスの境界がおかしい感じだ。rigth0.jpg を示す。
stereo_calib_41_160229.jpg

チェスボードの升目の境界辺りを拡大してみよう。やはり境界がおかしいようだ。
stereo_calib_43_160229.jpg

ちなみに、所々にあるドットは以前から分かっているバグである。
  1. 2016年03月01日 03:38 |
  2. ステレオカメラによる画像解析
  3. | トラックバック:0
  4. | コメント:0