OpenCV色彩空间详解

1,224 阅读5分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

何为色彩空间

为了更好的进行图像处理,我们有时会使用不同的色彩空间。色彩空间是一个抽象的数学模型概念,色彩是人的眼睛对于不同频率的光线的不同感受,为了更好的表示色彩,人们建立了多种色彩模型以一维、二维、三维等坐标系来描述不同色彩,这种坐标系所能定义的色彩范围即色彩空间。

色彩空间基础

首先介绍流行 OpenCV 中的色彩空间的基础知识—— RGBCIE L*a*b*HSLHSV 以及 YCbCr

OpenCV 提供了 150 多种色彩空间转换方法来执行用户所需的转换。在以下示例中,将演示如何将以 RGB 色彩空间加载的图像转换到其他色彩空间(例如,HSVHLSYCbCr)。

显示色彩空间

常用的色彩空间如下表所示:

色彩空间简介
RGB加色空间,特定颜色由红色、绿色和蓝色的分量值表示,其工作方式与人类视觉类似的,因此该色彩空间非常适合用于计算机显示图像图形
CIELAB也称为 CIE Lab* 或简称为 LAB,将特定颜色表示为三个数值,其中 L* 表示亮度,a* 表示绿-红分量,b* 表示蓝色-黄色成分,通常用于一些图像处理算法
HSVHSV 是RGB色彩空间的一种变形,特定颜色使用色相 (hue)、饱和度 (saturation)、明度 (value) 三个分量表示
HSL也称 HLS 或 HSI (I指intensity),与 HSV非常相似,区别在于其使用亮度 (lightness) 替代了明度 (brightness)
YCbCr视频和数字摄影系统中使用的一系列色彩空间,根据色度分量 (Y) 和两个色度分量( Cb 和 Cr )表示颜色,在图像分割中非常流行

在以下示例中,图像被加载到 BGR 色彩空间并转换为上述色彩空间。在此脚本中,关键函数是 cv2.cvtColor(),它可以将一种色彩空间的输入图像转换为另一种色彩空间。

def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(3, 6, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=8)
    plt.axis('off')

image = cv2.imread('example.png')

plt.figure(figsize=(12, 5))
plt.suptitle("Color spaces in OpenCV", fontsize=14, fontweight='bold')

gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

(bgr_b, bgr_g, bgr_r) = cv2.split(image)

# 转换为 HSV
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
(hsv_h, hsv_s, hsv_v) = cv2.split(hsv_image)

# 转换为 HLS
hls_image = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
(hls_h, hls_l, hls_s) = cv2.split(hls_image)

# 转换为 YCrCb
ycrcb_image = cv2.cvtColor(image, cv2.COLOR_BGR2YCrCb)
(ycrcb_y, ycrcb_cr, ycrcb_cb) = cv2.split(ycrcb_image)

show_with_matplotlib(image, "BGR - image", 1)

# Show gray image:
show_with_matplotlib(cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR), "gray image", 1 + 6)

# 显示 RGB 分量通道
show_with_matplotlib(cv2.cvtColor(bgr_b, cv2.COLOR_GRAY2BGR), "BGR - B comp", 2)
show_with_matplotlib(cv2.cvtColor(bgr_g, cv2.COLOR_GRAY2BGR), "BGR - G comp", 2 + 6)
show_with_matplotlib(cv2.cvtColor(bgr_r, cv2.COLOR_GRAY2BGR), "BGR - R comp", 2 + 6 * 2)

# 展示其他色彩空间分量通道
# ...

色彩空间

在进行色彩空间转换时,应明确指定通道的顺序( BGRRGB ):

# 将图像加载到 BGR 色彩空间中
image = cv2.imread('color_spaces.png')
# 将其转换为 HSV 色彩空间
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

可以看到,此处使用了 cv2.COLOR_BGR2HSV 而不是 cv2.COLOR_RGB2HSV

不同色彩空间在皮肤分割中的不同效果

上述色彩空间可用于不同的图像处理任务和技术。以皮肤分割为例,我们在不同的色彩空间内查看不同的算法执行皮肤分割的效果。

在这个示例中的关键函数除了上述 cv2.cvtColor() 函数外,还包括 cv2.inRange(),它用于检查数组中包含的元素是否位于接受的两个数组参数的元素之间(下边界数组和上边界数组)。

因此,我们使用 cv2.inRange() 函数来检测与皮肤对应的颜色。为这两个数组(下边界和上边界)定义的值在分割算法的性能中起着至关重要的作用,可以通过修改上、下边界数组进行实验,以找到最合适的值。

import numpy as np
import cv2
import matplotlib.pyplot as plt
import os

# Name and path of the images to load:
image_names = ['1.png', '2.png', '3.png', '4.png', '5.png', '6.png']
path = 'skin_test_imgs'


# Load all test images building the relative path using 'os.path.join'
def load_all_test_images():
    """Loads all the test images and returns the created array containing the loaded images"""

    skin_images = []
    for index_image, name_image in enumerate(image_names):
        # Build the relative path where the current image is:
        image_path = os.path.join(path, name_image)
        # print("image_path: '{}'".format(image_path))
        # Read the image and add it (append) to the structure 'skin_images'
        img = cv2.imread(image_path)
        skin_images.append(img)
    # Return all the loaded test images:
    return skin_images


# 可视化
def show_images(array_img, title, pos):
    for index_image, image in enumerate(array_img):
        show_with_matplotlib(image, title + "_" + str(index_image + 1), pos + index_image)


# 
def show_with_matplotlib(color_img, title, pos):
    # 将 BGR 图像转化为 RGB
    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(5, 6, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=8)
    plt.axis('off')

# 上下界数组
lower_hsv = np.array([0, 48, 80], dtype='uint8')
upper_hsv = np.array([20, 255, 255], dtype='uint8')

# 基于 HSV 颜色空间的皮肤检测
def skin_detector_hsv(bgr_image):
    hsv_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2HSV)
    # 在 HSV 色彩空间中查找具有肤色的区域
    skin_region = cv2.inRange(hsv_image, lower_hsv, upper_hsv)
    return skin_region

lower_hsv_2 = np.array([0, 50, 0], dtype="uint8")
upper_hsv_2 = np.array([120, 150, 255], dtype="uint8")


# 基于 HSV 颜色空间的皮肤检测
def skin_detector_hsv_2(bgr_image):
    # 将图片从 BRG 色彩空间转换到 HSV 空间
    hsv_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2HSV)

    # 在 HSV 色彩空间中查找具有肤色的区域
    skin_region = cv2.inRange(hsv_image, lower_hsv_2, upper_hsv_2)
    return skin_region

lower_ycrcb = np.array([0, 133, 77], dtype="uint8")
upper_ycrcb = np.array([255, 173, 127], dtype="uint8")

# 基于 YCrCb 颜色空间的皮肤检测
def skin_detector_ycrcb(bgr_image):
    ycrcb_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2YCR_CB)
    skin_region = cv2.inRange(ycrcb_image, lower_ycrcb, upper_ycrcb)
    return skin_region

# 基于 bgr 颜色空间的皮肤检测的阈值设定
def bgr_skin(b, g, r):
    # 值基于论文《RGB-H-CbCr Skin Colour Model for Human Face Detection》
    e1 = bool((r > 95) and (g > 40) and (b > 20) and ((max(r, max(g, b)) - min(r, min(g, b))) > 15) and (abs(int(r) - int(g)) > 15) and (r > g) and (r > b))
    e2 = bool((r > 220) and (g > 210) and (b > 170) and (abs(int(r) - int(g)) <= 15) and (r > b) and (g > b))
    return e1 or e2

# 基于 bgr 颜色空间的皮肤检测
def skin_detector_bgr(bgr_image):
    h = bgr_image.shape[0]
    w = bgr_image.shape[1]

    res = np.zeros((h, w, 1), dtype='uint8')

    for y in range(0, h):
        for x in range(0, w):
            (b, g, r) = bgr_image[y, x]
            if bgr_skin(b, g, r):
                res[y, x] = 255
    
    return res

skin_detectors = {
    'ycrcb': skin_detector_ycrcb,
    'hsv': skin_detector_hsv,
    'hsv_2': skin_detector_hsv_2,
    'bgr': skin_detector_bgr
}

def apply_skin_detector(array_img, skin_detector):
    skin_detector_result = []
    for index_image, image in enumerate(array_img):
        detected_skin = skin_detectors[skin_detector](image)
        bgr = cv2.cvtColor(detected_skin, cv2.COLOR_GRAY2BGR)
        skin_detector_result.append(bgr)
    return skin_detector_result

plt.figure(figsize=(15, 8))
plt.suptitle("Skin segmentation using different color spaces", fontsize=14, fontweight='bold')

# 加载图像
test_images = load_all_test_images()

# 绘制原始图像
show_images(test_images, "test img", 1)

# 对于每个图像应用皮肤检测函数
for i, key in enumerate(skin_detectors.keys()):
    show_images(apply_skin_detector(test_images, key), key, 7 + i * 6)

plt.show()

构建 skin_detectors 字典将所有皮肤分割算法应用于测试图像,上例中定义了四个皮肤检测器。可以使用以下方法调用皮肤分割检测函数(例如 skin_detector_ycrcb ):

detected_skin = skin_detectors['ycrcb'](image)

程序的分割结果如下所示:

皮肤分割

可以使用多个测试图像来查看应用不同皮肤分割算法的效果,以了解这些算法在不同条件下的工作方式。