Сложное заполнение дыр в изображении

Мне нужно заполнить дыры в изображениях с помощью python. Это изображение с объектами, которое мне удалось получить — это действительно края объектов, которые я хочу, поэтому мне нужно их заполнить. введите здесь описание изображения

Казалось бы, очень просто использовать ndimage.binary_fill_holes(A), но проблема в том, что получается вот это (вручную закрашено красным цветом):

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

Но мне нужно это:

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

Как-то это можно решить?

Это первое изображение без осей, если вы хотите попробовать: введите здесь описание изображения


person Phlya    schedule 04.05.2016    source источник
comment
Хорошая проблема. Думал, вы можете решить это с помощью иерархии контуров из OpenCV, как описано здесь docs.opencv.org/3.1.0/d9/d8b/, но это не работает, поскольку критический контур не является истинным потомком. Вы можете быстро увидеть, что это не работает, используя пример файла по адресу github.com/Itseez/opencv/blob/master/samples/python/contours.py   -  person tfv    schedule 05.05.2016
comment
Спасибо, @tfv! Отрицательный результат - это тоже результат...   -  person Phlya    schedule 05.05.2016
comment
Я бы сказал, что изображение, которое вы даете, не позволяет принять решение, которое вы ищете. Существует граница (которая должна быть удалена, если ожидается, что алгоритм даст вам то, что вы хотите), что, когда вы пересекаете ее, а затем пересекаете другую, вы все еще находитесь внутри дыры, что не имеет смысла для всех. нормальные случаи.   -  person roadrunner66    schedule 05.05.2016
comment
Итак, вы хотите подсчитать количество границ, которые вы пересекаете, двигаясь от точки к краю изображения.   -  person ev-br    schedule 05.05.2016
comment
@ roadrunner66, да, именно поэтому я назвал это сложной задачей. Если бы это было легко, я бы не опубликовал этот вопрос.   -  person Phlya    schedule 05.05.2016
comment
@ev-br проблема в том, что это число будет разным в зависимости от того, в каком направлении вы решите считать.   -  person Phlya    schedule 05.05.2016
comment
Что за приложение, позвольте спросить? Я не вижу разницы между второй интерпретацией (картинка) и первой.   -  person roadrunner66    schedule 05.05.2016
comment
@roadrunner66 Опухоли, выбранные вручную на иммуногистохимических изображениях. Бывает, что бомбардир сначала выбирает всю опухоль, а затем часть ее как неопухоль. А в данном случае неопухолевая часть касалась края опухоли, и я не исключаю, что это может произойти в будущем, поэтому хотелось бы, чтобы этот случай был освещен в моем сценарии.   -  person Phlya    schedule 05.05.2016
comment
@Phlya, но паритет будет одинаковым: либо оба четные, либо оба нечетные   -  person ev-br    schedule 05.05.2016
comment
@ ev-br хорошо, это имеет смысл, но как бы вы это реализовали?   -  person Phlya    schedule 05.05.2016
comment
@Phlya Теперь, когда вопрос превратился в «дай мне код», я внезапно потерял интерес   -  person ev-br    schedule 05.05.2016
comment
@ev-br Эм, извините, честно говоря, я понятия не имею, как к этому подойти. Я не видел никаких функций в scikit-image или scipy, которые могли бы помочь (может быть, я пропустил это?). Я не код прошу, а алгоритм?..   -  person Phlya    schedule 05.05.2016


Ответы (1)


Я думаю, что нашел решение. Это немного длинно, так как у меня не хватило времени, но, возможно, это поможет. Я закодировал if только для этой проблемы, но должно быть легко обобщить его для многих изображений.

Сначала некоторые соглашения об именах:

  • Я определяю «области первого уровня» как компактные области, окруженные фоном. Такие области первого уровня могут состоять из разных подобластей.
  • Область первого уровня, состоящая более чем из одной подобласти, называется критической областью.

Моя основная идея состоит в том, чтобы сравнить длины контуров двух подобластей, которые являются частью одной критической области. Однако я сравниваю не всю их длину по контуру, а только тот участок, который близок к фону. Тот, у которого более короткий участок контура находится близко к фону, считается отверстием.

Сначала я начну с изображений результатов.

Некоторый обзор того, о чем мы говорим, с визуализацией приведенных выше соглашений об именах:

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

Две подобласти критической области. Близкие к фону два краевых сегмента каждой из областей отмечены разными цветами (очень тонкими, синим и темно-красным, но видимыми). Эти отрезки явно не идеальны ("тонкие" участки вызывают ошибки), но достаточны для сравнения их длины:

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

Окончательный результат. Если вы хотите, чтобы отверстие было «закрытым», дайте мне знать, вам просто нужно назначить исходные черные контуры областям, а не фону ([EDIT] я включил три отмеченные строки кода, которые назначают границы в регионы, как вы хотели):

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

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

Несколько заключительных замечаний: сначала я попытался выполнить сопоставление контуров, используя наборы точек, что позволит избежать циклов и позволит использовать set.intersection для определения двух сегментов контура, близких к фону, но поскольку ваши черные линии довольно толстые, контуры немного не совпадают. Я попытался скелетировать контуры, но это открыло еще одну банку червей, поэтому я работал с подходом дампа, выполняя цикл и вычисляя расстояние между точками контура. Может быть, есть более приятный способ сделать эту часть, но он работает.

Я также рассматривал возможность использования модуля Shapely, возможно, есть способы получить некоторое преимущество от него, но я не нашел, поэтому снова сбросил.

import numpy as np
import scipy.ndimage as ndimage
from matplotlib import pyplot as plt
import cv2


img= ndimage.imread('image.png')

# Label digfferentz original regions
labels, n_regions = ndimage.label(img) 
print "Original number of regions found: ", n_regions
# count the number of pixels in each region
ulabels, sizes = np.unique(labels, return_counts=True)
print sizes

# Delete all regions with size < 2 and relabel
mask_size = sizes < 2
remove_pixel = mask_size[labels]
labels[remove_pixel] = 0
labels, n_regions = ndimage.label(labels) #,s)
print "Number of regions found (region size >1): ", n_regions
# count the number of pixels in each region
ulabels, sizes = np.unique(labels, return_counts=True)
print ulabels
print sizes


# Determine large "first level" regions
first_level_regions=np.where(labels ==1, 0, 1)
labeled_first_level_regions, n_fl_regions = ndimage.label(first_level_regions)
print "Number of first level regions found: ", n_fl_regions


# Plot regions and first level regions
fig = plt.figure()
a=fig.add_subplot(2,3,1)
a.set_title('All regions')
plt.imshow(labels, cmap='Paired', vmin=0, vmax=n_regions)
plt.xticks([]), plt.yticks([]), plt.colorbar()
a=fig.add_subplot(2,3,2)
a.set_title('First level regions')
plt.imshow(labeled_first_level_regions,  cmap='Paired', vmin=0, vmax=n_fl_regions)
plt.xticks([]), plt.yticks([]), plt.colorbar()


for region_label in range(1,n_fl_regions):
    mask= labeled_first_level_regions!=region_label
    result = np.copy(labels)
    result[mask]=0    
    subregions = np.unique(result).tolist()[1:]
    print region_label, ": ", subregions

    if len(subregions) >1:
        print "   Element 4 is a critical element: ",  region_label
        print "   Subregions: ", subregions

        #Critical first level region
        crit_first_level_region=np.ones(labels.shape)
        crit_first_level_region[mask]=0

        a=fig.add_subplot(2,3,4)
        a.set_title('Crit. first level region')
        plt.imshow(crit_first_level_region, cmap='Paired', vmin=0, vmax=n_regions)
        plt.xticks([]), plt.yticks([])

        #Critical Region Contour
        im = np.array(crit_first_level_region * 255, dtype = np.uint8)
        _, contours0, hierarchy = cv2.findContours( im.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        crit_reg_contour = [contours0[0].flatten().tolist()[i:i+2] for i in range(0, len(contours0[0].flatten().tolist()), 2)]
        print crit_reg_contour
        print len(crit_reg_contour)



        #First Subregion
        mask2= labels!=subregions[1] 
        first_subreg=np.ones(labels.shape)
        first_subreg[mask2]=0

        a=fig.add_subplot(2,3,5)
        a.set_title('First subregion: '+str(subregions[0]))
        plt.imshow(first_subreg, cmap='Paired', vmin=0, vmax=n_regions)
        plt.xticks([]), plt.yticks([])        

        #First Subregion Contour
        im = np.array(first_subreg * 255, dtype = np.uint8)
        _, contours0, hierarchy = cv2.findContours( im.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        first_sub_contour = [contours0[0].flatten().tolist()[i:i+2] for i in range(0, len(contours0[0].flatten().tolist()), 2)]
        print first_sub_contour
        print len(first_sub_contour)




        #Second Subregion
        mask3= labels!=subregions[0]
        second_subreg=np.ones(labels.shape)
        second_subreg[mask3]=0

        a=fig.add_subplot(2,3,6)
        a.set_title('Second subregion: '+str(subregions[1]))
        plt.imshow(second_subreg, cmap='Paired', vmin=0, vmax=n_regions)
        plt.xticks([]), plt.yticks([])      

        #Second Subregion Contour
        im = np.array(second_subreg * 255, dtype = np.uint8)
        _, contours0, hierarchy = cv2.findContours( im.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        second_sub_contour = [contours0[0].flatten().tolist()[i:i+2] for i in range(0, len(contours0[0].flatten().tolist()), 2)]
        print second_sub_contour
        print len(second_sub_contour)   


        maxdist=6
        print "Points in first subregion close to first level contour:"
        close_1=[]
        for p1 in first_sub_contour:
            for p2 in crit_reg_contour:
                if (abs(p1[0]-p2[0])+abs(p1[1]-p2[1]))<maxdist:
                    close_1.append(p1)
                    break

        print close_1
        print len(close_1)

        print "Points in second subregion close to first level contour:"
        close_2=[]
        for p1 in second_sub_contour:
            for p2 in crit_reg_contour:
                if (abs(p1[0]-p2[0])+abs(p1[1]-p2[1]))<maxdist:
                    close_2.append(p1)
                    break

        print close_2
        print len(close_2)      


        for p in close_1:
            result[p[1],p[0]]=1

        for p in close_2:
            result[p[1],p[0]]=2


        if len(close_1)>len(close_2):
            print "first subregion is considered a hole:", subregions[0]
            hole=subregions[0]
        else:            
            print "second subregion is considered a hole:", subregions[1]
            hole=subregions[1]


        #Plot Critical region with subregions
        a=fig.add_subplot(2,3,3)
        a.set_title('Critical first level region with subregions')
        plt.imshow(result, cmap='Paired', vmin=0, vmax=n_regions)
        plt.xticks([]), plt.yticks([])

        result2=result.copy()


#Plot result
fig2 = plt.figure()
a=fig2.add_subplot(1,1,1)
a.set_title('Critical first level region with subregions and bordering contour segments')
plt.imshow(result2, cmap='flag', vmin=0, vmax=n_regions)
plt.xticks([]), plt.yticks([])


#Plot result
mask_hole=np.where(labels ==hole, True, False)
labels[mask_hole]=1
labels=np.where(labels > 1, 2, 1)

# [Edit] Next two lines include black borders into final result
mask_borders=np.where(img ==0, True, False)
labels[mask_borders]=2


fig3 = plt.figure()
a=fig3.add_subplot(1,1,1)
a.set_title('Final result')
plt.imshow(labels, cmap='flag', vmin=0, vmax=n_regions)
plt.xticks([]), plt.yticks([])


plt.show()
person tfv    schedule 06.05.2016
comment
Спасибо! Кажется, у вас получилось сделать именно то, что мне нужно! (Только контуры должны быть частью регионов, это очень важно) Я проверю это более подробно позже и, вероятно, приму ваш ответ. - person Phlya; 06.05.2016
comment
Для этого я включил три отмеченные строки и соответствующим образом изменил финальное изображение. - person tfv; 06.05.2016