删除多余文件

luogang_branch
AetherPendragon 2 weeks ago
parent b914aaa73c
commit 704b32335b

@ -1,516 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>智途投送系统 - 软件体系结构图</title>
<style>
body {
font-family: "Microsoft YaHei", "SimHei", sans-serif;
background: #f5f6fa;
margin: 0;
padding: 20px;
color: #2c3e50;
}
h1 {
text-align: center;
color: #1a237e;
margin-bottom: 10px;
}
.subtitle {
text-align: center;
color: #555;
margin-bottom: 30px;
font-size: 14px;
}
.diagram-container {
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
margin: 30px auto;
max-width: 1100px;
padding: 30px;
}
.diagram-title {
font-size: 18px;
font-weight: bold;
color: #1a237e;
margin-bottom: 20px;
border-left: 4px solid #3949ab;
padding-left: 12px;
}
.diagram-note {
font-size: 12px;
color: #666;
margin-top: 15px;
line-height: 1.6;
}
svg {
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<h1>智途投送系统 — 软件体系结构图</h1>
<p class="subtitle">按《软件体系结构》课程规范绘制 | 管道-过滤器 / 分层 / 客户端-服务器 / 发布-订阅</p>
<!-- ===== 图1系统整体架构分层 + C/S ===== -->
<div class="diagram-container">
<div class="diagram-title">图1 系统整体体系结构 — 分层 + 客户端-服务器风格</div>
<svg width="1000" height="520" viewBox="0 0 1000 520">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#555"/>
</marker>
<marker id="arrow-dashed" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#888"/>
</marker>
</defs>
<!-- 背景层框 -->
<!-- 单兵终端 -->
<rect x="30" y="50" width="280" height="420" rx="10" fill="#e3f2fd" stroke="#1976d2" stroke-width="2"/>
<text x="170" y="80" text-anchor="middle" font-size="16" font-weight="bold" fill="#0d47a1">单兵终端APP客户端</text>
<text x="170" y="100" text-anchor="middle" font-size="11" fill="#555">Client-Server 架构 / 分层结构</text>
<!-- 后勤保障 -->
<rect x="360" y="50" width="280" height="420" rx="10" fill="#e8f5e9" stroke="#388e3c" stroke-width="2"/>
<text x="500" y="80" text-anchor="middle" font-size="16" font-weight="bold" fill="#1b5e20">后勤保障系统(服务器端)</text>
<text x="500" y="100" text-anchor="middle" font-size="11" fill="#555">Layered 分层架构 / REST API</text>
<!-- 无人机 -->
<rect x="690" y="50" width="280" height="420" rx="10" fill="#fff3e0" stroke="#f57c00" stroke-width="2"/>
<text x="830" y="80" text-anchor="middle" font-size="16" font-weight="bold" fill="#e65100">无人机软件系统</text>
<text x="830" y="100" text-anchor="middle" font-size="11" fill="#555">Pipe-Filter + Pub-Sub 混合架构</text>
<!-- ===== 单兵终端内部层次 ===== -->
<rect x="60" y="130" width="220" height="60" rx="6" fill="#bbdefb" stroke="#1976d2" stroke-width="1.5"/>
<text x="170" y="155" text-anchor="middle" font-size="13" font-weight="bold" fill="#0d47a1">表示层 (Presentation)</text>
<text x="170" y="175" text-anchor="middle" font-size="11" fill="#333">UI界面 / 地图 / 交互控件</text>
<rect x="60" y="210" width="220" height="60" rx="6" fill="#90caf9" stroke="#1976d2" stroke-width="1.5"/>
<text x="170" y="235" text-anchor="middle" font-size="13" font-weight="bold" fill="#0d47a1">业务逻辑层 (Service)</text>
<text x="170" y="255" text-anchor="middle" font-size="11" fill="#333">需求上报 / 位置同步 / 策略选择</text>
<rect x="60" y="290" width="220" height="60" rx="6" fill="#64b5f6" stroke="#1976d2" stroke-width="1.5"/>
<text x="170" y="315" text-anchor="middle" font-size="13" font-weight="bold" fill="#0d47a1">数据访问层 (Data Access)</text>
<text x="170" y="335" text-anchor="middle" font-size="11" fill="#333">REST API调用 / 本地存储</text>
<rect x="60" y="370" width="220" height="60" rx="6" fill="#42a5f5" stroke="#1976d2" stroke-width="1.5"/>
<text x="170" y="395" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">硬件抽象层 (HAL)</text>
<text x="170" y="415" text-anchor="middle" font-size="11" fill="#fff">GPS / 网络 / 摄像头</text>
<!-- 层间箭头 -->
<line x1="170" y1="190" x2="170" y2="210" stroke="#1976d2" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="170" y1="270" x2="170" y2="290" stroke="#1976d2" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="170" y1="350" x2="170" y2="370" stroke="#1976d2" stroke-width="2" marker-end="url(#arrow)"/>
<!-- ===== 后勤保障内部层次 ===== -->
<rect x="390" y="130" width="220" height="60" rx="6" fill="#c8e6c9" stroke="#388e3c" stroke-width="1.5"/>
<text x="500" y="155" text-anchor="middle" font-size="13" font-weight="bold" fill="#1b5e20">表示层 (Web前端)</text>
<text x="500" y="175" text-anchor="middle" font-size="11" fill="#333">HTML/CSS/JS / 地图可视化</text>
<rect x="390" y="210" width="220" height="60" rx="6" fill="#a5d6a7" stroke="#388e3c" stroke-width="1.5"/>
<text x="500" y="235" text-anchor="middle" font-size="13" font-weight="bold" fill="#1b5e20">应用层 (Flask API)</text>
<text x="500" y="255" text-anchor="middle" font-size="11" fill="#333">REST路由 / 任务调度 / 身份认证</text>
<rect x="390" y="290" width="220" height="60" rx="6" fill="#81c784" stroke="#388e3c" stroke-width="1.5"/>
<text x="500" y="315" text-anchor="middle" font-size="13" font-weight="bold" fill="#1b5e20">业务逻辑层 (Services)</text>
<text x="500" y="335" text-anchor="middle" font-size="11" fill="#333">路径规划 / 资源分配 / 威胁融合</text>
<rect x="390" y="370" width="220" height="60" rx="6" fill="#66bb6a" stroke="#388e3c" stroke-width="1.5"/>
<text x="500" y="395" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">数据层 (Data)</text>
<text x="500" y="415" text-anchor="middle" font-size="11" fill="#fff">SQLite / 内存数据 / 日志</text>
<!-- 层间箭头 -->
<line x1="500" y1="190" x2="500" y2="210" stroke="#388e3c" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="500" y1="270" x2="500" y2="290" stroke="#388e3c" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="500" y1="350" x2="500" y2="370" stroke="#388e3c" stroke-width="2" marker-end="url(#arrow)"/>
<!-- ===== 无人机内部 ===== -->
<rect x="720" y="130" width="220" height="70" rx="6" fill="#ffe0b2" stroke="#f57c00" stroke-width="1.5"/>
<text x="830" y="155" text-anchor="middle" font-size="13" font-weight="bold" fill="#e65100">ROS 节点层 (Pub-Sub)</text>
<text x="830" y="175" text-anchor="middle" font-size="11" fill="#333">威胁发布 / 航点订阅 / 状态广播</text>
<rect x="720" y="220" width="220" height="70" rx="6" fill="#ffcc80" stroke="#f57c00" stroke-width="1.5"/>
<text x="830" y="245" text-anchor="middle" font-size="13" font-weight="bold" fill="#e65100">感知流水线层 (Pipe-Filter)</text>
<text x="830" y="265" text-anchor="middle" font-size="11" fill="#333">声学/视觉/热成像 → 威胁地图</text>
<rect x="720" y="310" width="220" height="70" rx="6" fill="#ffb74d" stroke="#f57c00" stroke-width="1.5"/>
<text x="830" y="335" text-anchor="middle" font-size="13" font-weight="bold" fill="#e65100">算法核心层 (Core)</text>
<text x="830" y="355" text-anchor="middle" font-size="11" fill="#333">GCC-PHAT / CNN-GRU / SPL</text>
<rect x="720" y="400" width="220" height="50" rx="6" fill="#ffa726" stroke="#f57c00" stroke-width="1.5"/>
<text x="830" y="420" text-anchor="middle" font-size="13" font-weight="bold" fill="#fff">硬件接口层 (Drivers)</text>
<text x="830" y="438" text-anchor="middle" font-size="11" fill="#fff">麦克风 / 相机 / IMU / GPS</text>
<!-- 层间箭头 -->
<line x1="830" y1="200" x2="830" y2="220" stroke="#f57c00" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="830" y1="290" x2="830" y2="310" stroke="#f57c00" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="830" y1="380" x2="830" y2="400" stroke="#f57c00" stroke-width="2" marker-end="url(#arrow)"/>
<!-- ===== 子系统间通信 ===== -->
<!-- C/S 双向箭头 -->
<line x1="310" y1="250" x2="360" y2="250" stroke="#555" stroke-width="2" marker-end="url(#arrow)" marker-start="url(#arrow)"/>
<text x="335" y="240" text-anchor="middle" font-size="10" fill="#333">HTTP/REST</text>
<line x1="640" y1="250" x2="690" y2="250" stroke="#555" stroke-width="2" marker-end="url(#arrow)" marker-start="url(#arrow)"/>
<text x="665" y="240" text-anchor="middle" font-size="10" fill="#333">ROS/WebSocket</text>
<!-- 单兵-无人机 虚线 -->
<line x1="170" y1="450" x2="830" y2="450" stroke="#888" stroke-width="1.5" stroke-dasharray="6,4"/>
<text x="500" y="470" text-anchor="middle" font-size="10" fill="#666"> rosbridge / WebSocket间接通信经由后勤保障系统或直接</text>
</svg>
<p class="diagram-note">
<b>设计说明:</b>三大子系统各自内部采用分层架构,子系统间采用客户端-服务器风格交互。
上层单兵APP、Web前端通过 REST API 与后勤保障系统通信;后勤保障系统通过 ROS 网络与无人机交互。
分层架构的约束:每一层只使用直接下层提供的服务,层与层之间通过定义好的接口协议交互。
</p>
</div>
<!-- ===== 图2管道-过滤器(核心) ===== -->
<div class="diagram-container">
<div class="diagram-title">图2 声源分析模块 — 管道-过滤器体系结构(我负责部分)</div>
<svg width="1000" height="380" viewBox="0 0 1000 380">
<defs>
<marker id="arrow2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#555"/>
</marker>
<filter id="shadow" x="-5%" y="-5%" width="110%" height="110%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.12"/>
</filter>
</defs>
<!-- 标题框 -->
<rect x="20" y="15" width="960" height="40" rx="6" fill="#fff8e1" stroke="#ff8f00" stroke-width="2"/>
<text x="500" y="42" text-anchor="middle" font-size="15" font-weight="bold" fill="#e65100">
数据流:原始音频 → [缓冲 → 特征 → 分类 → 定位/测距 → 跟踪] → 威胁事件
</text>
<!-- ===== 数据源 ===== -->
<rect x="30" y="100" width="100" height="70" rx="8" fill="#eceff1" stroke="#546e7a" stroke-width="2" stroke-dasharray="4,2"/>
<text x="80" y="125" text-anchor="middle" font-size="12" font-weight="bold" fill="#37474f">数据源</text>
<text x="80" y="142" text-anchor="middle" font-size="10" fill="#555">麦克风阵列</text>
<text x="80" y="157" text-anchor="middle" font-size="10" fill="#555">WAV文件</text>
<!-- ===== Filter 1: AudioBuffer ===== -->
<rect x="170" y="90" width="130" height="90" rx="8" fill="#e3f2fd" stroke="#1565c0" stroke-width="2" filter="url(#shadow)"/>
<text x="235" y="118" text-anchor="middle" font-size="13" font-weight="bold" fill="#0d47a1">Filter 1</text>
<text x="235" y="138" text-anchor="middle" font-size="12" font-weight="bold" fill="#1565c0">AudioBuffer</text>
<text x="235" y="158" text-anchor="middle" font-size="10" fill="#555">循环缓冲区</text>
<text x="235" y="170" text-anchor="middle" font-size="9" fill="#777">滑动窗口管理</text>
<!-- ===== Filter 2: FeatureExtractor ===== -->
<rect x="340" y="90" width="130" height="90" rx="8" fill="#e8f5e9" stroke="#2e7d32" stroke-width="2" filter="url(#shadow)"/>
<text x="405" y="118" text-anchor="middle" font-size="13" font-weight="bold" fill="#1b5e20">Filter 2</text>
<text x="405" y="138" text-anchor="middle" font-size="12" font-weight="bold" fill="#2e7d32">FeatureExtractor</text>
<text x="405" y="158" text-anchor="middle" font-size="10" fill="#555">Mel频谱图提取</text>
<text x="405" y="170" text-anchor="middle" font-size="9" fill="#777">FFT + Mel滤波器组</text>
<!-- ===== Filter 3: GunshotClassifier ===== -->
<rect x="510" y="90" width="130" height="90" rx="8" fill="#fff3e0" stroke="#ef6c00" stroke-width="2" filter="url(#shadow)"/>
<text x="575" y="118" text-anchor="middle" font-size="13" font-weight="bold" fill="#e65100">Filter 3</text>
<text x="575" y="138" text-anchor="middle" font-size="12" font-weight="bold" fill="#ef6c00">GunshotClassifier</text>
<text x="575" y="158" text-anchor="middle" font-size="10" fill="#555">CNN-GRU分类</text>
<text x="575" y="170" text-anchor="middle" font-size="9" fill="#777">ONNX Runtime推理</text>
<!-- ===== Filter 4a: GccPhatLocalizer ===== -->
<rect x="680" y="90" width="130" height="90" rx="8" fill="#fce4ec" stroke="#c2185b" stroke-width="2" filter="url(#shadow)"/>
<text x="745" y="118" text-anchor="middle" font-size="13" font-weight="bold" fill="#880e4f">Filter 4a</text>
<text x="745" y="138" text-anchor="middle" font-size="12" font-weight="bold" fill="#c2185b">GccPhatLocalizer</text>
<text x="745" y="158" text-anchor="middle" font-size="10" fill="#555">GCC-PHAT定位</text>
<text x="745" y="170" text-anchor="middle" font-size="9" fill="#777">时延估计 → 方位角/俯仰角</text>
<!-- ===== Filter 4b: DistanceEstimator ===== -->
<rect x="680" y="210" width="130" height="90" rx="8" fill="#f3e5f5" stroke="#7b1fa2" stroke-width="2" filter="url(#shadow)"/>
<text x="745" y="238" text-anchor="middle" font-size="13" font-weight="bold" fill="#4a148c">Filter 4b</text>
<text x="745" y="258" text-anchor="middle" font-size="12" font-weight="bold" fill="#7b1fa2">DistanceEstimator</text>
<text x="745" y="278" text-anchor="middle" font-size="10" fill="#555">SPL距离估计</text>
<text x="745" y="290" text-anchor="middle" font-size="9" fill="#777">声压级衰减 + Kalman滤波</text>
<!-- ===== Filter 5: ThreatTracker ===== -->
<rect x="850" y="150" width="130" height="90" rx="8" fill="#e0f2f1" stroke="#00695c" stroke-width="2" filter="url(#shadow)"/>
<text x="915" y="178" text-anchor="middle" font-size="13" font-weight="bold" fill="#004d40">Filter 5</text>
<text x="915" y="198" text-anchor="middle" font-size="12" font-weight="bold" fill="#00695c">ThreatTracker</text>
<text x="915" y="218" text-anchor="middle" font-size="10" fill="#555">威胁跟踪/去重</text>
<text x="915" y="230" text-anchor="middle" font-size="9" fill="#777">时域去重 + ID分配</text>
<!-- ===== 数据汇点 ===== -->
<rect x="850" y="280" width="130" height="60" rx="8" fill="#eceff1" stroke="#546e7a" stroke-width="2" stroke-dasharray="4,2"/>
<text x="915" y="305" text-anchor="middle" font-size="12" font-weight="bold" fill="#37474f">数据汇点</text>
<text x="915" y="325" text-anchor="middle" font-size="10" fill="#555">AcousticThreat 事件</text>
<!-- ===== 管道连接(带标签) ===== -->
<!-- 源 → Buffer -->
<line x1="130" y1="135" x2="170" y2="135" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="150" y="128" text-anchor="middle" font-size="9" fill="#666">float[]</text>
<!-- Buffer → Feature -->
<line x1="300" y1="135" x2="340" y2="135" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="320" y="128" text-anchor="middle" font-size="9" fill="#666">float[]</text>
<!-- Feature → Classifier -->
<line x1="470" y1="135" x2="510" y2="135" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="490" y="128" text-anchor="middle" font-size="9" fill="#666">MatrixXf</text>
<!-- Classifier → Localizer -->
<line x1="640" y1="135" x2="680" y2="135" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="660" y="128" text-anchor="middle" font-size="9" fill="#666">label+conf</text>
<!-- Classifier → DistanceEstimator (branch) -->
<path d="M 575 180 L 575 255 L 680 255" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="630" y="248" text-anchor="middle" font-size="9" fill="#666">label</text>
<!-- Localizer → Tracker -->
<line x1="810" y1="135" x2="850" y2="175" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="840" y="148" text-anchor="middle" font-size="9" fill="#666">azimuth,elevation</text>
<!-- Distance → Tracker -->
<line x1="810" y1="255" x2="850" y2="215" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="830" y="240" text-anchor="middle" font-size="9" fill="#666">distance</text>
<!-- Tracker → Sink -->
<line x1="915" y1="240" x2="915" y2="280" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="940" y="262" text-anchor="middle" font-size="9" fill="#666">AcousticFrame</text>
<!-- ===== VAD 门控标注 ===== -->
<rect x="180" y="220" width="110" height="40" rx="5" fill="#fff" stroke="#999" stroke-width="1" stroke-dasharray="3,2"/>
<text x="235" y="238" text-anchor="middle" font-size="10" fill="#666">VAD门控</text>
<text x="235" y="252" text-anchor="middle" font-size="9" fill="#888">能量+过零率检测</text>
<line x1="235" y1="220" x2="235" y2="180" stroke="#999" stroke-width="1" stroke-dasharray="3,2"/>
<!-- ===== 时序平滑标注 ===== -->
<rect x="530" y="220" width="110" height="40" rx="5" fill="#fff" stroke="#999" stroke-width="1" stroke-dasharray="3,2"/>
<text x="585" y="238" text-anchor="middle" font-size="10" fill="#666">时序平滑</text>
<text x="585" y="252" text-anchor="middle" font-size="9" fill="#888">滑动窗口平均</text>
<line x1="585" y1="220" x2="585" y2="180" stroke="#999" stroke-width="1" stroke-dasharray="3,2"/>
<!-- 图例 -->
<rect x="30" y="320" width="140" height="40" rx="5" fill="#e3f2fd" stroke="#1565c0" stroke-width="1.5"/>
<text x="100" y="335" text-anchor="middle" font-size="10" fill="#333">过滤器组件</text>
<text x="100" y="350" text-anchor="middle" font-size="9" fill="#555">(独立处理单元)</text>
<rect x="190" y="320" width="140" height="40" rx="5" fill="#eceff1" stroke="#546e7a" stroke-width="1.5" stroke-dasharray="4,2"/>
<text x="260" y="335" text-anchor="middle" font-size="10" fill="#333">数据源/汇点</text>
<text x="260" y="350" text-anchor="middle" font-size="9" fill="#555">(系统边界)</text>
<line x1="350" y1="340" x2="400" y2="340" stroke="#555" stroke-width="2" marker-end="url(#arrow2)"/>
<text x="430" y="345" text-anchor="start" font-size="10" fill="#333">管道 (数据流)</text>
</svg>
<p class="diagram-note">
<b>管道-过滤器风格的四个核心特征在本模块的体现:</b><br>
<b>过滤器独立性</b>:每个 Filter如 FeatureExtractor、GunshotClassifier都是独立的类不与其他 Filter 共享状态;<br>
<b>数据流驱动</b>:音频数据沿管道单向流动,无循环(符合「不允许出现环」的约束);<br>
<b>局部变换</b>:每个 Filter 只负责一种局部变换(时域→频域→概率→方位→距离);<br>
<b>黑盒复用</b>GunshotClassifier 可独立替换为其他模型(如 Transformer只要输入输出 Mel 频谱图格式不变。
</p>
</div>
<!-- ===== 图3分层结构图 ===== -->
<div class="diagram-container">
<div class="diagram-title">图3 声源分析模块 — 分层体系结构(静态视角)</div>
<svg width="900" height="400" viewBox="0 0 900 400">
<defs>
<marker id="arrow3" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#555"/>
</marker>
</defs>
<!-- 层背景 -->
<rect x="50" y="50" width="800" height="80" rx="8" fill="#e8eaf6" stroke="#3949ab" stroke-width="2"/>
<text x="70" y="75" font-size="14" font-weight="bold" fill="#1a237e">Layer 3应用/集成层 (Application/Integration)</text>
<text x="70" y="95" font-size="11" fill="#555">ROS 包装器 / 节点生命周期管理 / 话题发布订阅</text>
<text x="70" y="110" font-size="11" fill="#555">acoustic_node.cpp | threat_publisher.cpp</text>
<rect x="50" y="150" width="800" height="80" rx="8" fill="#c5cae9" stroke="#3949ab" stroke-width="2"/>
<text x="70" y="175" font-size="14" font-weight="bold" fill="#1a237e">Layer 2业务逻辑层 (Business Logic / Pipeline)</text>
<text x="70" y="195" font-size="11" fill="#555">流水线编排 (Pipeline) / 配置管理 / 数据类型定义</text>
<text x="70" y="210" font-size="11" fill="#555">pipeline.cpp | types.h | 配置解析 (YAML)</text>
<rect x="50" y="250" width="800" height="110" rx="8" fill="#9fa8da" stroke="#3949ab" stroke-width="2"/>
<text x="70" y="275" font-size="14" font-weight="bold" fill="#1a237e">Layer 1算法核心层 (Algorithm Core)</text>
<text x="70" y="295" font-size="11" fill="#333">音频缓冲 (AudioBuffer) | 特征提取 (FeatureExtractor) | 分类器 (GunshotClassifier)</text>
<text x="70" y="310" font-size="11" fill="#333">声源定位 (GccPhatLocalizer) | 距离估计 (DistanceEstimator) | 威胁跟踪 (ThreatTracker)</text>
<text x="70" y="330" font-size="11" fill="#333">FFT工具 (fft_utils) | IO适配 (WavFileSource, MobilePhoneSource)</text>
<!-- 层间依赖箭头 -->
<line x1="450" y1="130" x2="450" y2="150" stroke="#3949ab" stroke-width="2.5" marker-end="url(#arrow3)"/>
<line x1="450" y1="230" x2="450" y2="250" stroke="#3949ab" stroke-width="2.5" marker-end="url(#arrow3)"/>
<!-- 依赖规则说明 -->
<rect x="520" y="135" width="300" height="40" rx="5" fill="#fff" stroke="#3949ab" stroke-width="1" stroke-dasharray="3,2"/>
<text x="530" y="152" font-size="10" fill="#3949ab">上层 → 下层:只允许向下依赖(依赖倒置原则)</text>
<text x="530" y="166" font-size="10" fill="#3949ab">core 层完全不依赖 ROS / yaml-cpp / 操作系统</text>
<!-- 跨平台标注 -->
<rect x="600" y="60" width="230" height="55" rx="5" fill="#fff" stroke="#2e7d32" stroke-width="1.5" stroke-dasharray="4,2"/>
<text x="715" y="80" text-anchor="middle" font-size="11" font-weight="bold" fill="#1b5e20">Ubuntu (实机部署)</text>
<text x="715" y="95" text-anchor="middle" font-size="10" fill="#333">BUILD_ROS_WRAPPER=ON</text>
<text x="715" y="108" text-anchor="middle" font-size="10" fill="#333">ROS节点 + 麦克风阵列驱动</text>
<rect x="600" y="160" width="230" height="55" rx="5" fill="#fff" stroke="#1565c0" stroke-width="1.5" stroke-dasharray="4,2"/>
<text x="715" y="180" text-anchor="middle" font-size="11" font-weight="bold" fill="#0d47a1">Windows / Linux (仿真调试)</text>
<text x="715" y="195" text-anchor="middle" font-size="10" fill="#333">BUILD_ROS_WRAPPER=OFF</text>
<text x="715" y="208" text-anchor="middle" font-size="10" fill="#333">离线WAV测试 + 单元测试</text>
<!-- 图例说明 -->
<rect x="50" y="360" width="800" height="30" rx="4" fill="#f5f5f5" stroke="#ccc" stroke-width="1"/>
<text x="60" y="380" font-size="11" fill="#555">
分层架构约束:① 每一层只使用直接下层的服务 ② 层间通过接口交互,不直接访问实现 ③ 下层修改不影响上层(只要接口不变)
</text>
</svg>
<p class="diagram-note">
<b>设计说明:</b>通过分层隔离算法核心层Layer 1完全与 ROS、操作系统解耦。
这意味着同一套 C++ 声学算法既可以在 Ubuntu 上作为 ROS 节点运行(控制真实无人机),
也可以在 Windows 上编译为独立可执行文件做离线仿真测试,实现了"一次开发,多处部署"。
</p>
</div>
<!-- ===== 图4ROS 发布-订阅运行时图 ===== -->
<div class="diagram-container">
<div class="diagram-title">图4 无人机感知系统 — 发布-订阅Pub-Sub运行时交互</div>
<svg width="1000" height="420" viewBox="0 0 1000 420">
<defs>
<marker id="arrow4" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#555"/>
</marker>
</defs>
<!-- ROS Master -->
<rect x="400" y="20" width="200" height="50" rx="25" fill="#fce4ec" stroke="#ad1457" stroke-width="2"/>
<text x="500" y="40" text-anchor="middle" font-size="13" font-weight="bold" fill="#880e4f">ROS Master</text>
<text x="500" y="58" text-anchor="middle" font-size="10" fill="#555">节点注册 / 话题匹配</text>
<!-- 话题总线 -->
<rect x="80" y="100" width="840" height="50" rx="6" fill="#f3e5f5" stroke="#7b1fa2" stroke-width="2" stroke-dasharray="6,3"/>
<text x="500" y="120" text-anchor="middle" font-size="12" font-weight="bold" fill="#4a148c">ROS Topic 总线(异步消息通道)</text>
<text x="500" y="138" text-anchor="middle" font-size="10" fill="#555">/microphone_array/audio | /acoustic_threat | /threat_map | /dynamic_waypoints</text>
<!-- Publisher 节点:麦克风驱动 -->
<rect x="80" y="190" width="180" height="90" rx="8" fill="#e3f2fd" stroke="#1565c0" stroke-width="2"/>
<text x="170" y="215" text-anchor="middle" font-size="12" font-weight="bold" fill="#0d47a1">Publisher</text>
<text x="170" y="235" text-anchor="middle" font-size="11" fill="#333">麦克风阵列驱动节点</text>
<text x="170" y="255" text-anchor="middle" font-size="10" fill="#555">发布:/microphone_array/audio</text>
<text x="170" y="270" text-anchor="middle" font-size="10" fill="#555">数据类型Float32MultiArray</text>
<!-- Publisher 节点:声学分析(我负责) -->
<rect x="300" y="190" width="180" height="90" rx="8" fill="#fff3e0" stroke="#ef6c00" stroke-width="2"/>
<text x="390" y="215" text-anchor="middle" font-size="12" font-weight="bold" fill="#e65100">Publisher我负责</text>
<text x="390" y="235" text-anchor="middle" font-size="11" fill="#333">声学分析节点 (acoustic_node)</text>
<text x="390" y="255" text-anchor="middle" font-size="10" fill="#555">订阅:/microphone_array/audio</text>
<text x="390" y="270" text-anchor="middle" font-size="10" fill="#555">发布:/acoustic_threat</text>
<!-- Subscriber 节点:多模态融合 -->
<rect x="520" y="190" width="180" height="90" rx="8" fill="#e8f5e9" stroke="#2e7d32" stroke-width="2"/>
<text x="610" y="215" text-anchor="middle" font-size="12" font-weight="bold" fill="#1b5e20">Subscriber + Publisher</text>
<text x="610" y="235" text-anchor="middle" font-size="11" fill="#333">多模态融合节点</text>
<text x="610" y="255" text-anchor="middle" font-size="10" fill="#555">订阅:/acoustic_threat + /vision_threat</text>
<text x="610" y="270" text-anchor="middle" font-size="10" fill="#555">发布:/threat_map</text>
<!-- Subscriber 节点:动态规划 -->
<rect x="740" y="190" width="180" height="90" rx="8" fill="#fce4ec" stroke="#c2185b" stroke-width="2"/>
<text x="830" y="215" text-anchor="middle" font-size="12" font-weight="bold" fill="#880e4f">Subscriber</text>
<text x="830" y="235" text-anchor="middle" font-size="11" fill="#333">动态路径规划节点</text>
<text x="830" y="255" text-anchor="middle" font-size="10" fill="#555">订阅:/threat_map</text>
<text x="830" y="270" text-anchor="middle" font-size="10" fill="#555">发布:/dynamic_waypoints</text>
<!-- 消息流向箭头 -->
<line x1="170" y1="280" x2="170" y2="330" stroke="#1565c0" stroke-width="2" marker-end="url(#arrow4)"/>
<line x1="170" y1="330" x2="350" y2="330" stroke="#1565c0" stroke-width="2" marker-end="url(#arrow4)"/>
<line x1="350" y1="330" x2="350" y2="280" stroke="#1565c0" stroke-width="2" marker-end="url(#arrow4)"/>
<text x="260" y="345" text-anchor="middle" font-size="10" fill="#1565c0">音频数据流</text>
<line x1="390" y1="280" x2="390" y2="330" stroke="#ef6c00" stroke-width="2" marker-end="url(#arrow4)"/>
<line x1="390" y1="330" x2="570" y2="330" stroke="#ef6c00" stroke-width="2" marker-end="url(#arrow4)"/>
<line x1="570" y1="330" x2="570" y2="280" stroke="#ef6c00" stroke-width="2" marker-end="url(#arrow4)"/>
<text x="480" y="345" text-anchor="middle" font-size="10" fill="#ef6c00">AcousticThreat 事件</text>
<line x1="610" y1="280" x2="610" y2="330" stroke="#2e7d32" stroke-width="2" marker-end="url(#arrow4)"/>
<line x1="610" y1="330" x2="790" y2="330" stroke="#2e7d32" stroke-width="2" marker-end="url(#arrow4)"/>
<line x1="790" y1="330" x2="790" y2="280" stroke="#2e7d32" stroke-width="2" marker-end="url(#arrow4)"/>
<text x="700" y="345" text-anchor="middle" font-size="10" fill="#2e7d32">融合后的 ThreatMap</text>
<!-- 特性说明 -->
<rect x="80" y="370" width="840" height="35" rx="5" fill="#fafafa" stroke="#999" stroke-width="1"/>
<text x="90" y="392" font-size="11" fill="#555">
发布-订阅特征:① 发布者与订阅者完全解耦,互不知道对方存在 ② 支持一对多广播(一个威胁事件可被多个消费者处理) ③ 异步非阻塞,适合实时感知系统的低延迟要求
</text>
</svg>
<p class="diagram-note">
<b>设计说明:</b>发布-订阅风格使得声学分析节点无需关心"谁会使用威胁结果"。
在仿真阶段,可以订阅 /acoustic_threat 做可视化验证;在实机阶段,同一个话题被多模态融合节点订阅。
这种「隐式调用」机制大幅降低了系统各模块间的耦合度。
</p>
</div>
<!-- ===== 图5PIMPL 信息隐藏示意图 ===== -->
<div class="diagram-container">
<div class="diagram-title">图5 PIMPL 惯用法 — 信息隐藏与编译隔离</div>
<svg width="900" height="320" viewBox="0 0 900 320">
<defs>
<marker id="arrow5" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#555"/>
</marker>
</defs>
<!-- 左侧:对外接口头文件 -->
<rect x="50" y="50" width="280" height="220" rx="10" fill="#e8eaf6" stroke="#3949ab" stroke-width="2"/>
<text x="190" y="78" text-anchor="middle" font-size="14" font-weight="bold" fill="#1a237e">公开头文件 (Public API)</text>
<text x="190" y="98" text-anchor="middle" font-size="11" fill="#555">pipeline.h / feature_extractor.h</text>
<rect x="80" y="120" width="220" height="120" rx="6" fill="#fff" stroke="#3949ab" stroke-width="1.5"/>
<text x="100" y="145" font-size="12" font-family="monospace" fill="#333">class Pipeline {</text>
<text x="100" y="165" font-size="12" font-family="monospace" fill="#333">public:</text>
<text x="120" y="185" font-size="12" font-family="monospace" fill="#2e7d32"> Process(audio)</text>
<text x="120" y="205" font-size="12" font-family="monospace" fill="#2e7d32"> FromYaml(path)</text>
<text x="120" y="225" font-size="12" font-family="monospace" fill="#2e7d32"> Reset()</text>
<text x="100" y="245" font-size="12" font-family="monospace" fill="#c62828">private:</text>
<text x="120" y="260" font-size="12" font-family="monospace" fill="#c62828"> Impl* impl_;</text>
<text x="100" y="275" font-size="12" font-family="monospace" fill="#333">};</text>
<!-- 右侧:实现文件 -->
<rect x="560" y="50" width="280" height="220" rx="10" fill="#fff3e0" stroke="#ef6c00" stroke-width="2"/>
<text x="700" y="78" text-anchor="middle" font-size="14" font-weight="bold" fill="#e65100">实现文件 (Private Implementation)</text>
<text x="700" y="98" text-anchor="middle" font-size="11" fill="#555">pipeline.cpp</text>
<rect x="590" y="120" width="220" height="120" rx="6" fill="#fff" stroke="#ef6c00" stroke-width="1.5"/>
<text x="610" y="145" font-size="12" font-family="monospace" fill="#333">struct Pipeline::Impl {</text>
<text x="610" y="165" font-size="12" font-family="monospace" fill="#555"> AudioBuffer*</text>
<text x="610" y="185" font-size="12" font-family="monospace" fill="#555"> FeatureExtractor*</text>
<text x="610" y="205" font-size="12" font-family="monospace" fill="#555"> GunshotClassifier*</text>
<text x="610" y="225" font-size="12" font-family="monospace" fill="#555"> GccPhatLocalizer*</text>
<text x="610" y="245" font-size="12" font-family="monospace" fill="#555"> ...</text>
<text x="610" y="260" font-size="12" font-family="monospace" fill="#333">};</text>
<!-- 中间箭头 -->
<line x1="350" y1="160" x2="560" y2="160" stroke="#555" stroke-width="2" marker-end="url(#arrow5)"/>
<text x="455" y="150" text-anchor="middle" font-size="11" fill="#555">编译依赖</text>
<text x="455" y="180" text-anchor="middle" font-size="10" fill="#888">.cpp 包含所有头文件)</text>
<line x1="560" y1="200" x2="350" y2="200" stroke="#999" stroke-width="1.5" stroke-dasharray="5,3"/>
<text x="455" y="220" text-anchor="middle" font-size="10" fill="#888">无反向依赖(接口不感知实现细节)</text>
<!-- 编译边界 -->
<rect x="390" y="100" width="130" height="30" rx="4" fill="#e8f5e9" stroke="#2e7d32" stroke-width="1"/>
<text x="455" y="120" text-anchor="middle" font-size="10" fill="#1b5e20">编译防火墙</text>
<!-- 底部说明 -->
<rect x="50" y="290" width="790" height="22" rx="4" fill="#f5f5f5" stroke="#ccc" stroke-width="1"/>
<text x="60" y="306" font-size="11" fill="#555">
效果:修改 Impl 内部(如替换 FFT 库、新增滤波器)→ 无需重新编译依赖 pipeline.h 的其他模块 → 大幅缩短编译时间,降低模块耦合
</text>
</svg>
<p class="diagram-note">
<b>设计说明:</b>PIMPLPointer to Implementation是 C++ 中实现「信息隐藏」的经典惯用法。
它将类的实现细节完全移入 .cpp 文件,头文件中只暴露接口指针。
这符合软件体系结构「接口与实现分离」的原则,也提升了系统的可修改性质量属性。
</p>
</div>
<div style="text-align:center; margin: 40px 0; color:#888; font-size:12px;">
智途投送系统 — 软件体系结构汇报用图 | 绘制规范参考《软件体系结构》课程
</div>
</body>
</html>

@ -1,132 +0,0 @@
#!/usr/bin/env python3
"""服务器端修复脚本:解决内存计数器重启归零导致的主键冲突"""
import os, sys
APP_PATH = "/opt/zhitu/app.py"
if not os.path.exists(APP_PATH):
print(f"错误:找不到 {APP_PATH}")
sys.exit(1)
# 备份
bak = APP_PATH + ".bak." + str(int(os.time()))
os.system(f"cp {APP_PATH} {bak}")
print(f"已备份: {bak}")
with open(APP_PATH, "r", encoding="utf-8") as f:
s = f.read()
# 1. 删除内存计数器
s = s.replace(
"_demand_id_counter = 0\n_task_id_counter = 0\n_danger_id_counter = 0",
"# 计数器已从内存变量改为数据库查询,避免服务重启后主键冲突"
)
# 2. 添加辅助函数
helper = '''def _next_demand_id():
"""从数据库查询当前最大需求ID生成下一个避免内存计数器重启归零导致主键冲突"""
conn = get_db()
row = conn.execute("SELECT id FROM demands WHERE id LIKE 'REQ-%' ORDER BY id DESC LIMIT 1").fetchone()
conn.close()
if row:
try:
num = int(row["id"].replace("REQ-", ""))
except ValueError:
num = 0
else:
num = 0
return "REQ-" + str(num + 1).zfill(3)
def _next_task_id():
"""从数据库查询当前最大任务ID生成下一个"""
conn = get_db()
row = conn.execute("SELECT id FROM tasks WHERE id LIKE '#%' ORDER BY id DESC LIMIT 1").fetchone()
conn.close()
if row:
try:
num = int(row["id"].replace("#", ""))
except ValueError:
num = 0
else:
num = 0
return "#" + str(num + 1).zfill(3)
'''
marker = "# ===== REST API: 物资需求单兵APP ====="
if marker in s and "_next_demand_id" not in s:
s = s.replace(marker, helper + marker)
# 3. 修改 post_demand
s = s.replace(
" global _demand_id_counter\n data = request.get_json(force=True)\n _demand_id_counter += 1",
" data = request.get_json(force=True)"
)
s = s.replace(
' demand_id = "REQ-" + str(_demand_id_counter).zfill(3)',
' demand_id = _next_demand_id()'
)
# 4. 修改 add_danger_zone
s = s.replace(
''' global _danger_id_counter
data = request.get_json(force=True)
_danger_id_counter += 1
zone = {
"id": _danger_id_counter,''',
''' data = request.get_json(force=True)
zone = {'''
)
s = s.replace(
''' conn.execute('''
INSERT INTO danger_zones (lat, lng, radius, description, created_at)
VALUES (?, ?, ?, ?, ?)
''', (zone["lat"], zone["lng"], zone["radius"], zone["description"], zone["created_at"]))
conn.commit()
conn.close()
return jsonify({"ok": True, "id": zone["id"]})''',
''' cur = conn.execute('''
INSERT INTO danger_zones (lat, lng, radius, description, created_at)
VALUES (?, ?, ?, ?, ?)
''', (zone["lat"], zone["lng"], zone["radius"], zone["description"], zone["created_at"]))
zone_id = cur.lastrowid
conn.commit()
conn.close()
return jsonify({"ok": True, "id": zone_id})'''
)
# 5. 修改 dispatch_task
s = s.replace(
''' global _task_id_counter
data = request.get_json(force=True)
soldier_id = data.get("soldier_id", "unknown")
_task_id_counter += 1''',
''' data = request.get_json(force=True)
soldier_id = data.get("soldier_id", "unknown")'''
)
s = s.replace(
''' "id": "#" + str(_task_id_counter).zfill(3),''',
''' "id": _next_task_id(),'''
)
# 6. 修改 sos
s = s.replace(
''' # 同时标记为危险区域
global _danger_id_counter
_danger_id_counter += 1
conn.execute('''',
''' # 同时标记为危险区域
conn.execute(''''
)
with open(APP_PATH, "w", encoding="utf-8") as f:
f.write(s)
# 语法检查
result = os.system(f"python3 -m py_compile {APP_PATH}")
if result == 0:
print("✅ 修复完成,语法检查通过")
else:
print("❌ 语法检查失败,已恢复备份")
os.system(f"cp {bak} {APP_PATH}")
sys.exit(1)

@ -1,287 +0,0 @@
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");
});

@ -1,291 +0,0 @@
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 epochCNN-GRU 网络,验证准确率 100%(合成数据过拟合属预期现象)"),
body("• ONNX 导出gunshot_classifier.onnx1.9MBopset 13"),
body("• ONNX 验证:枪声识别置信度 97.92%"),
h2("2.3 临时方案与最终方案分离"),
body("已实现 source_type 配置切换机制:"),
body("• mobile_phone手机单通道麦克风通过 UDP → ROS 话题传输,仅做分类"),
body("• mic_array4 通道麦克风阵列(最终方案),完整分类+定位+距离估计"),
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/LibraryCMake 已适配"),
body("• ONNX Runtime C++ v1.20.1:通过 Python 包提取 DLL + GitHub raw 下载头文件 + gendef/dlltool 生成 MinGW 导入库"),
body("• 全部测试通过test_core_lib7项、extract_mel_cpp、test_classifier_cppONNX 推理 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.cppaudio_buffer_wraparound 测试期望值错误5 应为 6gcc_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("• CMake4.1.0 已安装 ✅"),
body("• Eigen3bundled third_party/eigen-3.4.0 ✅"),
body("• ONNX Runtime C++v1.20.1 已配置(头文件 + DLL + MinGW 导入库)✅"),
body("• yaml-cppconda 0.8.0 已检测CMake 自动链接 ✅"),
body("• 构建方式:"),
body(" 快速命令行build_core_test.bat一键编译全部测试"),
body(" 标准 CMakebuild_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("实现 MicArraySourceALSA 麦克风阵列驱动)", 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");
});

@ -1,482 +0,0 @@
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
InternalHyperlink, Bookmark, FootnoteReferenceRun, PositionalTab,
PositionalTabAlignment, PositionalTabRelativeTo, PositionalTabLeader,
TabStopType, TabStopPosition, Column, SectionType,
TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
VerticalAlign, PageNumber, PageBreak } = require('docx');
const fs = require('fs');
// 辅助函数:创建代码块样式段落
function codeBlock(lines) {
const border = { style: BorderStyle.SINGLE, size: 1, color: "E0E0E0" };
const borders = { top: border, bottom: border, left: border, right: border };
return new Table({
width: { size: 9360, type: WidthType.DXA },
columnWidths: [9360],
rows: [
new TableRow({
children: [
new TableCell({
borders,
width: { size: 9360, type: WidthType.DXA },
shading: { fill: "F5F5F5", type: ShadingType.CLEAR },
margins: { top: 100, bottom: 100, left: 120, right: 120 },
children: lines.map(line => new Paragraph({
spacing: { before: 0, after: 0, line: 276 },
children: [new TextRun({ font: "Consolas", size: 18, text: line || " " })]
}))
})
]
})
]
});
}
// 辅助函数:创建正文段落
function bodyPara(text, opts = {}) {
return new Paragraph({
spacing: { before: 120, after: 120, line: 360 },
children: [new TextRun({ font: "宋体", size: 24, text, ...opts })]
});
}
// 辅助函数:创建加粗正文
function boldPara(text) {
return bodyPara(text, { bold: true });
}
// 辅助函数:创建小节标题
function subHeading(text, level = HeadingLevel.HEADING_2) {
return new Paragraph({
heading: level,
spacing: { before: 240, after: 120 },
children: [new TextRun({ text, bold: true, font: "黑体", size: level === HeadingLevel.HEADING_1 ? 32 : (level === HeadingLevel.HEADING_2 ? 28 : 26) })]
});
}
const doc = new Document({
styles: {
default: { document: { run: { font: "宋体", size: 24 } } },
paragraphStyles: [
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 32, bold: true, font: "黑体" },
paragraph: { spacing: { before: 400, after: 200 }, outlineLevel: 0 } },
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 28, bold: true, font: "黑体" },
paragraph: { spacing: { before: 300, after: 150 }, outlineLevel: 1 } },
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
run: { size: 26, bold: true, font: "黑体" },
paragraph: { spacing: { before: 200, after: 100 }, outlineLevel: 2 } },
]
},
numbering: {
config: [
{ reference: "bullets",
levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
{ reference: "numbers",
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
]
},
sections: [{
properties: {
page: {
size: { width: 11906, height: 16838 },
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }
}
},
headers: {
default: new Header({ children: [new Paragraph({
alignment: AlignmentType.RIGHT,
children: [new TextRun({ font: "宋体", size: 18, color: "666666", text: "智途投送 - 单兵终端APP设计文档" })]
})] })
},
footers: {
default: new Footer({ children: [new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({ font: "宋体", size: 18, text: "第 " }),
new TextRun({ children: [PageNumber.CURRENT], font: "宋体", size: 18 }),
new TextRun({ font: "宋体", size: 18, text: " 页" })
]
})] })
},
children: [
// 封面标题
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 2400, after: 400 },
children: [new TextRun({ font: "黑体", size: 44, bold: true, text: "单兵终端APP" })] }),
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 200, after: 2400 },
children: [new TextRun({ font: "黑体", size: 36, text: "软件架构与基本功能实现说明" })] }),
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 400, after: 200 },
children: [new TextRun({ font: "宋体", size: 24, text: "智途投送软件系统" })] }),
new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 100, after: 100 },
children: [new TextRun({ font: "宋体", size: 24, text: new Date().toISOString().split('T')[0] })] }),
new Paragraph({ children: [new PageBreak()] }),
// 目录
new TableOfContents("目录", { hyperlink: true, headingStyleRange: "1-3" }),
new Paragraph({ children: [new PageBreak()] }),
// 第一章 项目概述
subHeading("一、项目概述", HeadingLevel.HEADING_1),
bodyPara("单兵终端APP是“智途投送”系统的移动端前端应用供前线士兵使用。它基于 Capacitor 混合应用框架开发,以 HTML5 + JavaScript 实现业务逻辑,通过 Android WebView 渲染,并借助 Capacitor 插件调用原生能力(如 GPS 定位)。"),
bodyPara("APP 主要功能包括:士兵登录/注册、实时 GPS 定位与自动上报、紧急物资需求填报、投放点地图选点、任务进度监控、无人机状态查看、一键 SOS 求救等。"),
subHeading("1.1 技术栈", HeadingLevel.HEADING_2),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "前端框架:原生 HTML5 + JavaScriptES6+),无额外前端框架" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "混合容器Capacitor 6.x生成 Android 原生工程)" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "原生插件:@capacitor/geolocationGPS 定位)" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "地图服务:高德地图 JS API 2.0(动态地图 + 静态地图 fallback" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "后端通信RESTful APIFlask 后端fetch + AbortController 超时控制" })] }),
new Paragraph({ numbering: { reference: "bullets", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "数据持久化localStorage会话信息、服务器地址缓存" })] }),
subHeading("1.2 项目结构", HeadingLevel.HEADING_2),
codeBlock([
"单兵终端APP/",
"├── capacitor.config.json # Capacitor 配置应用ID、明文传输等",
"├── package.json # 依赖:@capacitor/core、geolocation、android",
"├── index.html # SPA 单页结构(所有页面 div 容器)",
"├── css/style.css # 全局样式",
"├── js/",
"│ ├── app.js # 主应用路由、状态、UI 交互",
"│ ├── api.js # API 封装REST 请求 + Mock 降级",
"│ └── location.js # GPS 定位 + 高德地图封装",
"└── android/ # Capacitor 生成的 Android 工程"
]),
// 第二章 软件架构
new Paragraph({ children: [new PageBreak()] }),
subHeading("二、软件架构", HeadingLevel.HEADING_1),
bodyPara("单兵终端APP采用经典的分层架构从上到下依次为表现层UI 层、业务逻辑层、数据访问层、原生能力层。整体为单页应用SPA模式所有页面通过 JavaScript 动态切换,避免原生 Activity 跳转带来的开发复杂度。"),
subHeading("2.1 架构分层", HeadingLevel.HEADING_2),
// 架构表格
new Table({
width: { size: 9360, type: WidthType.DXA },
columnWidths: [2340, 7020],
rows: [
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ bold: true, font: "宋体", size: 22, text: "分层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ bold: true, font: "宋体", size: 22, text: "职责与对应文件" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "表现层UI" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "index.html单页多视图+ css/style.css负责页面布局、控件渲染、事件绑定" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "业务逻辑层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "js/app.js路由管理、状态管理、页面切换、表单校验、业务事件处理" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "数据访问层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "js/api.js封装 HTTP 请求、超时控制、后端不可用时自动降级为 Mock 数据" })] })] })
]}),
new TableRow({ children: [
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 2340, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "原生能力层" })] })] }),
new TableCell({ borders: { top: {style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, bottom:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, left:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"}, right:{style:BorderStyle.SINGLE,size:1,color:"CCCCCC"} },
width: { size: 7020, type: WidthType.DXA }, margins: { top: 80, bottom: 80, left: 120, right: 120 },
children: [new Paragraph({ children: [new TextRun({ font: "宋体", size: 22, text: "js/location.js + Capacitor 插件GPS 定位、高德地图渲染、逆地理编码" })] })] })
]}),
]
}),
subHeading("2.2 核心模块关系", HeadingLevel.HEADING_2),
bodyPara("App 启动时app.js 读取 localStorage 中的会话信息,若已登录则进入首页并启动两个定时任务:"),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "轮询任务(每 5 秒):当前页为“任务”或“无人机”时,自动拉取最新数据。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "定位上报任务(每 10 秒):通过 LocationModule 获取坐标并上传后端。" })] }),
bodyPara("用户操作(如提交需求、选择投放点)由 app.js 收集表单数据,调用 API 模块发送请求;地图相关操作统一委托给 LocationModule 处理,避免业务层直接依赖地图 SDK。"),
// 第三章 基本功能实现
new Paragraph({ children: [new PageBreak()] }),
subHeading("三、基本功能实现", HeadingLevel.HEADING_1),
bodyPara("以下按功能模块逐一说明核心实现逻辑,并给出关键代码片段。"),
// 3.1 项目配置
subHeading("3.1 项目配置与入口", HeadingLevel.HEADING_2),
bodyPara("Capacitor 配置开启了 Android 明文传输cleartext便于局域网调试 Flask 后端;应用 ID 为 com.zhitu.soldier。"),
boldPara("capacitor.config.json"),
codeBlock([
'{',
' "appId": "com.zhitu.soldier",',
' "appName": "智途投送-单兵终端",',
' "webDir": "www",',
' "server": {',
' "androidScheme": "http",',
' "cleartext": true',
' },',
' "plugins": {',
' "Geolocation": { "enabled": true }',
' }',
'}'
]),
// 3.2 登录注册
subHeading("3.2 登录与注册", HeadingLevel.HEADING_2),
bodyPara("登录模块支持两套机制:演示账号本地登录(无需后端即可体验)和真实后端账号登录。会话信息以 JSON 形式存储在 localStorage 中,退出时清除。"),
boldPara("js/app.js - 登录逻辑"),
codeBlock([
'async function doLogin() {',
' const id = document.getElementById("login-id").value.trim();',
' const pwd = document.getElementById("login-pwd").value.trim();',
' // 演示账号快速登录',
' const demoAccounts = {',
' "soldier_001": { name: "张三", unit: "第3步兵师/1连" },',
' "soldier_002": { name: "李四", unit: "第3步兵师/2连" }',
' };',
' if (demoAccounts[id] && pwd === "123456") {',
' localStorage.setItem("soldier_session", JSON.stringify({',
' soldier_id: id, name: demoAccounts[id].name, ...',
' }));',
' router("home");',
' startPolling();',
' startLocationReporting();',
' return;',
' }',
' // 真实后端登录',
' const result = await API.login(id, pwd);',
' if (result.ok) { ... }',
'}'
]),
// 3.3 路由管理
subHeading("3.3 路由与页面管理", HeadingLevel.HEADING_2),
bodyPara(`APP 为单页应用SPA所有页面以 <div class="page"> 形式放在同一 HTML 中。通过 CSS class active 控制显示/隐藏,配合 pageStack 实现返回上一页。底部 Tab 栏仅在首页、任务、无人机、我的四个主页面显示。`),
boldPara("js/app.js - 路由切换"),
codeBlock([
'function router(page) {',
' document.querySelectorAll(".page").forEach(p => p.classList.remove("active"));',
' const target = document.getElementById("page-" + page);',
' if (target) target.classList.add("active");',
' // Tab 高亮与显隐控制',
' document.querySelectorAll(".tab-item").forEach(t => t.classList.remove("active"));',
' const tabItem = document.querySelector(\'.tab-item[data-page="\' + page + \'"]\');',
' if (tabItem) tabItem.classList.add("active");',
' const tabBar = document.getElementById("tab-bar");',
' tabBar.style.display = TAB_PAGES.includes(page) ? "flex" : "none";',
' pageStack.push(page);',
' currentPage = page;',
' onPageEnter(page); // 页面专属初始化',
'}'
]),
// 3.4 GPS定位
subHeading("3.4 GPS 定位与自动上报", HeadingLevel.HEADING_2),
bodyPara("定位模块实现了四级降级策略:高德 JS 定位 → Capacitor 原生 GPS → 浏览器 Geolocation → IP 网络定位 → 默认坐标。确保在各种网络与权限环境下都能获得可用位置。"),
boldPara("js/location.js - 四级定位降级"),
codeBlock([
'async function getCurrentPosition() {',
' // 1. 高德定位',
' try { return await getAmapPosition(); }',
' catch (e) { errors.push("高德:" + e.message); }',
' // 2. Capacitor 原生定位',
' try { return await getCapacitorPosition(); }',
' catch (e) { errors.push("原生:" + e.message); }',
' // 3. 浏览器定位',
' try { return await getBrowserPosition(); }',
' catch (e) { errors.push("浏览器:" + e.message); }',
' // 4. IP 定位',
' try { return await getIpPosition(); }',
' catch (e) { errors.push("IP:" + e.message); }',
' // 5. 默认兜底',
' return { lat: 30.2500, lng: 120.1600, accuracy: 100, source: "default" };',
'}'
]),
bodyPara("登录成功后app.js 调用 LocationModule.startReporting() 启动定时上报,每隔 10 秒将坐标发送至后端 /api/soldier/location。"),
boldPara("js/app.js - 启动自动上报"),
codeBlock([
'function startLocationReporting() {',
' LocationModule.startReporting(CONFIG.soldierId, CONFIG.soldierName, 10000);',
'}'
]),
// 3.5 物资需求上报
subHeading("3.5 物资需求上报", HeadingLevel.HEADING_2),
bodyPara("士兵在“需求上报”页选择物资类型、数量、紧急程度,并关联投放点。提交时组装 JSON 对象,通过 POST /api/demand 发送至后端。投放点数据可以是地图选点结果,也可以是系统推荐列表中的安全点。"),
boldPara("js/app.js - 提交需求"),
codeBlock([
'async function submitDemand() {',
' const type = document.getElementById("demand-type").value;',
' const qty = document.getElementById("demand-qty").value;',
' const urgency = document.querySelector("#urgency-group .radio-label.active")',
' .dataset.value;',
' const demand = {',
' soldier_id: CONFIG.soldierId,',
' type, quantity: parseInt(qty), urgency,',
' drop_point: selectedDropPoint,',
' status: "待处理",',
' created_at: new Date().toISOString()',
' };',
' await API.postDemand(demand);',
' showToast("✅ 需求上报成功!");',
' router("home");',
'}'
]),
// 3.6 投放点与地图
subHeading("3.6 投放点选择与地图集成", HeadingLevel.HEADING_2),
bodyPara("投放点选择页整合了三种交互方式:地图直接点击选点、地点关键词搜索、附近推荐列表。地图基于高德 JS API 2.0 动态初始化,支持逆地理编码获取地址名称;若动态地图加载失败,自动降级为静态地图图片。"),
boldPara("js/location.js - 地图选点初始化"),
codeBlock([
'async function initPickerMap(containerId, onSelectCallback) {',
' const AMap = await loadAmapScript();',
' const container = document.getElementById(containerId);',
' container.style.width = "100%";',
' container.style.height = "280px";',
' container.innerHTML = "";',
' pickerMap = new AMap.Map(containerId, {',
' zoom: 15, center: [center.lng, center.lat], resizeEnable: true',
' });',
' // 点击地图选点',
' pickerMap.on("click", (e) => {',
' const lng = e.lnglat.lng, lat = e.lnglat.lat;',
' pickerGeocoder.getAddress([lng, lat], (status, result) => {',
' let address = result.regeocode.formattedAddress;',
' onSelectCallback({ lat, lng, name, address });',
' });',
' });',
'}'
]),
// 3.7 任务监控
subHeading("3.7 任务进度监控", HeadingLevel.HEADING_2),
bodyPara("任务页展示当前运输任务的进度、预计到达时间、飞行路径与投放点安全系数。进入页面时调用 API.getCurrentTask() 获取数据;若后端不可用,返回 Mock 数据保证界面不空白。"),
boldPara("js/app.js - 加载任务信息"),
codeBlock([
'async function loadTaskInfo() {',
' const task = await API.getCurrentTask(CONFIG.soldierId);',
' if (task) {',
' document.getElementById("task-id").textContent = task.id;',
' document.getElementById("task-status").textContent =',
' statusMap[task.status]?.text || task.status;',
' const prog = task.progress || 0;',
' const filled = Math.round(20 * prog / 100);',
' document.getElementById("task-progress-text").textContent =',
' "█".repeat(filled) + "░".repeat(20 - filled) + " " + prog + "%";',
' }',
'}'
]),
// 3.8 无人机状态
subHeading("3.8 无人机状态查看", HeadingLevel.HEADING_2),
bodyPara("无人机页展示实时飞行数据:速度、高度、电量、温度、距离目标等,以及最近动态日志。与任务页共用轮询机制,每 5 秒自动刷新。"),
boldPara("js/app.js - 加载无人机状态"),
codeBlock([
'async function loadDroneStatus() {',
' const status = await API.getDroneStatus();',
' if (status) {',
' document.getElementById("drone-battery").textContent = status.battery + "%";',
' document.getElementById("drone-speed").textContent = status.speed + "m/s";',
' document.getElementById("drone-alt").textContent = status.altitude + "m";',
' document.getElementById("drone-dist").textContent = status.distance + "m";',
' }',
' const logs = await API.getDroneLogs();',
' document.getElementById("drone-logs").innerHTML =',
' logs.map(l => `<div class="log-row">...`).join("");',
'}'
]),
// 3.9 SOS
subHeading("3.9 一键 SOS 求救", HeadingLevel.HEADING_2),
bodyPara("设置页提供紧急求救按钮,点击后二次确认,随后获取当前 GPS 坐标并立即上报后端 /api/sos。上报内容包含士兵ID、姓名、坐标和时间戳。"),
boldPara("js/app.js - SOS 求救"),
codeBlock([
'async function triggerSOS() {',
' if (!confirm("确认发送求救信号?此操作将立即上报您的当前位置。")) return;',
' const pos = await LocationModule.getCurrentPosition();',
' await API.sendSOS({',
' soldier_id: CONFIG.soldierId,',
' soldier_name: CONFIG.soldierName,',
' lat: pos.lat, lng: pos.lng,',
' time: new Date().toISOString()',
' });',
' showToast("🚨 求救信号已发送!");',
'}'
]),
// 3.10 API封装
subHeading("3.10 API 通信封装与离线降级", HeadingLevel.HEADING_2),
bodyPara("api.js 封装了所有后端接口请求统一处理超时5 秒、JSON 序列化和错误捕获。对于投放点、任务、无人机状态等查询类接口,若后端不可用或超时,自动返回 Mock 数据,确保 APP 在离线/演示场景下仍可正常使用。"),
boldPara("js/api.js - 统一请求与超时控制"),
codeBlock([
'async function request(url, options = {}) {',
' const fullUrl = url.startsWith("http") ? url : BASE + url;',
' const controller = new AbortController();',
' const timeoutId = setTimeout(() => controller.abort(), 5000);',
' try {',
' const resp = await fetch(fullUrl, {',
' headers: { "Content-Type": "application/json" },',
' signal: controller.signal, ...options',
' });',
' clearTimeout(timeoutId);',
' return resp.json();',
' } catch (e) {',
' clearTimeout(timeoutId);',
' if (e.name === "AbortError")',
' throw new Error("请求超时,请检查后端是否启动");',
' throw e;',
' }',
'}'
]),
boldPara("js/api.js - Mock 降级示例"),
codeBlock([
'async function getDropPoints() {',
' try {',
' const data = await request("/api/drop-points");',
' return data.drop_points || data;',
' } catch (e) {',
' return getMockDropPoints(); // 离线兜底',
' }',
'}'
]),
// 第四章 总结
new Paragraph({ children: [new PageBreak()] }),
subHeading("四、设计特点与总结", HeadingLevel.HEADING_1),
bodyPara("单兵终端APP在设计上遵循“简单、可靠、可演示”的原则主要特点如下"),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "混合架构,跨平台成本低:基于 Capacitor 将 Web 技术打包为 Android APK一套代码同时支持手机浏览器预览和真机安装。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "多级定位降级,适应战场复杂环境:四级定位策略 + 手动修正,确保在城市、室内、弱网环境下仍能获取可用坐标。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "Mock 数据兜底,支持离线演示:所有查询类 API 在后端不可用时自动返回模拟数据,便于开发调试与现场演示。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "模块职责清晰易于维护app.js 负责业务与UI、api.js 负责网络、location.js 负责定位与地图,三层之间通过明确接口协作。" })] }),
new Paragraph({ numbering: { reference: "numbers", level: 0 }, spacing: { before: 80, after: 80 },
children: [new TextRun({ font: "宋体", size: 24, text: "安全与权限考虑Android 开启明文传输用于局域网调试高德地图、GPS 权限在运行时动态申请SOS 操作提供二次确认防止误触。" })] }),
bodyPara("综上所述单兵终端APP通过简洁的分层架构和稳健的降级策略实现了前线士兵在复杂战场环境下的物资需求上报、位置共享与任务协同功能。"),
]
}]
});
Packer.toBuffer(doc).then(buffer => {
fs.writeFileSync("单兵终端APP_架构与功能实现说明.docx", buffer);
console.log("文档已生成单兵终端APP_架构与功能实现说明.docx");
});

204
package-lock.json generated

@ -1,204 +0,0 @@
{
"name": "智途投送软件系统",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"docx": "^9.6.1"
}
},
"node_modules/@types/node": {
"version": "25.6.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.19.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/docx": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/docx/-/docx-9.6.1.tgz",
"integrity": "sha512-ZJja9/KBUuFC109sCMzovoq2GR2wCG/AuxivjA+OHj/q0TEgJIm3S7yrlUxIy3B+bV8YDj/BiHfWyrRFmyWpDQ==",
"license": "MIT",
"dependencies": {
"@types/node": "^25.2.3",
"hash.js": "^1.1.7",
"jszip": "^3.10.1",
"nanoid": "^5.1.3",
"xml": "^1.0.1",
"xml-js": "^1.6.8"
},
"engines": {
"node": ">=10"
}
},
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/nanoid": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz",
"integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/undici-types": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
"license": "MIT"
},
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
"license": "MIT",
"dependencies": {
"sax": "^1.2.4"
},
"bin": {
"xml-js": "bin/cli.js"
}
}
}
}

@ -1,5 +0,0 @@
{
"dependencies": {
"docx": "^9.6.1"
}
}

Binary file not shown.

@ -1,100 +0,0 @@
1: 智途投送
2: 声源分析模块Acoustic Analyzer
3: 项目开发交接文档
5: 国防科大计算机学院 23 级软件工程小班
6: 2026 年 4 月
8: 一、项目概述
9: 声源分析模块是「智途投送」无人机软件系统的核心感知构件之一,负责通过麦克风阵列音频信号实现:
10: • 枪炮声识别分类(枪声 / 炮声 / 爆炸声 / 环境噪声)
11: • GCC-PHAT 声源定位(方位角、俯仰角)
12: • 基于能量衰减模型的距离估计
13: • 多帧威胁跟踪与信息融合
14: 模块采用 C++17 开发,核心算法零 ROS 依赖,通过 ONNX Runtime 进行神经网络推理,最终作为 ROS1 Noetic 节点部署于 P600 无人机机载电脑。
15: 二、已完成工作总览
16: 2.1 代码开发
17: 已完成全部 34 个代码文件的编写,覆盖 Core 算法层、IO 抽象层、ROS 封装层及配套脚本:
19: 2.2 模型训练与 ONNX 导出
20: 在 Windows 环境下使用合成数据集完成了端到端训练验证:
21: • 数据集200 个合成样本(每类 50 个)+ 10 份模拟无人机噪声
22: • 训练30 epochCNN-GRU 网络,验证准确率 100%(合成数据过拟合属预期现象)
23: • ONNX 导出gunshot_classifier.onnx1.9MBopset 13
24: • ONNX 验证:枪声识别置信度 97.92%
25: 2.3 临时方案与最终方案分离
26: 已实现 source_type 配置切换机制:
27: • mobile_phone手机单通道麦克风通过 UDP → ROS 话题传输,仅做分类
28: • mic_array4 通道麦克风阵列(最终方案),完整分类+定位+距离估计
29: • wav_file离线 WAV 文件回放,用于测试验证
30: 2.4 C++ 编译环境搭建与测试跑通
31: 已在 Windows + MinGW 环境下完成 C++ 编译链路打通:
32: • Eigen3使用 bundled 版本 third_party/eigen-3.4.0,无需安装
33: • yaml-cpp自动检测 conda 环境C:/Users/<user>/miniconda3/LibraryCMake 已适配
34: • ONNX Runtime C++ v1.20.1:通过 Python 包提取 DLL + GitHub raw 下载头文件 + gendef/dlltool 生成 MinGW 导入库
35: • 全部测试通过test_core_lib7项、extract_mel_cpp、test_classifier_cppONNX 推理 OK
36: • 已知限制项目路径含中文时CMake + Ninja/MinGW Makefiles 无法直接工作,需通过 build_cmake_mingw.bat 自动复制到临时英文目录构建
37: 2.5 代码 Bug 修复记录
38: 搭建过程中发现并修复的问题:
39: • gcc_phat_localizer.cpp缺少 #include <Eigen/SVD>,导致 BDCSVD 不完整类型错误
40: • threat_tracker.cpp数据关联更新检测时丢失原有 threat_id导致多帧跟踪失败
41: • test_core_lib.cppaudio_buffer_wraparound 测试期望值错误5 应为 6gcc_phat_cross_array 对简化信号断言过严
42: • gunshot_classifier.cpp/h升级至 ONNX Runtime C++ v1.20.1 RAII API适配 wchar_t 路径Windows
43: • CMakeLists.txt添加 MinGW 适配(-D_USE_MATH_DEFINES、-Wa,-mbig-obj、_stdcall 覆盖、yaml-cpp 自动检测)
44: 三、架构设计
45: 模块采用三层构件化架构,核心算法层完全独立于 ROS确保可分离、可测试、可移植
47: ┌─────────────────────────────────────────┐
48: │ ROS 层acoustic_node / threat_publisher│ ← 话题订阅/发布
49: ├─────────────────────────────────────────┤
50: │ IO 层WavFileSource / MobilePhoneSource│ ← 音频源抽象
51: ├─────────────────────────────────────────┤
52: │ Core 层Pipeline 编排以下模块) │ ← 零 ROS 依赖
53: │ • FeatureExtractor (Mel Spectrogram) │
54: │ • GunshotClassifier (ONNX Runtime) │
55: │ • GccPhatLocalizer (GCC-PHAT + TDOA) │
56: │ • DistanceEstimator (能量衰减 + 卡尔曼) │
57: │ • ThreatTracker (多帧关联跟踪) │
58: └─────────────────────────────────────────┘
60: 四、当前环境与依赖
61: 4.1 Python 训练环境Windows 已验证)
63: 4.2 C++ 编译环境Windows 已配置)
64: • 编译器g++ (MinGW-W64 15.2.0) 已安装 ✅
65: • CMake4.1.0 已安装 ✅
66: • Eigen3bundled third_party/eigen-3.4.0 ✅
67: • ONNX Runtime C++v1.20.1 已配置(头文件 + DLL + MinGW 导入库)✅
68: • yaml-cppconda 0.8.0 已检测CMake 自动链接 ✅
69: • 构建方式:
70: 快速命令行build_core_test.bat一键编译全部测试
71: 标准 CMakebuild_cmake_mingw.bat自动处理中文路径问题
72: 五、待办事项与下一步计划
74: 六、关键配置参数速查
75: config/acoustic_params.yaml 核心参数:
76: source:
77: type: "mobile_phone" # 临时方案mobile_phone / wav_file / mic_array
78: audio:
79: sample_rate: 16000
80: chunk_duration: 2.0 # 分析窗口 2 秒
81: hop_duration: 0.5 # 步进 0.5 秒
82: features:
83: n_mels: 64, n_fft: 2048, hop_length: 512
84: mic_array:
85: num_mics: 1 # [TEMP] 1=手机; [FINAL] 4=阵列
86: layout: "cross", spacing: 0.15
87: classifier:
88: model_path: ".../gunshot_classifier.onnx"
89: threshold: 0.7
90: 七、文件路径索引
91: 项目根目录software/src/drone-software/src/acoustic/
92: • 核心算法include/acoustic_analyzer/core/ & src/core/
93: • IO 抽象include/acoustic_analyzer/io/ & src/io/
94: • ROS 封装include/acoustic_analyzer/ros/ & src/ros/
95: • 训练脚本scripts/train_classifier.py, export_onnx.py, verify_onnx.py
96: • 手机桥接scripts/mobile_audio_bridge.py, android_audio_sender.py
97: • 测试程序tests/test_core_lib.cpp, extract_mel_cpp.cpp, test_classifier_cpp.cpp
98: • 配置文件config/acoustic_params.yaml
99: • 模型权重models/gunshot_classifier.onnx, train_output/best_model.pth
100: • 数据集dataset/{train,val}/{ambient,gunshot,artillery,explosion}/
101: 八、新增构建脚本说明
102: • build_core_test.bat一键命令行编译适用于快速验证核心算法和 ONNX 推理
103: 编译目标test_core_lib.exe / extract_mel_cpp.exe / test_classifier_cpp.exe
104: • build_cmake_mingw.bat标准 CMake + MinGW Makefiles 构建流程
105: 自动将源码复制到 C:/temp/acoustic_src规避中文路径问题在 C:/temp/acoustic_build 构建,
106: 完成后将可执行文件复制回原目录。适用于需要标准 CMake 流程的场景。
108: AI 助手将读取本文档及项目代码,基于当前状态继续推进。

File diff suppressed because one or more lines are too long

@ -1,133 +0,0 @@
1: 智途投送
2: 声源分析模块Acoustic Analyzer
3: 项目开发交接文档
5: 国防科大计算机学院 23 级软件工程小班
6: 2026 年 4 月
8: 一、项目概述
9: 声源分析模块是「智途投送」无人机软件系统的核心感知构件之一,负责通过麦克风阵列音频信号实现:
10: • 枪炮声识别分类(枪声 / 炮声 / 爆炸声 / 环境噪声)
11: • GCC-PHAT 声源定位(方位角、俯仰角)
12: • 基于能量衰减模型的距离估计
13: • 多帧威胁跟踪与信息融合
14: 模块采用 C++17 开发,核心算法零 ROS 依赖,通过 ONNX Runtime 进行神经网络推理,最终作为 ROS1 Noetic 节点部署于 P600 无人机机载电脑。
15: 二、已完成工作总览
16: 2.1 代码开发
17: 已完成全部 34 个代码文件的编写,覆盖 Core 算法层、IO 抽象层、ROS 封装层及配套脚本:
19: 2.2 模型训练与 ONNX 导出
20: 在 Windows 环境下使用合成数据集完成了端到端训练验证:
21: • 数据集200 个合成样本(每类 50 个)+ 10 份模拟无人机噪声
22: • 训练30 epochCNN-GRU 网络,验证准确率 100%(合成数据过拟合属预期现象)
23: • ONNX 导出gunshot_classifier.onnx1.9MBopset 13
24: • ONNX 验证:枪声识别置信度 97.92%
25: 2.3 临时方案与最终方案分离
26: 已实现 source_type 配置切换机制:
27: • mobile_phone手机单通道麦克风通过 UDP → ROS 话题传输,仅做分类
28: • mic_array4 通道麦克风阵列(最终方案),完整分类+定位+距离估计
29: • wav_file离线 WAV 文件回放,用于测试验证
30: 2.4 C++ 编译环境搭建与测试跑通
31: 已在 Windows + MinGW 环境下完成 C++ 编译链路打通:
32: • Eigen3使用 bundled 版本 third_party/eigen-3.4.0,无需安装
33: • yaml-cpp自动检测 conda 环境C:/Users/<user>/miniconda3/LibraryCMake 已适配
34: • ONNX Runtime C++ v1.20.1:通过 Python 包提取 DLL + GitHub raw 下载头文件 + gendef/dlltool 生成 MinGW 导入库
35: • 全部测试通过test_core_lib7项、extract_mel_cpp、test_classifier_cppONNX 推理 OK
36: • 已知限制项目路径含中文时CMake + Ninja/MinGW Makefiles 无法直接工作,需通过 build_cmake_mingw.bat 自动复制到临时英文目录构建
37: 2.5 代码 Bug 修复记录
38: 搭建过程中发现并修复的问题:
39: • gcc_phat_localizer.cpp缺少 #include <Eigen/SVD>,导致 BDCSVD 不完整类型错误
40: • threat_tracker.cpp数据关联更新检测时丢失原有 threat_id导致多帧跟踪失败
41: • test_core_lib.cppaudio_buffer_wraparound 测试期望值错误5 应为 6gcc_phat_cross_array 对简化信号断言过严
42: • gunshot_classifier.cpp/h升级至 ONNX Runtime C++ v1.20.1 RAII API适配 wchar_t 路径Windows
43: • CMakeLists.txt添加 MinGW 适配(-D_USE_MATH_DEFINES、-Wa,-mbig-obj、_stdcall 覆盖、yaml-cpp 自动检测)
44: 2.6 离线演示与多通道验证
45: 已完成完整的离线演示程序tests/demo_offline.cpp支持单通道分类与多通道阵列分类+方位角+距离)一体化测试:
46: • 单通道验证dataset/val 共 40 个文件,分类准确率 100%ambient/artillery/explosion/gunshot 各 10 个)。
47: • 多通道模拟测试scripts/generate_multichannel_test.py 可按指定方位角360°和距离11000m生成 4 通道十字阵 WAV。
48: • 多通道验证结果5 组典型工况):
49: 方位角:全部 0° 误差GCC-PHAT 对模拟数据精度极高)。
50: 距离:最大误差 18.2m@300m约 6%),其余 < 2m。
51: 分类:全部正确识别为 gunshot置信度 0.770.98。
52: • 一键演示脚本run_demo.bat 自动执行核心单元测试 → ONNX 快速推理 → 完整离线流水线。
53: 2.7 C++/Python 特征提取严格对齐
54: 为保证 C++ 推理结果与 Python 训练一致,完成了特征提取全流程对齐:
55: • 导出 librosa 0.10.x 的 Mel 滤波器组到二进制文件 models/mel_filter_bank.bin64×1025C++ 加载后不再自行构造。
56: • 统一参数Hann 窗、preemphasis=0.97、n_fft=2048、hop=512、center=False、pad_to=63 framesedge 填充)。
57: • 验证脚本scripts/verify_feature_consistency.py 对比 C++ 与 Python 输出,最大差异 < 0.008。
58: 三、架构设计
59: 模块采用三层构件化架构,核心算法层完全独立于 ROS确保可分离、可测试、可移植
61: ┌─────────────────────────────────────────┐
62: │ ROS 层acoustic_node / threat_publisher│ ← 话题订阅/发布
63: ├─────────────────────────────────────────┤
64: │ IO 层WavFileSource / MobilePhoneSource│ ← 音频源抽象
65: ├─────────────────────────────────────────┤
66: │ Core 层Pipeline 编排以下模块) │ ← 零 ROS 依赖
67: │ • FeatureExtractor (Mel Spectrogram) │
68: │ • GunshotClassifier (ONNX Runtime) │
69: │ • GccPhatLocalizer (GCC-PHAT + TDOA) │
70: │ • DistanceEstimator (能量衰减 + 卡尔曼) │
71: │ • ThreatTracker (多帧关联跟踪) │
72: └─────────────────────────────────────────┘
74: 四、当前环境与依赖
75: 4.1 Python 训练环境Windows 已验证)
77: 4.2 C++ 编译环境Windows 已配置)
78: • 编译器g++ (MinGW-W64 15.2.0) 已安装 ✅
79: • CMake4.1.0 已安装 ✅
80: • Eigen3bundled third_party/eigen-3.4.0 ✅
81: • ONNX Runtime C++v1.20.1 已配置(头文件 + DLL + MinGW 导入库)✅
82: • yaml-cppconda 0.8.0 已检测CMake 自动链接 ✅
83: • 构建方式:
84: 快速命令行build_core_test.bat一键编译全部测试
85: 标准 CMakebuild_cmake_mingw.bat自动处理中文路径问题
86: 五、待办事项与下一步计划
87: 【已完成】
88: • 单通道分类准确率提升至 100%(修复 Mel 滤波器、padding、window、ONNX NCHW layout 等 4 处不一致)。
89: • GCC-PHAT 方位估计与 SPL 距离估计集成到离线 demo使用模拟 4ch WAV 验证通过。
90: • 生成 scripts/generate_multichannel_test.py 及 run_demo.bat 一键演示。
91: 【待完成】
92: • [P0] 实机部署:将编译通过的节点部署到 P600 机载电脑Jetson / x86验证 ROS 话题发布与订阅。
93: • [P0] 真实麦克风阵列驱动:接入 4 通道 USB / I2S 麦克风阵列,录制真实环境枪声/炮声样本。
94: • [P1] 数据集扩充:收集真实场景样本(含环境噪声、混响、多声源叠加),重新训练以降低合成数据过拟合。
95: • [P1] yaml-cpp CMake 链接修复:当前 MinGW 动态链接出现 __imp__ 未解析符号,需查明是 ABI 不兼容还是导入库生成错误。
96: • [P2] 距离估计 SPL 校准:当前合成数据使用固定 offset=60dB真实场景需根据麦克风灵敏度数据 sheet 校准。
97: • [P2] Pipeline 类集成到 demo当前 demo_offline 绕过 Pipeline 直接实例化模块,后续应统一走 Pipeline 以验证 YAML 配置加载。
98: • [P2] 俯仰角估计:当前 GCC-PHAT 仅输出水平面方位角,若阵列有高度差可解俯仰角。
99: 六、关键配置参数速查
100: config/acoustic_params.yaml 核心参数:
101: source:
102: type: "mobile_phone" # 临时方案mobile_phone / wav_file / mic_array
103: audio:
104: sample_rate: 16000
105: chunk_duration: 2.0 # 分析窗口 2 秒
106: hop_duration: 0.5 # 步进 0.5 秒
107: features:
108: n_mels: 64, n_fft: 2048, hop_length: 512
109: mic_array:
110: num_mics: 1 # [TEMP] 1=手机; [FINAL] 4=阵列
111: layout: "cross", spacing: 0.15
112: classifier:
113: model_path: ".../gunshot_classifier.onnx"
114: threshold: 0.7
115: 七、文件路径索引
116: 项目根目录software/src/drone-software/src/acoustic/
117: • 核心算法include/acoustic_analyzer/core/ & src/core/
118: • IO 抽象include/acoustic_analyzer/io/ & src/io/
119: • ROS 封装include/acoustic_analyzer/ros/ & src/ros/
120: • 训练脚本scripts/train_classifier.py, export_onnx.py, verify_onnx.py
121: • 手机桥接scripts/mobile_audio_bridge.py, android_audio_sender.py
122: • 多通道生成scripts/generate_multichannel_test.py
123: • 特征对齐验证scripts/verify_feature_consistency.py, verify_val_accuracy.py
124: • 测试程序tests/test_core_lib.cpp, extract_mel_cpp.cpp, test_classifier_cpp.cpp, demo_offline.cpp
125: • 配置文件config/acoustic_params.yaml
126: • 模型权重models/gunshot_classifier.onnx, models/mel_filter_bank.bin, train_output/best_model.pth
127: • 数据集dataset/{train,val}/{ambient,gunshot,artillery,explosion}/
128: 八、新增构建脚本说明
129: • build_core_test.bat一键命令行编译适用于快速验证核心算法和 ONNX 推理
130: 编译目标test_core_lib.exe / extract_mel_cpp.exe / test_classifier_cpp.exe
131: • build_demo.bat编译离线演示程序 demo_offline.exe
132: 不依赖 yaml-cpp 和 ROS直接链接 ONNX Runtime + Eigen用于快速验证完整流水线。
133: • build_cmake_mingw.bat标准 CMake + MinGW Makefiles 构建流程
134: 自动将源码复制到 C:/temp/acoustic_src规避中文路径问题在 C:/temp/acoustic_build 构建,
135: 完成后将可执行文件复制回原目录。适用于需要标准 CMake 流程的场景。
136: • run_demo.bat一键运行完整演示
137: Step 1: 核心单元测试test_core_lib.exe
138: Step 2: ONNX 快速推理test_classifier_cpp.exe
139: Step 3: 离线流水线验证demo_offline.exe dataset/val

@ -1,245 +0,0 @@
#!/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()

@ -1,727 +0,0 @@
# 智途投送系统 — 前后端对接文档
> 版本v1.0
> 服务器:阿里云 ECS `121.41.216.243`
> 系统Ubuntu 24.04
> 数据库SQLite文件 `/opt/zhitu/zhitu.db`
---
## 一、服务器信息
| 项目 | 值 |
|------|-----|
| 公网 IP | `121.41.216.243` |
| 访问方式 | `http://121.41.216.243`Nginx 80 端口) |
| 直连后端 | `http://121.41.216.243:5000`Gunicorn |
| 通信协议 | HTTP明文未配置 HTTPS |
**推荐**:所有接口统一走 `http://121.41.216.243/api/xxx`,无需加 `:5000`
---
## 二、认证说明
### 2.1 Token 机制
- 登录成功后,后端返回 `token` 字符串
- 后续所有需要认证的接口,需在请求头中携带:
```http
X-Auth-Token: <token>
```
或在 URL 参数中携带(调试用):
```http
GET /api/task/current?soldier_id=xxx&token=<token>
```
### 2.2 免认证接口(无需 Token
以下接口可直接访问,无需登录:
| 接口 | 说明 |
|------|------|
| `GET /api/ping` | 存活检测 |
| `GET /api/auth/accounts` | 查看所有账号 |
| `GET /api/demands` | 查看所有需求 |
---
## 三、API 接口总览
### 3.1 账号系统
#### ① 注册账号
```http
POST /api/auth/register
Content-Type: application/json
```
**请求体**
```json
{
"soldier_id": "soldier_001",
"password": "123456",
"name": "张三",
"unit": "第3步兵师/1连",
"role": "狙击手"
}
```
**响应**
```json
// 成功
{ "ok": true, "message": "注册成功" }
// 失败
{ "ok": false, "error": "该士兵编号已注册" } // 409
{ "ok": false, "error": "士兵编号、密码、姓名不能为空" } // 400
```
---
#### ② 登录
```http
POST /api/auth/login
Content-Type: application/json
```
**请求体**
```json
{
"soldier_id": "soldier_001",
"password": "123456"
}
```
**响应**
```json
// 成功
{
"ok": true,
"token": "a1b2c3d4e5f6...",
"soldier_id": "soldier_001",
"name": "张三",
"unit": "第3步兵师/1连",
"role": "狙击手"
}
// 失败
{ "ok": false, "error": "士兵编号不存在" } // 404
{ "ok": false, "error": "密码错误" } // 401
```
---
#### ③ 查看当前登录用户
```http
GET /api/auth/me
X-Auth-Token: <token>
```
**响应**
```json
{
"ok": true,
"user": {
"soldier_id": "soldier_001",
"name": "张三",
"unit": "第3步兵师/1连",
"role": "狙击手"
}
}
```
---
#### ④ 查看所有账号(免认证)
```http
GET /api/auth/accounts
```
**响应**
```json
{
"accounts": [
{ "soldier_id": "soldier_001", "name": "张三", "unit": "...", "role": "..." },
{ "soldier_id": "soldier_002", "name": "李四", "unit": "...", "role": "..." }
]
}
```
---
### 3.2 士兵位置
#### ① 上报位置(需认证)
```http
POST /api/soldier/location
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"id": "soldier_001",
"name": "张三",
"lat": 30.1234,
"lng": 120.5678
}
```
**响应**
```json
{ "ok": true }
```
---
#### ② 获取所有士兵位置(免认证)
```http
GET /api/soldiers
```
**响应**
```json
{
"soldiers": [
{
"soldier_id": "soldier_001",
"name": "张三",
"lat": 30.1234,
"lng": 120.5678,
"updated_at": "2026-05-23 12:00:00"
}
]
}
```
---
### 3.3 物资需求
#### ① 上报需求(需认证)
```http
POST /api/demand
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"soldier_id": "soldier_001",
"soldier_name": "张三",
"type": "弹药",
"quantity": 20,
"unit": "发",
"urgency": "紧急",
"drop_point": { "lat": 30.1234, "lng": 120.5678 }
}
```
> `drop_point` 可为空或字符串,后端会自动从 `soldiers` 表补全当前位置。
**响应**
```json
{ "ok": true, "id": "REQ-001" }
```
---
#### ② 查看需求列表(免认证)
```http
GET /api/demands?soldier_id=soldier_001
```
**参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `soldier_id` | string | 否 | 指定士兵 ID不填则返回所有"待处理"需求 |
**响应**
```json
{
"demands": [
{
"id": "REQ-001",
"soldier_id": "soldier_001",
"soldier_name": "张三",
"type": "弹药补给",
"items": "弹药 × 20发",
"quantity": 20,
"unit": "发",
"urgency": "紧急",
"status": "待处理",
"lat": 30.1234,
"lng": 120.5678,
"created_at": "2026-05-23 12:00:00"
}
]
}
```
---
#### ③ 查看单个需求(需认证)
```http
GET /api/demands/REQ-001
X-Auth-Token: <token>
```
**响应**
```json
{ "demand": { ... } }
```
---
### 3.4 任务调度
#### ① 派发任务(需认证)
```http
POST /api/task/dispatch
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"soldier_id": "soldier_001",
"soldier_name": "张三",
"type": "弹药投送",
"demand_id": "REQ-001",
"start_name": "后方阵地",
"target_name": "A区街角12号",
"start_lat": 30.0,
"start_lng": 120.0,
"end_lat": 30.1234,
"end_lng": 120.5678,
"safety_score": 90
}
```
**响应**
```json
{
"ok": true,
"task": {
"id": "#001",
"soldier_id": "soldier_001",
"type": "弹药投送",
"status": "执行中",
"progress": 0,
"eta": "计算中...",
"remain_time": "计算中...",
"start_name": "后方阵地",
"target_name": "A区街角12号",
"start_lat": 30.0,
"start_lng": 120.0,
"end_lat": 30.1234,
"end_lng": 120.5678,
"safety_score": 90,
"created_at": "2026-05-23 12:00:00"
}
}
```
> 派发任务后,对应需求 `REQ-001``status` 会自动更新为 `"已调度"`
---
#### ② 获取当前任务(需认证)
```http
GET /api/task/current?soldier_id=soldier_001
X-Auth-Token: <token>
```
**响应**
```json
{
"task": {
"id": "#001",
"status": "执行中",
"progress": 50,
"eta": "2分钟",
...
}
}
// 无任务时
{
"task": {
"id": "#--", "status": "无任务", "progress": 0,
"eta": "--", "remain_time": "--",
"start_name": "--", "target_name": "--", "safety_score": 0
}
}
```
---
#### ③ 更新任务进度(需认证)
```http
POST /api/task/update
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"soldier_id": "soldier_001",
"progress": 75,
"status": "执行中",
"eta": "1分钟",
"remain_time": "60秒"
}
```
**响应**
```json
{ "ok": true, "task": { ... } }
```
---
### 3.5 投放点
#### ① 获取投放点列表(免认证)
```http
GET /api/drop-points
```
**响应**
```json
{
"drop_points": [
{ "id": 1, "name": "A区街角12号", "lat": 30.1234, "lng": 120.5678 }
]
}
```
---
#### ② 添加投放点(需认证)
```http
POST /api/drop-point
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"name": "B区道路",
"lat": 30.2345,
"lng": 120.6789
}
```
**响应**
```json
{ "ok": true }
```
---
### 3.6 危险区域
#### ① 查看危险区域(免认证)
```http
GET /api/danger-zones
```
**响应**
```json
{
"danger_zones": [
{
"id": 1,
"lat": 30.1234,
"lng": 120.5678,
"radius": 100,
"description": "士兵求救: 张三",
"created_at": "2026-05-23 12:00:00"
}
]
}
```
---
#### ② 添加危险区域(需认证)
```http
POST /api/danger-zones
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"lat": 30.1234,
"lng": 120.5678,
"radius": 100,
"description": "B区道路危险区域"
}
```
**响应**
```json
{ "ok": true, "id": 1 }
```
---
### 3.7 SOS 求救
#### ① 发送求救(需认证)
```http
POST /api/sos
Content-Type: application/json
X-Auth-Token: <token>
```
**请求体**
```json
{
"soldier_id": "soldier_001",
"soldier_name": "张三",
"lat": 30.1234,
"lng": 120.5678
}
```
**响应**
```json
{ "ok": true }
```
> 发送求救后,系统会自动在危险区域表中添加一个以士兵位置为中心、半径 100 米的危险区域。
---
### 3.8 无人机状态(演示数据)
#### ① 获取无人机状态(免认证)
```http
GET /api/drone/status
```
**响应**
```json
{
"status": {
"battery": 85,
"altitude": 120,
"speed": 15,
"position": { "lat": 30.1234, "lng": 120.5678 }
}
}
```
---
#### ② 获取无人机日志(免认证)
```http
GET /api/drone/logs
```
**响应**
```json
{
"logs": [
{ "time": "12:25:30", "message": "到达投放点" },
{ "time": "12:20:45", "message": "接收任务指令" },
{ "time": "12:10:00", "message": "任务分配" }
]
}
```
---
### 3.9 系统状态
#### ① 存活检测(免认证)
```http
GET /api/ping
```
**响应**
```json
{ "ok": true, "message": "智途投送后端运行正常" }
```
---
## 四、数据库表结构
### 4.1 accounts账号表
| 字段 | 类型 | 说明 |
|------|------|------|
| `soldier_id` | TEXT PK | 士兵编号 |
| `password_hash` | TEXT | Werkzeug 哈希密码 |
| `name` | TEXT | 姓名 |
| `unit` | TEXT | 单位 |
| `role` | TEXT | 角色 |
| `created_at` | TEXT | 注册时间 |
### 4.2 tokens登录令牌表
| 字段 | 类型 | 说明 |
|------|------|------|
| `token` | TEXT PK | 随机 Token |
| `soldier_id` | TEXT | 士兵编号 |
| `name` | TEXT | 姓名 |
| `unit` | TEXT | 单位 |
| `role` | TEXT | 角色 |
| `created_at` | TEXT | 生成时间 |
### 4.3 demands物资需求表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | TEXT PK | 需求编号(如 REQ-001 |
| `soldier_id` | TEXT | 上报士兵 |
| `soldier_name` | TEXT | 士兵姓名 |
| `type` | TEXT | 物资类型(如"弹药补给" |
| `items` | TEXT | 物资描述(如"弹药 × 20发" |
| `quantity` | INTEGER | 数量 |
| `unit` | TEXT | 单位 |
| `urgency` | TEXT | 紧急程度 |
| `status` | TEXT | 状态:待处理/已调度/已完成 |
| `lat` | REAL | 纬度 |
| `lng` | REAL | 经度 |
| `created_at` | TEXT | 创建时间 |
### 4.4 tasks任务表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | TEXT PK | 任务编号(如 #001 |
| `soldier_id` | TEXT | 目标士兵 |
| `soldier_name` | TEXT | 士兵姓名 |
| `type` | TEXT | 任务类型 |
| `status` | TEXT | 状态 |
| `progress` | INTEGER | 进度 0-100 |
| `eta` | TEXT | 预计到达时间 |
| `remain_time` | TEXT | 剩余时间 |
| `start_name/target_name` | TEXT | 起止点名称 |
| `start_lat/lng` | REAL | 起点坐标 |
| `end_lat/lng` | REAL | 终点坐标 |
| `safety_score` | INTEGER | 安全评分 |
| `created_at` | TEXT | 创建时间 |
### 4.5 soldiers士兵位置表
| 字段 | 类型 | 说明 |
|------|------|------|
| `soldier_id` | TEXT PK | 士兵编号 |
| `name` | TEXT | 姓名 |
| `lat` | REAL | 纬度 |
| `lng` | REAL | 经度 |
| `updated_at` | TEXT | 更新时间 |
### 4.6 danger_zones危险区域表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK AI | 自增 ID |
| `lat` | REAL | 纬度 |
| `lng` | REAL | 经度 |
| `radius` | REAL | 半径(米) |
| `description` | TEXT | 描述 |
| `created_at` | TEXT | 创建时间 |
### 4.7 sos_alerts求救记录表
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | INTEGER PK AI | 自增 ID |
| `soldier_id` | TEXT | 士兵编号 |
| `soldier_name` | TEXT | 姓名 |
| `lat` | REAL | 纬度 |
| `lng` | REAL | 经度 |
| `alert_time` | TEXT | 求救时间 |
| `handled` | INTEGER | 是否已处理0/1 |
---
## 五、错误码速查
| HTTP 状态码 | 含义 | 典型场景 |
|-------------|------|----------|
| 200 | 成功 | 正常响应 |
| 400 | 请求参数错误 | 缺少必填字段 |
| 401 | 未认证 | Token 无效或缺失 |
| 404 | 资源不存在 | 士兵编号不存在、需求不存在 |
| 409 | 冲突 | 账号已注册 |
| 500 | 服务器内部错误 | 代码异常 |
---
## 六、联系
- 后端维护:赵昌
- 服务器 IP`121.41.216.243`
- 操作手册:`software/后端操作手册.md`
---
*文档生成时间2026-05-23*

@ -1,142 +0,0 @@
# 单兵终端APP + 声源分析模块 问题清单
> 评估范围:仅评估你们组负责的两个模块
> 评估时间2026-05-23
---
## 一、单兵终端APP 问题清单
### 🔴 严重问题(演示前必须修复)
| # | 问题 | 位置 | 影响 | 修复方式 |
|---|------|------|------|----------|
| 1 | **HTML 语法错误**`</nav>1` 多余字符 | `index.html:471` | DOM 解析异常,底部导航栏下方出现无意义文本 | 删除 `</nav>` 后的 `1` |
| 2 | **调用不存在函数** — `toggleTheme`、`showChangePasswordModal`、`selectDropPointFromMap` | `index.html:237,426,436` | 用户点击直接抛出 `TypeError`APP 崩溃 | 方案A`app.js` 中补齐这 3 个函数<br>方案B`index.html` 中移除这 3 处 `onclick` 绑定 |
| 3 | **数量验证逻辑漏洞** — `"abc" < 1``false` | `js/app.js:287` | 输入非数字(如"abc")绕过验证,`parseInt` 生成 `NaN` 发送到后端导致 500 | ```javascript
const num = parseInt(qty, 10);
if (isNaN(num) || num < 1) { showToast(''); return; }
``` |
| 4 | **高德地图 API Key 硬编码泄露** | `js/location.js:22,66` | Key 被盗用产生额外费用 | 从代码中删除硬编码 Key改为从配置文件读取 |
| 5 | **DOM 操作空值保护缺失**`loadDroneStatus` | `js/app.js:547-557` | 元素不存在时抛出 `TypeError` | 统一添加 `if (el) el.textContent = ...` |
| 6 | **敏感凭证明文存储** — `auth_token``localStorage` | `js/app.js:196`, `js/api.js:8` | XSS 攻击可直接窃取 Token | 改为 `sessionStorage`,或实现 Token 自动刷新 |
| 7 | **路由栈无限增长** — `pageStack` 只有 push | `js/app.js:75-123` | 内存泄漏,长时间使用后卡顿 | 登录成功用 `replace` 而非 `push`;限制栈深度 |
| 8 | **后台轮询未暂停** | `js/app.js:715`, `js/location.js:509` | 耗电、流量浪费 | 监听 `visibilitychange`,隐藏时 `clearInterval` |
| 9 | **请求超时过短** — 统一 5 秒 | `js/api.js:17` | 弱网环境下频繁超时 | 关键接口改为 10 秒,非关键保持 5 秒 |
### 🟡 一般问题(建议修复)
| # | 问题 | 位置 | 影响 |
|---|------|------|------|
| 10 | **重复嵌套目录** — `js/js/`、`www/js/js/` | 全项目 | 构建脚本路径配置错误APK 体积增大 |
| 11 | **登录后未清空密码** | `js/app.js:181-213` | 密码在内存中持续存在 |
| 12 | **Mock 数据字段缺失**`getMockDropPoints``lat`/`lng` | `js/api.js:177-183` | 地图定位到 `(0, 0)` |
| 13 | **`setInterval` async 未防并发** | `js/location.js:514` | 网络阻塞时请求堆积 |
| 14 | **位置上报失败静默吞掉** | `js/location.js:520-528` | 用户不知位置是否同步成功 |
| 15 | **网络/电量为静态假数据** | `index.html:99-100` | UI 永远显示 `--` |
| 16 | **偏移距离使用随机数** | `js/app.js:636-637` | `Math.random() * 100` 误导用户 |
### 🟢 建议(优化项)
| # | 建议 | 说明 |
|---|------|------|
| 17 | 删除 `rememberMe` 复选框 | 代码完全未实现,纯摆设 |
| 18 | 统一错误码体系 | 区分网络超时/500/认证失效/业务错误 |
| 19 | 地图实例缓存 | 避免每次进入页面销毁重建地图 |
---
## 二、声源分析模块acoustic问题清单
### 🔴 严重问题(演示前必须修复)
| # | 问题 | 位置 | 影响 | 修复方式 |
|---|------|------|------|----------|
| 1 | **`AudioBuffer` 除零崩溃** — `capacity_frames_ = 0``% 0` | `src/core/audio_buffer.cpp:24` | 构造传入 0 时程序崩溃 | 构造函数加 `assert(capacity_samples > 0)` |
| 2 | **`AudioBuffer` 假线程安全** — 头文件声明线程安全但实现无同步 | `include/acoustic_analyzer/core/audio_buffer.h:14` | 多线程调用时数据竞争、内存损坏 | 将 `head_`/`tail_`/`size_` 改为 `std::atomic` 或加 `std::mutex` |
| 3 | **GCC-PHAT 整数溢出**`int nfft` 左移可能溢出为负数 | `src/core/gcc_phat_localizer.cpp:88-133` | 大输入时死循环或分配负尺寸数组 | 循环索引统一用 `std::size_t`;对 `nfft` 加上限检查 |
| 4 | **YAML 路径不展开**`$(find pkg)` 语法原样传入 | `config/acoustic_params.yaml:49-50`<br>`src/ros/acoustic_node.cpp:77-78` | 模型加载失败,检测流水线静默失效,永远输出 "unknown" | ```cpp
std::string pkg_path = ros::package::getPath("acoustic_analyzer");
params_.model_path = pkg_path + "/models/gunshot_classifier.onnx";
``` |
| 5 | **UDP 接收队列无界**`buffer_` 无限增长 | `src/io/mobile_phone_source.cpp:69-95` | 消费慢时内存耗尽 | 设置队列上限,超限时丢弃最旧数据 |
| 6 | **C++ 多模态融合未订阅声学话题** ❗ | `src/多模态融合/cpp/src/main.cpp` | 声学节点发布的 `/acoustic/threats` 无人消费,声源数据无法参与融合避障 | 在融合节点的 `setup_ros()` 中增加:<br>```cpp
ros::Subscriber acoustic_sub_ = nh.subscribe("/acoustic/threats", 5,
&ThreatFusionNode::acoustic_threats_cb, this);
``` |
### 🟡 一般问题(建议修复)
| # | 问题 | 位置 | 影响 |
|---|------|------|------|
| 7 | **ONNX 节点名硬编码**`"input"`/`"output"` | `src/core/gunshot_classifier.cpp:170-172` | 模型节点名不同时 `session->Run` 失败 |
| 8 | **Mel 滤波器组路径硬编码** — 相对路径 `"models/..."` | `src/core/feature_extractor.cpp:33` | 工作目录不同时找不到文件 |
| 9 | **魔法数字 `63`** — 目标帧数与模型强耦合 | `src/core/feature_extractor.cpp:124` | 更换模型需重新编译 |
| 10 | **分类器与定位器输入来源不一致** | `src/core/pipeline.cpp:130-149` | 分类器看 0.5s,定位器看 2s精度下降 |
| 11 | **`Predict` 对空矩阵无校验** | `src/core/gunshot_classifier.cpp:155` | 传入空矩阵时行为未定义 |
| 12 | **demo 参数解析无异常保护** | `tests/demo_offline.cpp:200` | 输入非数字时崩溃 |
| 13 | **WAV 头解析依赖未打包结构体** | `src/io/wav_file_source.cpp:48-50` | 编译器插入填充时解析失败 |
| 14 | **缺少启动自检** — 模型加载失败不退出 | `src/ros/acoustic_node.cpp:63-85` | 节点启动后永远输出假数据 |
| 15 | **`CMakeLists.txt` 缺少 onnxruntime 目录** | `CMakeLists.txt:75` | 首次克隆构建失败 |
| 16 | **`package.xml` 未声明 yaml-cpp 依赖** | `package.xml` | `rosdep` 安装时遗漏 |
### 🟢 建议(优化项)
| # | 建议 | 说明 |
|---|------|------|
| 17 | 统一 ROS 话题命名空间 | 声学节点应支持 UAV 命名空间前缀,如 `/uav1/acoustic/threats` |
| 18 | 增加诊断心跳 | 发布 `DiagnosticArray`,报告模型加载状态、推理耗时 |
| 19 | 将 VAD 门限等参数移入配置 | `-60.0f dB`、`zcr_rate` 等不应写死在代码中 |
| 20 | 声速 `343.0f` 作为配置参数 | 不同温度/海拔下声速变化 |
---
## 三、两个模块之间的关联问题
### 问题 1后端与声源模块零集成 ❌
- **现状**:后端 `app.py` 没有任何与声源分析模块的接口
- **影响**单兵APP无法接收声源威胁告警
- **建议**:如需 APP 接收声源告警,后端应订阅 ROS `/acoustic/threats` 话题,通过 WebSocket 推送或轮询接口发送到 APP
### 问题 2后端无人机状态是演示数据 ⚠️
- **现状**`_drone_status` 和 `_drone_logs` 是内存变量,非真实无人机数据
- **影响**单兵APP看到的无人机状态是静态假数据
- **说明**:这是整体架构设计(电脑端前端直连 rosbridge不属你们模块的责任范围但需在演示时明确说明
---
## 四、优先修复清单(演示前)
### 单兵终端Top 5
1. 删除 `index.html:471``1`
2. 补齐/移除不存在的函数调用
3. 修复数量验证逻辑
4. 移除硬编码高德 Key
5. 为 DOM 操作加空值保护
### 声源分析Top 5
1. 修复 `AudioBuffer` 除零和线程安全
2. 修复 YAML 路径 `$(find)` 不展开问题
3. **在 C++ 多模态融合节点中订阅 `/acoustic/threats`**
4. 修复 GCC-PHAT 整数溢出
5. 为 UDP 接收队列加容量上限
---
## 五、演示注意事项
| 场景 | 预期表现 | 风险点 |
|------|----------|--------|
| 士兵登录APP | 正常 | Token 过期需重新登录 |
| 上报物资需求 | 正常 | 数量输入非数字可能崩溃 |
| 查看无人机状态 | 显示静态演示数据 | 需提前说明这是演示数据 |
| 声源检测 → 融合避障 | **可能无法工作** | 融合节点未订阅声学话题 |
| SOS求救 | 正常 | 会自动标记危险区域 |
**特别提醒**:声源分析模块虽然在离线 demo 中能正常运行,但在完整系统链路中**无法将声源数据送入多模态融合**,这是你们组需要与其他组协调修复的关键集成点。
---
*报告生成时间2026-05-23*

@ -1,146 +0,0 @@
# 智途投送后端操作手册
> 服务器:阿里云 ECS `121.41.216.243`Ubuntu 24.04
> 部署路径:`/opt/zhitu/`
> 服务名:`zhitu.service`
> 数据库SQLite `/opt/zhitu/zhitu.db`
---
## 1. 连接服务器
```bash
ssh root@121.41.216.243
# 输入密码
```
---
## 2. 服务启停(最常用)
| 操作 | 命令 |
|------|------|
| 查看状态 | `systemctl status zhitu` |
| 启动服务 | `systemctl start zhitu` |
| 停止服务 | `systemctl stop zhitu` |
| 重启服务 | `systemctl restart zhitu` |
| 重载配置 | `systemctl daemon-reload` |
| 开机自启 | `systemctl enable zhitu`(已配置)|
---
## 3. 查看日志(排错必备)
```bash
# 查看最新 50 行日志
journalctl -u zhitu -n 50
# 实时跟踪日志(调试时用,按 Ctrl+C 退出)
journalctl -u zhitu -f
```
---
## 4. 改 Worker 数量为 1避免 SQLite 并发锁)
当前配置为 2 个 workerSQLite 并发写入时可能锁竞争导致请求卡住,建议改为 1 个:
```bash
sed -i 's/-w 2/-w 1/' /etc/systemd/system/zhitu.service
systemctl daemon-reload
systemctl restart zhitu
systemctl status zhitu
```
---
## 5. 备份数据库
数据库文件在 `/opt/zhitu/zhitu.db`,课程结束前务必备份:
```bash
# 方式一:在服务器上备份到 root 目录
cp /opt/zhitu/zhitu.db ~/zhitu_backup_$(date +%Y%m%d_%H%M%S).db
# 方式二:下载到本地电脑(在本地终端执行)
scp root@121.41.216.243:/opt/zhitu/zhitu.db D:\zhitu_backup.db
```
---
## 6. 修改后端代码后热更新
改完 `app.py` 后需要重启生效:
```bash
# 先检查语法
python3 -m py_compile /opt/zhitu/app.py
# 语法通过后重启
systemctl restart zhitu
```
---
## 7. 常用验证命令
```bash
# 测试后端是否存活
curl http://121.41.216.243/api/ping
# 查看所有需求(无需登录)
curl http://121.41.216.243/api/demands
# 查看账号列表(无需登录)
curl http://121.41.216.243/api/auth/accounts
# 直接查看数据库中的 token调试用
sqlite3 /opt/zhitu/zhitu.db "SELECT * FROM tokens;"
# 查看所有需求记录
sqlite3 /opt/zhitu/zhitu.db "SELECT * FROM demands;"
```
---
## 8. 网络/端口检查
```bash
# 查看 5000 端口是否在监听
ss -tlnp | grep 5000
# 查看 Nginx 状态
systemctl status nginx
# 查看防火墙状态
ufw status
```
---
## 9. 常见问题速查
| 现象 | 可能原因 | 解决 |
|------|----------|------|
| APP 上报 500 | `_demand_id_counter` 内存计数器归零导致主键冲突 | 已修复:改为数据库查询生成 ID |
| APP 上报超时/卡住 | Gunicorn 多 worker + SQLite 并发锁竞争 | 改 `-w 1` |
| APP 登录 401 | Token 跨 worker 不共享 / localStorage 缓存旧地址 | 已修复Token 存数据库;检查 APP 服务器地址设置 |
| 电脑端首页 404 | 前端静态文件未上传到 `/opt/zhitu/` | 确保 `index.html`、`css/`、`js/` 都在部署目录 |
| 服务启动失败 | 语法错误 / 端口被占 | `python3 -m py_compile /opt/zhitu/app.py` 检查语法 |
---
## 10. 关键文件路径
| 文件/目录 | 路径 |
|-----------|------|
| 后端主程序 | `/opt/zhitu/app.py` |
| 数据库 | `/opt/zhitu/zhitu.db` |
| 服务配置 | `/etc/systemd/system/zhitu.service` |
| Nginx 配置 | `/etc/nginx/sites-enabled/zhitu` |
| Python 虚拟环境 | `/opt/zhitu/venv/` |
| 日志输出 | `journalctl -u zhitu` |
---
*手册生成时间2026-05-23*

@ -1,64 +0,0 @@
# 多模态融合节点 — 声学威胁订阅补丁
> 问题C++ 多模态融合节点未订阅 `/acoustic/threats`,导致声源数据无法参与融合避障。
> 适用文件:`src/多模态融合/cpp/src/main.cpp`
---
## 修改 1添加成员变量
`ros::Subscriber flash_det_sub_;` 之后添加:
```cpp
ros::Subscriber acoustic_sub_; // /acoustic/threats
```
## 修改 2在 setup_ros() 中订阅声学话题
`flash_enabled` 订阅块之后、`// ── 定时器 ──` 之前添加:
```cpp
// ── 声学威胁订阅者 ──
if (cfg_.acoustic_enabled) {
acoustic_sub_ = nh.subscribe("/acoustic/threats", 5,
&ThreatFusionNode::acoustic_threats_cb, this);
ROS_INFO("[ThreatFusion] 已订阅 /acoustic/threats");
}
```
## 修改 3添加回调函数
`flash_detection_cb` 之后、`fusion_timer_cb` 之前添加:
```cpp
void acoustic_threats_cb(const acoustic_analyzer::AcousticThreatArray::ConstPtr& msg) {
for (const auto& t : msg->threats) {
if (t.confidence < cfg_.acoustic_confidence_threshold) continue;
AcousticThreatData a;
a.threat_id = t.threat_id;
a.sound_type = t.sound_type;
a.confidence = t.confidence;
a.azimuth = t.azimuth;
a.elevation = t.elevation;
a.distance = t.distance;
a.distance_confidence = t.distance_confidence;
a.timestamp = get_time();
cache_.add_acoustic(a);
}
modality_status_["acoustic"] = "OK";
}
```
## 修改 4添加消息头文件 include
在文件顶部的 include 区域添加:
```cpp
#include <acoustic_analyzer/AcousticThreatArray.h>
```
> 注:若构建时提示找不到该头文件,需确保 `acoustic_analyzer` 包已编译(`catkin_make`),并在 CMakeLists.txt 的 `find_package` 中声明依赖。
---
*补丁生成时间2026-05-23*

@ -1,749 +0,0 @@
# "智途投送"软件开发方案
> 本文档基于《最后一公里软件构想》编写,为"智途投送"软件系统的开发实施方案。
---
## 一、项目概述
### 1.1 项目背景
"智途投送"软件系统是针对城市作战环境下"最后一公里"末端补给难题而设计的智能化物资投送系统。系统通过无人机替代有人运输,结合智能路径规划、动态避障和实时目标调整等功能,实现战场物资的安全、精准投送。
### 1.2 系统组成
本系统由三大核心子系统构成:
| 子系统 | 主要功能 | 部署位置 |
|--------|----------|----------|
| 单兵终端系统 | 需求上报、策略选择、位置同步 | 前线士兵随身携带 |
| 无人机软件系统 | 自主飞行、路径规划、动态避障 | 无人机机载 |
| 后勤保障系统 | 任务调度、资源管理、状态监控 | 后方指挥所 |
### 1.3 开发目标
- 实现前线士兵物资需求的实时上报与处理
- 实现无人机自主路径规划与动态避障
- 支持多种路径策略选择(最快、最安全、最省能耗)
- 支持动态目标调整与实时位置同步
- 构建完整的后勤调度与监控平台
---
## 二、技术选型
### 2.1 技术栈总览
| 层次 | 技术选型 | 说明 |
|------|----------|------|
| 后勤保障系统 | Python + Flask/FastAPI | 快速开发,便于算法集成 |
| 数据库 | SQLite / PostgreSQL | 轻量级或企业级可选 |
| 单兵终端APP | Flutter 或 Android (Kotlin) | 跨平台或原生开发 |
| 无人机控制 | Python + MAVSDK / PX4 | 开源飞控生态完善 |
| 静态路径规划 | Python (NetworkX, OMPL) | 图算法库,用于全局路径计算 |
| 视觉导航与图像分析 | Python + OpenCV | 成熟的图像处理工具链,支持枪口火光检测、障碍物识别 |
| 热成像分析 | Python + OpenCV + PyTorch | 红外图像处理,热异常检测,温度阈值判定 |
| 声源分析与定位 | Python + librosa + NumPy | 声学特征提取枪炮声分类麦克风阵列声源定位GCC-PHAT |
| 多模态融合 | Python + NumPy | 多源威胁信息融合,实时威胁地图生成 |
| 仿真环境 | Gazebo / AirSim | 无人机仿真验证,支持传感器模拟 |
| 地图服务 | Leaflet / Mapbox | Web地图可视化 |
### 2.2 技术选型理由
#### 2.2.1 后勤保障系统
**选择 Python + Flask/FastAPI 的理由:**
- Python 拥有丰富的科学计算和算法库NumPy、SciPy、NetworkX
- Flask/FastAPI 轻量级框架,便于快速构建 RESTful API
- 与路径规划算法、视觉算法无缝集成
- 团队熟悉 Python 开发
#### 2.2.2 单兵终端APP
**选择 Flutter 或 Android (Kotlin) 的理由:**
- Flutter一套代码支持 Android/iOS开发效率高
- Android (Kotlin):原生性能好,硬件调用方便
- 均支持地图SDK集成、网络通信、消息推送
#### 2.2.3 无人机软件
**选择 MAVSDK/PX4 的理由:**
- PX4 是成熟的开源飞控系统
- MAVSDK 提供Python API便于二次开发
- 支持航线规划、状态监控、自主飞行
- 社区活跃,文档完善
#### 2.2.4 仿真环境
**选择 Gazebo/AirSim 的理由:**
- Gazebo开源机器人仿真支持PX4集成
- AirSim微软开源提供逼真的城市环境
- 均支持传感器模拟摄像头、IMU、GPS
---
## 三、开发阶段规划
采用分阶段迭代开发策略,总周期约 **12-16周**
### 3.1 阶段一核心算法验证3-4周
#### 目标
验证路径规划算法、多模态感知算法和避障算法的可行性,为后续系统集成奠定基础。
#### 任务分解
| 任务 | 描述 | 产出物 |
|------|------|--------|
| 搭建仿真环境 | 安装配置 AirSim 或 Gazebo创建简化城市模型 | 可运行的仿真环境 |
| 静态路径规划 | 实现 A* 算法,基于城市地图、燃料约束、安全点标注等因素计算初始路径 | 静态路径规划模块 |
| 局部避障算法 | 实现 RRT 或 RRT* 算法,动态避开障碍物 | 避障算法模块 |
| 策略切换机制 | 实现三种策略(最快、最安全、最省能耗)的切换逻辑 | 策略选择模块 |
| 视觉识别原型 | 使用 OpenCV 实现墙体/障碍物边缘检测 | 视觉识别原型 |
| 热成像分析原型 | 实现红外图像热异常检测算法,包括枪管过热检测和枪口火焰热残留检测 | 热成像分析模块 |
| 相机图像分析原型 | 实现枪口火光检测算法(帧间差分+运动目标检测)和战场态势评估 | 相机图像分析模块 |
| 声源分析原型 | 实现枪炮声识别分类模型和麦克风阵列声源定位算法GCC-PHAT | 声源分析模块 |
| 多模态融合原型 | 实现三种感知结果融合逻辑,生成实时威胁地图 | 多模态融合模块 |
#### 技术要点
```python
# 静态路径规划算法示例架构
class StaticPathPlanner:
def __init__(self, city_map, threat_zones):
self.city_map = city_map
self.threat_zones = threat_zones
def plan(self, start, goal, strategy='safest', fuel_constraint=None,
safe_points=None, urgency='normal'):
"""
strategy: 'fastest' / 'safest' / 'energy_efficient'
fuel_constraint: 剩余电量约束
safe_points: 士兵标注的安全区域
urgency: 物资紧急程度
"""
pass
# 动态威胁检测示例架构
class DynamicThreatDetector:
def __init__(self):
self.thermal_analyzer = ThermalAnalyzer() # 热成像分析
self.camera_analyzer = CameraAnalyzer() # 相机图像分析
self.acoustic_analyzer = AcousticAnalyzer() # 声源分析
def detect_and_fuse(self, thermal_frame, camera_frame, audio_data):
"""融合三种感知结果,生成威胁地图"""
thermal_threats = self.thermal_analyzer.detect(thermal_frame)
visual_threats = self.camera_analyzer.detect(camera_frame)
acoustic_threats = self.acoustic_analyzer.detect(audio_data)
return self._fuse(thermal_threats, visual_threats, acoustic_threats)
```
#### 验收标准
- [ ] 仿真环境中无人机能够按规划路径飞行
- [ ] 能够成功避开预设的障碍物
- [ ] 三种策略能够正确切换并产生不同路径
- [ ] 热成像模块能检测到模拟的热异常点
- [ ] 相机模块能识别模拟的枪口火光闪烁
- [ ] 声源模块能区分枪炮声与环境噪声并估计声源方向
---
### 3.2 阶段二后勤保障系统开发3-4周
#### 目标
构建后端服务、数据库和可视化监控界面,实现任务调度和资源管理。
#### 任务分解
| 任务 | 描述 | 产出物 |
|------|------|--------|
| 数据库设计 | 设计物资、无人机、任务、用户等数据表 | 数据库Schema |
| 后端API开发 | 实现需求上报、任务调度、状态查询等接口 | RESTful API |
| 任务调度模块 | 实现无人机分配、任务状态管理逻辑 | 调度服务 |
| 可视化监控界面 | 开发Web界面显示地图、无人机位置、任务状态 | Web前端 |
| 通信服务模块 | 实现与终端APP、无人机的数据收发 | 通信服务 |
#### 数据库设计
##### E-R 图概念模型
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Soldier │ │ Task │ │ Drone │
│ (前线士兵) │ │ (任务) │ │ (无人机) │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ soldier_id │──┐ │ task_id │ ┌──│ drone_id │
│ name │ │ │ type │ │ │ name │
│ position │ │ │ priority │ │ │ status │
│ unit │ │ │ strategy │ │ │ battery │
└─────────────┘ │ │ status │ │ │ position │
│ │ create_time │ │ └─────────────┘
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Delivery │───┘
│ │ (配送记录) │
│ ├─────────────┤
└───▶│ delivery_id │
│ task_id │
│ drone_id │
│ soldier_id │
│ pickup_loc │
│ dropoff_loc │
│ start_time │
│ end_time │
└─────────────┘
┌─────────────┐
│ Material │
│ (物资) │
├─────────────┤
│ material_id │
│ name │
│ quantity │
│ location │
└─────────────┘
```
##### 数据表定义
**士兵表 (soldiers)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| soldier_id | VARCHAR(32) | 主键士兵ID |
| name | VARCHAR(64) | 姓名 |
| unit | VARCHAR(64) | 所属单位 |
| position_lat | FLOAT | 纬度 |
| position_lng | FLOAT | 经度 |
| status | VARCHAR(16) | 状态active/inactive |
| create_time | DATETIME | 创建时间 |
**无人机表 (drones)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| drone_id | VARCHAR(32) | 主键无人机ID |
| name | VARCHAR(64) | 名称 |
| status | VARCHAR(16) | 状态idle/flying/returning/maintenance |
| battery | INT | 电量百分比 |
| position_lat | FLOAT | 当前纬度 |
| position_lng | FLOAT | 当前经度 |
| max_payload | FLOAT | 最大载重(kg) |
**任务表 (tasks)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| task_id | VARCHAR(32) | 主键任务ID |
| soldier_id | VARCHAR(32) | 外键,请求士兵 |
| strategy | VARCHAR(16) | 路径策略fastest/safest/energy_efficient |
| priority | VARCHAR(16) | 优先级low/medium/high/urgent |
| status | VARCHAR(16) | 状态pending/assigned/in_flight/completed/cancelled |
| create_time | DATETIME | 创建时间 |
| assign_time | DATETIME | 分配时间 |
| complete_time | DATETIME | 完成时间 |
**配送记录表 (deliveries)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| delivery_id | VARCHAR(32) | 主键 |
| task_id | VARCHAR(32) | 外键,关联任务 |
| drone_id | VARCHAR(32) | 外键,执行无人机 |
| pickup_lat | FLOAT | 取货点纬度 |
| pickup_lng | FLOAT | 取货点经度 |
| dropoff_lat | FLOAT | 投放点纬度 |
| dropoff_lng | FLOAT | 投放点经点 |
| start_time | DATETIME | 开始时间 |
| end_time | DATETIME | 结束时间 |
**物资表 (materials)**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| material_id | VARCHAR(32) | 主键 |
| name | VARCHAR(64) | 物资名称 |
| category | VARCHAR(32) | 类别ammo/food/medicine/equipment |
| quantity | INT | 库存数量 |
| weight | FLOAT | 单件重量(kg) |
| location | VARCHAR(64) | 存放位置 |
#### API 接口设计
**基础URL**: `http://<server>:<port>/api/v1`
| 接口 | 方法 | 描述 |
|------|------|------|
| `/soldiers/{id}/position` | PUT | 更新士兵位置 |
| `/materials` | GET | 查询物资库存 |
| `/tasks` | POST | 创建物资需求任务 |
| `/tasks/{id}` | GET | 查询任务状态 |
| `/tasks/{id}/strategy` | PUT | 更新路径策略 |
| `/tasks/{id}/dropoff` | PUT | 更新投放点 |
| `/drones` | GET | 查询无人机列表 |
| `/drones/{id}/status` | POST | 无人机状态上报 |
| `/deliveries` | GET | 查询配送记录 |
#### 验收标准
- [ ] 数据库表创建完成,可正常读写
- [ ] 所有API接口可通过Postman测试
- [ ] Web界面可显示地图和无人机位置
- [ ] 任务创建到完成的完整流程可跑通
---
### 3.3 阶段三单兵终端APP开发2-3周
#### 目标
开发前线士兵使用的移动应用,实现需求上报、策略选择、消息接收等功能。
#### 任务分解
| 任务 | 描述 | 产出物 |
|------|------|--------|
| 项目框架搭建 | 创建Flutter/Android项目配置依赖 | 项目骨架 |
| 登录认证模块 | 实现士兵身份认证 | 登录界面 |
| 需求上报界面 | 物资类型、数量、紧急程度选择与提交 | 上报界面 |
| 策略选择界面 | 三种路径策略的选择交互 | 策略界面 |
| 地图显示模块 | 集成地图SDK显示投放点位置 | 地图模块 |
| 消息推送模块 | 接收后勤系统的通知消息 | 推送模块 |
| 位置上报服务 | 后台定时上报士兵位置 | 位置服务 |
#### 界面设计
```
┌─────────────────────────────────────┐
│ 智途投送 - 单兵终端 │
├─────────────────────────────────────┤
│ ┌─────────────────────────────────┐│
│ │ ││
│ │ [ 地图显示区域 ] ││
│ │ 显示当前位置 ││
│ │ 显示投放点位置 ││
│ │ ││
│ └─────────────────────────────────┘│
├─────────────────────────────────────┤
│ 快捷操作 │
│ ┌─────────┐ ┌─────────┐ │
│ │ 申请物资 │ │ 查看任务 │ │
│ └─────────┘ └─────────┘ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 库存查询 │ │ 消息中心 │ │
│ └─────────┘ └─────────┘ │
├─────────────────────────────────────┤
│ 当前任务:等待中 │
│ 任务ID: T-2024001 │
│ 物资: 急救药品 x5 │
│ 状态: 无人机已起飞 │
│ 预计到达: 14:35 │
└─────────────────────────────────────┘
```
#### 验收标准
- [ ] APP可正常安装运行
- [ ] 可成功提交物资需求
- [ ] 可选择路径策略
- [ ] 可接收投放点通知
- [ ] 位置可实时上报
---
### 3.4 阶段四无人机系统集成3-4周
#### 目标
实现无人机自主飞行、路径规划、动态避障等核心能力。
#### 任务分解
| 任务 | 描述 | 产出物 |
|------|------|--------|
| 飞控平台选型 | 选择无人机平台,配置开发环境 | 开发环境 |
| 航点飞行控制 | 实现按航点序列飞行的能力 | 飞控模块 |
| 状态回传模块 | 定时上报位置、电量、任务状态 | 状态模块 |
| 静态路径规划集成 | 集成阶段一开发的静态路径规划算法,综合考虑燃料、安全点、紧急程度等约束 | 静态规划模块 |
| 视觉避障模块 | 集成视觉识别,实现动态避障 | 避障模块 |
| 热成像感知模块 | 集成红外热成像传感器驱动与分析算法,实现枪管过热和火焰热残留检测 | 热成像模块 |
| 相机图像分析模块 | 集成可见光相机驱动与火光检测算法,实现枪口火光识别和战场态势评估 | 图像分析模块 |
| 声源感知模块 | 集成麦克风阵列驱动与声学分析算法,实现枪炮声识别和声源定位 | 声源分析模块 |
| 多模态融合模块 | 融合热成像、相机、声源三种感知结果,生成威胁地图并驱动动态航线调整 | 融合规划模块 |
| 通信模块 | 与后勤系统的数据收发 | 通信模块 |
| 应急处理模块 | 通信中断、电量不足等异常处理 | 应急模块 |
#### 软件架构
```
┌──────────────────────────────────────────────────────────────────┐
│ 无人机软件系统 │
├──────────────────────────────────────────────────────────────────┤
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────┐ │
│ │ 通信模块 │ │ 飞控模块 │ │静态路径规划│ │多模态融合 │ │
│ │ │ │ │ │ │ │与动态规划 │ │
│ │- 指令接收 │ │- 航点飞行 │ │- 地图规划 │ │ │ │
│ │- 状态回传 │ │- 悬停控制 │ │- 燃料约束 │ │- 威胁融合 │ │
│ │- 加密传输 │ │- 返航逻辑 │ │- 安全标注 │ │- 威胁地图 │ │
│ └─────┬─────┘ └─────┬─────┘ │- 策略切换 │ │- 动态绕行 │ │
│ │ │ └─────┬─────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴───────────────┘ │
│ │ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │热成像分析 │ │相机图像分析│ │ 声源分析 │ │ 任务管理器 │ │
│ │ │ │ │ │ │ │ │ │
│ │- 枪管过热 │ │- 枪口火光 │ │- 枪炮识别 │ │- 任务执行 │ │
│ │- 热残留 │ │- 态势感知 │ │- 声源定位 │ │- 策略切换 │ │
│ │- 温度检测 │ │- 危险评估 │ │- 火力评估 │ │- 应急处理 │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
├──────────────────────────────────────────────────────────────────┤
│ 硬件接口层 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 飞控 ││摄像头│ │热成像│ │麦克风│ │ IMU │ │ GPS │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
└──────────────────────────────────────────────────────────────────┘
```
#### 状态机设计
```
┌─────────────┐
│ IDLE │
│ (待机) │
└──────┬──────┘
│ 收到任务
┌─────────────┐
│ PLANNING │
│ (路径规划) │
└──────┬──────┘
│ 规划完成
┌─────────────┐
┌─────│ FLYING │─────┐
│ │ (飞行中) │ │
│ └─────────────┘ │
│ │ │
避障触发│ │到达目标 │电量不足
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ AVOIDING │ │ HOVERING │ │ RETURNING │
│ (避障中) │ │ (悬停) │ │ (返航) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└─────────────┴─────────────┘
┌─────────────┐
│ COMPLETED │
│ (完成) │
└─────────────┘
```
#### 验收标准
- [ ] 无人机可接收后勤系统下发的任务
- [ ] 可按规划路径自主飞行
- [ ] 可识别障碍物并自主避让
- [ ] 状态可实时回传后勤系统
- [ ] 通信中断时可自动返航
---
### 3.5 阶段五系统集成测试2周
#### 目标
完成各子系统的联调测试,验证端到端功能。
#### 任务分解
| 任务 | 描述 | 产出物 |
|------|------|--------|
| 接口联调测试 | 验证各子系统间的通信接口 | 测试报告 |
| 端到端流程测试 | 完整任务流程测试 | 测试报告 |
| 异常场景测试 | 通信中断、电量不足等异常测试 | 测试报告 |
| 性能测试 | 并发任务、响应时间测试 | 测试报告 |
| 用户验收测试 | 模拟真实使用场景测试 | 验收报告 |
#### 测试用例
| 编号 | 测试场景 | 预期结果 |
|------|----------|----------|
| TC001 | 士兵提交物资需求 | 后勤系统收到请求,分配无人机 |
| TC002 | 选择"最快"策略 | 路径优先考虑时间最短 |
| TC003 | 选择"最安全"策略 | 路径避开威胁区域 |
| TC004 | 动态调整投放点 | 无人机更新航线 |
| TC005 | 通信中断 | 无人机自动返航 |
| TC006 | 电量不足 | 无人机中止任务返航 |
| TC007 | 多任务并发调度 | 系统正确分配资源 |
| TC008 | 视觉避障测试 | 无人机成功绕过障碍物 |
| TC009 | 热成像枪管过热检测 | 检测到模拟热源,标记为威胁区域 |
| TC010 | 相机枪口火光检测 | 识别到模拟火光闪烁,区分环境光源 |
| TC011 | 声源枪炮声识别 | 正确区分枪声、炮声与环境噪声 |
| TC012 | 声源定位准确性 | 声源方向估计误差在可接受范围内 |
| TC013 | 多模态融合威胁检测 | 三种感知结果融合后生成一致的威胁地图 |
| TC014 | 动态威胁规避 | 检测到交火威胁后无人机自动绕行 |
#### 验收标准
- [ ] 所有测试用例通过
- [ ] 端到端流程完整可运行
- [ ] 异常场景处理正确
- [ ] 系统响应时间满足要求
---
## 四、项目结构
### 4.1 目录结构
```
ZhiTuTouSong/
├── docs/ # 项目文档
│ ├── 需求规格说明书.md
│ ├── 系统设计文档.md
│ ├── 接口设计文档.md
│ ├── 测试报告.md
│ └── 用户手册.md
├── backend/ # 后勤保障系统
│ ├── README.md
│ ├── requirements.txt # Python依赖
│ ├── config/ # 配置文件
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ └── logging.conf
│ ├── api/ # API接口
│ │ ├── __init__.py
│ │ ├── routes/
│ │ │ ├── __init__.py
│ │ │ ├── soldiers.py
│ │ │ ├── tasks.py
│ │ │ ├── drones.py
│ │ │ └── materials.py
│ │ └── schemas/
│ │ ├── __init__.py
│ │ ├── task.py
│ │ └── drone.py
│ ├── models/ # 数据模型
│ │ ├── __init__.py
│ │ ├── soldier.py
│ │ ├── task.py
│ │ ├── drone.py
│ │ └── material.py
│ ├── services/ # 业务逻辑
│ │ ├── __init__.py
│ │ ├── task_service.py
│ │ ├── drone_service.py
│ │ └── notification_service.py
│ ├── algorithms/ # 算法模块
│ │ ├── __init__.py
│ │ ├── path_planner.py
│ │ ├── obstacle_detector.py
│ │ └── strategy_selector.py
│ ├── database/ # 数据库
│ │ ├── __init__.py
│ │ ├── connection.py
│ │ └── migrations/
│ └── main.py # 入口文件
├── mobile-app/ # 单兵终端APP
│ ├── README.md
│ ├── lib/
│ │ ├── main.dart
│ │ ├── models/
│ │ ├── views/
│ │ │ ├── home_view.dart
│ │ │ ├── task_view.dart
│ │ │ └── map_view.dart
│ │ ├── viewmodels/
│ │ ├── services/
│ │ │ ├── api_service.dart
│ │ │ ├── location_service.dart
│ │ │ └── notification_service.dart
│ │ └── utils/
│ ├── assets/
│ ├── pubspec.yaml
│ └── android/
├── drone-software/ # 无人机软件
│ ├── README.md
│ ├── requirements.txt
│ ├── src/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── flight_control/
│ │ │ ├── __init__.py
│ │ │ ├── drone_controller.py
│ │ │ └── waypoint_manager.py
│ │ ├── vision/
│ │ │ ├── __init__.py
│ │ │ ├── camera.py
│ │ │ ├── obstacle_detector.py
│ │ │ ├── muzzle_flash_detector.py
│ │ │ └── visual_odometry.py
│ │ ├── thermal/
│ │ │ ├── __init__.py
│ │ │ ├── thermal_sensor.py # 红外热成像传感器驱动
│ │ │ ├── heat_anomaly_detector.py # 热异常检测(枪管过热/火焰热残留)
│ │ │ └── temperature_processor.py # 温度数据处理与阈值判定
│ │ ├── acoustic/
│ │ │ ├── __init__.py
│ │ │ ├── microphone_array.py # 麦克风阵列驱动
│ │ │ ├── gunshot_classifier.py # 枪炮声分类识别
│ │ │ ├── sound_localizer.py # 声源定位GCC-PHAT
│ │ │ └── noise_filter.py # 风噪滤波预处理
│ │ ├── communication/
│ │ │ ├── __init__.py
│ │ │ ├── radio_link.py
│ │ │ └── message_protocol.py
│ │ ├── planning/
│ │ │ ├── __init__.py
│ │ │ ├── global_planner.py # 静态路径规划A*等)
│ │ │ ├── local_planner.py # 局部避障规划
│ │ │ └── dynamic_planner.py # 多模态融合动态路径规划
│ │ └── mission/
│ │ ├── __init__.py
│ │ ├── mission_manager.py
│ │ └── emergency_handler.py
│ └── config/
│ └── drone_config.yaml
├── simulation/ # 仿真环境
│ ├── README.md
│ ├── city_model/ # 城市模型
│ │ ├── buildings.json
│ │ └── terrain.json
│ ├── scenarios/ # 测试场景
│ │ ├── scenario_1.json
│ │ └── scenario_2.json
│ └── scripts/
│ ├── setup_simulation.py
│ └── run_test.py
├── tests/ # 集成测试
│ ├── integration/
│ ├── e2e/
│ └── performance/
├── scripts/ # 工具脚本
│ ├── setup.sh
│ ├── run_backend.sh
│ └── deploy.sh
├── .gitignore
├── LICENSE
└── README.md
```
### 4.2 模块职责说明
| 模块 | 职责 | 所属子系统 |
|------|------|------------|
| api/ | RESTful API接口定义 | 后勤保障系统 |
| models/ | 数据模型定义 | 后勤保障系统 |
| services/ | 业务逻辑处理 | 后勤保障系统 |
| algorithms/ | 路径规划与避障算法 | 后勤保障系统 |
| flight_control/ | 飞行控制逻辑 | 无人机软件 |
| vision/ | 视觉识别、枪口火光检测与导航 | 无人机软件 |
| thermal/ | 红外热成像分析、枪管过热检测 | 无人机软件 |
| acoustic/ | 声源分析、枪炮声识别与声源定位 | 无人机软件 |
| planning/ | 静态路径规划与多模态融合动态规划 | 无人机软件 |
| communication/ | 通信协议与数据传输 | 无人机软件 |
| views/ | 移动端界面 | 单兵终端APP |
| services/ | 移动端服务API调用、位置上报 | 单兵终端APP |
---
## 五、开发规范
### 5.1 代码规范
- **Python**: 遵循 PEP 8 规范,使用 Black 格式化
- **Dart/Flutter**: 遵循 Dart 官方风格指南
- **文档**: 使用中文注释,函数需添加文档字符串
### 5.2 Git 分支管理
```
main # 主分支,稳定版本
├── develop # 开发分支
│ ├── feature/backend-task-service # 功能分支
│ ├── feature/mobile-home-view # 功能分支
│ └── feature/drone-flight-control # 功能分支
└── release/v1.0 # 发布分支
```
### 5.3 提交信息规范
```
<type>(<scope>): <subject>
# 示例
feat(backend): 添加任务调度API接口
fix(drone): 修复通信中断后无法返航的问题
docs(readme): 更新项目说明文档
test(backend): 添加路径规划算法单元测试
```
**Type 类型**:
- `feat`: 新功能
- `fix`: 修复bug
- `docs`: 文档更新
- `test`: 测试相关
- `refactor`: 代码重构
- `style`: 代码格式调整
---
## 六、风险管理
### 6.1 技术风险
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|----------|
| 路径规划算法性能不足 | 中 | 高 | 采用分层规划,全局离线+局部实时 |
| 视觉避障可靠性低 | 高 | 高 | 多传感器融合,设置置信度阈值 |
| 通信链路不稳定 | 高 | 中 | 设计通信中断应急机制 |
| GPS信号丢失 | 中 | 中 | 视觉导航辅助定位 |
### 6.2 项目风险
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|----------|
| 开发周期延期 | 中 | 高 | 分阶段交付,优先核心功能 |
| 无人机硬件获取困难 | 中 | 高 | 使用仿真环境替代实物测试 |
| 团队成员变动 | 低 | 中 | 代码规范,文档完善 |
---
## 七、里程碑计划
| 里程碑 | 时间节点 | 交付物 |
|--------|----------|--------|
| M1: 算法验证完成 | 第3周 | 路径规划仿真演示 |
| M2: 后勤系统可用 | 第7周 | 可运行的后端服务+Web界面 |
| M3: 终端APP可用 | 第10周 | 可安装的移动应用 |
| M4: 无人机集成完成 | 第14周 | 无人机自主飞行演示 |
| M5: 系统验收 | 第16周 | 完整系统演示+文档 |
---
## 八、附录
### 8.1 参考资料
- PX4 开发指南: https://docs.px4.io/
- MAVSDK Python: https://mavsdk.mavlink.io/
- AirSim 文档: https://microsoft.github.io/AirSim/
- OpenCV 官方文档: https://docs.opencv.org/
- Flutter 开发文档: https://flutter.dev/docs
### 8.2 开发工具
- IDE: VS Code / PyCharm / Android Studio
- 版本控制: Git
- 项目管理: GitHub Projects / 禅道
- API测试: Postman
- 数据库工具: DBeaver
---
*文档版本: v1.0*
*创建日期: 2026年4月*
Loading…
Cancel
Save