单色图最简单、最完美的抠像方法 - 透明渐变抠像 - C#

任务描述

1

用Ai制作图像,是一件非常简单的事情, 比如说, 导引ai先后生成了上面的人体简化图像, 但是 让它在抠除背景的时候,AI在处理精准性上表现很差,此时我们可以尝试的方式最少有两种。

PS抠像
第1种是用在线ps的魔棒工具进行抠像,但是抠除后的边缘锯齿感明显,整体效果非常的差 ,基本不可用:

2

3

AI抠像
然后我们还可以尝试使用即梦AI来抠像,效果比在线ps好了很多,但是仍然肉眼可见到边缘有背景色的存在:

1

2

自己写算法抠像
那么如何才能做到 让背景色从视觉上完全无感呢?

这里有一个小技巧,它处理的核心理念就是:越接近目标色,那么让它的透明通道值越接近255,越接近背景色,那么让它的透明通道值越接近于0,这样的话,在扣除色与目标色之间会形成完全平滑的透明过渡,从视觉上来说,会形成绝对的连贯性。

例如,用C#进行的算法处理,核心源码:

internal static class ChromaKeyProcessor
{
    public static Bitmap EnsureRgba32(Bitmap source)
    {
        if (source.PixelFormat == PixelFormat.Format32bppArgb)
        {
            return (Bitmap)source.Clone();
        }

        var rgba = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb);
        using (var g = Graphics.FromImage(rgba))
        {
            g.DrawImage(source, 0, 0, source.Width, source.Height);
        }
        return rgba;
    }

    public static void ApplyAlphaFromTwoColors(Bitmap rgba, Color keepColor, Color removeColor)
    {
        if (rgba.PixelFormat != PixelFormat.Format32bppArgb)
            throw new ArgumentException("需要 Format32bppArgb 位图。");

        int keepR = keepColor.R, keepG = keepColor.G, keepB = keepColor.B;
        int removeR = removeColor.R, removeG = removeColor.G, removeB = removeColor.B;

        var rect = new Rectangle(0, 0, rgba.Width, rgba.Height);
        var data = rgba.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
        try
        {
            int stride = data.Stride;
            int bytes = Math.Abs(stride) * rgba.Height;
            var buffer = new byte[bytes];
            Marshal.Copy(data.Scan0, buffer, 0, bytes);

            for (int y = 0; y < rgba.Height; y++)
            {
                int row = y * stride;
                for (int x = 0; x < rgba.Width; x++)
                {
                    int i = row + x * 4;
                    int b = buffer[i];
                    int g = buffer[i + 1];
                    int r = buffer[i + 2];

                    double dKeep = ColorDistanceSq(r, g, b, keepR, keepG, keepB);
                    double dRemove = ColorDistanceSq(r, g, b, removeR, removeG, removeB);
                    byte alpha = ComputeAlpha(dKeep, dRemove);
                    buffer[i + 3] = alpha;
                }
            }

            Marshal.Copy(buffer, 0, data.Scan0, bytes);
        }
        finally
        {
            rgba.UnlockBits(data);
        }
    }

    /// <summary>
    /// 将带透明通道的图叠到指定背景色上,用于预览。
    /// </summary>
    public static Bitmap CompositeOnBackground(Bitmap rgba, Color background)
    {
        var display = new Bitmap(rgba.Width, rgba.Height, PixelFormat.Format32bppArgb);
        using (var g = Graphics.FromImage(display))
        {
            g.Clear(background);
            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
            g.DrawImage(rgba, 0, 0);
        }
        return display;
    }

    private static double ColorDistanceSq(int r1, int g1, int b1, int r2, int g2, int b2)
    {
        int dr = r1 - r2;
        int dg = g1 - g2;
        int db = b1 - b2;
        return dr * dr + dg * dg + db * db;
    }

    /// <summary>
    /// alpha = 255 * dRemove / (dKeep + dRemove)
    /// 在保留色处 dKeep=0 → 255;在抠除色处 dRemove=0 → 0。
    /// </summary>
    private static byte ComputeAlpha(double dKeep, double dRemove)
    {
        const double epsilon = 1e-6;
        if (dKeep < epsilon && dRemove < epsilon)
            return 255;

        if (dKeep < epsilon)
            return 255;
        if (dRemove < epsilon)
            return 0;

        double sum = dKeep + dRemove;
        double t = dRemove / sum;
        if (t < 0) t = 0;
        if (t > 1) t = 1;
        return (byte)(t * 255.0 + 0.5);
    }
}

处理后的效果基本达到了完全可用的状态 :

1

2

细节的表现非常完美。

posted @ 2026-07-12 20:52  行人--  阅读(4)  评论(0)    收藏  举报