PotatoPie 4.0开发板教程目录(2024/04/15)
什么是色彩空间?
“色彩空间”一词源于西方的“Color Space”,又称作“色域”,色彩学中,人们建立了多种色彩模型,以一维、二维、三维甚至四维空间坐标来表示某一色彩,这种坐标系统所能定义的色彩范围即色彩空间。我们经常用到的色彩空间主要有RGB色彩空间和YUV色彩空间。
RGB 颜色空间
RGB 颜色系统可能是您更熟悉的一种。 RGB 色彩空间是红色、绿色和蓝色的缩写,是一个在每个屏幕像素中使用这三种颜色来显示照片和视频的系统。
与 YCbCr 色彩空间不同,RGB 同时处理颜色和亮度,因此您的文件将占用更多存储空间。
全 RGB 的颜色系统有 255 个级别 – 0 级表示绝对黑色,而 255 级表示绝对白色。在第 1 级到第 254 级之间,您将获得通过使用红色、绿色和蓝色的不同组合创建的全光谱颜色。
RGB 系统最常用于计算机显示器、电视、摄像、摄影等。它也是视频游戏、网页设计和数字媒体的首选色彩系统。
YCbCr色彩空间
YCbCr 根据亮度 (luma)(又称整体亮度)定义颜色。 “Y”代表亮度,“Cb”代表蓝色减去亮度,“Cr”代表红色减去亮度。
YCbCr 格式能够分离亮度和色度分量,从而尽可能地从文件中删除冗余。
考虑到人眼对亮度比颜色更敏感,YCbCr优先捕捉亮度细节,消除一些不必要和多余的色度细节。这使您的设备可以更好地压缩 YCbCr 文件并节省一些存储空间,同时尽可能保留视频和图像的详细信息。
考虑到这一点,YCbCr 格式仍然可以产生令人惊讶的广泛颜色范围,使其成为印刷机的绝佳选择。
YCbCr不是一种绝对色彩空间,是YUV压缩和偏移的版本。YCbCr的Y与YUV中的Y含义一致,Cb和Cr与UV同样都指色彩,Cb指蓝色色度,Cr指红色色度。YCbCr 也是视频的本机格式,在应用上很广泛,JPEG、MPEG、DVD、摄影机、数字电视等皆采此一格式。因此一般俗称的YUV大多是指YCbCr。
YCbCr 色彩空间与 YUV 色彩空间在概念上很相似,实际上在很多应用中,它们的术语常常被互换使用。但在技术上有一些微妙的区别:
-
YCbCr 色彩空间是被国际电信联盟(ITU)标准化的,而 YUV 并非官方标准,通常用于描述模拟视频信号。
-
YCbCr 色彩空间的转换公式经过了精确的数学推导和标准化,而 YUV 的转换公式可能会有一些变化。
-
YCbCr 色彩空间更为严格地定义了色度和亮度分量的范围,以便于数字信号的编码和解码。
总的来说,YCbCr 色彩空间是一种常用于数字视频处理的标准化色彩编码系统,它将图像的亮度和色度分开表示,便于对彩色图像进行压缩、传输和处理。
YCbCr格式有:
- 4∶4∶4
- 4∶2∶2
- 4∶1∶1
- 4∶2∶0
YCbCr 和 RGB 颜色之间的联系
YCbCr 和 RGB 都是决定成像系统可以表示的颜色的主要色彩空间 – 然而,这就是两者之间仅有的联系点。
RGB 将所有颜色表示为不同级别的红、绿和蓝的独特组合。所有三个通道都必须以全分辨率存储,因此您的图像和视频会更重并占用更多空间。
另一方面,YCbCr 将亮度和颜色分开,因此您始终可以以较高分辨率存储亮度以获取更多细节,并以较低分辨率存储颜色,因为无论如何人眼都无法完全看到它们。
YCbCr 与 RGB 的转换公式
RGB转YCbCrY = 0.2568*R + 0.5041*G + 0.0979*B + 16;
Cb = -0.1482*R - 0.2910*G + 0.4392*B + 128;
Cr = 0.4392*R - 0.3678*G - 0.0714*B + 128;
YCbCr转RGBR = 1.1644*(Y- 16) + 1.5960*(Cr - 128);
G = 1.1644*(Y - 16) - 0.3918*(Cb- 128) -0.8130*(Cr- 128);
B = 1.1644*(Y - 16) + 2.0172*(Cb- 128);
python实现上YCbCr转RGB
import cv2
import numpy as np
# 定义 RGB 到 YCbCr 转换的函数
def rgb_to_ycbcr(rgb):
# 分离 RGB 通道
R = rgb[:,:,0]
G = rgb[:,:,1]
B = rgb[:,:,2]
# 计算 Y、Cb、Cr 分量
Y = 0.2568 * R + 0.5041 * G + 0.0979 * B + 16
Cb = -0.1482 * R - 0.2910 * G + 0.4392 * B + 128
Cr = 0.4392 * R - 0.3678 * G - 0.0714 * B + 128
# 将 Y、Cb、Cr 分量合并成 YCbCr 图像
return np.stack((Y, Cb, Cr), axis=-1)
import os
# 文件绝对路径
current_file_path = __file__
# 借助dirname()从绝对路径中提取目录
current_file_dir = os.path.dirname(current_file_path)
print(f"current_file_dir: {current_file_dir}")
# 读取图片
img = cv2.imread(current_file_dir+'/Lena.jpg')
# 显示原始图像
cv2.imshow('Original Image', img)
# 调用转换函数,将 RGB 图像转换为 YCbCr 色彩空间
ycbcr = rgb_to_ycbcr(img)
# 提取 Y、Cb、Cr 分量
Y = ycbcr[:,:,0]
Cb = ycbcr[:,:,1]
Cr = ycbcr[:,:,2]
# 显示 Y、Cb、Cr 分量图
cv2.imshow('Y Component', Y.astype(np.uint8))
cv2.imshow('Cb Component', Cb.astype(np.uint8))
cv2.imshow('Cr Component', Cr.astype(np.uint8))
# 等待按下任意键继续并关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
代码读取一张图片并将其转换为 YCbCr 色彩空间。然后提取出 Y、Cb、Cr 分量,并显示每个分量的图像。请将 lena.jpg
替换为您要读取的图片文件的路径。
运行结果如下图
matlab实现上YCbCr转RGB
这个Matlab函数实现了RGB到YCbCr色彩空间的转换,并显示了原始图像以及转换后的Y、Cb、Cr分量图像。
import cv2
import numpy as np
# 定义 RGB 到 YCbCr 转换的函数
def rgb_to_ycbcr(rgb):
# 分离 RGB 通道
R = rgb[:,:,0]
G = rgb[:,:,1]
B = rgb[:,:,2]
# 计算 Y、Cb、Cr 分量
Y = 0.2568 * R + 0.5041 * G + 0.0979 * B + 16
Cb = -0.1482 * R - 0.2910 * G + 0.4392 * B + 128
Cr = 0.4392 * R - 0.3678 * G - 0.0714 * B + 128
# 将 Y、Cb、Cr 分量合并成 YCbCr 图像
return np.stack((Y, Cb, Cr), axis=-1)
import os
# 文件绝对路径
current_file_path = __file__
# 借助dirname()从绝对路径中提取目录
current_file_dir = os.path.dirname(current_file_path)
print(f"current_file_dir: {current_file_dir}")
# 读取图片
img = cv2.imread(current_file_dir+'/Lena.jpg')
# 显示原始图像
cv2.imshow('Original Image', img)
# 调用转换函数,将 RGB 图像转换为 YCbCr 色彩空间
ycbcr = rgb_to_ycbcr(img)
# 提取 Y、Cb、Cr 分量
Y = ycbcr[:,:,0]
Cb = ycbcr[:,:,1]
Cr = ycbcr[:,:,2]
# 显示 Y、Cb、Cr 分量图
cv2.imshow('Y Component', Y.astype(np.uint8))
cv2.imshow('Cb Component', Cb.astype(np.uint8))
cv2.imshow('Cr Component', Cr.astype(np.uint8))
# 等待按下任意键继续并关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
matlab运行脚本时会出现类似下图的错误,提示rgb2ycbcr.m已在matlab路径中存在,直接点更改文件夹即可。
FPGA工程分析
工程数据流图
层次分析
模块代码分析
与demo18相比,只是多了一个rgb2ycbcr的模块,也就是这一段代码,在从SDRAM读出来之后,经它处理后再输出hdmi_tx模块
rgb2ycbcr u_rgb2ycbcr
(
.i_clk(clk_pixel),
.i_rst_n(sys_rst_n),
.i_vs(VGA_VS),
.i_hs(VGA_HS),
.i_de(VGA_DE),
.i_r(VGA_RGB[23:16]),
.i_g(VGA_RGB[15:8]),
.i_b(VGA_RGB[7:0]),
.o_vs(ycbcr_vs),
.o_hs(ycbcr_hs),
.o_de(ycbcr_de),
.o_y(ycbcr_Y),
.o_cb(ycbcr_Cb),
.o_cr(ycbcr_Cr)
);
EG4 FPGA可以实现的转换公式
前面的转换公式存在浮点数,我们需要进行浮点转定点的转换。
方法是将式子的右边先乘以一个256,四舍五入取整,然后再除以一个256,这样等式右边相当于没变(不过有一点误差),而除以256对于逻辑来说就是右移256。当然也不是一定要乘以256,乘以1024或2048也是可以的,只是没有必要。
最终算式变成这样:
Y = (66*image_in_r + 129*image_in_g + 25*image_in_b + 4096 )>>8;
Cb = (-38*image_in_r - 74*image_in_g + 112*image_in_b + 32768)>>8;
Cr = (112*image_in_r - 94*image_in_g - 18*image_in_b + 32768 )>>8;
代码中主要就两段。
第一步先计算乘法:
r_d0 <= (66 * i_r) ;
g_d0 <= (129 * i_g) ;
b_d0 <= (25 * i_b) ;
r_d1 <= (38 * i_r) ;
g_d1 <= (74 * i_g) ;
b_d1 <= (112 * i_b) ;
r_d2 <= (112 * i_r) ;
g_d2 <= (94 * i_g) ;
b_d2 <= (18 * i_b) ;
第二步计算加减法:
y_d0 <= (r_d0 + g_d0 + b_d0 + 4096 ) ;
cb_d0 <= (b_d1 - r_d1 - g_d1 + 32768) ;
cr_d0 <= (r_d2 - g_d2 - b_d2 + 32768) ;
需要注意几点:
- 上面的代码之所以要将公式拆分成两步的原因是为了优化时序,所以做了一个两级流水。
- 由于上一步做了流水分隔,所以相应的行场信号要往后延一拍。
hs <= {hs[0],i_hs};
vs <= {vs[0],i_vs};
de <= {de[0],i_de};- 要注意因为计算y_d0,bc_d0, br_d0的时候先乘了256,因此需要位宽要16位,这样带来另一个好处,直接取高位就相当于右移了8位。
-
实验现象
实验结果
只显示Y分量的图像
- 只显示Cb分量
- 只显示Cr分量