如何在图像中找到矩形轮廓?

时间:2018-11-01 16:37:50

标签: python opencv image-processing

我目前正在研究一个从卫星图像识别足球场的项目。

这是足球场的顶视图卫星图像 It's a top view satellite image of a football field

我使用了模糊介质功能来清除此图像中的所有小杂质。后来我只选择了图像的绿色部分,并使用以下命令制作了蒙版 cv2.inRange(hsv_img,light_green,dark_green) 其中light_green和dark_green是我在hsv中绿色的范围。 等我戴上口罩之后 我得到这个作为输出:
output after getting the mask

因为它有一些杂质,所以我使用了中模糊功能 中位数= cv2.medianBlur(image,45) 我得到的输出是:
after using mediumblurr

如您所见,我有很多轮廓,中间有主要的矩形轮廓。我需要一种从图像中选择矩形轮廓的算法,而我不得不忽略其余的轮廓。之后我该怎么办?

2 个答案:

答案 0 :(得分:4)

我的方法是:

  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 shape detection”上的精彩文章。谢谢。

答案 1 :(得分:1)

因此,请注意:

  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