问题描述
很抱歉,如果有与此类似的问题,但我找不到。
我的问题很简单。我有如下图所示的二元曲线图像。我想在同一条曲线上的给定两点之间找到白色像素位置。我正在使用 Python,但任何有关算法的建议都会非常有帮助。
import numpy as np
import cv2
import matplotlib.pyplot as plt
curve = cv2.imread('curve.png',-1)
pts = np.array([[31,14],[34,51]])
y,x = np.nonzero(curve)
fig,ax=plt.subplots()
ax.scatter(pts[:,1],pts[:,0],s=10,c='b')
for xx,yy in zip(x,y):
if 51>xx>14:
ax.scatter(xx,yy,c='r')
ax.imshow(curve,cmap='gray')
plt.show()
蓝点是给定的点,红点是我想得到的点。在代码中,我添加了 if 部分只是为了显示我想要得到的内容。
我正在处理skeleton化的图像。因此,我正在为许多二值图像上的许多曲线寻找一种合理的方法。你知道做这种事情的任何算法/方法吗?提前致谢。
解决方法
解决此问题的最佳方法(还不是 cv2
或 numpy
)是使用广度优先搜索。 A* 算法不会总是返回最小路径,而且它更复杂。此外,由于像素之间没有权重,Dijkstra 对这个问题来说太复杂了。
以下是一些 Python 代码,用于进行原始广度优先搜索,以获得起点和终点之间的最短路径。请注意,路径数组包含开始和结束之间的所有内容,而不是开始/结束本身。也很容易调整以包括开始和结束。
import numpy as np
import cv2
import matplotlib.pyplot as plt
from collections import deque
import sys
curve = cv2.imread('curve.png',-1)
height,width = len(curve),len(curve[0])
# The start and end point you're looking at
start,end = (31,14),(34,51)
# All 8 directions
delta = [(-1,-1),(-1,0),1),(0,(1,-1)]
# Store the results of the BFS as the shortest distance to start
grid = [[sys.maxsize for _ in range(width)] for _ in range(height)]
grid[start[0]][start[1]] = 0
# The actual BFS algorithm
bfs = deque([start])
found = False
while len(bfs) > 0:
y,x = bfs.popleft()
# We've reached the end!
if (y,x) == end:
found = True
break
# Look all 8 directions for a good path
for dy,dx in delta:
yy,xx = y + dy,x + dx
# If the next position hasn't already been looked at and it's white
if 0 <= yy < height and 0 <= xx < width and grid[y][x] + 1 < grid[yy][xx] and curve[yy][xx] != 0:
grid[yy][xx] = grid[y][x] + 1
bfs.append((yy,xx))
if found:
# Now rebuild the path from the end to beginning
path = []
y,x = end
while grid[y][x] != 0:
for dy,dx in delta:
yy,x + dx
if 0 <= yy < height and 0 <= xx < width and grid[yy][xx] == grid[y][x] - 1:
path.append((yy,xx))
y,x = yy,xx
# Get rid of the starting point from the final path
path.pop()
# Display the final path on the plot
fig,ax = plt.subplots()
ax.scatter([start[1],end[1]],[start[0],end[0]],s=10,c='b')
for y,x in path:
ax.scatter(x,y,c='r')
ax.imshow(curve,cmap='gray')
plt.show()
else:
print(f'No path found between {start} and {end}')
这是一个很好的方法,因为它使用了 O(height * width)
最差的时间复杂度。由于您的图像主要是骨架,因此它的运行速度应该比平均速度快得多。
使用 cv2.floodFill 是个好主意。 该问题通常称为寻找测地曲线。这是我的代码:
import numpy as np
import cv2
img=cv2.imread('UA3xU.png',cv2.IMREAD_GRAYSCALE)
pts = np.array([[31,14],[34,51]])
mask = np.zeros((img.shape[0]+2,img.shape[1]+2),np.uint8)
mask1_image = img.copy()
mask1_image[pts[0,0],pts[0,1]]=0 # split curve in first point
cv2.floodFill(mask1_image,mask.copy(),(pts[1,1],pts[1,0]),flags=8)
mask2_image = img.copy()
mask2_image[pts[1,1]]=0 # split curve in second point
cv2.floodFill(mask2_image,(pts[0,flags=8)
# combine images
all_mask=cv2.bitwise_or(mask1_image,mask2_image)
out=cv2.bitwise_xor(img,all_mask)
cv2.imwrite('curve_between_two_points.png',out)
为了可靠地将曲线分成几部分,您可以不使用点,而是使用更大的 3x3 元素,例如。
之前和之后: