You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
software/update_handover_doc.py

246 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python3
"""
更新声源分析模块_项目交接文档.docx
将本次2026-05-06完成的多通道离线演示等工作追加到文档中。
"""
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
import os
def add_heading(doc, text, level=1):
"""添加标题段落"""
p = doc.add_paragraph()
run = p.add_run(text)
run.bold = True
if level == 1:
run.font.size = Pt(16)
elif level == 2:
run.font.size = Pt(14)
else:
run.font.size = Pt(12)
p.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
return p
def add_bullet(doc, text, indent=0):
"""添加项目符号段落"""
p = doc.add_paragraph(style='List Bullet')
p.paragraph_format.left_indent = Pt(indent * 10)
p.add_run(text)
return p
def add_normal(doc, text):
p = doc.add_paragraph()
p.add_run(text)
return p
def main():
doc_path = '声源分析模块_项目交接文档.docx'
doc = Document(doc_path)
# ========================================================================
# 1. 在 "2.5 代码 Bug 修复记录" 之后插入 2.6 小节
# ========================================================================
insert_idx = None
for i, para in enumerate(doc.paragraphs):
if para.text.strip().startswith('2.5'):
# 找到 2.5 的最后一项CMakeLists.txt 那行)之后
pass
if para.text.strip().startswith('三、架构设计'):
insert_idx = i
break
if insert_idx is None:
print("ERROR: 未找到插入点(三、架构设计)")
return
# 由于 python-docx 不支持在特定索引插入段落到 body
# 我们采用另一种策略:将新内容作为独立段落追加到文档末尾,
# 然后手动调整顺序。但更简单的方法是:直接在 2.5 和 三 之间添加。
# python-docx 的 add_paragraph 总是追加到末尾,所以我们需要操作 XML。
# 为了避免 XML 操作复杂性,我们直接将整个文档重新组织:
# 读取所有段落文本 -> 在合适位置插入新文本 -> 重新写入。
# 实际上,对于 docx最安全的方式是
# 保存一份副本,然后在新段落要插入的位置,使用 paragraphs 列表的 _element 进行 insert。
paragraphs = doc.paragraphs
# 找到 2.5 最后一行CMakeLists.txt的索引
idx_after_25 = None
for i, p in enumerate(paragraphs):
if 'CMakeLists.txt' in p.text and 'MinGW' in p.text:
idx_after_25 = i + 1
break
if idx_after_25 is None:
# 备用:找到 "三、架构设计"
for i, p in enumerate(paragraphs):
if p.text.strip().startswith('三、架构设计'):
idx_after_25 = i
break
# 构建要插入的新段落列表 (text, style_name)
new_sections = []
# ---- 2.6 离线演示与多通道验证 ----
new_sections.append(('2.6 离线演示与多通道验证', 'Heading 2'))
new_sections.append(('已完成完整的离线演示程序tests/demo_offline.cpp支持单通道分类与多通道阵列分类+方位角+距离)一体化测试:', None))
new_sections.append(('', 'List Bullet'))
# 修正:我们无法在 add_paragraph 之后修改之前的内容,所以换个策略
# 直接在 XML 层级插入
# 为了简化,我们把所有段落文本收集起来,重建文档
all_texts = []
for p in doc.paragraphs:
style = p.style.name if p.style else None
all_texts.append((p.text, style))
# 找到插入位置
insert_pos = None
for i, (text, style) in enumerate(all_texts):
if text.strip().startswith('三、架构设计'):
insert_pos = i
break
if insert_pos is None:
print("ERROR: 未找到 '三、架构设计'")
return
# 准备新内容
new_content = []
new_content.append(('2.6 离线演示与多通道验证', 'Heading 2'))
new_content.append(('已完成完整的离线演示程序tests/demo_offline.cpp支持单通道分类与多通道阵列分类+方位角+距离)一体化测试:', None))
new_content.append(('• 单通道验证dataset/val 共 40 个文件,分类准确率 100%ambient/artillery/explosion/gunshot 各 10 个)。', 'List Bullet'))
new_content.append(('• 多通道模拟测试scripts/generate_multichannel_test.py 可按指定方位角360°和距离11000m生成 4 通道十字阵 WAV。', 'List Bullet'))
new_content.append(('• 多通道验证结果5 组典型工况):', 'List Bullet'))
new_content.append((' 方位角:全部 0° 误差GCC-PHAT 对模拟数据精度极高)。', 'List Bullet'))
new_content.append((' 距离:最大误差 18.2m@300m约 6%),其余 < 2m。', 'List Bullet'))
new_content.append((' 分类:全部正确识别为 gunshot置信度 0.770.98。', 'List Bullet'))
new_content.append(('• 一键演示脚本run_demo.bat 自动执行核心单元测试 → ONNX 快速推理 → 完整离线流水线。', 'List Bullet'))
new_content.append(('2.7 C++/Python 特征提取严格对齐', 'Heading 2'))
new_content.append(('为保证 C++ 推理结果与 Python 训练一致,完成了特征提取全流程对齐:', None))
new_content.append(('• 导出 librosa 0.10.x 的 Mel 滤波器组到二进制文件 models/mel_filter_bank.bin64×1025C++ 加载后不再自行构造。', 'List Bullet'))
new_content.append(('• 统一参数Hann 窗、preemphasis=0.97、n_fft=2048、hop=512、center=False、pad_to=63 framesedge 填充)。', 'List Bullet'))
new_content.append(('• 验证脚本scripts/verify_feature_consistency.py 对比 C++ 与 Python 输出,最大差异 < 0.008。', 'List Bullet'))
# 插入
all_texts = all_texts[:insert_pos] + new_content + all_texts[insert_pos:]
# ========================================================================
# 2. 更新 "五、待办事项与下一步计划"
# ========================================================================
# 找到第五部分的起止范围
section5_start = None
section5_end = None
for i, (text, style) in enumerate(all_texts):
if text.strip().startswith('五、待办事项与下一步计划'):
section5_start = i
elif section5_start is not None and text.strip().startswith('六、'):
section5_end = i
break
if section5_start is not None and section5_end is not None:
# 替换第五部分的内容
new_section5 = []
new_section5.append(('五、待办事项与下一步计划', 'Heading 1'))
new_section5.append(('【已完成】', 'Heading 2'))
new_section5.append(('• 单通道分类准确率提升至 100%(修复 Mel 滤波器、padding、window、ONNX NCHW layout 等 4 处不一致)。', 'List Bullet'))
new_section5.append(('• GCC-PHAT 方位估计与 SPL 距离估计集成到离线 demo使用模拟 4ch WAV 验证通过。', 'List Bullet'))
new_section5.append(('• 生成 scripts/generate_multichannel_test.py 及 run_demo.bat 一键演示。', 'List Bullet'))
new_section5.append(('【待完成】', 'Heading 2'))
new_section5.append(('• [P0] 实机部署:将编译通过的节点部署到 P600 机载电脑Jetson / x86验证 ROS 话题发布与订阅。', 'List Bullet'))
new_section5.append(('• [P0] 真实麦克风阵列驱动:接入 4 通道 USB / I2S 麦克风阵列,录制真实环境枪声/炮声样本。', 'List Bullet'))
new_section5.append(('• [P1] 数据集扩充:收集真实场景样本(含环境噪声、混响、多声源叠加),重新训练以降低合成数据过拟合。', 'List Bullet'))
new_section5.append(('• [P1] yaml-cpp CMake 链接修复:当前 MinGW 动态链接出现 __imp__ 未解析符号,需查明是 ABI 不兼容还是导入库生成错误。', 'List Bullet'))
new_section5.append(('• [P2] 距离估计 SPL 校准:当前合成数据使用固定 offset=60dB真实场景需根据麦克风灵敏度数据 sheet 校准。', 'List Bullet'))
new_section5.append(('• [P2] Pipeline 类集成到 demo当前 demo_offline 绕过 Pipeline 直接实例化模块,后续应统一走 Pipeline 以验证 YAML 配置加载。', 'List Bullet'))
new_section5.append(('• [P2] 俯仰角估计:当前 GCC-PHAT 仅输出水平面方位角,若阵列有高度差可解俯仰角。', 'List Bullet'))
all_texts = all_texts[:section5_start] + new_section5 + all_texts[section5_end:]
# ========================================================================
# 3. 更新 "七、文件路径索引"
# ========================================================================
section7_start = None
section7_end = None
for i, (text, style) in enumerate(all_texts):
if text.strip().startswith('七、文件路径索引'):
section7_start = i
elif section7_start is not None and text.strip().startswith('八、'):
section7_end = i
break
if section7_start is not None and section7_end is not None:
new_section7 = []
new_section7.append(('七、文件路径索引', 'Heading 1'))
new_section7.append(('项目根目录software/src/drone-software/src/acoustic/', None))
new_section7.append(('• 核心算法include/acoustic_analyzer/core/ & src/core/', 'List Bullet'))
new_section7.append(('• IO 抽象include/acoustic_analyzer/io/ & src/io/', 'List Bullet'))
new_section7.append(('• ROS 封装include/acoustic_analyzer/ros/ & src/ros/', 'List Bullet'))
new_section7.append(('• 训练脚本scripts/train_classifier.py, export_onnx.py, verify_onnx.py', 'List Bullet'))
new_section7.append(('• 手机桥接scripts/mobile_audio_bridge.py, android_audio_sender.py', 'List Bullet'))
new_section7.append(('• 多通道生成scripts/generate_multichannel_test.py', 'List Bullet'))
new_section7.append(('• 特征对齐验证scripts/verify_feature_consistency.py, verify_val_accuracy.py', 'List Bullet'))
new_section7.append(('• 测试程序tests/test_core_lib.cpp, extract_mel_cpp.cpp, test_classifier_cpp.cpp, demo_offline.cpp', 'List Bullet'))
new_section7.append(('• 配置文件config/acoustic_params.yaml', 'List Bullet'))
new_section7.append(('• 模型权重models/gunshot_classifier.onnx, models/mel_filter_bank.bin, train_output/best_model.pth', 'List Bullet'))
new_section7.append(('• 数据集dataset/{train,val}/{ambient,gunshot,artillery,explosion}/', 'List Bullet'))
all_texts = all_texts[:section7_start] + new_section7 + all_texts[section7_end:]
# ========================================================================
# 4. 更新 "八、新增构建脚本说明"
# ========================================================================
section8_start = None
section8_end = None
for i, (text, style) in enumerate(all_texts):
if text.strip().startswith('八、新增构建脚本说明'):
section8_start = i
elif section8_start is not None and (text.strip().startswith('AI 助手') or text.strip().startswith('附录') or i == len(all_texts)-1):
section8_end = i if text.strip() else i
if i == len(all_texts)-1:
section8_end = len(all_texts)
break
if section8_start is not None and section8_end is None:
section8_end = len(all_texts)
if section8_start is not None and section8_end is not None:
new_section8 = []
new_section8.append(('八、新增构建脚本说明', 'Heading 1'))
new_section8.append(('• build_core_test.bat一键命令行编译适用于快速验证核心算法和 ONNX 推理', 'List Bullet'))
new_section8.append((' 编译目标test_core_lib.exe / extract_mel_cpp.exe / test_classifier_cpp.exe', 'List Bullet'))
new_section8.append(('• build_demo.bat编译离线演示程序 demo_offline.exe', 'List Bullet'))
new_section8.append((' 不依赖 yaml-cpp 和 ROS直接链接 ONNX Runtime + Eigen用于快速验证完整流水线。', 'List Bullet'))
new_section8.append(('• build_cmake_mingw.bat标准 CMake + MinGW Makefiles 构建流程', 'List Bullet'))
new_section8.append((' 自动将源码复制到 C:/temp/acoustic_src规避中文路径问题在 C:/temp/acoustic_build 构建,', 'List Bullet'))
new_section8.append((' 完成后将可执行文件复制回原目录。适用于需要标准 CMake 流程的场景。', 'List Bullet'))
new_section8.append(('• run_demo.bat一键运行完整演示', 'List Bullet'))
new_section8.append((' Step 1: 核心单元测试test_core_lib.exe', 'List Bullet'))
new_section8.append((' Step 2: ONNX 快速推理test_classifier_cpp.exe', 'List Bullet'))
new_section8.append((' Step 3: 离线流水线验证demo_offline.exe dataset/val', 'List Bullet'))
all_texts = all_texts[:section8_start] + new_section8 + all_texts[section8_end:]
# ========================================================================
# 重建文档
# ========================================================================
new_doc = Document()
for text, style_name in all_texts:
if not text and style_name == 'List Bullet':
# 空项目符号,跳过
continue
if style_name:
try:
p = new_doc.add_paragraph(text, style=style_name)
except:
p = new_doc.add_paragraph(text)
else:
p = new_doc.add_paragraph(text)
# 保留原始文档的页眉页脚等信息?简化处理:不保留
output_path = '声源分析模块_项目交接文档.docx'
new_doc.save(output_path)
print(f"[OK] 文档已更新: {output_path}")
if __name__ == '__main__':
main()