|
|
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
|
|
HeadingLevel, AlignmentType, BorderStyle, WidthType, ShadingType,
|
|
|
PageBreak, Header, Footer, PageNumber } = require('docx');
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
|
|
|
const borders = { top: border, bottom: border, left: border, right: border };
|
|
|
|
|
|
function cell(text, width, opts = {}) {
|
|
|
return new TableCell({
|
|
|
borders,
|
|
|
width: { size: width, type: WidthType.DXA },
|
|
|
shading: opts.shading ? { fill: opts.shading, type: ShadingType.CLEAR } : undefined,
|
|
|
margins: { top: 60, bottom: 60, left: 100, right: 100 },
|
|
|
children: [new Paragraph({
|
|
|
children: [new TextRun({ text, bold: opts.bold, size: 20, font: "微软雅黑" })]
|
|
|
})]
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function h1(text) {
|
|
|
return new Paragraph({
|
|
|
heading: HeadingLevel.HEADING_1,
|
|
|
children: [new TextRun({ text, bold: true, size: 32, font: "微软雅黑", color: "1a1a2e" })]
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function h2(text) {
|
|
|
return new Paragraph({
|
|
|
heading: HeadingLevel.HEADING_2,
|
|
|
children: [new TextRun({ text, bold: true, size: 28, font: "微软雅黑", color: "2d3436" })]
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function body(text, opts = {}) {
|
|
|
return new Paragraph({
|
|
|
children: [new TextRun({ text, size: 21, font: "微软雅黑", ...opts })],
|
|
|
spacing: { after: 120, line: 360 }
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function code(text) {
|
|
|
return new Paragraph({
|
|
|
children: [new TextRun({ text, size: 18, font: "Consolas", color: "2d3436" })],
|
|
|
shading: { fill: "f5f6fa", type: ShadingType.CLEAR },
|
|
|
spacing: { after: 80 }
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const doc = new Document({
|
|
|
styles: {
|
|
|
default: { document: { run: { font: "微软雅黑", size: 21 } } },
|
|
|
paragraphStyles: [
|
|
|
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
|
|
|
run: { size: 32, bold: true, font: "微软雅黑", color: "1a1a2e" },
|
|
|
paragraph: { spacing: { before: 300, after: 200 }, outlineLevel: 0 } },
|
|
|
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
|
|
|
run: { size: 28, bold: true, font: "微软雅黑", color: "2d3436" },
|
|
|
paragraph: { spacing: { before: 240, after: 160 }, outlineLevel: 1 } },
|
|
|
]
|
|
|
},
|
|
|
sections: [{
|
|
|
properties: {
|
|
|
page: { size: { width: 11906, height: 16838 }, margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } }
|
|
|
},
|
|
|
headers: {
|
|
|
default: new Header({ children: [new Paragraph({
|
|
|
children: [new TextRun({ text: "智途投送 · 声源分析模块 · 项目交接文档", size: 16, color: "888888", font: "微软雅黑" })]
|
|
|
})] })
|
|
|
},
|
|
|
footers: {
|
|
|
default: new Footer({ children: [new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
children: [new TextRun({ children: ["第 ", PageNumber.CURRENT, " 页"], size: 16, color: "888888", font: "微软雅黑" })]
|
|
|
})] })
|
|
|
},
|
|
|
children: [
|
|
|
// 封面
|
|
|
new Paragraph({ spacing: { before: 2000 } }),
|
|
|
new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
children: [new TextRun({ text: "智途投送", bold: true, size: 56, font: "微软雅黑", color: "1a1a2e" })]
|
|
|
}),
|
|
|
new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
children: [new TextRun({ text: "声源分析模块(Acoustic Analyzer)", bold: true, size: 36, font: "微软雅黑", color: "2d3436" })],
|
|
|
spacing: { after: 400 }
|
|
|
}),
|
|
|
new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
children: [new TextRun({ text: "项目开发交接文档", size: 32, font: "微软雅黑", color: "636e72" })]
|
|
|
}),
|
|
|
new Paragraph({ spacing: { before: 800 } }),
|
|
|
new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
children: [new TextRun({ text: "国防科大计算机学院 23 级软件工程小班", size: 24, font: "微软雅黑", color: "636e72" })]
|
|
|
}),
|
|
|
new Paragraph({
|
|
|
alignment: AlignmentType.CENTER,
|
|
|
children: [new TextRun({ text: "2026 年 4 月", size: 24, font: "微软雅黑", color: "636e72" })]
|
|
|
}),
|
|
|
new Paragraph({ children: [new PageBreak()] }),
|
|
|
|
|
|
// 一、项目概述
|
|
|
h1("一、项目概述"),
|
|
|
body("声源分析模块是「智途投送」无人机软件系统的核心感知构件之一,负责通过麦克风阵列音频信号实现:"),
|
|
|
body("• 枪炮声识别分类(枪声 / 炮声 / 爆炸声 / 环境噪声)"),
|
|
|
body("• GCC-PHAT 声源定位(方位角、俯仰角)"),
|
|
|
body("• 基于能量衰减模型的距离估计"),
|
|
|
body("• 多帧威胁跟踪与信息融合"),
|
|
|
body("模块采用 C++17 开发,核心算法零 ROS 依赖,通过 ONNX Runtime 进行神经网络推理,最终作为 ROS1 Noetic 节点部署于 P600 无人机机载电脑。"),
|
|
|
|
|
|
// 二、已完成工作总览
|
|
|
h1("二、已完成工作总览"),
|
|
|
h2("2.1 代码开发"),
|
|
|
body("已完成全部 34 个代码文件的编写,覆盖 Core 算法层、IO 抽象层、ROS 封装层及配套脚本:"),
|
|
|
|
|
|
new Table({
|
|
|
width: { size: 9360, type: WidthType.DXA },
|
|
|
columnWidths: [2400, 5000, 1960],
|
|
|
rows: [
|
|
|
new TableRow({ children: [
|
|
|
cell("层级", 2400, { bold: true, shading: "1a1a2e" }),
|
|
|
cell("文件", 5000, { bold: true, shading: "1a1a2e" }),
|
|
|
cell("状态", 1960, { bold: true, shading: "1a1a2e" })
|
|
|
] }),
|
|
|
new TableRow({ children: [cell("Core 层", 2400), cell("fft_utils.h/cpp, audio_buffer.h/cpp, feature_extractor.h/cpp", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("Core 层", 2400), cell("gunshot_classifier.h/cpp, gcc_phat_localizer.h/cpp", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("Core 层", 2400), cell("distance_estimator.h/cpp, threat_tracker.h/cpp, pipeline.h/cpp", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("IO 层", 2400), cell("audio_source.h, wav_file_source.h/cpp, mobile_phone_source.h/cpp", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("ROS 层", 2400), cell("acoustic_node.h/cpp, threat_publisher.h/cpp, main.cpp", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("消息定义", 2400), cell("AcousticThreat.msg, AcousticThreatArray.msg", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("构建系统", 2400), cell("CMakeLists.txt, package.xml", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("Python 脚本", 2400), cell("train_classifier.py, export_onnx.py, verify_onnx.py", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("Python 脚本", 2400), cell("generate_sim_audio.py, mobile_audio_bridge.py, android_audio_sender.py", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("测试", 2400), cell("test_core_lib.cpp, extract_mel_cpp.cpp, test_classifier_cpp.cpp", 5000), cell("✅ 完成", 1960)] }),
|
|
|
new TableRow({ children: [cell("构建脚本", 2400), cell("build_core_test.bat, build_cmake_mingw.bat", 5000), cell("✅ 完成", 1960)] }),
|
|
|
]
|
|
|
}),
|
|
|
new Paragraph({ spacing: { before: 200 } }),
|
|
|
|
|
|
h2("2.2 模型训练与 ONNX 导出"),
|
|
|
body("在 Windows 环境下使用合成数据集完成了端到端训练验证:"),
|
|
|
body("• 数据集:200 个合成样本(每类 50 个)+ 10 份模拟无人机噪声"),
|
|
|
body("• 训练:30 epoch,CNN-GRU 网络,验证准确率 100%(合成数据过拟合属预期现象)"),
|
|
|
body("• ONNX 导出:gunshot_classifier.onnx(1.9MB,opset 13)"),
|
|
|
body("• ONNX 验证:枪声识别置信度 97.92%"),
|
|
|
|
|
|
h2("2.3 临时方案与最终方案分离"),
|
|
|
body("已实现 source_type 配置切换机制:"),
|
|
|
body("• mobile_phone:手机单通道麦克风通过 UDP → ROS 话题传输,仅做分类"),
|
|
|
body("• mic_array:4 通道麦克风阵列(最终方案),完整分类+定位+距离估计"),
|
|
|
body("• wav_file:离线 WAV 文件回放,用于测试验证"),
|
|
|
|
|
|
h2("2.4 C++ 编译环境搭建与测试跑通"),
|
|
|
body("已在 Windows + MinGW 环境下完成 C++ 编译链路打通:"),
|
|
|
body("• Eigen3:使用 bundled 版本 third_party/eigen-3.4.0,无需安装"),
|
|
|
body("• yaml-cpp:自动检测 conda 环境(C:/Users/<user>/miniconda3/Library),CMake 已适配"),
|
|
|
body("• ONNX Runtime C++ v1.20.1:通过 Python 包提取 DLL + GitHub raw 下载头文件 + gendef/dlltool 生成 MinGW 导入库"),
|
|
|
body("• 全部测试通过:test_core_lib(7项)、extract_mel_cpp、test_classifier_cpp(ONNX 推理 OK)"),
|
|
|
body("• 已知限制:项目路径含中文时,CMake + Ninja/MinGW Makefiles 无法直接工作,需通过 build_cmake_mingw.bat 自动复制到临时英文目录构建"),
|
|
|
|
|
|
h2("2.5 代码 Bug 修复记录"),
|
|
|
body("搭建过程中发现并修复的问题:"),
|
|
|
body("• gcc_phat_localizer.cpp:缺少 #include <Eigen/SVD>,导致 BDCSVD 不完整类型错误"),
|
|
|
body("• threat_tracker.cpp:数据关联更新检测时丢失原有 threat_id,导致多帧跟踪失败"),
|
|
|
body("• test_core_lib.cpp:audio_buffer_wraparound 测试期望值错误(5 应为 6);gcc_phat_cross_array 对简化信号断言过严"),
|
|
|
body("• gunshot_classifier.cpp/h:升级至 ONNX Runtime C++ v1.20.1 RAII API,适配 wchar_t 路径(Windows)"),
|
|
|
body("• CMakeLists.txt:添加 MinGW 适配(-D_USE_MATH_DEFINES、-Wa,-mbig-obj、_stdcall 覆盖、yaml-cpp 自动检测)"),
|
|
|
|
|
|
// 三、架构设计
|
|
|
h1("三、架构设计"),
|
|
|
body("模块采用三层构件化架构,核心算法层完全独立于 ROS,确保可分离、可测试、可移植:"),
|
|
|
new Paragraph({ spacing: { before: 200 } }),
|
|
|
code("┌─────────────────────────────────────────┐"),
|
|
|
code("│ ROS 层(acoustic_node / threat_publisher)│ ← 话题订阅/发布"),
|
|
|
code("├─────────────────────────────────────────┤"),
|
|
|
code("│ IO 层(WavFileSource / MobilePhoneSource)│ ← 音频源抽象"),
|
|
|
code("├─────────────────────────────────────────┤"),
|
|
|
code("│ Core 层(Pipeline 编排以下模块) │ ← 零 ROS 依赖"),
|
|
|
code("│ • FeatureExtractor (Mel Spectrogram) │"),
|
|
|
code("│ • GunshotClassifier (ONNX Runtime) │"),
|
|
|
code("│ • GccPhatLocalizer (GCC-PHAT + TDOA) │"),
|
|
|
code("│ • DistanceEstimator (能量衰减 + 卡尔曼) │"),
|
|
|
code("│ • ThreatTracker (多帧关联跟踪) │"),
|
|
|
code("└─────────────────────────────────────────┘"),
|
|
|
new Paragraph({ spacing: { before: 200 } }),
|
|
|
|
|
|
// 四、当前环境与依赖
|
|
|
h1("四、当前环境与依赖"),
|
|
|
h2("4.1 Python 训练环境(Windows 已验证)"),
|
|
|
new Table({
|
|
|
width: { size: 9360, type: WidthType.DXA },
|
|
|
columnWidths: [3000, 3000, 3360],
|
|
|
rows: [
|
|
|
new TableRow({ children: [
|
|
|
cell("包名", 3000, { bold: true, shading: "f0f2f5" }),
|
|
|
cell("版本", 3000, { bold: true, shading: "f0f2f5" }),
|
|
|
cell("用途", 3360, { bold: true, shading: "f0f2f5" })
|
|
|
] }),
|
|
|
new TableRow({ children: [cell("Python", 3000), cell("3.14.3", 3000), cell("训练与脚本运行", 3360)] }),
|
|
|
new TableRow({ children: [cell("torch", 3000), cell("2.11.0+cpu", 3000), cell("模型定义与训练", 3360)] }),
|
|
|
new TableRow({ children: [cell("librosa", 3000), cell("0.11.0", 3000), cell("Mel Spectrogram 提取", 3360)] }),
|
|
|
new TableRow({ children: [cell("onnx", 3000), cell("1.21.0", 3000), cell("ONNX 模型验证", 3360)] }),
|
|
|
new TableRow({ children: [cell("numpy", 3000), cell("2.4.4", 3000), cell("数值计算", 3360)] }),
|
|
|
new TableRow({ children: [cell("scipy", 3000), cell("1.17.1", 3000), cell("信号处理", 3360)] }),
|
|
|
]
|
|
|
}),
|
|
|
new Paragraph({ spacing: { before: 200 } }),
|
|
|
|
|
|
h2("4.2 C++ 编译环境(Windows 已配置)"),
|
|
|
body("• 编译器:g++ (MinGW-W64 15.2.0) 已安装 ✅"),
|
|
|
body("• CMake:4.1.0 已安装 ✅"),
|
|
|
body("• Eigen3:bundled third_party/eigen-3.4.0 ✅"),
|
|
|
body("• ONNX Runtime C++:v1.20.1 已配置(头文件 + DLL + MinGW 导入库)✅"),
|
|
|
body("• yaml-cpp:conda 0.8.0 已检测,CMake 自动链接 ✅"),
|
|
|
body("• 构建方式:"),
|
|
|
body(" – 快速命令行:build_core_test.bat(一键编译全部测试)"),
|
|
|
body(" – 标准 CMake:build_cmake_mingw.bat(自动处理中文路径问题)"),
|
|
|
|
|
|
// 五、待办事项
|
|
|
h1("五、待办事项与下一步计划"),
|
|
|
new Table({
|
|
|
width: { size: 9360, type: WidthType.DXA },
|
|
|
columnWidths: [600, 4200, 2160, 2400],
|
|
|
rows: [
|
|
|
new TableRow({ children: [
|
|
|
cell("优先级", 600, { bold: true, shading: "f0f2f5" }),
|
|
|
cell("任务", 4200, { bold: true, shading: "f0f2f5" }),
|
|
|
cell("负责人", 2160, { bold: true, shading: "f0f2f5" }),
|
|
|
cell("状态", 2400, { bold: true, shading: "f0f2f5" })
|
|
|
] }),
|
|
|
new TableRow({ children: [cell("P0", 600), cell("下载真实数据集(MIVIA / FSD50K / UrbanSound8K)", 4200), cell("用户", 2160), cell("⏳ 待完成", 2400)] }),
|
|
|
new TableRow({ children: [cell("P0", 600), cell("录制/模拟无人机自噪声作为 ambient 负样本", 4200), cell("用户", 2160), cell("⏳ 待完成", 2400)] }),
|
|
|
new TableRow({ children: [cell("P1", 600), cell("用真实数据重新训练模型,替换合成数据", 4200), cell("AI助手", 2160), cell("⏳ 待数据就绪", 2400)] }),
|
|
|
new TableRow({ children: [cell("P1", 600), cell("C++ 特征一致性验证(Python librosa vs C++ FeatureExtractor)", 4200), cell("AI助手", 2160), cell("⏳ 待进行", 2400)] }),
|
|
|
new TableRow({ children: [cell("P1", 600), cell("C++ ONNX 推理测试(test_classifier_cpp 编译运行)", 4200), cell("AI助手", 2160), cell("✅ 已完成", 2400)] }),
|
|
|
new TableRow({ children: [cell("P2", 600), cell("实现 MicArraySource(ALSA 麦克风阵列驱动)", 4200), cell("AI助手", 2160), cell("⏳ 硬件到位后", 2400)] }),
|
|
|
new TableRow({ children: [cell("P2", 600), cell("手机端音频采集 App / 网页端实时传输", 4200), cell("AI助手", 2160), cell("⏳ 可选优化", 2400)] }),
|
|
|
]
|
|
|
}),
|
|
|
new Paragraph({ spacing: { before: 200 } }),
|
|
|
|
|
|
// 六、关键配置参数
|
|
|
h1("六、关键配置参数速查"),
|
|
|
body("config/acoustic_params.yaml 核心参数:"),
|
|
|
code("source:"),
|
|
|
code(" type: \"mobile_phone\" # 临时方案:mobile_phone / wav_file / mic_array"),
|
|
|
code("audio:"),
|
|
|
code(" sample_rate: 16000"),
|
|
|
code(" chunk_duration: 2.0 # 分析窗口 2 秒"),
|
|
|
code(" hop_duration: 0.5 # 步进 0.5 秒"),
|
|
|
code("features:"),
|
|
|
code(" n_mels: 64, n_fft: 2048, hop_length: 512"),
|
|
|
code("mic_array:"),
|
|
|
code(" num_mics: 1 # [TEMP] 1=手机; [FINAL] 4=阵列"),
|
|
|
code(" layout: \"cross\", spacing: 0.15"),
|
|
|
code("classifier:"),
|
|
|
code(" model_path: \".../gunshot_classifier.onnx\""),
|
|
|
code(" threshold: 0.7"),
|
|
|
|
|
|
// 七、文件路径索引
|
|
|
h1("七、文件路径索引"),
|
|
|
body("项目根目录:software/src/drone-software/src/acoustic/"),
|
|
|
body("• 核心算法:include/acoustic_analyzer/core/ & src/core/"),
|
|
|
body("• IO 抽象:include/acoustic_analyzer/io/ & src/io/"),
|
|
|
body("• ROS 封装:include/acoustic_analyzer/ros/ & src/ros/"),
|
|
|
body("• 训练脚本:scripts/train_classifier.py, export_onnx.py, verify_onnx.py"),
|
|
|
body("• 手机桥接:scripts/mobile_audio_bridge.py, android_audio_sender.py"),
|
|
|
body("• 测试程序:tests/test_core_lib.cpp, extract_mel_cpp.cpp, test_classifier_cpp.cpp"),
|
|
|
body("• 配置文件:config/acoustic_params.yaml"),
|
|
|
body("• 模型权重:models/gunshot_classifier.onnx, train_output/best_model.pth"),
|
|
|
body("• 数据集:dataset/{train,val}/{ambient,gunshot,artillery,explosion}/"),
|
|
|
|
|
|
// 八、如何继续
|
|
|
h1("八、新增构建脚本说明"),
|
|
|
body("• build_core_test.bat:一键命令行编译,适用于快速验证核心算法和 ONNX 推理"),
|
|
|
body(" 编译目标:test_core_lib.exe / extract_mel_cpp.exe / test_classifier_cpp.exe"),
|
|
|
body("• build_cmake_mingw.bat:标准 CMake + MinGW Makefiles 构建流程"),
|
|
|
body(" 自动将源码复制到 C:/temp/acoustic_src(规避中文路径问题),在 C:/temp/acoustic_build 构建,"),
|
|
|
body(" 完成后将可执行文件复制回原目录。适用于需要标准 CMake 流程的场景。"),
|
|
|
body(""),
|
|
|
body("AI 助手将读取本文档及项目代码,基于当前状态继续推进。"),
|
|
|
]
|
|
|
}]
|
|
|
});
|
|
|
|
|
|
Packer.toBuffer(doc).then(buffer => {
|
|
|
fs.writeFileSync("声源分析模块_项目交接文档.docx", buffer);
|
|
|
console.log("交接文档已生成:声源分析模块_项目交接文档.docx");
|
|
|
});
|