Итак, пара замечаний:
- Я уверен, что есть лучший способ сделать это (вероятно, есть библиотека, я на самом деле не проверял).
- Этот код работает только при поиске закрытых областей, таких как прямоугольники. Звезды, пончики и другие «сложные» формы не будут (полностью) найдены с помощью этого кода.
- Это находит только самую большую отдельную область, оно также возвращает список областей, вы, вероятно, можете написать что-то, что проверяет, имеет ли конкретная область правильные размеры и достаточно ли она прямоугольная
Программа работает следующим образом:
Сначала он читает изображение и определяет на пиксель, черный он или белый. Далее в строке указывается, где начинается и заканчивается белая область. После этого он объединяет области, где требуется по меньшей мере одно перекрытие пикселей, и каждая область должна находиться на последовательной линии. Осторожно, он соединяет только один регион на линию, если у вас есть, например, форма звезды, где две части соединяются в нижней точке, этот код НЕ БУДЕТ РАБОТАТЬ, и вам придется выполнить некоторые переделки (см. Ниже для пример того, что я имею в виду) . Наконец, он проверяет, какой регион является самым большим, и добавляет жирную красную линию вокруг него.
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)
Возвращение: