本篇博客整理一下 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];
四、上板验证
最终效果如下所示:
和上面 MATLAB 的情况一样,YCbCr取Y分量的灰度图更有层次感。
实验视频如下所示,本次设计和上一篇博客一样,利用了 key_select 按键选择模块,使用按键对图像进行效果切换。共5种显示效果,分别为原图、R分量灰度图、G分量灰度图、B分量灰度图、YCbCr的Y分量灰度图。
五、后记
很多图像算法都是基于 YCbCr 的 Y 分量来进行的,因此后面就用这个版本的工程做蓝本了。
参考资料:
[1]CrazyBingo 图像处理教程
[2]NingHechuan 图像处理教程
[3]小梅哥FPGA教程
[4]OpenS Lee:FPGA开源工作室(公众号)