cv3 — делаем OpenCV питоничным

TL; DR cv3 — обёртка над opencv-python, которая ускоряет написание кода, не сужая функциональность

Привет, Хабр! Хочу поделиться написанным мною фреймворком на Python, который упрощает работу с OpenCV и делает его более питоничным. Погнали!

Содержание

  1. Демонстрация возможностей

  2. Мотивация

  3. Знакомьтесь, cv3

  4. Особенности cv3

  5. cv2 vs cv3

  6. Обзор функций

  7. Сравнение с аналогами

  8. Подводим итоги

  9. Источники

Демонстрация возможностей

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

Визуализация предсказаний YOLOv5

Подробнее про 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)

Визуализация детекций от YOLOv5

Визуализация детекций от YOLOv5

В данном коде происходит чтение изображения, получение предсказаний от модели и отрисовка детекций. В конце создаётся окно, на котором отображается изображение с детекциями.

Перенос изображений с процессингом

Пример кода для переноса изображений из одной папки в другую с изменением размера изображений.

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, приводятся к размеру 256×256 и сохраняются под тем же именем в папке 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. У него есть ряд преимуществ:  

  1. Высокая скорость работы алгоритмов благодаря тому, что cv2 — интерфейс к OpenCV

  2. Работа с изображениями осуществляется через numpy матрицы, что даёт возможность пользоваться методами numpy

  3. Большой функционал: возможность использования всех алгоритмов, реализованных в OpenCV, с минимальными усилиями

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

  1. Изображения считываются и записываются в BGR формате. Это особенность присуща только OpenCV, в других библиотеках работа происходит в RGB формате. Из-за этого возникают конфликты при использовании разных библиотек в одном проекте (например, связка cv2+matplotlib). Данная фича создаёт путанницу, особенно у новичков, и отвлекает от решения основной задачи. Типичная проблема, возникающая на практике: аватары вместо людей.

    0432515bb364a46dcc5dcdf663589a17.png
  1. Ошибки не обработаны должным образом. Например при чтении несуществующего файла, не вылетают ошибки, а при проблеме во время записи изображения в файл возвращается безобидное False. Зачастую очень сложно понять, с чем связана та или иная ошибка. К примеру, ошибка TypeError: Expected cv: UMat for argument, периодически возникающая при рисовании фигур

  2. Жёсткая привязка функций OpenCV к матрицам типа uint8. В целом, это хорошее решение, так как каждый пиксель должен принимать значение от 0 до 255, но из ошибок не всегда можно понять, что проблема в типе матрицы

  3. Очень много лишнего кода, который приходится каждый раз копировать со stackoverflow (если у вас не феноменальная память на странные вещи). Например, для вращения изображения в cv2 необходимо сперва получить матрицу вращения, после чего применить её к изображению.

  4. Много обязательных аргументов функций, без которых можно обойтись. Например, обязательное указание цвета линий при рисовании прямоугольника или несколько различных параметров при нанесении текста. Или при записи видео в файл необходимо передавать разрешение кадров, хотя данную информацию можно извлечь из первого записанного кадра. И в случае неправильной передачи этого аргумента, видео просто-напросто не запишется без каких-либо ошибок.

  5. Огромное множество флагов, которые нельзя уместить в голове и, соответственно, приходится гуглить каждый раз. Примеры: cv2.cv.CV_CAP_PROP_FRAME_COUNT для получения количество кадров в видео, cv2.INTER_LINEAR для указания типа интерполяции и т. д.

  6. Разные названия параметров функций, которые отвечают за одно и то же. Например, в cv2.copyMakeBorder указываются параметры borderType и value для задания границ, а в cv2.warpAffine за это отвечают borderMode и borderValue. Ещё пример: для указания типа интерполяции в cv2.resize используется параметр interpolation, а в cv2.warpAffine — flags

    1d7eb056768fd6f27062c92b10de4ec3.png

Я уже много лет работаю в области компьютерного зрения и использую cv2 в своих проектах. И каждый раз работа с этой библиотекой доставляет дискомфорт, хоть и достойного аналога у неё нет. Поэтому в один момент я решил написать обёртку над cv2, чтобы ускорить работу с этой библиотекой — cv3.

Знакомьтесь, cv3

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

Цель проекта: сделать OpenCV более питоничным и удобным в использовании, понизить порог входа в область компьютерного зрения.

Задача cv3: абстрагироваться от лишнего кода (лишних аргументов и преобразований типов), при этом сохранив всю мощь и весь функционал этой библиотеки. Таким образом, фокус идёт на решении более важных задач компьютерного зрения, чем переписывание ненужного кода, копирование его со stackoverflow и его дебаг. 

Первоначальная идея была контрибьютить в репозиторий cv2, но я быстро от неё отказался. И вот почему: в cv3 очень много принципиальных вещей, которые не соотносятся с OpenCV. Это и внутренние состояния, и добавление исключений, и новые сущности, и синонимичные названия функций, и дополнительные методы и так далее. Так что cv3 разрабатывался как интерфейс к cv2.

Отношения cv3, opencv-python и OpenCV можно провизуализировать следующим образом:

f28e3cbbffc088612d45a15b0db24191.png

Особенности 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):

    1. «xyxy» (по умолчанию) — формат (x0, y0, x1, y1). Возможны 4 варианта, например, (xmin, ymin, xmax, ymax)

    2. «xywh» — формат (x0, y0, width, height)

    3. «ccwh» — формат (x-center, y-center, width, height)

    4. «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:

    1. int — целое число от 0 до 255

    2. float — целое число от 0 до 255 либо относительное от 0 до 1

    3. np.array/list/tuple — массив из 3 чисел, целых или относительных

    4. 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

Отличие cv3 от cv2

Отличие cv3 от cv2

Приведу сравнение кода, написанного на 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

Источники

  1. pycv — github 

  2. OpenCV 

  3. YOLOv5

  4. scikit-image

  5. PIL

  6. torchvision

  7. albumentations 

  8. simpleCV

  9. matplotlib

  10. dlib

© Habrahabr.ru