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)编码处理。
——百度百科《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 内的完整代码:

(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开源工作室(公众号)

浙公网安备 33010602011771号