使用 Python / PIL 检测 HSV 颜色空间中的阈值(来自 RGB)

Posted

技术标签:

【中文标题】使用 Python / PIL 检测 HSV 颜色空间中的阈值(来自 RGB)【英文标题】:Detecting thresholds in HSV color space (from RGB) using Python / PIL 【发布时间】:2011-06-20 21:28:30 【问题描述】:

我想拍摄一张 RGB 图像并将其转换为黑白 RGB 图像,如果像素的 HSV 值在某个范围之间,则该像素为黑色,否则为白色。

目前我创建了一个新图像,然后通过迭代其数据创建一个新像素值列表,然后.putdata() 该列表形成新图像。

感觉应该有一种更快的方法来做到这一点,例如与.point(),但似乎.point() 没有得到给定的像素,而是从0 到255 的值。是否有.point() 变换但在像素上?

【问题讨论】:

图片是否需要转为HSV?您可能会考虑在您的要求范围内进行转换以找到足够的 RGB 要求窗口(转换不是线性的,所以想知道近似值是否可以) 你使用 NumPy 吗?当事情不是“标准”图像调整时,我通常会避开大多数用于 numpy 数组操作的 PIL 函数。 我可以使用 numpy,虽然我不熟悉它。是的,转换必须是 hsv,但如果不是,我将如何使用 rgb 进行转换? 例如,如果您的 HSV 窗口很窄,并且您想从特定颜色的任何方向捕获一个具有 10 个 HSV 值的球体中的所有颜色,那么近似RGB 中的相同窗口,但它可能更像是一个椭圆形窗口,围绕着 RGB 空间中的相同颜色。 我使用scikits.image.color.rgb2hsv() 来获取 HSV 值,如果你能负担得起安装 numpy/scipy/et al 的费用,那就太好了。但是@paul 我敢打赌 HSV 函数不在 PIL 中,因为没有任何图像二进制格式将其数据存储为 HSV 值。此外,最近 PIL 中并没有添加太多东西...... 【参考方案1】:

好的,这确实工作(修复了一些溢出错误):

import numpy, Image
i = Image.open(fp).convert('RGB')
a = numpy.asarray(i, int)

R, G, B = a.T

m = numpy.min(a,2).T
M = numpy.max(a,2).T

C = M-m #chroma
Cmsk = C!=0

# Hue
H = numpy.zeros(R.shape, int)
mask = (M==R)&Cmsk
H[mask] = numpy.mod(60*(G-B)/C, 360)[mask]
mask = (M==G)&Cmsk
H[mask] = (60*(B-R)/C + 120)[mask]
mask = (M==B)&Cmsk
H[mask] = (60*(R-G)/C + 240)[mask]
H *= 255
H /= 360 # if you prefer, leave as 0-360, but don't convert to uint8

# Value
V = M

# Saturation
S = numpy.zeros(R.shape, int)
S[Cmsk] = ((255*C)/V)[Cmsk]

# H, S, and V are now defined as integers 0-255

它基于***对HSV 的定义。当我有更多时间时,我会看看它。肯定有加速,也许还有错误。如果你找到任何东西,请告诉我。干杯。


结果:

从这个色轮开始:

我得到了这些结果:

色相:

价值:

饱和度:

【讨论】:

我从未见过这种使用面具的方式。我必须承认它比我的方法更容易遵循。 我会删除它们。您介意使用相同的色轮发布您自己的结果吗? .png 可以在这里找到:palette.com/hsvwheel.png 色度 (c) 始终定义为 M-m,无论型号如何。 Value v 的定义应在您的代码中更改为 M。值始终为M,并且特定于六角锥模型。 使用您的代码会导致除以零,因为C 似乎为零(使用 hsvwheel.png)?! 我修复了上面的代码并添加了 RGB_TO_HSL。请参阅我在此线程中的帖子。【参考方案2】:

编辑 2:现在返回的结果与 Paul 的代码相同,因为它应该...

import numpy, scipy

image = scipy.misc.imread("test.png") / 255.0

r, g, b = image[:,:,0], image[:,:,1], image[:,:,2]
m, M = numpy.min(image[:,:,:3], 2), numpy.max(image[:,:,:3], 2)
d = M - m

# Chroma and Value
c = d
v = M

# Hue
h = numpy.select([c ==0, r == M, g == M, b == M], [0, ((g - b) / c) % 6, (2 + ((b - r) / c)), (4 + ((r - g) / c))], default=0) * 60

# Saturation
s = numpy.select([c == 0, c != 0], [0, c/v])

scipy.misc.imsave("h.png", h)
scipy.misc.imsave("s.png", s)
scipy.misc.imsave("v.png", v)

它给出了从 0 到 360 的色调,从 0 到 1 的饱和度和从 0 到 1 的值。我查看了图像格式的结果,它们看起来不错。

通过阅读您的问题,我不确定是否只是您感兴趣的 HSV 中的 V 中的“值”。如果是,那么您可以绕过大部分代码。

然后,您可以根据这些值选择像素,并使用以下方法将它们设置为 1(或白色/黑色):

newimage = (v > 0.3) * 1

【讨论】:

我收回了。这只是一个约定,灰度值定义为 0 的色调。只要您知道它们的存在,NAN 的工作就很好。 哇!几乎降到一个班轮!您缺少一个括号,并且未定义 d。色相测试中的条纹消失了,但还有其他问题。我想知道 scipy 的 misc.image 工具是否在做一些奇怪的事情? 我一定是在编辑过程中复制并粘贴了。顺便说一句,干得好。我一直希望在 numpy 中有一个 select-type 函数。现在我知道有一个! 这是我使用你的算法得到的色调:i.imgur.com/JKUsa.jpg 你也得到同样的结果吗?你用的是什么版本的 scipy? 这是一张单色图像。圆圈中心的像素具有非零的色调,但您的算法说它们为零。我采取了额外的步骤,将您的数组转换为“uint8”数组并通过 PIL (Image.fromarray((255*h/360).astype('uint8'),'L').save('hsvwheel_Hb2.png')) 输出,我得到了相同的结果。这意味着该错误与 scipy 的 misc.imsave() 函数无关。你的算法还是有问题。【参考方案3】:

此解决方案基于 Paul 的代码。我修复了 DivByZero 错误并将 RGB 实现为 HSL。还有HSL转RGB:

import numpy

def rgb_to_hsl_hsv(a, isHSV=True):
    """
    Converts RGB image data to HSV or HSL.
    :param a: 3D array. Retval of numpy.asarray(Image.open(...), int)
    :param isHSV: True = HSV, False = HSL
    :return: H,S,L or H,S,V array
    """
    R, G, B = a.T

    m = numpy.min(a, 2).T
    M = numpy.max(a, 2).T

    C = M - m #chroma
    Cmsk = C != 0

    # Hue
    H = numpy.zeros(R.shape, int)
    mask = (M == R) & Cmsk
    H[mask] = numpy.mod(60 * (G[mask] - B[mask]) / C[mask], 360)
    mask = (M == G) & Cmsk
    H[mask] = (60 * (B[mask] - R[mask]) / C[mask] + 120)
    mask = (M == B) & Cmsk
    H[mask] = (60 * (R[mask] - G[mask]) / C[mask] + 240)
    H *= 255
    H /= 360 # if you prefer, leave as 0-360, but don't convert to uint8


    # Saturation
    S = numpy.zeros(R.shape, int)

    if isHSV:
        # This code is for HSV:
        # Value
        V = M

        # Saturation
        S[Cmsk] = ((255 * C[Cmsk]) / V[Cmsk])
        # H, S, and V are now defined as integers 0-255
        return H.swapaxes(0, 1), S.swapaxes(0, 1), V.swapaxes(0, 1)
    else:
        # This code is for HSL:
        # Value
        L = 0.5 * (M + m)

        # Saturation
        S[Cmsk] = ((C[Cmsk]) / (1 - numpy.absolute(2 * L[Cmsk]/255.0 - 1)))
        # H, S, and L are now defined as integers 0-255
        return H.swapaxes(0, 1), S.swapaxes(0, 1), L.swapaxes(0, 1)


def rgb_to_hsv(a):
    return rgb_to_hsl_hsv(a, True)


def rgb_to_hsl(a):
    return rgb_to_hsl_hsv(a, False)


def hsl_to_rgb(H, S, L):
    """
    Converts HSL color array to RGB array

    H = [0..360]
    S = [0..1]
    l = [0..1]

    http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL

    Returns R,G,B in [0..255]
    """

    C = (1 - numpy.absolute(2 * L - 1)) * S

    Hp = H / 60.0
    X = C * (1 - numpy.absolute(numpy.mod(Hp, 2) - 1))

    # initilize with zero
    R = numpy.zeros(H.shape, float)
    G = numpy.zeros(H.shape, float)
    B = numpy.zeros(H.shape, float)

    # handle each case:

    mask = (Hp >= 0) == ( Hp < 1)
    R[mask] = C[mask]
    G[mask] = X[mask]

    mask = (Hp >= 1) == ( Hp < 2)
    R[mask] = X[mask]
    G[mask] = C[mask]

    mask = (Hp >= 2) == ( Hp < 3)
    G[mask] = C[mask]
    B[mask] = X[mask]

    mask = (Hp >= 3) == ( Hp < 4)
    G[mask] = X[mask]
    B[mask] = C[mask]

    mask = (Hp >= 4) == ( Hp < 5)
    R[mask] = X[mask]
    B[mask] = C[mask]

    mask = (Hp >= 5) == ( Hp < 6)
    R[mask] = C[mask]
    B[mask] = X[mask]

    m = L - 0.5*C
    R += m
    G += m
    B += m

    R *=255.0
    G *=255.0
    B *=255.0

    return R.astype(int),G.astype(int),B.astype(int)

def combineRGB(r,g,b):
    """
    Combines separated R G B arrays into one array = image.
    scipy.misc.imsave("rgb.png", combineRGB(R,G,B))
    """
    rgb = numpy.zeros((r.shape[0],r.shape[1],3), 'uint8')
    rgb[..., 0] = r
    rgb[..., 1] = g
    rgb[..., 2] = b
    return rgb

【讨论】:

【参考方案4】:

我认为最快的结果是通过 numpy.该函数看起来类似于(更新,在示例中添加了更多细节):

limg = im.convert("L", ( 0.5, 0.5, 0.5, 0.5 ) )
na = numpy.array ( limg.getdata() )
na = numpy.piecewise(na, [ na > 128 ], [255, 0])
limg.pytdata(na)
limg.save("new.png")

理想情况下,您可以使用分段函数而无需先转换为黑白,这将更像原始示例。语法类似于:

na = numpy.piecewise(na, [ na[0] > 128 ], [255, 0])

但是,您必须小心,因为 RGB 图像的返回值是 3 或 4 元组。

【讨论】:

使用 numpy 的 asarray() 函数是常用的转换技术,因为它节省了一步。我不清楚你要去哪里piecewise()。你能详细说明一下吗? 请详细说明这里发生了什么。

以上是关于使用 Python / PIL 检测 HSV 颜色空间中的阈值(来自 RGB)的主要内容,如果未能解决你的问题,请参考以下文章

使用`cv::inRange` (OpenCV) 为颜色检测选择正确的 HSV 上下边界

在OpenCV中检测对象上的颜色 - Python

如何在OpenCV中为InRange阈值选择颜色的最佳HSV值

如何使用 OpenCV Python 检测颜色

OpenCV:为颜色过滤选择 HSV 阈值

python使用openCV加载图像并将BGR格式转换成HSV格式定义HSV格式中需要分离颜色的掩码(掩模)区间(mask)并使用mask信息进行颜色分离BGR格式的图像转化为RGB并可视化