Поиск контуров наибольшей и второй наибольшей площади в Python OpenCV - PullRequest
0 голосов
/ 01 августа 2020

Я работаю над сценарием Python OpenCV, который должен найти на изображении самую большую и вторую по величине формы определенного цвета c. Для этого давайте посмотрим на изображение ниже. Я хотел бы определить координаты двух ее бледно-бежевых прямоугольников:

Образец изображения

Мне удалось получить два контура, которые появляются на маске:

img_path = "path\\to\\file.png"
img = cv2.imread(img_path)
imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(imgHSV, np.array([21,7,240]), np.array([21,7,255]))
contours = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)

Приведенный выше код вернул следующий список списков:

[array([[[  8, 245]], [[  7, 246]], [[  6, 246]], [[  6, 247]], [[  5, 248]], [[  0, 248]], [[  0, 300]], [[676, 300]], [[676, 248]], [[675, 247]], [[675, 246]], [[674, 246]], [[673, 245]]], dtype=int32), 
array([[[ 19,  22]], [[ 18,  23]], [[ 18,  24]], [[ 17,  25]], [[ 17, 120]], [[ 18, 121]], [[ 18, 122]], [[ 19, 123]], [[658, 123]], [[659, 122]], [[661, 122]], [[661,  23]], [[659,  23]], [[658,  22]]], dtype=int32)]

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

Я знаю, что cv2.contourArea(c) возвращает площадь контура, а cv2.moments(c) возвращает его центроид (в обоих случаях c обозначает элемент списка contours).

Мой подход, который я пробовал, был следующим:

  1. организовать контуры и области в одном кадре данных со столбцами contour и area
  2. найдите максимальную площадь по max(df['area'])
  3. найдите соответствующий кадр данных ['contour'], площадь которого максимальна
  4. получить его центроид

Это, если бы это сработало, решило бы первую половину проблемы, то есть нахождение центроида с наибольшей площадью:

contour_area = list()
for c in contours:
    contour_area.append(cv2.contourArea(c))
    M = cv2.moments(c)
df = pd.DataFrame(
    {'contour': contours,
     'area': contour_area
    })
largest_contour = df.loc[df['area'] == max(df['area']) ]['contour']
centroid = cv2.moments(largest_contour)

Но при запуске я получил сообщение об ошибке Expected Ptr<cv::UMat> for argument 'array' в последней строке. Я быстро проверил типы данных и обнаружил, что исходные элементы contour имели тип данных <class 'numpy.ndarray'>, а мой элемент largest_contour теперь имел тип данных <class 'pandas.core.series.Series'>. Поэтому я изменил последнюю строку на:

centroid = cv2.moments(largest_contour.to_numpy())

, что теперь гарантирует, что элемент largest_contour имеет тот же тип данных (<class 'numpy.ndarray'>), что и элементы contour. Однако при повторном запуске кода я получил точно такое же сообщение об ошибке: Expected Ptr<cv::UMat> for argument 'array'.

Я был бы очень благодарен за любую помощь или подсказку о том, как двигаться дальше!

1 Ответ

1 голос
/ 02 августа 2020

Вот один из способов сделать это в Python / OpenCV.

  • Прочитать ввод
  • Преобразовать в цветовое пространство HSV
  • Порог цвета
  • Получить контуры
  • Создать список (index, area, cx, cy) для всех контуров
  • Сортировать список по площади в обратном порядке
  • Распечатать индекс, area, cx, cy значения для первых двух контуров
  • Нарисуйте два контура разными цветами
  • Сохраните результаты

Ввод:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread('rectangles2.png')

# convert to HSV
imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# threshold on color
lower=(21,7,240)
upper=(21,7,255)
thresh = cv2.inRange(imgHSV, lower, upper)

# get contours
result = img.copy() 
cntrs_info = []
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
index=0
for cntr in contours:
    area = cv2.contourArea(cntr)
    M = cv2.moments(cntr)
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])
    cntrs_info.append((index,area,cx,cy))
    index = index + 1
    #print(index,area,cx,cy)

# sort contours by area
def takeSecond(elem):
    return elem[1]
cntrs_info.sort(key=takeSecond, reverse=True)

# get index, area and centroid for first two sorted contours
index_first = cntrs_info[0][0]
area_first = cntrs_info[0][1]
cx_first = cntrs_info[0][2]
cy_first = cntrs_info[0][3]
print("index1:", index_first, "area1:", area_first, "cx1:", cx_first, "cy1:", cy_first)
cv2.drawContours(result,[contours[index_first]],0,(0,0,255),1)

index_second = cntrs_info[1][0]
area_second = cntrs_info[1][1]
cx_second = cntrs_info[1][2]
cy_second = cntrs_info[1][3]
print("index2:", index_second, "area2:", area_second, "cx2:", cx_second, "cy2:", cy_second)
cv2.drawContours(result,[contours[index_second]],0,(0,255,0),1)

# save results
cv2.imwrite('rectangles2_thresh.png',thresh)
cv2.imwrite('rectangles2_contours.png',result)

# show results
cv2.imshow("thresh", thresh)
cv2.imshow("result", result)

cv2.waitKey(0)
cv2.destroyAllWindows()

Threshold image:

enter image description here

Largest Two Contours:

введите описание изображения здесь

Текстовые данные:

index1: 1 area1: 65033.0 cx1: 338 cy1: 71
index2: 0 area2: 37568.0 cx2: 339 cy2: 272

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...