我这有个方法要将图像转成指定格式的数组,只取黑白,目前转是可以转,但是效率非常慢,很吃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;
}
}
}
拿去试试,这是我试的效果:
十几K的基本不需要时间。