{"code":1,"msg":"成功","time":"1793358145","data":{"title":"〖2026嵌入式大赛〗基于RT-Thread OpenMV协议的嫌疑人跟踪系统","content":"# 1. 项目简介\r\n一套跑在 RA8P1 Titan Mini（OpenMV 固件）视觉模组 + PC 上位机上的**行人检测与嫌疑人点选锁定系统**：摄像头端跑神经网络实时检测行人，操作员在视频画面中点一下即可锁定目标嫌疑人，系统随即使红框持续跟随、记录轨迹并同步录像。\r\n## 1.1. 速览\r\n\r\n| 项目    | 内容                                                                       |\r\n| ----- | ------------------------------------------------------------------------ |\r\n| 定位    | 现场单板 + 单 PC 的嫌疑人识别与追踪控制台                                                 |\r\n| 上位机   | Python 3.11 + PySide6 6.7 + OpenCV + pyserial，打包为单文件 EXE                 |\r\n| 板端    | RA8P1 Titan Mini，MicroPython v1.28.0-openmv，Ethos-U55 NPU                |\r\n| 板端模型  | `yolov8n_coco80_192_u55_256.tflite` + 固件自带 YoloV8 后处理器（person = class 0） |\r\n| 链路    | USB CDC（枚举为 COM 口），**OpenMV CDC 二进制协议**（与 IDE 同一套），921600 仅为驱动兼容参数       |\r\n| 采集分辨率 | QVGA 320×240（默认）\/ VGA 640×480                                            |\r\n| 实测帧率  | QVGA 无检测 ~25 fps，带检测 16~19 fps；VGA 带检测 ~8 fps                            |\r\n| 锁定方式  | 推流时**点击画面中的行人**（就近吸附），无冻结帧、无框选、无下行依赖                                     |\r\n| 输出    | `<程序目录>\\log\\*_YYYY-MM-DD.log`、`<程序目录>\\recordings\\*.mp4`                  |\r\n## 1.2 适用场景\r\n安防值守、重点人员布控、无人值守卡口等需要\"人工确认 + 机器跟踪\"（当前效果）结合的场合：机器负责不漏检、不间断，人只负责在最关键的一步——点选确认——做判断。\r\n\r\n# 2. 系统组成\r\n\r\n![screenshot_image.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/3e32074c4b771fe748120bfe65497b24.png.webp)\r\n- **设备端**（MicroPython，单文件自包含脚本）：CSI 摄像头 320×240 采集，YOLOv8n 行人模型在片上 NPU 推理（实测约 40 ms\/帧，13 fps），检测结果与视频流同时输出；LCD 屏上叠加检测框、人数、录像、存储、事件等状态栏。\r\n- **上位机**（Python + PySide6，单文件 EXE 发布）：通过 OpenMV 固件自带 CDC 协议连接设备（与 OpenMV IDE 同一套协议自研实现），接收视频流并叠加检测框；操作员**点击行人即完成锁定**——无暂停、无弹窗，全程视频不断流。\r\n## 2.1 核心工作流\r\n1. **连接**：上位机一键连接，自动上传设备端页面脚本（约 1.5 s）。\r\n2. **点选锁定**：推流状态下点击视频中的任意行人 → 锁定 → 红框跟随，带运动预测的抗抖动跟踪，目标短暂遮挡后可重新捕获。\r\n3. **记录**：一键录像（AVI\/MJPEG），锁定嫌疑人的红框直接烧入录像帧；全程中文操作日志。\r\n4. **取数**：一键 U 盘模式导出设备 SD 卡中的录像与日志到电脑（自动断开页面 → 切 MSC → 拷贝 → 设备自动复位恢复）。\r\n## 2.2 亮点\r\n- **双模式一体**：同一份设备脚本既可独立跑（断电重启自动恢复值守，SD 卡自启动），也可与上位机协同（在线点选、录像、导出），拔线即回离线值守。\r\n- **全程视频不断流**：点选、锁定、清除标记均不暂停画面；带宽与延迟经逐帧台架实测调优。\r\n- **可靠性与自修复**：SD 卡录像段开机自动修复索引；上位机单实例守卫、看门狗自动恢复视频流、中英双语界面。\r\n- **深度嵌入式优化**：固件堆内存紧张环境下完成增量式存储记账、字模位图缓存、逐帧成本剖析，消除了 SD 全目录扫描导致的画面冻结（单帧峰值从 4.4 s 降至 0.1 s 量级）。\r\n# 3. 工程目录简介\r\n所有源码都在 `src\/` 下，仓库根目录只留配置、脚本和文档：\r\n```\r\ntitan_mini_suspect_tracker\/\r\n├── src\/\r\n│   ├── suspect_tracker\/      上位机应用包（PYTHONPATH=src 后 python -m suspect_tracker 启动）\r\n│   ├── device\/               设备端 MicroPython 脚本，连接时上传，不在本机执行\r\n├── docs\/                     工程介绍、协议说明、排查记录、diagrams\/*.png\r\n├── build_exe.ps1             PyInstaller 打包\r\n├── run_ui.bat                源码方式启动\r\n├── requirements.txt          运行期依赖（锁定版本）\r\n├── requirements-build.txt    构建期依赖\r\n└── pyproject.toml            包元数据 + ruff 配置\r\n```\r\n\r\n| 路径                                        | 职责                                                                  |\r\n| ----------------------------------------- | ------------------------------------------------------------------- |\r\n| `src\/suspect_tracker\/__main__.py`         | 启动器：单实例检查（早于 PySide6 导入）→ 启动日志 → 调 `app.main()`                     |\r\n| `src\/suspect_tracker\/app.py`              | 创建 QApplication \/ MainWindow，注册命名管道回调                               |\r\n| `src\/suspect_tracker\/openmv_protocol.py`  | **协议实现核心**：CRC、组包、握手、通道命令、读帧事务                                      |\r\n| `src\/suspect_tracker\/hardware_video.py`   | 打开 COM、上传脚本、解码像素、Qt 信号桥、看门狗                                         |\r\n| `src\/suspect_tracker\/protocol.py`         | 控制面 JSON Lines 的 `ProtocolFrame` 数据类                                |\r\n| `src\/suspect_tracker\/suspect_lock.py`     | Host 侧嫌疑人身份保持器（运动预测 + 外观门控 + 歧义拒绝）                                  |\r\n| `src\/suspect_tracker\/video.py`            | `Detection`、`VideoRecorder`（mp4）、`annotate_frame`、`MockVideoSource` |\r\n| `src\/suspect_tracker\/paths.py`            | 输出路径策略（程序目录 \/ 按天分文件 \/ 不可写回退）                                        |\r\n| `src\/suspect_tracker\/single_instance.py`  | Win32 互斥体 + `QLocalServer` 命名管道                                     |\r\n| `src\/suspect_tracker\/ui\/main_window.py`   | 全部交互编排：连接、推流、点选、录像、日志、语言\/主题                                         |\r\n| `src\/suspect_tracker\/ui\/video_display.py` | 自绘叠加层：绿框全量检测 + 红框嫌疑人 + 就近点击命中                                       |\r\n| `src\/suspect_tracker\/ui\/i18n.py`          | 中英文案 + 驱动英文诊断的本地化重渲染                                                |\r\n| `src\/suspect_tracker\/ui\/themes.py`        | 三套 QSS 配色                                                           |\r\n| `src\/device\/openmv_ae3_raw_jpeg.py`       | **设备端脚本**，IDE 与上位机共用同一份文本                                           |\r\n# 4. 协议实现\r\n\r\n## 4.1 OpenMV 固件协议\r\nOpenMV 固件自带的 CDC 二进制协议。它的设计目标是 让任何实现方（官方 IDE、官方 Python SDK、第三方工具）都能在同一根 USB 线上与固件对话：\r\n- 固件侧把能力暴露成**通道（channel）**：`stdin` 收脚本、`stream` 出帧、`stdout` 出文本；\r\n- 主机侧用一组**二进制命令**（`SYNC` \/ `GET_CAPS` \/ `SET_CAPS` \/ `CHANNEL_*`）与通道交互；\r\n- 所有命令和数据的**帧格式完全一致**（见 §3.2），区别只在 opcode 和通道号。\r\nOpenMV IDE 连接板子时执行的就是这套流程；本工程实现的是其中视频链路必需的子集 （`SYNC`、`GET_CAPS`、`SET_CAPS`、通道 6 条命令 + 读帧）。\r\n## 4.2 CDC 二进制帧格式\r\n![screenshot_image.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/3e32074c4b771fe748120bfe65497b24.png.webp)\r\n所有多字节整数 **little-endian**。固定 10 字节头；payload 非空时尾部再附 4 字节 CRC32。 **空 payload 不附 CRC32**（这一点很容易写错，读侧也按 `length ? +4 : +0` 计算包长）。\r\n```python\r\nHEADER_WITHOUT_CRC = struct.Struct(\"<HBBBBH\")   # 打头时先不带 CRC\r\nHEADER            = struct.Struct(\"<HBBBBHH\")   # 收包时一次解出 CRC\r\nHEADER_SIZE       = 10\r\n```\r\nCRC 参数（**不是** zlib 的 CRC32，不能用 `zlib.crc32` 顶替）：\r\n\r\n| | 多项式 | 初值 | 覆盖范围 | 实现 |\r\n| --- | --- | --- | --- | --- |\r\n| CRC16 | `0xF94F` | `0xFFFF` | 头前 8 字节 | 256 项 MSB 查表 |\r\n| CRC32 | `0xFA567D89` | `0xFFFFFFFF` | payload | 256 项 MSB 查表 |\r\n\r\n**为什么头用 CRC16、体用 CRC32**：头要逐包频繁校验（每帧 4 次命令往返），16 位足够挡住 同步字误命中；payload 可能是 17 KB 的 JPEG，需要 32 位来保证\"这不是半张图\"。实测 17 KB 帧的 CRC32 从逐位实现的 ~26 ms 降到查表后的 ~3.9 ms —— 这是 16 fps → 27 fps 的主因。\r\n## 4.3 标志位与响应语义\r\n\r\n| Bit | 名称 | 处理规则 |\r\n| --- | --- | --- |\r\n| 0 | `ACK` | 成功确认 |\r\n| 1 | `NAK` | 失败；payload 前 2 字节为状态码 |\r\n| 2 | `RTX` | 重传包；序号不是期望值时**回一个 ACK 然后丢弃** |\r\n| 3 | `ACK_REQ` | 必须回一个空 ACK（同 opcode\/channel\/sequence） |\r\n| 4 | `FRAGMENT` | 还有后续分片，继续累积 payload 并延长超时 |\r\n| 5 | `EVENT` | 异步事件，**不消费命令序号**，直接跳过 |\r\n`_receive_response()` 的判定顺序是有讲究的：\r\n先处理 `RTX`+错序号 → 再处理 `ACK_REQ` 回 ACK → 再跳过 `EVENT` → 才做\r\n`opcode\/channel` 匹配 → 最后校验序号。顺序颠倒会让设备的异步事件打断命令事务。\r\n\r\nNAK 状态码有两个特殊分支（其余一律抛错并写日志）：\r\n\r\n| 状态 | 名称 | 上位机的处理 |\r\n| --- | --- | --- |\r\n| `0x04` | BUSY | 返回 `False`（`CHANNEL_LOCK` 拿不到锁时走这条，属正常\"暂时没帧\"） |\r\n| `0x06` | SEQUENCE | 抛 `OpenMVSequenceError` → 触发重新同步并重放命令 |\r\n| 其他 | FAILED \/ INVALID \/ TIMEOUT \/ CHECKSUM \/ OVERFLOW … | 抛 `OpenMVProtocolError`，把原始码打进 `hardware.log` |\r\n\r\n## 4.4 连接握手：四步 + 载荷上限协商\r\n![screenshot_image.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/6ef7782eda72a5d9b5a45fe9287cfb6b.png.webp)\r\n关键约束：\r\n1. **初始 payload 只能是 52 字节**。同步阶段设备还没协商能力，用大包会超限。\r\n2. 同步失败重试 **3 次**，每次前都 `reset_input_buffer()` + 清空解析缓冲，避免上一次的残留字节把这次握手带偏（这是早期\"能连上但不稳定\"的根因之一）。\r\n3. `max_payload = min(4096, 设备上报值)`，且下限 64。设备硬上限是 **4082**，\r\n   所以 17 KB 的帧必然被切成 5 片，`SET_CAPS` 调大也没用（已实测，勿再试）。\r\n4. 握手成功后校验 `stdin` 与 `stream` **必须存在**，缺一个就直接报错——这两个是视频链路的最低要求，其他通道（`stdout`）都是可选增强。\r\n\r\n## 4.5 通道命令与 payload 布局\r\n| 命令 | opcode | 请求 payload | 响应 |\r\n| --- | ---: | --- | --- |\r\n| `CHANNEL_LIST` | `0x20` | — | 每 16 字节 `<BB14s>` |\r\n| `CHANNEL_LOCK` | `0x22` | — | ACK \/ NAK `0x04`(BUSY) |\r\n| `CHANNEL_UNLOCK` | `0x23` | — | ACK |\r\n| `CHANNEL_SIZE` | `0x25` | — | `<I>` 可读字节数 |\r\n| `CHANNEL_READ` | `0x26` | `<II>` offset, length | 数据（可能多分片） |\r\n| `CHANNEL_WRITE` | `0x27` | `<II>` offset, length + bytes | ACK |\r\n| `CHANNEL_IOCTL` | `0x28` | `<I>` ioctl + 参数 | ACK |\r\n写脚本时的分块大小是 `max_payload - 8`：因为 `CHANNEL_WRITE` 的 payload 里\r\n前 8 字节被 `offset + length` 占掉了。\r\n`stdin` ioctl：`STDIN_STOP=1`（中断现有脚本）、`STDIN_EXEC=2`（执行缓冲区脚本）、\r\n`STDIN_RESET=3`（清空缓冲区）。\r\n`stream` ioctl：`STREAM_CTRL=0`（`<I>` 0\/1 开关推流）、`STREAM_RAW_CTRL=1`（`<I>` 0 = 非 raw）。\r\n## 4.6 序号同步\r\n**问题**：设备端脚本一启动或一中断，**设备侧协议计数器会清零，而上位机还在往上数**。\r\n于是下一条命令的响应序号是 0，而期望值是 N → 报 `sequence mismatch: expected N, got 0`。\r\n**三级处理**（`_command` 里的 `resync_budget` + `allow_relax`）：\r\n```text\r\n收到序号不符\r\n   ├─ 第 1 级：PROTO_SYNC 重新对时（两侧同时归零）→ 重放该命令\r\n   ├─ 第 2 级：再对一次仍不符 → _strict_sequence = False（宽松模式）→ 重放\r\n   └─ 第 3 级：宽松模式下只告警不拒绝，靠 CRC16\/CRC32 + opcode\/channel 匹配兜底\r\n```\r\n宽松模式一旦打开就**本次会话不再回头**，并且每次发包前先 flush 输入缓冲，保证\"下一条匹配到同 opcode\/channel 的包只可能属于这条命令\"。这条设计让\"脚本重启导致失步\"从致命故障降级成了日志里一行提示——早期版本正是因为它直接断开连接，才出现\"连接成功但没图像\"。\r\n另外 `NAK 0x06` 被明确识别为**可恢复失步**，而不是链路故障。\r\n## 4.7 接收端组包状态机\r\nUSB `read()` 的边界和协议包边界完全无关：一次可能只到半个头，也可能一次到三个包。所以`_receive_packet()` 是一个带自愈的增量解析器：\r\n```text\r\n1. 找 sync(0xD5AA)；找不到 → 只保留最后 1 字节（跨 chunk 的半截同步字）\r\n2. 找到但前面有垃圾 → 丢掉前缀\r\n3. 缓冲不足 10 字节 → 继续读\r\n4. 解头；length > max_payload → 丢 1 字节后重新找 sync（防被垃圾 length 卡死）\r\n5. header CRC16 不符 → 丢 1 字节后重新找 sync\r\n6. 按 10 + length + (length?4:0) 等齐；不足 → 继续读\r\n7. payload CRC32 不符 → 丢 1 字节后重新找 sync（不是丢整包，避免误判后连锁丢帧）\r\n8. 成功 → 从缓冲头部删除整包并返回\r\n```\r\n\"失败就只丢 1 字节\"是刻意的：如果错误判断了一个假同步字，丢整包会把后面真正的包也一起吃掉；丢 1 字节只会多扫几个位置，代价可控。\r\n\r\n## 4.8 视频帧在 stream 通道内的布局\r\n`CHANNEL_SIZE` 拿到的是**整个通道 payload 的长度**，其中前 20 字节是帧头：\r\n\r\n| 字段 | 类型 | 说明 |\r\n| --- | --- | --- |\r\n| `width` | `u32` | 宽 |\r\n| `height` | `u32` | 高 |\r\n| `pixformat` | `u32` | 见下表 |\r\n| `depth` | `u32` | 位深（当前不使用） |\r\n| `data_offset` | `u32` | 图像数据起始偏移 |\r\n**不要假设 `data_offset == 20`**：固件可能加 padding 或其他字段。代码里做的是\r\n`20 <= data_offset <= len` 的合法性校验，然后取 `data[data_offset:]`。\r\n\r\n| 格式 | 常量 | 解码 |\r\n| --- | ---: | --- |\r\n| 灰阶 | `0x08020001` | 1 B\/px → `cv2.COLOR_GRAY2BGR` |\r\n| RGB565 | `0x0C030002` | 2 B\/px，拆 R5\/G6\/B5 扩到 8 位 |\r\n| JPEG | `0x06060000` | `cv2.imdecode` |\r\nRGB565 扩位时必须用**足够宽的中间类型**（`np.uint16` 再乘 255 除 31\/63），直接\r\n`uint8 * 255` 会溢出成花屏。解码后统一转成 `uint8 H×W×3 \/ BGR \/ C-contiguous`，\r\n这是 UI、绘制、录像三处的公共约定。\r\n## 4.9 读帧事务与并发保护\r\n![screenshot_image.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/b61e967e7e7549810fa05a471488a16e.png.webp)\r\n一帧必须作为**一个串行事务**完成：\r\n```python\r\nwith self._io_lock:                 # RLock，保护整段\r\n    if not CHANNEL_LOCK(stream):    # NAK 0x04 = BUSY → 本轮无帧\r\n        return None\r\n    try:\r\n        size = CHANNEL_SIZE(stream)\r\n        if size <= 20:              # 头都不够，视为无帧\r\n            return None\r\n        data = CHANNEL_READ(stream, 0, size)\r\n        width, height, fmt, depth, off = <IIIII>(data)\r\n        return width, height, fmt, depth, data[off:]\r\n    finally:\r\n        CHANNEL_UNLOCK(stream)      # 异常路径也必须解锁\r\n```\r\n`self._io_lock` 是必须的：取帧线程和 UI 线程（点\"停止视频流\"会发 `STREAM_CTRL=0`）会同时碰同一个串口。如果 `LOCK … UNLOCK` 中间插进一条 `set_streaming(False)`，设备侧的锁状态和上位机的假设就会错位，表现为\"停流后再启流就收不到帧\"。\r\n## 4.10 设备 → 上位机：stdout 上的 JSON Lines\r\n![screenshot_image.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/801b2da9cb8fa47b94c379933b4da2bc.png.webp)\r\n这块板子上，**设备唯一能主动说话的地方是 `stdout`**（`print()` 的输出）。所以检测结果走这里：\r\n```json\r\n{\"kind\":\"event\",\"name\":\"tracks_update\",\"payload\":{\r\n  \"tracks\":[{\"track_id\":7,\"bbox\":[112,58,198,236],\"confidence\":0.874,\"label\":\"person\"}],\r\n  \"frame_size\":[320,240],\"fps\":18.3}}\r\n```\r\n\r\n| 字段 | 说明 |\r\n| --- | --- |\r\n| `tracks[].track_id` | 设备内 IoU 跟踪器分配的稳定 ID |\r\n| `tracks[].bbox` | `[x1,y1,x2,y2]`，**已是图像像素坐标**，上位机不换算 |\r\n| `confidence` | YoloV8 后处理器给出的分数（阈值 0.3） |\r\n| `frame_size` | 采集分辨率，便于分辨率切换时校验 |\r\n| `fps` | 设备自报采集帧率，显示在状态栏\"设备采集 Y fps\" |\r\n上位机侧的三个工程决策：\r\n1. **轮询间隔 50 ms**（`STDOUT_POLL_INTERVAL`）。每帧轮询实测要掉 ~9 fps（每次都是串口往返），而 50 ms 和 20 ms 对帧率无差别。\r\n2. **解析失败就跳过**。`stdout` 里还有 REPL 横幅、`>>>` 提示符、MicroPython traceback，这些都不是 JSON。用 `try\/except` 吞掉，继续看下一行。\r\n3. **内压真实风险**。`stdout` 是环形缓冲，上位机不读会**阻塞 `print()` 并拖慢采集循环**。所以一行 JSON 只有 ~180 字节、`REPORT_EVERY = 1`（每拍报一次），靠低体积而不是靠降频来避免。\r\n## 4.11 上位机 → 设备：控制面的设计与现实\r\n**设计意图**：设备脚本用 `protocol` 模块注册一个 `tracker` 通道，上位机把\r\n`ProtocolFrame` 序列化成 JSON Lines 写进去，设备回传事件走同一通道。\r\n**实测现实**：现场固件（RA8P1 Titan Mini）**没有 `protocol` 模块**，`import protocol` 直接\r\n`ImportError`，`omv` 模块也没有任何通道 API。所以 `tracker` 通道**永远不存在**。\r\n\r\n代码对此的处理是\"优雅降级\"而不是\"连接失败\"：\r\n```python\r\ntry:\r\n    client.wait_for_channel(\"tracker\", timeout=1.5)\r\nexcept (TimeoutError, OpenMVProtocolError):\r\n    self.control_available = False       # 只降级，不抛\r\n```\r\n\r\n```python\r\ndef send_command(self, frame):\r\n    if not self.control_available:\r\n        raise ControlChannelUnavailable(\"...没有 tracker 通道...\")\r\n```\r\n\r\n于是所有控制命令都变成\"记录一条日志然后跳过\"：\r\n\r\n| 命令 | 真实效果 |\r\n| --- | --- |\r\n| `start_stream` \/ `stop_stream` | **不靠命令**。`video_source.start()\/stop()` 直接发 `STREAM_CTRL`，所以视频启停是真实生效的 |\r\n| `clear_suspect` \/ `start_recording` \/ `stop_recording` \/ `set_recording_mode` | 送不到设备；录像本来就在上位机本地做，所以功能不受影响 |\r\n| `mark_suspect` | 已随冻结帧工作流一起删除（点选锁定不需要下行） |\r\n\r\n**这不是缺陷，而是架构选择**：设备只负责\"检测所有行人 + 报 ID\"，\"谁是嫌疑人\"完全由上位机锁定某个 `track_id` 决定。这样既绕开了无自定义通道的限制，也满足了\"画面永不中断\"的硬约束（任何需要重启采集循环的下行方案都会断流）。\r\n# 5. 设计局限\r\n- 对嫌疑人的不正常动作\/行为缺少数据源，没针对性地训练模型，无法自动抓到嫌疑行为并自动进行跟踪，弥补方案是人为圈选嫌疑人，这导致设计不够智能。\r\n- 摄像清晰度不够，效果难以实用。推理和录像窗口目前设定为320x240，大了效果很差，掉帧且推理速度变慢导致体验下降。进一步说明硬件存在很大局限（NPU, 内存，摄像头）\r\n- 设备在线和离线模式切换的时候需要重启，不符合实际需要。\r\n# 6. 实物图片和效果\r\n![screenshot_image.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/5eda5fc8adca6c6aa214fee0538df3fd.png.webp)\r\n![screenshot_屏幕截图 2026-10-24 184306.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/11ba68dca8a131d981312626b6cbfc8d.png.webp)\r\n![screenshot_Pasted image 20261024193124.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/bca818f8088b527519d37a723e54a115.png.webp)\r\n![screenshot_Pasted image 20261024193030.png](https:\/\/oss-club.rt-thread.org\/uploads\/20261024\/c14995c6d1945ea9a6e12c8f09e93abe.png.webp)\r\n# 7. 源码连接和视频\r\n## 7.1 源码：https:\/\/github.com\/Jason0204\/titan_mini_suspect_tracker.git\r\n## 7.2 视频：https:\/\/www.bilibili.com\/video\/BV1HEHM6UE3V\/?spm_id_from=333.1387.homepage.video_card.click&vd_source=e0be6734fdcff12612242c356fc0ddb3","updatetime":"2026-10-25 10:39:50","author":"generalissimo"}}