В прошлой статье мы разобрались с контурами, теперь перейдем более сложной задачи : Поиску прямоугольных и круглых объектов на фото. А так же будем определять угол их него наклона. Применения такого алгоритма может быть например : когда манипулятору надо захватить объект.
Функция для поиска прямоугольников minAreaRect()
Данная функция пытается найти прямоугольник максимального размера, который может вписаться в заданный замкнутый контур. Надо заметить, что эта функция не определяет является ли контур прямоугольным, она пытается вписать в него прямоугольник оптимальным способом. Это важно!
minAreaRect( контур )
Если не знаете что такое контур в OpenCV и откуда его взять : тык-тык . Напишем программу, которая найдет на картинке все контуры в которые можно вписать прямоугольник :
import numpy as np import cv2 as cv hsv_min = np.array((98, 0, 142), np.uint8) hsv_max = np.array((234, 155, 255), np.uint8) if __name__ == '__main__': fn = '345.jpg' # имя файла, который будем анализировать img = cv.imread(fn) hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) # меняем цветовую модель с BGR на HSV thresh = cv.inRange(hsv, hsv_min, hsv_max) # применяем цветовой фильтр contours0, hierarchy = cv.findContours(thresh.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) cv.imshow('filter', thresh) # перебираем все найденные контуры в цикле for cnt in contours0: rect = cv.minAreaRect(cnt) # пытаемся вписать прямоугольник box = cv.boxPoints(rect) # поиск четырех вершин прямоугольника box = np.int0(box) # округление координат cv.drawContours(img, [box], -1, (255, 0, 0), 0) # рисуем прямоугольник cv.imshow('contours', img) # вывод обработанного кадра в окно cv.waitKey() cv.destroyAllWindows()

Не очень удачная фотка, зато можно понять алгоритм работы данной функции.Так же алгоритм вписывает треугольники во всем найденные контуры. Немного пояснений к коду:
- cv.boxPoints(rect) — Функция находит четыре вершины повернутого прямоугольника. Эта функция полезна для рисования прямоугольника.
- np.int0(box) — преобразуем массив в формат int64 .
>>> import numpy
>>> numpy.int0 is numpy.int64
True
Функция для поиска эллипсов fitEllipse()
Собственно эта функция работает так же как и minAreaRect() . Пытается вписать круг во все найденные контуры :
import numpy as np import cv2 as cv hsv_min = np.array((0, 77, 17), np.uint8) hsv_max = np.array((208, 255, 255), np.uint8) fn = '173442.jpg' img = cv.imread(fn) hsv = cv.cvtColor( img, cv.COLOR_BGR2HSV ) thresh = cv.inRange( hsv, hsv_min, hsv_max ) contours0, hierarchy = cv.findContours( thresh.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) for cnt in contours0: if len(cnt)>4: ellipse = cv.fitEllipse(cnt) cv.ellipse(img,ellipse,(0,0,255),2) cv.imshow('contours', img) cv.waitKey() cv.destroyAllWindows()

Условие
if len(cnt)>4:
необходимо для того, чтобы отсечь контуры с контурами меньше 5 точек. Так же мы можем поступить с прямоугольниками , посчитать площадь и отсечь маленькие :
box = np.int0(box) # округление координат area = int(rect[1][0]*rect[1][1]) # вычисление площади if area > 500: cv.drawContours(img,[box],0,(255,0,0),2)
Вычисление угла поворота прямоугольника в OpenCV .
Здесь нам не потребуется специальные функции , одна лишь математика :
import numpy as np import cv2 as cv import math hsv_min = np.array((86, 11, 0), np.uint8) hsv_max = np.array((132, 255, 255), np.uint8) color_blue = (255, 0, 0) color_yellow = (0, 255, 255) if __name__ == '__main__': fn = '34562.jpg' # имя файла, который будем анализировать img = cv.imread(fn) hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) # меняем цветовую модель с BGR на HSV thresh = cv.inRange(hsv, hsv_min, hsv_max) # применяем цветовой фильтр contours0, hierarchy = cv.findContours(thresh.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) # перебираем все найденные контуры в цикле for cnt in contours0: rect = cv.minAreaRect(cnt) # пытаемся вписать прямоугольник box = cv.boxPoints(rect) # поиск четырех вершин прямоугольника box = np.int0(box) # округление координат center = (int(rect[0][0]), int(rect[0][1])) area = int(rect[1][0] * rect[1][1]) # вычисление площади # вычисление координат двух векторов, являющихся сторонам прямоугольника edge1 = np.int0((box[1][0] - box[0][0], box[1][1] - box[0][1])) edge2 = np.int0((box[2][0] - box[1][0], box[2][1] - box[1][1])) # выясняем какой вектор больше usedEdge = edge1 if cv.norm(edge2) > cv.norm(edge1): usedEdge = edge2 reference = (1, 0) # горизонтальный вектор, задающий горизонт # вычисляем угол между самой длинной стороной прямоугольника и горизонтом angle = 180.0 / math.pi * math.acos((reference[0] * usedEdge[0] + reference[1] * usedEdge[1]) / (cv.norm(reference) * cv.norm(usedEdge))) if area > 100: cv.drawContours(img, [box], 0, (255, 0, 0), 2) # рисуем прямоугольник cv.circle(img, center, 5, color_yellow, 2) # рисуем маленький кружок в центре прямоугольника # выводим в кадр величину угла наклона cv.putText(img, "%d" % int(angle), (center[0] + 20, center[1] - 20), cv.FONT_HERSHEY_SIMPLEX, 1, color_yellow, 2) cv.imshow('contours', img) cv.waitKey() cv.destroyAllWindows()

Вместо заключения.
Итак, мы умеем определять угол наклона прямоугольника. Плохая новость — мы не можем быть уверены, что в кадре именно прямоугольник. Та же ситуация с эллипсом.