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. 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. 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:
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 ". 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:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.