OpenCV. Инструменты визуализации
Вводная
Содержание курса по Open CV:- Основы работы с изображениями
- Open CV. Основы работы с видео
- Инструменты визуализации
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 битами.
Время экспериментов! Что будет, если каждый элемент нашего массива будет случайным числом от 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()
При желании можно поэкспериментировать с “примерными” коэффициентами, цветами и шрифтами.