Как использовать контуры Opencv для однонаправленного описания точек линий - PullRequest
6 голосов
/ 25 марта 2020

Я использую opencvs findContour, чтобы найти точки для описания изображения, состоящего из линий (не многоугольников), как: cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);.

1 Ответ

8 голосов
/ 29 марта 2020

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

Редактировать:

Согласно запрос OP, строки должны быть шириной 1 пиксель. Для этого перед нахождением подключенных компонентов применяется операция прореживания. Также добавлены изображения шагов.

Обратите внимание, что точки каждого подключенного компонента сортируются в порядке возрастания y-шнуров.

img_path = "D:/_temp/fig.png"
output_dir = 'D:/_temp/'

img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)

_, img = cv2.threshold(img, 128, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)

total_white_pixels = cv2.countNonZero(img)
print ("Total White Pixels Before Thinning = ", total_white_pixels)

cv2.imwrite(output_dir + '1-thresholded.png', img)

#apply thinning -> each line is one-pixel wide
img = cv2.ximgproc.thinning(img)
cv2.imwrite(output_dir + '2-thinned.png', img)

total_white_pixels = cv2.countNonZero(img)
print ("Total White Pixels After Thinning = ", total_white_pixels)

no_ccs, labels = cv2.connectedComponents(img)

label_pnts_dic = {}

colored = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

i = 1 # skip label 0 as it corresponds to the backgground points
sum_of_cc_points = 0 
while i < no_ccs:
    label_pnts_dic[i] = np.where(labels == i) #where return tuple(list of x cords, list of y cords)
    colored[label_pnts_dic[i]] = (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255))
    i +=1

cv2.imwrite(output_dir + '3-colored.png', colored)    


print ("First ten points of label-1 cc: ")
for i in range(10):
    print ("x: ", label_pnts_dic[1][1][i], "y: ", label_pnts_dic[1][0][i])

Вывод:

Total White Pixels Before Thinning =  6814
Total White Pixels After Thinning =  2065
First ten points of label-1 cc: 
x:  312 y:  104
x:  313 y:  104
x:  314 y:  104
x:  315 y:  104
x:  316 y:  104
x:  317 y:  104
x:  318 y:  104
x:  319 y:  104
x:  320 y:  104
x:  321 y:  104

Изображения :

1.На пороге

enter image description here

Разбавленный

enter image description here

Цветные компоненты

enter image description here

Edit2:

После обсуждения с OP, I понял, что наличие списка (рассеянных) точек недостаточно. Точки должны быть упорядочены так, чтобы их можно было отслеживать. Для достижения этой новой логики c должен быть введен после применения прореживания к изображению.

  1. Поиск крайних точек (точек с одним 8-соседним соединением)
  2. Поиск точек подключения ( точки с 3-сторонней связью)
  3. Поиск простых точек (все остальные точки)
  4. Начинайте трассировку с крайней точки до достижения другой крайней точки или точки соединения.
  5. Извлечение пройденный путь.
  6. Проверьте, не превратилась ли точка соединения в простую точку, и обновите ее статус.
  7. Повтор
  8. Проверьте, есть ли замкнутые контуры простых точек которые не были достигнуты с какой-либо крайней точки, извлеките каждую замкнутую l oop в качестве дополнительной путевой точки.

Код для классификации экстремальных / соединительных / простых точек

def filter_neighbors(ns):    
    i = 0
    while i < len(ns):
        j = i + 1
        while j < len(ns):
            if (ns[i][0] == ns[j][0] and abs(ns[i][1] - ns[j][1]) <= 1) or (ns[i][1] == ns[j][1] and abs(ns[i][0] - ns[j][0]) <= 1):
                del ns[j]
                break                                    
            j += 1
        i += 1    

def sort_points_types(pnts):
    extremes = []
    connections = []
    simple = []

    for i in range(pnts.shape[0]):
        neighbors = []
        for j in range (pnts.shape[0]):
            if i == j: continue
            if abs(pnts[i, 0] - pnts[j, 0]) <= 1 and abs(pnts[i, 1] - pnts[j, 1]) <= 1:#8-connectivity check
                neighbors.append(pnts[j])
        filter_neighbors(neighbors)
        if len(neighbors) == 1:
            extremes.append(pnts[i])
        elif len(neighbors) == 2:
            simple.append(pnts[i])
        elif len(neighbors) > 2:
            connections.append(pnts[i])
    return extremes, connections, simple


img_path = "D:/_temp/fig.png"
output_dir = 'D:/_temp/'

img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)

_, img = cv2.threshold(img, 128, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
img = cv2.ximgproc.thinning(img)

pnts = cv2.findNonZero(img)
pnts = np.squeeze(pnts)


ext, conn, simple = sort_points_types(pnts)

for p in conn:
    cv2.circle(img, (p[0], p[1]), 5, 128)

for p in ext:
    cv2.circle(img, (p[0], p[1]), 5, 128)

cv2.imwrite(output_dir + "6-both.png", img)

print (len(ext), len(conn), len(simple))

Edit3:

Гораздо более эффективная реализация для классификации точек за один проход путем проверки соседей способом, подобным ядру, благодаря * 10 75 * eldesgraciado !

Примечание : перед вызовом этого метода изображение должно быть дополнено одним пикселем, чтобы избежать проверки границ или эквивалентными затемнениями пикселей на границе.

def sort_points_types(pnts, img):
    extremes = []
    connections = []
    simple = []

    for p in pnts:
        x = p[0]
        y = p[1]
        n = []
        if img[y - 1,x] > 0: n.append((y-1, x))
        if img[y - 1,x - 1] > 0: n.append((y-1, x - 1))
        if img[y - 1,x + 1] > 0: n.append((y-1, x + 1))
        if img[y,x - 1] > 0: n.append((y, x - 1))
        if img[y,x + 1] > 0: n.append((y, x + 1))
        if img[y + 1,x] > 0: n.append((y+1, x))
        if img[y + 1,x - 1] > 0: n.append((y+1, x - 1))
        if img[y + 1,x + 1] > 0: n.append((y+1, x + 1))
        filter_neighbors(n)
        if len(n) == 1:
            extremes.append(p)
        elif len(n) == 2:
            simple.append(p)
        elif len(n) > 2:
            connections.append(p)
    return extremes, connections, simple

Изображение, визуализирующее крайние и соединительные точки:

enter image description here

...