Python实现RGB与YCbCr颜色空间转换详解
本文详细解析了Python中RGB到YCbCr颜色空间的转换原理与代码实现。通过ITUT BT-601标准矩阵进行像素级计算,并展示了逆变换过程。同时,对比了自定义实现与OpenCV标准库在YCrCb格式下的转换差异,帮助开发者深入理解颜色空间变换机制。
前言:
人类对颜色的感知与理解是一个复杂的课题,本文不涉及生物学或心理学层面的分析,而是聚焦于“数值大小如何影响颜色表现”。文章主要阐述RGB与YCbCr两种颜色空间的基本概念及其相互转换关系。
1、灰度值与亮度的关系
人类能够从灰度图像中提取理解场景所需的大部分信息,因此观看黑白电视并不会严重影响对视频内容的理解。图像的亮度与像素值成正比,若需提升图像亮度(例如从黑色渐变至白色),可通过增加单通道灰度图像的像素值来实现。保存灰度图像时,每个像素通常占用8个bit,像素值范围在0到255之间。
以下示例展示了随着灰度图像像素值增加,亮度发生变化的过程,假设图像初始像素值为0:

上图显示了Lena图像在像素值增加过程中肤色的变化。代码实现较为简单,主要步骤为读取图片,然后不断对图像每个像素值增加偏移量:
import numpy as np
import matplotlib.pyplot as plt
import imageio
image = imageio.imread("lena.jpg")
# 设置每次循环像素的增加量
shift = 6*np.ones(shape=(64, 64))
plt.figure()
for i in range(1, 17):
plt.subplot(4, 4, i)
plt.imshow(image/255, cmap="gray", vmin=0, vmax=1)
plt.axis("off")
image = image + shift2、RGB颜色空间与颜色控制
RGB模型在硬件设备中应用广泛,通过R(红色)、G(绿色)、B(蓝色)三者的叠加可以混合出更多颜色。RGB颜色空间与后续介绍的YCbCr及HSV颜色空间之间存在线性变换关系,因此只要拥有RGB图像,即可推导出其他颜色空间的图像。
在一幅图像中,R、G、B分别作为三个通道。如果其中两个通道的值为0,图像的颜色将由非零的那个通道主导。
例如:

实现上述效果需要三个步骤:
- (1)创建一幅3通道的空图像
- (2)给3通道空图像的R通道添加一幅单通道图像
- (3)给3通道图像的R通道像素值不断增加偏移量
# 1:创建一幅3通道的空图像
= np.zeros(shape=(64, 64, 3))
r = imageio.imread("lena.jpg")/2
# 2:给3通道空图像的R通道添加一幅单通道图像
image[:, :, 0] = image[:, :, 0] + r
shift = 4*np.ones(shape=(64, 64))
plt.figure()
for i in range(1, 17):
plt.subplot(4, 4, i)
plt.imshow(image/255, vmin=0, vmax=1)
plt.axis("off")
# (3)给3通道图像的R通道像素值不断增加偏移量
image[:, :, 0] = image[:, :, 0] + shift然而,由于最终图像呈现的颜色是R、G、B三者的叠加,而现实中的颜色并非单一通道所能决定,因此很难直接控制最终图像的颜色表现,这就需要我们引入其他颜色空间。
3、YCbCr颜色空间及与RGB的变换关系
YCbCr颜色空间中,Y代表亮度通道,Cb代表蓝色分量,Cr代表红色分量。该空间在电视系统中应用广泛,例如早期的黑白电视机使用彩色电视信号线时,可以单独使用亮度值;这种功能RGB颜色空间无法实现,因为我们不能仅使用RGB中的某个通道作为亮度信号。
由于YCbCr与YUV颜色空间非常相似,二者常被误认为存在从属或等价关系。根据维基百科的说法:YUV是模拟信号,而YCbCr是数字信号。
YCbCr与RGB之间存在线性变换关系,本文介绍的变换矩阵源自ITUT BT-601标准,规定的变换矩阵Trans形式如下:

实现rgb2ycbcr()函数仅需两个步骤:(1)创建变换矩阵Trans;(2)遍历图像每个像素点,并对三个通道分别进行矩阵计算。
以下代码展示了如何实现从RGB空间到YCbCr的变换:
def rgb2ycbcr(rgb_image):
"""convert rgb into ycbcr"""
if len(rgb_image.shape)!=3 or rgb_image.shape[2]!=3:
raise ValueError("input image is not a rgb image")
rgb_image = rgb_image.astype(np.float32)
# 1:创建变换矩阵,和偏移量
transform_matrix = np.array([[0.257, 0.564, 0.098],
[-0.148, -0.291, 0.439],
[0.439, -0.368, -0.071]])
shift_matrix = np.array([16, 128, 128])
ycbcr_image = np.zeros(shape=rgb_image.shape)
w, h, _ = rgb_image.shape
# 2:遍历每个像素点的三个通道进行变换
for i in range(w):
for j in range(h):
ycbcr_image[i, j, :] = np.dot(transform_matrix, rgb_image[i, j, :]) + shift_matrix
return ycbcr_image若需进行逆变换,只需根据矩阵求逆法则操作即可。需要注意的是:在逆变换时,偏移矩阵也需要左乘变换矩阵Trans的逆!逆变换只需将rgb2ycbcr中的transform_matrix求逆即可,再次强调:shift_matrix也需要乘以transform_matrix的逆,而不是直接减去shift_matrix!
def ycbcr2rgb(ycbcr_image):
"""convert ycbcr into rgb"""
if len(ycbcr_image.shape)!=3 or ycbcr_image.shape[2]!=3:
raise ValueError("input image is not a rgb image")
ycbcr_image = ycbcr_image.astype(np.float32)
transform_matrix = np.array([[0.257, 0.564, 0.098],
[-0.148, -0.291, 0.439],
[0.439, -0.368, -0.071]])
transform_matrix_inv = np.linalg.inv(transform_matrix)
shift_matrix = np.array([16, 128, 128])
rgb_image = np.zeros(shape=ycbcr_image.shape)
w, h, _ = ycbcr_image.shape
for i in range(w):
for j in range(h):
rgb_image[i, j, :] = np.dot(transform_matrix_inv, ycbcr_image[i, j, :]) - np.dot(transform_matrix_inv, shift_matrix)
return rgb_image.astype(np.uint8)所需的包以及绘图代码如下,绘图使用了上述定义的两个函数。首先将RGB转为YCbCr,再从YCbCr转回RGB:
import numpy as np
import imageio
import matplotlib.pyplot as plt
rgb_image = imageio.imread("lena.jpg")
ycbcr_image = rgb2ycbcr(rgb_image)
cycle_image = ycbcr2rgb(ycbcr_image)
images = [rgb_image, ycbcr_image, cycle_image]
titles = ["orignal", "ycbcr", "cycle"]
for i in range(1, len(images)+1):
plt.subplot(1, 3, i)
plt.title(titles[i-1])
plt.imshow(images[i-1]/255)下图中,左边是原始的RGB图像,中间是转换得到的YCbCr空间图像,右边是再次转回RGB空间的图像:

最后,对比了OpenCV提供的标准库转换效果:
import cv2
rgb_image = imageio.imread("lena.jpg")
ycrcb_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2YCR_CB)
cycle_image = cv2.cvtColor(ycbcr_image, cv2.COLOR_YCR_CB2RGB)
images = [rgb_image, ycrcb_image, cycle_image]
titles = ["orignal", "ycrcb", "cycle"]
for i in range(1, len(images)+1):
plt.subplot(1, 3, i)
plt.title(titles[i-1])
plt.imshow(images[i-1]/255)OpenCV得到的结果如下:

原始RGB效果与重构(cycle)效果非常接近,而中间结果不一致是因为OpenCV采用的是“YCrCb”格式,而非“YCbCr”。
到此这篇关于Python实现RGB与YCbCr颜色空间转换的文章就介绍到这了,更多相关Python颜色空间转换内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













