前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RGB888 转 YCbCr444 算法的 HDL 实现

RGB888 转 YCbCr444 算法的 HDL 实现

作者头像
碎碎思
发布2020-06-30 11:16:45
1.4K0
发布2020-06-30 11:16:45
举报
文章被收录于专栏:OpenFPGAOpenFPGA

1.1.1 RGB888 转 YCbCr 介绍

虽说 OV5640 可以通过寄存器的设置,直接输出 YCbCr444 格式的视频流,但为了研究图像处理,以及最基本的视频格式转换,有意执行一次 RGB888转 YCbCr444 操作。

关于 YCbCr(YUV) 格式视频流的介绍,详见《Image\004_OV5640_DDR3_RGB888_YCbCr444\DOC\YCbCr(YUV)格式视频流》。

RGB 转 YCbCr,实际上只是色度空间的转换,前者为三原色色度空间,后者为亮度与色差, OV5640 相机的软件应用手册详细给出了 RGB 与 YCbCr 色度空间转换公式,其中 RGB 转 YCbCr 的公式如下所示:

图4‑3 RGB 转 YCbCr 的公式

由于 Verilog HDL 无法进行浮点运算,因此使用扩大 256 倍,再向右移 8Bit的方式,来转换公式, 如下所示:

图4‑4 RGB 转 YCbCr 的公式转化

此时,剩下的问题就是如何将上面的公式移植到 FPGA 中去。 FPGA 富含乘法器,移位寄存器,加法器, 如果直接用表达式描述上面的运算,理论上也是能计算出结果的; 不过代价是效率、资源; 同时当表达式小于 0 的时候, 运算结果出错!

因此,在正式进行算法移植前,我们需要进行运算的拆分,同时进行一定的变换。过程中为了防止过程中出现负数,先将 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

此时 Y 必然为正值, Cb 括号内最小值为:

256*(-43*1-85*1+128) = 256*(-127+128) =256*1

因此 Cb 括号内必然大于 0。同样可以推断出 Cr 必然大于 0。经过上述变换后,可以放心的进行运算,而不用考虑运算结果溢出的问题。

1.1.2 RGB888 转 YCbCr 的 HDL 实现

新建并保存 VIP_RGB888_YCbCr444.v 与 src/Video_Image_Processor,具体的算法的 HDL 实现过程如下:

(1) 第二步,分别计算出 Y、 Cb、 Cr 中每一个乘法的乘积, HDL 如下:

代码4‑1计算出 Y、 Cb、 Cr 中每一个乘法的乘积

1. always@(posedge clk or negedge rst_n) 2. begin 3. if(!rst_n) 4. begin 5. img_red_r0 <= 0; 6. img_red_r1 <= 0; 7. img_red_r2 <= 0; 8. img_green_r0 <= 0; 9. img_green_r1 <= 0; 10. img_green_r2 <= 0; 11. img_blue_r0 <= 0; 12. img_blue_r1 <= 0; 13. img_blue_r2 <= 0; 14. end 15. else 16. begin 17. img_red_r0 <= per_img_red * 8'd77; 18. img_red_r1 <= per_img_red * 8'd43; 19. img_red_r2 <= per_img_red * 8'd128; 20. img_green_r0 <= per_img_green * 8'd150; 21. img_green_r1 <= per_img_green * 8'd85; 22. img_green_r2 <= per_img_green * 8'd107; 23. img_blue_r0 <= per_img_blue * 8'd29; 24. img_blue_r1 <= per_img_blue * 8'd128; 25. img_blue_r2 <= per_img_blue * 8'd21; 26. end 27.end

(2) 计算出 Y、 Cb、 Cr 括号内的值, HDL 如下:

代码4‑2计算出 Y、 Cb、 Cr 括号内的值

1. always@(posedge clk or negedge rst_n) 2. begin 3. if(!rst_n) 4. begin 5. img_Y_r0 <= 0; 6. img_Cb_r0 <= 0; 7. img_Cr_r0 <= 0; 8. end 9. else 10. begin 11. img_Y_r0 <= img_red_r0 + img_green_r0 + img_blue_r0; 12. img_Cb_r0 <= img_blue_r1 - img_red_r1 - img_green_r1 + 16'd32768; 13. img_Cr_r0 <= img_red_r2 + img_green_r2 + img_blue_r2 + 16'd32768; 14. end 15.end

(3) 第三步,右移 8Bit。 这里由于 Step2 计算结果为 16Bit, 因此提取高8Bit 即可, HDL 如下所示:

代码4‑3右移 8Bit

1. always@(posedge clk or negedge rst_n) 2. begin 3. if(!rst_n) 4. begin 5. img_Y_r1 <= 0; 6. img_Cb_r1 <= 0; 7. img_Cr_r1 <= 0; 8. end 9. else 10. begin 11. img_Y_r1 <= img_Y_r0[15:8]; 12. img_Cb_r1 <= img_Cb_r0[15:8]; 13. img_Cr_r1 <= img_Cr_r0[15:8]; 14. end 15.end

实际上从(1) ~(3)的运算, 均直接通过寄存器描述,没有顾虑行场有效时序等。但实际上的操作会有一个数据流上的先后顺序,同时(1)~(3)同时对连续数据进行处理,采用这种方式实现的硬件加速, 我们称之为流水线设计技巧。在 HDL 中是一种非常重要而且常用的算法实现思维,是 FPGA 硬件加速的精髓之一。

前面说过,处理模块输入的数据接口时序,与输出完全一样。前面计算出 Y、 Cb、 Cr 我们消耗了(1)(2)(3)这三个时钟, 因此需要将输入的行场信号、使能信号同步移动 3 个时钟,采用寄存器移位实现,代码所示:

代码4‑4将输入的行场信号、使能信号同步移动 3 个时钟

1. //lag 3 clocks signal sync 2. reg [2:0] per_frame_vsync_r; 3. reg [2:0] per_frame_href_r; 4. reg [2:0] per_frame_clken_r; 5. always@(posedge clk or negedge rst_n) 6. begin 7. if(!rst_n) 8. begin 9. per_frame_vsync_r <= 0; 10. per_frame_href_r <= 0; 11. per_frame_clken_r <= 0; 12. end 13. else 14. begin 15. per_frame_vsync_r <= {per_frame_vsync_r[1:0], per_frame_vsync}; 16. per_frame_href_r <= {per_frame_href_r[1:0], per_frame_href}; 17. per_frame_clken_r <= {per_frame_clken_r[1:0], per_frame_clken}; 18. end 19.end 20.assign post_frame_vsync = per_frame_vsync_r[2]; 21.assign post_frame_href = per_frame_href_r[2]; 22.assign post_frame_clken = per_frame_clken_r[2]; 23.assign post_img_Y = post_frame_href ? img_Y_r1 : 8'd0; 24.assign post_img_Cb = post_frame_href ? img_Cb_r1: 8'd0; 25.assign post_img_Cr = post_frame_href ? img_Cr_r1: 8'd0;

此外,如上代码,输出的 Y、 Cb、 Cr 信号与输出的行有效信号进行了使能运算,保证了在 post_frame_href 无效时数据输出为 0,吻合时序约定。

至此,我们便简单的实现了 RGB888 转 YCbCr444 功能。由于这里Video_Image_Processor 模块,我们只进行了 RGB888 转 YCbCr 功能,因此其Module 信号列表与 Video_Image_Processor 完全保持一致(注意 Y、 Cb、 Cr 的定义)。 Video_Image_Processor.v 文件的列表与例化详见代码。

1.1.3 RGB888 转 YCbCr 功能测试

重新回到顶层文件,映射 sensor_decode.v 输出的信号。由于Video_Image_Processor 输入的为 RGB888 信号,而sensor_decode.v 输出为 RGB565 格式,因此通过高位补充低位的模式,实现 8Bit 的输入。关于RGB565 转 RGB888 相关理论,详见如下:

量化补偿

http://lhtao31.blog.163.com/blog/static/2972647020103814044158/

24bit RGB888-> 16bit RGB565 的转换

24ibt RGB888 {R7 R6 R5 R4 R3 R2R1 R0} {G7 G6 G5 G4 G3 G2 G1 G0} {B7 B6 B5 B4 B3 B2 B1 B0}

16bit RGB656 {R7 R6 R5 R4 R3}{G7 G6 G5 G4 G3 G2} {B7 B6 B5 B4 B3}

可以修正,比如(当然人眼无法感觉,但是RG888-RGB565-RGB888的时候更好补偿)

R:197=>197>>3=24

R:197=192+5=>24+0.625≈25

所以

R5=R[2] ? R[7:3]+1 : R[7:3];

G5=G[1] ? G[7:2]+1 : G[7:2];

B5=B[2] ? B[7:3]+1 : B[7:3];

16bit RGB565-> 24bit RGB888 的转换

16bit RGB656 {R4 R3 R2 R1 R0}{G5 G4 G3 G2 G1 G0} {B4 B3 B2 B1 B0}

24ibt RGB888 {R4 R3 R2 R1 R0 0 00} {G5 G4 G3 G2 G1 G0 0 0} {B4 B3 B2 B1 B0 0 0 0}

24ibt RGB888 {R4 R3 R2 R1 R0 R2R1 R0} {G5 G4 G3 G2 G1 G0 G1 G0} {B4 B3 B2 B1 B0 B2 B1 B0}

8bit RGB332-> 24bit RGB888 的转换

8bit RGB332 {R2 R1 R0} {G2 G1G0} {B1 B0}

24bit RGB888 {R2 R1 R0 0 0 0 00} {G2 G1 G0 0 0 0 0 0} {B1 B0 0 0 0 0 0 0}

24bit RGB888 {R2 R1 R0 R2 R1 R00 0} {G2 G1 G0 G2 G1 G0 0 0} {B1 B0 B1 B0 0 0 0 0}

24bit RGB888 {R2 R1 R0 R2 R1 R0R2 R1} {G2 G1 G0 G2 G1 G0 G2 G1} {B1 B0 B1 B0 B1 B0 0 0}

24bit RGB888 {R2 R1 R0 R2 R1 R0R2 R1} {G2 G1 G0 G2 G1 G0 G2 G1} {B1 B0 B1 B0 B1 B0 B1 B0}

总结一下:

量化压缩的方法:三个字取高位

量化补偿的方法:

1. 将原数据填充至高位

2. 对于低位,用原始数据的低位进行补偿

在 Video_Image_Processor 的例化中,需要注意的是 clk 必须输入 cmos_pclk,即 OV5640 的像素时钟,否则 100%出错!

最后,将 Video_Image_Processor 输出得到的处理后的视频流信号,输出给DDR 的 RD-FIFO 写入端口。虽然我们完成了完整的 RGB888 转 YCbCr444,不过灰度显示只需要 Y 通道,因此 CbCr 这里并没有使用到。

此外,这里需要注意的是由于输入的是 8Bit 灰度信号,而输出端(VGA 需要) 的彩色信号,需要进行 R = G = B = Y 来将彩色转换为灰度信号, HDL 如下所示:

1. wire clk_write = cmos_pclk; //Change with input signal 2. wire[23:0]sys_data_in = {post_img_Y,post_img_Y, post_img_Y}; 3. wire sys_we = post_frame_clken;

必须注意的是,DDR RD-FIFO 写入时钟为 cmos_pclk,而写入使能、写入数据为 图像处理后的数据。

全编译,下载后测试, HDMI完美的显示了灰度视频。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 OpenFPGA 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.1.2 RGB888 转 YCbCr 的 HDL 实现
  • 1.1.3 RGB888 转 YCbCr 功能测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档