“视频上看得见的洪水淹没预演”:视频数字孪生完整实践

#实现"视频上看得见的洪水淹没预演":视频数字孪生完整实践

摘要:水利部数字孪生流域建设要求具备"预报、预警、预演、预案"四预能力,其中"预演"最直观也最难做------如何让淹没分析的结果"长"在真实视频画面上?本文完整记录笔者用 Vue 3 + TypeScript + Vite + Canvas 从零实现一套"实景指挥系统"Demo 的全过程:底图清洗、岸线几何标定、动态水面渲染、水文过程驱动、AR 标注联动、实时视频流接入,以及一路踩过的坑。全文配效果图与核心代码,工程源码结构在文末给出。

一、背景:为什么要在视频上做淹没分析

传统洪水风险分析的成果是静态专题图:淹没范围、水深分级、到达时间......都对,但非专业人员看不懂,指挥人员用不上。数字孪生流域要解决的就是这个问题------把模型算出来的淹没过程,叠加到真实场景上正向演绎:水位涨到 7.3 m 警戒线时哪些滩地先淹、涨到 8 m 保证水位时哪些房屋被围、洪水多久漫过码头......一眼看清。笔者参考了某实景指挥系统的效果(高点视频为底、AR 水情标注、预演时河水上涨漫岸),决定用前端技术栈复刻并工程化一套可交互的 Demo,目标有三:

  1. 真实感 :真实视频画面做底、真实系统时间、特写机位是实时播放的视频流;2. 科学性 :淹没范围由水位过程驱动(演示用余弦演进 + 水位-流量定线,生产可无缝替换为水动力模型输出);3. 工程化:Vue 3 + TS 组件体系、数据服务适配层(mock/api 可切换)、可导出预演报告。

二、总体架构

系统按数字孪生流域的通行分层组织,Demo 重点实现"孪生场景层":

端到端的技术链路如下,本文第三节按此顺序展开:

三、关键实现

3.1 底图:从带 HUD 的视频帧到干净实景

第一版底图取自真实项目视频抽帧,但画面上叠满了原系统的 AR 面板(水位牌、流速框、警戒线......),直接拿来当底图会出现"双层 HUD"。清洗思路是颜色分割 + 连通域过滤 + 图像修复pythonimport cv2, numpy as npimg = cv2.imread("frame.jpg")hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)H, S, V = hsv[:,:,0].astype(int), hsv[:,:,1].astype(int), hsv[:,:,2].astype(int)mask = np.zeros(img.shape[:2], np.uint8)# 强青色 HUD 图形 / 品红特征线 / 亮黄警戒线 / 红色目标框mask |= ((H >= 80) & (H <= 102) & (S > 120) & (V > 150)).astype(np.uint8) * 255mask |= ((H >= 142) & (H <= 168) & (S > 100) & (V > 140)).astype(np.uint8) * 255mask |= ((H >= 21) & (H <= 32) & (S > 150) & (V > 170)).astype(np.uint8) * 255mask |= (((H <= 7) | (H >= 173)) & (S > 130) & (V > 140)).astype(np.uint8) * 255# 连通域过滤:面积过大的连通块是场景本体(水面/天空),不能修num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 8)clean = np.zeros_like(mask)for i in range(1, num): area = stats[i, cv2.CC_STAT_AREA] if 6 < area < 60000: clean[labels == i] = 255clean = cv2.dilate(clean, np.ones((3, 3), np.uint8), iterations=2)result = cv2.inpaint(img, clean, 5, cv2.INPAINT_TELEA)两个经验值很重要:阈值要收紧 (第一版掩膜覆盖了 47% 像素,把水面天空全抹了);半透明面板填充色饱和度低、颜色法抓不到 ,需要第二遍按残留面板的矩形区域定向 inpaint。后来为了更好的观感,底图换成了免授权航拍素材(Mixkit《Flying low over a large river in nature》,可商用),并做了日间化与白平衡校正去除黄昏粉调:pythonhsv[..., 1] = np.clip(hsv[..., 1] * 1.18, 0, 255) # 增饱和hsv[..., 2] = np.clip(hsv[..., 2] * 1.16 + 6, 0, 255) # 提亮# 白平衡偏冷:压红提蓝b, g, r = cv2.split(img.astype(np.float32))b = np.clip(b * 1.12 + 4, 0, 255); r = np.clip(r * 0.93, 0, 255)得到的干净底图:开阔河面、两岸树带、坡地村舍,非常接近水库/河道监控机位的构图。### 3.2 动态水面:用 Canvas 把"水位"画出来淹没可视化的核心是一个朴素的思想:水面顶缘 = 岸线基准高程 − 水位抬升量 。岸线用一组控制点描述(可由水域颜色检测自动提取后人工微调),逐列插值得到基准水线;水位上涨时水线按演进系数上移,再叠加正弦波扰动模拟波浪:ts// geometry.ts:岸线控制点与满水位抬升量(像素,1920x1080 画幅)export const SHORE: Pt[] = [ [0, 635], [180, 625], [360, 615], [520, 575], [620, 500], [700, 472], [1000, 462], [1300, 470], [1600, 487], [1920, 522]]export const RISE: Pt[] = [ [0, 150], [180, 155], [360, 160], [520, 150], [620, 100], [700, 85], [1000, 75], [1300, 85], [1600, 105], [1920, 130]]// WaterCanvas.vue:逐列绘制水面顶缘for (let x = x1; x <= x2; x += step) { const base = interp(SHORE, x) const rise = interp(RISE, x) * ease(f) // f = 洪水演进系数 [0,1] const y = waveY(x, base - rise, 1.6 + 3.2 * f, t) first ? ctx.moveTo(x, y) : ctx.lineTo(x, y)}材质上做了清水→浑水的平滑过渡 :平时是低透明度的蓝绿色薄层(保留照片自身的水面质感与倒影),预演涨水后渐变为高透明度的浑黄洪水色,并加镜面高光条带与泡沫边缘线。水面上的漂浮目标(皮划艇/船只)从底图裁成 sprite,每帧重绘在水面之上并随波轻微起伏,避免"船被水淹没"的穿帮。### 3.3 预演状态机与水文过程驱动全局状态用一个响应式仓库管理,模式机三态:LIVE(实时监测)/ PREVIEW(动态预演)/ STATIC(静态淹没分析)。主步进函数 tick(dt) 由 App 的单一 rAF 循环调用:tsexport function tick(dt: number) { store.animT += dt if (store.mode === 'PREVIEW') { if (store.playing) { store.simT += dt * store.speed * 900 // 4X ≈ 1 模拟小时/秒 if (store.simT >= store.simDur) { store.playing = false; store.planAuto = true } } store.Z = zAt(store.simT / store.simDur) // 水位过程插值 if (!store.warned && store.Z >= 7.30) { // 阈值穿越 → 预警联动 store.warned = true showAlert('⚠ 预警:水位超过警戒水位 7.30m,请启动应急响应') } } else if (store.mode === 'LIVE') { store.Z = store.Z0 + Math.sin(store.animT * 0.3) * 0.008 }}演示用的水位过程是余弦演进 Z(t) = Z0 + ΔZ·(1−cos(πt/T))/2,流量用水位-流量幂律定线 Q = 762 + 1564·((Z−Z0)/4.69)^1.7(参数对齐真实测站量级)。生产环境只需实现 WaterDataSource 接口 (文中给出 mock/api 双实现),把过程线换成水动力模型的时序输出,上层渲染零改动------这就是适配层的价值。### 3.4 实时视频流与实时时钟:逼真感的最后两块拼图特写机位 最初是一张静态图做慢速平移,观感很假。改为真实视频流:把同一条河的航拍素材用 ffmpeg 做"正放+倒放"拼接成无缝循环(避免循环跳帧),仅 707KB:bashffmpeg -i river.mp4 -vf "scale=960:-2,split[a][b];[b]reverse[r];[a][r]concat=n=2:v=1:a=0" \ -c:v libx264 -crf 30 -an -movflags +faststart pip_live.mp4``````html<video src="/pip_live.mp4" autoplay muted loop playsinline :poster="poster"></video>配合左下角呼吸闪烁的 LIVE 徽标、扫描线纹理,直播感立刻到位。时钟 全部改用真实系统时间:主时间戳每秒走动、特写时间戳比主时钟慢 1s 模拟编码延迟、预演时间轴以"当前时刻"为起点推演未来 72h。这里踩了一个 Vue 响应式的经典坑,见 5.4。## 四、效果展示实时监测模式 :清水实景 + 深蓝玻璃拟态面板,AR 标注(水位台、浮标 ADCP、警戒/历史最高水位线、AI 漂浮目标识别框)钉在真实地物上:预演进行中 (T+60h,水位 7.69m):水体转浑黄、漫过两岸树带,超警戒自动弹出预警横幅,右侧淹没影响统计(面积/房屋/人口/道路)实时联动:预演终点 (T+72h,水位 8.00m,流量 2326m³/s):时间轴回放、水位-流量双过程线同步推进,结束后自动弹出应急预案联动面板,可一键导出含现场快照与过程线的 HTML 预演报告:多情景对比 :预置"20 年一遇 / 50 年一遇 / 调度方案 A(控泄)"等情景,勾选即在过程线上叠加对比曲线,点击切换推演情景:## 五、踩坑记录(价值不低于正文)1. headless 浏览器截图必须用绝对路径msedge --headless --screenshot=rel.png 会报 0x5 拒绝访问,给绝对路径即正常。这是笔者整套"截图→目检→迭代"UI 校验流的基础。2. headless 虚拟时间不推进 CSS transition 。加载遮罩用 opacity 淡出时,--virtual-time-budget 耗尽会截到半透明残影;校验用遮罩应直接 display:none 移除,并加 setTimeout 兜底防弱网卡死。3. 异步函数的 await 边界会覆盖同步赋值startPreview() 是 async,URL 参数跳时若在调用后同步写 store.simT,会被 await 之后的 simT = 0 覆盖;必须 await startPreview() 后再赋值。4. computed 缓存冻住实时时钟computed(() => fmtClock(new Date(...performance.now())))performance.now() 不是响应式依赖,computed 求值一次后永久缓存,时间戳不走动。解法是显式依赖每帧推进的状态:void store.animT 强制每帧失效重算。验证方式:间隔 10s 截两张图对比时间戳。5. 免授权视频素材的获取路径 :Pixabay 页面 WebFetch 返回 403、preview slug 直链不通;正确姿势是 WebFetch 详情页拿到结构化数据里的 CDN 地址(assets.mixkit.co/videos/{id}/{id}-1080.mp4),curl 可直接下载。6. OpenCV inpaint 的掩膜宁紧勿松 :阈值过宽会把场景本体(水面、天空)一起抹成色块;半透明 UI 面板饱和度低、颜色法抓不到,要按矩形区域二次定向修复。## 六、工程结构与运行demo-vue/├── src/│ ├── store.ts # 响应式仿真状态仓库(模式机/tick/预警联动/情景管理)│ ├── geometry.ts # 岸线控制点、抬升量、插值与波浪函数│ ├── services/│ │ ├── waterDataService.ts # 数据源适配层:WaterDataSource 接口 + mock/api 实现│ │ └── reportExporter.ts # 预演报告导出(快照合成 + 过程线 + 指标表)│ ├── components/│ │ ├── WaterCanvas.vue # Canvas 动态水面渲染│ │ ├── ArLayer.vue # AR 水情标注层│ │ ├── GaugePanel.vue # 水位尺(特征水位刻度)│ │ ├── ChartPanel.vue # 水位-流量双轴过程线 + 多情景叠加│ │ ├── TimelineBar.vue # 预演时间轴(倍速/Pointer 拖拽)│ │ ├── PipPanel.vue # 特写实时视频流│ │ ├── ScenarioPanel.vue # 情景对比面板│ │ └── AppModals.vue # 四预弹窗│ └── App.vue # 布局组装 + 单一 rAF 主循环 + 键盘快捷键└── public/pip_live.mp4 # 无缝循环特写视频流``````bashnpm installnpm run dev # http://localhost:5173# 演示直达:?auto=1 自动预演;?auto=1&t=48 跳到 T+48h;?show=s2,s3 叠加对比情景快捷键:空格 暂停/继续、1/2/4/8 倍速、P 预演、F 全屏、Esc 退出。## 七、总结与展望这套 Demo 证明了一件事:"视频上看得见的淹没预演"并不依赖重型 GIS/游戏引擎,前端技术栈(Canvas + 响应式状态机 + 视频元素)配合严谨的几何标定与模型接口,就能做出既有真实感又有科学性的四预演示载体。下一步计划:接入真实水情 API 替换 mock 数据源、主画面升级为低透明度视频底、淹没范围改用 DEM 栅格计算替代岸线插值,向生产级数字孪生场景靠拢。完整源码与更多细节欢迎评论区交流。如果这篇文章对你有帮助,点赞收藏就是笔者持续输出实践文的最大动力。## 参考- 《数字孪生流域建设技术导则》SL/T 853-2025- 水利部《数字孪生流域建设技术大纲(试行)》- 视频素材:Mixkit - Flying low over a large river in nature(免费可商用)- OpenCV 文档:cv2.inpaint / connectedComponentsWithStats- Vue 3 响应性原理:computed 缓存与依赖追踪

相关推荐
angushine1 小时前
文本转视频
音视频
angushine2 小时前
文本转视频2
人工智能·音视频·语音识别
刘广睿3 小时前
给素材库加语音转写:Whisper 本地部署与批量字幕生成实践
人工智能·aigc·音视频·语音识别·效率工具
benbenAItalk12 小时前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
AI创界者15 小时前
前沿多模态大模型 MiniMax-H3无审查 本地部署与深度实测指南
人工智能·aigc·音视频
世岩清上20 小时前
展厅数字内容同质化严重,怎样打造专属叙事风格?
大数据·前端·javascript·人工智能·html·音视频·展厅改造
LCG元21 小时前
STM32F407 I2S驱动WM8978音频播放实战:PLLI2S时钟整定、DMA双缓冲防爆音与WAV采样率实测
stm32·嵌入式硬件·音视频
jbk331121 小时前
PixiuCut「视频批量分割」支持智能镜头分割、按时长、按数量分割,可定义最小时长避免无效片段等多可选参数可设置
人工智能·音视频·剪辑软件·剪映自动化软件
投票竞赛1 天前
作品投票版权注意事项,线上评选上传图片视频须知
python·音视频