Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6 色阶分层

✅ 已验证 · 代码 code/cel.py(分档复用 segment.tiers / segment.pick_flat

层次是这么来的

人类画的插画靠明确的几档色阶构成层次,不是连续渐变。实测参考画和 bicubic 放大的 origin 在同一块 1:1 特写上量 8 级量化色数,前者更少却更有层次。所以每个部件要切成几档:固有色、阴影 1、阴影 2、高光……

分档

cel.levels(rgb, mask, k=None)

  1. segment.tiers() 在部件内做 KMeans,按亮度排序
  2. segment.pick_flat()面积最大的那档定为 Flat(不是最亮的——白裙的本色就是最亮档)
  3. 比 Flat 暗的档叫 Shadow1 / Shadow2 …,比它亮的叫 Light1 / Light2 …

档数 kpick_k() 按部件的亮度跨度(3–97 分位)自适应,2–5 档。

嵌套,不是并列

这一条是关键。每一档的 mask 不是「只属于这档的像素」,而是「这档及所有比它更深的像素」:

Flat      = 整个部件
Shadow1   = 所有比 Flat 暗的像素      (包含 Shadow2)
Shadow2   = 所有比 Shadow1 暗的像素   (包含 Shadow3)

叠层时上层盖下层,每一档只管「比上一档再深一点的那块」。

为什么不并列?并列切法的各档各自独立矢量化,曲线不会正好贴合,档与档之间会漏缝。 嵌套则永远是上层盖下层,没有缝。这也正是画师的图层叠法。

清理

KMeans 分出来的 mask 有碎点和针孔,矢量化之后是几百个 2px 的小环。clean_mask() 做闭运算 + 开运算,去掉小于 min_px=24 的连通域,填掉小于 24 的洞。

矢量化

cel.vectorize() 把每一档变成曲线(第 7 章)。Flat 用对整幅解的 α(另一侧是别的部件),其余档用 domain=部件 限定在部件内解(另一侧是同部件的更浅档)。Flat 还向外扩 expand=0.6 像素盖住接缝(第 14 章)。

实测

origin 的 15 个部件:

部件控制点
Hair_Front51244254
Dress_Frill51264148
Hair_Back5612831
Dress_Skirt5431848
合计59420,495

耗时 26.7s(裸 python)。

档数不是越多越好

试过给脸加档:

Skin_Facek=4k=8k=16
只铺色阶的 PSNR18.8920.0820.80

4 倍的档数只换来 1.9 dB。色阶的作用是给结构(眼睛在哪、阴影形状是什么),连续变化的部分交给笔画。这也是为什么第 17 章的规划器起点是「色阶铺完的画面」。

只铺色阶的 1x 模拟。眼睛被压成色块是预期的——这是「铺平涂」阶段,画师的平涂稿也长这样。