ucgitxsfiqucgitxsfiq
2021-11-22 07:25
采纳率: 100%
浏览 428

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;
    }
  • 好问题 提建议
  • 收藏

7条回答 默认 最新

  • 急速光粒 2021-11-22 11:59
    已采纳

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

    已采纳该答案
    评论
    解决 1 无用
    打赏 举报
    1人已打赏
  • 於黾 2021-11-22 08:43

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

    评论
    解决 无用
    打赏 举报
  • 文盲老顾 2021-11-22 09:06

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

    评论
    解决 无用
    打赏 举报
  • 申龙斌 2021-11-22 10:26

    猜你的程序的逻辑实在让人痛苦,最直接的提升效率的办法可能是把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];
    

    编辑记录

    评论
    解决 无用
    打赏 举报
  • xuzuning 2021-11-22 10:59

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

    评论
    解决 无用
    打赏 举报
  • 申龙斌 2021-11-22 14:27

    我修改了一下代码,大概提高了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;
    }
    

    编辑记录

    评论
    解决 无用
    打赏 举报
    1人已打赏
  • hxycsdn9159 2021-11-22 16:09

    看你的代码不是取黑白,就是每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的基本不需要时间。

    编辑记录

    评论
    解决 无用
    打赏 举报
    1人已打赏

相关推荐 更多相似问题