Векторизация обычного RGB -> Преобразование градаций серого - PullRequest
1 голос
/ 04 мая 2020

Как описано в заголовке, я хочу сделать очень точное c преобразование из RGB в оттенки серого.

У меня есть несколько изображений, которые могут выглядеть следующим образом:

enter image description here

и я хочу преобразовать их в изображение, подобное этому enter image description here.

Теперь вы можете задаться вопросом, почему я не просто использую Встроенные функции opencv. Причина в том, что мне нужно сопоставить каждый цвет изображения RGB с заданным значением c интенсивности в оттенках серого, что не так уж сложно, так как у меня только шесть цветов.

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

Теперь у меня есть Я уже создал массив с некоторыми объектами, которые содержат эти отображения, и я просто перебираю свои изображения, чтобы назначить новые цветовые коды. Однако это очень медленно, и я ожидаю отрастить великолепную бороду, прежде чем это будет закончено для всех изображений (также я хочу знать это для учебной цели). Это мой очень медленно работающий код и объект отображения на данный момент:

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   

Я рад любым предложениям по использованию встроенных библиотек или улучшению вычисления этих кодов. Цветовая карта считывается из файла json, который выполняет функцию автоматизации, поскольку я должен сделать это как минимум для двух пакетов изображений с разными кодировками.

Ответы [ 2 ]

4 голосов
/ 04 мая 2020

Метод # 1

Вот один векторизация, основанная на 1D преобразовании + np.searchsorted, вдохновленная этим постом -

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

Пример выполнения -

# 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]])

Мы можем предварительно отсортировать сопоставления и, следовательно, избавиться от сортировки, необходимой для поиска, и это должно еще больше повысить производительность -

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

Метод # 2

Мы можем использовать массив сопоставления, который при индексации с помощью 1D преобразованных цветов приведет нас непосредственно к конечному изображению в градациях серого, как показано ниже -

def map_colors_with_mappingar_solution(img):
    # Edit the custom colors and values here
    colors = np.array([
        [  0,   0, 255],
        [ 42,  42, 165],
        [255, 255,   0],
        [  0, 255, 127],
        [255, 255, 255],
        [128,   0, 128]], dtype=np.uint8) # BGR format
    vals = np.array([25, 120, 127, 50, 155, 90], dtype=np.uint8)      
    return map_colors_with_mappingar(img, colors, vals, 0)            

def map_colors_with_mappingar(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)

    N = colors1D.max()+1
    mapar = np.empty(N, dtype=np.uint8)
    mapar[colors1D] = vals

    mask = np.zeros(N, dtype=bool)
    mask[colors1D] = True

    valid = img1D < N
    valid &= mask[img1D]

    out = np.full(len(img1D), invalid_val, dtype=np.uint8)
    out[valid] = mapar[img1D[valid]]
    return out.reshape(img.shape[:2])

Это должно масштабироваться по мере увеличения количество пользовательских цветов.

Давайте определим время для данного образца изображения -

# Read in sample image
In [360]: im = cv2.imread('blobs.png')

# @Mark Setchell's solution
In [362]: %timeit remap2(im)
7.45 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# Method2 from this post
In [363]: %timeit map_colors_with_mappingar_solution(im)
6.76 ms ± 46.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Далее перф. boost

Пройдя еще один шаг, мы могли бы сделать сокращение 1D более производительным способом и, следовательно, добиться дальнейшего совершенства. буст, вот так -

# https://stackoverflow.com/a/57236217/ @tstanisl
def scalarize(x):
    # compute x[...,2]*65536+x[...,1]*256+x[...,0] in efficient way
    y = x[...,2].astype('u4')
    y <<= 8
    y +=x[...,1]
    y <<= 8
    y += x[...,0]
    return y

def map_colors_with_mappingar(img, colors, vals, invalid_val=0):    
    img1D = scalarize(img)
    colors1D = scalarize(colors)

    N = colors1D.max()+1
    mapar = np.empty(N, dtype=np.uint8)
    mapar[colors1D] = vals

    mask = np.zeros(N, dtype=bool)
    mask[colors1D] = True

    valid = img1D < N
    valid &= mask[img1D]

    out = np.full(img1D.shape, invalid_val, dtype=np.uint8)
    out[valid] = mapar[img1D[valid]]
    return out

# On given sample image
In [10]: %timeit map_colors_with_mappingar_solution(im)
5.45 ms ± 143 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2 голосов
/ 04 мая 2020

Это, вероятно, так же просто, как и все остальное. Он делает 6 проходов над вашим изображением, так что некоторые умные Numpy люди могут знать лучший способ, но это будет намного быстрее, чем ваши петли.

#!/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)

Вы можете заставить его работать в 5 мс против 30 мс путем преобразования каждого RGB-триплета в одно 24-битное число, вдохновленное этим ответом.

#!/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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...