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

YCbCr422 转 RGB888 的 HDL 实现

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

1.1 ITU-R BT.656 格式简说

ITU-R BT.601 和 ITU-R BT.656 是 国 际 电 信 联 盟 ( International Telecommunication Union)无线通信部门(ITU-R)制定的标准。严格来说,ITU-RBT.656 应该是隶属 ITU-RBT.601 的一个子协议。 ITU-RBT.601 是演播室数字电视编码参数标准,而 ITU-R BT.656 则是 ITU-R BT.601 附件 A 中的数字接口标准,用于主要数字视频设备(包括芯片)之间采用 27Mhzs 并口或 243Mbs 串行接口的数字传输接口标准。

详见《Image\005_OV5640_DDR3_YCbCr422_RGB888\DOC\ITU-R BT.656 协议.docx》

由于 ITU-R BT.656 视频信号为 YUV 信号,同时,目前 CMOS 摄像头支持RGB565、 YCbCr、 Bayer 这几种模式。只玩过 RGB565,未免不太爽了。研究YUV 格式视频的解码,对于未来 BT.656 视频流的处理,以及相关算法的了解,很有好处。既然决定了做视频图像算法, 那么 YCbCr 转 RGB888 算法, 就必须得搞定。

YUV 信号的提出,是因为国际上出现彩色电视,为了兼容黑白电视的信号而设计的,其由于视频码率,压缩,兼容性的优势,一直被沿用至今。如下是完整的 YUV4:2:2 的视频格式数据流:

图5‑1完整的 YUV4:2:2 的视频格式数据流

为了识别帧头帧尾,在 YUV 标准中,还添加了帧头帧尾基准码,如FF0000XY。 PAL/NTSC 都是通过模拟传输的,在接收端通过解码后,将是以上的序列,因此可以用通过 FF0000XY 这几个序列的软件解码,解码出标准数字视频流的行场信号。 更多关于 BT.656 视频流的帧头、帧尾识别标准,请查看相关文档。

《Image\005_OV5640_DDR3_YCbCr422_RGB888\DOC\图像时序规范》

在本章中,我们只研究针对 OV5640 的 YCbCr 解码算法的实现。

OV5640 在 YCbCr422 格式下输出的视频流格式如下。当然由于 OV5640 同时输出了行场信号,我们可以直接硬件解码,不需要通过 FF0000XY 识别。同行OV5640 输出的 YCbCr422 是阉割版的 BT.656 视频流信号,即视频流逐行输出,不存在标准 BT.656 所谓的奇场,偶场信号。

图5‑2 OV5640 输出的 YCbCr422 是阉割版的 BT.656 视频流信号

由于 YCbCr422 格式下,每行发送 640*4 个数据,与 RGB565 一样。因此,我们只需要根据 cmos_vsync、 cmos_href,完全按照 RGB565 一样的接收模式接受数据,同时经过后续 YUV422 转 RGB888 的算法处理,便可以实现 YCbCr422实现 RGB888 数据格式的转换。

1.2 YUV/YCbCr 视频格式简说

YUV 由 Y、 U、 V 复合而成,其中 Y:亮度(16-235) , U:色彩 V:饱和度。YUV 有很多格式,比如 4:2:2; 4:2:2; 4:2:0 等。使用最多的是 YUV422 格式,如下图所示:

图5‑4 YUV422格式码流

YUV422 模式即水平方向上 UV 的采样速度为 Y 的一半,相当于每两个点采样一个 U、 V,每一个点采样一个 Y。这样被允许的原因是因为,我们的眼睛对亮度的敏感度远大于对色度的敏感度,因此可以通过牺牲色度的采样率来达到图像数据压缩的目的。

当年的黑白电视,只有亮度,即 Y; YUV 格式的出现很好的兼容了不同制式的电视,因为 YUV 既能兼容灰度信号,又能通过 YUV2RGB 可以转换为彩色图像,兼容彩色液晶。不明白的孩子,可以直接让{R,G,B}={Y,Y,Y},看看是不是黑白灰度的图像。

YUV 主要应用在模拟系统中,而 YCbCr 是通过 YUV 信号的发展,通过了校正,主要应用在数字视频中的一种格式,一般意义上 YCbCr 即为 YUV 信号,没有严格的划分。 CbCr 分别为蓝色色差、红色色差,详细的说明请看前面的文章。

1.3 YUV422 格式的配置与拼接捕获

此时我们将注意力转移到 OV5640的寄存器配置中来。 前面我们已经完成了RGB565 格式、 RAW8 格式的视频流输出配置, 我们只需要修改极少的一两个寄存器,便能转换为 YUV422 输出。 关于 0x4300 的 寄存器设置, 仅需修改此处便能配置为 YUV422 格式输出,寄存器如下所示:

图5‑8 YUV422格式输出寄存器设置

详细说明请看数据手册86页。最常见的格式为 Cb、 Y、 Cr、 Y, 即 UYVYUYVY,即如图中设置。

这里还需要设置一下ISP即0x501f这一个寄存器,如上图所示。

不过有一个值得开心的事情是, RGB565 与 YUV 格式输出的视频,每行均有640*2 个 像素 , 及 速 率 完 全 一 样 , 因 此 我 们 可 以 直 接 套 用sensor_decode 的采集框架, 模块输出的 16Bit 的 cmos_frame_data, 即为 UY 或 VY 的拼接信号, 并且按照 UY、 VY、 UY、 VY...的序列输出。

此时我们已经得到了 YCbCr 相邻 2 个数据拼接后的结果,在后续模块中,可以直接通过这个序列,来完成 YUV422 到 RGB888 的转换

1.4 YUV422 转 YUV444 的 HDL实现

首先,第一步,前面得到的 YCbCr422 为 2:1 的分量,为了更直观的实现YCbCr转 RGB 的算法,我们首先将 YCbCr422 转换成 YCbCr444, 即通过 Cb、Cr 的分配,完整的将每个像素均赋予 YCbCr 的格式。 这里 Bingo 通过多级寄存及分量拼接, 从第三个像素的时刻开始,持续输出完整的 YCbCr 的格式, 实现的步骤如下:

(1) 寄存 Cb0、 Y0

(2) 寄存 Cr0、 Y1

(3) 输出 Y0、 Cb0、 Cr0,寄存 Cb1、 Y2

(4) 输出 Y1、 Cb0、 Cr0,寄存 Cr1、 Y3

(5) 输出 Y2、 Cb1、 Cr1,寄存 Cb02、 Y02

(6) 输出 Y3、 Cb1、 Cr1,寄存 Cr02、 Y12

(7) ……

可见,通过(1)与(2)的寄存,从(3) 开始,便可以持续的输出完整的YCbCr 格式。 但是问题(1) 与(2) 消耗了 2 个时钟,因此我们需要人为的生成 2 个时钟, 来补充最后 2 个像素的数据输出。 这可以通过 per_frame_clken 的寄存实现, 如下所示:

这里延时 4 个时钟的原因,是由于cmos_pclk 的时钟频率,为拼接后输出的速度的 2 倍。因此 4 次寄存,刚好延时了 2 个像素。 yuv_process_href 与yuv_process_clken 作为前面(1) ~(7) ……的读取使能与读取时钟信号。这里给出 YCbCr422 恢复 YCbCr444 的实现方式, 如下:

这里给出上述 0~5 的状态机转移图, 如下所示。可见从 0~1 为寄存, 2~5 开始循环输出, 直到一行数据的结束。

5‑9状态机转移图

1.5 YUV444 转 RGB888 的 HDL 实现

上一小节中,我们已经得到了每个像素均完整的 8Bit 的 Y、 Cb、 Cr 信号,在此设计 YCbCr444 转 RGB888 算法, 完全几乎与 RGB888 转 YCbCr444 类似的实现方式。

相关的软件应用手册同样给出了 YCbCr 转 RGB 的算法,如下所示:

用上述的转换方式,图像非常的可以。因此转换公式,如下:

R = 1.164(Y-16) + 1.596(Cr-128)

G = 1.164(Y-16) - 0.391(Cb-128)- 0.813(Cr-128)

B = 1.164(Y-16) + 2.018(Cb-128)

->

R = 1.164Y + 1.596Cr - 222.912

G = 1.164Y - 0.391Cb - 0.813Cr +135.488

B = 1.164Y + 2.018Cb - 276.928

->

R << 9 = 596Y + 817Cr -114131

G << 9 = 596Y - 200Cb -416Cr + 69370

B << 9 = 596Y + 1033Cb –141787

针对上式而言,我们需要提取最后的 R、 G、 B,这里只需要进行三个步骤。 首先,计算每一个步中的分量,如下所示:

代码5‑1

1. reg [19:0] img_Y_r1; //8 + 9 + 1 = 18Bit 2. reg [19:0] img_Cb_r1, img_Cb_r2; 3. reg [19:0] img_Cr_r1, img_Cr_r2; 4. always@(posedge clk or negedge rst_n) 5. begin 6. if(!rst_n) 7. begin 8. img_Y_r1 <= 0; 9. img_Cb_r1 <= 0; img_Cb_r2 <= 0; 10. img_Cr_r1 <= 0; img_Cr_r2 <= 0; 11. end 12. else 13. begin 14. img_Y_r1 <= per_img_Y * 18'd596; 15. img_Cb_r1 <= per_img_Cb * 18'd200; 16. img_Cb_r2 <= per_img_Cb * 18'd1033; 17. img_Cr_r1 <= per_img_Cr * 18'd817; 18. img_Cr_r2 <= per_img_Cr * 18'd416; 19. end 20.end

第二步,计算 512 倍扩大、 9 次移位后的结果, 如下所示:

代码 5‑2

1. //-------------------------------------------- 2. /********************************************** 3. R << 9 = 596Y + 817Cr - 114131 4. G << 9 = 596Y - 200Cb - 416Cr + 69370 5. B << 9 = 596Y + 1033Cb - 141787 6. **********************************************/ 7. reg [19:0] XOUT; 8. reg [19:0] YOUT; 9. reg [19:0] ZOUT; 10.always@(posedge clk or negedge rst_n) 11.begin 12. if(!rst_n) 13. begin 14. XOUT <= 0; 15. YOUT <= 0; 16. ZOUT <= 0; 17. end 18. else 19. begin 20. XOUT <= (img_Y_r1 + img_Cr_r1 - 20'd114131)>>9; 21. YOUT <= (img_Y_r1 - img_Cb_r1 - img_Cr_r2 + 20'd69370)>>9; 22. ZOUT <= (img_Y_r1 + img_Cb_r2 - 20'd141787)>>9; 23. end 24.end

第三步,根据上述 XOUT、 YOUT、 ZOUT 的结果, 如果小于 0(Bit[10] ==1) ,则赋 0; 如果大于 255, 则赋 255; 如果在 0~255 之间,则保持原值, 具体的实现如下所示:

代码 5‑3

1. //------------------------------------------ 2. //Divide 512 and get the result 3. //{xx[19:11], xx[10:0]} 4. reg [7:0] R, G, B; 5. always@(posedge clk or negedge rst_n) 6. begin 7. if(!rst_n) 8. begin 9. R <= 0; 10. G <= 0; 11. B <= 0; 12. end 13. else 14. begin 15. R <= XOUT[10] ? 8'd0 : (XOUT[9:0] > 9'd255) ? 8'd255 : XOUT[7:0]; 16. G <= YOUT[10] ? 8'd0 : (YOUT[9:0] > 9'd255) ? 8'd255 : YOUT[7:0]; 17. B <= ZOUT[10] ? 8'd0 : (ZOUT[9:0] > 9'd255) ? 8'd255 : ZOUT[7:0]; 18. end 19.end

此时我们将得到最终的 RGB888 信号,由于前面经过了 3 个时钟的计算, 因此必须将行场、读取使能信号偏移 3 个时钟。 同时根据行有效信号,使能输出最终的 RGB 信号,如下所示:

代码 5‑4

1. //------------------------------------------ 2. //lag n clocks signal sync 3. reg [2:0] post_frame_vsync_r; 4. reg [2:0] post_frame_href_r; 5. reg [2:0] post_frame_clken_r; 6. always@(posedge clk or negedge rst_n) 7. begin 8. if(!rst_n) 9. begin 10. post_frame_vsync_r <= 0; 11. post_frame_href_r <= 0; 12. post_frame_clken_r <= 0; 13. end 14. else 15. begin 16. post_frame_vsync_r <= {post_frame_vsync_r[1:0], per_frame_vsync}; 17. post_frame_href_r <= {post_frame_href_r[1:0], per_frame_href}; 18. post_frame_clken_r <= {post_frame_clken_r[1:0], per_frame_clken}; 19. end 20.end 21.assign post_frame_vsync = post_frame_vsync_r[2]; 22.assign post_frame_href = post_frame_href_r[2]; 23.assign post_frame_clken = post_frame_clken_r[2]; 24.assign post_img_red = post_frame_href ? R : 8'd0; 25.assign post_img_green = post_frame_href ? G : 8'd0; 26.assign post_img_blue = post_frame_href ? B : 8'd0;

1.6 YCbCr422 转 RGB888 功能测试

将 CMOS_Capture_RGB565 模 块 捕 获 的 YCbCr 输 出 给Video_Image_Processor, 并且经过相应的算法模块,实现YCbCr422 转 RGB888 功能

详细代码请参考源码。

最后,全编译,下载测试, HDMI显示通过 YCbCr422→YCbCr444→RGB888的彩色图像。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.1 ITU-R BT.656 格式简说
  • 1.2 YUV/YCbCr 视频格式简说
  • 1.3 YUV422 格式的配置与拼接捕获
  • 1.4 YUV422 转 YUV444 的 HDL实现
  • 1.5 YUV444 转 RGB888 的 HDL 实现
  • 1.6 YCbCr422 转 RGB888 功能测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档