Открытый расчет контуров CV - PullRequest
4 голосов
/ 07 апреля 2020

Я только начинаю играть с OpenCV и обнаружил очень странное поведение в функции contourArea.

Смотрите это изображение.

Original

У него три не связанные области, слева - группа длинных штрихов, а в центре вверху - одна точка и, наконец, большой квадрат справа.

Когда я запускаю свою функцию Я получаю этот результат

Contours detected

Contour[0] Area: 221, Length: 70, Colour: Red 
Contour[1] Area: 13772, Length: 480, Colour: Green 
Contour[2] Area: 150, Length: 2370, Colour: Blue 

Хотя я фактически не посчитал площадь левой части, кажется, что она охватывает гораздо больше, чем 150 пикселей и, безусловно, будет иметь более высокое значение, чем точка в верхнем центре, я бы сказал, что точка должна быть в состоянии вписаться в левую часть по крайней мере 10 раз. Площадь квадрата действительно работает.

Square Area
width = 118
height = 116
118 * 116 = 13,688

13 688 действительно близко к тому, что opencv дал как площадь (13 772), разница, скорее всего, ошибка измерения от моего имени. Я вручную вычислил площадь точки

Dot Area
width = 27
height = 6 
27*6 = 162

Не слишком далеко от того, что OpenCV сказал, что это будет (221)

Чтение со страницы документов OpenCV на contourArea это говорит о том, что это даст неправильные результаты для контуров с самопересечениями. Не совсем понимая, что такое самопересечения, я сделал тестовое изображение.

ORIGINAL PIC HERE

Как вы видите, у меня есть прямоугольник слева и крестик в средний и другой крест повернут на 45 градусов. Я ожидаю, что площадь креста будет немного меньше, чем удвоенная площадь прямоугольника из-за перекрытия в центре.

CONTOURED PIC HERE

Contour[0] Area: 1805, Length: 423, Colour: Red 
Contour[1] Area: 947, Length: 227, Colour: Green 
Contour[2] Area: 1825, Length: 415, Colour: Blue 

Как вы Можно увидеть площадь двух крестов, чуть меньше удвоенной площади прямоугольника. Как и ожидалось.

Я не заинтересован в том, чтобы захватить внутреннюю часть квадрата или нарисовать прямоугольник вокруг фигуры слева и точки (хотя это было бы очень интересно), это не совсем то, о чем я спрашиваю в этом вопрос.

Итак, мой вопрос: почему площадь моей неправильной формы сильно недооценена?

  1. Я использую неправильную функцию?
  2. Я правильно использую правильную функцию?
  3. Нашел ли я ошибку в opencv?
  4. Имеет ли само пересечение значение, которое не было продемонстрировано в моем тесте?

Я скопировал большую часть этого кода из этого урока

Я сократил свой код этого автономного примера ниже.

def contour_test(name):
    import cv2 as cv
    colours = [{'name': 'Red ',   'bgr': (0, 0, 255)}, 
               {'name': 'Green ', 'bgr': (0, 255, 0)},
               {'name': 'Blue ',  'bgr': (255, 0, 0)}]
    src = cv.imread(cv.samples.findFile(name))
    src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    src_gray = cv.blur(src_gray, (3,3))
    threshold = 100
    canny_output = cv.Canny(src_gray, threshold, threshold * 2)
    contours, _ = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
    # Get the moments
    mu = [None for i in contours]
    for i in range(len(contours)):
        mu[i] = cv.moments(contours[i])
    # Get the mass centers
    mc = [None for i in contours]
    for i in range(len(contours)):
        mc[i] = (mu[i]['m10'] / (mu[i]['m00'] + 1e-5), mu[i]['m01'] / (mu[i]['m00'] + 1e-5))
    # Draw contours
    drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
    for i, j in enumerate(contours):
        colour = colours[i]['bgr']
        cv.drawContours(drawing, contours, i, colour, 2)
        area = int(cv.contourArea(contours[i]))
        length = int(cv.arcLength(contours[i], True))
        print('Contour[{0}] Area: {1}, Length: {2}, Colour: {3}'.format(i, area, length, colours[i]['name']))

1 Ответ

2 голосов
/ 07 апреля 2020

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

  • Не используйте cv.Canny до findContours (cv.blur также не требуется).
  • Убедитесь, что контуры белые, а не черные.
    Вы можете использовать cv.threshold с опцией cv.THRESH_BINARY_INV для инвертирования полярности.
    Рекомендуется добавить опцию cv.THRESH_OTSU для автоматов c порог.

Вы можете заменить cv.blur и cv.Canny и cv.findContours(canny_output... на:

_, src_thresh = cv.threshold(src_gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
contours, _ = cv.findContours(src_thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

Результат (верхнего изображения):

enter image description here

Contour[0] Area: 13531, Length: 476, Colour: Red
Contour[1] Area: 184, Length: 71, Colour: Green
Contour[2] Area: 4321, Length: 1202, Colour: Blue

Вот полный (обновленный) код:

import numpy as np

def contour_test(name):
    import cv2 as cv
    colours = [{'name': 'Red ',   'bgr': (0, 0, 255)}, 
               {'name': 'Green ', 'bgr': (0, 255, 0)},
               {'name': 'Blue ',  'bgr': (255, 0, 0)}]
    src = cv.imread(cv.samples.findFile(name))
    src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    #src_gray = cv.blur(src_gray, (3,3))
    #threshold = 100
    #canny_output = cv.Canny(src_gray, threshold, threshold * 2)
    #contours, _ = cv.findContours(canny_output, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

    _, src_thresh = cv.threshold(src_gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
    cv.imshow('src_thresh', src_thresh);cv.waitKey(0);cv.destroyAllWindows()  # Show src_thresh for testing
    contours, _ = cv.findContours(src_thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

    # Get the moments
    mu = [None for i in contours]
    for i in range(len(contours)):
        mu[i] = cv.moments(contours[i])
    # Get the mass centers
    mc = [None for i in contours]
    for i in range(len(contours)):
        mc[i] = (mu[i]['m10'] / (mu[i]['m00'] + 1e-5), mu[i]['m01'] / (mu[i]['m00'] + 1e-5))
    # Draw contours
    drawing = np.zeros((src_thresh.shape[0], src_thresh.shape[1], 3), dtype=np.uint8)
    for i, j in enumerate(contours):
        colour = colours[i]['bgr']
        cv.drawContours(drawing, contours, i, colour, 2)
        area = int(cv.contourArea(contours[i]))
        length = int(cv.arcLength(contours[i], True))
        print('Contour[{0}] Area: {1}, Length: {2}, Colour: {3}'.format(i, area, length, colours[i]['name']))

    cv.imshow('drawing', drawing);cv.waitKey(0);cv.destroyAllWindows()  # Show drawing for testing

contour_test('img.jpg')

Я добавил cv.imshow в двух местах для тестирования.

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