多模态融合实战:从零搭建海上遇险识别Demo 这次我们不聊一个现成的开源仓库而是把标题当作一个海上安全 AI 场景带大家从零搭一个可本地运行的“语义级遇险识别 Demo”。场景本身很有戏剧性圣诞夜里一艘船发出求救信号弹附近另一艘船看到后回复“这是我们的圣诞节”。为什么会发生这种误解因为监控系统只看到火光没有把图像里的信号弹轨迹、通信文本里的关键词和当时的节日语境组合起来。用单一视觉分类器很难分辨烟花和求救信号弹真正需要的是多模态信息视觉帧、烟火轨迹、通信文本、时间地点。下面我们就用本地可部署的目标检测模型加文本意图分类再加一个规则引擎把这条链路跑通。这类需求在港口监控、船舶安全、海事实训中很常见。它不是单纯的目标检测问题而是“视觉事件 语义理解 场景决策”的组合问题。本文会把项目拆成三个模块烟火目标检测模块、文本意图分类模块、融合决策模块并给出一个可以通过 REST API 调用的本地服务。第一层用目标检测模型圈出画面中的光亮目标第二层用文本分类模型判断通信内容是遇险还是节日问候第三层把两者结果融合输出“求救信号弹”或“庆祝烟花”等事件类型。最后还提供批量目录处理和 CSV 导出的示例方便后续接到自己的监控流程里。1. 核心能力速览能力项说明项目类型多模态海上安全识别 Demo视觉检测 文本意图识别 规则融合输入内容图片或视频帧 文本通信内容输出结果事件类型、置信度、告警级别、处理建议启动方式命令行启动本地 Web 服务浏览器访问页面支持 HTTP API硬件要求优先使用带 CUDA 的 N 卡视觉模型建议独立显卡具体显存以目标检测模型大小为准CPU 也可运行但推理速度会慢不少API 能力支持 REST API可接收图片和文本返回 JSON批量任务支持目录批量识别结果输出 CSV / JSON适合场景港口视频监控测试、船舶安全演示、AI 多模态课堂实验、海上应急系统原型这个 Demo 的核心并不是做一个比 YOLO 更先进的检测器而是把“视觉目标”和“文本语义”放在同一个事件里做判断。从实际工程角度看纯视觉模型很容易把求救信号弹和烟花都识别成“发光物体”因为两者的像素特征有重叠。但如果把通信文本也拿进来判断难度就下降很多比如文本里出现“Mayday”“SOS”“flooding”“sinking”再加上画面中存在上升的红色火光基本可以判定为求救信号如果文本是“Merry Christmas”“Happy New Year”画面中又是高空开花的光点就应归类为庆祝烟花。所以这里的核心能力不是单一模型而是一条数据链路。项目重点关注三个问题第一如何把图片和文本统一到一个请求里第二如何用规则融合视觉和文本结果第三如何让这些能力通过 API 被外部系统调用。下面的章节会按部署、测试、接口、批量任务、排查的顺序展开读者可以照着搭一个可用的原型系统再替换成自己的模型和规则。2. 场景拆解为什么视觉分类不够先回到开头这个荒诞场景一艘船发射信号弹另一艘船回复“这是我们的圣诞节”。这个问题出在哪如果我们只看单张夜拍照片画面中的红色亮点确实像烟花如果只看通信文本“This is our Christmas”也确实没有明显攻击性。把两者放一起才发现“信号弹 圣诞祝福”构成了严重的信息错位。从 AI 系统角度拆解这个场景至少包含三层信息第一层是视觉目标。信号弹通常有较明显的上升轨迹、红色或白色强光、持续时间短烟花则在高空炸开形态是球状扩散光点。但夜间远距离拍摄时这些差别可能不明显目标检测模型很容易把两者都归为“light flare”或“firework”。第二层是文本意图。遇险通信会有明显的求救词、船舶代号、位置信息和紧急程度描述例如“Mayday, vessel sinking, need immediate assistance”而节日通信则多是祝福、问候、庆祝用语。第三层是场景规则。圣诞夜、港口附近、商业船队集聚区等上下文信息会影响判断。例如圣诞夜出现高空烟花很常见但在非节假日的公海出现红色上升信号弹则更可能是求救。规则引擎的价值就在这里它可以把时间和地点信息转成先验权重。因此这个项目选择“目标检测 文本分类 规则融合”的三段式架构而不是只训练一个“信号弹 vs 烟花”图片分类器。这样做的好处是每个模块都可以独立替换、独立测试视觉模型识别的是“光目标”文本模型识别的是“通信意图”最后由决策模块根据双方置信度和场景规则输出最终事件类型。这种设计思路也适合迁移到其他安全场景比如火灾报警中区分“做饭油烟”和“真实火情”或者智能家居中区分“打碎玻璃声”和“鞭炮声”。本质上都是把单一信号源的问题转成多维度交叉验证的问题。3. 系统架构与模块设计这个 Demo 在工程上分为五个模块数据接入模块、视频抽帧模块、视觉检测模块、文本意图模块、融合决策模块。数据接入模块负责读取本地图片、视频文件或调用摄像头视频抽帧模块从视频流中按帧率截取画面视觉检测模块使用目标检测模型定位画面中的光亮目标文本意图模块对通信文本做分类融合决策模块把两个输出和外部规则合并生成最终的 JSON 结果。这里给出一种典型实现思路# 伪代码融合决策逻辑 def decide(visual_result, text_result, scene_context): firework_score 0.0 distress_score 0.0 # 视觉置信度 if visual_result[class] flare: distress_score visual_result[confidence] * 0.6 elif visual_result[class] firework: firework_score visual_result[confidence] * 0.6 # 文本意图置信度 text_label text_result[label] if text_label distress: distress_score text_result[confidence] * 0.4 elif text_label celebration: firework_score text_result[confidence] * 0.4 # 场景先验例如圣诞节港口附近 if scene_context.get(holiday) christmas: firework_score 0.1 if scene_context.get(open_sea) and scene_context.get(night): distress_score 0.1 if distress_score firework_score: return {event: distress, level: high} return {event: firework, level: low}这个伪代码展示了融合决策的基本思想。实际项目中视觉模型和文本模型的置信度权重需要根据测试集调优不能固定为 0.6 和 0.4。如果系统更多依赖通信文本可以把文本权重调高如果视频证据更可靠则增加视觉权重。规则引擎部分可以做成配置文件方便在不同港口、节假日、天气条件下切换。因为这是一个示例工程我建议后端使用 FastAPI 提供接口前端用简单的 HTML 页面做演示。FastAPI 对图片和 JSON 的输入支持比较友好自带接口文档也方便后续做批量任务和外部系统对接。视频抽帧可以使用 OpenCV帧率建议按 1 帧/秒处理避免重复计算和资源浪费。4. 环境准备与本地部署4.1 环境准备清单先把机器环境确认好。操作系统建议使用 Linux 或 Windows两者都能运行Linux 服务器更适合长期跑批次任务Windows 适合本地调试。Python 版本建议 3.9 以上并创建独立虚拟环境。视觉检测部分如果使用 PyTorch 或 ONNX Runtime最好准备好 CUDA 工具包和对应驱动如果只是 CPU 推理则不需要额外安装 CUDA但速度会慢不少。部署前建议确认以下几项检查项说明操作系统Windows 10/11、Ubuntu 20.04 或更高版本Python 版本3.9 及以上GPU 驱动如果使用 GPU 推理需要安装 NVIDIA 驱动和 CUDA模型文件准备好目标检测模型权重和文本分类模型权重磁盘空间模型、图片素材、依赖包合计预留至少 20GB 比较稳妥端口状态默认服务端口建议 8000启动前检查是否被占用4.2 安装依赖建议使用 requirements.txt 管理依赖下面是通用模板具体包版本需要按实际模型和环境调整fastapi uvicorn opencv-python torch torchvision transformers pillow pydantic pandas requests安装命令pip install -r requirements.txt如果显存不足可以尝试 CPU 推理模式但需要把模型初始化参数中的 device 设置为 cpu。模型下载通常需要网络国内环境可以考虑使用镜像站或提前下载后放到本地目录不要在运行时反复触发下载。4.3 准备模型和测试素材模型文件的路径建议用配置文件统一管理例如 config.json{ detector_model: ./models/flare_detector.onnx, text_model: ./models/intent_classifier, input_dir: ./data/input, output_dir: ./data/output, device: cuda, api_port: 8000 }将目标检测模型和文本分类模型放到 models 目录把测试图片放到 data/input 目录。测试素材建议准备两组一组是夜间天空中的红色/白色亮点标记为烟花或信号弹一组是包含“Mayday”“SOS”“Merry Christmas”等文本内容的通信记录。这些素材如果来自真实海上场景必须确认已经获得相关单位授权避免使用未脱敏的船只坐标、船员姓名和通信内容。4.4 启动服务下面以 FastAPI 为例给出一个最小可运行的服务骨架# main.py import json from fastapi import FastAPI, File, UploadFile, Form from PIL import Image import numpy as np import io app FastAPI() def analyze_image(image_bytes: bytes) - dict: # 这里替换为真实检测模型调用 return {class: flare, confidence: 0.87} def analyze_text(text: str) - dict: # 这里替换为真实文本分类模型调用 if mayday in text.lower() or sos in text.lower(): return {label: distress, confidence: 0.95} if christmas in text.lower() or happy in text.lower(): return {label: celebration, confidence: 0.92} return {label: unknown, confidence: 0.5} app.post(/api/analyze) async def analyze( file: UploadFile File(...), text: str Form(...), scene: str Form(default) ): image_bytes await file.read() visual analyze_image(image_bytes) text_result analyze_text(text) # 融合判断 event unknown level low if visual[class] flare and text_result[label] distress: event, level distress_flare, high elif visual[class] firework and text_result[label] celebration: event, level celebration_firework, low return { event: event, level: level, visual: visual, text: text_result } if __name__ __main__: import uvicorn uvicorn.run(app, host127.0.0.1, port8000)启动服务python main.py启动后访问http://127.0.0.1:8000/docs可以看到 FastAPI 自带的接口文档。如果端口被占用可以通过--port 8001修改端口或者直接修改代码中的端口参数。5. 功能测试与效果验证服务启动后先用最小样例验证整条链路。建议准备 4 组测试数据信号弹图片 遇险文本烟花图片 节日文本信号弹图片 节日文本烟花图片 遇险文本。最后一组是典型的“错位样本”最能体现多模态融合的价值。5.1 用 curl 测试单条识别curl -X POST http://127.0.0.1:8000/api/analyze \ -F file./data/input/flare_sample.jpg \ -F textMayday, we are sinking \ -F scenenight_open_sea预期返回 JSON{ event: distress_flare, level: high, visual: {class: flare, confidence: 0.87}, text: {label: distress, confidence: 0.95} }如果返回的 event 是“unknown”说明两个模型至少有一个没有正确识别。常见原因是测试图片清晰度太低、模型权重没有正确加载或者文本分类模型没有针对海上通信语料做微调。5.2 用 Python 脚本批量测试批量测试可以按目录遍历图片并把结果统一输出。以下脚本适合在实验阶段快速试跑# batch_test.py import requests import os import pandas as pd api_url http://127.0.0.1:8000/api/analyze input_dir ./data/input results [] for filename in os.listdir(input_dir): if not filename.lower().endswith((.jpg, .jpeg, .png)): continue file_path os.path.join(input_dir, filename) with open(file_path, rb) as f: response requests.post( api_url, files{file: (filename, f, image/jpeg)}, data{text: Mayday, vessel flooding, scene: test} ) result response.json() results.append({ filename: filename, event: result.get(event), level: result.get(level), visual_class: result.get(visual, {}).get(class), text_label: result.get(text, {}).get(label) }) df pd.DataFrame(results) df.to_csv(./data/output/batch_result.csv, indexFalse) print(df)这个脚本会遍历 input 目录下所有图片用同一段遇险文本做测试。测试完成后打开 CSV重点看每一行的 event 和 level 是否合理。如果大部分图片都输出“unknown”优先检查模型路径和输入图片格式。5.3 判断测试是否成功判断标准不要只看接口是否返回 200。至少要满足三个条件第一视觉模型对“光亮目标”的检出框能够稳定出现在烟火附近第二文本分类对明显求救词和节日祝福词能给出超过 0.8 的置信度第三融合接口能把“信号弹 遇险文本”组合判断为 high 级别告警。可以准备一张“错位样本”把信号弹图片和“Merry Christmas”文本放一起再请求一次接口。理想结果是系统不输出 high 告警而输出 low 级别并提示“视觉疑似信号弹但文本为节日祝福建议人工复核”。这说明融合逻辑没有盲目相信单一信号源。6. 接口 API 与批量任务6.1 API 请求设计对外接口尽量保持简单。这个 Demo 的/api/analyze接口接收三个字段参数类型必填说明file文件是上传的图片或视频帧截图text字符串是通信文本内容scene字符串否场景标识例如 night、holiday、port、open_sea响应结构包含 event、level、visual、text 四部分。外部监控系统可以只读取 event 和 level 字段然后触发自己的告警逻辑。如果要接入实时视频流建议增加一个抽帧接口或者把视频发送到服务端后按帧率处理。示例工程里可以增加/api/analyze_video接口内部使用 OpenCV 读取视频按间隔抽帧再把每一帧和同一段通信文本送入融合逻辑。视频处理会比单张图片费时间批量任务必须考虑超时和失败重试机制。6.2 批量任务目录设计批量任务适合在离线巡检场景使用比如整理某港口一段时间的监控截图。可以设计一个输入目录和一个输出目录data/ ├── input/ │ ├── 2025-01-01_001.jpg │ ├── 2025-01-01_002.jpg │ └── 2025-01-01_003.jpg └── output/ └── batch_result.csv脚本会扫描 input 目录下所有图片依次调用 API 或本地模型把结果写入 CSV。如果中间某张图片识别失败不要直接结束整个批次而是记录 error 状态并继续处理后续文件。批量任务可以配合 Python 的 ThreadPoolExecutor 来加速但要注意 GPU 显存是否足够。6.3 批量并发示例# batch_parallel.py import os import requests import pandas as pd from concurrent.futures import ThreadPoolExecutor, as_completed api_url http://127.0.0.1:8000/api/analyze input_dir ./data/input def process_file(filename): file_path os.path.join(input_dir, filename) with open(file_path, rb) as f: try: response requests.post( api_url, files{file: (filename, f, image/jpeg)}, data{text: Mayday, engine failure, scene: port}, timeout30 ) data response.json() return { filename: filename, event: data.get(event), level: data.get(level), error: } except Exception as exc: return { filename: filename, event: error, level: unknown, error: str(exc) } files [f for f in os.listdir(input_dir) if f.endswith(.jpg)] results [] with ThreadPoolExecutor(max_workers4) as executor: future_map {executor.submit(process_file, f): f for f in files} for future in as_completed(future_map): results.append(future.result()) df pd.DataFrame(results) df.to_csv(./data/output/batch_parallel.csv, indexFalse) print(df)并发数不建议一开始就设置很高。从 4 个并发开始观察 GPU 显存和 CPU 占用再逐步提高。如果接口超时优先检查模型推理是否被吃满而不是盲目增加线程。7. 资源占用与性能观察这个 Demo 的资源占用主要由视觉模型决定。文本分类模型通常很小CPU 也能跑得动目标检测模型如果使用 YOLO 或类似结构GPU 推理速度会明显优于 CPU。建议在测试过程中打开显存监控nvidia-smi -l 1如果使用 Windows也可以打开任务管理器查看 GPU 显存占用。需要重点观察三个指标推理时的峰值显存、单张图片处理耗时、批量任务在并发状态下的稳定性。显存占用会随模型输入分辨率、批大小和模型参数量变化不同模型之间的差异可能很大所以不能只凭经验估算。为了降低显存占用可以尝试以下方法将图像输入分辨率从 1280 降到 640检测精度会有所下降但显存占用明显减少。控制批量任务并发数避免多个推理请求同时进入 GPU。使用 FP16 或 ONNX Runtime 的 GPU 加速模式部分模型可以显著减少显存占用。文本分类模型尽量使用 CPU 推理把 GPU 留给视觉模型。推理耗时方面CPU 模式适合小批量验证GPU 模式适合批量任务和实时监控。实际效果需要以本机测试为准不同显卡、不同驱动版本、不同模型架构都会影响最终结果。建议每次调整参数后记录一份推理耗时和显存峰值方便后续做对比。8. 常见问题与排查方法问题现象可能原因排查方式解决方案FastAPI 服务启动失败端口被占用或依赖缺失查看启动日志检查端口监听状态更换端口重新安装依赖接口返回 500模型文件路径错误或图片格式不支持查看服务端日志确认模型文件能否被加载修改配置路径使用标准 JPG/PNG 格式图片检测不到烟火目标目标检测模型训练数据不足或图像分辨率过低在图片上画出检测框查看原始输出提高输入分辨率替换训练数据更充分的模型文本分类结果错误文本语言和模型训练语料不一致或文本拼写不规范单测文本分类模型打印置信度扩充海上通信语料增加 Mayday、SOS、flooding 等关键词显存不足输入分辨率太高或同时涌入多个请求使用 nvidia-smi 查看显存占用降低分辨率限制并发数开启 FP16批量任务卡住单个 API 调用超时异常没有捕获在批量脚本中加入 timeout 参数使用 timeout捕获单文件异常继续处理后续文件信号弹和烟花频繁误报融合规则权重不合理检查测试集里两类样本的置信度分布调整视觉与文本权重加入时间和节日先验规则模型下载失败网络不稳定或下载地址受限查看下载日志手动下载模型文件提前下载模型文件放到本地 models 目录排查时不要一次性改多个参数。先固定文本输入只调视觉检测再固定图片只调文本分类最后再调融合权重。通过这种单变量测试能快速定位是哪一层出现了问题。9. 最佳实践与合规提醒这类识别系统本质上是安全辅助工具不能替代真实的遇险通信和海事救援流程。在港口监控或船舶安全场景中AI 识别结果只能作为提示最终处置仍需要由值班人员确认。系统告警后至少要保留一张截图、一段文本记录和一条时间戳日志方便人工复查。训练和测试素材必须注意授权。真实船舶通信记录、船员声音、船舶位置信息都属于敏感数据未脱敏数据不能直接用于模型训练或公开发布。如果需要做课堂演示可以使用合成的告警文本和开源数据集避免引入真实个人和船只信息。工程化落地时建议保留一套最小可运行配置。把模型文件、输入素材、输出结果分目录管理配置文件单独存放不要硬编码路径。每个模型文件记录版本号或 hash 值方便回退。批量任务要加日志和失败重试机制遇到单张图片失败时不中断整个批次而是写入错误列表后继续。阈值调整也要结合具体场景。如果是处理求救信号宁可多一些误报也要尽可能减少漏报如果是处理烟花庆祝则要控制误报避免频繁打扰值班人员。建议在测试集上跑出不同阈值和召回率的数据再决定最终的告警阈值。这个环节千万别跳直接固定一个阈值很容易在真实场景里翻车。10. 总结与下一步这个项目最值得尝试的点是用“视觉 文本 规则”三层交叉验证解决单一视觉模型容易把求救信号弹和烟花搞混的问题。整个链路跑起来并不复杂难点在于测试集和权重调优。建议实际动手时先用 2 张图片和 4 段文本把 API 跑通再把视频抽帧和批量目录接进来。最容易踩的坑有两个一个是把视觉模型输出直接当成最终结果忽略了文本语义另一个是在没有测试集的情况下直接调整融合权重导致误报和漏报都无法量化。后续如果想工程化可以往三个方向扩展接入 AIS 船舶自动识别数据把船只位置、航速、离岸距离作为规则引擎输入接入 VHF 语音识别把语音通信转成文本后送入同样的意图分类模块再加上告警推送服务通过企业微信、钉钉或短信把 high 级别事件发给值班人员。把这些模块逐个往上加这个 Demo 就能从课堂实验变成一个可以真正在港口安全测试中使用的原型系统。