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

517 lines
34 KiB

This file contains ambiguous Unicode characters!

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

<!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>