Обнаружение начальной и конечной точки линии в изображении (массив NumPy) - PullRequest
4 голосов
/ 14 мая 2019

У меня есть изображение, подобное следующему:

enter image description here

Я хотел бы получить координаты начальной и конечной точки каждого сегмента,На самом деле я подумал о том, чтобы учесть тот факт, что каждая крайняя точка должна иметь только одну точку, принадлежащую сегменту в его окрестности, в то время как все другие точки должны иметь как минимум 2. К сожалению, линия не имеет толщины, равной одному пикселю, поэтому это рассуждениене держит.

Ответы [ 3 ]

2 голосов
/ 14 мая 2019

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

from skimage.morphology import medial_axis
import cv2

# read the lines image
img = cv2.imread('/tmp/tPVCc.png', 0)

# get the skeleton
skel = medial_axis(img)

# skel is a boolean matrix, multiply by 255 to get a black and white image
cv2.imwrite('/tmp/res.png', skel*255)

enter image description here

См. эту страницу о методах скелетонизации в скимаге.

2 голосов
/ 15 мая 2019

Вот довольно простой способ сделать это:

  • загрузить изображение и сбросить лишний альфа-канал
  • скелет
  • ищет окрестности 3х3, у которых установлен центральный пиксель и только один

#!/usr/bin/env python3

import numpy as np
from PIL import Image
from scipy.ndimage import generic_filter
from skimage.morphology import medial_axis

# Line ends filter
def lineEnds(P):
    """Central pixel and just one other must be set to be a line end"""
    return 255 * ((P[4]==255) and np.sum(P)==510)

# Open image and make into Numpy array
im = Image.open('lines.png').convert('L')
im = np.array(im)

# Skeletonize
skel = (medial_axis(im)*255).astype(np.uint8)

# Find line ends
result = generic_filter(skel, lineEnds, (3, 3))

# Save result
Image.fromarray(result).save('result.png')

enter image description here


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

convert lines.png -alpha off -morphology HMT LineEnds result.png

Или, если вы хотите, чтобы они были числами, а не изображениями:

convert result.png txt: | grep "gray(255)"

Пример вывода

134,78: (65535)  #FFFFFF  gray(255)    <--- line end at coordinates 134,78
106,106: (65535)  #FFFFFF  gray(255)   <--- line end at coordinates 106,106
116,139: (65535)  #FFFFFF  gray(255)   <--- line end at coordinates 116,139
196,140: (65535)  #FFFFFF  gray(255)   <--- line end at coordinates 196,140

Еще один способ сделать это - использовать scipy.ndimage.morphology.binary_hit_or_miss и настроить ваши "Хиты" в качестве белых пикселей на изображении ниже и ваши «Мисс» как черные пиксели:

enter image description here

Диаграмма из превосходного материала Энтони Тиссена здесь .


Аналогично тому, как описано выше, вы можете в равной степени использовать "Hits" и "Misses" ядра выше с OpenCV , как описано здесь

morphologyEx(input_image, output_image, MORPH_HITMISS, kernel);

Я подозреваю, что это будет самый быстрый метод.


Ключевые слова : Python, изображение, обработка изображений, концы строк, концы строк, морфология, Hit или Miss, HMT, ImageMagick, фильтр.

1 голос
/ 14 мая 2019

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

Пример игрушки:

0000000
0111110
0111110
0110000
0110000
0000000

Где 0 обозначает черный, а 1 обозначает белый.

Теперь моя реализация решения:

import numpy as np
img = np.array([[0,0,0,0,0,0,0],
[0,255,255,255,255,255,0],
[0,255,255,255,255,255,0],
[0,255,255,0,0,0,0],
[0,0,0,0,0,0,0]],dtype='uint8')

def flood(arr,value):
    flooded = arr.copy()
    for y in range(1,arr.shape[0]-1):
        for x in range(1,arr.shape[1]-1):
            if arr[y][x]==255:
                if arr[y-1][x]==value:
                    flooded[y][x] = value
                elif arr[y+1][x]==value:
                    flooded[y][x] = value
                elif arr[y][x-1]==value:
                    flooded[y][x] = value
                elif arr[y][x+1]==value:
                    flooded[y][x] = value
    return flooded

ends = np.zeros(img.shape,dtype='uint64')

for y in range(1,img.shape[0]-1):
    for x in range(1,img.shape[1]-1):
        if img[y][x]==255:
            temp = img.copy()
            temp[y][x] = 127
            count = 0
            while 255 in temp:
                temp = flood(temp,127)
                count += 1
            ends[y][x] = count

print(ends)

Выход:

[[0 0 0 0 0 0 0]
 [0 5 4 4 5 6 0]
 [0 5 4 3 4 5 0]
 [0 6 5 0 0 0 0]
 [0 0 0 0 0 0 0]]

Теперь концы обозначаются позициями максимальных значений в указанном массиве (6 в данном случае).

Объяснение : Я проверяю все белые пиксели как возможные концы. Для каждого такого пикселя я «заливаю» изображение - я помещаю специальное значение (127 - отличное от 0 и отличное от 255), а затем распространяю его - на каждом шаге все 255, которые являются соседями (в фоновом режиме) Чувство Неймана) особой ценности сами становятся особыми ценностями. Я считаю шаги, необходимые для удаления всех 255. Потому что если вы начнете (постоянную скорость) затопление с конца, это займет больше времени, чем если бы у вас был источник в каком-либо другом месте, тогда максимальное время затопления - это конец вашей линии.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...