Создайте генератор цветовой палитры, такой как 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:

  1. Выбирает заданное количество кластеров (цветов) случайным образом.
  2. Измеряет расстояния каждого цвета пикселя до кластеров и присваивает цвет ближайшему кластеру.
  3. Повторяет процесс с расстояниями до среднего значения каждого кластера до тех пор, пока в связи с кластерами не будут внесены изменения.
  4. Теперь измерьте вариацию внутри кластеров и запустите снова.
  5. После ряда итераций алгоритм возвращает кластеризацию с небольшими вариациями внутри кластеров.

В результате 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()
>>>

Имея большое количество преобразованных изображений и найденную большую коллекцию цветов, мы попытаемся предсказать художника по наиболее распространенным присутствующим цветам. В следующих статьях я попробую использовать различные алгоритмы, чтобы найти лучший из них, а позже я буду использовать преобразованные изображения для обучения нейронной сети.

Оставайтесь с нами!

Вы действительно прочитали всю информацию выше?
Спасибо за проявленный интерес, я рад, что вы здесь.

Не могли бы вы дать мне свой отзыв? Положительные отзывы побуждают меня продолжать, а отрицательные подсказывают, куда приложить больше усилий.

Вы нашли это интересным? Не стесняйтесь нажимать репо.
Как вам кажется, нам стоит встретиться? Свяжитесь с нами через моя веб-страница.

Я с нетерпением жду встречи с вами!