Перевожу родной OpenCV-шный туториал. И он хорош! (Сложно сказать, чем не понравились те, что есть.)
Изначально туториал в виде ноутбука, поэтому что-то я убрал. А что-то добавил. В общем, это помесь перевода с пересказом.
Вступление
Эта записнушка поможет с первыми шагами в изучении обработки изображений и машинном зрении через OpenCV. Несколько простых примеров объяснят важные вещи! Рассмотрим:
Как открыть изображение
Проверить его атрибуты, вроде формы или типа данных в нём
Матричное представление картинки в Numpy
Цветные картинки и работу с каналами изображения
Вывод изображения через matplotlib
Сохранение изображений
Импортируем нужные библиотеки
import cv2 # собственно OpenCV
import numpy as np # для работы с математикой
import matplotlib.pyplot as plt # для вывода картинки
Открываем изображения в OpenCV
Картинкой пойдёт крошечная доска в шашечку, 18×18 пикселей:
OpenCV позволяет работать с разными форматами: JPG, PNG, и так далее. Можно загружать цветные и чб-картинки, изображения с альфа-каналом. Для загрузки воспользуйтесь функцией cv2.imread()
.
cv2.imread() — синтаксис и аргументы
retval = cv2.imread( filename[, flags] )
retval
: если картинка не загрузилась, в retval запишется None
. Такое бывает при ошибке в имени/пути, или если изображение битое.
В функцию передаётся один обязательный аргумент и один необязательный флаг:
filename
: Может быть как относительным, так и абсолютным путём. Это обязательный аргумент.Flags
: Флаги нужны для чтения изображения в определенном формате (например, в оттенках серого/цветном/с альфа-каналом). Необязательный аргумент! Значение по умолчаниюcv2.IMREAD_COLOR
или1
: этот флаг загрузит изображение как цветное.
Перед примерами посмотрим на пару флагов:
cv2.IMREAD_GRAYSCALE
или0
: Загружаем картинку как чёрно-белуюcv2.IMREAD_COLOR
или1
: Флаг по умолчанию, загружает картинку как цветную, без альфа-канала.cv2.IMREAD_UNCHANGED
или-1
: Загружает картинку в том виде, в котором она есть, включая альфу.
Флагов, конечно, больше.
Подробнее про imread(): ссылка на офф. документацию
Подробнее про флаги: ссылка на офф. документацию
Теперь посмотрим, что внутри шашечек:
# Читаем изображение как чёрно-белое
cb_img = cv2.imread("checkerboard_18x18.png",0)
# Печатаем что прочитали. Каждый пиксель есть элемент двумерного массива numpy.
# Значение пикселей восьмибитное: [0,255]
print(cb_img)
Посмотрим атрибуты изображения
# вывод размера изображения (то есть, массива Numpy)
print("Image size is ", cb_img.shape)
# тип данных в изображении (то есть, в массиве Numpy)
print("Data type of image is ", cb_img.dtype)
В ответ получим:
>>>Image size is (18, 18)
>>>Data type of image is uint8
Что за uint8?
8-bit unsigned integer arrays!
Uint8 — стандартный способ отображения изображений, где пиксель описывается диапазоном от 0 до 255. Если это изображение в градациях серого, пиксель со значением 0 является черным, а пиксель со значением 255 — белым.
Есть и другие форматы, конечно.
В общем, работаeт всё, что работает с ndarray
: .ndim
, .itemsize
, .fill()
...
А теперь выведем картинку через matplotlib
Двумя командами:
# рисуем шашечки
plt.imshow(cb_img)
# выводим шашечки
plt.show()
Удивляемся:
Прочитанная цветовая палитра и отображённая могут и не совпасть. Поэтому явно укажем цветовое пространство для вывода изображения:
# Поставим настройку color map
plt.imshow(cb_img, cmap='gray')
Другой пример
Расплывчатые шашечки! Такие же, как в прошлый раз, но теперь с нечёткими гранями:
# Читаем картинку как чб
cb_img_fuzzy = cv2.imread("checkerboard_fuzzy_18x18.jpg",0)
# Печатаем массив
print(cb_img_fuzzy)
# Показываем картинку
plt.imshow(cb_img_fuzzy,cmap='gray')
plt.show()
Переходим к цвету
До этого момента мы говорили про чёрно-белые изображения, а теперь поговорим про цветные.
Подопытным кроликом будет лого колы:
Загружаем, проверяем размер и тип данных:
coke_img = cv2.imread("coca-cola-logo.png",1)
print("Image size is ", coke_img.shape)
print("Data type of image is ", coke_img.dtype)
В ответ получим:
>>>Image size is (700, 700, 3)
>>>Data type of image is uint8
Размер картинки поменялся, потому как чб-изображение состоит из одного канала — от чёрного до белого, а цветное — из нескольких, например, трёх: RGB.
Выведем на экран уже известными манипуляциями:
plt.imshow(coke_img)
plt.show()
Цвет лого явно отличается от того, что было. Matplotlib ожидает картинку в формате RGB, а OpenCV хранит их в формате BGR. То есть, для корректного отображения нам нужно поменять местами красный и зелёный каналы.
# ниже numpy-специфичная конструкция:
# (:) — взять каждый элемент по порядку,
# (::-1) — взять каждый элемент, но в обратном порядке.
coke_img_channels_reversed = coke_img[:, :, ::-1]
plt.imshow(coke_img_channels_reversed)
Разделение и объединение каналов
Две функции:
cv2.split()
— разделит многоканальный массив на несколько одноканальных.cv2.merge()
— объединит массивы в один многоканальный. Массивы должны быть одинакового размера.
Проверим их на озере. В смысле, озером.
# Разбиваем картинку на каналы: B,G,R
img_NZ_bgr = cv2.imread("New_Zealand_Lake.jpg",cv2.IMREAD_COLOR)
b,g,r = cv2.split(img_NZ_bgr)
# Отрисовываем их
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(r,cmap='gray');plt.title("Red Channel")
plt.subplot(142);plt.imshow(g,cmap='gray');plt.title("Green Channel")
plt.subplot(143);plt.imshow(b,cmap='gray');plt.title("Blue Channel")
# Собираем обратно в BGR
imgMerged = cv2.merge((b,g,r))
# Выводим, что получилось
plt.subplot(144);plt.imshow(imgMerged[:,:,::-1]);plt.title("Merged Output")
plt.show()
точка с запятой в коде...
...не поощряется, но допускается. Помогает писать код в одну строку, а иногда это удобно!
Перевод в иные цветовые пространства
cv2.cvtColor()
— преобразует изображение из одного цветового пространства в другое. В случае преобразования в RGB и из него порядок каналов надо указать явно: RGB или BGR. Формат цвета по умолчанию в OpenCV часто называют RGB, но на самом деле это BGR. Первый байт в стандартном 24-битном цветном изображении будет 8-битным синим компонентом, второй байт будет зеленым, а третий байт будет красным. Четвертый, пятый и шестой байты будут, соответственно, вторым пикселем — синий, зеленый, красный, и так до конца картинки.
Преобразование BGR в RGB
img_NZ_rgb = cv2.cvtColor(img_NZ_bgr, cv2.COLOR_BGR2RGB)
plt.imshow(img_NZ_rgb)
plt.show()
Преобразование в HSV
HSV — Hue, Saturation, Value — тон, насыщенность, значение.
img_hsv = cv2.cvtColor(img_NZ_bgr, cv2.COLOR_BGR2HSV)
# Разобъём картинку на H,S,V каналы
h,s,v = cv2.split(img_hsv)
# Нарисуем и покажем их
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(h,cmap='gray');plt.title("H Channel")
plt.subplot(142);plt.imshow(s,cmap='gray');plt.title("S Channel")
plt.subplot(143);plt.imshow(v,cmap='gray');plt.title("V Channel")
plt.subplot(144);plt.imshow(img_NZ_rgb);plt.title("Original")
plt.show()
Модификация отдельного канала
Хочется синий посиней? Есть решение!
img_NZ_bgr = cv2.imread("New_Zealand_Lake.jpg",cv2.IMREAD_COLOR)
b,g,r = cv2.split(img_NZ_bgr)
b = b+50 # осиняем
plt.figure(figsize=[20,5])
plt.subplot(141);plt.imshow(r,cmap='gray');plt.title("Red Channel")
plt.subplot(142);plt.imshow(g,cmap='gray');plt.title("Green Channel")
plt.subplot(143);plt.imshow(b,cmap='gray');plt.title("Blue Channel")
imgMerged = cv2.merge((r,g,b)) # в этот раз соберём сразу RGB
plt.subplot(144);plt.imshow(imgMerged);plt.title("Merged Output")
plt.show()
Сохранение изображений
Почти cv2.imread
, только cv2.imwrite
. Первым аргументом передаём путь и имя, вторым — изображение. Оба два обязательны. Расширение OpenCV подберёт, отталкиваясь от указанного в имени. Ещё можно докинуть параметров, и, например, указать качество JPG.
cv2.imwrite( filename, img[, params] )
Подробнее про imwrite(): ссылка на оф. документацию
Подробнее про параметры: ссылка на офф. документацию
Вот и всё! Первый маленький шажок к человеку-фотошопу пройден! До встречи в следующих сериях.
Комментарии (4)
artemsnad
22.07.2022 18:13Хорошая статья. Можно добавить, что в OpenCV есть встроенный метод для вывода изображений:
cv2.imshow('name',image)
Не обязательно использовать pyplot из matplotlib.
goshkalinin Автор
22.07.2022 18:23Это тогда надо будет ещё добавить
cv2.waitKey(0)
, а там иcv2.destroyAllWindows()
, а потом ещё что-нибудь.
В общем, можно, но как-то ловко это надо провернуть. Может, не грешно отдельный микропост по этому поводу набросать.
Daddy_Cool
Спасибо! Для старта как раз.
Я так понимаю, это Питон, но есть и для Си - https://docs.opencv.org/4.x/d3/d96/tutorial_basic_geometric_drawing.html
goshkalinin Автор
Рад!
Да, всё так.