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

Posted

技术标签:

【中文标题】将 Rgb 图像转换为灰度 C# 代码时的性能问题【英文标题】:Performance issue while converting Rgb image to grayscale C# Code 【发布时间】:2016-12-19 21:03:23 【问题描述】:

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

所以我在网上搜索将 Rgb 图像转换为灰度图像的 C# 解决方案,我found this code。

这会执行 3 次操作以提高 tesseract 的准确性。

    调整图片大小 然后转换成灰度图,去除图像中的噪点

现在这个转换后的图像可以提供几乎 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) Resize() 方法内的RemoveNoise(Bitmap bmap) 函数逻辑,以消除对 for 循环的重复使用

但这并没有解决我的问题。

【问题讨论】:

为什么要调整图片大小? 如果我使用 newsize=oldSize*2 会提高 20% 的准确率 【参考方案1】:

众所周知,Bitmap 类的 GetPixel()SetPixel() 方法对于多次读/写来说速度很慢。在位图中访问和设置单个像素的一种更快的方法是先锁定它。

here 有一个很好的例子来说明如何做到这一点,有一个很好的类 LockedBitmap 来包裹陌生人 Marshaling 代码。

基本上它所做的是使用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() 那样占用太多时间,但它的时间仍然很重要,尤其是对于较大的图像。

【讨论】:

感谢@PC Luddite 指出这一点。让我探索一下,我一定会尽快回复你。【参考方案2】:

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

【讨论】:

Bystrom 给我一些时间来探索这个问题,我也会更新两个答案所用的时间。

以上是关于将 Rgb 图像转换为灰度 C# 代码时的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

如何将 RGB 图像转换为灰度但保留一种颜色? - 爪哇

Matlab图像处理彩色图像转换为灰度图像(初学必看)

无法使用 scikit 图像将 RGB 图像转换为灰度

Python如何将RGB图像转换为Pytho灰度图像?

在 DLIB 中将 RGB 图像转换为灰度图像

python库skimage 将针对灰度图像的滤波器用于RGB图像 逐通道滤波;转换为HSV图像滤波