Сегодня я хочу представить вам третью статью из серии «Нейронные сети для начинающих». Мы научимся обрабатывать изображения и сохранять результаты в отдельные файлы. Вот небольшой список задач, которые будут разобраны ниже:
- Преобразование изображения в оттенки серого.
- Уменьшение размерности изображения (в пикселях) в 4 раза.
- Нахождение и выделение (рамкой) самого тёмного объекта на изображении.
Для работы нам понадобится любой редактор кода. Можете выбрать ваш любимый, я же буду использовать PyCharm. Подробнее про работу с PyCharm вы можете почитать на официальной странице редактора или же посмотреть в кратком гайде.
Перед началом работы советую вам прочитать предыдущие статьи для более полного понимания материала:
- #1 Нейронные сети для начинающих. Решение задачи классификации Ирисов Фишера
- #2 Нейронные сети для начинающих. NumPy. MatplotLib. Операции с изображениями в OpenCV
Код
Итак, приступим. Для начала нам потребуется библиотека OpenCV, которую мы разбирали в предыдущей статье в пункте «Поверхностное знакомство с библиотекой OpenCV». Давайте установим её. Для этого введём в терминале команду:
pip install opencv-python
После этого в нашем файле с расширением .py, мы можем импортировать библиотеку cv2 (краткое название библиотеки OpenCV). Давайте пропишем это:
import cv2 import numpy as np
Также мы импортировали библиотеку NumPy, которая предназначена для математических вычислений. Подробнее о ней вы можете прочитать в предыдущей статье, в пункте «Знакомство с библиотекой NumPy».
Теперь давайте отойдём от кода и вспомним немного теории.
Основные операции с изображениями
Установив OpenCV, пощупаем основные функциональные возможности данной библиотеки.
▍ Вывод изображения на экран
Процесс вывода изображения на экран состоит из двух шагов. Сначала мы должны загрузить изображение, а после вывести его на экран. Эти операции выполняются последовательно, и для каждой из них предназначена отдельная функция.
Для вывода изображения на экран нам необходимо задать две вещи:
- Путь к файлу, в котором содержится изображение (подойдёт как относительный, так и абсолютный путь).
- Режим чтения файла (только чтение, запись и т. д.).
Функция, при помощи которой мы считываем изображение, называется cv2.imread(). У неё есть три режима работы:
- IMREAD_GRAYSCALE. Как видно из названия, он преобразует изображение в чёрно-белое с оттенками серого.
- IMREAD_UNCHANGED, который загружает изображение без обрезания альфа-канала.
- IMREAD_COLOR (используемый по умолчанию). Он просто загружает цветное изображение, используя RGB-каналы.
Вот пример кода:
import cv2 my_bike = cv2.imread('bike.png')
Замечание: если в результате выполнения данного кода возникла ошибка, есть три возможных причины для этого. Первая — вы неправильно задали путь к файлу. Вторая — такого файла просто не существует, и третья — тип изображения (jpg/jpeg/png) задан неверно.
Теперь давайте выведем на экран только что загруженное изображение. Для этого используется функция cv2.imshow(). Если вы пользовались Matlab, её работа должна быть вам знакома.
cv2.imshow('my_bike', my_bike)
Первый параметр функции imshow() — это строка, которую мы хотим использовать в качестве заголовка к нашему изображению. Второй параметр — переменная, содержащая загруженное нами изображение.
▍ Сохранение изображений
Для сохранения результатов нашей работы с изображениями в библиотеке OpenCV существует функция cv2.imwrite().
Вот пример её использования:
cv2.imwrite('bike.png', my_bike)
Здесь мы задали название файла и переменную, в которой содержится изображение. Оно будет сохранено в текущую рабочую директорию.
▍ Преобразование изображений
Эта тема находит применение в самых разных приложениях, но отдельно следует упомянуть задачу аугментации данных для моделей машинного обучения. Речь идёт о ситуациях, когда в датасете недостаточно данных для полноценного обучения, и мы, дополняя и видоизменяя существующие картинки, увеличиваем первоначальный датасет до нужного размера. Это помогает серьёзно увеличить точность работы обучаемой модели.
Список возможных преобразований весьма велик и включает в себя масштабирование, афинное преобразование изображений, вращение, транспонирование и многое другое. Мы кратко расскажем только про масштабирование и вращение, но в библиотеке OpenСV есть поддержка всех возможных преобразований. Разберём масштабирование.
▍ Масштабирование
Попросту говоря, масштабирование — это не что иное, как изменение размеров изображения, его увеличение либо уменьшение. В библиотеке OpenCV для этого существует функция resize. У этой функции, в свою очередь, есть три метода: INTER_CUBIC, INTER_LINEAR и INTER_AREA. Давайте на примере конкретного кода разберём, как это всё работает. Пожалуйста, внимательно изучите код, комментарии к нему и описание ниже.
import cv2 import numpy as np import matplotlib.pyplot as plt image = cv2.imread('my_bike.jpg') # Увеличиваем масштаб/расширяем в 2 раза по ширине и высоте result_1 = cv2.resize(image, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC) # Уменьшаем масштаб/сжимаем в 2 раза по ширине и высоте result_2 = cv2.resize(image, None, fx=2, fy=2, interpolation=cv2.INTER_AREA) # Выводим на экран получившиеся изображения plt.imshow(result_1) plt.imshow(result_2) plt.show()
Здесь в функции resize параметр fx определяет масштаб изменений по ширине, fy — по высоте, а параметр interpolation отвечает за сам способ изменений (то есть расширение или сжатие).
OpenCV cvtColor
Цвета, присутствующие в изображении, представлены цветовыми пространствами в OpenCV. Существует несколько цветовых пространств, каждое из которых имеет свою важность, например, RGB, CMYK и т. д. Всякий раз, когда возникает необходимость преобразовать изображение из одного цветового пространства в другое в OpenCV, мы используем функцию cvtColor(). Всего в OpenCV доступно более 150 кодов преобразования цветового пространства (cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV и т. д.). Преобразование цветового пространства с помощью функции cvtColor() очень полезно для решения задач в области компьютерного зрения.
Синтаксис для определения функции cvtColor() в OpenCV следующий:
cvtColor(image, code)
Здесь image — это изображение, цветовое пространство которого должно быть преобразовано, а code — это код преобразования цветового пространства.
▍ Как работает функция cvtColor() в OpenCV
Функция cvtColor() в OpenCV принимает два параметра, а именно изображение и код. То есть изображение, цветовое пространство которого должно быть преобразовано в другое цветовое пространство, а также код преобразования цвета. Функция cvtColor() возвращает изображение с изменённым цветовым пространством.
▍ Обсудим примеры OpenCV cvtColor
Допустим, что мы пишем программу на OpenCV и языке программирования Python, с помощью которой мы собираемся изменить цветовое пространство изображения на другое цветовое пространство и отобразить полученное изображение с изменённым цветовым пространством в качестве вывода на экран.
Для примера будем использовать следующую картинку:
Код программы:
import cv2 # Считывание изображения, которое должно быть преобразовано # в цветовое пространство HSV image1 = cv2.imread('test_img_color.png') # Преобразование изображения в цветовое пространство HSV # с помощью функции cvtColor и сохранение полученного изображения imageresult = cv2.cvtColor(image1, cv2.COLOR_BGR2HSV) # Вывод результата cv2.imshow("Start image", image1) cv2.imshow("End image", imageresult) cv2.waitKey(0)
В приведённой выше программе мы импортируем модуль cv2. Затем мы читаем изображение, цветовое пространство которого необходимо преобразовать с помощью функции imread(). Далее используем функцию cvtColor(), указав код преобразования цветового пространства. Полученное изображение отображается как вывод на экране. Результат показан на снимке выше.
Выглядит круто! А теперь давайте поменяем цветовое пространство на серое. Для этого используем следующий код:
import cv2 # Считывание изображения, которое должно быть преобразовано # в серое цветовое пространство image1 = cv2.imread('test_img_color.png') # Преобразование изображения в серое цветовое пространство # с помощью функции cvtColor и сохранение полученного изображения imageresult = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) # Вывод результата cv2.imshow("Start image", image1) cv2.imshow("End image", imageresult) cv2.waitKey(0)
Алгоритм детектирования краёв Canny Edge Detector
Обнаружение кромок является важной техникой анализа изображений, когда кто-то заинтересован в распознавании объектов по их контурам, а также считается важным шагом в восстановлении информации из изображений.
Например, такие функции, как линии и кривые, могут быть извлечены с помощью обнаружения краёв, которые затем обычно используются компьютерным зрением более высокого уровня или алгоритмами обработки изображений. Хороший алгоритм будет выделять расположение основных краёв на изображении, в то же время игнорируя любые ложные края, вызванные шумом.
Но что такое рёбра? Края — это особенности изображения, которые можно использовать для оценки и анализа структуры объектов на нём. Они представляют значительные локальные изменения, которые произошли в интенсивности изображения (то есть значение пикселя). Края обычно появляются на границе между двумя разными областями изображения.
Сейчас мы познакомимся с алгоритмом детектора рёбер Canny и тем, как мы можем реализовать его в Python.
Небольшая историческая справка: алгоритм детектора краёв Canny назван в честь его изобретателя Джона Ф. Кэнни, который изобрёл алгоритм в 1986 году. Детектор краёв Canny обычно принимает изображение в градациях серого в качестве входных данных и создаёт изображение, показывающее местоположение разрывов интенсивности в качестве выходных данных.
Не хочется описывать здесь много математики, но давайте разберёмся, что происходит за кулисами в алгоритме детектора краёв с точки зрения высокого уровня.
Первое, что делает детектор краёв Canny, — использует гауссовую свёртку для сглаживания входного изображения и удаления шума. Затем первый оператор производной применяется к сглаженному изображению, чтобы выделить области изображения с высокими первыми пространственными производными. Затем алгоритм находит как величину, так и направление градиента, вычисляя x-производную и y-производную. Тем более что знание направления градиента фактически позволяет нам найти направление рёбер.
Затем алгоритм выполняет то, что называется «не максимальным подавлением», где он отслеживает вершину гребней, которые поднимаются от краёв, и устанавливает в ноль пиксели, которые находятся не на вершине гребня, в результате чего получается тонкая линия.
Другими словами, мы проверяем, считается ли градиент, вычисленный на предыдущем шаге, максимальным среди соседних точек, лежащих как в положительном, так и в отрицательном направлении. Если градиент был максимальным, он считается частью края, и наоборот.
Процесс отслеживания, описанный выше, контролируется двумя порогами, t1 и t2, так что t1>t2, называемый порогом гистерезиса. Отслеживание начинается в точке на гребне выше t1, а затем продолжается в обоих направлениях из этой точки, пока высота гребня не станет меньше t2.
В результате мы выбираем все граничные точки, которые находятся выше верхнего порога t1, а затем исследуем, существуют ли соседи этих точек, которые рассматриваются ниже верхнего порога t1 и выше нижнего порога t2. В этом случае такие соседи будут частью края.
Таким образом, ширина гауссова ядра, используемого для сглаживания входного изображения, и пороговые значения t1 (верхний) и t2 (нижний), используемые трекером, являются параметрами, которые определяют эффект детектора краёв Canny.
▍ Немного кода для понимания реализации Canny Edge Detector с использованием OpenCV
В OpenCV есть функция canny() для применения алгоритма детектора краёв Canny к изображению. Следующий код показывает, как мы можем использовать OpenCV, чтобы найти края в нашем изображении:
import cv2 import matplotlib.pyplot as plt img = cv2.imread('test_img_color.png') edges = cv2.Canny(img,25,255,L2gradient=False) cv2.imshow('Start image', img) plt.imshow(edges, cmap='gray') plt.show() cv2.waitKey(0)
Обратите внимание, что я передал следующее в качестве аргументов функции Canny():
- img — имя изображения,
- lower threshold — 25,
- upper threshold — 255,
- L2gradient=False — это означает, что используется L1-норма. Если установлено значение True, будет использоваться L2-норма.
Затем для построения результатов используем библиотеку matplotlib и получаем следующий результат:
Более подробно про алгоритм можно почитать здесь (очень советую к прочтению) и здесь.
Возвращение к коду
Надеюсь, вы не забыли, что мы учимся обрабатывать изображения и сохранять результаты в отдельные файлы, поэтому давайте продолжим. После импорта необходимых библиотек нам нужно считать нашу тестовую картинку. У меня их будет две:
На первой изображены геометрические фигуры разных цветов (нас будет интересовать самый тёмный объект, то есть чёрный). На второй же мы видим тестовую карту с цветовыми квадратами, разбитыми на сектора. На ней нас также будет интересовать самый тёмный объект, то есть чёрный.
Хорошо, на картинки мы полюбовались, а как же их загрузить?
Из пункта «Основные операции с изображениями» мы знаем, что для загрузки изображения нам нужно прописать команду cv2.imread. Давайте загрузим оба изображения и выведем их с помощью кода:
img_1 = cv2.imread('test_map_1.png') img_2 = cv2.imread('test_map_2.png') cv2.imshow('First image', img_1) cv2.imshow('Second image', img_2) # функция waitKey() позволяет нам не закрывать выведенные картинки сразу, а дожидаться нажатия любой кнопки, перед тем как всё закрыть cv2.waitKey(0)
Вот что нам выведет программа:
Далее мы выполняем следующий код (внимательно прочитайте комментарии к нему):
import cv2 import numpy as np img = cv2.imread('test_map_1.png') # Создаём копию изначального изображения img_cont = img.copy() # Переводим изначальное изображение img в серый канал (с этим методом # мы познакомились выше) и сохраняем в переменной gray gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Производим изменение размерности в 4 раза (изменение размерности производится # в пикселях) относительно изначальной картинки img и сохраняем полученное # изображение в переменной img_resize img_resize = cv2.resize(img, (int(img.shape[1] / 4), int(img.shape[0] / 4))) # Далее идёт большой блок кода, в котором мы создаём # алгоритм детектирования краёв Canny Edge Detector (с этим методом # мы познакомились выше) canny_1 = 200 canny_2 = 225 canny = cv2.Canny(img, canny_1, canny_2) contours, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) min_black = 255 cnt_black = [] for cnt in contours: c_area = cv2.contourArea(cnt) + 1e-7 if cv2.contourArea(cnt) + 1e-7 > 500: cv2.drawContours(img_cont, [cnt], -1, 3) mask = np.zeros_like(gray) cv2.drawContours(mask, [cnt], -1, (255,255,255), -1) temp_mask = cv2.bitwise_and(gray, mask) temp_col = np.sum(temp_mask).real/(cv2.contourArea(cnt)+1e-7) if (temp_col < min_black) or (len(cnt_black) == 0): cnt_black = cnt min_black = temp_col if len(cnt_black)!=0: cv2.drawContours(img_cont, [cnt_black], -1, (0,0,255), 3)
Нам остаётся совсем немного, а именно вывести результат работы нашей программы на экран и сохранить данные в папку с проектом. Это легко сделать с помощью следующего кода:
# Сохранение результатов работы нашей программы в папку # Сохранение изображения с детектированными контурами cv2.imwrite('img_contour_1_1.jpg', img_cont) # Сохранение изображения в сером цветовом канале cv2.imwrite('img_gray_channel_1_1.png', gray) # Сохранение уменьшенного в 4 раза изображения cv2.imwrite('img_resize_1_1.png', img_resize) # Вывод на экран изначального изображения cv2.imshow('Basic image', img) # Вывод на экран изображения с детектированными контурами cv2.imshow('Contour image', img_cont) # Вывод на экран изображения в сером цветовом канале cv2.imshow('Gray channel image', gray) # Вывод на экран уменьшенного в 4 раза изображения cv2.imshow('Resize image', img_resize) # Режим ожидания нажатия кнопки cv2.waitKey(0)
Это код выполняет преобразование первой картинки с именем «test_map_1». Чтобы преобразовать вторую, достаточно в строке img = cv2.imread(‘test_map_1.png’) поменять название файла с «test_map_1.png», на «test_map_2.png».
Теперь давайте посмотрим результаты работы нашей программы для «test_map_1.png».
Изначальное изображение:
Изображение в сером цветовом канале:
Уменьшенное в 4 раза изображение:
Изображение с детектированными контурами самого тёмного элемента:
То же самое давайте посмотрим для «test_map_2.png».
Изначальное изображение:
Изображение в сером цветовом канале:
Уменьшенное в 4 раза изображение:
Изображение с детектированными контурами самого тёмного элемента:
import cv2 import numpy as np img = cv2.imread('test_map_1.png') img_cont = img.copy() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img_resize = cv2.resize(img, (int(img.shape[1] / 4), int(img.shape[0] / 4))) canny_1 = 200 canny_2 = 225 canny = cv2.Canny(img, canny_1, canny_2) contours, hierarchy = cv2.findContours(canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) min_black = 255 cnt_black = [] for cnt in contours: c_area = cv2.contourArea(cnt) + 1e-7 if cv2.contourArea(cnt) + 1e-7 > 500: cv2.drawContours(img_cont, [cnt], -1, 3) mask = np.zeros_like(gray) cv2.drawContours(mask, [cnt], -1, (255,255,255), -1) temp_mask = cv2.bitwise_and(gray, mask) temp_col = np.sum(temp_mask).real/(cv2.contourArea(cnt)+1e-7) if (temp_col < min_black) or (len(cnt_black) == 0): cnt_black = cnt min_black = temp_col if len(cnt_black)!=0: cv2.drawContours(img_cont, [cnt_black], -1, (0,0,255), 3) cv2.imwrite('img_basic_1_1.jpg', img) cv2.imwrite('img_contour_1_1.jpg', img_cont) cv2.imwrite('img_gray_channel_1_1.png', gray) cv2.imwrite('img_resize_1_1.png', img_resize) cv2.imshow('Basic image', img) cv2.imshow('Contour image', img_cont) cv2.imshow('Gray channel image', gray) cv2.imshow('Resize image', img_resize) cv2.waitKey(0)
Всё очень просто!
Все файлы для повторения кода из статьи, включая код и картинки, вы можете найти на моём GitHub.
А теперь вопрос к вам! Давайте поразмышляем, где можно применить данные технологии? Как, преобразовав задачу и усовершенствовав код, все эти знания можно использовать в реальных проектах? Жду ваших комментариев!
RUVDS | Community в telegram и уютный чат
ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/694994/
Добавить комментарий