defsample_proposal(self, p, odom_delta, scan): pose0 = self.sample_motion(p, *odom_delta) # 先按里程计走一步 best, score = self.scan_match(pose0, scan, p) # 在粒子的"似然场"上爬山对齐 if score < min_score: # 匹配失败 -> 退回朴素版 return pose0 # 在匹配点附近撒 K 个候选, 拟合高斯, 从中采样 cands = [perturb(best) for _ inrange(K)] w = [self.score(c, scan, p) * self.motion_prob(c, pose0) for c in cands] mu, Sigma = weighted_gaussian_fit(cands, w) # 加权均值+协方差 return np.random.multivariate_normal(mu, Sigma) # ⭐ 改进提议分布落地
2.3 扫描匹配:爬山版(~30 行核心)
思路:在网格上小步挪动位姿,找激光得分最高的位置(就是拼图对齐):
1 2 3 4 5 6 7 8 9 10 11 12
defscan_match(self, pose, scan, p, max_iter=5): step = 0.05 for _ inrange(max_iter): best_gain, best_move = 0, None for dx, dy, dth in [(-step,0,0),(step,0,0),(0,-step,0),(0,step,0), (0,0,-0.03),(0,0,0.03)]: cand = pose + np.array([dx, dy, dth]) gain = self.score(cand, scan, p) - self.score(pose, scan, p) if gain > best_gain: best_gain, best_move = gain, (dx,dy,dth) if best_move isNone: break pose = pose + np.array(best_move) return pose, self.score(pose, scan, p)
打分函数(似然场思想):把激光点按位姿投到地图上,离最近的障碍物越近得分越高:
1 2 3 4 5 6 7
defscore(self, pose, scan, p): s = 0 for r, a in scan.points: hit = pose.transform(r, a) # 激光点 -> 全局坐标 d = distance_to_nearest_occupancy(p, hit) # 到粒子地图最近障碍的距离 s += np.exp(-d**2 / (2*sigma_l**2)) # 高斯核打分 return s
三、模块③:建图 = Bresenham 光线投射 + log-odds 更新
这是最值得亲手写的一块(第 2 篇讲过原理):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
OCC, FREE = 0.9, -0.4# 命中加多少 / 穿过减多少 (log-odds)
defupdate_map(self, p, scan): for r, a in scan.points: end = p.pose.transform(r, a) for cell in bresenham(p.pose[:2], end): # 沿光束逐格 self.grid[cell] += FREE # 穿过 -> 更空闲 self.grid[to_cell(end)] += OCC # 命中 -> 更占用 np.clip(self.grid, -5, 5, out=self.grid) # 防饱和
defneff(self): w = np.array([p.weight for p inself.particles]) w /= w.sum() return1.0 / np.sum(w**2) # 第3篇公式原样
defresample(self): w = normalize([p.weight for p inself.particles]) idx = np.random.choice(len(self.particles), len(self.particles), p=w) self.particles = [deepcopy(self.particles[i]) for i in idx] # 地图一起复制 for p inself.particles: p.weight = 1.0 / len(self.particles)