C# .NET绿屏背景去除

5
我正在为Windows 8的桌面PC开发一款照片软件,并希望通过色度键控的方式去除照片中的绿色背景。
我是图像处理的初学者,找到了一些不错的链接(如http://www.quasimondo.com/archives/000615.php),但无法将其转化为C#代码。
我正在使用带有aforge.net的网络摄像头来预览和拍摄照片。我尝试过颜色过滤器,但绿色背景并不是完全均匀的,所以这种方法行不通。
请问如何用C#正确地实现这个功能?

不确定为什么这被标记为下降 - 这个问题和答案对我正在尝试解决的问题有很大帮助。干杯。 - Gregory William Bryant
3个回答

8

即使背景不是统一的,它也可以工作,您只需要适当的策略来捕捉所有绿幕而不替换其他任何东西。

由于链接页面上至少有一些链接失效,因此我尝试了自己的方法:

  • 基础很简单:将图像像素的颜色与某个参考值进行比较,或应用其他公式以确定是否应该透明/替换。

  • 最基本的公式可能涉及诸如“确定绿色是否是最大值”的简单内容。虽然这对于非常基本的场景可以起作用,但它可能会出现问题(例如白色或灰色也将被过滤掉)。

我使用了一些简单的示例代码进行玩耍。虽然我使用了Windows Forms,但它应该可以无问题地移植,并且我相信您能够解释代码。请注意,这不一定是执行此操作的最有效方式。

Bitmap input = new Bitmap(@"G:\Greenbox.jpg");

Bitmap output = new Bitmap(input.Width, input.Height);

// Iterate over all piels from top to bottom...
for (int y = 0; y < output.Height; y++)
{
    // ...and from left to right
    for (int x = 0; x < output.Width; x++)
    {
        // Determine the pixel color
        Color camColor = input.GetPixel(x, y);

        // Every component (red, green, and blue) can have a value from 0 to 255, so determine the extremes
        byte max = Math.Max(Math.Max(camColor.R, camColor.G), camColor.B);
        byte min = Math.Min(Math.Min(camColor.R, camColor.G), camColor.B);

        // Should the pixel be masked/replaced?
        bool replace =
            camColor.G != min // green is not the smallest value
            && (camColor.G == max // green is the biggest value
            || max - camColor.G < 8) // or at least almost the biggest value
            && (max - min) > 96; // minimum difference between smallest/biggest value (avoid grays)

        if (replace)
            camColor = Color.Magenta;

        // Set the output pixel
        output.SetPixel(x, y, camColor);
    }
}

我使用了维基百科上的示例图片,得到了以下结果:

Masked result (magenta would be replaced by your background)

请注意,您可能需要不同的阈值(在我上面的代码中是896),甚至可能希望使用不同的术语来确定是否应替换某些像素。您还可以在帧之间添加平滑处理、混合(当绿色差异较小时)等方法,以减少硬边缘。


我进行了一项研究:为了获得更好的性能,您必须锁定位图。我使用了一个简单的库,来自于这里:http://www.codeproject.com/Articles/15192/FastPixel-A-much-faster-alternative-to-Bitmap-SetP - daniel
@zoidbergi 是的,GetPixel()SetPixel() 对于这个不是特别理想。在生产代码中,我可能会使用整个本地代码。 - Mario

4

我尝试了Mario的解决方案,它完美地解决了我的问题,但对我来说有点慢。

我寻找了另一种解决方案,在这里找到了一个使用更有效方法的项目。

Github postworthy GreenScreen

该项目可以处理一个文件夹中的所有文件,但我只需要一张图片,所以我做了这个:

private Bitmap RemoveBackground(Bitmap input)
    {
        Bitmap clone = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
        {
            using (input)
            using (Graphics gr = Graphics.FromImage(clone))
            {
                gr.DrawImage(input, new Rectangle(0, 0, clone.Width, clone.Height));
            }

            var data = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadWrite, clone.PixelFormat);

            var bytes = Math.Abs(data.Stride) * clone.Height;
            byte[] rgba = new byte[bytes];
            System.Runtime.InteropServices.Marshal.Copy(data.Scan0, rgba, 0, bytes);

            var pixels = Enumerable.Range(0, rgba.Length / 4).Select(x => new {
                B = rgba[x * 4],
                G = rgba[(x * 4) + 1],
                R = rgba[(x * 4) + 2],
                A = rgba[(x * 4) + 3],
                MakeTransparent = new Action(() => rgba[(x * 4) + 3] = 0)
            });

            pixels
                .AsParallel()
                .ForAll(p =>
                {
                    byte max = Math.Max(Math.Max(p.R, p.G), p.B);
                    byte min = Math.Min(Math.Min(p.R, p.G), p.B);

                    if (p.G != min && (p.G == max || max - p.G < 7) && (max - min) > 20)
                        p.MakeTransparent();
                });

            System.Runtime.InteropServices.Marshal.Copy(rgba, 0, data.Scan0, bytes);
            clone.UnlockBits(data);

            return clone;
        }
    }

不要忘记处理您的输入位图和此方法的返回结果。如果需要保存图像,请使用位图的“保存”指令。
clone.Save(@"C:\your\folder\path", ImageFormat.Png);

在这里,您可以找到更快地处理图像的方法。C#中的快速图像处理


你可以使用unsafe{}直接访问内存:var rgba = (byte*)data.Scan0 ... 或者创建一个RGBA结构(LayoutKind.Sequential)并执行:var rgba = (RGBA*)data.Scan0 ... 在任一情况下,rgba将是一个一维数组(byte或RGBA),您可以根据ImageLockFlags读取或写入它,无需复制到单独分配的数组中。 - dynamichael

0

照片上的色键应该假定为模拟输入。在现实世界中,精确值非常罕见。

你如何补偿这个问题?在你选择的绿色周围提供一个阈值,包括色调和色相。在此阈值内(包括)的任何颜色都应该被你选择的背景替换;透明可能是最好的选择。在第一个链接中,掩码输入和掩码输出参数可以实现这一点。预处理和后处理模糊参数试图使背景更加均匀,以减少编码噪声副作用,从而可以使用更窄的(首选)阈值。

为了提高性能,你可能需要编写一个像素着色器来将“绿色”转换为透明,但这是在你让它工作之后考虑的问题。


好的,但是像我说的,我在图像处理方面是个初学者。我也需要一些代码。很多人和软件都可以做到这一点。你拍照片、用网络摄像头或其他什么东西;有一个绿屏背景,然后它会去掉绿色并放上你想要的背景。我只想做到这一点,但我找不到好的方法。 - user2964381
你能给一个像素着色器的链接吗?这个与GPU有关吗? - daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接