OpenCV-Python实战(6)——OpenCV中的色彩空间和色彩映射(❤️含大量示例❤️,建议收藏)
- 0. 前言
- 1. 色彩空间
- 1.1 显示色彩空间
- 1.2 不同色彩空间在皮肤分割中的不同效果
- 2. 色彩映射
- 2.1 OpenCV 中的色彩映射
- 2.2 自定义色彩映射
- 2.3 显示自定义色彩映射图例
- 小结
- 系列链接
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
位彩色图像来完成,以便存储所有创建的颜色。之后,可以以下方法通过查找表将图像的灰度强度映射到定义的颜色:
- 使用
cv2.LUT()
函数 - 使用
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):
"""Shows an image using matplotlib capabilities"""
# Convert BGR image to RGB
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(2, 3, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
# Read grayscale image:
gray_img = cv2.imread('shades.png', cv2.IMREAD_GRAYSCALE)
plt.figure(figsize=(14, 3))
plt.suptitle("Custom color maps based on key colors", fontsize=14, fontweight='bold')
# 读取图像并转化为灰度图像
gray_img = cv2.cvtColor(cv2.imread('8.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图像运算(❤️万字长文,含大量示例❤️)