OpenCV, Машинное зрение на Python: Прямоугольные объекты. Часть 3

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

Функция для поиска прямоугольников 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()

Вместо заключения.

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