将Rgb图像转换为灰度c#代码时的性能问题

本文关键字:代码 性能 问题 灰度 Rgb 图像 转换 | 更新日期: 2023-09-27 18:02:08

我正在为Tesseract Ocr编写。net包装器,如果我使用灰度图像而不是rgb图像作为输入文件,那么结果非常好。

所以我在网上搜索c#解决方案,将Rgb图像转换为灰度图像,我找到了这个代码。

执行3个操作来提高tesseract的精度。

  1. 调整图片大小
  2. 然后将
  3. 转换为灰度图像,并去除图像
  4. 中的噪声

现在这个转换后的图像给出了几乎90%的准确结果。

//Resize
public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
{    
    Bitmap temp = (Bitmap)bmp;
    Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);
    double nWidthFactor = (double)temp.Width / (double)newWidth;
    double nHeightFactor = (double)temp.Height / (double)newHeight;
    double fx, fy, nx, ny;
    int cx, cy, fr_x, fr_y;
    Color color1 = new Color();
    Color color2 = new Color();
    Color color3 = new Color();
    Color color4 = new Color();
    byte nRed, nGreen, nBlue;
    byte bp1, bp2;
    for (int x = 0; x < bmap.Width; ++x)
    {
        for (int y = 0; y < bmap.Height; ++y)
        {
            fr_x = (int)Math.Floor(x * nWidthFactor);
            fr_y = (int)Math.Floor(y * nHeightFactor);
            cx = fr_x + 1;
            if (cx >= temp.Width)
                cx = fr_x;
            cy = fr_y + 1;
            if (cy >= temp.Height)
                cy = fr_y;
            fx = x * nWidthFactor - fr_x;
            fy = y * nHeightFactor - fr_y;
            nx = 1.0 - fx;
            ny = 1.0 - fy;
            color1 = temp.GetPixel(fr_x, fr_y);
            color2 = temp.GetPixel(cx, fr_y);
            color3 = temp.GetPixel(fr_x, cy);
            color4 = temp.GetPixel(cx, cy);
            // Blue
            bp1 = (byte)(nx * color1.B + fx * color2.B); 
            bp2 = (byte)(nx * color3.B + fx * color4.B);
            nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
            // Green
            bp1 = (byte)(nx * color1.G + fx * color2.G);    
            bp2 = (byte)(nx * color3.G + fx * color4.G);    
            nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
            // Red
            bp1 = (byte)(nx * color1.R + fx * color2.R);   
            bp2 = (byte)(nx * color3.R + fx * color4.R);
            nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
            bmap.SetPixel(x, y, System.Drawing.Color.FromArgb(255, nRed, nGreen, nBlue));
        }
    }
    //here i included the below to functions logic without the for loop to remove repetitive use of for loop but it did not work and taking the same time.
    bmap = SetGrayscale(bmap);
    bmap = RemoveNoise(bmap);
    return bmap;
}
//SetGrayscale
public Bitmap SetGrayscale(Bitmap img)
{
    Bitmap temp = (Bitmap)img;
    Bitmap bmap = (Bitmap)temp.Clone();
    Color c;
    for (int i = 0; i < bmap.Width; i++)
    {
        for (int j = 0; j < bmap.Height; j++)
        {
            c = bmap.GetPixel(i, j);
            byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
            bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
        }
    }
    return (Bitmap)bmap.Clone();
}
//RemoveNoise
public Bitmap RemoveNoise(Bitmap bmap)
{    
    for (var x = 0; x < bmap.Width; x++)
    {
        for (var y = 0; y < bmap.Height; y++)
        {
            var pixel = bmap.GetPixel(x, y);
            if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
                bmap.SetPixel(x, y, Color.Black);
        }
    }
    for (var x = 0; x < bmap.Width; x++)
    {
        for (var y = 0; y < bmap.Height; y++)
        {
            var pixel = bmap.GetPixel(x, y);
            if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
                bmap.SetPixel(x, y, Color.White);
        }
    }
    return bmap;
}

但问题是转换它要花很多时间

所以我包括了SetGrayscale(Bitmap bmap)RemoveNoise(Bitmap bmap)函数逻辑中的Resize()方法,以消除重复使用的for循环

将Rgb图像转换为灰度c#代码时的性能问题

Bitmap类的GetPixel()SetPixel()方法对于多次读/写是出了名的慢。在位图中访问和设置单个像素的一种更快的方法是先锁定它。

这里有一个很好的例子,关于如何做到这一点,用一个很好的类LockedBitmap来包装陌生的Marshal代码。

本质上,它所做的是使用Bitmap类中的LockBits()方法,传递一个矩形,用于您想要锁定的位图区域,然后将这些像素从其非托管内存位置复制到托管内存位置,以便于访问。

下面是一个关于如何使用SetGrayscale()方法的示例类的示例:

public Bitmap SetGrayscale(Bitmap img)
{
    LockedBitmap lockedBmp = new LockedBitmap(img.Clone());
    lockedBmp.LockBits(); // lock the bits for faster access
    Color c;
    for (int i = 0; i < lockedBmp.Width; i++)
    {
        for (int j = 0; j < lockedBmp.Height; j++)
        {
            c = lockedBmp.GetPixel(i, j);
            byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
            lockedBmp.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
        }
    }
    lockedBmp.UnlockBits(); // remember to release resources
    return lockedBmp.Bitmap; // return the bitmap (you don't need to clone it again, that's already been done).
}

这个包装类在位图处理上节省了我大量的时间。一旦你在你所有的方法中实现了这一点,最好只调用LockBits()一次,那么我确信你的应用程序的性能将大大提高。


我还看到你克隆了很多图像。这可能不会像SetPixel()/GetPixel()那样占用那么多时间,但它的时间仍然很重要,特别是对于较大的图像。

最简单的方法是使用DrawImage并传递合适的ColorMatrix将图像重新绘制到自身上。谷歌ColorMatrix和灰度,你会发现大量的例子,例如:http://www.codeproject.com/Articles/3772/ColorMatrix-Basics-Simple-Image-Color-Adjustment