Как найти прямоугольный контур на изображении? - PullRequest
0 голосов
/ 01 ноября 2018

В настоящее время я работаю над проектом по определению футбольных полей по спутниковому снимку.

Это спутниковое изображение вида сверху футбольного поля It's a top view satellite image of a football field

Я использовал функцию mediumblur, чтобы очистить все мелкие загрязнения на этом изображении. позже я выбрал только зеленую часть изображения и сделал маску с помощью команды cv2.inRange (hsv_img, светлый зеленый, темный зеленый) где light_green и dark_green - мой диапазон зеленого цвета в hsv. после того как я получил свою маску. Я получил это в качестве вывода:
output after getting the mask

Так как в нем есть некоторые примеси, я использовал функцию mediumblur median = cv2.medianBlur (изображение, 45) вывод, который я получил:
after using mediumblurr

Как видите, у меня много контуров и основной прямоугольный контур посередине. Мне нужен алгоритм, который выбирает такие прямоугольные контуры из изображения, и я должен пренебречь остальными. что мне делать после этого?

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

Мой подход будет:

  1. найти "квадрат" или "прямоугольник", как фигуры из входного изображения
  2. найдите тот с максимальным размером области.

Предполагая, что ввод является "медианным" результатом, как вы вычислили:

enter image description here

Среднее изображение (вход):

  1. Прежде всего, импортируйте необходимые библиотеки и очистите образ.

    import cv2
    import numpy as np
    # assuming you have the result image store in median
    # median = cv2.imread("abc.jpg", 0)
    image_gray = median 
    image_gray = np.where(image_gray > 30, 255, image_gray)
    image_gray = np.where(image_gray <= 30, 0, image_gray)
    image_gray = cv2.adaptiveThreshold(image_gray, 255,
                               cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                               cv2.THRESH_BINARY, 115, 1)
    
  2. Найдите контуры, а затем примените функцию фильтра на основе их форм.

    _, contours, _ = cv2.findContours(image_gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    rect_cnts = []
    for cnt in contours:
        peri = cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, 0.04 * peri, True)
        (x, y, w, h) = cv2.boundingRect(cnt)
        ar = w / float(h)
        if len(approx) == 4: # shape filtering condition
            rect_cnts.append(cnt)
    
  3. Найдите тот, который имеет максимальную площадь, и нарисуйте результат.

    max_area = 0
    football_square = None
    for cnt in rect_cnts:
        (x, y, w, h) = cv2.boundingRect(cnt)
        if max_area < w*h:
            max_area = w*h
            football_square = cnt
    
    # Draw the result
    image = cv2.cvtColor(image_gray, cv2.COLOR_GRAY2RGB)
    cv2.drawContours(image, [football_square], -1, (0, 0,255), 5)
    cv2.imshow("Result Preview", image)
    cv2.waitKey()
    

Предварительный просмотр результата:

enter image description here

Этот ответ взят из замечательной статьи PyImageSearch " Обнаружение формы OpenCV ". Спасибо.

0 голосов
/ 01 ноября 2018

Итак, пара замечаний:

  1. Я уверен, что есть лучший способ сделать это (вероятно, есть библиотека, я на самом деле не проверял).
  2. Этот код работает только при поиске закрытых областей, таких как прямоугольники. Звезды, пончики и другие «сложные» формы не будут (полностью) найдены с помощью этого кода.
  3. Это находит только самую большую отдельную область, оно также возвращает список областей, вы, вероятно, можете написать что-то, что проверяет, имеет ли конкретная область правильные размеры и достаточно ли она прямоугольная

Программа работает следующим образом:

Сначала он читает изображение и определяет на пиксель, черный он или белый. Далее в строке указывается, где начинается и заканчивается белая область. После этого он объединяет области, где требуется по меньшей мере одно перекрытие пикселей, и каждая область должна находиться на последовательной линии. Осторожно, он соединяет только один регион на линию, если у вас есть, например, форма звезды, где две части соединяются в нижней точке, этот код НЕ БУДЕТ РАБОТАТЬ, и вам придется выполнить некоторые переделки (см. Ниже для пример того, что я имею в виду) . Наконец, он проверяет, какой регион является самым большим, и добавляет жирную красную линию вокруг него.

enter image description here

from PIL import Image
from copy import copy


def area(lst):
    '''
    :param lst: a list of tuples where each subsequent tuple indicates a row and the first two values indicate the start and end values of the row 
    :return: the total area of the shape described by these tuples
    '''
    pixels_counted = 0
    for i in lst:
        pixels_counted += i[1] - i[0]
    return pixels_counted


def get_image(location):
    '''
    :param location: where your image is saved
    :return:
        - an Image class
        - a list of lists where everything is either a 1 (white) or 0 (black)
        - a picture class
    '''
    picture = Image.open(location)
    rgb_im = picture.convert('RGB')
    w, y = picture.size
    rgb = [[1 if sum(rgb_im.getpixel((i, j))) < 255 * 1.5 else 0 for i in range(w)] for j in range(y)]
    return picture, rgb, rgb_im


def get_borders(rgb):
    borders = []
    for i in range(len(rgb)):
        border = []
        if 0 in rgb[i]:
            start = rgb[i].index(0)
            for j in range(start, len(rgb[i])):
                if start != -1 and rgb[i][j] == 1:
                    border.append((start, j - 1, i))
                    start = -1
                if start == -1:
                    if rgb[i][j] == 0:
                        start = j
            if start != -1:
                border.append((start, j - 1, i))

        borders.append(copy(border))
    return borders


def get_rectangles(borders):
    '''
    :param borders: a list of lists, for each row it lists where an area starts or ends
    :return: a list of areas

    This function reads from the top to the bottom. it tries to group the largest areas together. This will work
    as long as these areas are relatively simple, however, if they split up (like a donut for instance) this will
    definitely raise issues.
    '''
    rectangles = []
    started = []
    for i in range(len(borders)):
        started_new = []
        started_borders = [z[1] for z in sorted([(z[1] - z[0], z) for z in borders[i]], reverse=True)]
        for region in started_borders:
            existing = False
            left = region[0]
            right = region[1]
            started_areas = [z[1] for z in sorted([(area(z), z) for z in started], reverse=True)]

            # If in the previous row an area existsed in that overlaps with this region, this region is connected to it
            for k in started_areas:
                if right < k[-1][0] or left > k[-1][1]:
                    continue
                started_new.append(k + [region])
                existing = True
                del started[started.index(k)]
            # If there was no part in the previous row that already connects to it, it will be added to the list of
            # shapes as a new area of itself
            if not existing:
                started_new.append([region])
        for k in started:
            rectangles.append(copy(k))
        started = copy(started_new)

    # Add any remaining areas to the list
    for i in started_new:
        rectangles.append(i)
    return rectangles


def get_biggest_rectangle(rectangles):
    areas = []
    for i in rectangles:
        areas.append((area(i), i))

    probable_rectangle = sorted(areas)[-1][1]
    return probable_rectangle


def show_image(rgb, rgb_im, probable_rectangle):
    # I honestly cannot figure out how to change the picture variable, so I just make a new figure
    w, y = len(rgb[0]), len(rgb)
    img = Image.new('RGB', (w, y), "black")
    pixels = img.load()

    for i in range(w):
        for j in range(y):
            pixels[i, j] = rgb_im.getpixel((i, j))  # set the colour accordingly

    for i in probable_rectangle:
        pixels[i[0], i[-1]] = (255, 0, 0)
        pixels[i[1], i[-1]] = (255, 0, 0)
        for y in range(-10, 10):
            for z in range(-10, 10):
                pixels[i[0] + y, i[-1] + z] = (255, 0, 0)
                pixels[i[1] + y, i[-1] + z] = (255, 0, 0)

    img.show()


if __name__ == '__main__':
    picture, rgb, rgb_im = get_image('C:\\Users\\Nathan\\Downloads\\stack.jpg')
    borders = get_borders(rgb)
    rectangles = get_rectangles(borders)
    probable_rectangle = get_biggest_rectangle(rectangles)
    show_image(rgb, rgb_im, probable_rectangle)

Возвращение:

enter image description here

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