副标题:从单帧到视频流——一条可演示的蓝天滤镜决策链
摘要:甲方要的不是 P 图,是接流 → 分天空 → 只调天的颜色 → 推回去。本篇讲我怎么拆需求、怎么定路线、关键分叉怎么选;海天线算法见 第二篇,甲方设备烂见 第三篇。复现用脚本见文末附录。
0. 甲方到底在吐槽什么
这种需求确实存在。
监控、直播、固定机位拍天空——画面里的天经常会发黄、发灰。甲方一看就不满意,嘴上说「天不够蓝」,心里往往觉得:你们视频处理是不是有问题?
先交代一句:这篇是我自己做着玩的实验,不是生产规范。真实工作里,很多场景下最好的做法其实是流啥样,给啥样——别乱动画面,别给自己找锅。
但如果甲方(或者你自己)就是想把「灰蒙蒙的天」救一救(纯展示用),用人话说,需求也就这几句:
- 把发黄、发灰的天空单独抠出来;
- 只对这一块提蓝、提饱和;
- 建筑、海面、人别染色;
- 最好直播里看起来也干净、好看——不是离线 P 一张静图糊弄过去。
翻译成工程,链路就是四段:分天空(mask)→ 调色(filter)→ 离线验效果 → 伪流/demo 验链路。
后面所有 Step,围着三件事转:mask 准不准、滤镜真不真、浏览器里能不能播。
1. 路线怎么定:最小实现,先图后流
我的思路是最小实现——能跑通一条 demo 就行,别一上来就堆全套。
当时也没有现成的「检测天空」模型可以直接拿来用(至少不是我手边即插即用的那种)。所以不是第一天就抠图、标数据、训分割模型。第一步很土:从视频里抽一帧,用 HSV 试着把天分出来;单帧上能扣、能染色,再谈 mp4,再谈伪流。抽帧 ffmpeg 一行也行,跑 Python 脚本也行,怎么顺手怎么来。
整条骨架是这么长出来的:
抽单帧 → HSV 分天空 → 单帧染色验效果 → 再上视频 → 伪标注训 YOLO → 10s 对比片 → 浏览器伪流
三条原则,其实是踩坑踩出来的:
- 先图后视频——单帧 mask 和滤镜不对,上 mp4 只是放大错误。
- 先 HSV 后 YOLO——灰天、海天、云洞,靠取色和规则能迭代;HSV 调通了,同机位 mask 还能当伪标注,再训 YOLO 不迟。
- mask 和滤镜分开——后面换 YOLO、甚至第三篇无 GPU 的 HSV fallback,调色栈都可以不动。
就是从这片海岸素材里抽出来的那一帧——后面所有调参都在它上面闭环。
2. 单帧上怎么做:就两件事
抽完帧之后,我在一张图上其实只干两件事:
- 找出哪块是天——输出一张 mask(白=要动,黑=别碰)。
- 只动 mask 里的像素——提蓝、提饱和;海、建筑、码头保持原样。
用 OpenCV 的话说,就是 inRange / 分割出天空区域 → 在 mask 内做 hue、sat 变换。视频和伪流,不过是「对每一帧重复这两步」;所以单帧没闭环,后面全是白搭。
我当时的顺序也简单:
原图 frame_raw
→ 取色 + HSV 规则 → 天空 mask(overlay 肉眼验)
→ mask 内 unified 调色 → frame_after
→ 满意了,再套到 mp4 / 伪流上
mask 怎么算和颜色怎么调是两件事——后面换 YOLO 出 mask、甚至第三篇无 GPU 走 HSV 伪流,调色逻辑都可以不改。单帧阶段先把 overlay 看顺眼就够了;系列后文才用得上「换 mask、不换滤镜」这句。
3. 分天空:先能扣,再谈好看
mask 我迭代了好几轮——六点取色、海天线、裁误检那些,第二篇单独写。
第一篇只交代:为什么先用 HSV,以及「扣出来长什么样」。
没有现成天空模型,HSV 的好处是在单帧上能很快试:点几个色、看 overlay,不对就改 JSON,不用等训练。A 轮锁主天,B′ 补低饱和云洞,合并成一张 mask。海天线怎么定、ROI 怎么换,此处不展开。
肉眼验收就三句:天盖住了、海和建筑在外面、云缝尽量别断。
overlay 顺眼了,再进下一节调色。
4. 调色:云洗过,还是假——最后选 unified
mask 有了,下一步是染色。本来我想走精细路线:主天调蓝了,云还是有点脏,能不能把云单独抠出来洗洗?
这就是 C 轮——mask 里 A 抠出来的「洞」就是云候选,再 inRange 一轮认云,天一路调蓝、云一路单独洗。逻辑很顺,代码也跑通过。
但目视下来:云区容易过曝、发假,像 P 上去的,不如整片一起调自然。
所以交付改 unified:mask 内统一提蓝、提饱和,云略脏一点,整体最贴眼。C 轮留在仓库里当调试经验,不是标准路径。
老实讲:目前云还没有让我满意的解法——unified 是「整体能交差」,不是「云也完美」。以后若再玩,可能从局部蒙版、PS 画笔那条线走;本篇不展开。
调色本身不复杂:mask 内动 hue、sat,边缘软一点,海和建筑不动。
这一帧对了,后面 10s 片和伪流才有意义。
5. mask 换代:HSV 没白做,狠狠拿它当伪标注
单帧 HSV 跑通之后,教程的下一步通常是:OpenCV 手抠图,一帧一帧标 mask——俗称纯手抠。我这个视频抽 32 帧,粗算 2~3 小时 坐那儿点鼠标。
我寻思:前面 mask 都调那么顺了,干嘛非要手抠?
于是写脚本,用 HSV 输出直接生成 YOLO 标签,狠狠让脚本帮我标注,再训一版 YOLOv8n-seg。
诶,你猜怎么着——权重训出来了,能用。
交付上就换 YOLO 出 mask;调色栈不动,unified 那套一路沿用。Roboflow 文档里留着当备胎,本片不阻塞在这上面。
Step 2 因此干了两次活:开发期调参,和生产前顺便当标注工厂。
6. 怎么验收:单帧会骗你,视频才现形
离线 10s 对比片——单帧参数搬到时间上,甲方一眼能看前后。
我用的就是同一段海岸素材的前 10 秒。
单帧看着没问题,不代表视频没问题。第一轮做到这儿,我放 10s 片才看见怪东西——天空右上有一条淡紫横带,单图肉眼看几乎不显,一动起来特别扎眼。
排查也土:用取 xy 的小工具,把那块区域坐标抠出来,单独截 ROI 对比、看 mask 有没有盖住。磨了一会儿,大致对上两类原因:
- YOLO mask 帧顶留空——最上一截天没进 mask,滤镜没走到,和周围已调过的天一对 diff,像一条紫边。
- 低饱和区被 hue/sat 拉猛了——灰天、薄云边缘容易偏品红,视频里比静帧更明显。
修完是第二轮:mask 按列补顶,再跑一遍链路。下面这张就是紫带消除之后的最后对比效果。
所以我的验收顺序固定了:先单帧闭环 → 再上 10s 片抓动态问题 → 最后浏览器伪流。
伪流那边 go2rtc 起原片 RTSP,Python 读帧、套 mask+滤镜、推回去;页面里并排原片 / 蓝片,看天里更蓝、非天别染色就行。无 GPU、跳帧那些,第三篇再聊。
7. 系列导航
| 想深入… | 读 |
|---|---|
| 海天线、ROI、B′、A″、bottom_up | 第二篇 · 海天线算法的前世今生 |
| 甲方盒子没 GPU | 第三篇 · 当你的甲方设备过烂,要如何快速出效果? |
第一篇先把全流程串到这里。最磨人的海天线,见第二篇。