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/generate_blog_doc.js

288 lines
20 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.

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 h3(text) {
return new Paragraph({
heading: HeadingLevel.HEADING_3,
children: [new TextRun({ text, bold: true, size: 24, font: "微软雅黑", color: "2d3436" })]
});
}
function body(text, opts = {}) {
return new Paragraph({
children: [new TextRun({ text, size: 21, font: "微软雅黑", ...opts })],
spacing: { after: 120, line: 360 }
});
}
function quote(text) {
return new Paragraph({
children: [new TextRun({ text, italics: true, size: 21, font: "微软雅黑", color: "636e72" })],
spacing: { after: 120, line: 360 },
indent: { left: 400 }
});
}
function code(text) {
return new Paragraph({
children: [new TextRun({ text, size: 18, font: "Consolas", color: "2d3436" })],
shading: { fill: "f5f6fa", type: ShadingType.CLEAR },
spacing: { after: 60 }
});
}
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 } },
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 24, bold: true, font: "微软雅黑", color: "2d3436" },
paragraph: { spacing: { before: 200, after: 120 }, outlineLevel: 2 } },
]
},
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: 600 } }),
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: "基于 C++ 的无人机声源分析模块设计与实现", bold: true, size: 40, font: "微软雅黑", color: "1a1a2e" })]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: "——「智途投送」战场末端补给系统的多模态感知实践", size: 26, font: "微软雅黑", color: "636e72" })],
spacing: { after: 400 }
}),
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: "作者:国防科大计算机学院 23 级软件工程小班", size: 21, font: "微软雅黑", color: "636e72" })]
}),
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new TextRun({ text: "2026 年 4 月", size: 21, font: "微软雅黑", color: "636e72" })],
spacing: { after: 600 }
}),
// 摘要
h1("摘要"),
body("在城市作战环境下战场末端补给的「最后一公里」始终是制约作战效能的关键瓶颈。本文介绍「智途投送」软件系统中声源分析模块Acoustic Analyzer的设计与实现过程。该模块通过麦克风阵列采集音频信号结合 GCC-PHAT 声源定位算法与 ONNX Runtime 神经网络推理引擎,实现了枪炮声的实时识别、方位估计与距离推算。模块采用 C++17 开发,遵循构件化设计原则,核心算法零 ROS 依赖,支持临时方案(手机单通道)与最终方案(麦克风阵列)的无缝切换。本文详细阐述了系统架构、核心算法、工程实践中的关键决策及踩坑记录,为同类嵌入式感知系统开发提供参考。"),
new Paragraph({ spacing: { before: 100 } }),
body("关键词无人机声源定位GCC-PHATONNX Runtime构件化设计战场感知", { bold: true }),
// 一、项目背景
h1("一、项目背景与需求分析"),
h2("1.1 战场末端补给的痛点"),
body("近年来多场局部战争的实战经验反复证明后勤补给的「最后一公里」已成为制约战场持续作战能力的核心瓶颈。2023 年以哈战争中以军在加沙城市巷战环境遭遇严重后勤困境2022 年俄乌冲突中,前线部队长期面临弹药、食品、急救药品严重短缺。传统有人运输在最后几公里内频繁遭遇炮火覆盖,伤亡率极高。即便引入无人机等无人平台,也因缺乏统一调度、智能路径规划和安全投放策略,末端配送效率低下、协调困难。"),
body("针对这一现实挑战,我们团队设计开发了「智途投送」智能化末端补给系统,通过无人机替代有人运输,结合多模态感知与智能路径规划,实现战场物资的安全精准投送。"),
h2("1.2 声源分析模块的定位"),
body("在「智途投送」系统的多模态感知架构中,声源分析模块与视觉分析模块、热成像分析模块并列,共同构成威胁感知层。其具体功能需求包括:"),
body("• 枪炮声识别:区分枪声、炮声、爆炸声与环境噪声"),
body("• 声源定位:通过麦克风阵列估计威胁源的方位角与俯仰角"),
body("• 距离估计:基于信号能量衰减模型推算威胁距离"),
body("• 威胁跟踪:对连续帧检测结果进行关联与生命周期管理"),
body("• 构件化设计:核心算法与 ROS 解耦,支持独立编译与测试"),
// 二、系统架构
h1("二、系统架构设计"),
h2("2.1 总体架构"),
body("模块采用三层架构设计,严格遵循「关注点分离」原则:"),
new Paragraph({ spacing: { before: 200 } }),
code("┌──────────────────────────────────────────┐"),
code("│ ROS 封装层acoustic_node │"),
code("│ - 话题订阅:/microphone_array/audio │"),
code("│ - 话题发布:/acoustic/threats │"),
code("├──────────────────────────────────────────┤"),
code("│ IO 抽象层AudioSource 接口) │"),
code("│ - WavFileSource (离线测试) │"),
code("│ - MobilePhoneSource (临时方案)[TEMP] │"),
code("│ - MicArraySource (最终方案)[FINAL] │"),
code("├──────────────────────────────────────────┤"),
code("│ Core 算法层(零 ROS 依赖) │"),
code("│ Pipeline → {FeatureExtractor, │"),
code("│ GunshotClassifier, │"),
code("│ GccPhatLocalizer, │"),
code("│ DistanceEstimator, │"),
code("│ ThreatTracker} │"),
code("└──────────────────────────────────────────┘"),
new Paragraph({ spacing: { before: 200 } }),
h2("2.2 构件化设计的工程意义"),
body("将 Core 层设计为纯 C++ 库(零 ROS 依赖)带来了显著的工程优势:"),
body("1. 可测试性:可在无 ROS 环境中运行单元测试CI/CD 友好"),
body("2. 可移植性:未来迁移至 ROS2 时,仅需重写 ROS 层Core 与 IO 层零改动"),
body("3. 可复用性Core 库可独立部署于地面站笔记本,用于离线数据分析"),
body("4. 可分离性:满足课程项目对「构件化」的要求,模块边界清晰"),
// 三、核心算法
h1("三、核心算法详解"),
h2("3.1 Mel Spectrogram 特征提取"),
body("由于项目采用 C++ 开发,无法直接使用 Python 生态的 librosa 库。我们在 C++ 端从零实现了完整的 Mel Spectrogram 提取流程:"),
body("预加重 → 分帧加窗Hamming→ FFTKiss FFT→ 功率谱 → Mel 滤波器组 → 对数压缩"),
body("实现中特别注意了与 Python librosa 的参数对齐:"),
new Table({
width: { size: 9360, type: WidthType.DXA },
columnWidths: [3000, 3000, 3360],
rows: [
new TableRow({ children: [
cell("参数", 3000, { bold: true, shading: "f0f2f5" }),
cell("C++ 值", 3000, { bold: true, shading: "f0f2f5" }),
cell("librosa 对应", 3360, { bold: true, shading: "f0f2f5" })
] }),
new TableRow({ children: [cell("sample_rate", 3000), cell("16000", 3000), cell("sr=16000", 3360)] }),
new TableRow({ children: [cell("n_fft", 3000), cell("2048", 3000), cell("n_fft=2048", 3360)] }),
new TableRow({ children: [cell("hop_length", 3000), cell("512", 3000), cell("hop_length=512", 3360)] }),
new TableRow({ children: [cell("n_mels", 3000), cell("64", 3000), cell("n_mels=64", 3360)] }),
new TableRow({ children: [cell("f_max", 3000), cell("8000.0", 3000), cell("fmax=8000", 3360)] }),
new TableRow({ children: [cell("preemphasis", 3000), cell("0.97", 3000), cell("coef=0.97", 3360)] }),
new TableRow({ children: [cell("window", 3000), cell("Hamming", 3000), cell("window='hamming'", 3360)] }),
new TableRow({ children: [cell("center", 3000), cell("false", 3000), cell("center=False", 3360)] }),
]
}),
new Paragraph({ spacing: { before: 200 } }),
body("其中 center=false 是关键对齐点。librosa 默认 center=true帧中心对齐会导致帧数比 C++ 实现多 1-2 帧。我们在训练脚本中显式设置 center=False并在 C++ 端使用 (n_samples - n_fft) / hop + 1 的帧数计算,确保训练-推理特征完全一致。"),
h2("3.2 GCC-PHAT 声源定位"),
body("GCC-PHATGeneralized Cross-Correlation with Phase Transform是一种经典的时延估计TDOA算法。其核心思想是通过相位变换加权消除信号幅度的影响仅保留相位信息用于互相关计算"),
quote("R_ij(τ) = IFFT{ X_i(f) · X_j*(f) / |X_i(f) · X_j*(f)| }"),
body("我们在实现中做了以下工程优化:"),
body("• 抛物线插值:在 GCC-PHAT 峰值附近进行抛物线拟合,将时延分辨率从采样点级提升至亚采样级"),
body("• 最小二乘方向解算:利用多对麦克风的 TDOA 构建超定方程组,通过 SVD 求解声源方向向量"),
body("• 阵列几何自适应:支持十字、线性、圆形及自定义阵列布局,通过配置文件热切换"),
h2("3.3 枪炮声分类模型"),
body("分类器采用轻量级 CNN-GRU 网络结构:"),
code("输入 (1, 64, T) → Conv1D(64→128→256) → MaxPool → GRU(128, bidirectional)"),
code(" → GlobalAvgPool → Dense(64) → Dropout(0.3) → Dense(4) → Softmax"),
new Paragraph({ spacing: { before: 200 } }),
body("模型推理使用 ONNX Runtime C++ API相比 LibTorch 轻量一个数量级,推理延迟约 10-50ms满足实时性要求。模型从 PyTorch 训练后导出为 ONNX 格式,支持动态 batch 和动态时间轴。"),
h2("3.4 距离估计与威胁跟踪"),
body("距离估计采用能量衰减模型:"),
quote("d = d₀ · 10^((L₀ - L_measured) / (20·α))"),
body("其中 L₀ 根据分类结果动态选择(枪声 150dB、炮声 180dB、爆炸 170dBα 为城市环境衰减系数(默认 0.6)。为降低单帧估计噪声,引入一维卡尔曼滤波进行时序平滑。"),
body("威胁跟踪采用最近邻数据关联算法:连续帧中方位角差 < 15° 且类型一致则判定为同一威胁,分配唯一 ID 并持续跟踪,连续 5 帧未检测到则淘汰。"),
// 四、工程实践
h1("四、工程实践与关键决策"),
h2("4.1 临时方案与最终方案的分离设计"),
body("项目初期面临一个现实问题麦克风阵列硬件尚未到位但需要提前验证算法通路和系统集成。我们设计了一套「source_type」配置切换机制"),
body("• mobile_phone手机通过 WiFi/UDP 发送单通道音频,仅做分类检测,定位模块自动跳过"),
body("• mic_array4 通道阵列,完整分类+定位+距离估计"),
body("• wav_file离线 WAV 回放,用于算法调试"),
body("这种设计使得团队可以在无硬件条件下并行推进软件开发,硬件到位后仅需修改一行配置即可切换至最终方案。"),
h2("4.2 踩坑记录"),
h3("坑 1librosa center 参数导致的训练-推理不一致"),
body("初期发现 C++ 端 Mel Spectrogram 与 Python 端存在系统性偏差,排查后发现是 librosa 默认 center=true 导致的帧数差异。修复方案:训练时显式设置 center=False并在 C++ 端严格对齐分帧逻辑。"),
h3("坑 2ONNX 导出动态轴与 torch 2.x 的兼容性"),
body("torch 2.11 默认使用 dynamo 导出器,对 GRU 层的动态轴支持存在问题。修复方案:使用传统 TorchScript 导出器dynamo=False并将 opset 提升至 13。"),
h3("坑 3数据增强导致时间维度变化"),
body("训练脚本中的时间拉伸增强改变了 Mel Spectrogram 的时间帧数,导致 batch 拼接失败。修复方案增强后统一插值对齐到目标帧数63 帧)。"),
// 五、实验验证
h1("五、实验验证"),
h2("5.1 合成数据训练验证"),
body("在硬件到位前,我们使用合成数据集验证了完整的训练-导出-部署流程:"),
new Table({
width: { size: 9360, type: WidthType.DXA },
columnWidths: [4000, 5360],
rows: [
new TableRow({ children: [
cell("指标", 4000, { bold: true, shading: "f0f2f5" }),
cell("结果", 5360, { bold: true, shading: "f0f2f5" })
] }),
new TableRow({ children: [cell("数据集", 4000), cell("200 合成样本 + 10 份模拟无人机噪声", 5360)] }),
new TableRow({ children: [cell("训练 epoch", 4000), cell("30", 5360)] }),
new TableRow({ children: [cell("验证准确率", 4000), cell("100%(合成数据,过拟合预期)", 5360)] }),
new TableRow({ children: [cell("ONNX 模型大小", 4000), cell("1.9 MB", 5360)] }),
new TableRow({ children: [cell("ONNX 推理验证", 4000), cell("枪声识别置信度 97.92%", 5360)] }),
new TableRow({ children: [cell("C++ 编译", 4000), cell("g++ 15.2 + CMake 4.1 通过", 5360)] }),
]
}),
new Paragraph({ spacing: { before: 200 } }),
body("需要强调的是合成数据上的高准确率不代表真实场景性能。当前模型仅用于验证代码通路后续需用真实数据集MIVIA、FSD50K 等)重新训练。"),
h2("5.2 特征一致性验证"),
body("我们编写了纯 NumPy 参考实现与 C++ FeatureExtractor 进行比对验证。在相同 WAV 输入下,两者 Mel Spectrogram 的逐元素最大误差 < 1e-4验证了 C++ 端特征提取的正确性。"),
// 六、总结与展望
h1("六、总结与展望"),
h2("6.1 已完成工作"),
body("• 完成了声源分析模块的全部 C++ 代码开发34 个文件)"),
body("• 实现了 Mel Spectrogram、GCC-PHAT、ONNX 推理、距离估计、威胁跟踪五大核心算法"),
body("• 设计了临时/最终方案分离机制,支持无硬件条件下的软件开发"),
body("• 在 Windows 上完成了 Python 训练环境搭建、模型训练、ONNX 导出与验证"),
h2("6.2 后续计划"),
body("• 下载真实数据集MIVIA、FSD50K、UrbanSound8K替换合成数据"),
body("• 录制无人机自噪声作为 ambient 负样本,解决旋翼噪声误报问题"),
body("• 在 Ubuntu + ROS Noetic 环境下编译 C++ 代码,完成特征一致性端到端验证"),
body("• 麦克风阵列硬件到位后,实现 ALSA 驱动并完成实机联调"),
body("• 考虑模型量化INT8以进一步降低 Jetson 平台的推理延迟"),
// 参考文献
h1("参考文献"),
body("[1] Knapp C H, Carter G C. The generalized correlation method for estimation of time delay[J]. IEEE Trans. on ASSP, 1976."),
body("[2] Microsoft. ONNX Runtime documentation[EB/OL]. https://onnxruntime.ai/docs/"),
body("[3] McFee B, et al. librosa: Audio and Music Signal Analysis in Python[C]. SciPy, 2015."),
body("[4] 智途投送软件开发方案内部文档国防科大计算机学院2026."),
]
}]
});
Packer.toBuffer(doc).then(buffer => {
fs.writeFileSync("基于C++的无人机声源分析模块设计与实现_技术博客.docx", buffer);
console.log("技术博客已生成基于C++的无人机声源分析模块设计与实现_技术博客.docx");
});