使用AForge部分转换为灰度?

时间:2014-02-24 20:07:00

标签: c# aforge

使用AForge将位图转换为灰度非常简单:

public static Bitmap ConvertToGrayScale(this Bitmap me)
{
     if (me == null)
         return null;

     // first convert to a grey scale image
     var filterGreyScale = new Grayscale(0.2125, 0.7154, 0.0721);

     me = filterGreyScale.Apply(me);
     return me;
}

但我需要更棘手的事情:

想象一下,您希望将所有内容转换为灰度除外),以便在位图中间使用圆圈。换句话说:给定位图中间的圆圈应保持其原始颜色。

我们假设圆的半径是20px,我应该如何处理?

1 个答案:

答案 0 :(得分:3)

这可以使用MaskedFilter使用掩码来完成,该掩码定义了您描述的圆圈区域。正如文档所述

  

可以将掩码指定为.NET的托管位图,如UnmanagedImage或   作为字节数组。如果将mask指定为image,则它必须为8   bpp灰度图像。在所有情况下,掩模大小必须与大小相同   要处理的图像。

因此必须根据源图像的宽度和高度生成蒙版图像。

我没有编译下面的代码,但它应该让你顺利。如果圆圈始终位于同一点,则可以在方法外生成图像蒙版,以便每次应用滤镜时都不必重新生成。实际上,如果除了源图像没有任何变化,你可以在应用它的方法之外生成整个 MaskedFilter

public static Bitmap ConvertToGrayScale(this Bitmap me)
{
     if (me == null)
         return null;

     var radius = 20, x = me.Width / 2, y = me.Height / 2;

     using (Bitmap maskImage = new Bitmap(me.Width, me.Height, PixelFormat.Format8bppIndexed))
     {
        using (Graphics g = Graphics.FromImage(maskImage))
           using (Brush b = new SolidBrush(ColorTranslator.FromHtml("#00000000")))
              g.FillEllipse(b, x, y, radius, radius);

        var maskedFilter = new MaskedFilter(new Grayscale(0.2125, 0.7154, 0.0721), maskImage);

        return maskedFilter.Apply(me);
     }
}

修改

对此的解决方案比我预期的要复杂得多。主要问题是MaskedFilter不允许使用更改图像格式的过滤器,Grayscale过滤器会这样做(它将源更改为8bpp或16bpp图像)。

以下是我测试过的结果代码,并在ConvertToGrayScale方法的每个部分添加了注释,解释了它背后的逻辑。由于Merge滤镜不支持合并具有不同格式的两张图像,因此必须将图像的灰度部分转换回RGB。

static class MaskedImage
{
    public static void DrawCircle(byte[,] img, int x, int y, int radius, byte val)
    {
        int west = Math.Max(0, x - radius),
            east = Math.Min(x + radius, img.GetLength(1)),
            north = Math.Max(0, y - radius),
            south = Math.Min(y + radius, img.GetLength(0));

        for (int i = north; i < south; i++)
            for (int j = west; j < east; j++)
            {
                int dx = i - y;
                int dy = j - x;
                if (Math.Sqrt(dx * dx + dy * dy) < radius)
                    img[i, j] = val;
            }
    }

    public static void Initialize(byte[,] arr, byte val)
    {
        for (int i = 0; i < arr.GetLength(0); i++)
            for (int j = 0; j < arr.GetLength(1); j++)
                arr[i, j] = val;
    }

    public static void Invert(byte[,] arr)
    {
        for (int i = 0; i < arr.GetLength(0); i++)
            for (int j = 0; j < arr.GetLength(1); j++)
                arr[i, j] = (byte)~arr[i, j];
    }

    public static Bitmap ConvertToGrayScale(this Bitmap me)
    {
        if (me == null)
            return null;

        int radius = 20, x = me.Width / 2, y = me.Height / 2;
        // Generate a two-dimensional `byte` array that has the same size as the source image, which will be used as the mask.
        byte[,] mask = new byte[me.Height, me.Width];
        // Initialize all its elements to the value 0xFF (255 in decimal).
        Initialize(mask, 0xFF);
        // "Draw" a circle in the `byte` array setting the positions inside the circle with the value 0.
        DrawCircle(mask, x, y, radius, 0);

        var grayFilter = new Grayscale(0.2125, 0.7154, 0.0721);
        var rgbFilter = new GrayscaleToRGB();
        var maskFilter = new ApplyMask(mask);
        // Apply the `Grayscale` filter to everything outside the circle, convert the resulting image back to RGB
        Bitmap img = rgbFilter.Apply(grayFilter.Apply(maskFilter.Apply(me)));
        // Invert the mask
        Invert(mask);
        // Get only the cirle in color from the original image
        Bitmap circleImg = new ApplyMask(mask).Apply(me);
        // Merge both the grayscaled part of the image and the circle in color in a single one.
        return new Merge(img).Apply(circleImg);
    }
}