C#图像点阵处理性能问题

我这有个方法要将图像转成指定格式的数组,只取黑白,目前转是可以转,但是效率非常慢,很吃CPU,想请大拿帮我看看,有没有什么可以高效转换的方法,12K的图片,转换要300毫秒,想控制在50毫秒内。
public byte[] GetGrayByte(Bitmap srcBmp)
{
DateTime dt = DateTime.Now;
int iHeight = srcBmp.Height;
int iWidth = srcBmp.Width;

        int num = srcBmp.Height / 8;
        if (srcBmp.Height % 8 > 0)
        {
            num++;
        }

        string text2 = "";
        Rectangle rect = new Rectangle(0, 0, srcBmp.Width, srcBmp.Height);
        BitmapData srcBmpData = srcBmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

        byte[] grayValues = new byte[(rect.Width * rect.Height) / 8];  //定义转化为灰度后需要存储的数组

        int stride = srcBmpData.Stride;  // 扫描线的宽度,比实际图片要大
        int offset = stride - iWidth * 3;  // 显示宽度与扫描线宽度的间隙
        IntPtr ptr = srcBmpData.Scan0;   // 获取bmpData的内存起始位置的指针
        int scanBytesLength = stride * iHeight;  // 用stride宽度,表示这是内存区域的大小

        byte[] rgbValues = new byte[scanBytesLength];  // 为目标数组分配内存
        Marshal.Copy(ptr, rgbValues, 0, scanBytesLength);  // 将图像数据拷贝到rgbValues中

        byte blue, green, red, YUI;

        int iLenght = 0;
        for (int i = 0; i < iWidth; i++)
        {
            for (int j = 0; j < num; j++)
            {
                text2 = "";
                for (int k = 0; k < 8; k++)
                {
                    int num2 = k + j * 8;
                    if (num2 >= iHeight)
                    {
                        text2 += "1";
                        continue;
                    }
                    int iPosition = num2 * stride + i * 3;
                    //blue = rgbValues[iPosition];
                    //green = rgbValues[iPosition + 1];
                    red = rgbValues[iPosition + 2];
                    text2 = red == 0 ? (text2 + "0") : (text2 + "1");
                    //YUI = (byte)(0.229 * red + 0.587 * green + 0.144 * blue);
                    //text2 = YUI > 127 ? (text2 + "1") : (text2 + "0");
                }
                grayValues[iLenght] = Convert.ToByte(text2, 2);
                iLenght++;
            }
        }

        //解锁位图
        srcBmp.UnlockBits(srcBmpData);  //读取完元信息,这里就不用了,一定要记得解锁,否则会报错
        srcBmp.Dispose();
        dTemplateCodingTime = (DateTime.Now - dt).TotalMilliseconds;
        return grayValues;
    }

感觉你对图像的多波段位的理解有一些问题,其实只要处理rgb就好了,你现在的逻辑又复杂且效率很低,灰度的话效率应该很高的。

说实话你这逻辑我是没怎么看懂,关键算法一句注释都没有
不过可以提几个建议:
1.想办法把3重循环压缩为2重,图像只有宽和高,完全不明白你第三层循环到底在干什么
2.字符串的拼接想办法用位运算代替,感觉你折腾半天折腾了个寂寞,最终不还是变byte了
3.使用using关键字声明图像变量,不要显性的调用dispose方法,一来万一报错进了catch,你dispose得不到执行,二来每次建立图像都要立即销毁会消耗时间

你这个指定格式是个什么格式?算法说明一下,不要直接拿自己的代码让人猜你的算法

猜你的程序的逻辑实在让人痛苦,最直接的提升效率的办法可能是把k循环去掉,这里用string应该是效率最低的,可以用整数的位运算来解决。
最前面的一行逻辑可能这样写更直接:

int num = (srcBmp.Height + 7) / 8;
            
//int num = srcBmp.Height / 8;
//if (srcBmp.Height % 8 > 0)
//{
//    num++;
//}

另外,还发现一个bug,grayValues数组的空间声明可能不够,应该改成这样:

byte[] grayValues = new byte[rect.Width * num];

你这是内存法
换用指针法可提升一倍
都是抄来的代码,多试试就知道哪个好用了
我说最好用的是AForge.NET,简洁高效,也要你试试才知道

我修改了一下代码,大概提高了4倍左右的效率。还修改了几处地方:
1)变量名尽量有含义,newHeight比num要好
int newHeight = (bmp.Height + 7) / 8;
2)grayValues申请的内存空间有bug
byte[] grayValues = new byte[width * newHeight];
3)许多匈牙利命名法iWidth, iHeight不适合C#编程规范
4)错误的拼写Lenght -> Length
5)bitmap并没有写入操作,用ImageLockMode.ReadOnly即可
6)主要的优化在k循环,用位操作即可,每读入一个数设置相应的二进制位,然后左移,取满8位即可;另外先算好起始位置,以后每次增加stride即可,可以减少很多重复的整数运算
7)没有对num2进行优化,感觉这个应该在图片的最底部的一行像素才会发生作用,多了许多无谓的判断
8)bmp遵循谁初始化,谁释放的原则,函数体里并没有初始化bmp,应该放在函数外释放
9)最好不要亲自调用Dispose()方法,而改用using 语法更好
10)为了方便测试,我增加了Main()方法,自己随便找了一张图片进行了测试,更严谨的话,需要弄一个单元测试用例

如果感觉有用,请采纳。

static void Main(string[] args)
{
    using (Bitmap bmp = new Bitmap(Bitmap.FromFile("csdn-question.png")))
    {
        DateTime time1 = DateTime.Now;
        var bs = GetGrayByte(bmp);
        var elapsedTime = (DateTime.Now - time1).TotalMilliseconds;
        Console.WriteLine(elapsedTime);
    }
    Console.ReadLine();
}


public static byte[] GetGrayByte(Bitmap bmp)
{
    int height = bmp.Height;
    int width = bmp.Width;

    int newHeight = (bmp.Height + 7) / 8;
            
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData srcBmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

    byte[] grayValues = new byte[width * newHeight];  //定义转化为灰度后需要存储的数组

    int stride = srcBmpData.Stride;  // 扫描线的宽度,比实际图片要大
    int scanBytesLength = stride * height;  // 用stride宽度,表示这是内存区域的大小
    byte[] rgbValues = new byte[scanBytesLength];  
    Marshal.Copy(srcBmpData.Scan0, rgbValues, 0, scanBytesLength);  

    int count = 0;
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < newHeight; j++)
        {
            int pos = j * 8 * stride + i * 3;
            int color = 0;
            for (int k = 0; k < 8; k++)
            {
                color <<= 1;
                int num2 = k + j * 8; // 这个地方应该还可以优化,可能只在图片的边缘才会有这种情况
                if (num2 >= height)
                {
                    color |= 1;
                    continue;
                }
                byte red = rgbValues[pos + 2];
                if (red != 0) color |= 1;
                pos += stride;
            }
            grayValues[count++] = (byte)color;
        }
    }

    //解锁位图
    bmp.UnlockBits(srcBmpData);  //读取完元信息,这里就不用了,一定要记得解锁,否则会报错
    //bmp.Dispose(); //谁初始化的bmp,谁应该负责释放bmp,不应该在这里,应该在函数外面释放
    return grayValues;
}

看你的代码不是取黑白,就是每8行为一个单位取每个像素的红色值判断是否为0然后组成一个新的字节放到数组返回。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch watcher = new Stopwatch();
            Bitmap bitmap = new Bitmap(Image.FromFile("1.png"));
            double fileSize = File.ReadAllBytes("1.png").Length / 1000d;
            watcher.Start();
            byte[] grayData = GetGrayByte(bitmap);
            Debug.WriteLine("耗时:" + watcher.ElapsedMilliseconds + "ms  图像大小:" + fileSize + "KB" );
            watcher.Stop();
        }

        public static byte[] GetGrayByte(Bitmap srcBmp)
        {
            // 校验参数
            if (srcBmp == null) throw new ArgumentNullException("参数:'srcBmp' 不能为空!");

            // 获取水平以及垂直方向需要扫描的次数
            int horizontalScanNum = srcBmp.Width;
            int veticalScanNum = srcBmp.Height >> 3;
            if (srcBmp.Height % 8 != 0) veticalScanNum++;

            // 获取位图的bgr数据到数组
            Rectangle rect = new Rectangle(0, 0, srcBmp.Width, srcBmp.Height);
            BitmapData srcBmpData = srcBmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            int bgrValuesLength = srcBmpData.Stride * srcBmpData.Height;
            byte[] bgrValues = new byte[bgrValuesLength];
            Marshal.Copy(srcBmpData.Scan0, bgrValues, 0, bgrValuesLength);

            // 补齐bgr数组,使其正好被8整除,并且初始化所有值为1
            byte[] alignBgrValues = new byte[srcBmpData.Stride * (veticalScanNum << 3)];
            for (int i = 0; i < alignBgrValues.Length; i++)
            {
                alignBgrValues[i] = 1;
            }
            Array.Copy(bgrValues, alignBgrValues, bgrValues.Length);


            // 遍历bgr数据只取r分量构建需要返回的数组
            byte[] retData = new byte[horizontalScanNum * veticalScanNum];
            int index = 0;

            for (int i = 0; i < horizontalScanNum; i++)
            {
                for (int j = 0; j < veticalScanNum; j++)
                {
                    // 获取需要的返回数据
                    retData[index] = ClampToByte(ClampToBit(alignBgrValues[srcBmpData.Stride * j * 8 + i * 3 + 2]) << 7
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 1) + i * 3 + 2]) << 6
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 2) + i * 3 + 2]) << 5
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 3) + i * 3 + 2]) << 4
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 4) + i * 3 + 2]) << 3
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 5) + i * 3 + 2]) << 2
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 6) + i * 3 + 2]) << 1
                        | ClampToBit(alignBgrValues[srcBmpData.Stride * (j * 8 + 7) + i * 3 + 2])
                        );
                    index++;
                }
            }
            srcBmp.UnlockBits(srcBmpData);
            return retData;
        }

        // 钳制到字节 0~255
        public static byte ClampToByte(int b)
        {
            if (b > 255) return 255;
            if (b < 0) return 0;
            return (byte)b;
        }

        // 钳制到位 0~1
        public static byte ClampToBit(int b)
        {
            if (b > 0) return 1;
            return 0;
        }
    }
}



拿去试试,这是我试的效果:

img

十几K的基本不需要时间。