FC2カウンター FPGAの部屋 RBG 24 ビット・データ入出力対応のメディアン・フィルタを Vitis HLS 2021.1 で作成する1
fc2ブログ

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

FPGAの部屋

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

RBG 24 ビット・データ入出力対応のメディアン・フィルタを Vitis HLS 2021.1 で作成する1

RBG 24 ビット・データ入出力対応のガウシアン・フィルタを Vitis HLS 2021.1 で作成する1
RBG 24 ビット・データ入出力対応のガウシアン・フィルタを Vitis HLS 2021.1 で作成する2
RBG 24 ビット・データ入出力対応のガウシアン・フィルタを Vitis HLS 2021.1 で作成する3
でガウシアン・フィルタを作ってきたが、ガウシアン・フィルタのノイズ除去性能は以前 OpenCV で実装したメディアン・フィルタの性能を下回っている。そこで、メディアン・フィルタも自分で実装してみたくなった。

メディアン・フィルタは中央値フィルタで、3 x 3 のカーネル内のピクセルの中央の値を取るフィルタだ。

カラー画像は RGB の 3 個の値があるので、中央値はどうやって取るんだろう?ということで、輝度を求めて、その中央値のインデックスの画素を選んでいた。しかし、ツィッターで tomoaki_teshima さんに RGB それぞれ中央値を取って、それを合わせて RGB にするということを教えていただいた。ありがとうございます。
考えてみれば、色がノイズで全く変わっているけど、輝度は同じという時に対応できないと思ったので、教えていただいた実装にした。
なお、ソートはバブルソートで”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”を参照した。

それでは、ヘッダファイルの median_filter_axis_RBG.h から貼っておく。

// median_filter_axis_RBG.h
// 2021/10/29 by marsee
//

#ifndef __MEDIAN_FILTER_AXIS_RBG10_H__
#define __MEDIAN_FILTER_AXIS_RBG10_H__

#define HORIZONTAL 0
#define VERTICAL 1

#define HD_720

#ifdef HD_RES
#define DISPLAY_WIDTH 1920
#define DISPLAY_HIGHT 1080
#endif

#ifdef HD_720
#define DISPLAY_WIDTH 1280
#define DISPLAY_HIGHT 720
#endif

#ifdef SVGA_RES
#define DISPLAY_WIDTH 800
#define DISPLAY_HIGHT 600
#endif

#ifdef SMALL_RES
#define DISPLAY_WIDTH 64
#define DISPLAY_HIGHT 48
#endif

#define ALL_PIXEL_VALUE (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define ORIGINAL_IMAGE 0
#define MEDIAN_FILTER 1

#endif


次に median_filter_axis_RBG.cpp を示す。

// median_filter_axis_RBG.cpp
// 2021/10/29 by marsee
//

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "median_filter_axis_RBG.h"

constexpr int size = 3;

void median_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result);
ap_int<32> pixel_sort(ap_int<32> *y);
ap_int<32> separate_rbg(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &b, ap_int<32> &g);

int median_filter_axis(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int function){
#pragma HLS INTERFACE s_axilite port=function
#pragma HLS INTERFACE axis register_mode=both register port=outs
#pragma HLS INTERFACE axis register_mode=both register port=ins
#pragma HLS INTERFACE s_axilite port=return

    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> median;
    ap_uint<24> val;

    ap_int<32> line_buf[2][DISPLAY_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    ap_int<32> pix_mat[size][size];
#pragma HLS array_partition variable=pix_mat complete

    LOOP_WAIT_USER : do {   // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        ins >> pix;
    } while(pix.user == 0);

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

            LOOP_PIX_MAT_K: for(int k=0; k<3; k++){
                LOOP_PIX_MAT_M: for(int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];
            ap_int<32> y_val = pix.data;
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            median_fil(pix_mat, val);
            median.data = val;

            if(x<2 || y<2)
                median.data = 0;

            if(x==0 && y==0) // 最初のピクセル
                median.user = 1;
            else
                median.user = 0;
            if(x == (DISPLAY_WIDTH-1)) // 行の最後
                median.last = 1;
            else
                median.last = 0;

            if(function == MEDIAN_FILTER)
                outs << median;
            else
                outs << pix;
        }
    }
    return(0);
}

// median filter
//
// x0y0 x1y0 x2y0
// x0y1 x1y1 x2y1
// x0y2 x1y2 x2y2
//
void median_fil(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result){
    ap_int<32> pix_1d_r[9], pix_1d_b[9], pix_1d_g[9];
    ap_int<32> y_r, y_b, y_g, y;

    for(int i=0; i<9; i++){
        separate_rbg(pix_mat[i/3][i%3], pix_1d_r[i], pix_1d_b[i], pix_1d_g[i]);
    }

    y_r = pixel_sort(pix_1d_r);
    y_b = pixel_sort(pix_1d_b);
    y_g = pixel_sort(pix_1d_g);

    result = (y_r << 16) + (y_b << 8) + y_g;
}

// pixel_sort()
// bubble sort
// ”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”参照
// https://cgengo.sakura.ne.jp/arg04.html
ap_int<32> pixel_sort(ap_int<32> *y){
#pragma HLS ARRAY_PARTITION variable=y dim=1 complete
    ap_int<32> tmp;

    for(int i=1; i<9; i++){
        for(int j=0; j<9-i; j++){
            if(y[j] < y[j+1]){
                tmp = y[j];
                y[j] = y[j+1];
                y[j+1] = tmp;
            }
        }
    }
    return(y[4]);
}

// separate_rbg
// RGBを分離する
// RBGのフォーマットは、{R(8bits), B(8bits), G(8bits)}, 1pixel = 32bits
//
ap_int<32> separate_rbg(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &b, ap_int<32> &g){
    b = (rbg>>8) & 0xff;
    g = rbg & 0xff;
    r = (rbg>>16) & 0xff;
    return(0);
}


テストベンチの median_filter_axis_RBG_tb.cpp を示す。

// median_filter_axis_RBG_tb.cpp
// 2021/10/29 by marsee
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "median_filter_axis_RBG.h"

constexpr int size = 3;

int median_filter_axis(hls::stream<ap_axiu<24,1,1,1> >& ins, hls::stream<ap_axiu<24,1,1,1> >& outs, int function);
int median_filter_axis_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int function);
void median_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result);
ap_int<32> pixel_sort_soft(ap_int<32> *y);
ap_int<32> separate_rbg_soft(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &b, ap_int<32> &g);

const char INPUT_JPG_FILE[] = "test2.jpg";
const char OUTPUT_JPG_FILE[] = "median.jpg";
const char ORG_OUT_JPG_FILE[] = "org.jpg";

int main(){
    hls::stream<ap_axiu<24,1,1,1> > ins, ins2;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft;
    hls::stream<ap_axiu<24,1,1,1> > outs, outs2;
    hls::stream<ap_axiu<24,1,1,1> > outs_soft;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<24,1,1,1> vals, vals_soft;

    // JPG ファイルをMat に読み込む
    cv::Mat img = cv::imread(INPUT_JPG_FILE);

    // ピクセルを入れる領域の確保
    std::vector<int32_t> rd_bmp(sizeof(int32_t)*img.cols*img.rows);
    std::vector<int32_t> hw_median(sizeof(int32_t)*(img.cols)*(img.rows));
    std::vector<int32_t> sw_median(sizeof(int32_t)*(img.cols)*(img.rows));

    // rd_bmp にJPGのピクセルを代入
    cv::Mat_<cv::Vec3b> dst_vec3b = cv::Mat_<cv::Vec3b>(img);
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            cv::Vec3b pixel;
            pixel = dst_vec3b(y,x);
            rd_bmp[y*img.cols+x] = (pixel[1] & 0xff) | ((pixel[0] & 0xff)<<8) | ((pixel[2] & 0xff)<<16); // RBG 8 bits
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

    // ins に入力データを用意する
    for(int i=0; i<5; i++){ // dummy data
        pix.user = 0;
        pix.data = i;
        ins << pix;
    }

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (int32_t)rd_bmp[(j*img.cols)+i];

            if (j==0 && i==0)   // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
            else
                pix.user = 0;

            if (i == img.cols-1) // 行の最後でTLASTをアサートする
                pix.last = 1;
            else
                pix.last = 0;

            ins << pix;
            ins2 << pix;
            ins_soft << pix;
        }
    }

    median_filter_axis(ins, outs, MEDIAN_FILTER); // ハードウェアのメディアンフィルタ
    median_filter_axis_soft(ins_soft, outs_soft, MEDIAN_FILTER);  // ソフトウェアのメディアンフィルタ

    // ハードウェアとソフトウェアのメディアンフィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_uint<32> val = vals.data;
            hw_median[y*img.cols+x] = (int32_t)val;
            if (val != vals_soft.data){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %x, SW = %x\n",
                        x, y, val, vals_soft.data);
                //return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int median_row = img.rows;
    const int median_cols = img.cols;
    cv::Mat wbmpf(median_row, median_cols, CV_8UC3);
    // wbmpf にmedian フィルタ処理後の画像を入力
    cv::Mat_<cv::Vec3b> sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            int32_t rbg = hw_median[y*wbmpf.cols+x];
            pixel[0] = ((rbg >> 8) & 0xff); // blue
            pixel[1] = (rbg & 0xff); // green
            pixel[2] = ((rbg >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのメディアンフィルタの結果を jpg ファイルへ出力する
    cv::imwrite(OUTPUT_JPG_FILE, wbmpf);

    median_filter_axis(ins2, outs2, ORIGINAL_IMAGE); // 元画像出力

    cv::Mat wbmpf2(median_row, median_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            outs2 >> vals;
            int32_t val = vals.data;
            pixel[0] = ((val >> 8) & 0xff); // blue
            pixel[1] = (val & 0xff); // green
            pixel[2] = ((val >> 16) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を jpg ファイルへ出力する
    cv::imwrite(ORG_OUT_JPG_FILE, wbmpf2);

    return(0);
}

int median_filter_axis_soft(hls::stream<ap_axiu<24,1,1,1> >& ins,
        hls::stream<ap_axiu<24,1,1,1> >& outs, int function){

ap_axiu<24,1,1,1> pix;
ap_axiu<24,1,1,1> median;
ap_uint<24> val;

ap_int<32> line_buf[2][DISPLAY_WIDTH];

ap_int<32> pix_mat[size][size];

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

LOOP_Y: for(int y=0; y<DISPLAY_HIGHT; y++){
    LOOP_X: for(int x=0; x<DISPLAY_WIDTH; x++){
        if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
            ins >> pix; // AXI4-Stream からの入力

        LOOP_PIX_MAT_K: for(int k=0; k<3; k++){
            LOOP_PIX_MAT_M: for(int m=0; m<2; m++){
                pix_mat[k][m] = pix_mat[k][m+1];
            }
        }
        pix_mat[0][2] = line_buf[0][x];
        pix_mat[1][2] = line_buf[1][x];
        ap_int<32> y_val = pix.data;
        pix_mat[2][2] = y_val;

        line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
        line_buf[1][x] = y_val;

        median_fil_soft(pix_mat, val);
        median.data = val;

        if(x<2 || y<2)
            median.data = 0;

        if(x==0 && y==0) // 最初のピクセル
            median.user = 1;
        else
            median.user = 0;
        if(x == (DISPLAY_WIDTH-1)) // 行の最後
            median.last = 1;
        else
            median.last = 0;

        if(function == MEDIAN_FILTER)
            outs << median;
        else
            outs << pix;
        }
    }
    return(0);
}

// median filter
//
// x0y0 x1y0 x2y0
// x0y1 x1y1 x2y1
// x0y2 x1y2 x2y2
//
void median_fil_soft(ap_int<32> (&pix_mat)[size][size], ap_uint<24> &result){
    ap_int<32> pix_1d_r[9], pix_1d_b[9], pix_1d_g[9];
    ap_int<32> y_r, y_b, y_g, y;

    for(int i=0; i<9; i++){
        separate_rbg_soft(pix_mat[i/3][i%3], pix_1d_r[i], pix_1d_b[i], pix_1d_g[i]);
    }

    y_r = pixel_sort_soft(pix_1d_r);
    y_b = pixel_sort_soft(pix_1d_b);
    y_g = pixel_sort_soft(pix_1d_g);

    result = (y_r << 16) + (y_b << 8) + y_g;
}

// pixel_sort()
// bubble sort
// ”メジアン(中央値)、範囲(レンジ)、ヒストグラムを求める”参照
// https://cgengo.sakura.ne.jp/arg04.html
ap_int<32> pixel_sort_soft(ap_int<32> *y){
    ap_int<32> tmp;

    for(int i=1; i<9; i++){
        for(int j=0; j<9-i; j++){
            if(y[j] < y[j+1]){
                tmp = y[j];
                y[j] = y[j+1];
                y[j+1] = tmp;
            }
        }
    }
    return(y[4]);
}

// separate_rbg
// RGBを分離する
// RBGのフォーマットは、{R(8bits), B(8bits), G(8bits)}, 1pixel = 32bits
//
ap_int<32> separate_rbg_soft(ap_int<32> rbg, ap_int<32> &r, ap_int<32> &b, ap_int<32> &g){
    b = (rbg>>8) & 0xff;
    g = rbg & 0xff;
    r = (rbg>>16) & 0xff;
    return(0);
}


画像は、ガウシアン・フィルタと同じファイル(test2.jpg)を使用している。このファイルでは、ノイズ除去の様子を見るために、画像ソフトの Pinta でノイズを加えた。
gaussian_kv260_1_211028.jpg

ソースコードやヘッダファイル、テストベンチを入れた median_filter_axis_RBG プロジェクトを示す。
median_kv260_1_211101.png

今回のテストベンチ・コードでは OpenCV ライブラリを使用している。
Vitis HLS 2021.1 には内蔵された OpenCV は無いので、別にインストールした OpenCV を指定する。
Vitis HLS の Project メニューから Project Settings... を選択して、Project Settings ダイアログを開いた。
Simulation タブを開いて、median_filter_axis_RBG_tb.cpp の CFLAGS に

-I/usr/local/include

を設定した。
inker Flags に

-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc

を設定した。
median_kv260_2_211101.png

更に、 Synthesis をクリックして、 Top Function に median_filter_axis を指定した。
median_kv260_3_211101.png
  1. 2021年11月01日 05:06 |
  2. KRIA KV260 Vision AI Starter Kit
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
https://marsee101.blog.fc2.com/tb.php/5397-2b1aa0cc
この記事にトラックバックする(FC2ブログユーザー)