Полностью преобразовать черно-белое изображение в набор линий (векторизация с использованием только линий) - PullRequest
5 голосов
/ 11 октября 2019

У меня есть несколько черно-белых изображений, и я хотел бы преобразовать их в набор линий, чтобы я мог полностью или, по крайней мере, почти полностью восстановить исходное изображение из линий. Другими словами, я пытаюсь векторизовать изображение в набор линий.

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

До сих пор я пробовал следующий код, используя houghLineTransform:

import numpy as np
import cv2

MetersPerPixel=0.1

def loadImageGray(path):
    img=(cv2.imread(path,0))
    return img

def LineTransform(img):
    edges = cv2.Canny(img,50,150,apertureSize = 3)
    minLineLength = 10
    maxLineGap = 20
    lines = cv2.HoughLines(edges,1,np.pi/180,100,minLineLength,maxLineGap)
    return lines;

def saveLines(liness):
    img=np.zeros((2000,2000,3), np.uint8)
    for lines in liness:
        for x1,y1,x2,y2 in lines:
            print(x1,y1,x2,y2)
            img=cv2.line(img,(x1,y1),(x2,y2),(0,255,0),3)
    cv2.imwrite('houghlines5.jpg',img)

def main():
    img=loadImageGray("loadtest.png")
    lines=LineTransform(img)
    saveLines(lines)

main()

Однако при тестировании с использованием следующего image

я получил это изображение: output

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

Редактировать: по предложению @MarkSetchell я попробовал pypotrace, используя следующий код, в настоящее время он в значительной степени игнорирует кривые Безье и просто пытается действовать так, как будто это прямые линии, однако я остановлюсь на этой проблеме позже, однакосейчас результаты тоже не оптимальны:

def TraceLines(img):
    bmp = potrace.Bitmap(bitmap(img))
    path=bmp.trace()
    lines=[]
    i=0
    for curve in path:
        for segment in curve:
            print(repr(segment))
            if segment.is_corner:
                c_x, c_y = segment.c
                c2_x ,c2_y= segment.end_point
                            lines.append([[int(c_x), int(c_y),int(c2_x) ,int(c2_y)]])

            else:
                c_x, c_y = segment.c1
                c2_x ,c2_y= segment.end_point
            i=i+1
    return lines

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

Кто-нибудь получил какие-либо предложения о том, как лучше подойти к этой проблеме?

edit edit: вот еще одно тестовое изображение: variable wall width, оно включает несколько линий ширины, которые я хотел бы захватить.

Ответы [ 3 ]

7 голосов
/ 16 октября 2019

OpenCV

Используя OpenCV findContours и drawContours, можно сначала векторизовать линии, а затем точно воссоздать исходное изображение:

import numpy as np

import cv2

img = cv2.imread('loadtest.png', 0)

result_fill = np.ones(img.shape, np.uint8) * 255
result_borders = np.zeros(img.shape, np.uint8)

# the '[:-1]' is used to skip the contour at the outer border of the image
contours = cv2.findContours(img, cv2.RETR_LIST,
                            cv2.CHAIN_APPROX_SIMPLE)[0][:-1]

# fill spaces between contours by setting thickness to -1
cv2.drawContours(result_fill, contours, -1, 0, -1)
cv2.drawContours(result_borders, contours, -1, 255, 1)

# xor the filled result and the borders to recreate the original image
result = result_fill ^ result_borders

# prints True: the result is now exactly the same as the original
print(np.array_equal(result, img))

cv2.imwrite('contours.png', result)

Результат

enter image description here

Scikit-Image

Использование scikit-изображения find_contoursи approximate_polygon позволяет уменьшить число линий путем аппроксимации полигонов (на основе этот пример ):

import numpy as np
from skimage.measure import approximate_polygon, find_contours

import cv2

img = cv2.imread('loadtest.png', 0)
contours = find_contours(img, 0)

result_contour = np.zeros(img.shape + (3, ), np.uint8)
result_polygon1 = np.zeros(img.shape + (3, ), np.uint8)
result_polygon2 = np.zeros(img.shape + (3, ), np.uint8)

for contour in contours:
    print('Contour shape:', contour.shape)

    # reduce the number of lines by approximating polygons
    polygon1 = approximate_polygon(contour, tolerance=2.5)
    print('Polygon 1 shape:', polygon1.shape)

    # increase tolerance to further reduce number of lines
    polygon2 = approximate_polygon(contour, tolerance=15)
    print('Polygon 2 shape:', polygon2.shape)

    contour = contour.astype(np.int).tolist()
    polygon1 = polygon1.astype(np.int).tolist()
    polygon2 = polygon2.astype(np.int).tolist()

    # draw contour lines
    for idx, coords in enumerate(contour[:-1]):
        y1, x1, y2, x2 = coords + contour[idx + 1]
        result_contour = cv2.line(result_contour, (x1, y1), (x2, y2),
                                  (0, 255, 0), 1)
    # draw polygon 1 lines
    for idx, coords in enumerate(polygon1[:-1]):
        y1, x1, y2, x2 = coords + polygon1[idx + 1]
        result_polygon1 = cv2.line(result_polygon1, (x1, y1), (x2, y2),
                                   (0, 255, 0), 1)
    # draw polygon 2 lines
    for idx, coords in enumerate(polygon2[:-1]):
        y1, x1, y2, x2 = coords + polygon2[idx + 1]
        result_polygon2 = cv2.line(result_polygon2, (x1, y1), (x2, y2),
                                   (0, 255, 0), 1)

cv2.imwrite('contour_lines.png', result_contour)
cv2.imwrite('polygon1_lines.png', result_polygon1)
cv2.imwrite('polygon2_lines.png', result_polygon2)

Результаты

Выход Python:

Contour shape: (849, 2)
Polygon 1 shape: (28, 2)
Polygon 2 shape: (9, 2)
Contour shape: (825, 2)
Polygon 1 shape: (31, 2)
Polygon 2 shape: (9, 2)
Contour shape: (1457, 2)
Polygon 1 shape: (9, 2)
Polygon 2 shape: (8, 2)
Contour shape: (879, 2)
Polygon 1 shape: (5, 2)
Polygon 2 shape: (5, 2)
Contour shape: (973, 2)
Polygon 1 shape: (5, 2)
Polygon 2 shape: (5, 2)
Contour shape: (224, 2)
Polygon 1 shape: (4, 2)
Polygon 2 shape: (4, 2)
Contour shape: (825, 2)
Polygon 1 shape: (13, 2)
Polygon 2 shape: (13, 2)
Contour shape: (781, 2)
Polygon 1 shape: (13, 2)
Polygon 2 shape: (13, 2)

contour_lines.png:

contour_lines.png

polygon1_lines.png:

polygon1_lines.png

polygon2_lines.png:

polygon2_lines.png

Затем можно рассчитать длину линий, применив теорему Пифагора к координатам: line_length = math.sqrt(abs(x2 - x1)**2 + abs(y2 - y1)**2). Если вы хотите получить ширину линий в виде числовых значений, взгляните на ответы «Как определить ширину линий?» для некоторых предлагаемых подходов.

5 голосов
/ 16 октября 2019

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

Я думаю, что некоторые из проблем связаны с выбором Canny в качестве обнаружения края, потому что это приводит к двум краям, поэтому мой первый план атаки состоял в том, чтобы заменить это скелетонизацией из scikit-image. Это дает edge изображение:

enter image description here

Тогда я решил использовать HoughLinesP, а не HoughLines, но, похоже, ничего особенного,Я пытался увеличить и уменьшить параметры разрешения, но это не помогло. Итак, я решил немного расширить (откармливать) скелет, а затем он начинает лучше определять формы, и я получаю следующее:

enter image description here

Я не уверен, почему он так чувствителен к толщине линии, и, как я уже сказал, если кто-то еще захочет взять его и поэкспериментировать, вот где я получил код:

#!/usr/bin/env python3

import numpy as np
import cv2
from skimage.morphology import medial_axis, dilation, disk

def loadImageGray(path):
    img=cv2.imread(path,0)
    return img

def LineTransform(img): 
    # Try skeletonising image rather than Canny edge - only one line instead of both sides of line
    skeleton = (medial_axis(255-img)*255).astype(np.uint8)
    cv2.imwrite('skeleton.png',skeleton)

    # Try dilating skeleton to make it fatter and more detectable
    selem = disk(2)
    fatskel = dilation(skeleton,selem)
    cv2.imwrite('fatskeleton.png',fatskel)

    minLineLength = 10
    maxLineGap = 20
    lines = cv2.HoughLinesP(fatskel,1,np.pi/180,100,minLineLength,maxLineGap)
    return lines

def saveLines(liness):
    img=np.zeros((2000,2000,3), np.uint8)
    for lines in liness:
        for x1,y1,x2,y2 in lines:
            print(x1,y1,x2,y2)
            img=cv2.line(img,(x1,y1),(x2,y2),(0,255,0),3)
    cv2.imwrite('houghlines.png',img)

img=loadImageGray("loadtest.png")
lines=LineTransform(img)
saveLines(lines)

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

def LineTransform(img): 
    minLineLength = 10
    maxLineGap = 20
    lines = cv2.HoughLinesP(255-img,1,np.pi/180,100,minLineLength,maxLineGap)
    return lines
0 голосов
/ 22 октября 2019

@ Thijser, в OpenCV вы можете делать следующее:

import cv2
from matplotlib import pyplot as plt
import numpy as np

filename = "three.jpg"
src = cv2.imread(filename)

max_lowThreshold = 100
window_name = 'Edge Map'
title_trackbar = 'Min Threshold:'
ratio = 3
kernel_size = 3
def CannyThreshold(val):
    low_threshold = val
    img_blur = cv2.blur(src_gray, (3,3))
    detected_edges = cv2.Canny(img_blur, low_threshold, low_threshold*ratio, kernel_size)
    mask = detected_edges != 0
    dst = src * (mask[:,:,None].astype(src.dtype))
    cv2.imshow(window_name, dst)
src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
cv2.namedWindow(window_name)
cv2.createTrackbar(title_trackbar, window_name , 0, max_lowThreshold, CannyThreshold)
CannyThreshold(0)
cv2.waitKey()

Вы получите:

enter image description here

...