什么是 HSV 颜色
HSV 与 HSL(或在 OpenCV 中的 HLS 中)一样,是圆柱颜色空间之一。
这个名称在某种程度上描述了它们的值是如何被引用的。
色调表示为从 0 到 360 的度数(在 OpenCV 中,为了适应 8 位无符号整数格式,它们的度数除以 2 得到一个从 0 到 179 的数字;所以 OpenCV 中的 110 是 220度)。如果您要获取色调值的“范围”,这就像从蛋糕上切下一片。你只是在吃蛋糕。
饱和度通道是你离中心的距离——你所在的半径。中心绝对没有饱和度——只有从黑色到白色的灰色。如果您采用这些值的范围,则类似于剃掉圆柱体的外部,或从中心切出一个圆。例如,如果范围是 0 到 255,那么范围 0 到 127 将是一个仅延伸到一半半径的圆柱体; 127 到 255 的范围将切割一个半径为一半的内圆柱。
价值通道是一个有点混乱的名字;它不完全是从暗到亮,因为最高值代表直接颜色,而最低值是黑色。这是圆柱体的高度。不难想象垂直切割圆柱体的一部分。
HSV 值的范围
函数cv2.inRange(image, lower_bound, upper_bound) 查找图像在lower_bound 和upper_bound 之间的所有值。例如,如果您的图像是具有 3 通道的 3x3 图像(仅用于简单演示目的),它可能看起来像这样:
# h channel # s channel # v channel
100 150 250 150 150 100 50 75 225
50 100 125 75 25 50 255 100 50
0 255 125 100 200 250 50 75 100
如果我们想选择 100 到 200 之间的色调,那么我们的 lower_b 应该是 [100, 0, 0] 和 upper_b 应该是 [200, 255, 255]。这样我们的蒙版将只考虑色调通道中的值,而不受饱和度和值的影响。这就是 HSV 如此受欢迎的原因——您可以通过色调来选择颜色,而不管它们的明暗程度如何,因此只需指定色调通道的最小值和最大值,就可以选择深红色和亮红色。
但是假设我们只想选择明亮的白色。回顾一下圆柱体模型——我们看到圆柱体的顶部中心给出了白色,所以s 值低,v 值高,并且颜色角度没有事情。所以lower_b 看起来像[0, 0, 200] 和upper_b 看起来像[255, 50, 255]。这意味着所有H 值都将被包括在内,并且不会影响我们的掩码。但随后将仅包括介于 0 和 50 之间的 S 值(朝向圆柱体的中心),并且仅包括从 200 到 255 的 V 值(朝向圆柱体的顶部)。
可视化 HSV 中的一系列颜色
可视化范围内所有颜色的一种方法是为两个通道中的每一个创建沿两个方向长度的渐变,然后在变化的第三个通道上进行动画处理。
例如,您可以为S 值的范围创建从左到右的值梯度,为V 值的范围创建从上到下的梯度,然后循环遍历每个H 值。整个程序可能看起来像这样:
import numpy as np
import cv2
lower_b = np.array([110,50,50])
upper_b = np.array([130,255,255])
s_gradient = np.ones((500,1), dtype=np.uint8)*np.linspace(lower_b[1], upper_b[1], 500, dtype=np.uint8)
v_gradient = np.rot90(np.ones((500,1), dtype=np.uint8)*np.linspace(lower_b[1], upper_b[1], 500, dtype=np.uint8))
h_array = np.arange(lower_b[0], upper_b[0]+1)
for hue in h_array:
h = hue*np.ones((500,500), dtype=np.uint8)
hsv_color = cv2.merge((h, s_gradient, v_gradient))
rgb_color = cv2.cvtColor(hsv_color, cv2.COLOR_HSV2BGR)
cv2.imshow('', rgb_color)
cv2.waitKey(250)
cv2.destroyAllWindows()
现在这个 gif 每帧显示一个新的 H 值。从左到右我们有最小到最大S 值,从上到下我们有最小到最大V 值。此动画中显示的每一种颜色都将从您的图像中选择,作为您的mask 的一部分。
制作你自己的 inRange() 函数
要完全理解OpenCV函数,最简单的方法就是自己制作函数来完成任务。一点都不难,代码也不多。
该函数背后的想法很简单:找到每个通道的值在min 和max 之间的位置,然后将所有通道& 放在一起。
def inRange(img, lower_b, upper_b):
ch1, ch2, ch3 = cv2.split(img)
ch1m = (lower_b[0] <= ch1) & (ch1 <= upper_b[0])
ch2m = (lower_b[1] <= ch2) & (ch2 <= upper_b[1])
ch3m = (lower_b[2] <= ch3) & (ch3 <= upper_b[2])
mask = ch1m & ch2m & ch3m
return mask.astype(np.uint8)*255
您可以阅读OpenCV docs 以了解这确实是使用的公式。我们也可以验证它。
lower_b = np.array([200,200,200])
upper_b = np.array([255,255,255])
mask = cv2.inRange(img, lower_b, upper_b) # OpenCV function
mask2 = inRange(img, lower_b, upper_b) # above defined function
print((mask==mask2).all()) # checks that the masks agree on all values
# True
如何找到合适的颜色
找到用于特定图像的正确值可能有点棘手。不过,有一种简单的实验方法。您可以在 OpenCV 中创建跟踪栏,并使用它们来控制每个通道的最小值和最大值,并让 Python 程序在您每次更改值时更新您的掩码。我为此制作了一个程序,你可以在 GitHub here 上获取它。这是一个动画.gif,用于演示: