Вот решение, которое, я думаю, работает так, как вы просили (хотя я согласен с @Peter Boone, что это займет некоторое время). В моей реализации есть тройной цикл, но, может быть, кто-то другой сможет сделать это быстрее!
Сначала прочитайте изображение. В моем методе значения пикселей находятся в диапазоне от 0 до 1 (а не целые числа от 0 до 255).
import urllib
import matplotlib.pyplot as plt
import numpy as np
from skimage.morphology import binary_dilation, binary_erosion, disk
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
# create a file-like object from the url
f = urllib.request.urlopen("https://i.stack.imgur.com/JXxJM.png")
# read the image file in a numpy array
# note that all pixel values are between 0 and 1 in this image
a = plt.imread(f)
Во-вторых, добавьте отступы по краям и пороговое значение изображения. Я использовал метод Оцу, но ответ @Peter Boone тоже хорошо работает.
# add black padding around image 100 px wide
a = np.pad(a, ((100,100), (100,100), (0,0)), mode = "constant")
# convert to greyscale and perform Otsu's thresholding
grayscale = rgb2gray(a)
global_thresh = threshold_otsu(grayscale)
binary_global1 = grayscale > global_thresh
# define number of pixels to expand the image
num_px_to_expand = 50
Изображение, binary_global1
- маска, которая выглядит следующим образом:
Поскольку изображение состоит из трех каналов (RGB), я обрабатываю каналы отдельно. Я заметил, что мне нужно растереть изображение на ~ 5 px, потому что внешняя сторона изображения имеет некоторые необычные цвета и узоры.
# process each channel (RGB) separately
for channel in range(a.shape[2]):
# select a single channel
one_channel = a[:, :, channel]
# reset binary_global for the each channel
binary_global = binary_global1.copy()
# erode by 5 px to get rid of unusual edges from original image
binary_global = binary_erosion(binary_global, disk(5))
# turn everything less than the threshold to 0
one_channel = one_channel * binary_global
# update pixels one at a time
for jj in range(num_px_to_expand):
# get 1 px ring of to update
px_to_update = np.logical_xor(binary_dilation(binary_global, disk(1)),
binary_global)
# update those pixels with the average of their neighborhood
x, y = np.where(px_to_update == 1)
for x, y in zip(x,y):
# make 3 x 3 px slices
slices = np.s_[(x-1):(x+2), (y-1):(y+2)]
# update a single pixel
one_channel[x, y] = (np.sum(one_channel[slices]*
binary_global[slices]) /
np.sum(binary_global[slices]))
# update original image
a[:,:, channel] = one_channel
# increase binary_global by 1 px dilation
binary_global = binary_dilation(binary_global, disk(1))
Когда я строю вывод, я получаю что-то вроде этого:
# plot image
plt.figure(figsize=[10,10])
plt.imshow(a)