[英]how to find a rectangular contour in an image?
I am currently working on a project of identifying football fields from a satellite image. 我目前正在开展一项从卫星图像识别足球场的项目。
It's a top view satellite image of a football field 这是足球场的顶视图卫星图像
I used mediumblur function to clear all the small impurities in this image. 我使用mediumblur函数清除了此图像中的所有小杂质。 later I selected only green part of the image and made a mask using the command cv2.inRange(hsv_img, light_green, dark_green) where light_green and dark_green is my range of green in hsv.
后来我只选择了图像的绿色部分,并使用命令cv2.inRange(hsv_img,light_green,dark_green)作了蒙版,其中light_green和dark_green是我在hsv中绿色的范围。 after I got my mask.
等我戴上口罩之后 I got this as an output:
我得到这个作为输出:
Since it has some impurities, i used mediumblur function median = cv2.medianBlur(image, 45) the output that I got is: 由于它包含一些杂质,因此我使用了mediumblur函数,其中位数= cv2.medianBlur(image,45),我得到的输出是:
As you can see, I have got many contours and the main rectangular contour in the middle. 如您所见,我有很多轮廓,中间有主要的矩形轮廓。 I need an algorithm which selects such rectangular contours from the image and I have to neglect the rest.
我需要一种从图像中选择矩形轮廓的算法,而我不得不忽略其余的轮廓。 what should I do after this?
之后该怎么办?
My approach would be: 我的方法是:
Assuming the input is the "median" result as you have computed: 假设输入是您计算出的“中位数”结果:
Median Image (Input): 中位数图片(输入):
First of all, import necessary libraries and purify the image. 首先,导入必要的库并净化图像。
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)
Find the contours and then apply filter function based on their shapes. 找到轮廓,然后根据其形状应用过滤器功能。
_, 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)
Find the one with the maximum area, and draw the result. 找到面积最大的一个,然后绘制结果。
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()
Result Preview: 结果预览:
This answer is derived from the great article at PyImageSearch " OpenCV shape detection ". 这个答案来自PyImageSearch上的一篇很棒的文章“ OpenCV形状检测 ”。 Thanks.
谢谢。
So, a couple notes: 因此,请注意:
The program works as following: 该程序的工作原理如下:
First it reads the image and determines per pixel wether it is black or white. 首先,它读取图像并确定每个像素是黑色还是白色。 Next, it reads where a white region starts and ends, per line.
接下来,它按行读取白色区域的开始和结束位置。 After this, it acclomerates regions where at least one pixel overlap is required and each region has to be on a consecutive line.
此后,它会聚集需要至少一个像素重叠且每个区域必须在连续线上的区域。 Watch out, it only connects one region per line, if you have, for instance, a star shape where two parts connect at a lower point this code WILL NOT WORK, and you will have to do some rework (see below for an example of what I exactly mean) .
请注意,它仅连接每行一个区域,例如,如果您有一个星形,其中两个部分在较低点连接,则此代码将无法正常工作,您将需要进行一些重做(请参见以下示例)我的意思是) 。 Lastly, it checks what region is the largest and adds a fat red line around it.
最后,它检查哪个区域最大,并在其周围添加一条红色的粗线。
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)
Returning: 返回:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.