详解色彩模型、色域以及颜色空间转换

常见的色彩模型

  • RGB色彩模型:常用于电视、摄像机等显示器,遵循加色法则(光的三原色,越混越白);缺点是色彩空间不够均匀,不容易进行色差的评价;与直观的色调、饱和度、亮度没有直接联系,不符合人的认知心理

  • CMYK模型:用于彩色打印,对应青、品红、黄、黑,遵循减色法则(颜料三原色,越混越黑),可以满足打印的要求

  • YCbCr模型:是YUV压缩和偏移的版本,将亮度(Y)和色度(U, V)分开。Cb、Cr指蓝色色度、红色色度,图片(JPEG)、视频(MPEG)、DVD、摄像机、数字电视都采用这一格式,注意在不同色域(如BT.601, BT.709)下要使用相应的RGB2YUV转换矩阵

  • CIEXYZ色彩模型:为人眼实际感知的物体色彩响应值,是一种设备无关的颜色系统,可以由反射率、CIE XYZ人眼颜色匹配函数、光源SPD (Spectral Power Distribution)量化,是一种非均匀的颜色空间。各颜色空间各自独立,但可以通过XYZ作为媒介,变换至其他的颜色空间

  • CIELAB色彩模型:是一种设备无关的颜色系统,基于一种颜色不能同时既是蓝又是黄这个理论而建立。同一个椭圆内的颜色人眼观察起来是相同的,可见CIELAB的椭圆大小和形状优于CIELUV,是一种相对均匀的颜色空间
    在这里插入图片描述

  • CIELCH色彩模型:采用了同CIELAB一样的颜色空间,但它采用L表示明度值,C表示饱和度以及H表示色调角度值的柱形坐标

  • HSL色彩模型 (Hue, Saturation, Lightness):类似于人眼对颜色的感知方式。HSL将RGB色彩模型中的点在圆柱坐标系中表示,H-色相,如红色、黄色等;S-饱和度,取0-100%;L-亮度,取0-100%
    在这里插入图片描述

  • HSV色彩模型 (Hue, Saturation, Value):同样类似于人眼对颜色的感知方式。HSV在概念上常被认为是颜色的倒圆锥体,代表可区分出的饱和度和色相的级别数目随着明度接近黑色而降低。实际应用经常选择HSV色轮,色相表示为圆环, 三角形表示饱和度和明度,对饱和度的定义和HSL是明显不同的
    在这里插入图片描述

常见的色域

CIE 1931 XYZ颜色空间:为人眼最大能感知色彩的范围,将人眼三刺激值量化后,取x和y平面图。其中黑线为普朗克曲线,从右至左的范围为1000K-20000K
在这里插入图片描述

在这里插入图片描述

  • sRGB:是美国HP公司和Microsoft公司于1997年开发的标准颜色空间
  • Adobe RGB:是Adobe Systems于1998年开发的色彩空间,粗略包括了50%的Lab色彩空间中的可视色彩,主要在青绿色系上有所提升,能够在CMYK彩色印刷中利用计算机显示器等设备的RGB颜色模式上囊括更多的颜色
  • DCI-P3:美国电影行业推出的一种广色域标准,是目前数位电影回放设备的色彩标准之一,比sRGB大了25%,绿色和红色的范围更广;苹果、索尼、三星等公司正逐步将DCI-P3作为广色域的标准;90% DCI-P3色域代表HDR规格显示器的基本色彩要求
  • BT. 2020:传统电视使用BT.601和BT.709色域,大多数高清电视和电脑会使用BT.709或DCI/P3,超高清电视会使用更广的色域空间,如BT.2020,包含可视颜色的57.3%
    在这里插入图片描述

颜色空间转换

包括:

  1. RGB2XYZ / XYZ2RGB
  2. XYZ2LAB / LAB2XYZ
  3. LAB2LCH
  4. RGB2HSL / HSL2RGB
  5. RGB2HSV / HSV2RGB
  6. RGB2YUV / YUV2RGB (Origin / Full Range / Limit Range)
  7. RGB2LAB
RGB2XYZ / XYZ2RGB
  • sRGB
    在这里插入图片描述
  • P3
    在这里插入图片描述
  • RGB2XYZ Code
def RGB2XYZ(RGB, mode):
	if mode == "sRGB":
		RGB2XYZ_matrix = np.array(
			[[0.412391, 0.357584, 0.180481],
			 [0.212639, 0.715169, 0.072192],
			 [0.019331, 0.119195, 0.950532]])
	if mode == "P3":
		RGB2XYZ_matrix = np.array(
			[[0.486571, 0.265668, 0.198217], 
			 [0.228975, 0.691739, 0.079287], 
			 [-0.0000003, 0.045113, 1.043944]])
	XYZ = np.matmul(RGB2XYZ_matrix, RGB)
	return XYZ
  • XYZ2RGB Code
def XYZ2RGB(XYZ, mode):
    if mode == "sRGB":
        XYZ_to_RGB_matrix = np.array(
            [[3.2404542, -1.5371385, -0.4985314],
             [-0.9692660, 1.8760108, 0.0415560],
             [0.0556434, -0.2040259, 1.0572252]])
    elif mode == "P3":
        XYZ_to_RGB_matrix = np.array(
            [[2.4935223, -0.9313173, -0.4027241],
             [-0.8294634, 1.7626022, 0.0236200],
             [0.0358510, -0.0761817, 0.9570290]]
        )
    RGB = np.matmul(XYZ_to_RGB_matrix, XYZ)
    return RGB

XYZ2LAB / LAB2XYZ

转换参考色Xr, Yr, Zr使用D65光源。其中,L∈(0, 100),a∈(-128, 127),代表红绿,b∈(-128, 127),代表黄蓝。在OpenCV中,对LAB数据对齐做了量化,使其处于0~255的范围。
在这里插入图片描述

  • 基于LAB空间,可以计算色差ΔE
    在这里插入图片描述
  • 也可以计算Δab
    在这里插入图片描述
  • XYZ2LAB Code
def XYZ2LAB(XYZ):
	XYZ = np.array(XYZ).astype(np.float64) * 100
	# d65
	white = np.array([95.0489, 100.00, 108.884])
	LAB = np.array([0.0, 0.0, 0.0])
	fx = np.array([0.0, 0.0, 0.0])
	for c in range(3):
        r_white = 0
        if (XYZ[c] / white[c]) > ((6.0 / 29.0) ** 3.0):
            r_white = 1
        fx[c] = fx[c] + r_white * (XYZ[c] / white[c]) ** (1 / 3.0)
        fx[c] = fx[c] + (1 - r_white) * ((841.0 / 108.0) * XYZ[c] / white[c] + 4.0 / 29.0)
    LAB[0] = 116 * fx[1] - 16
    LAB[1] = 500 * (fx[0] - fx[1])
    LAB[2] = 200 * (fx[1] - fx[2]
	return LAB
  • LAB2XYZ Code
def LAB2XYZ(LAB):
    white = np.array([95.0489, 100.00, 108.884])
    xyz = np.array([0.0, 0.0, 0.0])
    XYZ = np.array([0.0, 0.0, 0.0])
    fx = np.array([0.0, 0.0, 0.0])
    fx[1] = (LAB[0] + 16) / 116
    fx[0] = LAB[1] / 500 + fx[1]
    fx[2] = fx[1] - LAB[2] / 200
    if fx[0] ** 3.0 > ((6.0 / 29.0) ** 3.0):
        xyz[0] = fx[0] ** 3.0
    else:
        xyz[0] = (108.0 / 841.0) * fx[0] - 432.0 / 24389.0
    if LAB[0] > 8.0:
        xyz[1] = ((LAB[0] + 16) / 116) ** 3.0
    else:
        xyz[1] = LAB[0] * (3.0 / 29.0) ** 3.0
    if fx[2] ** 3.0 > ((6.0 / 29.0) ** 3.0):
        xyz[2] = fx[2] ** 3.0
    else:
        xyz[0] = (108.0 / 841.0) * fx[2] - 432.0 / 24389.0
    for c in range(3):
        XYZ[c] = xyz[c] * white[c] / 100
    return XYZ

  • ΔE / Δab Code
def deltaC(lab1, lab2):
    """
    求Delta E, Delta ab
    :param lab1: [num,3]
    :param lab2: [num,3]
    :return: Delta E, Delta ab
    """
	delta_E = np.power((np.sum(np.power((lab1[:, 0:] - lab2[:, 0:]), 2), axis=1)), 0.5)
    delta_ab = np.power((np.sum(np.power((lab1[:, 1:] - lab2[:, 1:]), 2), axis=1)), 0.5)  # shape=(120000,)
    delta_E_avg = np.average(delta_E)
    delta_ab_avg = np.average(deltaC)
    return delta_E_avg, delta_ab_avg
LAB2LCH

在这里插入图片描述

  • 基于LCH色彩空间,可以计算ΔHue
    在这里插入图片描述
  • 也可以计算ΔChroma
    在这里插入图片描述
  • LAB2LCH Code
def LAB2LCH(LAB)
	LAB = np.array(LAB)
    [L, A, B] = np.array([LAB[..., x] for x in range(LAB.shape[-1])])
    C = np.hypot(A, B)
    H = np.rad2deg(np.arctan2(B, A))
    LCH = np.concatenate([x[..., np.newaxis]] for x in [L, C, H], axis=-1)
    return LCH
  • ΔHue / ΔChroma Code
def calculate_delta_hue_chroma(LCH1, LCH2):
	[L1, C1, H1] = np.array([LCH1[..., x] for x in range(LCH1.shape[-1])])
	[L2, C2, H2] = np.array([LCH2[..., x] for x in range(LCH2.shape[-1])])
	D_hue = abs(H1 - H2)
	D_chroma = C1 - C2
	return D_hue, D_chroma
RGB2HSL / HSL2RGB

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • RGB2HSL Code
def RGB2HSL(RGB):
    R, G, B = RGB[:, 0], RGB[:, 1], RGB[:, 2]
    max_rgb, argmax_rgb = RGB.max(1)
    min_rgb, argmin_rgb = RGB.min(1)
    del_rgb = max_rgb - min_rgb
    del_R = 60.0 * (G - B) / (del_rgb + epsilon)
    del_G = 60.0 * (B - R) / (del_rgb + epsilon) + 120.0
    del_B = 60.0 * (R - G) / (del_rgb + epsilon) + 240.0
    H = torch.stack((del_R, del_G, del_B), dim=0).gather(dim=0, index=argmax_rgb.unsqueeze(0)).squeeze(0)
    H = torch.where(H < 0, H + 360, H)
    L = (max_rgb + min_rgb) / 2
    S = torch.where(L > 0.5, del_rgb / (2 - 2 * L + epsilon), del_rgb / (2 * L + epsilon))
    HSL = torch.stack((H, S, L), dim=1)
    return HSL

  • HSL2RGB Code
def HSL2RGB(HSL):
    H, S, L = HSL[:, 0].clone(), HSL[:, 1], HSL[:, 2]
    var_2 = torch.where(L < 0.5, L * (1 + S), (L + S) - (L * S))
    var_1 = 2.0 * L - var_2
    H = H / 360.0
    R = hue2rgb(var_1, var_2, h + (1.0 / 3.0))
    G = hue2rgb(var_1, var_2, h)
    B = hue2rgb(var_1, var_2, h - (1.0 / 3.0))
    rgb_lut = torch.stack((R, G, B), dim=1)
    return rgb_lut

def hue2rgb(v1, v2, vh):
	vh_temp = (vh + 1).frac()
    c1 = v1 + (v2 - v1) * 6.0 * vh_temp
    c2 = c3 = v2
    c4 = v1 + (v2 - v1) * ((2.0 / 3.0) - vh_temp) * 6.0
    c5 = c6 = v1
    c = torch.stack((c1, c2, c3, c4, c5, c6), dim=0)
    index = (torch.ceil(vh_temp * 6)).to(torch.long) - 1
    index = torch.where(index < 0, 0, index)
    c = (c.gather(dim=0, index=index.unsqueeze(0))).squeeze(0)
    return c

RGB2HSV / HSV2RGB
  • 对于RGB2HSV,色相H的定义相同,其他分量S, V不同
    在这里插入图片描述
  • 对于HSV2RGB,如果饱和度S和明度V变化于0~1之间,那么RGB可以表示为
    在这里插入图片描述
  • RGB2HSV Code
# answer
def rgb2hsv(img):
    _img = img.copy().astype(np.float32)# / 255
    v_max = _img.max(axis=2)
    v_min = _img.min(axis=2)
    v_argmin = _img.argmin(axis=2)
    hsv = np.zeros_like(_img, dtype=np.float32)
    r, g, b = np.split(_img, 3, axis=2)
    r, g, b = r[..., 0], g[..., 0], b[..., 0]

    diff = np.maximum(v_max - v_min, 1e-10)
    
    # Hue
    ind = v_argmin == 2
    hsv[..., 0][ind] = 60 * (g - r)[ind] / diff[ind] + 60
    ind = v_argmin == 0
    hsv[..., 0][ind] = 60 * (b - g)[ind] / diff[ind] + 180
    ind = v_argmin == 1
    hsv[..., 0][ind] = 60 * (r - b)[ind] / diff[ind] + 300
    ind = v_max == v_min
    hsv[..., 0][ind] = 0
    # Saturation
    hsv[..., 1] = v_max - v_min
    # Value
    hsv[..., 2] = v_max
    return hsv
  • HSV2RGB Code
def hsv2rgb(hsv):
    h, s, v = np.split(hsv, 3, axis=2)
    h, s, v = h[..., 0], s[..., 0], v[..., 0]
    _h = h / 60
    x = s * (1 - np.abs(_h % 2 - 1))
    z = np.zeros_like(x)
    vals = np.array([[s, x, z], [x, s, z], [z, s, x], [z, x, s], [x, z, s], [s, z, x]])
    
    img = np.zeros_like(hsv)
    
    for i in range(6):
        ind = _h.astype(int) == i
        for j in range(3):
            img[..., j][ind] = (v - s)[ind] + vals[i, j][ind]
            
    return np.clip(img, 0, 255).astype(np.uint8)
RGB2YUV / YUV2RGB (Origin)

思路:由Kr, Kg, Kb而来,代表整体R, G, B亮度比例
在这里插入图片描述

  • BT.601
    在这里插入图片描述
    在这里插入图片描述
  • BT.709
    在这里插入图片描述
    在这里插入图片描述
  • Adobe RGB
    在这里插入图片描述
    在这里插入图片描述
  • Display P3
    在这里插入图片描述
    在这里插入图片描述
  • Theater P3
    在这里插入图片描述
    在这里插入图片描述
  • BT.2020
    在这里插入图片描述
    在这里插入图片描述
  • RGB2YUV Code
def RGB2YUV(RGB, mode):
    if mode == "BT601":
        RGB_to_YUV_matrix = np.array(
        	[[0.299, 0.587, 0.114],
        	 [-0.169, -0.331, 0.5],
        	 [0.5, -0.419, -0.081]]
	)
    elif mode == "BT709":
        RGB_to_YUV_matrix = np.array(
        	[[0.2126, 0.7152, 0.072],
        	 [-0.1146, -0.3854, 0.5],
        	 [0.5, -0.4542, -0.0458]]
        )
    elif mode == "AdobeRGB":
        RGB_to_YUV_matrix = np.array(
        	[[0.2973, 0.6274, 0.0753],
        	 [-0.1608, -0.3392, 0.5],
        	 [0.5, -0.4464, -0.0536]]
        )
    elif mode == "DisplayP3":
        RGB_to_YUV_matrix = np.array(
        	[[0.2290, 0.6917, 0.0793],
        	 [-0.1243, -0.3757, 0.5],
        	 [0.5, -0.4486, -0.0514]]
        )
    elif mode == "TheaterP3":
        RGB_to_YUV_matrix = np.array(
        	[[0.2095, 0.7216, 0.0689],
        	 [-0.1125, -0.3875, 0.5],
        	 [0.5, -0.4564, -0.0436]]
        )
    elif mode == "BT2020":
    	RGB_to_YUV_matrix = np.array(
    		[[0.2627, 0.6780, 0.0593],
    		 [-0.1396, -0.3604, 0.5],
    		 [0.5, -0.4598, -0.0402]]
        )
    YUV = np.matmul(RGB_to_YUV_matrix, RGB)
    return YUV
  • YUV2RGB Code
def YUV2RGB(YUV, mode):
    if mode == "BT601":
        YUV_to_RGB_matrix = np.array(
        	[[1.0000, -0.0009, 1.4017],
        	 [1.0000, -0.3437, -0.7142],
        	 [1.0000, 1.7722, 0.0010]]
	)
    elif mode == "BT709":
        YUV_to_RGB_matrix = np.array(
        	[[1.0000, -0.0009, 1.5747],
        	 [1.0000, -0.1873, -0.4682],
        	 [1.0000, 1.8556, 0.0000]]
        )
    elif mode == "AdobeRGB":
        YUV_to_RGB_matrix = np.array(
        	[[1.0000, 0.0000, 1.4053],
        	 [1.0000, -0.2220, -0.6661],
        	 [1.0000, 1.8494, 0.0000]]
        )
    elif mode == "DisplayP3":
        YUV_to_RGB_matrix = np.array(
        	[[1.0000, 0.0000, 1.5421],
        	 [1.0000, -0.2111, -0.5104],
        	 [1.0000, 1.8414, 0.0000]]
        )
    elif mode == "TheaterP3":
        YUV_to_RGB_matrix = np.array(
        	[[1.0000, 0.0000, 1.5810],
        	 [1.0000, -0.1778, -0.4590],
        	 [1.0000, 1.8622, 0.0000]]
        )
    elif mode == "BT2020":
    	YUV_to_RGB_matrix = np.array(
    		[[1.0000, 0.0000, 1.4746],
    		 [1.0000, -0.1646, -0.5714],
    		 [1.0000, 1.8814, 0.0000]]
        )
    RGB = np.matmul(YUV_to_RGB_matrix, YUV)
    return RGB
RGB2YUV / YUV2RGB (Full Range)

所有RGB和YCbCr的范围都是0~255,与RGB2YUV转换公式一致,最后加上0, 128, 128用来拉伸至255

  • BT.601
    在这里插入图片描述
    在这里插入图片描述
  • BT.709
    在这里插入图片描述
    在这里插入图片描述
  • Adobe RGB
    在这里插入图片描述
    在这里插入图片描述
  • Display P3
    在这里插入图片描述
    在这里插入图片描述
  • Theater P3
    在这里插入图片描述
    在这里插入图片描述
  • BT.2020
    在这里插入图片描述
    在这里插入图片描述
RGB2YUV / YUV2RGB (Limited Range)

指Y, Cb, Cr的值不到最大值的255,其中Y的范围16~235,Cb和Cr为16-240,RGB范围为0-255。Limit Range由Full Range转换而来,先将Y, U, V各通道乘以219, 224, 224转成Y, Cb, Cr,再将得到的数除以255得到系数,如果要转为8bit再乘以256

  • BT.601
    在这里插入图片描述
    在这里插入图片描述
  • BT.709
    在这里插入图片描述
    在这里插入图片描述
  • Adobe RGB
    在这里插入图片描述
    在这里插入图片描述
  • Display P3
    在这里插入图片描述
    在这里插入图片描述
  • Theater P3
    在这里插入图片描述
    在这里插入图片描述
  • BT.2020
    在这里插入图片描述
    在这里插入图片描述
RGB2LAB

即先做RGB2XYZ, 再做XYZ2LAB

def rgb2lab(rgb, color_gamut):
    """
    RGB2LAB
    :param rgb: rgb矩阵
    :param color_gamut: 色域
    :return: lab矩阵
    """

    # ============sRGB转xyz============
    xyz = rgb  # 转为float
    # gamma矫正
    mask = xyz > 0.04045
    xyz[mask] = np.power((xyz[mask] + 0.055) / 1.055, 2.4)
    xyz[~mask] /= 12.92
    # 线性变换
    if color_gamut == 'sRGB':
        xyz = xyz @ MAT_RGB2XYZ.T
    if color_gamut == 'P3':
        xyz = xyz @ MAT_P32XYZ.T
    # ============xyz转lab============
    XYZ_REF_WHITE = np.array([0.95047, 1.0, 1.08883]) # 白点校正
    xyz /= XYZ_REF_WHITE

    # nonlinear transform
    mask = xyz > 0.008856
    xyz[mask] = np.power(xyz[mask], 1.0 / 3.0)
    xyz[~mask] = 7.787 * xyz[~mask] + 16.0 / 116.0
    x, y, z = xyz[..., 0], xyz[..., 1], xyz[..., 2]

    # linear transform
    lab = np.zeros(xyz.shape)
    lab[..., 0] = (116.0 * y) - 16.0  # L channel
    lab[..., 1] = 500.0 * (x - y)  # a channel
    lab[..., 2] = 200.0 * (y - z)  # b channel
    return lab
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值