Преобразование изображения RGB в индексное изображение

Я хочу преобразовать 3-канальное изображение RGB в индексное изображение с помощью Python. Он используется для обработки меток обучения глубокой сети для семантической сегментации. Под индексным изображением я подразумеваю, что у него один канал, и каждый пиксель является индексом, который должен начинаться с нуля. И, конечно, они должны иметь одинаковый размер. Преобразование основано на следующем отображении в Python dict:

color2index = {
        (255, 255, 255) : 0,
        (0,     0, 255) : 1,
        (0,   255, 255) : 2,
        (0,   255,   0) : 3,
        (255, 255,   0) : 4,
        (255,   0,   0) : 5
    }

Я реализовал наивную функцию:

def im2index(im):
    """
    turn a 3 channel RGB image to 1 channel index image
    """
    assert len(im.shape) == 3
    height, width, ch = im.shape
    assert ch == 3
    m_lable = np.zeros((height, width, 1), dtype=np.uint8)
    for w in range(width):
        for h in range(height):
            b, g, r = im[h, w, :]
            m_lable[h, w, :] = color2index[(r, g, b)]
    return m_lable

Вход im массив NumPy, созданный cv2.imread(), Однако этот код очень медленный. Так как im находится в массиве NumPy, я сначала попробовал ufunc из NumPy с чем-то вроде этого:

RGB2index = np.frompyfunc(lambda x: color2index(tuple(x)))
indices = RGB2index(im)

Но оказывается, что ufunc занимает только один элемент каждый раз. Мне удалось дать функции три аргумента (значение RGB) один раз.

Так есть ли другие способы сделать оптимизацию? Отображение не должно быть таким, если существует более эффективная структура данных. Я заметил, что доступ к Python dict не требует больших затрат времени, но приведение из массива к кортежу(который можно хэшировать) делает.

PS: одна идея, которую я получил, - реализовать ядро ​​в CUDA. Но это было бы сложнее.

UPDATA1: Ответ Дана Машека работает отлично. Но сначала мы должны преобразовать изображение RGB в оттенки серого. Это может быть проблематично, когда два цвета имеют одинаковое значение в градациях серого.

Я вставляю рабочий код здесь. Надеюсь, что это может помочь другим.

lut = np.ones(256, dtype=np.uint8) * 255
lut[[255,29,179,150,226,76]] = np.arange(6, dtype=np.uint8)
im_out = cv2.LUT(cv2.cvtColor(im, cv2.COLOR_BGR2GRAY), lut)

7 ответов

Как насчет этого?

color2index = {
    (255, 255, 255) : 0,
    (0,     0, 255) : 1,
    (0,   255, 255) : 2,
    (0,   255,   0) : 3,
    (255, 255,   0) : 4,
    (255,   0,   0) : 5
}

def rgb2mask(img):

    assert len(img.shape) == 3
    height, width, ch = img.shape
    assert ch == 3

    W = np.power(256, [[0],[1],[2]])

    img_id = img.dot(W).squeeze(-1) 
    values = np.unique(img_id)

    mask = np.zeros(img_id.shape)

    for i, c in enumerate(values):
        try:
            mask[img_id==c] = color2index[tuple(img[img_id==c][0])] 
        except:
            pass
    return mask

Тогда просто позвоните:

mask = rgb2mask(ing)

Подобно тому, что предлагали Армали и Мендрика, мне как-то пришлось немного подправить его, чтобы заставить его работать (возможно, полностью моя вина). Поэтому я просто хотел поделиться фрагментом, который работает.

COLORS = np.array([
    [0, 0, 0],
    [0, 0, 255],
    [255, 0, 0]
])
W = np.power(255, [0, 1, 2])

HASHES = np.sum(W * COLORS, axis=-1)
HASH2COLOR = {h : c for h, c in zip(HASHES, COLORS)}
HASH2IDX = {h: i for i, h in enumerate(HASHES)}


def rgb2index(segmentation_rgb):
    """
    turn a 3 channel RGB color to 1 channel index color
    """
    s_shape = segmentation_rgb.shape
    s_hashes = np.sum(W * segmentation_rgb, axis=-1)
    func = lambda x: HASH2IDX[int(x)]
    segmentation_idx = np.apply_along_axis(func, 0, s_hashes.reshape((1, -1)))
    segmentation_idx = segmentation_idx.reshape(s_shape[:2])
    return segmentation_idx

segmentation = np.array([[0, 0, 0], [0, 0, 255], [255, 0, 0]] * 3).reshape((3, 3, 3))
rgb2index(segmentation)

Пример сюжета

Код также доступен здесь: https://github.com/theRealSuperMario/supermariopy/blob/dev/scripts/rgb2labels.py

На самом деле цикл for занимает много времени.

binary_mask = (im_array[:,:,0] == 255) & (im_array[:,:,1] == 255) & (im_array[:,:,2] == 0) 

может быть, приведенный выше код может помочь вам

Я реализовал наивную функцию: … Сначала попробовал ufuncof numpy на что-то вроде этого: …

Я предлагаю использовать еще более простую функцию, которая преобразует всего один пиксель:

def rgb2index(rgb):
    """
    turn a 3 channel RGB color to 1 channel index color
    """
    return color2index[tuple(rgb)]

Тогда использование подпрограммы numpy - хорошая идея, но нам не нужнаufunc:

np.apply_along_axis(rgb2index, 2, im)

Вот numpy.apply_along_axis() используется для применения наших rgb2index() функция для срезов RGB по последней из трех осей (0, 1, 2) для всего изображения im.

Можно было бы даже обойтись без функции и просто написать:

np.apply_along_axis(lambda rgb: color2index[tuple(rgb)], 2, im)

Вот небольшая служебная функция для преобразования изображений (np.array) в метки (индексы) для каждого пикселя, которые также могут быть в горячем кодировании:

def rgb2label(img, color_codes = None, one_hot_encode=False):
    if color_codes is None:
        color_codes = {val:i for i,val in enumerate(set( tuple(v) for m2d in img for v in m2d ))}
    n_labels = len(color_codes)
    result = np.ndarray(shape=img.shape[:2], dtype=int)
    result[:,:] = -1
    for rgb, idx in color_codes.items():
        result[(img==rgb).all(2)] = idx

    if one_hot_encode:
        one_hot_labels = np.zeros((img.shape[0],img.shape[1],n_labels))
        # one-hot encoding
        for c in range(n_labels):
            one_hot_labels[: , : , c ] = (result == c ).astype(int)
        result = one_hot_labels

    return result, color_codes


img = cv2.imread("input_rgb_for_labels.png")
img_labels, color_codes = rgb2label(img)
print(color_codes) # e.g. to see what the codebook is

img1 = cv2.imread("another_rgb_for_labels.png")
img1_labels, _ = rgb2label(img1, color_codes) # use the same codebook

Он вычисляет (и возвращает) кодовую книгу цветов, если None в комплект поставки.

Если вы довольны использованием MATLAB, возможно, сохраните результат как *.matи загрузка с scipy.io.loadmat- Здесь rgb2indфункция в MATLAB, которая делает именно то, что вы просите. Если нет, то его можно использовать в качестве вдохновения для аналогичной реализации на Python.

Вы проверяли библиотеку подушек https://python-pillow.org/? Насколько я помню, в нем есть несколько классов и методов для преобразования цветов. См.: https://pillow.readthedocs.io/en/4.0.x/reference/Image.html

Другие вопросы по тегам