Не могу обратиться к столбцу массива по индексу enumerate
colourArray = np.asarray(mask_out)
colours, count = np.unique(colourArray.reshape(-1, 3), return_counts=True, axis=0)
count_colours = np.column_stack((count, colours))
for index, count in enumerate(count_colours):
count_colours[index, 0] = count/(np.sum(count_colours, axis = 0)/100)
print(count_colours)
Нужно переопределить значение первой ячейки каждого столбца (перевести в проценты). Использую enumerate
чтобы иметь доступ и к индексу, и к значению. Но при попытке обратится к элементу по переменной индекса вылезает ошибка "обращение к элементу по последовательности вместо номера". Как исправить?
P.S. Не уверен, что обратился к столбцу, а не строке, из-за ошибки не могу проверить результат.
P.P.S. Здесь colours
- это оригинальные цвета, выделенные из изображения mask_out
; count
- это количество вхождений каждого цвета, которые нужно перевести в проценты.
Ответы (1 шт):
Полагаю, это продолжение задачи о работе с изображением, где у вас есть трехмерный массив, первые две размерности которого - это координаты пикселей, а третья - это цветовые каналы.
В цикле for ...
вы хотите перевести количество повторений в процентное выражение. Структурная ошибка состоит в неправильном понимании:
- строк вашей таблицы
count_colours
- это не скаляр, а вектор, описывающий уникальный цвет как количество его повторений, за которым следуют значения в цветовых каналах; - суммы
np.sum(count_colours, axis = 0)
- это не скаляр, а вектор сумм по всем столбцам таблицыcount_colours
.
Поэтому первым исправлением будет добавление нулевой координаты [0]
к числителю и знаменателю в формуле внутри цикла, чтобы работать с количествами повторений, а не всей строкой:
count[0]/(np.sum(count_colours, axis = 0)[0]/100)
Но таблица count_colours
содержит целочисленный тип данных. Вещественное число, полученное при делении, не кастуется в целое. Поэтому вторым исправлением будет смена типа данных таблицы count_colours
:
count_colours = np.column_stack((count, colours)).astype(float)
С этими изменениями код будет работать, но результат будет ошибочным. На каждой итерации вы меняете содержимое столбца количества повторений, а значит будет меняться сумма np.sum(count_colours, 0)[0]
. Поэтому третьим исправлением будет вычисление этой суммы либо перед циклом, либо по другому, например как произведение высоты на ширину картинки:
count_colours[index, 0] = count[0] / (colourArray[0] * colourArray[1] / 100)
Итого, исправленный код, который возвращает частоту встречаемых цветов, выглядит так:
colourArray = np.asarray(mask_out)
# Вычленяем высоту, ширину и количество каналов картинки
height, width, n_channels = colourArray.shape
# Обобщим эту строку, заменив 3 на n_channels
colours, count = np.unique(colourArray.reshape(-1, n_channels),
return_counts=True, axis=0)
# Работаем в поле вещественных чисел, поэтому astype(float)
count_colours = np.column_stack((count, colours)).astype(float)
for index, count in enumerate(count_colours):
# Сумма всех повторений цветов = количество пикселей картинки
count_colours[index, 0] = count[0] / (height * width / 100)
P.S. Код можно улучшить, заменив цикл функционалом numpy
и, как вариант, более детально определив тип записей для count_colours
:
import numpy as np
from PIL import Image
from io import BytesIO
from requests import get
# Возьмем картинку для примера
mask_out = Image.open(BytesIO(get('https://i.sstatic.net/ykTaz890.jpg').content))
colourArray = np.asarray(mask_out)
height, width, n_channels = colourArray.shape
colours, counts = np.unique(colourArray.reshape(-1, n_channels),
return_counts=True, axis=0)
# Определим типы данных, описывающие цветовые каналы и записи об уникальных цветах
colour_channels = np.dtype([(f'channel{i}', np.uint8) for i in range(1, 1 + n_channels)])
colour_record = np.dtype([('count', np.float64), ('colour', colour_channels)])
# Создадим и заполним таблицу уникальных цветов
count_colours = np.ndarray(counts.shape, dtype=colour_record)
count_colours['count'] = counts # или сразу записать 100*counts/counts.sum()
count_colours['colour'] = colours.view(dtype=colour_channels).squeeze()
# Переведем количество повторений в частоту, % (вместо цикла for)
count_colours['count'] /= count_colours['count'].sum()/100 # или заменить count_colours['count'].sum() на height*width
# Отсортируем цвета по убыванию частоты
count_colours = count_colours[np.argsort(-counts)]
# Показать 5 наиболее частых цветов
print('TOP 5', '\N{BOX DRAWINGS LIGHT HORIZONTAL}'*14, sep='\n')
for count, color in count_colours[:5]:
print(f'#{bytes(color).hex()}: {count:.2f}%')
Результат на тестовом изображении (сравните с предыдущим):
TOP 5
──────────────
#32477c: 6.39%
#2d4277: 5.11%
#304177: 1.00%
#2e4277: 0.61%
#353e75: 0.57%