Создайте генератор цветовой палитры, такой как Adobe Color, с помощью алгоритма машинного обучения Python и KNN.
Некоторые данные
Привет! Это мабаталла. Если мы встречались раньше, вы уже знаете, что я специалист по данным с опытом работы в области архитектуры и строительства. Если нет, и вы хотите узнать обо мне больше, вы можете посетить мою веб-страницу, когда закончите читать эту статью.
Еще в университете История искусств была одним из моих любимых предметов. Я также интересуюсь уличным искусством, промышленным дизайном или модой. С этой отправной точки я хотел изучить возможности работы с изображениями и машинного обучения, поэтому я начал личный проект, посвященный картинам, чтобы глубже понять различные алгоритмы машинного обучения. Отправной точкой этого проекта является интуиция о том, как отличить картины, представленные в музее, от художников, которые их нарисовали.
Сегодня я покажу вам, как извлечь наиболее релевантные цвета изображения с помощью Python и алгоритма KNN.
К концу этой статьи вы получите что-то вроде этого:

От натуральных пигментов до цветового режима RGB
Традиционно художники использовали для своих работ натуральные пигменты. Древние известные пигменты: лимонит, гематит, уголь, азурит, малахит… но древние пигменты имели не только минеральное происхождение. Другие пигменты получали от насекомых, таких как кошениль, прогорклое оливковое масло или даже коровий навоз.
Некоторые миксы были ревниво сохранены и представляли художников, таких как International Blue Klein (Ив Кляйн), или такие регионы, как желтый Шенбрунн (Austro -Венгерская империя).
Зная это, я подумал, что, возможно, можно отличить одного художника от другого по цвету его картин, поэтому я решил закодировать генератор цветовой палитры.
Цвета на экране создаются пикселями. Один пиксель — это маленькая точка, которая имеет три канала: R (красный), G (зеленый), B (голубой). Комбинация различных интенсивностей в каждом канале создает целую палитру. Текущий стандарт (24-битный цветовой режим RGB) устанавливает 256 значений для каждого канала (от 0 до 255), что дает 16 777 216 цветов. Это 256x256x256 = 2²⁴ = 16,777,216.
Если вам интересно, человеческий глаз должен различать до десяти миллионов цветов.

Чтобы иметь больше шансов на успех и найти образцы цветов, использованные одним художником, я установлю цветовую палитру каждой картины на сокращенную версию.
Приступаем к кодированию!
Полные скрипты, записные книжки и примеры изображений можно найти в этом репозитории. Не паникуйте, если возникнет какое-либо исключение, я пропустил несколько шагов, чтобы сократить эту статью. Если у вас есть какие-либо сомнения, вы можете связаться со мной через моя веб-страница.
Это необходимые библиотеки и настройки
import cv2
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import plotly.graph_objs as go
from plotly.offline import iplot
# Modules settings
%matplotlib inline
sns.set_theme(style='whitegrid',
rc={'figure.figsize': (20, 10),
'axes.grid': False})
Прежде всего нам нужно импортировать изображения в режиме RGB и посмотреть:
img = cv2.imread('../data/raw_images/sample_img/sample_img_05.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
>>>

Вы можете думать о каждом пикселе как о точке в трехмерном пространстве, где его координаты x, y, z являются значениями канала RGB. На самом деле изображение представляет собой массив значений RGB.
img
>>>
array([[[191, 191, 191],
[191, 191, 191],
[191, 191, 191],
...,
[191, 191, 255],
[211, 253, 255],
[213, 213, 255]],
[[191, 191, 191],
[191, 191, 191],
[191, 191, 191],
...,
[191, 231, 255],
[191, 213, 255],
[191, 255, 255]],
[[191, 191, 191],
[191, 191, 191],
[191, 191, 191],
...,
[191, 191, 255],
[211, 253, 255],
[213, 255, 255]],
...,
[[233, 169, 127],
[147, 85, 83],
[151, 127, 87],
...,
[191, 127, 191],
[191, 127, 171],
[191, 127, 127]],
[[191, 191, 127],
[191, 191, 127],
[191, 191, 127],
...,
[191, 127, 127],
[191, 127, 127],
[191, 127, 127]],
[[191, 191, 127],
[191, 191, 127],
[191, 191, 151],
...,
[191, 127, 127],
[191, 127, 127],
[191, 127, 127]]], dtype=uint8)
Если вы посмотрите на форму этого объекта, вы получите триплет, обозначающий высоту (в пикселях), ширину (в пикселях) и количество каналов. Другие форматы изображений, такие как .PNG, имеют 4 канала, поскольку они используют цветовой режим RGBA (lpha).
img.shape >>> (394, 600, 3)
Чтобы иметь более четкое представление о цветах, присутствующих на изображении, вы можете построить Scatter3d.
img_flat = img.flatten()
scatter_3d = go.Scatter3d(x=img_flat[:-2:3],
y=img_flat[1:-1:3],
z=img_flat[2::3],
mode='markers',
marker=dict(size=1.5,
color='rgb(93,145,153)'))
layout = go.Layout(margin=dict(t=10, r=10, b=10, l=10,
pad=50),
scene=dict(xaxis_title='Red',
yaxis_title='Green',
zaxis_title='Blue'),
autosize=False,
width=800,
height=800)
fig = go.Figure(data=scatter_3d, layout=layout)
iplot(fig)
>>>

Уф... Это как-то запутанно... Если вы помните несколько строк, я говорил вам, что мы будем превращать цветовую палитру каждой картины в уменьшенную версию, и вот почему.
Несмотря на то, что International Klein Blue имеет конкретный триплет RGB (0, 47, 167) и цифровые художники всегда могут использовать один и тот же цвет, это невозможно для традиционных художников. Поскольку я делаю вид, что работаю с такими артистами, как Гойя, Караваджо или Моне, небольшие различия в микшировании или даже в процессе оцифровки могут испортить результат.
Мы говорили, что 24-битный цветовой режим RGB поддерживает 256 значений для каждого канала (от 0 до 255). Давайте создадим функцию, которая делит этот диапазон на заданное число (допустимые значения), вычисляет расстояние каждого значения канала до этих значений и устанавливает значение канала равным ближайшему к нему допустимому значению.
def map_channel(channel_value, max_values):
"""
Map an RGB channel value (0 to 255) to a limited options.
The number of bins sets the number of possible values that the channel can
get.
Parameters
----------
channel_value : int
Value of one channel
max_values : int
Number of posible values
Returns
-------
mapped_value : int
New value for the channel
Examples
--------
>>> R_px = 200
... map_channel(R_px, 3)
255
>>> G_px = 140
... map_channel(G_px, 3)
127
>>> B_px = 150
... map_channel(B_px, 3)
127
"""
step = (255/(max_values - 1))
values = list(np.fix(np.arange(0, 256, step)))
distances = [abs(channel_value - i) for i in values]
mapped_value = values[distances.index(min(distances))]
return mapped_value
Если вы примените эту функцию к изображению, оно получится из этого:

К этому:

И теперь его 3D-диаграмма рассеяния выглядит так:

На этом этапе мы можем применить алгоритм KNN для кластеризации этих цветов. Алгоритм KNN работает примерно так же, как наша функция map_channel:
- Выбирает заданное количество кластеров (цветов) случайным образом.
- Измеряет расстояния каждого цвета пикселя до кластеров и присваивает цвет ближайшему кластеру.
- Повторяет процесс с расстояниями до среднего значения каждого кластера до тех пор, пока в связи с кластерами не будут внесены изменения.
- Теперь измерьте вариацию внутри кластеров и запустите снова.
- После ряда итераций алгоритм возвращает кластеризацию с небольшими вариациями внутри кластеров.
В результате KNN вернет кластеры с более высокой вариацией между кластерами и более низкой вариацией внутри каждого кластера.
def color_clustering(image, color_mode='HEX', max_values=5, num_of_colors=10, show_chart=True):
"""
Extract a number of colors from an image.
This function applies a color quantization based on cv2.kmeans function
as described in the OpenCV docs to reduce the colors present on an image.
Parameters
----------
image : numpy.ndarray
Image to extract color from
color_mode : str
Color mode to return colors (RGB, HEX)
max_values : int
Number of possible values for each RGB channel
num_of_colors : int
Number of clusters
show_chart : bool
Whether to show a chart with found colors
Returns
-------
colors : list
List of colors in specified color mode
"""
# Collapse image into one dimension (KMeans requirement)
img = image.reshape(image.shape[0]*image.shape[1], 3)
# Use KMeans to generate num_of_colors number of clusters
model_kmeans = KMeans(n_clusters=num_of_colors)
labels = model_kmeans.fit_predict(img) # This returns a number of cluster for each pixel
color_clusters = model_kmeans.cluster_centers_ # This are the RGB values of the centroids
# Transform color clusters to a discrete variable and its type to list
color_clusters = np.array(color_clusters) # Needed for reduce_col_palette
color_clusters = reduce_col_palette(np.array(color_clusters), max_values=max_values)
color_clusters = color_clusters.tolist()
# Count and sort the pixels in each cluster to order colors by most common
color_counts = Counter(labels) # Get color as keys and counts as values
color_counts = color_counts.most_common() # Order color by counts
# Get RGB and HEX indexes
ordered_RGB_colors = [color_clusters[i[0]] for i in color_counts]
ordered_HEX_colors = [rgb_to_hex(i).upper() for i in ordered_RGB_colors]
if show_chart:
plot_colors(ordered_HEX_colors)
plt.show()
if color_mode == 'RGB':
colors = ordered_RGB_colors
else:
colors = ordered_HEX_colors
return color_clusters
Вам потребуются еще две функции, чтобы все заработало, но я опустил их, чтобы перейти к сути. Вы можете найти их в репозитории.
Если вы примените KNN к изображению, вы получите наиболее распространенные цвета:
colors = color_clustering(img, color_mode='RGB', num_of_colors=5, show_chart=True) >>>

colors >>> [[127.0, 63.0, 63.0], [191.0, 191.0, 127.0], [127.0, 127.0, 127.0], [191.0, 191.0, 191.0], [191.0, 127.0, 127.0]]

Последний шаг — применить эти преобразования к большому набору изображений (наш музей) и сохранить извлеченные данные в файл .csv.
collection = pd.read_csv(‘./data/processed_img/caravaggio.csv’) collection.head() >>>

Имея большое количество преобразованных изображений и найденную большую коллекцию цветов, мы попытаемся предсказать художника по наиболее распространенным присутствующим цветам. В следующих статьях я попробую использовать различные алгоритмы, чтобы найти лучший из них, а позже я буду использовать преобразованные изображения для обучения нейронной сети.
Оставайтесь с нами!
Вы действительно прочитали всю информацию выше?
Спасибо за проявленный интерес, я рад, что вы здесь.
Не могли бы вы дать мне свой отзыв? Положительные отзывы побуждают меня продолжать, а отрицательные подсказывают, куда приложить больше усилий.
Вы нашли это интересным? Не стесняйтесь нажимать репо.
Как вам кажется, нам стоит встретиться? Свяжитесь с нами через моя веб-страница.
Я с нетерпением жду встречи с вами!