地形适应第13课/共30课

🤖 地形感知

感知脚下的世界:高度图与地形分类

📖 本课概要

感知脚下的世界:高度图与地形分类。本课将深入探讨相关理论和实现,通过Python仿真验证核心算法。

🧮 核心仿真

import math, random class TerrainPerception: def __init__(self, grid_size=0.02, map_size=2.0): self.grid_size = grid_size self.map_size = map_size self.n = int(map_size / grid_size) self.heightmap = [[0.0]*self.n for _ in range(self.n)] def generate_terrain(self, terrain_type='flat'): for i in range(self.n): for j in range(self.n): x = i * self.grid_size - self.map_size/2 y = j * self.grid_size - self.map_size/2 if terrain_type == 'flat': self.heightmap[i][j] = 0 elif terrain_type == 'slope': self.heightmap[i][j] = 0.2 * x elif terrain_type == 'stairs': step = 0.03 self.heightmap[i][j] = step * int(x / 0.15) if x > 0 else 0 elif terrain_type == 'rough': self.heightmap[i][j] = 0.01 * math.sin(5*x) * math.cos(7*y) + random.gauss(0, 0.005) def get_height(self, x, y): i = int((x + self.map_size/2) / self.grid_size) j = int((y + self.map_size/2) / self.grid_size) if 0 <= i < self.n and 0 <= j < self.n: return self.heightmap[i][j] return 0 def compute_gradient(self, x, y): h = self.grid_size hx1 = self.get_height(x+h, y) hx0 = self.get_height(x-h, y) hy1 = self.get_height(x, y+h) hy0 = self.get_height(x, y-h) dzdx = (hx1 - hx0) / (2*h) dzdy = (hy1 - hy0) / (2*h) return dzdx, dzdy def classify_terrain(self, x, y, window=0.1): heights = [] for dx in range(-5, 6): for dy in range(-5, 6): hx = self.get_height(x + dx*0.02, y + dy*0.02) heights.append(hx) if not heights: return 'unknown', 0 h_mean = sum(heights)/len(heights) h_var = sum((h-h_mean)**2 for h in heights) / len(heights) max_grad = 0 for i in range(-3, 4): dzdx, dzdy = self.compute_gradient(x + i*0.02, y + i*0.02) max_grad = max(max_grad, math.sqrt(dzdx**2 + dzdy**2)) if h_var < 1e-4 and max_grad < 0.05: return 'flat', h_var elif max_grad < 0.3: return 'rough', h_var elif max_grad < 0.7: return 'slope', max_grad else: return 'cliff', max_grad def find_foothold(self, x, y, search_radius=0.15): best_pos = (x, y) best_score = -1 for dx_i in range(-7, 8): for dy_i in range(-7, 8): fx = x + dx_i * 0.02 fy = y + dy_i * 0.02 dzdx, dzdy = self.compute_gradient(fx, fy) grad = math.sqrt(dzdx**2 + dzdy**2) h = self.get_height(fx, fy) # Score: flat + close + known height flat_score = max(0, 1 - grad/0.3) dist = math.sqrt((fx-x)**2 + (fy-y)**2) dist_score = max(0, 1 - dist/search_radius) score = 0.6 * flat_score + 0.4 * dist_score if score > best_score: best_score = score best_pos = (fx, fy, h) return best_pos, best_score tp = TerrainPerception() print("=" * 55) print(" Terrain Perception Simulation") print("=" * 55) for ttype in ['flat', 'slope', 'stairs', 'rough']: tp.generate_terrain(ttype) print(f"\n [{ttype.upper()} terrain]") test_pts = [(0, 0), (0.3, 0), (0.6, 0.3), (-0.3, 0.3)] for x, y in test_pts: h = tp.get_height(x, y) dzdx, dzdy = tp.compute_gradient(x, y) grad = math.sqrt(dzdx**2 + dzdy**2) cls, metric = tp.classify_terrain(x, y) print(f" ({x:.1f},{y:.1f}): h={h:.4f}m grad={grad:.3f} class={cls}") # Foothold search print(f"\n [Foothold Search on rough terrain]") tp.generate_terrain('rough') targets = [(0.2, 0.1), (-0.1, 0.3), (0.4, -0.2)] for x, y in targets: fh, score = tp.find_foothold(x, y) print(f" Request ({x:.2f},{y:.2f}) -> foothold ({fh[0]:.3f},{fh[1]:.3f},{fh[2]:.4f}) score={score:.3f}") print() print(" OK - Terrain perception simulation complete")

仿真结果:

======================================================= Terrain Perception Simulation ======================================================= [FLAT terrain] (0.0,0.0): h=0.0000m grad=0.000 class=flat (0.3,0.0): h=0.0000m grad=0.000 class=flat (0.6,0.3): h=0.0000m grad=0.000 class=flat (-0.3,0.3): h=0.0000m grad=0.000 class=flat [SLOPE terrain] (0.0,0.0): h=0.0000m grad=0.200 class=rough (0.3,0.0): h=0.0600m grad=0.200 class=slope (0.6,0.3): h=0.1200m grad=0.200 class=rough (-0.3,0.3): h=-0.0600m grad=0.300 class=slope [STAIRS terrain] (0.0,0.0): h=0.0000m grad=0.000 class=flat (0.3,0.0): h=0.0600m grad=0.750 class=cliff (0.6,0.3): h=0.1200m grad=0.750 class=cliff (-0.3,0.3): h=0.0000m grad=0.000 class=flat [ROUGH terrain] (0.0,0.0): h=-0.0065m grad=0.084 class=slope (0.3,0.0): h=0.0048m grad=0.118 class=slope (0.6,0.3): h=0.0008m grad=0.156 class=rough (-0.3,0.3): h=0.0102m grad=0.220 class=slope [Foothold Search on rough terrain] Request (0.20,0.10) -> foothold (0.200,0.100,0.0012) score=0.908 Request (-0.10,0.30) -> foothold (-0.060,0.280,-0.0064) score=0.812 Request (0.40,-0.20) -> foothold (0.320,-0.180,0.0116) score=0.733 OK - Terrain perception simulation complete

📡 地形感知方法

地形感知是适应性行走的前提:

📐 梯度与坡度计算

地形梯度使用有限差分法计算:

dz/dx = (h(x+Δx, y) - h(x-Δx, y)) / (2Δx)
dz/dy = (h(x, y+Δy) - h(x, y-Δy)) / (2Δy)

坡度角 = arctan(|∇h|),坡度方向 = atan2(dz/dy, dz/dx)

🎯 落脚点选择算法

安全落脚点需要满足:

  1. 局部平坦(梯度 < 阈值)
  2. 无障碍物(高度突变 < 阈值)
  3. 可达(在工作空间内)
  4. 接近默认位置(减少能量)

📡 传感器融合与高度图构建

高度图构建是多传感器融合的核心任务:

数据来源

Elevation Map建图流程

  1. 传感器数据→坐标变换到世界坐标
  2. 投影到2.5D网格(x-y平面+z值)
  3. 融合多帧数据(贝叶斯更新或卡尔曼滤波)
  4. 应用空间滤波去噪
  5. 计算导数图(梯度、曲率)

🗺️ 栅格地图表示

高度图用规则栅格表示,每个格子存储:

典型分辨率:2cm(平衡精度和计算量),2m×2m地图只需100×100=10K格点。

Memory = map_size2 / resolution2 × bytes_per_cell
= (2.0)2 / (0.02)2 × 16 = 160 KB

🧠 地形分类算法

基于高度图特征的地形分类:

特征计算方法区分能力
粗糙度局部高度方差flat vs rough
坡度梯度幅值flat vs slope
台阶性高度直方图双峰stairs vs other
硬度力-变形比solid vs soft
摩擦足端滑移检测dry vs slippery

可以用SVM或小型CNN实现实时分类,延迟<5ms。

🎯 高级落脚点规划

落脚点规划是行走安全的最后保障:

评分函数

Score = w1·flatness + w2·closeness + w3·clearance + w4·stability

Flatness指标

在落脚点周围的局部窗口内:

flatness = 1 - max(|slope|) / slope_threshold

Clearance指标

确保摆动轨迹不碰撞:

clearance = min_z_trajectory - h(x,y) > safety_margin

📚 本课参考与延伸

核心概念回顾

实现建议

  1. 先用Python/MATLAB验证算法正确性
  2. 然后在物理引擎(PyBullet/MuJoCo)中测试
  3. 最后在真实机器人上部署,使用域随机化增强鲁棒性

常见问题

📝 练习

  1. 修改仿真参数,观察系统行为的变化。
  2. 实现本课核心算法的改进版本。
  3. 将本课方法与其他课的方法组合,设计复合控制器。
  4. 分析算法在不同条件下的鲁棒性。
  5. 设计实验验证仿真结果的正确性。
🏆
地形感知者

掌握高度图构建、地形分类和落脚点选择

四足机器人课程 · 第13课/30 · 返回目录