Python 将二值图像的骨架转换为多段线
我的目标(简单示例): 让我们想象一个具有一组1像素宽直线的二值图像。这些线的方向(即它们相对于x轴的角度)遵循一些(窄)分布,但也存在异常值。我希望能够:Python 将二值图像的骨架转换为多段线,python,image-processing,polyline,Python,Image Processing,Polyline,我的目标(简单示例): 让我们想象一个具有一组1像素宽直线的二值图像。这些线的方向(即它们相对于x轴的角度)遵循一些(窄)分布,但也存在异常值。我希望能够: 找出异常值并将其从考虑范围中排除 确定端点彼此接近且角度相似的直线,并将其组合成一条较长的直线(即,用从第一条直线远端到第二条直线远端的直线替换) 确定直线的平均角度 然而,在我的实际用例中,线条不一定是直的 我的方法: 使用多边形线近似直线,即沿原始直线的顶点坐标组。 一旦创建了这些多边形线,剩下的就相当简单了: 如果顶点离最外层顶点
- 找出异常值并将其从考虑范围中排除
- 确定端点彼此接近且角度相似的直线,并将其组合成一条较长的直线(即,用从第一条直线远端到第二条直线远端的直线替换)
- 确定直线的平均角度
如何在Python中创建如上所述的多边形线结构?是否有可能有用的库/包?嗯,我构建了一些似乎有效的东西。如果您有任何关于如何改进的想法和意见,我将不胜感激。我正在使用Python 3.7 首先,创建一个列表,其中包含表示8个相邻连接区域的树:
from treelib import Tree
def extract_regions(image):
"""
Extracts 8-neighbors-connected regions from a binary image.
:param image: A homogeneous list of lists containing binary values (booleans or 1s and 0s).
:return: A list of trees containing 2D pixel coordinates of connected regions. The x axis
is assumed to be left to right, y axis top to bottom.
"""
regions = []
y = 0
for line in image:
x = 0
for pixel in line:
if pixel: # i.e. pixel is 1
if not in_trees((x, y), regions): # i.e. pixel is not labeled
# create tree of connected region
regions.append(create_region_tree(x, y, image_transpose))
# keep track of pixel position
x += 1
y += 1
return regions
def create_region_tree(center_x, center_y, image):
tree = Tree()
tag = 0
tree.create_node(tag, (center_x, center_y)) # root node
tracker = [(center_x, center_y)]
for p in tracker:
# define the 3x3 neighborhood around the center pixel; I try not wrap around, not sure if that's needed
from_x = p[0] - 1 if p[0] >= 1 else 0
to_x = p[0] + 2 if p[0] + 2 <= len(image[0]) else len(image[0])
from_y = p[1] - 1 if p[1] >= 1 else 0
to_y = p[1] + 2 if p[1] + 2 <= len(image) else len(image)
# find pixels in the neighborhood and add them to tree
b = from_y
for line in image[from_y:to_y]:
a = from_x
for neighbor in line[from_x:to_x]:
if neighbor:
if not (a, b) in tree:
tracker.append((a, b)) # keeps track of pixels in region
tag += 1
tree.create_node(tag, (a, b), parent=p)
a += 1
b += 1
return tree
然后将这些多边形线“拉直”:
最后,将这些直线相互比较,如果它们足够接近和相似(这条直线需要最长的时间才能完成,因此欢迎提供任何提示):
def组合直线(直线列表,距离公差平方=5**2,
角度公差=2,最小线长度平方=15**2):
临时行列表=行列表。复制()
新行列表=[]
#将距离列表的索引(见下文)转换为相应的向量
#两行中的一行(0:起始向量,1:结束向量)
向量表=[(0,0),(1,0),(0,1),(1,1)]
#最后一行
对于i,枚举中的第0行(临时行列表):
保持不变
#i之后的所有行,以避免“双重参照”(a->b&b->a)
对于范围内的j(i+1,len(临时线列表)):
第1行=临时列表[j]
距离=(
向量距离平方(直线0[0],直线1[0]),
向量距离平方(直线0[1],直线1[0]),
向量距离平方(直线0[0],直线1[1]),
向量距离平方(直线0[1],直线1[1]))
最小距离=最小(距离)
#检查最近的线向量是否足够接近
如果最小距离那么,我构建了一个似乎有效的东西。如果您有任何关于如何改进的想法和意见,我将不胜感激。我正在使用Python 3.7
首先,创建一个列表,其中包含表示8个相邻连接区域的树:
from treelib import Tree
def extract_regions(image):
"""
Extracts 8-neighbors-connected regions from a binary image.
:param image: A homogeneous list of lists containing binary values (booleans or 1s and 0s).
:return: A list of trees containing 2D pixel coordinates of connected regions. The x axis
is assumed to be left to right, y axis top to bottom.
"""
regions = []
y = 0
for line in image:
x = 0
for pixel in line:
if pixel: # i.e. pixel is 1
if not in_trees((x, y), regions): # i.e. pixel is not labeled
# create tree of connected region
regions.append(create_region_tree(x, y, image_transpose))
# keep track of pixel position
x += 1
y += 1
return regions
def create_region_tree(center_x, center_y, image):
tree = Tree()
tag = 0
tree.create_node(tag, (center_x, center_y)) # root node
tracker = [(center_x, center_y)]
for p in tracker:
# define the 3x3 neighborhood around the center pixel; I try not wrap around, not sure if that's needed
from_x = p[0] - 1 if p[0] >= 1 else 0
to_x = p[0] + 2 if p[0] + 2 <= len(image[0]) else len(image[0])
from_y = p[1] - 1 if p[1] >= 1 else 0
to_y = p[1] + 2 if p[1] + 2 <= len(image) else len(image)
# find pixels in the neighborhood and add them to tree
b = from_y
for line in image[from_y:to_y]:
a = from_x
for neighbor in line[from_x:to_x]:
if neighbor:
if not (a, b) in tree:
tracker.append((a, b)) # keeps track of pixels in region
tag += 1
tree.create_node(tag, (a, b), parent=p)
a += 1
b += 1
return tree
然后将这些多边形线“拉直”:
最后,将这些直线相互比较,如果它们足够接近和相似(这条直线需要最长的时间才能完成,因此欢迎提供任何提示):
def组合直线(直线列表,距离公差平方=5**2,
角度公差=2,最小线长度平方=15**2):
临时行列表=行列表。复制()
新行列表=[]
#将距离列表的索引(见下文)转换为相应的向量
#两行中的一行(0:起始向量,1:结束向量)
向量表=[(0,0),(1,0),(0,1),(1,1)]
#最后一行
对于i,枚举中的第0行(临时行列表):
保持不变
#i之后的所有行,以避免“双重参照”(a->b&b->a)
对于范围内的j(i+1,len(临时线列表)):
第1行=临时列表[j]
距离=(
向量距离平方(直线0[0],直线1[0]),
向量距离平方(直线0[1],直线1[0]),
向量距离平方(直线0[0],直线1[1]),
向量距离平方(直线0[1],直线1[1]))
最小距离=最小(距离)
#检查最近的线向量是否足够接近
如果距离最小
def straight_line_segments_from_polylines(poly_line_list, max_distance_from_line_squared=4 ** 2):
# turn a polyline into a (set of) straight line(s)
def straighten(p_line):
end = len(p_line) - 1
# get coordinates of first and last vector of the polyline
x1, y1 = p_line[0]
x2, y2 = p_line[-1]
# for distance calculation, see below
factor_x = y2 - y1
factor_y = x2 - x1
term = x2 * y1 - y2 * x1
denominator = (y2 - y1) ** 2 + (x2 - x1) ** 2
for i in range(1, end): # i.e. all vectors except first and last
x0, y0 = p_line[i]
# calculate distance between current vector and the line between first and last vector of the polyline
numerator = abs(factor_x * x0 - factor_y * y0 + term) ** 2
distance = numerator / denominator
if distance > max_distance_from_line_squared: # i.e. current vector is too far away from the straight line
# split the polyline at current vector, convert the now 2 polylines into sets of straight lines
return straight_line_segments_from_polylines([p_line[:i], p_line[i:]])
# return start and end vector of the polyline (a now straight line)
straight_line = [[p_line[0], p_line[-1]]]
return straight_line
straight_lines = []
for polyline in poly_line_list:
straight_lines += straighten(polyline)
return straight_lines
def combine_straight_lines(line_list, distance_tolerance_squared=5 ** 2,
angle_tolerance=2, min_line_length_squared=15 ** 2):
temp_line_list = line_list.copy()
new_line_list = []
# translates indices of the distance list (see below) to the corresponding vectors
# of the 2 lines (0: starting vector, 1: end vector)
vector_nr_table = [(0, 0), (1, 0), (0, 1), (1, 1)]
# all lines before last
for i, line0 in enumerate(temp_line_list):
keeping = True
# all lines_after i, as to not "double reference" (a -> b & b -> a)
for j in range(i+1, len(temp_line_list)):
line1 = temp_line_list[j]
distance = (
vector_distance_squared(line0[0], line1[0]),
vector_distance_squared(line0[1], line1[0]),
vector_distance_squared(line0[0], line1[1]),
vector_distance_squared(line0[1], line1[1]))
smallest_distance = min(distance)
# check if the closest line vectors are close enough
if smallest_distance <= distance_tolerance_squared:
# calculate angle of the lines and compare
angle0 = get_straight_line_angle(line0)['deg']
angle1 = get_straight_line_angle(line1)['deg']
angle_diff = abs(angle1 - angle0)
if angle_diff <= angle_tolerance: # i.e. angles are similar
# determine the vectors of each line farthest away from each other,
# replace j with these vectors
max_dist = int(np.argmax(distance))
v0, v1 = vector_nr_table[max_dist]
temp_line_list[j] = [line0[v0], line1[v1]]
# line i has been incorporated into j, can be left out from result
keeping = False
break
if keeping:
line_length = get_straight_line_length_squared(line0)
# skip if too short
if line_length >= min_line_length_squared:
new_line_list.append(line0)
return new_line_list
def vector_distance_squared(vec0, vec1):
np_vec0 = np.array(vec0)
np_vec1 = np.array(vec1)
result = np.sum(ce.np.square(np_vec0 - np_vec1))
return result
def get_straight_line_angle(line):
numerator = line[1][1] - line[0][1]
denominator = line[1][0] - line[0][0]
angle = np.pi / 2 if denominator == 0 else np.arctan(numerator / denominator)
result = {'rad': angle, 'deg': 180 * angle / np.pi}
return result
def get_straight_line_length_squared(line):
a = line[1][0] - line[0][0]
b = line[1][1] - line[0][1]
length_squared = a ** 2 + b ** 2
return length_squared