FPGA实现图像灰度转换(2):RGB转YCbCr转Gray

  本篇博客整理一下 RGB565 转 RGB888,再转YCbCr444的算法,最后取 YCbCr 的 Y 分量即可实现 Gray 灰度效果。

一、YCbCr介绍

  “YCbCr或Y'CbCr有的时候会被写作:YCBCR或是Y'CBCR,是色彩空间的一种,通常会用于影片中的影像连续处理,或是数字摄影系统中。Y'为颜色的亮度(luma)成分、而CB和CR则为蓝色和红色的浓度偏移量成份。Y'和Y是不同的,而Y就是所谓的亮度(luminance),表示光的浓度且为非线性,使用伽马修正(gamma correction)编码处理。

  正如几何上用坐标空间来描述坐标集合,色彩空间用数学方式来描述颜色集合。常见的3 个基本色彩模型是RGB,CMYKYUV。YCbCr 则是在世界数字组织视频标准研制过程中作为ITU - R BT.601 建议的一部分,其实是YUV经过缩放和偏移的翻版。其中Y与YUV 中的Y含义一致,Cb,Cr 同样都指色彩,只是在表示方法上不同而已。在YUV 家族中,YCbCr 是在计算机系统中应用最多的成员,其应用领域很广泛,JPEGMPEG均采用此格式。一般人们所讲的YUV大多是指YCbCr。YCbCr 有许多取样格式,如4∶4∶4,4∶2∶2,4∶1∶1 和4∶2∶0。
  YCbCr其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量。人的肉眼对视频的Y分量更敏感,因此在通过对色度分量进行子采样来减少色度分量后,肉眼将察觉不到的图像质量的变化。主要的子采样格式有 YCbCr 4:2:0、YCbCr 4:2:2 和 YCbCr 4:4:4。
  YCbCr 4:4:4 即 YUV三个信道的抽样率相同,因此在生成的图像里,每个象素的三个分量信息完整(每个分量通常8比特),经过8比特量化之后,未经压缩的每个像素占用3个字节。“

——百度百科《YCbCr》

  RGB888 转 YCbCr 的公式如下所示:

 

 

二、MATLAB实现

  首先还是在 MATLAB 中实现,由于 MATLAB 中本来就是 RGB888 的格式,因此不需要 RGB565 转 RGB888 的操作。MATLAB代码如下所示:

 1 %--------------------------------------------------------------------------
 2 %                       RGB转YCbCr取Y值转灰度图
 3 %--------------------------------------------------------------------------
 4 clc;
 5 clear all;
 6 RGB = imread('flower.bmp'); %读取图像
 7 
 8 R = RGB(:,:,1);             %R分量
 9 G = RGB(:,:,2);             %G分量
10 B = RGB(:,:,3);             %B分量
11 
12 [ROW,COL,N] = size(RGB);    %获得图像尺寸[高度,长度,维度]
13 for r = 1:ROW 
14     for c = 1:COL
15         Y(r,c)  =  0.299*R(r,c) + 0.587*G(r,c) + 0.114*B(r,c);
16         Cb(r,c) = -0.172*R(r,c) - 0.339*G(r,c) + 0.511*B(r,c) + 128;
17         Cr(r,c) =  0.511*R(r,c) - 0.428*G(r,c) - 0.083*B(r,c) + 128;
18     end
19 end 
20 
21 subplot(2,2,1);imshow(R);title('R分量灰度图');
22 subplot(2,2,2);imshow(G);title('G分量灰度图');
23 subplot(2,2,3);imshow(B);title('B分量灰度图');
24 subplot(2,2,4);imshow(Y);title('Y分量灰度图');

  点击运行,得到如下效果:

  由结果可以看到,相比 RGB 分量转灰度图来说,YCbCr 取 Y 分量的灰度图更具有层次感,和原图片更为接近。

三、FPGA实现

1、RGB565 转 RGB888

  本次实验的输入像素是 RGB565 的,因此需要先转换为 RGB888。前面的博客介绍过 RGB332 转 RGB565,原理是一样的,即低位补 0 或继续补充原通道的低位。代码如下所示:

//RGB565 转 RGB888
assign R0 = {RGB_data[15:11],RGB_data[13:11]}; //R8
assign G0 = {RGB_data[10: 5],RGB_data[ 6: 5]}; //G8
assign B0 = {RGB_data[ 4: 0],RGB_data[ 2: 0]}; //B8

2、公式变形

  RGB888 转 YCbCr 的原公式如下所示:

  如果直接对着这个公式进行 Verilog 代码编写是不行的,因为 FPGA 无法进行浮点数的运算,先将公式变一下形,变形过程如下所示:

  第一次变形:由于 FPGA 无法进行浮点运算,因此将包含乘法的部分扩大256倍,然后再右移 8 位,右移8位即在二进制中即除以256的意思。

  第二次变形:将 128 也扩大 256 倍后移到括号里,最后一起右移 8 位。

3、流水线设计

  所谓流水线设计,即数据流由原先的一条线运算转变为多条线同时运算,最终再汇合在一起。这样能充分利用 FPGA 并行的特点,扩大了面积但是提高了速度。

  (1)计算乘法,第一个时钟先将所有的乘法运算集中到一个 always 块中计算。

//clk 1
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        {R1,G1,B1} <= {16'd0, 16'd0, 16'd0};
        {R2,G2,B2} <= {16'd0, 16'd0, 16'd0};
        {R3,G3,B3} <= {16'd0, 16'd0, 16'd0};
    end
    else begin
        {R1,G1,B1} <= { {R0 * 16'd77},  {G0 * 16'd150}, {B0 * 16'd29 } };
        {R2,G2,B2} <= { {R0 * 16'd43},  {G0 * 16'd85},  {B0 * 16'd128} };
        {R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
    end
end

  (2)计算加减法,第二个时钟将所有的加减法运算集中到一个 always 块中计算。

//clk 2
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        Y1  <= 16'd0;
        Cb1 <= 16'd0;
        Cr1 <= 16'd0;
    end
    else begin
        Y1  <= R1 + G1 + B1;
        Cb1 <= B2 - R2 - G2 + 16'd32768; //128扩大256倍
        Cr1 <= R3 - G3 - B3 + 16'd32768; //128扩大256倍
    end
end

  (3)右移8位,第三个时钟将所有的移位运行集中到一个 always 块中计算,这样便得到了 YCbCr 的不同分量值。

//clk 3,除以256即右移8位,即取高8位
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        Y2  <= 8'd0;
        Cb2 <= 8'd0;
        Cr2 <= 8'd0;
    end
    else begin
        Y2  <= Y1[15:8];  
        Cb2 <= Cb1[15:8];
        Cr2 <= Cr1[15:8];
    end
end

4、Y分量赋值

  得到 YCbCr 的三个分量后,取 Y 分量赋值给我们的 RGB565 通道即可。

assign gray_data = {Y2[7:3],Y2[7:2],Y2[7:3]}; //只取Y分量给RGB565格式

5、打拍计算

  在图像处理时,我们是有数据的同步信号的,有的是数据使能信号,有的还有行同步信号和帧同步信号。经过图像处理后的数据延迟了拍数,这些同步信号也要相应的打拍,否则最终的图像显示会出问题。

  本次设计我们共耗费了 3 拍,因此同步信号也要相应的延迟 3 拍。

//==========================================================================
//==    信号同步
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        RGB_de_r    <= 3'b0;
        RGB_hsync_r <= 3'b0;
        RGB_vsync_r <= 3'b0;
    end
    else begin  
        RGB_de_r    <= {RGB_de_r[1:0],    RGB_de};
        RGB_hsync_r <= {RGB_hsync_r[1:0], RGB_hsync};
        RGB_vsync_r <= {RGB_vsync_r[1:0], RGB_vsync};
    end
end

assign gray_de    = RGB_de_r[2];
assign gray_hsync = RGB_hsync_r[2];
assign gray_vsync = RGB_vsync_r[2];

四、完整代码

  后来整理图像处理时,各个文件代码都做了微调,这里展示下 ISP_top 内的完整代码:

image

(1)ISP_top

//**************************************************************************
// *** 名称 : ISP_top.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : 图像处理模块的顶层文件
//**************************************************************************

module ISP_top
//========================< 参数 >==========================================
#(
parameter H_DISP            = 12'd480               ,   //图像宽度
parameter V_DISP            = 12'd272                   //图像高度
)
//========================< 端口 >==========================================
(
input   wire                clk                     ,   //时钟
input   wire                rst_n                   ,   //复位
//RGB -----------------------------------------------
input   wire                RGB_hsync               ,   //RGB行同步
input   wire                RGB_vsync               ,   //RGB场同步
input   wire    [15:0]      RGB_data                ,   //RGB数据
input   wire                RGB_de                  ,   //RGB数据使能
//key -----------------------------------------------
input   wire    [ 1:0]      key_vld                 ,   //消抖后的按键值
//DISP ----------------------------------------------
output  wire                DISP_hsync              ,   //最终显示的行同步
output  wire                DISP_vsync              ,   //最终显示的场同步
output  wire    [15:0]      DISP_data               ,   //最终显示的数据
output  wire                DISP_de                     //最终显示的数据使能
);
//========================< 连线 >==========================================
wire                        Y_hsync                 ;   //Y分量行同步
wire                        Y_vsync                 ;   //Y分量场同步
wire    [ 7:0]              Y_data                  ;   //Y分量数据
wire                        Y_de                    ;   //Y分量数据使能
//==========================================================================
//==            RGB565转YCbCr444,再取Y分量作为灰度数据
//==========================================================================
RGB565_Y u_RGB565_Y
(
    .clk                    (clk                    ),  //时钟
    .rst_n                  (rst_n                  ),  //复位
    //RGB ------------------------------------------- 
    .RGB_hsync              (RGB_hsync              ),  //RGB行同步
    .RGB_vsync              (RGB_vsync              ),  //RGB场同步
    .RGB_data               (RGB_data               ),  //RGB数据
    .RGB_de                 (RGB_de                 ),  //RGB数据使能
    //Y分量 ----------------------------------------- 
    .Y_hsync                (Y_hsync                ),  //Y分量行同步
    .Y_vsync                (Y_vsync                ),  //Y分量场同步
    .Y_data                 (Y_data                 ),  //Y分量数据
    .Y_de                   (Y_de                   )   //Y分量数据使能
);
//==========================================================================
//==                        按键选择不同图像效果
//==========================================================================
display u_display
(
    .clk                    (clk                    ),  //时钟
    .rst_n                  (rst_n                  ),  //复位
    //pre -------------------------------------------
    .pre_hsync              (RGB_hsync              ),  //pre行同步
    .pre_vsync              (RGB_vsync              ),  //pre场同步
    .pre_data               (RGB_data               ),  //pre数据
    .pre_de                 (RGB_de                 ),  //pre数据使能
    //post ------------------------------------------
    .post_hsync             (Y_hsync                ),  //post行同步
    .post_vsync             (Y_vsync                ),  //post场同步
    .post_data              ({Y_data[7:3],Y_data[7:2],Y_data[7:3]}),  //post数据
    .post_de                (Y_de                   ),  //post数据使能
    //key -------------------------------------------
    .key_vld                (key_vld                ),  //消抖后的按键值
    //DISP ------------------------------------------
    .DISP_hsync             (DISP_hsync             ),  //最终显示的行同步
    .DISP_data              (DISP_data              ),  //最终显示的场同步
    .DISP_de                (DISP_de                ),  //最终显示的数据 
    .DISP_vsync             (DISP_vsync             )   //最终显示的数据使能
);



endmodule

(2)RGB565_Y

//**************************************************************************
// *** 名称 : RGB565_Y.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : RGB565转RGB888再转YCbCr444,再取Y分量作为灰度数据
//--------------------------------------------------------------------------
//        Y   =   0.299*R + 0.587*G + 0.114*B
//        Cb  =   0.586*(B-Y) + 128 = -0.172*R - 0.339*G + 0.511*B + 128
//        Cr  =   0.713*(R-Y) + 128 =  0.511*R - 0.428*G - 0.083*B + 128
// --->
//        Y   =   ( 77*R  +  150*G  +   29*B) >> 8
//        Cb  =   (-43*R  -   85*G  +  128*B) >> 8 + 128
//        Cr  =   (128*R  -  107*G  -   21*B) >> 8 + 128
// --->
//        Y   =   ( 77*R  +  150*G  +   29*B) >> 8
//        Cb  =   (-43*R  -   85*G  +  128*B + 32768) >> 8
//        Cr  =   (128*R  -  107*G  -   21*B + 32768) >> 8
//**************************************************************************

module RGB565_Y
//========================< 端口 >==========================================
(
input   wire                clk                     ,   //时钟
input   wire                rst_n                   ,   //复位
//input ---------------------------------------------
input   wire                RGB_hsync               ,   //RGB行同步
input   wire                RGB_vsync               ,   //RGB场同步
input   wire    [15:0]      RGB_data                ,   //RGB数据
input   wire                RGB_de                  ,   //RGB数据使能
//output --------------------------------------------
output  wire                Y_hsync                 ,   //Y分量行同步
output  wire                Y_vsync                 ,   //Y分量场同步
output  wire    [ 7:0]      Y_data                  ,   //Y分量数据
output  wire                Y_de                        //Y分量数据使能
);
//========================< 信号 >==========================================
wire    [ 7:0]              R0, G0, B0              ;
reg     [15:0]              R1, G1, B1              ;
reg     [15:0]              R2, G2, B2              ;
reg     [15:0]              R3, G3, B3              ;
reg     [15:0]              Y1, Cb1, Cr1            ;
reg     [ 7:0]              Y2, Cb2, Cr2            ;
//---------------------------------------------------
reg     [ 2:0]              RGB_de_r                ;
reg     [ 2:0]              RGB_hsync_r             ;
reg     [ 2:0]              RGB_vsync_r             ;
//==========================================================================
//==    RGB565转RGB888
//==========================================================================
assign R0 = {RGB_data[15:11],RGB_data[13:11]};
assign G0 = {RGB_data[10: 5],RGB_data[ 6: 5]};
assign B0 = {RGB_data[ 4: 0],RGB_data[ 2: 0]};
//==========================================================================
//==    RGB888转YCbCr
//==========================================================================
//clk 1
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        {R1,G1,B1} <= {16'd0, 16'd0, 16'd0};
        {R2,G2,B2} <= {16'd0, 16'd0, 16'd0};
        {R3,G3,B3} <= {16'd0, 16'd0, 16'd0};
    end
    else begin
        {R1,G1,B1} <= { {R0 * 16'd77},  {G0 * 16'd150}, {B0 * 16'd29 } };
        {R2,G2,B2} <= { {R0 * 16'd43},  {G0 * 16'd85},  {B0 * 16'd128} };
        {R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
    end
end

//clk 2
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        Y1  <= 16'd0;
        Cb1 <= 16'd0;
        Cr1 <= 16'd0;
    end
    else begin
        Y1  <= R1 + G1 + B1;
        Cb1 <= B2 - R2 - G2 + 16'd32768; //128扩大256倍
        Cr1 <= R3 - G3 - B3 + 16'd32768; //128扩大256倍
    end
end

//clk 3,除以256即右移8位,即取高8位
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        Y2  <= 8'd0;
        Cb2 <= 8'd0;
        Cr2 <= 8'd0;
    end
    else begin
        Y2  <= Y1[15:8];  
        Cb2 <= Cb1[15:8];
        Cr2 <= Cr1[15:8];
    end
end

assign Y_data = Y2; //只取Y分量输出
//==========================================================================
//==    信号同步
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        RGB_de_r    <= 3'b0;
        RGB_hsync_r <= 3'b0;
        RGB_vsync_r <= 3'b0;
    end
    else begin  
        RGB_de_r    <= {RGB_de_r[1:0],    RGB_de};
        RGB_hsync_r <= {RGB_hsync_r[1:0], RGB_hsync};
        RGB_vsync_r <= {RGB_vsync_r[1:0], RGB_vsync};
    end
end

assign Y_de    = RGB_de_r[2];
assign Y_hsync = RGB_hsync_r[2];
assign Y_vsync = RGB_vsync_r[2];



endmodule

(3)display

//**************************************************************************
// *** 名称 : display.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : 按键切换不同的图像效果
//**************************************************************************

module display
//========================< 端口 >==========================================
(
input   wire                clk                     ,   //时钟
input   wire                rst_n                   ,   //复位
//pre -----------------------------------------------
input   wire                pre_hsync               ,   //pre行同步
input   wire                pre_vsync               ,   //pre场同步
input   wire    [15:0]      pre_data                ,   //pre数据
input   wire                pre_de                  ,   //pre数据使能
//post ----------------------------------------------
input   wire                post_hsync              ,   //post行同步
input   wire                post_vsync              ,   //post场同步
input   wire    [15:0]      post_data               ,   //post数据
input   wire                post_de                 ,   //post数据使能
//key -----------------------------------------------
input   wire    [ 1:0]      key_vld                 ,   //消抖后的按键值
//DISP ----------------------------------------------
output  reg                 DISP_hsync              ,   //最终显示的行同步
output  reg                 DISP_vsync              ,   //最终显示的场同步
output  reg     [15:0]      DISP_data               ,   //最终显示的数据 
output  reg                 DISP_de                     //最终显示的数据使能
);
//========================< 信号 >==========================================
reg                         mode                    ;   //模式切换
//==========================================================================
//==    按键切换不同显示效果
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        mode <= 1'b0;
    end
    else if(key_vld[0]) begin
        mode <= 1'b1;
    end
    else if(key_vld[1]) begin
        mode <= 1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        DISP_hsync <= 0;
        DISP_vsync <= 0;
        DISP_data  <= 0;
        DISP_de    <= 0;
    end
    else if(mode==1'b0) begin   //pre
        DISP_hsync <= pre_hsync;
        DISP_vsync <= pre_vsync;
        DISP_data  <= pre_data;
        DISP_de    <= pre_de;
    end
    else if(mode==1'b1) begin   //post
        DISP_hsync <= post_hsync;
        DISP_vsync <= post_vsync;
        DISP_data  <= post_data;
        DISP_de    <= post_de;
    end
end



endmodule

五、上板验证

  最终效果如下所示:

  和上面 MATLAB 的情况一样,YCbCr取Y分量的灰度图更有层次感。

  实验视频如下所示,本次设计和上一篇博客一样,利用了 key_select 按键选择模块,使用按键对图像进行效果切换。共5种显示效果,分别为原图、R分量灰度图、G分量灰度图、B分量灰度图、YCbCr的Y分量灰度图。

 

五、后记

  很多图像算法都是基于 YCbCr 的 Y 分量来进行的,因此后面就用这个版本的工程做蓝本了。

 

参考资料:

    [1]CrazyBingo 图像处理教程

    [2]NingHechuan 图像处理教程

    [3]小梅哥FPGA教程

    [4]OpenS Lee:FPGA开源工作室(公众号)

posted @ 2020-03-04 13:33  咸鱼IC  阅读(8037)  评论(0)    收藏  举报