0%

FPGA图像处理之RGB转YCbCr

本节主要介绍了FPGA图像处理中RGB转YCbCr算法,以及它的MATLAB与FPGA实现。

RGB与YCbCr色域介绍

  • RGB介绍:计算机处理的BMP图片为24Bit的位图,即每一通道的颜色可以细分为$2^8 = 256$级别(RGB888),每一通道的色彩分辨率能达到256级,总共能综合出的颜色种类为:
    $$
    R的种类\times G的种类\times B的种类 = 2^8 \times 2^8\times 2^8 = 16777216\approx1600万
    $$

  • YCbCr介绍:Y表示颜色的明亮度和浓度,Cb和Cr则分别表示颜色的蓝色浓度偏移量和红色浓度偏移量。YCbCr是一种数字信号,其色彩模型源于YUV颜色模型。在数字视频领域应用广泛是计算机中应用最多的格式,JPEG、MPEG、H.264/5、AVS等都采用YCbCr格式。YCbCr格式可以继续分为两种格式:tv range格式与full range格式

    • tv range格式:$Y\in [16, 235], Cb\in[16,240], Cr\in[16, 240]$,主要是广播电视采用的数字标准。
    • full range格式:$Y、Cb、Cr\in [0, 255]$,主要是PC端采用的标准,所以也称为pc range格式
  • 对full range或者pc range的YCbCr格式,其RGB与YCbCr的相互转换公式为:
    $$
    \begin{bmatrix}
    Y \\
    Cb \\
    Cr \\
    \end{bmatrix} = \begin{bmatrix}
    0 \\
    128 \\
    128 \\
    \end{bmatrix}+\begin{bmatrix}
    0.299 & 0.587 & 0.114 \\
    -0.169 & -0.331 & 0.500 \\
    0.500 & -0.419 & -0.081 \\
    \end{bmatrix}\times \begin{bmatrix}
    R \\
    G \\
    B \\
    \end{bmatrix}, 其中\begin{cases}
    \begin{aligned}
    R/G/B \in [0,255]\
    Y/Cb/Cr \in [0,255]
    \end{aligned}
    \end{cases}
    $$


RGB转YCbCr硬件实现推导

  • 流水线计算

    • Step1:多个乘法器并行计算多个像素点的RGB通道乘法
    • Step2:将每个像素点乘完后的结果相加
    • Step3:将累加后的结果取高8bit,得到最后的结果
  • 定点量化推导

    • 把数值进行256倍扩大,舍去小数

    • 那么以Y通道为例,量化后的Y1计算为:$Y1 = R\times76.544+G\times150.272+B\times 29.184\approx R\times 76 +G \times 150+B\times 29$

    • 由于数值扩大了256倍,即2的8次方,因此对上述结果再右移8bit(实际可取高8bit,不用移位),得到的结果如下(其中76+150+29=255 <256)
      $$
      Y2 = (R\times 76 + G \times 150 +B\times 29) >> 8\
      Cb = (-R\times 43 - G\times 84 + B\times 128+32768) >>8\
      Cr = (R\times128 -G\times107 -B\times20+32768)>>8
      $$


RGB转YCbCr的MATLAB实现

  • MATLAB代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    clear all; close all; clc;

    % -------------------------------------------------------------------------
    % Read PC image to Matlab
    IMG1 = imread('../../0_images/Scart.jpg'); % 读取jpg图像
    h = size(IMG1,1); % 读取图像高度
    w = size(IMG1,2); % 读取图像宽度
    subplot(221);imshow(IMG1);title('RGB Image');

    % -------------------------------------------------------------------------
    % Relized by user logic
    % Y = ( R*76 + G*150 + B*29) >>8
    % Cb = (-R*43 - G*84 + B*128 + 32768) >>8
    % Cr = ( R*128 - G*107 - B*20 + 32768) >>8
    IMG1 = double(IMG1);
    IMG_YCbCr = zeros(h,w,3);
    for i = 1 : h
    for j = 1 : w
    IMG_YCbCr(i,j, 1) = bitshift(( IMG1(i,j,1)*76 + IMG1(i,j,2)*150 + IMG1(i,j,3)*29),-8);
    IMG_YCbCr(i,j,2) = bitshift((-IMG1(i,j,1)*43 - IMG1(i,j,2)*84 + IMG1(i,j,3)*128 + 32768),-8);
    IMG_YCbCr(i,j,3) = bitshift(( IMG1(i,j,1)*128 - IMG1(i,j,2)*107 - IMG1(i,j,3)*20 + 32768),-8);
    end
    end

    % -------------------------------------------------------------------------
    % Display Y Cb Cr Channel
    IMG_YCbCr = uint8(IMG_YCbCr);
    subplot(222); imshow(IMG_YCbCr(:,:,1)); title('Y Channel');
    subplot(223); imshow(IMG_YCbCr(:,:,2)); title('Cb Channel');
    subplot(224); imshow(IMG_YCbCr(:,:,3)); title('Cr Channel');
    • 仿真结果:

      RGB转YCbCr

RGB转YCbCr的FPGA实现

  • 源代码VIP_RGB888_YCbCr444.v:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    `timescale 1ns/1ns
    module VIP_RGB888_YCbCr444
    (
    //global clock
    input clk, //cmos video pixel clock
    input rst_n, //global reset

    //Image data prepred to be processed
    input per_img_vsync, //Prepared Image data vsync valid signal
    input per_img_href, //Prepared Image data href vaild signal
    input [7:0] per_img_red, //Prepared Image red data to be processed
    input [7:0] per_img_green, //Prepared Image green data to be processed
    input [7:0] per_img_blue, //Prepared Image blue data to be processed

    //Image data has been processed
    output post_img_vsync, //Processed Image data vsync valid signal
    output post_img_href, //Processed Image data href vaild signal
    output [7:0] post_img_Y, //Processed Image brightness output
    output [7:0] post_img_Cb, //Processed Image blue shading output
    output [7:0] post_img_Cr //Processed Image red shading output
    );

    //--------------------------------------------
    /*********************************************
    //Refer to full/pc range YCbCr format
    Y = R*0.299 + G*0.587 + B*0.114
    Cb = -R*0.169 - G*0.331 + B*0.5 + 128
    Cr = R*0.5 - G*0.419 - B*0.081 + 128
    --->
    Y = (76 *R + 150*G + 29 *B)>>8
    Cb = (-43*R - 84 *G + 128*B + 32768)>>8
    Cr = (128*R - 107*G - 20 *B + 32768)>>8
    **********************************************/
    //Step 1
    reg [15:0] img_red_r0, img_red_r1, img_red_r2;
    reg [15:0] img_green_r0, img_green_r1, img_green_r2;
    reg [15:0] img_blue_r0, img_blue_r1, img_blue_r2;
    always@(posedge clk)
    begin
    img_red_r0 <= per_img_red * 8'd76;
    img_red_r1 <= per_img_red * 8'd43;
    img_red_r2 <= per_img_red * 8'd128;
    img_green_r0 <= per_img_green * 8'd150;
    img_green_r1 <= per_img_green * 8'd84;
    img_green_r2 <= per_img_green * 8'd107;
    img_blue_r0 <= per_img_blue * 8'd29;
    img_blue_r1 <= per_img_blue * 8'd128;
    img_blue_r2 <= per_img_blue * 8'd20;
    end

    //--------------------------------------------------
    //Step 2
    reg [15:0] img_Y_r0;
    reg [15:0] img_Cb_r0;
    reg [15:0] img_Cr_r0;
    always@(posedge clk)
    begin
    img_Y_r0 <= img_red_r0 + img_green_r0 + img_blue_r0;
    img_Cb_r0 <= img_blue_r1 - img_red_r1 - img_green_r1 + 16'd32768;
    img_Cr_r0 <= img_red_r2 - img_green_r2 - img_blue_r2 + 16'd32768;
    end


    //--------------------------------------------------
    //Step 3
    reg [7:0] img_Y_r1;
    reg [7:0] img_Cb_r1;
    reg [7:0] img_Cr_r1;
    always@(posedge clk)
    begin
    img_Y_r1 <= img_Y_r0[15:8];
    img_Cb_r1 <= img_Cb_r0[15:8];
    img_Cr_r1 <= img_Cr_r0[15:8];
    end

    //------------------------------------------
    //lag 3 clocks signal sync
    reg [2:0] per_img_vsync_r;
    reg [2:0] per_img_href_r;
    always@(posedge clk or negedge rst_n)
    begin
    if(!rst_n)
    begin
    per_img_vsync_r <= 0;
    per_img_href_r <= 0;
    end
    else
    begin
    per_img_vsync_r <= {per_img_vsync_r[1:0], per_img_vsync};
    per_img_href_r <= {per_img_href_r[1:0], per_img_href};
    end
    end
    assign post_img_vsync = per_img_vsync_r[2];
    assign post_img_href = per_img_href_r[2];
    assign post_img_Y = post_img_href ? img_Y_r1 : 8'd0;
    assign post_img_Cb = post_img_href ? img_Cb_r1: 8'd0;
    assign post_img_Cr = post_img_href ? img_Cr_r1: 8'd0;

    endmodule
  • testbench.sv:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    `timescale  1ns/1ns
    module testbench;

    localparam image_width = 640;
    localparam image_height = 480;
    //----------------------------------------------------------------------
    // clk & rst_n
    reg clk;
    reg rst_n;

    initial
    begin
    clk = 1'b0;
    forever #5 clk = ~clk;
    end

    initial
    begin
    rst_n = 1'b0;
    repeat(50) @(posedge clk);
    rst_n = 1'b1;
    end

    //----------------------------------------------------------------------
    // Image data prepred to be processed
    reg per_img_vsync;
    reg per_img_href;
    reg [7:0] per_img_red;
    reg [7:0] per_img_green;
    reg [7:0] per_img_blue;

    // Image data has been processed
    wire post_img_vsync;
    wire post_img_href;
    wire [7:0] post_img_Y;
    wire [7:0] post_img_Cb;
    wire [7:0] post_img_Cr;

    //----------------------------------------------------------------------
    // task and function
    task image_input;
    bit [31:0] row_cnt;
    bit [31:0] col_cnt;
    bit [7:0] mem [image_width*image_height*3-1:0];
    $readmemh(".././source_datasets/img_RGB.dat",mem);

    for(row_cnt = 0;row_cnt < image_height;row_cnt++)
    begin
    repeat(5) @(posedge clk);
    per_img_vsync = 1'b1;
    repeat(5) @(posedge clk);
    for(col_cnt = 0;col_cnt < image_width;col_cnt++)
    begin
    per_img_href = 1'b1;
    per_img_red = mem[(row_cnt*image_width+col_cnt)*3+0];
    per_img_green = mem[(row_cnt*image_width+col_cnt)*3+1];
    per_img_blue = mem[(row_cnt*image_width+col_cnt)*3+2];
    @(posedge clk);
    end
    per_img_href = 1'b0;
    end
    per_img_vsync = 1'b0;
    @(posedge clk);

    endtask : image_input

    reg post_img_vsync_r;

    always @(posedge clk)
    begin
    if(rst_n == 1'b0)
    post_img_vsync_r <= 1'b0;
    else
    post_img_vsync_r <= post_img_vsync;
    end

    wire post_img_vsync_pos;
    wire post_img_vsync_neg;

    assign post_img_vsync_pos = ~post_img_vsync_r & post_img_vsync;
    assign post_img_vsync_neg = post_img_vsync_r & ~post_img_vsync;

    task image_result_check;
    bit frame_flag;
    bit [31:0] row_cnt;
    bit [31:0] col_cnt;
    bit [ 7:0] mem [image_width*image_height*3-1:0];

    frame_flag = 0;
    $readmemh(".././source_datasets/img_YCbCr.dat",mem);

    while(1)
    begin
    @(posedge clk);
    if(post_img_vsync_pos == 1'b1)
    begin
    frame_flag = 1;
    row_cnt = 0;
    col_cnt = 0;
    $display("##############image result check begin##############");
    end

    if(frame_flag == 1'b1)
    begin
    if(post_img_href == 1'b1)
    begin
    if((post_img_Y != mem[(row_cnt*image_width+col_cnt)*3+0])||(post_img_Cb != mem[(row_cnt*image_width+col_cnt)*3+1])||(post_img_Cr != mem[(row_cnt*image_width+col_cnt)*3+2]))
    begin
    $display("result error ---> row_num : %0d;col_num : %0d;pixel data(y cb cr) : (%h %h %h);reference data(y cb cr) : (%h %h %h)",row_cnt+1,col_cnt+1,post_img_Y,post_img_Cb,post_img_Cr,mem[(row_cnt*image_width+col_cnt)*3+0],mem[(row_cnt*image_width+col_cnt)*3+1],mem[(row_cnt*image_width+col_cnt)*3+2]);
    end
    col_cnt = col_cnt + 1;
    end

    if(col_cnt == image_width)
    begin
    col_cnt = 0;
    row_cnt = row_cnt + 1;
    end
    end

    if(post_img_vsync_neg == 1'b1)
    begin
    frame_flag = 0;
    $display("##############image result check end##############");
    end
    end
    endtask : image_result_check

    //----------------------------------------------------------------------
    VIP_RGB888_YCbCr444 u_VIP_RGB888_YCbCr444
    (
    // global clock
    .clk (clk ),
    .rst_n (rst_n ),

    // Image data prepred to be processed
    .per_img_vsync (per_img_vsync ),
    .per_img_href (per_img_href ),
    .per_img_red (per_img_red ),
    .per_img_green (per_img_green ),
    .per_img_blue (per_img_blue ),

    // Image data has been processed
    .post_img_vsync (post_img_vsync ),
    .post_img_href (post_img_href ),
    .post_img_Y (post_img_Y ),
    .post_img_Cb (post_img_Cb ),
    .post_img_Cr (post_img_Cr )
    );

    initial
    begin
    per_img_vsync = 0;
    per_img_href = 0;
    per_img_red = 0;
    per_img_green = 0;
    per_img_blue = 0;
    end

    initial
    begin
    wait(rst_n);
    fork
    begin
    repeat(5) @(posedge clk);
    image_input;
    end
    image_result_check;
    join
    end

    endmodule
  • 仿真结果:

    image-20241024213005958

  • 仿真平台的架构和代码写法要重点学习:

    image-20241024213116660
  • 在该设计文件中,是直接对数据进行流水计算的,没有在数据计算前先判断数据是否有效再计算(我直接一直都是这么写得),但该设计中是在数据输出的时候,增加了一个assign组合逻辑的选择判断输出的,感觉这个写法更好,简洁明了,在数据计算的时候不要总担心数据是否有效,最后算完再来把关就行。

    1
    2
    3
    assign  post_img_Y     = post_img_href ? img_Y_r1 : 8'd0;
    assign post_img_Cb = post_img_href ? img_Cb_r1: 8'd0;
    assign post_img_Cr = post_img_href ? img_Cr_r1: 8'd0;

Reference

  • 《基于MATLAB与FPGA的图像处理教程》图书及其配套资料
欢迎来到ssy的世界