import numpy as np
import matplotlib.pyplot as plt
from skimage import io, color, transform, morphology
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
# 导入图片 original map
omg = io.imread("../Assets/Resources/Environment/890c.png")
omg = np.flip(omg, 0) # 图片翻转
omg = transform.resize(omg, (500, 500)) # 大小
plt.axis("off"), plt.gray()
plt.xlim(0, omg.shape[1])
plt.ylim(0, omg.shape[0])
plt.imshow(omg)
<matplotlib.image.AxesImage at 0x16b6d04c0a0>
关于坐标:
(x,y)
, 对应 img[y, x]
.img[y, x] == img.T[x, y]
高维矩阵可以交换维度。img.shape[1]
, 原因同上.img=np.flip(img,0)
. 则等效为图片坐标原点转化为左下角.# 转换为灰度图并二值化 (这里选的是另一个更方便的图)
img = io.imread("../Assets/Resources/Environment/map_original.png")
img = color.rgb2gray(img)
img = transform.resize(img, (500, 500))
img = img < 0.44
img = np.flip(img, 0) # 图片翻转, 图片坐标原点转换到左下角
img = morphology.binary_closing(img) # 消除小黑点
img = morphology.binary_opening(img, np.ones((6, 6)))
plt.axis("off"), plt.gray()
plt.xlim(0, img.shape[1])
plt.ylim(0, img.shape[0])
plt.imshow(img) # 得到一个 500x500 大小的地图
<matplotlib.image.AxesImage at 0x16b6d04c070>
获取障碍物信息:
地图大小 500x500; 0表示不可通行, 1表示可以
用于改进的 tips: 开闭运算消除细小区域; 腐蚀操作扩大障碍区域判定范围
随机数生成
import random
obstacle = morphology.binary_erosion(img, np.ones((10,10))) # 扩大障碍物判定范围
target = []
while len(target) < 20:
p = (random.randint(0, 499), random.randint(0, 499))
if obstacle[p[1], p[0]]:
target.append(p)
else:
target = np.array(target) # 最后转变为 ndarray
plt.axis("off"), plt.gray()
plt.xlim(0, img.shape[1])
plt.ylim(0, img.shape[0])
plt.imshow(img)
plt.scatter(target[:, 0], target[:, 1], color="#6495ED", s=5, marker="x")
<matplotlib.collections.PathCollection at 0x16b6d1cc6a0>
通用代码
import pickle
import time
# 数据保存在 assets 下面
def save(var, name="data"):
time_str = time.strftime("%Y%m%d%H%M%S")
filename = f"{time_str} {name}.pickle"
filepath = f"assets/{filename}"
with open(filepath, 'wb') as f:
pickle.dump(var, f)
print("save {} to \"{}\"".format(type(var), filepath))
def load(filename):
with open(f"assets/{filename}", 'rb') as f:
data = pickle.load(f)
return data
保存到文件
# save(target, "targets")
# save(img, "map")
# save(omg, "original_map")
从文件读取
target = load("20220423164508 targets.pickle")
img = load("20220423164508 map.pickle")
omg = load("20220423171747 original_map.pickle")
plt.axis("off"), plt.gray()
plt.xlim(0, img.shape[1])
plt.ylim(0, img.shape[0])
plt.imshow(img)
plt.scatter(target[:, 0], target[:, 1], color="#6495ED", s=5, marker="x")
<matplotlib.collections.PathCollection at 0x16b704c0be0>
这里的思路是用a星在500x500的网格中进行路径规划, 获得路径长度作为邻接矩阵对应位置的权重.
n个目标点就是规划 $1+2+...+n$ 个, 计算量比较大.
可以用一些更快的方法, 不需要获得精确的距离, 只需要获得的距离之间大小关系一致.
或者可以保存下来路径, 后续基于这个图用其他方法进行目标点排序, 排完再把路径拼起来
然后根据
距离是按照像素计算的.
WARNING: 计算时间比较久
from assets.astar import GridWithWeights, a_star_search, reconstruct_path
target_list = [(t[0],t[1]) for t in target]
graph = GridWithWeights(img)
def get_distance(graph, start, goal):
came_from, cost_so_far = a_star_search(graph, start, goal)
path = reconstruct_path(came_from, start, goal)
return len(path) - 1
n = len(target_list)
adj = np.ones((n, n)) # adjacency matrix
for i, p1 in enumerate(target_list):
for j, p2 in enumerate(target_list[i + 1:]):
# 计算 P1,P2 之间的距离
distance = get_distance(graph, p1, p2)
# 矩阵对应位置赋值
adj[[i, i + j + 1], [i + j + 1, i]] = distance
调参确定 gamma, gamma 越小, 数据越靠近1
gamma = 5e-6
affinity_matrix = np.exp(-gamma * adj**2)
for i in affinity_matrix:
for index, j in enumerate(i):
if j != 1.0:
plt.scatter(index, j)
# from sklearn.cluster import KMeans
from sklearn.cluster import SpectralClustering
# 前面算好 affinity_matrix
cluster = 3
sc = SpectralClustering(n_clusters=cluster, affinity='precomputed')
result_sc = sc.fit_predict(affinity_matrix)
plt.axis("off")
plt.xlim(0, omg.shape[1])
plt.ylim(0, omg.shape[0])
plt.imshow(omg)
# plt.scatter(target[:, 0], target[:, 1], color="#6495ED", s=6) # 画上目标点
plt.scatter(target[:, 0],
target[:, 1],
c=result_sc,
marker='x',
s=5,
cmap="tab10") # 画上目标点
<matplotlib.collections.PathCollection at 0x279ee4dbd60>
readme = """
img: 20220423164508 map.pickle
omg: 20220423171747 original_map.pickle
target: 20 个目标点 ndarry 20220423164508 targets.pickle
cluster: 3 个聚类结果
affinity_matrix: 邻接矩阵
"""
MAP = {
"omg": omg, # 底图
"img": img, # 二值化后障碍信息
"target": target, # 目标点
"cluster": result_sc, # 目标点聚类结果(聚类数自己看)
"affinity_matrix": affinity_matrix, # 邻接矩阵, 以免想要重新聚类
"readme": readme
}
# save(MAP,"MAP")
MAP = load("20220423172802 MAP.pickle")
# MAP = load("MAP 20220212162020.pickle")
omg = MAP["omg"]
img = MAP["img"]
target = MAP["target"]
cluster = MAP["cluster"]
from assets.som import Network
# 画图
def plot_path(img, targets, network):
fig = plt.figure()
axis = fig.subplots()
axis.set_xlim(0, img.shape[1])
axis.set_ylim(0, img.shape[0])
plt.axis("off")
axis.imshow(img) # 画地图
axis.scatter(targets[:, 0], targets[:, 1], marker='x', s=2.5,
c="r") # 画任务点
axis.plot(network[:, 0], network[:, 1]) # 画路线
return plt.axis
# 单线 SOM 的话, 确定起点和终点
def get_start_end(array):
# (maximum - minimum) along an axis.
index = array.ptp(axis=0).argmax() # 获得相距最远的轴 (x or y)
start_index = array.argmin(axis=0)[index]
end_index = array.argmax(axis=0)[index]
start = array[start_index]
end = array[end_index]
return start, end
# start, end = get_start_end(target) # 确定起始位置
# net = Network(target, img, start, end)
# net.training()
# path_lenghth = net.route_distance()
# print(path_lenghth, "km")
# plot_path(omg, target, net.network)
# plt.scatter(start[0], start[1], marker='o', c="r", s=5) # 画起点
# plt.scatter(end[0], end[1], marker='o', c="r", s=5) # 画终点
result = cluster
distances = []
routes = []
for i in range(result.max() + 1):
subt = target[result == i]
start, end = get_start_end(subt)
net = Network(subt, img, start, end)
net.training()
routes.append(net.network)
distances.append(net.route_distance())
plt.xlim(0, omg.shape[1])
plt.ylim(0, omg.shape[0])
plt.axis("off")
plt.imshow(omg) # 画地图
plt.scatter(target[:, 0],
target[:, 1],
c=cluster,
marker='x',
s=5,
cmap="tab10") # 画任务点
for path in routes: # 画路线
plt.plot(path[:, 0], path[:, 1])
# axis.scatter(path[:,0],path[:,1],s=1)
readme = """
20220423172802 MAP.pickle, 30 个目标点, 3组分类 (全部都是 numpy 矩阵)
"""
# PATH = {
# "routes": routes, # list of paths array
# "distances": distances,
# "readme": readme
# }
# save(PATH, "PATHS")
PATH = load("20220423175003 PATHS.pickle")
routes = PATH["routes"]
distances = PATH["distances"]
MAP = load("20220423172802 MAP.pickle")
omg = MAP["omg"]
img = MAP["img"]
target = MAP["target"]
cluster = MAP["cluster"]
plt.xlim(0, omg.shape[1])
plt.ylim(0, omg.shape[0])
plt.axis("off")
plt.imshow(omg) # 画地图
plt.scatter(target[:, 0],
target[:, 1],
c=cluster,
marker='x',
s=50,
cmap="tab10") # 画任务点
for path in routes: # 画路线
plt.plot(path[:, 0], path[:, 1])
# axis.scatter(path[:,0],path[:,1],s=1)
from scipy.interpolate import make_interp_spline
def Bspline(path):
"""考虑路径长度进行圆滑处理
默认输入路径 15m 一个路径点, 输出 0.7m 1个路径点
"""
di_vec = np.diff(path, n=1, axis=0) # 沿行一阶差分
di_dis = np.linalg.norm(di_vec, axis=1) # 相邻路径点之间的欧氏距离
di_dis = np.insert(di_dis, 0, 0, axis=0) # 开头插入一个 0
di_overall = sum(di_dis) # 路线总长度
di_index = np.cumsum(di_dis) / di_overall # 先累积距离,再除以总长度,变成0-1的索引
# 初次平滑, 隔 20m 输出一个路径点 "降采样"
spl1 = make_interp_spline(di_index, path, bc_type="clamped")
t1 = np.linspace(0, 1, int(di_overall // 20))
splined_routes1 = spl1(t1)
# 二次平滑, 每隔 1m 输出一个路径点
spl2 = make_interp_spline(t1, splined_routes1, bc_type="clamped")
t2 = np.linspace(0, 1, int(di_overall // 1))
splined_routes2 = spl2(t2)
return splined_routes2
# # test code
# path=routes[0]
# distance=distances[0]
# splined_routes = Bspline(path)
# plt.plot(path[:, 0], path[:, 1], '-', alpha=0.25, c="gray",linewidth=3)
# plt.plot(splined_routes[:, 0], splined_routes[:, 1], '-',alpha=0.5)
# plt.scatter(splined_routes[:, 0], splined_routes[:, 1],s=1, c="red")
splined_routes = []
for path in routes:
splined_routes.append(Bspline(path))
plt.axis("off")
plt.xlim(0, omg.shape[1])
plt.ylim(0, omg.shape[0])
plt.imshow(omg)
# plt.scatter(target[:, 0], target[:, 1], color="#6495ED", s=6) # 画上目标点
plt.scatter(target[:, 0],
target[:, 1],
c=cluster,
marker='x',
s=5,
cmap="tab10") # 画上目标点
# 绘图比较 灰色的是原始, 彩色的是平滑过后的
for i, j in zip(routes, splined_routes):
plt.plot(i[:, 0], i[:, 1], '-', alpha=0.75, c="black", linewidth=2)
plt.plot(j[:, 0], j[:, 1], '-', linewidth=1)
输出一组 targets & path
matlab 沿 path.csv 仿真
# splined_route = splined_routes[0]
# plt.plot(splined_route[:, 0], splined_route[:, 1], '-', linewidth=1)
# st = target[cluster==0]
# plt.scatter(st[:,0],st[:,1],marker="x")
for index, path in enumerate(splined_routes):
np.savetxt(f"path{index}.csv", path, delimiter=',', fmt='%.3f')
np.savetxt("all_targets.csv", target, delimiter=',', fmt='%.3f')