OpenCV. Инструменты визуализации

Вводная

Содержание курса по Open CV:
  1. Основы работы с изображениями
  2. Open CV. Основы работы с видео
  3. Инструменты визуализации
Добро пожаловать на третью часть курса по Открытой Библиотеке Машинного Зрения. Здесь разберемся в структуре основного объекта OpenCV - изображения и научимся накладывать на кадр объекты интерфейса

NumPy

NumPy для Питона устанавливали здесь вместе с OpenCV.
NumPy - это самобытная библиотека, позволяющая удобно работать с большими структурированными объемами данных т.е. массивами, которые могут быть как одномерными
[1, “a”, “qwerty’]
так и двух- и трех- и еще более многомерными.
Так как объект нашего курса - изображение, то применение NumPy здесь в основном следующее: изображение - это матрица пикселей (двумерный массив), а каждый пиксель представляется тремя цветами (для цветного изображения).
Один пиксель:
[R,G,B]
Строка изображения:
[ [R,G,B],[R,G,B],[R,G,B],[R,G,B], ... ]
Все изображение:
[
[ [R,G,B],[R,G,B],[R,G,B],[R,G,B], ... ],
[ [R,G,B],[R,G,B],[R,G,B],[R,G,B], ... ],
[ [R,G,B],[R,G,B],[R,G,B],[R,G,B], ... ],

[ [R,G,B],[R,G,B],[R,G,B],[R,G,B], ... ]
]

При квадратном изображении со стороной в n пикселей имеем трехмерный массив n*n*3. NumPy даёт нам возможность создать эту структуру одной строкой:
import numpy as np
import cv2
img = np.zeros(shape=(512,512,3), dtype=np.uint8)
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
Функция zeros() создает массив, заполненный нулями согласно аргументам:
  • shape - размерность - в нашем случае это матрица 512 на 512, в которой каждый элемент - это тоже массив из 3х элементов
  • dtype - тип данных, необязательный аргумент - по умолчанию применяет float (число с плавающей точкой типа 39.0), но мы указываем 8 бит значение, так как каждый из трех цветов (Red, Green, Blue) имеет градацию от 0 до 255 - это 256 значений - 2 в восьмой степени т.е. значение, кодируемое 8 битами.
Записанная в переменную img матрица как раз является стандартным представлением изображения в OpenCV, поэтому без проблем выводим черный квадрат на экран в новом окне:
Время экспериментов! Что будет, если каждый элемент нашего массива будет случайным числом от 0 до 255? Это легко проверить:
import numpy as np
import cv2
img = np.random.randint(255, size=(512, 512, 3), dtype=np.uint8)
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()

Конечно, есть вероятность, что на экран выведется пейзаж заката на Марсе, но на деле всегда получается просто белый шум:

Уроки рисования

Наверняка вы видели примеры работы OpenCV следующего вида:
В данном случае демонстрируется функция Распознавания объектов. Забегая вперед скажу, что в коде это представляет собой тоже буквально одну функцию, в которую аргументами передаются кадр и файл обученного алгоритма, однако наносить на выводимое изображение красивые рамки и надписи придется отдельно. Посмотрим как это делается.

Линия

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

Новая линия задается функцией объекта OpenCV:
cv2.line(img,(0,0),(511,511),(255,0,0),50)
в которую по порядку передаем следующие аргументы:
  • img - переменная с OpenCV-изображением, в нее же запишется результат выполнения
  • (0,0) - координаты начала
  • (511,511) - координаты конца
  • (255,0,0) - цвет, по умолчанию используется формат BGR, то есть Blue=255, а все остальные цвета нулевые - это будет чисто синий цвет
  • 50 - толщина линии в пикселях
import numpy as np
import cv2
img = np.zeros((512,512,3), np.uint8)
cv2.line(img,(0,0),(511,511),(255,0,0),50)
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()

Вот так незаметно прошла первая встреча с новым типом данных в Python - кортежем (tuple). Передача аргументов координат в функцию cv2.line() осуществляется массивами в круглых скобках (0,0) - это и есть кортежи. Как и обычные массивы кортежи могут быть многомерными и содержать в себе любые типы данных, а основных отличий у них два:
  • кортеж нельзя изменять, только читать
  • кортеж занимает меньше оперативной памяти:

Прямоугольник

Строится функцией rectangle():
cv2.rectangle(img,(184,50),(410,128),(0,255,0),3)
Со следующими аргументами:
  • img - переменная с изображением-полотном
  • координаты левой верхней точки
  • координаты правой нижней точки
  • цвет
  • толщина линий, при отрицательном значении фигура закрашивается цветом линии
import numpy as np
import cv2
img = np.zeros((512,512,3), np.uint8)
cv2.rectangle(img,(40,20),(210,90),(0,255,0),5)
cv2.rectangle(img,(250,30),(450,180),(0,255,0),-1)
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()


Окружность

Строится функцией circle():
cv2.circle(img,(447,63), 63, (255,0,0), -1)

Со следующими аргументами:
  • img - переменная с изображением-полотном
  • (447,63) - координаты центра окружности
  • 63 - радиус окружности
  • (255,0,0) - цвет
  • -1 - заливка цветом окружности или толщина линии (>0)
import numpy as np
import cv2
img = np.zeros((512,512,3), np.uint8)
cv2.rectangle(img,(250,30),(450,180),(0,255,0),-1)
cv2.circle(img,(447,63), 63, (255,0,0), -1)
cv2.circle(img,(300,63), 63, (0,0,255), 0)
cv2.circle(img,(100,63), 63, (0,255,0), 20)
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
На примере заметно, что при пересечении на заднем плане остаются фигуры, построенные первее.

Текст

Выводится функцией putText():
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV',(10,400), font, 4,(255,255,255),3,cv2.LINE_AA)
Со следующими аргументами:
  • img - переменная с изображением-полотном
  • 'OpenCV' - текст, который необходимо вывести
  • (10,400) - координаты левой нижней точки курсора, откуда начнет печататься текст
  • font - переменная с выбранным шрифтом
  • 4 - размер шрифта
  • (255,255,255) - цвет
  • 3 - толщина линии
  • cv2.LINE_AA - алгоритм отображения линии, кроме этого существуют LINE_4 и LINE_8 дающее худшее качество кривых линий
Выбор шрифтов не слишком разнообразен:
  • FONT_HERSHEY_SIMPLEX
  • FONT_HERSHEY_PLAIN
  • FONT_HERSHEY_DUPLEX
  • FONT_HERSHEY_COMPLEX
  • FONT_HERSHEY_TRIPLEX
  • FONT_HERSHEY_COMPLEX_SMALL
  • FONT_HERSHEY_SCRIPT_SIMPLEX
  • FONT_HERSHEY_SCRIPT_COMPLEX

import numpy as np
import cv2
img = np.zeros((512,512,3), np.uint8)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV',(10,400), font, 4,(255,255,255),3,cv2.LINE_AA)
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()



Функции в программировании

Это объединения повторяющихся участков кода в единый неразрывный блок из нескольких строк в одном месте для удобного обращения к ним.
Рисуя фигуры мы как раз обращались к функциям, только их код находится в одном из файлов библиотеки, которую импортируем в начале скрипта.
Объявление новой функции происходит следующим образом:
def <название_без_пробелов>(<аргументы>):
Аргументов может и не быть, в этом случае остаются пустые скобки с двоеточием. Следующая строка после объявления функции пишется с отступом, увеличенным на единицу.
В Python окончание функции никак не обозначается, вместо этого идет пустая строка, и далее код продолжаем писать с отступом, какой был у строки объявления функции.
Переходим к практике - напишем функцию, выводящую рамки с надписями как в примере с распознаванием автомобилей. Назовем ее rect (rectangle - прямоугольник), а передавать ей будем следующие аргументы:
  • img - переменную с изображением-полотном
  • координату Х левого верхнего угла прямоугольника
  • координату Y левого верхнего угла прямоугольника
  • ширину
  • высоту
  • надпись (не будет являться обязательным аргументом)
Чтоб сделать аргумент необязательным при объявлении новой функции следует сразу задать его значение по умолчанию - в нашем случае это пустая строка. Если же при вызове функции этот аргумент будет указан, то его значение просто перепишется поверх дефолтного.
def rect(img,x,y,w,h,text=""):

Далее нарисуем сам прямоугольник, благо все для этого у нас есть: полотно и координаты. Цвет и толщину рамки не позволим выбирать вообще - для упрощения, а то аргументов и так немало.

Координаты. Функция отрисовки требует ввода координат углов прямоугольника: с первым все понятно (x,y), а второй получим прибавив полученные размеры к координатам левого верхнего угла: (x+w,y+h).
 cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

Готово, уже можно пробовать рисовать рамки в промышленных масштабах только что написанной упрощенной функцией:
import numpy as np
import cv2
def rect(img,x,y,w,h,text=""):
 cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
img = np.zeros((512,512,3), np.uint8)
rect(img,10,200,100,50,"")
rect(img,200,150,300,150,"")
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()

Текст. Является необязательным аргументом, поэтому дальше будем действовать через условие.
 if text != "":

Если полученный аргумент не равен дефолтному (пустой строке) - попадаем в тело условия, где рисуем текст, иначе пропускаем это действие.
Располагать текст будет над рамкой слева, и прежде необходимо вывести фон, на котором текст будет хорошо читаться. Сделаем это с помощью того же прямоугольника, только на этот раз полностью закрасим его площадь. Но еще нужно получить его размеры:
Хнач равен этой же координате основной рамки: х
Yнач будет равен этой же координате основной рамки с вычетом высоты шрифта y-22
Хконц равен Хнач с прибавлением длины текста: x+l
Yконц высоте начала основной рамки, то есть y
Таким образом фон текста будем выводить командой:
 cv2.rectangle(img,(x,y-22),(x+l,y),(0,255,0),-1)

Длину текста l вычислим получив количество символов стандартной функцией Питона len(), умножив полученное на примерную длину одного символа:
 l = len(text)*14

Остается поверх фона вывести сам текст. Координатами для текста как раз послужат начальные координаты основной рамки:
 font = cv2.FONT_HERSHEY_SIMPLEX
 cv2.putText(img,text,(x,y), font, 0.8,(0,0,0),2,cv2.LINE_AA)


Вот теперь весь функционал функции rect(img,x,y,w,h,text="") готов к проверке:
import numpy as np
import cv2
def rect(img,x,y,w,h,text=""):
 cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
 if text != "":
 l = len(text)*14
 cv2.rectangle(img,(x,y-22),(x+l,y),(0,255,0),-1)
 font = cv2.FONT_HERSHEY_SIMPLEX
 cv2.putText(img,text,(x,y), font, 0.8,(0,0,0),1,cv2.LINE_AA)
img = np.zeros((512,512,3), np.uint8)
rect(img,10,200,100,50,"test")
rect(img,200,150,300,150,"Border")
rect(img,10,30,400,70,"Very very long frame")
cv2.imshow('frame', img)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()

При желании можно поэкспериментировать с “примерными” коэффициентами, цветами и шрифтами.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.