Обрезка, оттенки серого, каналы RGB, пороги интенсивности, обнаружение краев и цветовые фильтры

При разработке признаков мы сразу же думаем о табличных данных. Тем не менее, мы также можем получить функции для данных изображения. Цель состоит в том, чтобы извлечь наиболее важные аспекты изображения. Это облегчит поиск сопоставления между нашими данными и целевой переменной.

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

Мы рассмотрим некоторые методы разработки признаков изображения с использованием Python:

  • Обрезка
  • Оттенки серого
  • Выбор каналов RGB
  • Пороги интенсивности
  • Обнаружение края
  • Цветовые фильтры (т.е. извлечение пикселей в заданном цветовом диапазоне)

Чтобы было интересно, мы будем делать это для автоматизированного автомобиля. Как показано ниже, мы хотим обучить модель, используя изображения трека. Затем модель будет использоваться для прогнозирования движения автомобиля. В заключение мы обсудим ограничения разработки признаков на основе данных изображения.

Разработка функций против расширения

Прежде чем мы углубимся в это, стоит обсудить увеличение изображения. Этот метод имеет те же цели, что и проектирование признаков. Тем не менее, он достигает их по-разному.

Что такое увеличение данных?

Увеличение данных — это когда мы систематически или случайным образом изменяем данные с помощью кода. Для изображений это включает в себя такие методы, как переворачивание, настройка цвета и добавление случайного шума. Эти методы позволяют нам искусственно вводить шум и увеличивать размер нашего набора данных. Если вам нужна дополнительная информация об увеличении изображения, я предлагаю эту статью:



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

Цель расширения данных — создать модель, устойчивую к изменениям в этих условиях. Он делает это, добавляя шум, который имитирует условия реального мира. Например, изменение яркости изображений аналогично сбору данных в разное время суток.

Увеличивая размер нашего набора данных, аугментация также позволяет нам обучать более сложные архитектуры. Другими словами, это помогает параметрам модели сходиться.

Разработка признаков с данными изображений

Цели разработки признаков аналогичны. Мы хотим создать более надежную модель. За исключением того, что сейчас мы удаляем любой шум, который не нужен для точных прогнозов. Другими словами, мы удаляем переменные, которые будут меняться при разных условиях.

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

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

Разработка функций изображения с помощью Python

Хорошо, имея все это в виду, давайте перейдем к разработке функций. Мы пройдемся по коду, а также вы можете найти проект на GitHub.

Для начала мы будем использовать импорт ниже. У нас есть несколько стандартных пакетов (строки 2–3). Glob используется для обработки путей к файлам (строка 5). Также у нас есть несколько пакетов для работы с изображениями (строки 7–8).

#Imports 
import numpy as np
import matplotlib.pyplot as plt

import glob

from PIL import Image
import cv2

Как уже упоминалось, мы будем работать с изображениями, используемыми для питания автоматизированного автомобиля. Вы можете найти такие примеры на Kaggle. Мы загружаем одно из этих изображений с кодом ниже. Начнем с загрузки путей к файлам всех изображений (строки 2–3). Затем загрузите (строка 8) и отобразите (строка 9) изображение на первом пути. Вы можете увидеть это изображение на Рис. 1.

#Load image paths
read_path = "../../data/direction/"
img_paths = glob.glob(read_path + "*.jpg")

fig = plt.figure(figsize=(10,10))

#Display image
img = Image.open(img_paths[0])
plt.imshow(img) 

Обрезка

Простой метод — обрезать изображения, чтобы удалить ненужные внешние области. Цель состоит в том, чтобы удалить только те части изображения, которые не нужны для прогнозов. Для нашего автоматизированного автомобиля мы могли бы удалить пиксели с фона.

Для этого мы загружаем изображение (строка 2). Затем мы преобразуем это изображение в массив (строка 5). Этот массив будет иметь размеры 224 x 224 x 3. Высота и ширина изображения составляют 224 пикселя, и каждый пиксель имеет канал RGB. Чтобы обрезать изображение, мы выбираем только пиксели с позиции 25 и далее по оси Y (строка 8). Результат вы можете увидеть на Рис. 2.

#Load image
img = Image.open(img_paths[609])

#Covert to array
img = np.array(img)

#Simple crop
crop_img = img[25:,]

Возможно, вы захотите сохранить соотношение сторон. В этом случае вы можете добиться аналогичных результатов, сделав ненужные пиксели черными (строка 3).

#Change pixels to black
crop_img = np.array(img)
crop_img[:25,] = [0,0,0]

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

Глядя на изображение выше, у вас может возникнуть соблазн обрезать его еще больше. То есть вы можете обрезать левую часть изображения, не удаляя ни одной дорожки. Однако на Рис. 4 видно, что для других изображений мы удалили важные части дорожки.

#Additional cropping 
crop_img = np.array(img)
crop_img[:25,] = [0,0,0]
crop_img[:,:40] = [0,0,0]

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

Оттенки серого

Для некоторых приложений цвет изображения не важен. В этом случае мы можем сделать изображение в градациях серого. Мы делаем это с помощью функции cvtColor из OpenCV (строка 2).

#Gray scale
gray_img = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)

Оттенки серого работают, фиксируя интенсивность цвета в изображении. Он делает это, взяв средневзвешенное значение каналов RGB. В частности, функция выше использует эту формулу:

Y = 0.299*R + 0.587*G + 0.114*B

Мы можем понять преимущества этого, посмотрев на количество входных значений для каждого изображения. Если бы мы использовали все каналы RGB, он состоял бы из 150 528 значений (224*224*3). Для изображений в градациях серого у нас теперь есть только 50 176 значений (224*224). Более простой ввод означает, что нам нужно меньше данных и более простые модели.

RGB-каналы

Один из каналов может быть более важным. Вместо оттенков серого мы можем использовать только этот канал. Ниже мы выбираем каналы R (строка 6), G (строка 7) и B (строка 8). Каждый из результирующих массивов будет иметь размер 224 x 224. Вы можете увидеть соответствующие изображения на Рис. 6.

#Load image
img = Image.open(img_paths[700])
img = np.array(img)

#Get rgb channels
r_img = img[:, :, 0]
g_img = img[:, :, 1]
b_img = img[:, :, 2]

Вы также можете использовать функцию channel_filter. Здесь параметр канала (c) принимает значения 0,1 или 2 в зависимости от того, какой канал вы хотите. Имейте в виду, что некоторые пакеты будут загружать каналы в разном порядке. Мы используем PIL, который представляет собой RGB. Однако, если вы загружаете изображения с помощью cv2.imread(), каналы будут в порядке BGR.

def channel_filter(img,c=0):
    """Returns given channel from image pixels"""
    img = np.array(img)
    c_img = img[:, :, c]

    return c_img

При этих преобразованиях нужно подумать о том, удаляете ли вы важную информацию с изображения. Для нашего приложения дорожка оранжевого цвета. Другими словами, цвет дорожки может помочь отличить ее от остального изображения.

Порог интенсивности

При градациях серого каждый пиксель будет иметь значение от 0 до 255. Мы можем еще больше упростить ввод, преобразовав его в двоичные значения. Если значение шкалы серого выше порогового значения, значение пикселя равно 1, в противном случае оно равно 0. Мы называем это пороговое значение порогом интенсивности.

Приведенная ниже функция используется для применения этого порога. Сначала мы делаем изображение в градациях серого (строка 5). Если пиксель выше отсечки, ему присваивается значение 1000 (строка 8). Если мы установим пиксель равным 1, он будет ниже отсечки. Другими словами, на следующем шаге (строка 9) все пиксели будут установлены в 0. Наконец, мы масштабируем все пиксели так, чтобы они принимали значение либо 0, либо 1 (строка 11).

def threshold(img,cutoff=80):
    """Apply intesity thresholding"""
    
    img = np.array(img)
    img = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    
    #Apply cutoff
    img[img>cutoff] = 1000 #black
    img[img<=cutoff] = 0 #white
    
    img = img/1000

    return img

Часть проекта автоматизированного автомобиля заключалась в том, чтобы избегать препятствий. Это были банки, выкрашенные в черный цвет. На Рис. 7 видно, как, применяя функцию порога интенсивности, мы можем изолировать олово от остального изображения. Это возможно только потому, что олово черное. Другими словами, его интенсивность больше, чем остальная часть изображения.

Отсечение можно рассматривать как гиперпараметр. Глядя на рисунок 7, большая отсечка означает, что мы включаем меньше фонового шума. Недостатком является то, что мы захватываем меньше консервной банки.

Обнаружение края

Если мы хотим изолировать дорожку, мы можем использовать хитрое обнаружение границ. Это многоэтапный алгоритм, используемый для обнаружения границ в изображениях. Если вы хотите понять, как это работает, я предлагаю прочитать статью Софиан Сахир о хитром обнаружении краев.

Мы применяем алгоритм с функцией cv2.Canny(). Параметры threshold1 и threshold2 предназначены для процедуры гистерезиса. Это последний процесс алгоритма обнаружения краев, и он используется для определения того, какие линии на самом деле являются краями.

#Apply canny edge detection
edge_img = cv2.Canny(img,threshold1 = 50, threshold2 = 80)

Вы можете увидеть несколько примеров на Рис. 8. Как и в случае с порогом интенсивности, у нас остается бинарная карта — белая для краев и черная в остальных случаях. Есть надежда, что трек теперь будет легче отличить от остального изображения. Однако вы можете видеть, что края на заднем плане также обнаруживаются.

Цветовой фильтр

Возможно, нам повезет больше, если мы изолируем дорожку, используя цвет пикселя. Мы делаем это с помощью функции pixel_filter ниже. Используя cv2.inRange(), мы преобразуем изображение в двоичную карту (строка 10). Эта функция проверяет, попадает ли пиксель в диапазон, заданный нижним (строка 5) и верхним (строка 6) списками. В частности, каждый канал RGB должен находиться в соответствующем диапазоне (например, 134-t ≤ R ≤ 194+t).

def pixel_filter(img, t=0):
    
    """Filter pixels within range"""
    
    lower = [134-t,84-t,55-t]
    upper = [192+t,121+t,101+t]

    img = np.array(img)
    orange_thresh = 255 - cv2.inRange(img, np.array(lower), np.array(upper))

    return orange_thresh

Проще говоря, функция определяет, достаточно ли близок цвет пикселя к оранжевому цвету дорожки. Результаты показаны на рис. 9. Параметр t обеспечивает некоторую гибкость. При более высоких значениях мы можем захватить больше дорожки, но сохранить больше шума. Это связано с тем, что пиксели фона попадают в этот диапазон.

Вам может быть интересно, откуда мы получаем нижнюю и верхнюю границы. Откуда мы знаем, что каналы попадут между [134, 84, 55] и [192, 121, 101]? Ну, мы используем палитру цветов, созданную с помощью Python. Мы расскажем, как это создается, в статье ниже.



На Рис. 10 вы можете увидеть средство выбора в действии. Мы выбираем пиксели из нескольких изображений и пытаемся выделить их в разных местах на дорожке. Таким образом, мы получаем полный диапазон значений пикселей в различных условиях.

Всего мы выбрали 60 цветов. Вы можете увидеть все это на Рис. 11 (с дополнительной визуальной иллюзией). Каналы RGB всех этих цветов хранятся в списке — «цвета».

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

lower = [min(x[0] for x in colours),
              min(x[1] for x in colours),
              min(x[2] for x in colours)]

upper = [max(x[0] for x in colours),
              max(x[1] for x in colours),
              max(x[2] for x in colours)]

Ограничения разработки функций

После всего этого, вы не можете быть убеждены. Основным преимуществом глубокого обучения является то, что оно может идентифицировать сложные шаблоны без необходимости разработки признаков. Это хорошая точка.

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

Кроме того, мы увидели, что для некоторых методов нам не удалось устранить весь шум. Например, черный фон при пороговой регулировке интенсивности. Как это ни парадоксально, оставшийся шум теперь может быть еще труднее отличить от того, что важно. То есть остальные шумы и пиксели объекта имеют одинаковое значение (1).

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

Надеюсь, вам понравилась эта статья! Вы можете поддержать меня, став одним из моих приглашенных участников :)



| Твиттер | Ютуб | Информационный бюллетень — подпишитесь на БЕСПЛАТНЫЙ доступ к Курсу Python SHAP

Источники изображений

Все изображения мои собственные или взяты с www.flaticon.com. В случае последнего у меня есть Полная лицензия, как определено в их Премиум-плане.

Набор данных

Изображения JatRacer (CC0: Public Domain) https://www.kaggle.com/datasets/conorsully1/jatracer-images

Рекомендации

OpenCV, преобразование цвета https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html