Как преобразить 3d массив цветовых каналов в 2d, где каждая ячейка - это цвет RGB в текущей позиции?

Собственно, вопрос в заголовке. Нужно подготовить массив для последующей сортировки цветов по частоте и последующей дедупликации с подсчетом того, как часто в процентном соотношении они встречаются на изображении. Подскажите подходящие параметры для np.transpose и np.reshape и обратную операцию для преобразования полученного массива к изначальной форме.


Ответы (2 шт):

Автор решения: CrazyElf

Приведите пример в вопросе. А вообще не вижу проблем при использовании Numpy. Вы можете получить сразу весь цвет картинки просто правильно написав срез. Если я правильно понял структуру, то что-то типа:

r, g, b = img[:,:,0], img[:,:,1], img[:,:,2]

Ну или там не RGB, а BGR внутри, если вы PIL или cv2 используете, нужно уточнять на всякий. Но суть не меняется, срезы в Numpy удобные и быстрые, вы по любым размерностям можете написать либо : либо конкретный индекс, и получить таким образом нужную цветовую компоненту при любой топологии данных.

Зачем вам обратное преобразование - вообще непонятно, вы вроде бы только анализ данных хотите делать, судя по описанию. Но вообще срезы работают и на запись, если вы что-то сделаете с массивами цветовых компонент, вы их можете обратно точно так же записать, если сохранилась размерность:

img[:,:,0], img[:,:,1], img[:,:,2] = r, g, b
→ Ссылка
Автор решения: Vitalizzare ушел в монастырь

Пусть для определенности у нас есть такое JPG-изображение:

import PIL, io, requests
img = PIL.Image.open(io.BytesIO(requests.get('https://i.sstatic.net/ykTaz890.jpg').content))

image

Если нужные преобразования можно сделать средствами PIL не заходя в numpy, то как минимум сортировку цветов по частоте их появления можно выполнить через collections.Counter:

from collections import Counter 

data = img.getdata()
total = len(data)
count = Counter(data)
top_5 = (f'#{bytes(k).hex()}: {v/total:.2%}' for k, v in count.most_common(5))
print(*top_5, sep='\n')
#32477c: 6.39%
#2d4277: 5.11%
#304177: 1.00%
#2e4277: 0.61%
#353e75: 0.57%

Если обращение к numpy неизбежно, или изображение передано в виде numpy.ndarray, то мы можем попробовать numpy.view и пользовательский numpy.dtype, чтобы работать с третьим измерением как единой структурой:

import numpy as np

data = np.asarray(img)

# Будем исходить из того, что данные уложены построчно
assert data.flags.c_contiguous

# Опираемся на то, что в RGB содержатся однобайтовые неотрицательные числа
assert data.dtype == np.uint8

# Структура для чтения цвета вдоль последней координаты как единого блока
rgb = np.dtype([('r', np.uint8), ('g', np.uint8), ('b', np.uint8)])

# Если именованный доступ к каналам не принципиален, то можем рассматривать 
# данные о цвете просто как последовательность из трех байт:
# rgb = np.dtype('S3')

# Создаем представление о картинке как о двумерном массиве цветов
# (squeeze убирает третье измерение, свернутое в одно значение)
data_2d = data.view(dtype=rgb).squeeze(-1)

# Сравним размерность и содержимое
print(f'{data.shape    = }',
      f'{data_2d.shape = }',
      f'{data[0, 0]    = }',      
      f'{data_2d[0, 0] = }',
      sep='\n')

# Соберем уникальные цвета и количество их появлений
colours, count = np.unique(data_2d, return_counts=True)
ordered_index = np.argsort(count)
top_5_indices = ordered_index[:-6:-1]     # индексы 5ти наиболее частых цветов
top_5_colours = (f'#{bytes(k).hex()}: {v/data_2d.size:.2%}' 
                 for k, v in zip(colours[top_5_indices], count[top_5_indices]))
print('TOP 5:', *top_5_colours, sep='\n')
data.shape    = (192, 256, 3)
data_2d.shape = (192, 256)
data[0, 0]    = array([84, 69, 92], dtype=uint8)
data_2d[0, 0] = np.void((84, 69, 92), dtype=[('r', 'u1'), ('g', 'u1'), ('b', 'u1')])

TOP 5:
#32477c: 6.39%
#2d4277: 5.11%
#304177: 1.00%
#2e4277: 0.61%
#353e75: 0.57%

Если ограничится только задачей расчета частот, то вышеизложенная логика применима без преобразования типа данных. Но тогда нам придется убрать размерность изображения, оставив только координаты цвета, чтобы иметь возможность применить параметр axis в функции numpy.unique:

colours, count = np.unique(data.reshape(-1, 3), return_counts=True, axis=0)
→ Ссылка