使用Python/PIL从RGB转换到HSV颜色空间并检测阈值

10

我想将RGB图像转换为黑白RGB图像,其中如果像素的HSV值在某个范围内,则为黑色,否则为白色。

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

感觉应该有更快的方法来做到这一点,例如使用.point(),但似乎.point()不是给定像素,而是0到255的值。是否存在一个基于像素的.point()转换?


需要将图像转换为HSV吗?您可以考虑根据您的要求范围进行转换,以找到适当的RGB要求窗口(转换不是线性的,因此想知道是否可以使用近似值)。 - Paul
2
你使用NumPy吗?当事情不是“标准”的图像调整时,我通常会避免使用大多数PIL函数,而是使用NumPy数组操作。 - Paul
这段代码包含了RGB-HSV转换的优秀C代码http://www.cs.rit.edu/~ncs/color/t_convert.html,现在只需要将其转化为NumPy格式即可。 - Benjamin
@Claudiu:如果这对你的问题有帮助,我可以建议将标题更改为类似于“使用Python / PIL在HSV颜色空间(从RGB)中检测阈值”? - Benjamin
2
我使用 scikits.image.color.rgb2hsv() 来获取HSV值,如果你能安装numpy/scipy等库的话,这是很好的选择。但是@paul,我敢打赌PIL中没有HSV函数,因为没有一种图像二进制格式存储它们的数据为HSV值。而且,最近在PIL中几乎没有添加任何东西...<哭脸/> - fish2000
显示剩余5条评论
4个回答

21

好的,这个有效的(修复了一些溢出错误):

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的定义。我会在有更多时间时仔细查看它。肯定有加速和可能存在的错误。如果您发现任何问题,请告诉我。干杯。


结果:

从这个色轮开始:

enter image description here

我得到了以下结果:

色相:

enter image description here

值:

enter image description here

饱和度:

enter image description here


我从没见过这种使用掩码的方式。我必须承认,这比我的方法更易于理解。 - Benjamin
我会删除它们。您介意使用相同的色轮发布自己的结果吗?.png文件可以在此处找到:http://www.palette.com/hsvwheel.png - Paul
1
不论型号如何,c始终被定义为M-m。在你的代码中,应该将Value (v) 的定义更改为 M。Value 始终是 M,仅适用于 hexcone 型号。 - Paul
使用您的代碼會造成因為C似乎為零(使用hsvwheel.png)而導致除以零的結果! - Stefan Profanter
我修复了上面的代码并添加了RGB_TO_HSL。请查看我在这个帖子中的发言。 - Stefan Profanter

5

编辑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)

这段代码给出了颜色的 Hue 值在 0 到 360 之间,饱和度值在 0 到 1 之间,亮度值在 0 到 1 之间。我以图像格式查看了结果,它们看起来不错。

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

然后,您可以根据这些值选择像素,并使用类似以下内容的代码将它们设置为 1(或白色/黑色):

newimage = (v > 0.3) * 1

我收回之前的说法。灰度值定义为色相为0只是一种惯例。只要你知道它们存在,NAN也同样有效。 - Paul
哇!几乎变成了一行代码!你漏了一个括号,而且“d”没有定义。色相测试中的条纹已经消失了,但还有其他问题。我想知道scipy的misc.image工具是否在做一些奇怪的事情? - Paul
我肯定是在编辑过程中复制粘贴了。顺便说一下,你的工作做得很好。我一直希望numpy有一个类似于“select”的函数。现在我知道有这个函数了! - Paul
1
这是一张单色图像。圆形中心的像素具有非零色调,但您的算法却显示它们为零。我采取了额外的步骤,将您的数组转换为“uint8”数组,并通过PIL输出(Image.fromarray((255*h/360).astype('uint8'),'L').save('hsvwheel_Hb2.png')),结果相同。这意味着错误与scipy的misc.imsave()函数无关。您的算法仍然存在问题。 - Paul
哎呀!你对 Mm 的定义 包括 alpha 通道。你的问题在于 scipy 是支持 alpha 的!把 m, M = numpy.min(image, 2), numpy.max(image, 2) 改为 m, M = numpy.min(image[:,:,:3], 2), numpy.max(image[:,:,:3], 2) 就可以解决问题了。 - Paul
显示剩余3条评论

2

这个解决方案基于Paul的代码。我修复了除零错误并实现了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

1

我认为最快的结果可能是通过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元组。


1
使用NumPy的asarray()函数是通常的转换技术,因为它可以节省一步。我不清楚你在使用piecewise()做什么。你能详细说明一下吗? - Paul
请详细说明这里发生了什么。 - Claudiu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接