FC2カウンター FPGAの部屋 Vitis HLS 2023.1 で RGB を HSV に変換する IP を作成する7
fc2ブログ

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

FPGAの部屋

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

Vitis HLS 2023.1 で RGB を HSV に変換する IP を作成する7

Vitis HLS 2023.1 で RGB を HSV に変換する IP を作成する6”の続き。

前回は、C コードの合成、C/RTL 協調シミュレーション、Export RTL、Implementation を行って、完成とするはずだったが、”Vitis HLS 2023.1 で HSV を RGB に変換する IP を作成する3”で、MAG = 16、つまり、小数点以下の桁が 16 ビットのほうが精度が良いとわかったので、今回は、それを修正し、更にテストベンチも浮動小数点数で演算した結果と比較することにした。結果として、任意精度固定小数点数で演算した HSV 値と浮動小数点数で演算した HSV 値の差分が 1 より大きくはないことがわかった。

ソースコードの RGB24toHSV.cpp を示す。

// RGB24toHSV.cpp
// 2023/10/09 by marsee
// 2023/10/15 : バグを修正
// 2023/10/17 : MAG を 8 ビットから 16 ビットに変更
//
// 入力フォーマット:23ビット〜16ビットーRED、15ビット〜8ビットーGREEN、7ビット〜0ビットーBLUE
// 出力フォーマット:24ビット〜16ビットー H、15ビット〜8ビットーS、7ビット〜0ビットーV
// ”RGBとHSV・HSBの相互変換ツールと変換計算式”を参考にしています (https://www.peko-step.com/tool/hsvrgb.html)

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

#define MAG 16 // 小数点以下のビット幅

#define ForAXIVdma  0
#define ForAXIDma   1

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

    ap_axiu<24,1,1,1> pix;
    ap_axiu<32,1,1,1> hsvst;
    int32_t r, g, b;
    int32_t h, s, v;
    int32_t max, min;
    int32_t hsv;

    do{
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        ins >> pix;
        if(function == ForAXIDma)
            break;
    }while(pix.user == 0);

    loop_y: for(int y=0; y<row_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=48
        loop_x: for(int x=0; x<col_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=64
            if(!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            b = pix.data & 0xff;
            g = (pix.data>>8) & 0xff;
            r = (pix.data>>16) & 0xff;

            // h と max, min を求める
            if(r==g && g==b && r==b){
                max = r; // 8倍
                min = r;
            }else if(r>=g && r>=b){ // r が最大
                max = r;
                if(g>=b)
                    min = b;
                else
                    min = g;
            }else if(g>=r && g>=b){ // g が最大
                max = g;
                if(r>=b)
                    min = b;
                else
                    min = r;
            }else{ // b が最大
                max = b;
                if(r>=g)
                    min = g;
                else
                    min = r;
            }
            if(max-min == 0)
                h = 0;
            else if(max == r)
                h = 60 * (((g-b)<<MAG)/(max-min)); // MAGビットシフトして精度を確保
            else if(max == g)
                h = 60 * (((b-r)<<MAG)/(max-min)) + (120<<MAG); // MAGビットシフトして精度を確保
            else // if(max == b)
                h = 60 * (((r-g)<<MAG)/(max-min)) + (240<<MAG); // MAGビットシフトして精度を確保

            if(h < 0)
                h += 360<<MAG;
            h += 1<<(MAG-1); // +0.5、四捨五入
            h >>= MAG; // 桁を戻す

            if(max == 0)
                s = 0;
            else
                s = (((max - min)<<MAG)/max) * 255; // MAGビットシフトして精度を確保

            s += 1<<(MAG-1); // +0.5、四捨五入
            s >>= MAG; // 桁を戻す

            v = max;

            hsv = ((h&0x1ff)<<16) + ((s&0xff)<<8) + (v&0xff);

            hsvst.data = hsv;

            if(function == ForAXIVdma){
                if (x==0 && y==0) // 最初のデータでは、TUSERをアサートする
                    hsvst.user = 1;
                else
                    hsvst.user = 0;
                if (x == (col_size-1))    // 行の最後で TLAST をアサートする
                    hsvst.last = 1;
                else
                    hsvst.last = 0;
            } else {
                hsvst.user = 0;
                hsvst.last = pix.last;
            }
            hsvst.keep = 0xf;
            hsvst.strb = 0xf;

            outs << hsvst;
        }
    }

    return(0);
}


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

// RGB24toHSV_tb.cpp
// 2023/10/09 by marsee
// 2023/10/15 : バグを修正、RGB値、HSV値をテキスト・ファイルに出力した。
// 2023/10/17 : MAG を 8 ビットから 16 ビットに変更、RGB24toHSV_soft() を浮動小数点数で実装
//
// ”RGBとHSV・HSBの相互変換ツールと変換計算式”を参考にしています (https://www.peko-step.com/tool/hsvrgb.html)

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <math.h>
#include <ap_axi_sdata.h>

#include "bmp_header.h"

#define ForAXIVdma  0
#define ForAXIDma   1

#define BMP_FILE_NAME   "test2.bmp"
#define SQUARE_ERROR_LIMIT    1 // 2乗誤差のエラー限界、この数より上はエラーとする

#define H    0
#define S    1
#define V    2

#define HSV_TEXT    0
#define RGB_TEXT    1

int RGB24toHSV(hls::stream<ap_axiu<24,1,1,1> >& ins, hls::stream<ap_axiu<32,1,1,1> >& outs,
        int32_t function, int32_t row_size, int32_t col_size);
int RGB24toHSV_soft(hls::stream<ap_axiu<24,1,1,1> >& ins, hls::stream<ap_axiu<32,1,1,1> >& outs,
        int32_t function, int32_t row_size, int32_t col_size);
void WriteBmpFile(FILE *fbmpw, BITMAPFILEHEADER &bmpfhr, BITMAPINFOHEADER &bmpihr, uint32_t *pixel_buf, uint32_t select_hsv);
int WriteTextFile(FILE *fbmpw, BITMAPFILEHEADER &bmpfhr, BITMAPINFOHEADER &bmpihr, uint32_t *pixel_buf, uint32_t HSVorRGB);

int main(){
    using namespace std;

    hls::stream<ap_axiu<24,1,1,1> > ins;
    hls::stream<ap_axiu<24,1,1,1> > ins_soft;
    hls::stream<ap_axiu<32,1,1,1> > outs;
    hls::stream<ap_axiu<32,1,1,1> > outs_soft;
    ap_axiu<24,1,1,1> pix;
    ap_axiu<32,1,1,1> vals;
    ap_axiu<32,1,1,1> vals_soft;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw, *fbmpwf;
    uint32_t *rd_bmp, *hw_hsv, *sw_hsv;
    uint32_t blue, green, red;

    if ((fbmpr = fopen(BMP_FILE_NAME, "rb")) == NULL){ // BMP ファイル をオープン
        fprintf(stderr, "Can't open test2.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

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

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int 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);

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

    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*bmpihr.biWidth)+i];

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

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

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

    RGB24toHSV(ins, outs, ForAXIVdma, bmpihr.biHeight, bmpihr.biWidth);
    RGB24toHSV_soft(ins_soft, outs_soft, ForAXIVdma, bmpihr.biHeight, bmpihr.biWidth);

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    cout << endl;
    cout << "outs" << endl;
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_int<32> val = vals.data;
            ap_int<32> val_soft = vals_soft.data;

            hw_hsv[(j*bmpihr.biWidth)+i] = (int)val;
            sw_hsv[(j*bmpihr.biWidth)+i] = (int)val_soft;

            red = (rd_bmp[(j*bmpihr.biWidth)+i]>>16) & 0xff;
            green = (rd_bmp[(j*bmpihr.biWidth)+i]>>8) & 0xff;
            blue = rd_bmp[(j*bmpihr.biWidth)+i] & 0xff;

            if ((double)pow((double)(val&0xff)-(val_soft&0xff),(double)2) > SQUARE_ERROR_LIMIT || // v の2乗誤差が4よりも大きい
                (double)pow((double)((val>>8)&0xff)-((val_soft>>8)&0xff),(double)2) > SQUARE_ERROR_LIMIT || // s の2乗誤差が4よりも大きい
                (double)pow((double)((val>>16)&0x1ff)-((val_soft>>16)&0x1ff),(double)2) > SQUARE_ERROR_LIMIT){ // h の2乗誤差が4よりも大きい
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld, Red = %d, Green = %d, Blue = %d, "
                    "HW_h = %d, HW_s = %d, HW_v = %d, SW_h = %d, SW_s = %d, SW_v = %d\n",
                    i, j, red, green, blue, (int)(val>>16)&0xff, (int)(val>>8)&0xff, (int)val&0xff,
                    (int)(val_soft>>16)&0xff, (int)(val_soft>>8)&0xff, (int)val_soft&0xff);
                //return(1);
            }
            //if (vals.last)
                //cout << "AXI-Stream is end" << endl;
        }
    }
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    if ((fbmpw=fopen("h_hw.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open h_hw.bmp by binary write mode\n");
        exit(1);
    }
    WriteBmpFile(fbmpw, bmpfhr, bmpihr, hw_hsv, H);

    if ((fbmpw=fopen("s_hw.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open s_hw.bmp by binary write mode\n");
        exit(1);
    }
    WriteBmpFile(fbmpw, bmpfhr, bmpihr, hw_hsv, S);

    if ((fbmpw=fopen("v_hw.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open v_hw.bmp by binary write mode\n");
        exit(1);
    }
    WriteBmpFile(fbmpw, bmpfhr, bmpihr, hw_hsv, V);

    if ((fbmpw=fopen("h_sw.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open h_sw.bmp by binary write mode\n");
        exit(1);
    }
    WriteBmpFile(fbmpw, bmpfhr, bmpihr, sw_hsv, H);

    if ((fbmpw=fopen("s_sw.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open s_sw.bmp by binary write mode\n");
        exit(1);
    }
    WriteBmpFile(fbmpw, bmpfhr, bmpihr, sw_hsv, S);

    if ((fbmpw=fopen("v_sw.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open v_sw.bmp by binary write mode\n");
        exit(1);
    }
    WriteBmpFile(fbmpw, bmpfhr, bmpihr, sw_hsv, V);

    if ((fbmpw=fopen("rgb_text.txt", "wt")) == NULL){
        fprintf(stderr, "Can't open rgb_text.txt by binary write mode\n");
        exit(1);
    }
    WriteTextFile(fbmpw, bmpfhr, bmpihr, rd_bmp, RGB_TEXT);

    if ((fbmpw=fopen("hsv_text.txt", "wt")) == NULL){
        fprintf(stderr, "Can't open hsv_text.txt by binary write mode\n");
        exit(1);
    }
    WriteTextFile(fbmpw, bmpfhr, bmpihr, hw_hsv, HSV_TEXT);

    free(rd_bmp);
    free(hw_hsv);
    free(sw_hsv);


}

// float で計算を行う
int RGB24toHSV_soft(hls::stream<ap_axiu<24,1,1,1> >& ins, hls::stream<ap_axiu<32,1,1,1> >& outs,
        int32_t function, int32_t row_size, int32_t col_size){
    ap_axiu<24,1,1,1> pix;
    ap_axiu<32,1,1,1> hsvst;
    int32_t r, g, b;
    int32_t h, s, v;
    float ht, st;
    float max, min;
    int32_t hsv;

    do{
        ins >> pix;
        if(function == ForAXIDma)
            break;
    }while(pix.user == 0);

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

            b = pix.data & 0xff;
            g = (pix.data>>8) & 0xff;
            r = (pix.data>>16) & 0xff;

            // h と max, min を求める
            if(r==g && g==b && r==b){
                max = (float)r; // 8倍
                min = (float)r;
            }else if(r>=g && r>=b){ // r が最大
                max = (float)r;
                if(g>=b)
                    min = (float)b;
                else
                    min = (float)g;
            }else if(g>=r && g>=b){ // g が最大
                max = (float)g;
                if(r>=b)
                    min = (float)b;
                else
                    min = (float)r;
            }else{ // b が最大
                max = (float)b;
                if(r>=g)
                    min = (float)g;
                else
                    min = (float)r;
            }
            if(max-min == 0.0)
                ht = 0.0;
            else if(max == (float)r)
                ht = 60.0 * (((float)g-(float)b)/(max-min));
            else if(max == (float)g)
                ht = 60.0 * (((float)b-(float)r)/(max-min)) + 120.0; // MAGビットシフトして精度を確保
            else // if(max == b)
                ht = 60.0 * (((float)r-(float)g)/(max-min)) + 240.0; // MAGビットシフトして精度を確保

            if(ht < 0)
                ht += 360.0;
            h = (uint32_t)(ht + 0.5);

            if(max == 0.0)
                st = 0.0;
            else
                st = ((max - min)/max) * 255.0; // MAGビットシフトして精度を確保
            s = (uint32_t)(st + 0.5);

            v = (uint32_t)max;

            hsv = ((h&0x1ff)<<16) + ((s&0xff)<<8) + (v&0xff);

            hsvst.data = hsv;

            if(function == ForAXIVdma){
                if (x==0 && y==0) // 最初のデータでは、TUSERをアサートする
                    hsvst.user = 1;
                else
                    hsvst.user = 0;
                if (x == (col_size-1))    // 行の最後で TLAST をアサートする
                    hsvst.last = 1;
                else
                    hsvst.last = 0;
            } else {
                hsvst.user = 0;
                hsvst.last = pix.last;
            }
            hsvst.keep = 0xf;
            hsvst.strb = 0xf;

            outs << hsvst;
        }
    }

    return(0);
}


// SV のうちの1つをblue, green, redの値として同じ値をBMPファイルに書く
// H は S,V の値を255とした時の H の値に対する RGB の値を計算する
void WriteBmpFile(FILE *fbmpw, BITMAPFILEHEADER &bmpfhr, BITMAPINFOHEADER &bmpihr, uint32_t *pixel_buf, uint32_t select_hsv){
    uint32_t h;
    uint32_t sv;
    uint32_t r, g, b;

    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);
    // RGB データの書き込み、逆順にする
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            switch(select_hsv){
            case H:
                h = (pixel_buf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0x1ff;
                if(h>=0 && h<60){
                    r = 255;
                    g = (int)(((float)h/60.0)*255.0+0.5);
                    b = 0;
                }else if(h>=60 && h<120){
                    r = (int)(((120.0-(float)h)/60.0)*255+0.5);
                    g = 255;
                    b = 0;
                }else if(h>=120 && h<180){
                    r = 0;
                    g = 255;
                    b = (int)((((float)h-120.0)/60.0)*255+0.5);
                }else if(h>=180 && h<240){
                    r = 0;
                    g = (int)(((240.0-(float)h)/60.0)*255+0.5);
                    b = 255;
                }else if(h>=240 && h<300){
                    r = (int)((((float)h-240.0)/60.0)*255+0.5);
                    g = 0;
                    b = 255;
                }else{ // h>=300 && h<=360
                    r = 255;
                    g = 0;
                    b = (int)(((360.0-(float)h)/60.0)*255+0.5);
                }
                break;
            case S:
                sv = (pixel_buf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
                break;
            default: // case V:
                sv = pixel_buf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                break;
            }

            if(select_hsv==S || select_hsv==V){
                fputc(sv, fbmpw);
                fputc(sv, fbmpw);
                fputc(sv, fbmpw);
            }else{
                fputc(b, fbmpw);
                fputc(g, fbmpw);
                fputc(r, fbmpw);
            }
        }
    }
    fclose(fbmpw);
}

// rgb, hsv をテキストファイルに書き込む
int WriteTextFile(FILE *fbmpw, BITMAPFILEHEADER &bmpfhr, BITMAPINFOHEADER &bmpihr, uint32_t *pixel_buf, uint32_t HSVorRGB){
    uint32_t hr, sg, vb;

    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            if (HSVorRGB == HSV_TEXT)
                hr = (pixel_buf[y*bmpihr.biWidth+x]>>16) & 0x1ff;
            else
                hr = (pixel_buf[y*bmpihr.biWidth+x]>>16) & 0xff;
            sg = (pixel_buf[y*bmpihr.biWidth+x] >> 8) & 0xff;
            vb = pixel_buf[y*bmpihr.biWidth+x] & 0xff;
            fprintf(fbmpw, "%d, %d, %d\n",hr, sg, vb);
        }
    }
    fclose(fbmpw);

    return(0);
}


solution3 を作成し、C シミュレーションを行った。結果を示す。
zub1cg_i5filters_46_231020.png

C コードの合成を行った。
前回は、FF が 5310 個、LUT が 4324 個だったが、今回は、FF が 9197 個、LUT が 6950 個で増えていた。全体のビット長は変えずに、小数点以下を変えただけなのに、増え方が激しい気がする。
zub1cg_i5filters_47_231020.png

Export RTL、Implementation を行った。
solution2 と solution3 の比較を示す。やはり、solution3 の方がリソース使用量が大きい。
zub1cg_i5filters_48_231020.png
zub1cg_i5filters_49_231020.png
  1. 2023年10月20日 04:58 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


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

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