首頁 > 軟體

python實現RGB與YCBCR顏色空間轉換

2022-03-10 13:01:02

前言:

人類如何感知或者理解顏色是個非常複雜的問題,本文不討論如何從生物學或者心理學角度來分析顏色,而是分析“數值大小如何影響顏色”。文中主要介紹了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 + shift

2、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

但是,由於最終影象呈現出的顏色是三RGB三者的疊加,而現實中不僅僅是其中之一的顏色,所以很難控制最終影象的顏色,所以我們需要其它的顏色空間。

3、YCbCr顏色空間及與RGB的變換關係

YCbCr顏色空間中的Y是亮度通道,Cb是藍色分量,Cr是紅色分量。它在電視系統中比較常用,比如早期的黑白電視機使用彩色電視訊號線,就可以單獨使用亮度值;這種功能RGB顏色空間就做不到,因為我們不能僅僅使用RGB中某個通道作為亮度訊號來使用。

由於YCbCr經常和YUV顏色空間比較相似,所以二者容易被認為是從屬或者等價關係,按照維基百科的說法:YUV 是模擬訊號,而YCbCr是數位訊號。

YCbCr和RGB存線上性的變換關係,本文介紹的變換矩陣來自ITU.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顏色空間轉換內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com