如何用Python的Numpy按顺序获取线条/路径坐标及处理圆形形状
按顺序排列线条/形状的像素坐标解决方案
这是个很常见的计算机视觉问题,我来给你拆解两种场景的具体解决方案:
一、手绘单像素线条的坐标排序
你用numpy.where拿到所有线条像素坐标后,之所以没法直接排序,是因为这个方法返回的是无序的坐标集合——毕竟图像的存储是按行/列来的,和线条的走向没关系。这里有两种靠谱的排序思路:
方法1:基于起点距离排序(适合近似直线的线条)
如果你的线条比较接近直线,直接计算每个点到红色起点的欧氏距离,按距离从小到大排序就足够了。代码示例:
import numpy as np # 假设img是你的二值图像,线条像素为1,背景为0 y_coords, x_coords = np.where(img == 1) # 转成(x, y)格式的坐标数组 points = np.column_stack((x_coords, y_coords)) # 假设你已经知道起点(红色像素)坐标 start_point = np.array([x0, y0]) # 计算每个点到起点的距离 distances = np.linalg.norm(points - start_point, axis=1) # 按距离排序 sorted_points = points[np.argsort(distances)]
⚠️ 注意:如果线条有明显弯曲甚至折返,这个方法会失效,因为有些离起点远的点可能在线条的“分支”上,排序结果会乱。
方法2:路径追踪排序(适合任意弯曲的线条)
这是更可靠的方案,相当于沿着线条一步步“走”:从起点开始,每次找当前点的最近邻未访问点,直到遍历完所有像素。这种方法完全贴合手绘线条的走向,代码示例:
def sort_line_points(points, start_point): remaining = points.copy() sorted_list = [start_point] current = start_point # 先从剩余点中移除起点 idx = np.where((remaining == current).all(axis=1))[0][0] remaining = np.delete(remaining, idx, axis=0) while len(remaining) > 0: # 计算当前点到所有剩余点的距离 dists = np.linalg.norm(remaining - current, axis=1) # 找到最近的点 nearest_idx = np.argmin(dists) nearest_point = remaining[nearest_idx] # 添加到排序列表,移除已访问的点 sorted_list.append(nearest_point) remaining = np.delete(remaining, nearest_idx, axis=0) current = nearest_point return np.array(sorted_list) # 调用方法得到排序后的坐标 sorted_points = sort_line_points(points, start_point)
二、圆形(闭合形状)的坐标排序
圆形是闭合的,没有天然的起点终点,排序的核心是确定旋转方向(顺时针/逆时针),然后基于极角排序:
- 先计算圆形的近似中心:
# 用所有像素坐标的均值作为中心 center = np.mean(points, axis=0)
- 计算每个点相对于中心的极角:
# 计算每个点到中心的向量 vectors = points - center # arctan2(y, x)返回从x轴正方向逆时针旋转的角度(弧度) angles = np.arctan2(vectors[:, 1], vectors[:, 0])
- 按极角排序:
# 逆时针排序(从右水平方向开始逆时针绕圈) sorted_circle_points = points[np.argsort(angles)] # 顺时针排序(从右水平方向开始顺时针绕圈) sorted_circle_points = points[np.argsort(-angles)]
如果你的圆形不是完全闭合(有缺口),可以先指定一个起始点,用线条的路径追踪方法排序;或者结合极角排序后,手动调整起始位置。
额外注意事项
- 如果线条宽度大于1像素,一定要先提取单像素骨架(比如用OpenCV的
cv2.ximgproc.thinning()),否则多像素的排序会完全混乱。 - 手绘线条可能有噪声点,排序前可以用形态学操作(比如
cv2.morphologyEx())移除孤立的小像素块,避免干扰排序结果。
内容的提问来源于stack exchange,提问作者Johannes Schmidt




