9

Quiero tomar una imagen RGB y convertirla en una imagen RGB en blanco y negro, donde un píxel es negro si su valor HSV está entre un cierto rango y blanco de lo contrario.Detección de umbrales en el espacio de color HSV (desde RGB) usando Python/PIL

Actualmente creo una nueva imagen, luego creo una lista de nuevos valores de píxel iterando a través de sus datos, luego .putdata() esa lista para formar la nueva imagen.

Parece que debería haber una manera mucho más rápida de hacerlo, p. con .point(), pero parece que .point() no obtiene píxeles sino valores de 0 a 255 en su lugar. ¿Hay una transformación .point() pero en píxeles?

+0

¿Es necesario convertir la imagen a HSV? Puede considerar realizar la transformación en el rango de requisitos para encontrar una ventana de requisitos de RGB adecuada (la transformación no es lineal, por lo que se pregunta si una aproximación es correcta) – Paul

+2

¿Utiliza NumPy? Por lo general, evito la mayoría de las funciones de PIL para las operaciones de matriz numpy cuando las cosas no son ajustes de imagen "estándar". – Paul

+0

podría usar numpy, aunque no estoy familiarizado con él. y sí, la transformación debe ser hsv, pero si no fuera así, ¿cómo lo haría con rgb? – Claudiu

Respuesta

21

Ok, esto hace trabajo (corregidos algunos errores de desbordamiento):

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 

Se basa en la definición de HSV de la Wikipedia. Lo revisaré a medida que tenga más tiempo. Definitivamente hay aceleraciones y tal vez errores. Por favor, avíseme si encuentra alguno. aclamaciones.


Resultados:

que empiecen con este colorwheel: enter image description here

que consiguen estos resultados:

Hue:

enter image description here

Valor:

enter image description here

Saturación:

enter image description here

+0

Nunca he visto esta forma de usar máscaras. Debo admitir que es más fácil de seguir que mi método. – Benjamin

+0

Los eliminaré. ¿Te importaría publicar tus propios resultados usando la misma rueda de colores? a .png se puede encontrar aquí: http://www.palette.com/hsvwheel.png – Paul

+1

croma ('c') siempre se define como' M-m' independientemente del modelo. La definición de Value 'v' debe cambiarse a M en su código. El valor siempre es 'M' y es específico del modelo hexcone. – Paul

1

creo que el resultado sería más rápido a través numpy. La función sería algo como (actualizan, añadido más detalle con el ejemplo):

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") 

Idealmente, usted podría utilizar la función a trozos sin antes convertir a blanco y negro, que sería más parecido al original ejemplo. La sintaxis sería algo a lo largo de las líneas de:

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

Pero, usted tiene que tener cuidado ya que una imagen RGB es o bien un 3 o 4 tupla en el valor de retorno.

+1

usando la función 'asarray()' de numpy es la técnica de conversión habitual, ya que guarda un paso. No estoy seguro de dónde vas con 'piecewise()'. ¿Podrías elaborar? – Paul

+0

ya por favor explica qué está pasando aquí. – Claudiu

+0

actualizado para mostrar el uso de la función por partes. – Claris

5

EDIT 2: Esto ahora devuelve los mismos resultados que el código de Pablo, como debe ser ...

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) 

que da el tono de 0 a 360, la saturación de 0 a 1 y el valor de 0 a 1. Miré los resultados en formato de imagen, y parecen buenos.

No estaba seguro al leer su pregunta si era solo el "valor" como en V de HSV lo que le interesaba. Si es así, puede omitir la mayor parte de este código.

A continuación, puede seleccionar píxeles en base a esos valores y los puso a 1 (o blanco/negro) usando algo como:

newimage = (v > 0.3) * 1 
+0

Lo llevo de vuelta. Es solo una convención que los valores grises se definen con un matiz de 0. El trabajo de NAN funciona tan bien como usted sabe que están allí. – Paul

+0

¡Guau! casi hasta un trazador de líneas! Te falta un paren y 'd' no está definido. Las rayas desaparecieron de la prueba Hue, pero hay otros problemas. Me pregunto si la herramienta de misc.image de scipy está haciendo algo raro. – Paul

+0

Debo haber copiado y pegado a mediados de edición. Buen trabajo por cierto. Siempre he deseado una función de tipo 'select' en numpy. ¡Ahora sé que hay uno! – Paul

2

Esta solución se basa en el código de Pablo. Arreglé DivByZero Bug e implementé RGB a HSL. También hay HSL a 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 
Cuestiones relacionadas