9

有没有一种有效的方法可以在 C# 中调整图像的对比度?

我看过这篇文章,它提倡进行逐像素操作。不快。

我已经在一些地方使用了颜色矩阵,并且发现它们很快。有没有办法使用它们来调整对比度?(注意:这家伙弄错了。)

我也在使用 EmguCV。我注意到 OpenCV(Emgu 包装)似乎具有对比功能- 有没有办法通过 Emgu 访问它?目前我在 Emgu 中所能做的就是标准化直方图,这确实改变了对比度,但我没有任何程度的控制。

有人有什么想法吗?

4

3 回答 3

30

如果该示例中的代码适合您,您可以使用 来大幅加速(按数量级)Bitmap.LockBits,它返回一个BitmapData允许通过指针访问位图像素数据的对象。网上和 StackOverflow 上有许多示例展示了如何使用 LockBits。

Bitmap.SetPixel()并且Bitmap.GetPixel()是人类已知的最慢的方法,它们都使用了Color类,这是人类已知的最慢的类。它们应该被命名Bitmap.GetPixelAndByGodYoullBeSorryYouDid()Bitmap.SetPixelWhileGettingCoffee作为对粗心的开发人员的警告。

更新: 如果您要修改该示例中的代码,请注意此块:

System.Drawing.Bitmap TempBitmap = Image;
System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width,
    TempBitmap.Height);
System.Drawing.Graphics NewGraphics = 
    System.Drawing.Graphics.FromImage(NewBitmap);
NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0, 
    TempBitmap.Width, TempBitmap.Height), 
    new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height),
    System.Drawing.GraphicsUnit.Pixel);
NewGraphics.Dispose();

可以用这个代替:

Bitmap NewBitmap = (Bitmap)Image.Clone();

更新 2: 这是 AdjustContrast 方法的 LockBits 版本(还有其他一些速度改进):

public static Bitmap AdjustContrast(Bitmap Image, float Value)
{
    Value = (100.0f + Value) / 100.0f;
    Value *= Value;
    Bitmap NewBitmap = (Bitmap)Image.Clone();
    BitmapData data = NewBitmap.LockBits(
        new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), 
        ImageLockMode.ReadWrite,
        NewBitmap.PixelFormat);
    int Height = NewBitmap.Height;
    int Width = NewBitmap.Width;

    unsafe
    {
        for (int y = 0; y < Height; ++y)
        {
            byte* row = (byte*)data.Scan0 + (y * data.Stride);
            int columnOffset = 0;
            for (int x = 0; x < Width; ++x)
            {
                byte B = row[columnOffset];
                byte G = row[columnOffset + 1];
                byte R = row[columnOffset + 2];

                float Red = R / 255.0f;
                float Green = G / 255.0f;
                float Blue = B / 255.0f;
                Red = (((Red - 0.5f) * Value) + 0.5f) * 255.0f;
                Green = (((Green - 0.5f) * Value) + 0.5f) * 255.0f;
                Blue = (((Blue - 0.5f) * Value) + 0.5f) * 255.0f;

                int iR = (int)Red;
                iR = iR > 255 ? 255 : iR;
                iR = iR < 0 ? 0 : iR;
                int iG = (int)Green;
                iG = iG > 255 ? 255 : iG;
                iG = iG < 0 ? 0 : iG;
                int iB = (int)Blue;
                iB = iB > 255 ? 255 : iB;
                iB = iB < 0 ? 0 : iB;

                row[columnOffset] = (byte)iB;
                row[columnOffset + 1] = (byte)iG;
                row[columnOffset + 2] = (byte)iR;

                columnOffset += 4;
            }
        }
    }

    NewBitmap.UnlockBits(data);

    return NewBitmap;
}

注意:此代码需要using System.Drawing.Imaging;在您的类中使用 using 语句,并且需要allow unsafe code选中项目的选项(在项目的 Build Properties 选项卡上)。

GetPixel 和 SetPixel 对于逐像素操作如此缓慢的原因之一是方法调用本身的开销开始成为一个巨大的因素。通常,我的代码示例将被视为重构的候选对象,因为您可以编写自己的使用现有 BitmapData 对象的 SetPixel 和 GetPixel 方法,但函数内部的数学处理时间相对于方法开销而言非常小每次通话。这就是为什么我也删除Clamp了原始方法中的调用。

另一种加快速度的方法是简单地使其成为“破坏性”函数,并修改传递的 Bitmap 参数,而不是制作副本并返回修改后的副本。

于 2010-06-25T02:29:13.710 回答
3

@MusiGenesis,

只是想指出,我将这种方法用于我一直在编写的图像编辑器。它运行良好,但有时此方法会在此行触发 AccessViolationException:

byte B = row[columnOffset];

我意识到这是因为 BitDepth 没有标准化,所以如果图像是 32 位颜色,我会收到此错误。所以我改变了这一行:

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

到:

BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); 

希望这会有所帮助,因为它似乎已经消除了我的问题。

谢谢你的帖子。

吊臂

于 2010-12-16T13:11:32.400 回答
0

我有点晚了,但是使用颜色矩阵实现,因为这些将针对此类转换进行优化,并且比自己操作像素要容易得多:http ://www.geekpedia.com/tutorial202_Using-the-ColorMatrix-in-Csharp .html

于 2011-02-22T14:27:43.127 回答