Files
Docs/DevLogs/Day20.md
2025-12-31 16:18:28 +08:00

10 KiB
Raw Permalink Blame History

Day 20 - 性能瓶颈深度分析与优化

日期: 2025-12-24
主题: 解决导盲系统开启后卡顿问题


问题诊断

症状

  • 导盲系统一开启就卡顿、不流畅、有延迟
  • GPU 利用率仅 7%
  • 帧处理 FPS 仅 3-4

确认的瓶颈(经过再三分析)

瓶颈 影响 状态
过量日志输出 _detect_obstacles 每次调用输出 20+ 行 logger.info 已修复
GPU Semaphore 限流 默认只允许 2 个并发 GPU 调用 已修复
检测串行执行 盲道和障碍物检测顺序执行 已修复

实施的优化

1. 精简日志输出 (workflow_blindpath.py)

修改前:每次障碍物检测输出 20+ 行日志

logger.info(f"[_detect_obstacles] 开始执行...")
logger.info(f"[_detect_obstacles] 调用...")
for obj in detected_obstacles:
    logger.info(f"物体 {i+1}...")
    logger.info(f"  - 类别: ...")
    logger.info(f"  - 面积: ...")
    # 每个障碍物 5-6 行!

修改后:只输出一行摘要(每 30 帧)

if detected_obstacles and self.frame_counter % 30 == 0:
    names = [o.get('name', '?') for o in detected_obstacles[:3]]
    logger.info(f"[障碍物] 检测到 {len(detected_obstacles)} 个: {names}")

2. 增加 GPU 并发槽位 (obstacle_detector_client.py)

# 修改前
GPU_SLOTS = int(os.getenv("AIGLASS_GPU_SLOTS", "2"))

# 修改后
GPU_SLOTS = int(os.getenv("AIGLASS_GPU_SLOTS", "4"))

3. GPU 并行检测 (gpu_parallel.py)

使用 CUDA Stream 让盲道检测和障碍物检测并行执行。

4. TTS WebSocket 断连修复 (app_main.py)

问题TTS 语音有时不播放,日志显示 [TTS->WS] Buffering TTS audio

原因set_tts_websocket(ws) 只在 start_ai_with_text() 时调用WebSocket 重连后引用丢失

修复:在 ws_audio 连接建立时立即保存引用

@app.websocket("/ws_audio")
async def ws_audio(ws: WebSocket):
    global esp32_audio_ws
    esp32_audio_ws = ws
    from audio_stream import set_tts_websocket
    set_tts_websocket(ws)  # Day 20: 连接时立即保存
    await ws.accept()

修改文件

文件 修改
workflow_blindpath.py 精简日志输出
obstacle_detector_client.py GPU 槽位 2→4
gpu_parallel.py 新建CUDA Stream 并行
app_main.py TTS 修复、性能诊断

预期效果

  • 日志 I/O 减少 95%:每次检测从 20 行减到 1 行
  • GPU 并发能力翻倍4 槽位 vs 2 槽位
  • 检测延迟减半:并行执行 vs 串行执行

可视化网页优化

UI 改进

优化项 说明
IMU 浮窗 宽度 600px添加可折叠功能
折叠状态优化 折叠后只显示标题和按钮,完全隐藏 3D 模型和数据面板
Badge 动画 连接中闪烁 (blink)、已连接脉冲 (pulse)
按钮美化 渐变背景 + 悬停上浮效果
移动端适配 @media 600px/1100px 响应式布局
状态中文化 📷 已连接 替代 Camera: connected
底部边界修复 移除 .chat { height: 100vh } 解决超出问题
背景色统一 .stage 使用 var(--card) 与右侧一致
Favicon 添加 添加 /static/favicon.png

折叠功能实现

/* 折叠状态 - 只显示标题和按钮 */
.imu-float.collapsed {
  width: 180px;
  height: 40px;
  overflow: hidden;
}
.imu-float.collapsed .imu-row,
.imu-float.collapsed #imu_top_status {
  display: none !important;
}
// JS 折叠逻辑
document.addEventListener('DOMContentLoaded', () => {
  const imuFloat = document.getElementById('imuFloat');
  const imuToggle = document.getElementById('imuToggle');
  imuToggle.onclick = function(e) {
    const isCollapsed = imuFloat.classList.toggle('collapsed');
    this.textContent = isCollapsed ? '+' : '';
  };
});

修改文件

文件 修改
templates/index.html 样式优化、折叠按钮、移动端适配、底部边界修复、Favicon
static/main.js setBadge 支持 connecting、折叠逻辑、数据面板优化
static/favicon.png 新增网站图标

待验证

部署后观察:

[PERF] 帧:60 | 客户端FPS:?? | 帧间隔:??ms | 广播:??ms | 导航:??ms

目标:导航 耗时 < 80ms


Day 20 追加修复2025-12-24 下午)

问题复现

上午优化后,导航仍有严重卡顿。日志分析发现:

帧数 导航耗时
120 245.7ms
240 1410.1ms
420 1571.5ms
660 1766.0ms

根因: CUDA Stream 并行检测未生效,两个模型仍串行执行。

追加修复

文件 修改
workflow_blindpath.py UNIFIED_DETECTION_INTERVAL 10→20帧
workflow_blindpath.py OBSTACLE_CACHE_DURATION_FRAMES 12→20帧
gpu_parallel.py 移除无效 CUDA Stream改用 ThreadPoolExecutor 真正并行

预期效果

  • 检测频率减半 → GPU 负载降低
  • 线程池并行 → 盲道和障碍物检测真正同时执行
  • 导航耗时目标:稳定在 200ms 以下

Day 20 可视化性能优化(下午)

问题复现

追加修复后,导航耗时仍为 267-501ms。服务器资源分析发现

指标 含义
app_main.py CPU 120% 超过1核但受 Python GIL 限制
GPU 使用率 8% GPU 等待 CPU利用率低
可视化耗时 200-300ms 主要瓶颈!

根因: _draw_visualizations 中的 numpy 逐像素半透明混合非常耗 CPU。

优化内容

文件 修改
workflow_blindpath.py mask 半透明填充 → 轮廓绘制
workflow_blindpath.py 移除 image.copy() 避免复制开销

代码对比

# 修改前(慢 ~200-300ms
- for c in range(3):
-     local_region[:, :, c] = np.where(
-         binary_mask > 0,
-         (1 - alpha) * local_region[:, :, c] + alpha * color_overlay[:, :, c],
-         local_region[:, :, c]
-     )

# 修改后(快 ~5-10ms
+ cv2.polylines(image, [points], isClosed=True, color=color, thickness=thickness)

预期效果

  • 可视化耗时200-300ms → ~10-20ms
  • 导航总耗时目标:~100-150ms

Day 20 Numba 多核加速

问题背景

Python GIL 导致无法利用多核 CPU。即使使用 ThreadPoolExecutorCPU 密集型的 numpy 操作也只能使用单核。

解决方案

引入 Numba JIT 编译,将 Python 代码编译为机器码并启用多核并行:

from numba import jit, prange

@jit(nopython=True, parallel=True, cache=True)
def compute_mask_stats_numba(mask: np.ndarray) -> tuple:
    for i in prange(h):  # 自动多核并行
        for j in range(w):
            if mask[i, j] > 0:
                ...

新增文件

文件 说明
numba_utils.py Numba 加速工具函数mask 像素计数、统计、交集计算

修改内容

文件 修改
obstacle_detector_client.py 使用 Numba 加速 mask 统计和交集计算
app_main.py 启动时预热 Numba JIT 编译

预期效果

  • 多核利用率提升:从单核 ~120% 分摊到多核
  • mask 操作加速numpy 操作 → Numba 编译后 10-100x 提升
  • 首次调用无延迟:启动时预热 JIT 编译

Day 20 TensorRT 加速集成 (17:30)

问题背景

为了进一步加速 GPU 推理,将 YOLO 模型导出为 TensorRT 引擎。

实施内容

步骤 说明
模型导出 yolo-seg.engineyoloe-11l-seg.enginetrafficlight.engine (FP16)
工具函数 model_utils.py - get_best_model_path() 自动选择 .engine
代码适配 全部模型加载点改用工具函数

遇到的问题与修复

问题1.to().fuse() 不兼容 TensorRT

TypeError: model='model/yolo-seg.engine' should be a *.pt PyTorch model

原因TensorRT 引擎已经在 GPU 上,不能调用 .to("cuda").fuse()

修复:添加 is_tensorrt_engine() 检测函数,条件跳过

文件 修改
model_utils.py 新增 is_tensorrt_engine() 函数
app_main.py 跳过 .to().fuse()
models.py 跳过 .to().fuse()
yoloe_backend.py 跳过 .to()get_text_pe()
obstacle_detector_client.py 跳过 .to().fuse()
workflow_crossstreet.py 跳过 .to()

问题2StopIteration 错误

print(f"[NAVIGATION] 模型设备: {next(obstacle_detector.model.parameters()).device}")
StopIteration

原因TensorRT 引擎没有 .parameters() 迭代器

修复:检测 TensorRT 模式时跳过设备打印

问题3推理尺寸不匹配

[GPU_PARALLEL] 盲道检测失败: input size torch.Size([1, 3, 640, 640]) not equal to max model size (1, 3, 480, 480)

原因TensorRT 引擎用 480x480 导出,但代码硬编码 imgsz=640

修复:统一使用环境变量 AIGLASS_YOLO_IMGSZ=480

文件 修改
yoloe_backend.py 默认 imgsz 改为从环境变量读取
yolomedia.py 移除 4 处硬编码 imgsz=640

修复后状态

服务启动成功TensorRT 引擎正常加载:

[NAVIGATION] TensorRT 引擎已加载,跳过 .to() 和 .fuse()
[TRT] Loaded engine size: 140 MiB

⚠️ 待验证问题 (Day 21 继续)

1. 语音识别停止响应 🔴

症状:停止盲道导航后,语音指令不再被识别

日志分析

  • 音频仍在接收:[AUDIO] 📥 Received: 8900 packets...
  • 但没有 ASR 输出

可能原因

  • ASR 状态逻辑问题
  • 需要检查 state=CHAT 时 ASR 是否正常处理

2. TensorRT 预热警告(非致命)

[NAVIGATION] 模型预热失败: input size torch.Size([1, 3, 640, 640]) not equal to max model size (1, 3, 480, 480)

预热代码尝试用 640x640但引擎用 480 导出。实际推理正常,仅预热失败。

3. 画面延迟约 2 秒

导航启动后画面流畅但有约 2 秒延迟,需进一步分析是网络还是处理延迟。