Como se describe en el título, quiero hacer una conversión muy específica de RGB a escala de grises.

Tengo un montón de imágenes que podrían verse así:

enter image description here

Y quiero convertirlos en una imagen como esta ingrese la descripción de la imagen aquí.

Ahora quizás se pregunte por qué no estoy usando las funciones incorporadas de opencv. La razón es que necesito asignar cada color de la imagen RGB a un valor de intensidad específico en escala de grises, lo que no es demasiado difícil ya que solo tengo seis colores.

Red, rgb(255,0,0)           ->  25
Brown, rgb(165,42,42)       ->  120
Light Blue, rgb(0,255,255)  ->  127
Green, rgb(127,255,0)       ->  50
Yellow, rgb(255,255,255)    ->  159
Purple, rgb(128, 0, 128)    ->  90

Ahora ya he creado una matriz con algunos objetos que contienen estas asignaciones y simplemente estoy iterando sobre mis imágenes para asignar los nuevos códigos de color. Sin embargo, esto es muy lento y espero que me crezca una barba magnífica antes de que se termine para todas las imágenes (también quiero saber esto con fines de aprendizaje). Este es mi código de ejecución súper lenta y el objeto de mapeo hasta ahora:

colorMapping = [ColorMapping(RGB=[255, 0, 0], Grayscale=25),
 ColorMapping(RGB=[165, 42, 42], Grayscale=120), ... ]

def RGBtoGray(RGBimg, colorMapping):
    RGBimg = cv2.cvtColor(RGBimg, cv2.COLOR_BGR2RGB)
    row = RGBimg.shape[0]
    col = RGBimg.shape[1]
    GRAYimg = np.zeros((row, col))
    for x in range(0,row):
        for y in range(0,col):
            pixel = RGBimg[x,y,:]
            for cm in colorMapping:
                if np.array_equal(pixel, np.array(cm.RGB)):
                    GRAYimg[x,y] = cm.Grayscale    
    return GRAYimg   

Me complace cualquier sugerencia para usar bibliotecas integradas o mejorar este cálculo de códigos. El mapa de color se lee desde un archivo json, que funciona como un paso de automatización ya que tengo que hacer esto al menos para dos lotes de imágenes con codificaciones diferentes.

1
madave 4 may. 2020 a las 18:37

2 respuestas

Aquí hay una vectorizada basada en 1D transformación + np.searchsorted, inspirada en esta publicación -

def map_colors(img, colors, vals, invalid_val=0):
    s = 256**np.arange(3)
    img1D = img.reshape(-1,img.shape[2]).dot(s)
    colors1D = colors.reshape(-1,img.shape[2]).dot(s)
    sidx = colors1D.argsort()
    idx0 = np.searchsorted(colors1D, img1D, sorter=sidx)
    idx0[idx0==len(sidx)] = 0
    mapped_idx = sidx[idx0]
    valid = colors1D[mapped_idx] == img1D
    return np.where(valid, vals[mapped_idx], invalid_val).reshape(img.shape[:2])

Ejecución de muestra -

# Mapping colors array
In [197]: colors
Out[197]: 
array([[255,   0,   0],
       [165,  42,  42],
       [  0, 255, 255],
       [127, 255,   0],
       [255, 255, 255],
       [128,   0, 128]])

# Mapping values array
In [198]: vals
Out[198]: array([ 25, 120, 127,  50, 155,  90])

# Input 3D image array
In [199]: img
Out[199]: 
array([[[255, 255, 255],
        [128,   0, 128],
        [255,   0,   0],
        [127, 255,   0]],

       [[127, 255,   0],
        [127, 255,   0],
        [165,  42,  42],
        [  0,   0,   0]]]) # <= one color absent in mappings

# Output
In [200]: map_colors(img, colors, vals, invalid_val=0)
Out[200]: 
array([[155,  90,  25,  50],
       [ 50,  50, 120,   0]])

Podríamos ordenar previamente las asignaciones y, por lo tanto, deshacernos de la clasificación necesaria en torno a la búsqueda ordenada y esto debería aumentar aún más el rendimiento:

def map_colors_with_sorting(img, colors, vals, invalid_val=0):
    s = 256**np.arange(3)
    img1D = img.reshape(-1,img.shape[2]).dot(s)
    colors1D = colors.reshape(-1,img.shape[2]).dot(s)
    sidx = colors1D.argsort()
    colors1D_sorted = colors1D[sidx]
    vals_sorted = vals[sidx]

    idx0 = np.searchsorted(colors1D_sorted, img1D)
    idx0[idx0==len(sidx)] = 0
    valid = colors1D_sorted[idx0] == img1D
    return np.where(valid, vals_sorted[idx0], invalid_val).reshape(img.shape[:2])

Tiempos en la matriz de imagen 2000 x 2000 -

In [230]: np.random.seed(0)
     ...: idx = np.random.randint(0,len(colors), size=(2000,2000))
     ...: img = colors[idx]
     ...: img[-1,-1] = [0,0,0]

In [231]: %timeit map_colors(img, colors, vals, invalid_val=0)
148 ms ± 399 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [232]: %timeit map_colors_with_sorting(img, colors, vals, invalid_val=0)
127 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
0
Divakar 4 may. 2020 a las 16:29

Esto es probablemente tan fácil como cualquier otra cosa. Realiza 6 pases sobre su imagen, por lo que algunas personas inteligentes de Numpy pueden conocer una mejor manera, pero será mucho más rápido que sus bucles.

#!/usr/bin/env python3

import numpy as np
import cv2

# Load image
im = cv2.imread('blobs.png')

# Make output image
res = np.zeros_like(im[:,:,0])

res[np.all(im == (0, 0, 255),   axis=-1)] = 25
res[np.all(im == (42,42,165),   axis=-1)] = 120
res[np.all(im == (255,255,0),   axis=-1)] = 127
res[np.all(im == (0,255,127),   axis=-1)] = 50
res[np.all(im == (255,255,255), axis=-1)] = 159
res[np.all(im == (128,0,128),   axis=-1)] = 90

# Write image of just palette indices
cv2.imwrite('indices.png', res)

Puede hacer que se ejecute en 5 ms frente a 30 ms convirtiendo cada triplete RGB en un solo número de 24 bits inspirado en esta respuesta .

#!/usr/bin/env python3

import numpy as np
import cv2
def remap2(im):
    # Make output image
    res = np.zeros_like(im[:,:,0])

    # Make a single 24-bit number for each pixel
    r = np.dot(im.astype(np.uint32),[1,256,65536]) 

    c0 =   0 +   0*256 + 255*65536
    c1 =  42 +  42*256 + 165*65536
    c2 = 255 + 255*256 +   0*65536
    c3 =   0 + 255*256 + 127*65536
    c4 = 255 + 255*256 + 255*65536
    c5 = 128 +   0*256 + 128*65536

    res[r == c0] = 25
    res[r == c1] = 120
    res[r == c2] = 127
    res[r == c3] = 50
    res[r == c4] = 159
    res[r == c5] = 90
    return res

# Load image
im = cv2.imread('blobs.png')
res = remap2(im)
cv2.imwrite('result.png',res)
1
Mark Setchell 4 may. 2020 a las 16:45