当前位置: 首页 > news >正文

单色过渡色还原 PNG:从白底结果反推透明通道

目录

1. 问题与目标

2. 还原条件

3. 还原策略

4. 关键算法

4.1 向量投影反推 alpha(结构反解)

4.2 结构融合

4.3 输出像素合成

​编辑4.4. 参数说明与调参建议

5. 可视化验证指标

6. 源码


1. 问题与目标

已知一张“单色软边过渡”图像原本来自透明层(例如红色喷笔),后续被合成为白底图。 现在希望反推回“透明 PNG”,并在保持空间结构(层次、扩散范围、边缘平滑度)的前提下,支持色调替换。

核心目标:

  • 透明结构层次尽量与原图一致(避免中心被压重或外圈被截断)。
  • 支持目标色调实时调整(例如红 → 蓝、红 → 紫)。
  • 提供强制对齐选项,使最强点更贴近目标色基调。

2. 还原条件

在 straight-alpha 模型下,白底合成可写为:

C = α·F + (1-α)·W

其中:C为观测颜色,F为前景颜色,W为已知底色(白),α为透明度。 单像素共有 3 个方程(RGB),未知数 4 个(F 的三个通道 + α),因此一般不可唯一反解

严格可逆的典型条件:

  • 底色W已知;
  • 前景色F可认为恒定(或已知函数)。

结论:若前景颜色本身随空间变化,仅凭一张白底结果图无法无损恢复原始 (F, α)。工程上要“尽量像”,必须引入先验与约束。


3. 还原策略

本文实现采用两阶段:结构优先 + 色调重映射

阶段目标说明
阶段 A:反推结构还原 alpha 空间层次从白底图直接估计每像素透明度,优先保持扩散范围与层次分布。
阶段 B:色调映射替换到目标色系在保留结构的基础上,把颜色映射到目标色;支持“强制对齐”增强中心色匹配。

“结构优先”:

若直接做 RGB 调色或 alpha 拉伸,容易出现“中心偏重、外圈变薄”的视觉偏差。 结构优先策略先锁定空间分布,再做色调替换,能更好贴近原图组织方式。


4. 关键算法

4.1 向量投影反推 alpha(结构反解)

对于目标色F_t,白底W,观测像素C

a = dot(W-C, W-F_t) / ||W-F_t||²

并裁剪到[0,1]。这相当于在最小二乘意义下估计该像素对目标色方向的“浓度”。

4.2 结构融合

将“基础反解 alpha”与“投影 alpha”按比例融合,抑制噪点并保持层次:

a = forceAlign ? aProj : lerp(aBase, aProj, 0.72)

其中forceAlign为“强制对齐目标色”模式。

4.3 输出像素合成

输出采用:

A = round(a * 255) RGB = lerp(baseTone, targetTone, toneMix) out = Color.FromArgb(A, RGB)

放入“alpha 热力图 + 径向剖面曲线(原图 vs 还原)”,

这样可以在同一透明结构上连续变换色调,且实时响应 UI 参数调节。


4.4. 参数说明与调参建议

参数范围作用建议
targetToneRGB目标色基调先选接近期望中心色,再调强度。
toneMix0~1色调替换强度常用 0.65~0.9;1.0 为近似完全替换。
forceAlignbool是否强制对齐目标色中心偏灰时开启;结构过硬时关闭。
融合系数0~1lerp(aBase, aProj, k)k越大越依赖结构投影,越小越平稳。

5. 可视化验证指标

  • 径向均匀性:等距半径上亮度/饱和度变化是否平滑。
  • 中心-外圈比例:中心峰值与半高宽是否接近目标图。
  • 色调一致性:中心区域 HSV 与目标色偏差(ΔH、ΔS、ΔV)。
  • 结构保真:对比还原前后 alpha 热力图(或灰度图)。

6. 源码

核心算法源码:

public static class SingleColorTransBackProcessor { public static SingleColorTransBackResult Process(Bitmap flatOnWhite, SingleColorTransBackOptions options) { if (flatOnWhite == null) throw new ArgumentNullException(nameof(flatOnWhite)); if (options == null) throw new ArgumentNullException(nameof(options)); var baseRecovered = RecoverBitmapFromBackground(flatOnWhite, options.KnownForeground, options.Background); var preview = BuildStructurePreservingPreview(flatOnWhite, baseRecovered, options); return new SingleColorTransBackResult(baseRecovered, preview); } public static Bitmap BuildPreview(Bitmap flatOnWhite, Bitmap baseRecovered, SingleColorTransBackOptions options) { if (flatOnWhite == null) throw new ArgumentNullException(nameof(flatOnWhite)); if (options == null) throw new ArgumentNullException(nameof(options)); return BuildStructurePreservingPreview(flatOnWhite, baseRecovered, options); } private static Bitmap RecoverBitmapFromBackground(Bitmap source, Color knownForeground, Color background) { var lb = new LockBitmap(source); lb.LockBits(); try { var dst = new Bitmap(lb.Width, lb.Height, PixelFormat.Format32bppArgb); for (var y = 0; y < lb.Height; y++) { for (var x = 0; x < lb.Width; x++) { var c = lb.GetPixel(x, y); var o = RecoverStraightAlpha(c, knownForeground, background); dst.SetPixel(x, y, o); } } return dst; } finally { lb.UnlockBits(); } } private static Color RecoverStraightAlpha(Color c, Color f, Color w) { var a1 = SolveAlpha(c.R, f.R, w.R); var a2 = SolveAlpha(c.G, f.G, w.G); var a3 = SolveAlpha(c.B, f.B, w.B); var a = MedianNonNaN(a1, a2, a3); if (double.IsNaN(a)) a = 0.0; a = Clamp01(a); var ab = (byte)Math.Round(a * 255.0); return Color.FromArgb(ab, f.R, f.G, f.B); } private static Bitmap BuildStructurePreservingPreview(Bitmap flatOnWhite, Bitmap baseRecovered, SingleColorTransBackOptions options) { var toneMix = Clamp01(options.ToneMix); var structureBlend = Clamp01(options.StructureBlend); var w = flatOnWhite.Width; var h = flatOnWhite.Height; var dst = new Bitmap(w, h, PixelFormat.Format32bppArgb); var white = new[] { 255.0, 255.0, 255.0 }; var baseTone = new[] { (double)options.KnownForeground.R, (double)options.KnownForeground.G, (double)options.KnownForeground.B }; var target = new[] { (double)options.TargetTone.R, (double)options.TargetTone.G, (double)options.TargetTone.B }; var toneNow = new[] { Lerp(baseTone[0], target[0], toneMix), Lerp(baseTone[1], target[1], toneMix), Lerp(baseTone[2], target[2], toneMix) }; var vf = new[] { white[0] - toneNow[0], white[1] - toneNow[1], white[2] - toneNow[2] }; var denom = vf[0] * vf[0] + vf[1] * vf[1] + vf[2] * vf[2]; if (denom < 1e-6) denom = 1.0; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var c = flatOnWhite.GetPixel(x, y); var wc = new[] { white[0] - c.R, white[1] - c.G, white[2] - c.B }; var aProj = (wc[0] * vf[0] + wc[1] * vf[1] + wc[2] * vf[2]) / denom; aProj = Clamp01(aProj); var aBase = baseRecovered != null ? baseRecovered.GetPixel(x, y).A / 255.0 : aProj; var a = options.ForceAlignTone ? aProj : Lerp(aBase, aProj, structureBlend); var an = (int)Math.Round(Clamp01(a) * 255.0); var r = ClampByte((int)Math.Round(toneNow[0])); var g = ClampByte((int)Math.Round(toneNow[1])); var b = ClampByte((int)Math.Round(toneNow[2])); dst.SetPixel(x, y, Color.FromArgb(an, r, g, b)); } } return dst; } private static double SolveAlpha(byte c, byte f, byte w) { var den = f - w; if (Math.Abs(den) < 1e-6) return double.NaN; return (c - w) / (double)den; } private static double MedianNonNaN(double a, double b, double c) { var arr = new System.Collections.Generic.List<double>(3); if (!double.IsNaN(a) && !double.IsInfinity(a)) arr.Add(a); if (!double.IsNaN(b) && !double.IsInfinity(b)) arr.Add(b); if (!double.IsNaN(c) && !double.IsInfinity(c)) arr.Add(c); if (arr.Count == 0) return double.NaN; arr.Sort(); return arr[arr.Count / 2]; } private static int ClampByte(int v) => v < 0 ? 0 : (v > 255 ? 255 : v); private static double Clamp01(double v) => v < 0 ? 0 : (v > 1 ? 1 : v); private static double Lerp(double a, double b, double t) => a + (b - a) * t; }

在不同透明背景下,我们可以更换任意色调:

完整Demo源码:https://download.csdn.net/download/LateFrames/92830783?spm=1001.2014.3001.5501

http://www.jsqmd.com/news/710014/

相关文章:

  • 2026工业窑炉厂家推荐:性价比高+长期运行成本更低,享设计到安装全流程服务 - 品牌种草官
  • 2026年新加坡留学服务口碑好的机构:五家优选深度解析 - 科技焦点
  • 深度学习驱动的参数化CAD曲面生成技术解析
  • 3步实战:将Amlogic电视盒子改造为高性能Armbian服务器
  • 华硕笔记本的“瘦身“秘籍:3分钟让G-Helper成为你的性能管家
  • 2026 最新日语网课机构推荐|高性价比日语机构排名 - 资讯焦点
  • Agent+MCP+Skills 重构自动化测试:从脚本生成到测试闭环
  • 国内专业防抛网厂家综合实力排行及核心优势解析 - 资讯焦点
  • 2026年宁波韩国留学机构哪家口碑好:五家优选评测 - 科技焦点
  • 树莓派4B双WIFI配置实战:告别手动切换,让设备自动连接信号更好的网络
  • 2026年3月性价比高的宁夏品牌碗蒸羊羔肉餐馆口碑推荐,精美凉菜/黄牛肉炒糊饽/宁夏清真菜,宁夏品牌碗蒸羊羔肉门店推荐 - 品牌推荐师
  • day15-Trae实现换脸微信小程序02
  • 华硕笔记本性能优化完全指南:G-Helper开源控制工具实用教程
  • 突破限制:如何为Android Auto安装第三方应用
  • 2026年3月恒温恒湿车间改造推荐,恒温恒湿车间/净化车间/无尘室/车间净化/净化工程/洁净室,恒温恒湿车间厂商哪家好 - 品牌推荐师
  • 告别内存焦虑:用VastGaussian的渐进式分块策略搞定超大场景3D重建(附保姆级配置流程)
  • 2026年新加坡留学机构哪个比较好:五家优选深度解析 - 科技焦点
  • 别再死记硬背命令了!AutoCAD 2020图层、捕捉、约束三大辅助工具实战指南(附机械零件图案例)
  • 2026 必看!学日语机构推荐|靠谱日语网课精选 - 资讯焦点
  • Excel中xlPicture对应的就是 ‌增强型图元文件EMF格式
  • 概念引导微调(CFT)技术解析与工程实践
  • 2026 年硅胶制品加工必备硫化机厂家精选 - 资讯焦点
  • 2026年3月机床铸件企业口碑推荐,球墨铸件/机床铸件/铸铁平台,机床铸件厂家哪家专业 - 品牌推荐师
  • TTP229触摸模块避坑指南:51单片机驱动时如何解决误触和抗干扰问题?(实测分享)
  • 3个关键场景解锁IPATool:命令行如何重塑iOS应用下载体验
  • 如何用SRWE突破游戏窗口分辨率限制:终极窗口编辑器完整指南
  • 大语言模型安全评估:现状、挑战与DeepSight解决方案
  • 力扣第180题文件组合,来看看滑动窗口的巧妙思想!
  • 2026主管护师考试模拟卷大测评,甄选带详细解析的优质模拟试卷 - 医考机构品牌测评专家
  • 无敌烤肉大王的地盘索引