什么是HSV颜色
HSV(与HSL或在OpenCV中的HLS一样)是圆柱形色彩空间之一。
![cylindrical colorspaces](https://istack.dev59.com/kbBMc.webp)
该名称在某种程度上描述了它们的值如何被引用。
色调表示为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
之间的值。例如,如果您的图像是一个3x3图像(仅用于简单演示目的),具有3个通道,可能如下所示:
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
值都将被包括在内,并且不会影响我们的掩码。但是,只有
S
值在0到50之间(朝向圆柱体中心)和
V
值从200到255(朝向圆柱体顶部)才会被包括。
可视化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()
![范围值的动态图](https://istack.dev59.com/ISFgH.gif)
这个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文档,以了解这确实是使用的公式。我们也可以进行验证。
lower_b = np.array([200,200,200])
upper_b = np.array([255,255,255])
mask = cv2.inRange(img, lower_b, upper_b)
mask2 = inRange(img, lower_b, upper_b)
print((mask==mask2).all())
如何找到合适的颜色
对于特定的图像来说,找到合适的颜色值可能有点棘手。不过有一种简单的方法可以进行实验。您可以在OpenCV中创建滑块,并使用它们控制每个通道的最小值和最大值,并让Python程序在更改值时更新您的遮罩。我为此编写了一个程序,您可以在GitHub上获取这里。这是一个动画.gif
,演示了它的使用方法:
![cspaceThresh程序的GIF](https://i.imgur.com/Kah3s7n.gif)
s_min
到s_max
,从上到下从v_min
到v_max
,然后循环遍历每个色调,以获得它看起来像什么的动画。我会为此做些准备,请给我一点时间。 - alkasmupper = np.array([[[60, 255, 255]]], dtype=np.uint8); upper = cv2.cvtColor(upper, cv2.COLOR_HSV2BGR); upper_img = np.ones((500,500,3), dtype=np.uint8)*upper; cv2.imshow('', upper_img); cv2.waitKey(0)
- alkasm