副标题:六点取色、海天线与前生今世------蓝天滤镜里我最磨人的一条线
摘要 :上篇 全流程速通 只交代 mask 结论;这篇细说原理 ------六点怎么取、取完怎么印中间步骤验收、magic number(ROI 0.65 / TOP_BAND)为什么存在、又怎么换成 bottom_up。无 GPU 见 第三篇。
素材:同一片 Pexels 海岸固定机位 · 1280×720
0. 上篇留白的地方
上篇说过:A 轮锁主天,B′ 补云洞,合并成 mask。六点怎么点、海天线怎么定、ROI 怎么换------全在这儿。三篇里最磨人的也是这段。
灰蓝天海岸片里,海天交界管两件事:
- 别把海抠进 mask------白沫、深蓝一进 mask,滤镜就染海。
- 补云洞时别用 magic 比例------「画面上半屏 47%」不是海平线,换构图就废。
先看 A 轮 mask 叠上去长什么样:

有淡黄色一层的是天,剩下的是海和岩石。验收就一句:海天交界贴不贴眼 。后面所有迭代,都是为这条线服务------但线能不能准,先得六点取色,跑起来还要印中间步骤。
1. 六点取色:A 轮锁主天,故意不用 cloud
A 轮的 job 是锁主天盘 + 海天分界 ,在 frame_raw.jpg 上点六个采样色,算出 hsv_threshold_a.json。
原计划取色表里有 cloud。我没把 sky_cloud 放进六点------云 H 离群、偏黄,merge 进 A 的 JSON 会把岩石/岛色相拉进区间,海天变脏。取色网站上 cloud 点一点 merge,区间就背叛你。
A 锁的是盘,不是认云;云和主天色域分叉,不能为顶云把 H 拉穿。云洞交给后面的 B′,这是第一次偏离 steps 字面流程,但符合设计。
2. 取色别翻车:换算、坐标、脑子里要有数
每个点其实要在心里有一套标准。可以用取色网站辅助,填 JSON 前先把 HSV 口径对齐。
网站 HSV ↔ OpenCV HSV
| 通道 | OpenCV(脚本用的) | 网站常见写法 | 怎么换算 |
|---|---|---|---|
| H | 0~179(色环 ÷2) | 0~360° | H_opencv = H_网站 ÷ 2 |
| S | 0~255 | 0~100% | S_opencv = S% × 255 ÷ 100 |
| V | 0~255 | 0~100% | V_opencv = V% × 255 ÷ 100 |
网站 S/V 若已是 0~255,直接用。最稳还是在 frame_raw.jpg 1280×720 上用 OpenCV 读像素------和 run_mask_hsv_a.py 一致,别跟 1080 预览坐标混了。
六个点管哪块
| 点 | 管哪块 |
|---|---|
sky_top_L / sky_top_R |
左上角、右上角(光照不均,最容易漏) |
sky_mid |
中间大块天 |
sky_blue |
偏下一点、相对「更蓝」的一带 |
sky_gray |
云缝 / 灰蒙区(最灰的那块天) |
sky_near_horizon |
海天交界 正上方(锁分界线) |
取点规则(踩坑版)
| 规则 | 为什么 |
|---|---|
| 必须在 1280×720 原帧上点 | 1080 预览坐标整体偏,mask 全歪 |
| y 要在「天」里:顶 ~y<200,海天 ~y≈300~360 仍可能是天 | 点太低就是海 |
| x 顶角要真的在角上 | 本片 sky_top_L x≈86,sky_top_R x≈1030 |
| 避云边、避岛、避码头 | 边缘像素 H/S 混合,拉区间会脏 |
sky_near_horizon 在 海平线正上方最后一截天 |
锁边界;太低吃进海,太高锁不住 |
右侧近海 不能当 sky_gray |
xy 对但语义不对------其实是海/反光 |
| 太近又太像 不加第 7 点 | 不撑 min/max、不占新角落 |
灰蓝天(本片这类)H / S / V 怎么想
H(色相)· OpenCV 0~179
| 心里预期 | 本片六点实测 |
|---|---|
| 天 ≈ 100~115(青蓝,不是绿、不是黄) | 全部 102~105,紧得离谱 |
不对的信号:H < 95 且点在「像天」的位置 → 多半云边偏黄、反光或岩石;H > 120 少见纯天;网站 0~360 忘了 ÷2 整表废。所以有 CLAMP_H_LOWER = 92------merge 把 H 拉脏时,也不让区间吃到偏黄的 H。
S(饱和度)· 灰天关键轴
| 心里预期 | 本片 |
|---|---|
| 灰天 S 很低 :大概 10~50 | 最低 12(海天),最高 45(mid) |
| 真「蓝天」尖儿可以更高 | 后来 (668,78) S≈91,upper 不够 → 补 sky_blue2 |
不对的信号:S > 60~70 还说是天 → 多半植被、饱和礁石;S 极低 + V 极高且在云心 → 那是云,交给 B′;sky_gray 要点云缝里仍算「天」的地方,不是随便灰一块。
V(明度)
| 心里预期 | 本片 |
|---|---|
| 天 偏亮 :大概 V ≥ 140 | 六点 186~196 |
不对的信号:V < 120 → 云影、阴面、深水;海天附近可以 S 很低但 V 仍高 (sky_near_horizon 就是这种)。
灰天靠 S 宽、V 够亮;H 只信窄带,且用 clamp 保底;点要散在天的不同位置,绝不点云心、海面、岩石。
六点只是起点。真正磨人的,是跑起来之后才发现漏了哪块天、海从哪儿渗进来------下面按我实际踩坑的顺序讲。
3. 取点会翻车:印中间步骤才算数
六点是在 frame_raw.jpg 一张图上 点的。这片海岸固定机位,我赌它代表性强;但赌归赌,点完不等于完工。
跑完 A 轮 overlay,顶角漏天、海天吃进海、饱和蓝天抠成洞------全是正常翻车。本片后来还补过点(比如 (668,78) 的 sky_blue2,upper S 不够宽)。只信第一次 merge 出来的 JSON,一定会栽。
我摸索出来的验收法很土,但管用:
- 把 A、B 各阶段的中间图打出来 ------
inRange后、ROI 后、morph 后、B′ allow 带、合并前,能存啥存啥。 - 叠 overlay 看「还缺哪块天」------缺的是 A 的色域问题,还是云洞没补上。
- 分清 A 和 B′ 的锅 :A 锁主天盘;B′ 只补云洞,不补天。低饱和云吃不进 A 的方盒,才交给 B′ 在低 S 规则里长洞。把 B′ 当「再抠一轮天」,海天一定脏。
sky_near_horizon 帮你锁分界,但六点取对了,后面还有 ROI、A″、B′ 围着海天线磨------不印中间步骤,根本不知道卡在哪一道。
4. 第一次海天线:写死的比例(magic number)
没有「海天」这根线之前,海也会进 mask ------白沫、深蓝和天色相重叠,inRange 全图扫一遍,下半片海面会被当成天。
A 轮:ROI 0.65------「下面默认是海」
最早的做法是 ROI :从某条水平线往下,默认当海、不当天 ;主要在上半截里信 mask。
旧方案就是 上面 65%,下面 35% 整片清零 。读代码时注意顺序:inRange 其实先扫了全图 ,ROI 是扫完再砍一刀------不是「只在上半张图里 inRange」。
这不是拍脑袋:我对着很多帧的中间 mask 看过,0.65 在这片视频里能跑通 ------海面误检控得住,真天也没大面积被砍。但它仍是 magic number:海天更高的构图、海占画面更少的时候,0.65 会先误伤真天,后面精裁也救不回来(ROI 清零的像素补不回去)。
B′ 轮:TOP_BAND------画面比例也不是海平线
A 吃不进低饱和云,B′ 用低 S 规则补洞。一开始也想偷懒:只在上半屏 跑,比如 TOP_BAND × h,只让 y < 0.47h 参与。
本片 0.47 还能凑合;拧到 0.60 又开始扫海面;换一支海天更高的视频,整个带就歪了。画面高比例 ≠ 海平线------跟 ROI 0.65 是同一类病:写死比例能兜本片,换构图就废。
5. 换掉 magic number:从 mask 里读线
2026-06-24 半夜想通的:别跟画面百分比较劲,跟 mask 自己说话。
ROI:0.65 → bottom_up
看很多张中间 mask:底下仍以黑(海)居多,往上才进天。左 / 中 / 右各取几列,从底向上扫 ,连续几行填充够密,就算进天区------得到动态 y_cap,替换写死的 0.65。读失败才回 0.65 兜底。
所以:旧 ROI = 写死的「下面当海」;新 ROI = 从 mask 估出来的「下面当海」------心里那个模型没变,只是线不再 magic。

红:固定比例 y≈468 · 绿:bottom_up y≈359(贴海天)
本片实测两张 overlay 叠上去肉眼差别不大------看线比看 overlay 清楚。换构图时,固定 0.65 会先砍真天,那时候才分得开。
B′:跟 mask_a 的海岸线走
B′ 不再用 TOP_BAND,改成跟 mask_a 自己的天际线------按列扫白色结束行,低 S 云洞只长在 allow 带里,再和 A 合并。
A″:海面误检,B′ 救不了
换掉 ROI 之后,还有一类问题:海面已经进 A 了。B′ 只能管洞长在哪儿,retro-fix 不了 A 里的误检。
inRange 偶发把白沫、深蓝吃进来。做法是 morph 之后再 按列 cap 一刀 ------从上往下扫中央带,连续几行天像素很少就算过海天;再按列找每列 mask 最靠下的天行,cap 以下砍掉。这一步叫 A″ ,和 B′ 分工:A″ 修 A 里的海面,B′ 补云洞。
完整顺序(读 run_mask_hsv_a.py 用)
text
① inRange 六点 JSON → 色域方盒,第一次「像天的颜色」
② ROI 粗门帘:y_cap 以下不信(bottom_up 或 0.65 兜底)
③ morph 形态学修边修洞
④ A′ exclude 可选手抠矩形扣岛/码头
⑤ A″ 精修刀:按 mask 形状削海面误检
→ mask_hsv_a.png
↓
B′ 低 S 补云洞(跟 mask_a 海天线,不补天)→ 合并
调试时最容易搅混 ROI 和 A″ :都带「海天」,时机完全不同------ROI 在 morph 前 粗挡,A″ 在 morph 后 精裁。0.65 是这片能跑的 ROI 兜底,不是 A″ 本身。
6. 迭代时间线
text
六点取色(一张图,会补点)→ 印中间步骤验收
A 轮:ROI 0.65(magic)→ bottom_up(读 mask)
B′:TOP_BAND(magic)→ 跟 mask_a 海天线
A″:morph 后裁海面误检(一直有)
| 日期 | 我干了啥 |
|---|---|
| 06-20 | 六点不用 cloud · B′ 跟 mask_a · 毙 TOP_BAND |
| 06-21 | A″ 海天裁切 |
| 06-24 | bottom_up 替换 ROI 0.65 |
方案不必一次成熟。0.65 和 TOP_BAND 都是本片能跑的兜底;一步步从 mask 里读出规律换掉 magic number,比假装一开始完美更真实。
7. 系列导航
| 篇 | 内容 |
|---|---|
| 上篇 · 甲方吐槽天不够蓝 | 全流程速通 |
| 本篇 | 六点取色 + 海天线 |
| 下篇 · 甲方设备过烂 | 无 GPU 快速出效果 |
8. 收尾
三篇里,这篇最磨人------六点怎么点才不算海、取完还要印中间步骤、magic number 为什么存在、ROI 和 A″ 别搅混、梦里 bottom_up 落地,来来回回看 overlay 和画线。
每次做完项目我都想写一篇文章------记录当时的方案,也是记录当时的自己。参数和脚本可以进仓库,但「B′ 只补云不补天」「为什么 0.65 是兜底不是算法」「ROI 清零的像素 A″ 救不回」------这些判断不写下来,过几个月就只剩一张 mask,想不起当时怎么想的。
就像许愿变成 Ice King 的 Simon:愿望成真,也失去了自己。我不想只留能跑的 mask,忘了为什么从 magic number 走到读 mask 规律。