Show HN:用简单算法与色彩空间生成多样化的肤色
文章摘要
作者 automatoney 在做数字绘画和游戏开发时遇到一个具体的麻烦:要挑出一批既合理、又足够多样的肤色,出乎意料地困难。他好奇能不能定义一个色彩空间来让这件事变简单,于是有了这个项目——一个交互式的肤色取色器、一套配套的程序化生成算法,以及一篇把整个构造过程完整摊开的长文。全文标题里那句自嘲的定语很关键:这不是「正确的」色彩空间,而是一个「足够好的」(good enough)色彩空间。
文章开篇给出了问题的量级:emoji 说我们有 5 种肤色(或者干脆是卡通黄);化妆品牌可能说有 50 种;而角色创建器往往两手一摊让你从 16,777,216 个 RGB 值里自己挑。作者认为真正有用的答案应该落在「几种」和「几百万种」之间。他在动手之前先诚实列出了这项工作的局限:皮肤远不止一个颜色,身体不同部位差异极大,还受血流、黑色素浓度、光在皮肤各层中的复杂散射,以及白癜风、雀斑、色素沉着、疤痕等等影响;某些健康状况(银中毒会让皮肤呈蓝灰色、高胆红素会让皮肤发黄发绿)会产生「看起来不合理」的真实肤色;而且他只是一个人、不是研究者,所有判断都带着他个人的偏见和色觉;最后,颜色在不同显示器和不同光照环境下根本不会被一致地感知。
方法论部分他自己打了个「非科学方法预警」,但流程本身相当清晰,只有三步:手工标注 RGB 颜色以得到数据集的粗略形状 → 对数据集做 N=3 的主成分分析(PCA)把形状变成好处理的样子 → 用画图软件手工凑出一组方程,把目标空间里的一个球面映射到 PCA 空间里的数据上。
手工标注环节他做了一个简单的网页 UI:点击就把一张脸在「是」和「否」两侧之间移动。最初只是色块,后来他干脆画了张脸把颜色贴上去——他说盯着这个「粗糙的研究助手」的眼睛,会更容易迅速判断「嗯,我能想象这个人在街上走」。把标注数据画出来,得到的是一个在 (0,0,0) 与 (255,255,255) 之间摆荡的香蕉形,曲线偏红、远离蓝。他在这里卡了最久,试过凸包、各种回归、四次多项式,写出了他这辈子最难看的代码,最后是学到 PCA 才破的局。PCA 把那根悬在空中的香蕉「放平在地上」并沿坐标轴对齐,同时给出一个能在 RGB 与 PCA 空间之间来回转换的矩阵。
最后一步是拟合。他从一个球面方程 R² = t² + u² + v² 出发,把 t、u、v 用 x、y、z 表达,然后不断修改这些定义直到球被拉伸成与数据吻合,再把关系反解回去。他坦承:这里没有任何回归,纯粹是在 Desmos 3D 里手工试错、肉眼判断拟合效果。他说自己不背书这套具体做法,但确实认可整体方法——「标注数据 → PCA → 拟合一个球形方程」这条路。
结果最迷人的部分在于三个控制轴的语义是 PCA 自己长出来的,而不是他事先设定的:T 轴控制深/浅(deep/fair),U 轴控制潮红/赭黄(flushed/ochre),V 轴控制冷/暖(cool/warm)。他自己也是做完取色器之后才发现这三个方向各自对应什么概念,并把这当作 PCA 作用的一次漂亮演示。他也解释了为什么它不等同于 HSL/HSV:T 和 U 看似接近 Value 和 Hue 但并不完全吻合,而 V 与 Saturation 的差别最大——V 是从蓝到橙,而 Saturation 更像是从灰到橙。
R² 参数是这套方案最实用的设计。因为空间构造中用了球,所以只需一个半径就能定义采样范围。作者给了一张对照表:0.1 几乎没有变化;1.0 是随机取色要有像样多样性的下限;1.5「在保持真实感的同时有不错的多样性」;2.0「可能有点卡通,随机取样偶尔会出现离群值」;2.5 适合取色器 UI 但不适合自动生成;10 则「大概适合奇幻设定」。关键性质在于:缩小半径不是把深色或浅色一刀切掉,而是让深肤色、浅肤色、潮红、赭黄、冷调、暖调六个方向均匀地减少变化。把 R² 一路降到 0 得到空间原点,如果这套空间构造得当,这个原点应该是一个非常中性、含混的色调——换句话说,原点可以用来检测这套映射本身有没有偏向。
文章中间还专门插了一节「人文中场」,作者停下数学,直言技术存在于社会语境中:浅肤色在历史与当下都被推崇和优先,深肤色则被边缘化和污名化。他在这里推荐了一批相关作品,包括 Nyma Tang 的《The Darkest Shade》系列(评测各品牌最深色号)、Vox 关于美妆业如何辜负有色人种女性的视频、Austin Walker 关于《动物森友会》中种族呈现的随笔,以及 Angélica Dass 的摄影项目 Humanae——后者用 Pantone 色号记录人类真实的肤色,作者说这与自己的工作精神相通,只是一个偏记录与对话,一个偏描述。
HN 评论精华
这条 Show HN 拿到 625 分、89 条评论,讨论的质量相当高,且大量集中在色彩科学的技术细节上。
-
dr_scully 贡献了整个帖子里权威性最强的一段:他的博士论文表明,肤色其实可以用黑色素浓度和血红蛋白浓度来参数化,这意味着它大致上确实是二维的;由此还可以反解一个像素的颜色、推出这个人的发色团浓度。GolDDranks 追问东亚人未晒黑时那种「偏黄」是否来自皮下脂肪、是否属于第三个自由度,dr_scully 回答说他们采用的近似是把皮下组织当作完美白色反射面,因此厚度不影响;胶原纤维的散射方式会造成偏黄的色调,所以在没有黑色素和血红蛋白影响时,皮肤可能就呈现这种样子。他还提到自己当年用 OpenCL 写了一个蒙特卡洛皮肤模拟器,理论上任何发色团只要知道浓度、散射曲线和反射光谱就能插进去建模。mdnahas 补充了论文里的一个要点:血红蛋白不是静态的,皮肤的血流会随时间变化。
-
mbo 提供了一个漂亮的旁证:他之前用 The Pudding 关于粉底色号的数据集,把它投到 Oklab 色彩空间里,结果这些色号自己排成了和本文中一模一样的新月形。作者的回应是”这两个我都超爱,等有空一定加进正文链接”。
-
s1mon 指出作者虽然调研了一些既有方案,但没提到 Pantone SkinTone——他因缘际会做过三个涉及测量肤色的美妆项目,认为 Pantone 那套虽不完美但做得相当好且易懂,也是两个轴:明暗轴和红黄轴。ptmkenny 补充了另一个重要参照:Dr. Monk 与 Google 合作的 Monk Skin Tone Scale,专为机器学习设计,以 CC BY 4.0 发布、提供包括十六进制在内的多种格式。itishappy 则纠正了顺着话头说「Pantone 不是免费色彩空间」的人:Pantone 根本不是色彩空间,它是物理色样的供应商;多数颜料可以在色彩空间里表示,但那个表示随光源变化,而某些 Pantone 颜料(尤其是荧光色)根本无法用色彩空间表示。tialaramex 把这一点升华成一条通用忠告:颜色和时间一样是个无底洞,学一点就知道后面还有海量的东西,实际用到时确认你们对细节的共识刚好够用、别更少——邀请人下周野餐可能需要懂时区,但不需要懂历法系统和闰秒;改个官网 CSS 配色可能需要懂色彩空间,但不需要懂次表面散射和光的量子理论。
-
关于「为什么会出现绿色、蓝色、紫色」是最高频的困惑。andai 一上来就问了这个,作者承认得很坦率:取色器 UI 里可以放宽范围,但生成场景下就该收紧;而且「不排除掉一些真实存在的颜色就无法解决这个问题」这个判断百分之百正确。richiebful1 给了实用答案:把 R² 降到约 1.5,极端的蓝绿紫就没有了,是包含真实离群值与排除绿色之间的一个合适折中。wongarsu 则从另一头开玩笑:把滑块拉到底就只剩合理肤色,拉到另一头你可以配出兽人、纳美人和精灵的肤色。rsynnott 用一条维基链接回应「蓝色」:银中毒(Argyria)了解一下。而 compass_copium 提了个直接的 UX 建议:把「显示球体」默认打开,他一开始也被绕晕了。
-
RIMR 和 LocalH 都提醒了一个哭笑不得的坑:先把你的暗色模式插件关掉。有人抱怨「浅肤色的多样性表现不足」,答案原来是浏览器的强制暗色模式在捣鬼。作者的反应是”哇,完全没想到这类东西会影响页面”。1-6 补充说他在 iPhone Safari 上用 Noir 插件强制暗色,结果这个工具把白色处理成了「无颜色」。
-
herf 提出了一个真正的技术 bug:这套实现似乎是在没有伽马校正的情况下做 XYZ→RGB 的。XYZ 是线性的、到 RGB 的变换也是线性的,所以通常最后应该把线性 RGB 转成伽马编码的 sRGB;而 JS 代码看起来是把线性 RGB 直接输出到网页上。他也指出了修复的难点:如果训练数据本身也是伽马编码的,那可能需要用线性值重新拟合。Sharlin 则建议不要把 PCA 后的空间叫 XYZ,因为 CIE XYZ 是一个众所周知的标准色彩空间;他还观察到 U 和 V 轴大致对应照片编辑器白平衡工具里的色调/色温(绿-品红、蓝-琥珀)两轴,也就是相机里的白平衡设置。
-
stu2b50 提了一个绘画视角的重要限定:这一切成立的前提是光源色温接近日光。真正画画时,皮肤可以是任何颜色——蓝色就是完全合理、而且相当常见的肤色,看看月光下的人就知道了。”探戈要两个人跳,另一个舞伴是光源。” manoDev 补充了两个物理效应:次表面散射,以及某些红外发射(这正是让一些人在数码照片里显得偏品红的原因)——所以就算调色板对了,要把它画成一种可信的材质依然极难。
-
dahart 分享了一个来自 Peter Shirley(《Ray Tracing in One Weekend》作者、同时也是兼职色彩科学家)的冷知识:把任何种族的人像照片饱和度拉到 100%,肤色都会变成橙色;他隐约记得有些人脸检测算法就是基于这个原理工作的。madcaptenor 一句话点破:”棕色不过就是暗一点的橙色。”
-
dofm 从代表性角度提了一个具体反馈:在他的旧款 iPhone 上,1.5 这一档似乎覆盖不到澳大利亚原住民和托雷斯海峡岛民的肤色范围(他们的深肤色来自与其他深肤色人群不太一样的基因表达),而 2.0 的「离群值」档才涵盖得到;马赛人和部分索马里人的肤色同样是在离群值档里表现更好。作者回应说 2.0 正是他调参时用的基准值,但沿深/浅轴压缩空间以改善低 R² 下的结果确实还有很大余地,他遇到的障碍是空间越出 RGB 边界之后的处理,估计给最终的 R、G、B 值加上钳制就能修好。
-
vermarish 从数据科学角度详细复盘了自己阅读时的三次预期落空——先以为会用 PCA 做 3D 降 2D(更好用的取色器,代价是无法代表所有人),再以为会止步于 U 空间向量和对应的椭圆,结果作者做了函数拟合,”哪怕是手工完成的,这个想法也非常漂亮”。他唯一没看懂的是开头的手工标注环节,觉得那里会编码进偏见——不过点云和结果的一致性确实很有说服力。作者承认偏见确实存在,并说未来工作里应该把标注流程科学化(多标注者、专家标注)再喂回方程生成步骤,他当时的对冲办法只是靠”艺术脑”和脑子里的一堆参考图(并特意致敬了 Humanae 项目)。zestyping 提了同一个疑问的另一面:为什么不直接从真实人脸照片里采样颜色,而要给自己随机上色的脸做二分类?
-
rrradical 问代码能不能用在自己项目里,作者顺势补上了 MIT 协议,并补了一句有意思的判断:据他理解数学和方程本身不受版权保护,所以这项工作的核心(方程和想法)在法律上应该是零限制的——即便许可证是限制性的。
-
也有对项目立意本身的保留意见。E-Reverance 说,他不代表所有非白人,但他个人觉得过分执着于这件事本身是令人不适甚至冒犯的,不管出发点是种族主义还是反种族主义;不过他确实觉得这套建模和数据驱动的视角很酷。benj111 附和说他也不太理解「需要被代表」这件事——”我是被马力欧代表的吗?” 另一边,ptsneves 给了完全相反的框定:”对我来说这就跟做本地化框架一样自然,只不过这次是给肤色做的。” hluska 的评价则最有画面感:”一个 49 岁和一个 10 岁同时看得入迷,这很了不起。”
-
peepsick 提炼出了这个项目最可迁移的一点启发:”我很喜欢把色彩空间当作一个任务专用的优化问题、而不是某种普适之物来对待。这让我怀疑我们在程序化生成里是不是普遍低估了领域专用色彩空间的价值。”
-
帖子里最好笑的几条都在同一个方向上。efitz:「从没见过 #E6FFF8(右下角)这个颜色的人。是不是常年喝胶体银的白化病患者?」ralusek:「如果你是 #CCFFFF,请立刻去医院。」JacobAsmuth:「我在海滩待一天之后:#BA3A17。」