OpenCV-Python实战(6)——OpenCV中的色彩空间和色彩映射

0. 前言

为了更好的图像处理,我们有时会使用不同的色彩空间。色彩空间是一个抽象的数学模型概念。颜色是人眼对不同频率光的不同感知。为了描述不同的颜色,这个坐标系可以定义的颜色范围就是颜色空间。颜色映射是将图像从一个颜色空间映射到另一个颜色空间的操作,通常将灰度图像渲染成等效的伪彩色图像。

1. 色彩空间

首先介绍流行OpenCV中的色彩空间的基础知识——RGB、CIE L*a*b*、HSL、HSV以及YCbCr。
OpenCV提供了150多种色彩空间转换方法来执行用户所需的转换。在以下示例中,将演示如何将以RGB色彩空间加载的图像转换到其他色彩空间(例如,HSV、HLS或YCbCr)。

1.1 显示色彩空间

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

色彩空间 简介
RGB 加色空间,特定颜色由红色、绿色和蓝色的分量值表示,其工作方式与人类视觉类似的,因此该色彩空间非常适合用于计算机显示图像图形
CIELAB 也称为 CIE Lab* 或简称为 LAB,将特定颜色表示为三个数值,其中 L* 表示亮度,a* 表示绿-红分量,b* 表示蓝色-黄色成分,通常用于一些图像处理算法
HSV HSV 是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)

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

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

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

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

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

上述色彩空间可用于不同的图像处理任务和技术。以皮肤分割为例,我们可以看到不同算法在不同颜色空间中的皮肤分割效果如何。
在这个示例中的关键函数除了上述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)

程序的分割结果如下:

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

2. 色彩映射

在某些计算机视觉应用中,算法的输出是灰度图像。然而,人眼对彩色图像的变化比对灰度图像的变化更敏感。因此,我们经常需要将灰度图像重新着色为等效的伪彩色图像。

2.1 OpenCV 中的色彩映射

为了执行这种色彩转换,OpenCV 包含多种色彩映射来增强可视化效果,cv2.applyColorMap()函数在给定的图像上应用色彩映射,例如应用cv2.COLORMAP_HSV色彩映射:

img_COLORMAP_HSV = cv2.applyColorMap(gray_img, cv2.COLORMAP_HSV)

OpenCV定义的色彩映射如下(可以直接利用编号作为参数来调用相应色彩映射,类似别名):

色彩映射名 编号
COLORMAP_AUTUMN 0
COLORMAP_BONE 1
COLORMAP_JET 2
COLORMAP_WINTER 3
COLORMAP_RAINBOW 4
COLORMAP_OCEAN 5
COLORMAP_SUMMER 6
COLORMAP_SPRING 7
COLORMAP_COOL 8
COLORMAP_HSV 9
COLORMAP_HOT 11
COLORMAP_PINK 10
COLORMAP_PARULA 12

我们可以将所有颜色图应用到同一张灰度图像上,并在同一张图中绘制它们:

def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]

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

# 加载图像并转化为灰度图像
gray_img = cv2.imread('12.png', cv2.IMREAD_GRAYSCALE)

# 色彩映射列表
colormaps = ["AUTUMN", "BONE", "JET", "WINTER", "RAINBOW", "OCEAN", "SUMMER", "SPRING", "COOL", "HSV", "HOT", "PINK", "PARULA"]

plt.figure(figsize=(12, 5))
plt.suptitle("Colormaps", fontsize=14, fontweight='bold')

show_with_matplotlib(cv2.cvtColor(gray_img, cv2.COLOR_GRAY2BGR), "GRAY", 1)

# 应用色彩映射
for idx, val in enumerate(colormaps):
    show_with_matplotlib(cv2.applyColorMap(gray_img, idx), val, idx + 2)

plt.show()

运行程序的结果如下图所示:

色彩映射
在上图中,您可以看到将所有预定义的颜色图应用于灰度图像以增强可视化效果。

2.2 自定义色彩映射

可以通过多种方式将自定义颜色图应用于图像。
第一种方法是定义一个色彩映射,将0到255个灰度值映射到256种颜色。这可以通过创建大小为256 x 1的8位彩色图像来完成,以便存储所有创建的颜色。之后,可以以下方法通过查找表将图像的灰度强度映射到定义的颜色:

  1. 使用cv2.LUT()函数
  2. 使用cv2.applyColorMap()函数
    需要注意的是,在创建大小为256 x 1的8位彩色图像用于存储图像时,如果打算使用cv2.LUT(),则应按如下方式创建图像:
lut = np.zeros((256, 3), dtype=np.uint8)

如果打算使用cv2.cv2.applyColorMap(),则应使用:

lut = np.zeros((256, 1, 3), dtype=np.uint8)

完整代码如下:

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

def apply_rand_custom_colormap_values(im_gray):
    lut = np.random.randint(255, size=(256, 1, 3), dtype=np.uint8)

    im_color = cv2.applyColorMap(im_gray, lut)
    return im_color

def apply_rand_custom_colormap_values2(im_gray):
    # 创建随机 LUT
    lut = np.random.randint(255, size=(256, 3), dtype=np.uint8)

    # 使用 cv2.LUT() 应用自定义色彩映射
    s0, s1 = im_gray.shape
    im_color = np.empty(shape=(s0, s1, 3), dtype=np.uint8)
    for i in range(3):
        im_color[..., i] = cv2.LUT(im_gray, lut[:, i])
    return im_color

def show_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]

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


# 读取图像并转化为灰度图像
gray_img = cv2.cvtColor(cv2.imread('8.png'), cv2.COLOR_BGR2GRAY)

plt.figure(figsize=(12, 2))
plt.suptitle("Custom colormaps providing all values", fontsize=14, fontweight='bold')

show_with_matplotlib(cv2.cvtColor(gray_img, cv2.COLOR_GRAY2BGR), "gray", 1)

# 应用色彩映射
custom_rand_1 = apply_rand_custom_colormap_values(gray_img)
custom_rand_2 = apply_rand_custom_colormap_values2(gray_img)
# 可以自行创建固定值色彩映射并应用,与随机创建类似,在此不再赘述
# custom_values_1 = apply_custom_colormap_values(gray_img)
# custom_values_2 = apply_custom_colormap_values2(gray_img)

# 可视化
show_with_matplotlib(custom_rand_1, "cv2.applyColorMap()", 2)
show_with_matplotlib(custom_rand_2, "cv2.LUT()", 3)

plt.show()

自定义色彩映射

自定义颜色图的第二种方法是只提供几个关键颜色,然后对这些值进行插值以获得构建查找表所需的所有颜色。
编写build_lut()函数根据这些关键颜色构建查找表:基于 5 个预先定义的色点,调用np.linespace()在预定义的每个色点区间内计算均匀间隔的颜色:

def build_lut(cmap):
    lut = np.empty(shape=(256, 3), dtype=np.uint8)

    max = 256
    # 构建查找表
    lastval, lastcol = cmap[0]
    for step, col in cmap[1:]:
        val = int(step * max)
        for i in range(3):                                                                     lastval, val - lastval))
            lut[lastval:val, i] = np.linspace(lastcol[i], col[i], val - lastval)
        lastcol = col
        lastval = val

    return lut

然后应用自定义颜色图:

def show_with_matplotlib(color_img, title, pos):
    # 通道转换
    img_RGB = color_img[:, :, ::-1]

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

def apply_color_map_1(gray, cmap):

    lut = build_lut(cmap)
    s0, s1 = gray.shape
    out = np.empty(shape=(s0, s1, 3), dtype=np.uint8)

    for i in range(3):
        out[..., i] = cv2.LUT(gray, lut[:, i])
    return out

def apply_color_map_2(gray, cmap):

    lut = build_lut(cmap)
    lut_reshape = np.reshape(lut, (256, 1, 3))
    im_color = cv2.applyColorMap(gray, lut_reshape)
    return im_color

# 读取图像并转化为灰度图像
gray_img = cv2.cvtColor(cv2.imread('example.png'), cv2.COLOR_BGR2GRAY)

# 应用色彩映射
custom_1 = apply_color_map_1(gray_img, ((0, (255, 0, 255)), (0.25, (255, 0, 180)), (0.5, (255, 0, 120)),
                                        (0.75, (255, 0, 60)), (1.0, (255, 0, 0))))

custom_2 = apply_color_map_1(gray_img, ((0, (0, 255, 128)), (0.25, (128, 184, 64)), (0.5, (255, 128, 0)),
                                        (0.75, (64, 128, 224)), (1.0, (0, 128, 255))))

custom_3 = apply_color_map_2(gray_img, ((0, (255, 0, 255)), (0.25, (255, 0, 180)), (0.5, (255, 0, 120)),
                                        (0.75, (255, 0, 60)), (1.0, (255, 0, 0))))

custom_4 = apply_color_map_2(gray_img, ((0, (0, 255, 128)), (0.25, (128, 184, 64)), (0.5, (255, 128, 0)),
                                        (0.75, (64, 128, 224)), (1.0, (0, 128, 255))))

# 可视化
show_with_matplotlib(custom_1, "custom 1 using cv2.LUT()", 2)
show_with_matplotlib(custom_2, "custom 2 using cv2.LUT()", 3)
show_with_matplotlib(custom_3, "custom 3 using cv2.applyColorMap()", 5)
show_with_matplotlib(custom_4, "custom 4 using using cv2.applyColorMap()", 6)

自定义色彩映射
在上图中,您可以看到将两个自定义颜色图应用于灰度图像的效果。

2.3 显示自定义色彩映射图例

最后,我们也可以在显示自定义颜色映射时提供图例。为了构建色彩映射图例,编写build_lut_image():

def build_lut_image(cmap, height):
    lut = build_lut(cmap)
    image = np.repeat(lut[np.newaxis, ...], height, axis=0)

    return image

其首先调用build_lut()函数以获取查找表。然后,调用np.repeat()以多次复制此查找表( height 次)。这是由于查找表的形状是 (256, 3),而输出图像的形状为 (height, 256, 3) ,为了增加新维度,我们还需要将np.repeat()与np.newaxis()一起使用:

image = np.repeat(lut[np.newaxis, ...], height, axis=0)

运行结果如下图所示:

色彩映射图例

在上图中,您可以看到将两个自定义颜色图应用于灰度图像并为每个颜色图显示图例的效果。

概括

为了更好的表示色彩,建立了多种色彩模型以一维、二维、三维等坐标系来描述不同色彩,这种坐标系所能定义的色彩范围即色彩空间,,函数cv2.cvtColor()可以将一种色彩空间的输入图像转换为另一种色彩空;而色彩映射是将图像在一个色彩空间映射至另一色彩空间的操作,cv2.applyColorMap()或cv2.LUT()函数在给定的图像上应用色彩映射,通常可以将灰度图像着色为等效的伪色彩图像,同时还介绍了 用于获取限定上下界的cv2.inRange()函数。

系列链接

OpenCV-Python实战(1)——OpenCV简介与图像处理基础
OpenCV-Python实战(2)——图像与视频文件的处理
OpenCV-Python实战(3)——OpenCV中绘制图形与文本
OpenCV-Python实战(4)——OpenCV常见图像处理技术
OpenCV-Python实战(5)——OpenCV图像运算)
OpenCV-Python实战(7)——直方图详解
OpenCV-Python实战(8)——直方图均衡化
OpenCV-Python实战(9)——OpenCV用于图像分割的阈值技术
OpenCV-Python实战(10)——OpenCV轮廓检测
OpenCV-Python实战(11)——OpenCV轮廓检测相关应用
OpenCV-Python实战(12)——一文详解AR增强现实
OpenCV-Python实战(13)——OpenCV与机器学习的碰撞
OpenCV-Python实战(14)——人脸检测详解

版权声明:本文为博主盼小辉丶原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/LOVEmy134611/article/details/120069317

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2022年2月18日
下一篇 2022年2月18日

相关推荐