Обработка изображения - заполнение пустых кружков

У меня есть двоичные черно-белые изображения, которые выглядят так

введите здесь описание изображения

Я хочу заполнить эти белые круги сплошными белыми дисками. Как я могу сделать это в Python, предпочтительно используя skimage?


person Baron Yugovich    schedule 17.08.2018    source источник


Ответы (3)


Выполните морфологическое закрытие (explanation), чтобы заполнить эти крошечные пробелы, чтобы завершить круги. Затем заполните результирующее бинарное изображение.

Код :

from skimage import io
from skimage.morphology import binary_closing, disk
import scipy.ndimage as nd
import matplotlib.pyplot as plt

# Read image, binarize
I = io.imread("FillHoles.png")
bwI =I[:,:,1] > 0

fig=plt.figure(figsize=(24, 8))

# Original image
fig.add_subplot(1,3,1)
plt.imshow(bwI, cmap='gray')

# Dilate -> Erode. You might not want to use a disk in this case,
# more asymmetric structuring elements might work better
strel = disk(4)
I_closed = binary_closing(bwI, strel)

# Closed image
fig.add_subplot(1,3,2)
plt.imshow(I_closed, cmap='gray')

I_closed_filled = nd.morphology.binary_fill_holes(I_closed)

# Filled image
fig.add_subplot(1,3,3)
plt.imshow(I_closed_filled, cmap='gray')

Результат :

Рисунок, изображающий различные морфологические операции

Обратите внимание, как мусор сегментации слился с вашим объектом в правом нижнем углу, а небольшая накидка в нижней части среднего объекта была закрыта. Возможно, вы захотите продолжить морфологическую эрозию или открытие после этого.

РЕДАКТИРОВАТЬ: Длинный ответ на комментарии ниже

Диск (4) был просто примером, который я использовал для получения результатов, показанных на изображении. Вам нужно будет найти подходящее значение самостоятельно. Слишком большое значение приведет к тому, что маленькие объекты будут сливаться с более крупными объектами рядом с ними, как в правом кластере на изображении. Это также закроет промежутки между объектами, хотите вы этого или нет. Слишком маленькое значение приведет к тому, что алгоритм не сможет завершить круги, поэтому операция заполнения не будет выполнена.

Морфологическая эрозия сотрет зону размером с элемент структурирования с границ объектов. Морфологическое открытие — это операция, обратная закрытию, поэтому вместо расширения->размывания будет происходить размывание->расширение. Чистый эффект открытия заключается в том, что все объекты и накидки меньшего размера, чем структурирующий элемент, исчезнут. Если вы сделаете это после заполнения, то большие объекты останутся относительно такими же. В идеале он должен удалить множество артефактов сегментации, вызванных морфологическим закрытием, которое я использовал в примере кода, которое может иметь или не иметь отношение к вам в зависимости от вашего приложения.

person Tapio    schedule 17.08.2018
comment
Не могли бы вы рассказать немного о морфологической эрозии и открытии, что они делают, для чего я могу их использовать? Кроме того, не знаю, о каком нижнем правом объекте вы говорите, не могли бы вы обозначить его на исходном изображении красным или чем-то еще, чтобы я мог его увидеть? - person Baron Yugovich; 17.08.2018
comment
Кроме того, вы используете жестко закодированный диск (4), это вообще будет работать? - person Baron Yugovich; 17.08.2018
comment
Я только что попробовал это на обычном изображении, но он не обнаруживает все круги. Как мне решить эту проблему, может быть, я зациклюсь и попробую разные значения для r на диске (r)? - person Baron Yugovich; 17.08.2018

Вы можете обнаружить круги с помощью методов skimage hough_circle и hough_circle_peaks, а затем нарисовать их, чтобы «заполнить» их.

В следующем примере большая часть кода выполняет «иерархические» вычисления для наиболее подходящих кругов, чтобы избежать рисования кругов, которые находятся один внутри другого:

# skimage version 0.14.0

import math
import numpy as np
import matplotlib.pyplot as plt

from skimage import color
from skimage.io import imread
from skimage.transform import hough_circle, hough_circle_peaks
from skimage.feature import canny
from skimage.draw import circle
from skimage.util import img_as_ubyte

INPUT_IMAGE = 'circles.png' # input image name
BEST_COUNT = 6              # how many circles to draw
MIN_RADIUS = 20             # min radius should be bigger than noise
MAX_RADIUS = 60             # max radius of circles to be detected (in pixels)
LARGER_THRESH = 1.2         # circle is considered significantly larger than another one if its radius is at least so much bigger
OVERLAP_THRESH = 0.1        # circles are considered overlapping if this part of the smaller circle is overlapping

def circle_overlap_percent(centers_distance, radius1, radius2):
    '''
    Calculating the percentage area overlap between circles
    See Gist for comments:
        https://gist.github.com/amakukha/5019bfd4694304d85c617df0ca123854
    '''
    R, r = max(radius1, radius2), min(radius1, radius2)
    if centers_distance >= R + r:
        return 0.0
    elif R >= centers_distance + r:
        return 1.0
    R2, r2 = R**2, r**2
    x1 = (centers_distance**2 - R2 + r2 )/(2*centers_distance)
    x2 = abs(centers_distance - x1)
    y = math.sqrt(R2 - x1**2)
    a1 = R2 * math.atan2(y, x1) - x1*y
    if x1 <= centers_distance:
        a2 = r2 * math.atan2(y, x2) - x2*y
    else:
        a2 = math.pi * r2 - a2
    overlap_area = a1 + a2
    return overlap_area / (math.pi * r2)

def circle_overlap(c1, c2):
    d = math.sqrt((c1[0]-c2[0])**2 + (c1[1]-c2[1])**2)
    return circle_overlap_percent(d, c1[2], c2[2])

def inner_circle(cs, c, thresh):
    '''Is circle `c` is "inside" one of the `cs` circles?'''
    for dc in cs:
        # if new circle is larger than existing -> it's not inside
        if c[2] > dc[2]*LARGER_THRESH: continue
        # if new circle is smaller than existing one...
        if circle_overlap(dc, c)>thresh:
            # ...and there is a significant overlap -> it's inner circle
            return True
    return False

# Load picture and detect edges
image = imread(INPUT_IMAGE, 1)
image = img_as_ubyte(image)
edges = canny(image, sigma=3, low_threshold=10, high_threshold=50)

# Detect circles of specific radii
hough_radii = np.arange(MIN_RADIUS, MAX_RADIUS, 2)
hough_res = hough_circle(edges, hough_radii)

# Select the most prominent circles (in order from best to worst)
accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii)

# Determine BEST_COUNT circles to be drawn
drawn_circles = []
for crcl in zip(cy, cx, radii):
    # Do not draw circles if they are mostly inside better fitting ones
    if not inner_circle(drawn_circles, crcl, OVERLAP_THRESH):
        # A good circle found: exclude smaller circles it covers
        i = 0
        while i<len(drawn_circles):
            if circle_overlap(crcl, drawn_circles[i]) > OVERLAP_THRESH:
                t = drawn_circles.pop(i)
            else:
                i += 1
        # Remember the new circle
        drawn_circles.append(crcl)
    # Stop after have found more circles than needed
    if len(drawn_circles)>BEST_COUNT:
        break

drawn_circles = drawn_circles[:BEST_COUNT]

# Actually draw circles
colors  = [(250, 0, 0), (0, 250, 0), (0, 0, 250)]
colors += [(200, 200, 0), (0, 200, 200), (200, 0, 200)]
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
image = color.gray2rgb(image)
for center_y, center_x, radius in drawn_circles:
    circy, circx = circle(center_y, center_x, radius, image.shape)
    color = colors.pop(0)
    image[circy, circx] = color
    colors.append(color)

ax.imshow(image, cmap=plt.cm.gray)
plt.show()

Результат:

Обнаруженные круги

person Andriy Makukha    schedule 18.08.2018

Я не знаю skimage, но если бы вы использовали OpenCv, я бы сделал преобразование Хафа для кругов, а затем просто нарисовал бы их.

Преобразование Хафа является надежным, если в кругах есть небольшие отверстия, это не проблема.

Что-то типа:

circles = cv2.HoughCircles(gray, cv2.cv.CV_HOUGH_GRADIENT, 1.2, 100)

# ensure at least some circles were found
if circles is not None:
    # convert the (x, y) coordinates and radius of the circles to integers
    circles = np.round(circles[0, :]).astype("int")

    # loop over the (x, y) coordinates and radius of the circles
    # you can check size etc here.
    for (x, y, r) in circles:
        # draw the circle in the output image
        # you can fill here.
        cv2.circle(output, (x, y), r, (0, 255, 0), 4)

    # show the output image
    cv2.imshow("output", np.hstack([image, output]))
    cv2.waitKey(0)

Подробнее см. здесь: https://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/

person RobAu    schedule 17.08.2018