TL;DR cv3 - обёртка над opencv-python, которая ускоряет написание кода, не сужая функциональность
Привет! Хочу поделиться написанным мною фреймворком на Python, который упрощает работу с OpenCV и делает его более питоничным. Погнали!
Содержание
Демонстрация возможностей
Сразу начну с демонстрации возможностей на примере реальных задач, которые встают перед исследователями компьютерного зрения, дата инженерами в области CV, разработчиками в области обработки изображений и другими специалистами.
Визуализация предсказаний YOLOv5
import cv3
import torch
model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
img = cv3.imread('zidane.jpg')
outputs = model([img])
results = outputs.pandas().xyxy[0]
for xmin, ymin, xmax, ymax, conf, cls_id, cls_name in results.values:
cv3.rectangle(img, xmin, ymin, xmax, ymax, color=cv3.COLORS[cls_id], t=5)
cv3.text(img, f'{cls_name} {conf:.2f}', xmin, ymin+30, color='white', t=2)
with cv3.Window() as wind:
wind.imshow(img)
wind.wait_key(0)
В данном коде происходит чтение изображения, получение предсказаний от модели и отрисовка детекций. В конце создаётся окно, на котором отображается изображение с детекциями.
Перенос изображений с процессингом
Пример кода для переноса изображений из одной папки в другую с изменением размера изображений.
import cv3
from pathlib import Path
SRC_DIR = Path('images')
DST_DIR = Path('images256')
TARGET_SIZE = (256, 256)
for img_path in SRC_DIR.glob('*.jpeg'):
img = cv3.imread(img_path)
resized = cv3.resize(img, *TARGET_SIZE)
dst_path = DST_DIR / img_path.name
cv3.imwrite(dst_path, resized, mkdir=True)
Здесь обходятся все jpeg изображения из папки images, приводятся к размеру 256x256 и сохраняются под тем же именем в папке images256.
Визуализация детекций от dlib
Код для отрисовки детекций лиц и ландмарков, полученных от dlib. Подробнее про dlib
import cv3
import dlib
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
img = cv3.imread('musk.jpg')
dets = detector(img)
for det in dets:
cv3.rectangle(img, det.left(), det.top(), det.right(), det.bottom(), t=2)
shape = predictor(img, det)
for point in shape.parts():
cv3.point(img, point.x, point.y, color='lime', r=2)
with cv3.Window() as wind:
wind.imshow(img)
wind.wait_key(0)
В коде считывается изображение, на нём запускается детектор лиц, и для каждой детекции определяются ключевые точки лица. Полученные детекции наносятся на изображение, после чего изображение выводится на экран.
Вывод видео с веб-камеры
import cv3
with cv3.Windows(['Original', 'Gray', 'Threshold', 'HSV']) as ws:
for frame in cv3.Video(0):
gray = cv3.rgb2gray(frame)
thr = cv3.threshold(gray)
hsv = cv3.rgb2hsv(frame)
ws['Original'].imshow(frame)
ws['Gray'].imshow(gray)
ws['Threshold'].imshow(thr)
ws['HSV'].imshow(hsv)
if cv3.wait_key(1) == ord('q'):
break
В коде создаётся 4 окна, далее происходит итерирование по видеопотоку с веб-камеры. Кадры обрабатываются (цвета конвертируются в gray/hsv; применение трешолда) и отправляются на вывод в соответствующие окна. Если вводится ‘q’, то окна закрываются и программа завершается.
Процессинг видео
Пример кода, с помощью которого из цветного видео можно получить черно-белое.
import cv3
with cv3.Video('output.mp4', 'w') as out:
with cv3.Video('vid.mp4') as cap:
for frame in cap:
gray = cv3.rgb2gray(frame)
out.write(gray)
В коде создаются поток записи и чтения видео. Далее происходит итерирование по видеопотоку чтения, пока он не завершится. На каждой итерации кадр преобразуется из RGB в GRAY формат и отправляется в выходной поток.
Применение трансформаций
Ниже приведён код для создания анимации из изображения при помощи преобразований масштабирования и вращения.
import cv3
import numpy as np
n_iter = 100
img_orig = cv3.imread('fractal.jpg')
scales = np.linspace(0.3, 1.7, n_iter)
with cv3.Video('output.mp4', 'w') as out:
for i in range(n_iter):
img = cv3.transform(img_orig, scale=scales[i], angle=i, border='default')
out.write(img)
В коде считывается изображение и создаётся поток записи видео. Далее к изображению последовательно применяются трансформации, и результат отправляется в выходной поток.
Мотивация
В большинстве проектов по обработке изображений и компьютерному зрению используется библиотека OpenCV, написанная на C++. Данная библиотека хорошо зарекомендовала себя и пока не уступает аналогам, благодаря огромному числу реализованных алгоритмов компьютерного зрения и своей гибкости. У OpenCV есть интерфейс, написанный на Python: opencv-python или cv2. У него есть ряд преимуществ:
-
Высокая скорость работы алгоритмов благодаря тому, что cv2 - интерфейс к OpenCV
-
Работа с изображениями осуществляется через numpy матрицы, что даёт возможность пользоваться методами numpy
-
Большой функционал: возможность использования всех алгоритмов, реализованных в OpenCV, с минимальными усилиями
Однако в ней есть ряд недостатков, связанных с интерфейсом, с которыми сообщество ничего не делает и, судя по всему, не собирается делать. Приведу их:
-
Изображения считываются и записываются в BGR формате. Это особенность присуща только OpenCV, в других библиотеках работа происходит в RGB формате. Из-за этого возникают конфликты при использовании разных библиотек в одном проекте (например, связка cv2+matplotlib). Данная фича создаёт путанницу, особенно у новичков, и отвлекает от решения основной задачи. Типичная проблема, возникающая на практике: аватары вместо людей.
-
Ошибки не обработаны должным образом. Например при чтении несуществующего файла, не вылетают ошибки, а при проблеме во время записи изображения в файл возвращается безобидное False. Зачастую очень сложно понять, с чем связана та или иная ошибка. К примеру, ошибка TypeError: Expected cv::UMat for argument, периодически возникающая при рисовании фигур
-
Жёсткая привязка функций OpenCV к матрицам типа uint8. В целом, это хорошее решение, так как каждый пиксель должен принимать значение от 0 до 255, но из ошибок не всегда можно понять, что проблема в типе матрицы
-
Очень много лишнего кода, который приходится каждый раз копировать со stackoverflow (если у вас не феноменальная память на странные вещи). Например, для вращения изображения в cv2 необходимо сперва получить матрицу вращения, после чего применить её к изображению.
-
Много обязательных аргументов функций, без которых можно обойтись. Например, обязательное указание цвета линий при рисовании прямоугольника или несколько различных параметров при нанесении текста. Или при записи видео в файл необходимо передавать разрешение кадров, хотя данную информацию можно извлечь из первого записанного кадра. И в случае неправильной передачи этого аргумента, видео просто-напросто не запишется без каких-либо ошибок.
-
Огромное множество флагов, которые нельзя уместить в голове и, соответственно, приходится гуглить каждый раз. Примеры: cv2.cv.CV_CAP_PROP_FRAME_COUNT для получения количество кадров в видео, cv2.INTER_LINEAR для указания типа интерполяции и т. д.
-
Разные названия параметров функций, которые отвечают за одно и то же. Например, в cv2.copyMakeBorder указываются параметры borderType и value для задания границ, а в cv2.warpAffine за это отвечают borderMode и borderValue. Ещё пример: для указания типа интерполяции в cv2.resize используется параметр interpolation, а в cv2.warpAffine - flags
Я уже много лет работаю в области компьютерного зрения и использую cv2 в своих проектах. И каждый раз работа с этой библиотекой доставляет дискомфорт, хоть и достойного аналога у неё нет. Поэтому в один момент я решил написать обёртку над cv2, чтобы ускорить работу с этой библиотекой - cv3.
Знакомьтесь, cv3
cv3 или pycv - это более питоничный интерфейс к OpenCV. Он упрощает работу с этой библиотекой, расширяет его синтаксические возможности, а также ускоряет исследования в области компьютерного зрения и выполнение задач по обработке изображений, при этом сохраняя гибкость и функциональность OpenCV.
Цель проекта: сделать OpenCV более питоничным и удобным в использовании, понизить порог входа в область компьютерного зрения.
Задача cv3: абстрагироваться от лишнего кода (лишних аргументов и преобразований типов), при этом сохранив всю мощь и весь функционал этой библиотеки. Таким образом, фокус идёт на решении более важных задач компьютерного зрения, чем переписывание ненужного кода, копирование его со stackoverflow и его дебаг.
Первоначальная идея была контрибьютить в репозиторий cv2, но я быстро от неё отказался. И вот почему: в cv3 очень много принципиальных вещей, которые не соотносятся с OpenCV. Это и внутренние состояния, и добавление исключений, и новые сущности, и синонимичные названия функций, и дополнительные методы и так далее. Так что cv3 разрабатывался как интерфейс к cv2.
Отношения cv3, opencv-python и OpenCV можно провизуализировать следующим образом:
Особенности cv3
К главным фичам cv3 можно отнести:
-
Избавление от жёсткой типизации
-
Поддержка относительных координат
-
Использование однотипных интерфейсов для работы с потоками. К примеру, VideoCapture, VideoWriter, Window поддерживают открытие через контекстный менеджер и могут быть закрыты через метод .close()
-
Выбрасывание понятных исключений при возникновении проблем. Например, при чтении несуществующего файла или записи в видеофайл кадра с неправильным разрешением.
-
Использование snake_case, вместо CamelCase, в соответствии со стандартом Python. К примеру, вместо cvtColor предлагается использовать cvt_color
Далее расскажу подробнее про особенности.
Общие особенности
-
Чтение и запись изображений/видео, цвет в RGB формате. Это обеспечивается хранением внутреннего состояния cv3.opt.RGB=True. Данное состояние можно переключить через cv3.opt.set_bgr()
-
Автокаст изображений. Если в функцию передаётся изображение в неправильном формате, то оно приводится к np.uint8, и появляется предупреждение. Также поддерживается изображение в формате PIL
-
Поддержка относительных координат. Возможность делать обрезку изображения (cv3.crop), паддинг (cv3.pad), ресайз (cv3.resize), отрисовку фигур в относительных координатах. Параметр rel=True означает, что переданы относительные координаты; rel=False - целые координаты. По умолчанию rel=None, в этом случае координаты считаются относительными, если они имеют тип float и находятся в диапазоне от 0 до 1.
-
Добавление множества функций на основе методов cv2, которые часто используются на практике: cv3.transform (вращение и масштабирование изображения) cv3.rgb2gray (перевод rgb изображения в gray формат), cv3.hline (рисование горизонтальной линии) и т. д.
-
Поддержка нескольких режимов (mode) отрисовки прямоугольника (cv3.rectangle) или обрезки изображения (cv3.crop):
-
“xyxy” (по умолчанию) - формат (x0, y0, x1, y1). Возможны 4 варианта, например, (xmin, ymin, xmax, ymax)
-
“xywh” - формат (x0, y0, width, height)
-
“ccwh” - формат (x-center, y-center, width, height)
-
“yyxx” - формат (y0, y1, x0, x1)
-
Особенности ввода/вывода
-
cv3.imread вторым параметром может принимать flag: либо cv2 флаг, либо строка (‘color’, ‘gray’, ‘alpha’)
-
Поддержка pathlib при чтении/записи изображений/видео
-
Автоматическое создание папок при записи изображения или видео при необходимости (при передаче параметра mkdir=True)
-
Добавление класса Window. Он позволяет удобным образом управлять окнами, в которых отображаются изображения/видео. Также содержит в себе счётчик окон, благодаря чему необязательно задавать название окон при инициализации.
-
Инициализация видеопотоков через cv3.Video. Для чтения из source используется cv3.Video(source), для записи в файл - cv3.Video(filename, ‘w’). Под капотом классы: cv3.VideoCapture и cv3.VideoWriter соответственно, которые расширяют возможности одноименных классов cv2. Например, поддерживается контекстный менеджер (видеопотоки автоматически закрываются при выходе из блока with..as).
Особенности cv3.VideoCapture
-
cap.read() возвращает кадр либо выбрасывает исключение: OSError (ошибка чтения) или StopIteration (видео закончено)
-
Итерирования по видеопотоку чтения: реализованы методы __iter__ и __next__. Благодаря этому можно пользоваться циклом for.
-
Свойства видео хранятся в полях объекта: frame_cnt (количество кадров, также возвращается от __len__), fps, width, height. Для получения текущего номера кадра используется обновляемое свойство cap.now.
-
Перемотка на n-ый кадр осуществляется через cap.rewind(n). А для получение n-ого кадра через индексацию: cap[n].
Особенности cv3.VideoWriter
-
Не надо передавать размеры кадров при инициализации cv3.VideoWriter: инициализация “родительского” cv2.VideoWriter происходит при записи первого кадра (здесь же определяется разрешение видео)
-
Нет необходимости передавать fps и fourcc: используются значения по умолчанию: cv3.opt.FPS и cv3.opt.FOURCC. Параметр fourcc можно передавать как строку или как результат вызова cv2.VideoWriter_fourcc
-
Выбрасывание исключения OSError в случае, если видео не может быть записано
-
Валидация размеров записываемых кадров: в случае, если размеры не совпадают, то выбрасывается исключение.
Особенности параметров рисования
-
При отрисовке можно передать параметр copy=True - тогда функция вернёт копию изображения с нанесённой фигурой
-
При отрисовке фигуры нет необходимости указывать её цвет: используется заданное значение cv3.opt.COLOR, которое можно переопределить. Помимо этого, можно переопределить толщину линий фигуры через cv3.opt.THICKNESS
-
При отрисовке можно передавать дополнительные параметры color (цвет), t (толщина), line_type (тип линии)
-
Параметр color. Поддерживаются именованные цвета и относительные значения цветов. Если cv3.opt.RGB=True, то цвет ожидается в формате RGB, а иначе BGR. Если в качестве параметра цвета передана строка и cv3.opt.RGB=False, то соответствующий цвет разворачивается (RGB->BGR). Поддерживаются следующие типы значений параметра color:
-
int - целое число от 0 до 255
-
float - целое число от 0 до 255 либо относительное от 0 до 1
-
np.array/list/tuple - массив из 3 чисел, целых или относительных
-
str - название цвета. Доступные цвета доступны в cv3.COLORS
-
-
Параметр line_type. Ожидается либо cv2 флаг, либо строка: 'filled', 'line_4', 'line_8', 'line_aa'
-
При нанесении текста достаточно указать только строку текста. По умолчанию, текст будет начинаться с центра изображения (x=0.5, y=0.5). Можно указывать тип шрифта через параметр font (cv2 флаг либо строка: 'simplex', 'plain', 'duplex', 'complex', 'triplex', 'complex_small', 'script_simplex', 'script_complex', 'italic'). Параметр scale отвечает за масштаб текста, flip - за разворот текста (принимает True/False)
Особенности трансформаций
-
За интерполяцию при применении трансформаций отвечает параметр inter. Он есть в функциях cv3.transform, cv3.rotate, cv3.scale, cv3.resize и т.п. Параметр inter может принимать либо cv2 флаг, либо строку. Возможные строковые значения: 'nearest', 'linear', 'area', 'cubic', 'lanczos4'
-
За границы отвечают параметры border (тип границы) и value. Они есть в функциях cv3.transform, cv3.rotate, cv3.scale, cv3.shift, cv3.pad и т.п. В качестве border передаётся либо cv2 флаг, либо строка: 'constant', 'replicate', 'reflect', 'wrap', 'default'. value используется только когда тип границы ‘constant’. Принимает value такие же типы значений, как параметр color в функциях рисования
-
cv3.crop позволяет обрезать изображение, даже если координаты выходят за его пределы
cv2 vs cv3
Приведу сравнение кода, написанного на cv2 и cv3, для решения стандартных задач.
Чтение и запись изображений
cv2
import cv2 as cv
import sys, os
img = cv.imread(cv.samples.findFile("img.jpeg"))
if img is None:
sys.exit("Could not read the image.")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
os.makedirs('outputs', exist_ok=True)
cv.imwrite('outputs/gray.jpg', gray)
cv3
import cv3
img = cv3.imread('img.jpeg')
gray = cv3.rgb2gray(img)
cv3.imwrite('outputs/gray.jpg', gray, mkdir=True)
Рисование
cv2
import cv2 as cv
img = cv.imread(cv.samples.findFile("img.jpeg"))
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
cv.rectangle(img, (40, 30), (260, 220), (0, 0, 255), 3)
cv.putText(img, 'Parrot', (50, 25), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_8)
cv3
import cv3
img = cv3.imread('img.jpeg')
cv3.rectangle(img, 40, 30, 260, 220, color="blue", t=3)
cv3.text(img, "Parrot", 50, 25, color="white", t=2)
Чтение и запись видео
cv2
import cv2 as cv
cap = cv.VideoCapture(0)
fourcc = cv.VideoWriter_fourcc(*'XVID')
out = None
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break
if out is None:
h, w = frame.shape[:2]
out = cv.VideoWriter('output.avi', fourcc, 20.0, (w, h))
frame = cv.flip(frame, 0)
out.write(frame)
cv.imshow('frame', frame)
if cv.waitKey(1) == ord('q'):
break
cap.release()
out.release()
cv.destroyAllWindows()
cv3
import cv3
with (
cv3.Video(0) as cap,
cv3.Video('output.avi', 'w', fps=20, fourcc='XVID') as out,
cv3.Window('frame') as ww
):
for frame in cap:
frame = cv3.vflip(frame)
out.write(frame)
ww.imshow(frame)
if cv3.wait_key(1) == ord('q'):
break
Сравнение производительности
Задача |
cv2 |
cv3 |
Чтение и запись изображений |
3.21 ms ± 129 µs |
3.9 ms ± 76.3 µs |
Рисование |
1.47 ms ± 13 µs |
1.59 ms ± 16.1 µs |
Чтение и запись видео |
88.4 ms ± 3.33 ms |
103 ms ± 1.53 ms |
Результаты сравнения
Как и ожидалось, код на cv3 отрабатывает медленнее cv2, так как использует дополнительные обёртки и интерфейсы. Однако с точки зрения Python такое замедление незначительное, и в результате получается понятный код. Такой код пишется быстрее и менее подвержен ошибкам. Таким образом, cv3 позволяет снизить время на разработку и багфикс, сделать код понятнее, но при этом слегка снижает производительность.
Обзор функций
Сперва нужно установить библиотеку. Делается это следующим образом:
pip install git+https://github.com/gorodion/pycv.git
Для импортирования её в Python достаточно прописать:
import cv3
Ознакомиться с функциями и позапускать код можно в гугл колабе по ссылке
Репозиторий github здесь
По всем функциям были проведены юнит-тесты, протестировано на разных платформах, версиях python и opencv-python.
Сравнение с аналогами
Рассмотрим аналоги с cv3. При рассмотрении будем считать, что cv3 реализует все алгоритмы из OpenCV, и сравнивать по большей мере будем юзабилити
-
scikit-image. В данной библиотеке реализовано огромное число алгоритмов компьютерного зрения, однако она всё-равно уступает OpenCV по этому критерию. Также не поддерживается работа с видео.
-
PIL. Предназначена для работы с изображениями. Реализует много аналогичных функций из OpenCV. Из недостатков: работа с изображениями происходит через сущность Image, а не numpy матрицу; также не поддерживается работа с видео.
-
torchvision. Это библиотека, являющаяся дополнением к библиотеке глубокого обучения PyTorch. Она реализует большое число алгоритмов для аугментации изображений. Также здесь поддерживается работа с видео. Из недостатков то, что функции ожидают в качестве входов PIL.Image либо torch.Tensor, Помимо этого, нет гибкости при работе с видео из-за того, что библиотека завязана на задачи, связанные с глубоким обучением.
-
albumentations. Это библиотека, написанная на OpenCV, которая предназначена для аугментации изображений. В ней реализовано огромное множество функций для преобразований изображений. К плюсам можно отнести то, что поддерживаются numpy матрицы в качестве входов. Однако за счёт того, что библиотека заточена на аугментации изображений, в ней используется специфичный интерфейс, который избыточен для задач по обработке изображений. Здесь также не реализован функционал для работы с видео потоками.
-
imutils. Это серия удобных и полезных функций, написанных на cv2, для работы с изображениями. При этом, это дополнение к cv2, а не отдельная библиотека. Также там есть очень интересная функция: opencv2matplotlib, разворачивающая каналы из BGR в RGB, которой вряд ли кто-либо пользуется :)
-
SimpleCV. Также как и cv3, данная библиотека, нацелена на простоту в использовании при работе с изображениями. Однако она завязана на внутренние сущности, и в ней ограниченный функционал, по сравнению с OpenCV. Из-за этого возникает проблема негибкости библиотеки. Помимо этого, поддержка SimpleCV прекратилась в 2021 году, тогда как OpenCV обновляется по сей день. SimpleCV можно назвать хорошим инструментом для изучения компьютерного зрения, но для использования в реальных задачах он непригоден.
Таким образом, OpenCV можно считать универсальной библиотекой в области компьютерного зрения, а cv3 - удобным интерфейсом к ней, у которого на данный момент не имеется аналогов.
Подводим итоги
В данной статье был представлен новый фреймворк для обработки изображений - cv3, который аналогично cv2 реализует функции OpenCV, но делает код более питоничным. Были приведены возможности cv3, а также сравнение с аналогами и cv2 по производительности и юзабилити. Как и ожидалось, cv3 уступает cv2 по производительности, но при этом обходит его по скорости написания кода, readability; снижает количество кода и число ошибок. Таким образом, cv3 больше подходит для исследовательских задач по CV и написания скриптов по обработке и переносу изображений. В случае же, когда скорость исполнения кода важна, например, в продакшн условиях, то следует использовать cv2.
Пока реализованы основные функции, по всем ним проведены тесты.
Проект планируется развивать, постепенно будут появляться новые функции. Предстоит ещё много работы, и один я с ней точно не справлюсь. Задач много: написание и поддержка документации в readthedocs, размещение проекта на PyPi, переписывание других функций OpenCV на более удобный лад и т.д.
Надеюсь данный проект будет многим полезен и сообщество поддержит данную инициативу. Контрибутьте!
По вопросам и предложениям пишите: @gorodion
Источники
Автор:
gorodex