C#:优化图像像素操作
以图像阈值化为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | # very slow solution public static unsafe Bitmap ApplyThreshold(Bitmap scrBitmap, int lower_value, int upper_value) { Color newColor = Color.Red; Bitmap newBitmap = new Bitmap(scrBitmap.Width, scrBitmap.Height); //Locking the bitmap's bits allows you to iterate through it's color-data many times faster than using GetPixel, using unsafe code. lock (_imageLock) { var data = scrBitmap.LockBits( new Rectangle(0, 0, scrBitmap.Width, scrBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); var offset = data.Stride - scrBitmap.Width * 3; var p = ( byte *)data.Scan0.ToPointer(); for ( var i = 0; i < scrBitmap.Height; i++) { for ( var j = 0; j < scrBitmap.Width; j++, p += 3) { var v = ( int )(p[0] + p[1] + p[2]) / 3; var c = Color.FromArgb(p[2], p[1], p[0]); if (v > upper_value || v < lower_value) newBitmap.SetPixel(j, i, newColor); else newBitmap.SetPixel(j, i, c); } p += offset; } scrBitmap.UnlockBits(data); } return newBitmap; } # speed up using mutiple threads public static unsafe Bitmap ApplyThresholdParallel2(Bitmap scrBitmap, int lower_value, int upper_value) { //Locking the bitmap's bits allows you to iterate through it's color-data many times faster than using GetPixel, using unsafe code. lock (_imageLock) { var data = scrBitmap.LockBits( new Rectangle(0, 0, scrBitmap.Width, scrBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int bytesPerPixel = 3; int stride = data.Stride; var p = ( byte *)data.Scan0.ToPointer(); Task[] tasks = new Task[4]; for ( int i = 0; i < tasks.Length; i++) { int ii = i; tasks[i] = Task.Factory.StartNew(() => { int minY = ii < 2 ? 0 : data.Height / 2; int maxY = ii < 2 ? data.Height / 2 : data.Height; int minX = ii % 2 == 0 ? 0 : data.Width / 2; int maxX = ii % 2 == 0 ? data.Width / 2 : data.Width; for ( int y = minY; y < maxY; y++) { byte * row = p + (y * data.Stride); for ( int x = minX; x < maxX; x++) { int bIndex = x * bytesPerPixel; int gIndex = bIndex + 1; int rIndex = bIndex + 2; byte pixelR = row[rIndex]; byte pixelG = row[gIndex]; byte pixelB = row[bIndex]; int v = (pixelR + pixelG + pixelB) / 3; if (v > upper_value || v < lower_value) { row[rIndex] = 255; row[gIndex] = 0; row[bIndex] = 0; } } } }); } Task.WaitAll(tasks); scrBitmap.UnlockBits(data); } return scrBitmap; } # speed up using Parallel for public static unsafe Bitmap ApplyThresholdParallel(Bitmap scrBitmap, int lower_value, int upper_value) { var rect = new Rectangle(0, 0, scrBitmap.Width, scrBitmap.Height); Bitmap targetBmp = scrBitmap.Clone(rect, PixelFormat.Format24bppRgb); //Locking the bitmap's bits allows you to iterate through it's color-data many times faster than using GetPixel, using unsafe code. lock (_imageLock) { var data = targetBmp.LockBits(rect, ImageLockMode.ReadWrite, targetBmp.PixelFormat); int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(targetBmp.PixelFormat) / 8; int heightInPixels = data.Height; int widthInBytes = data.Width * bytesPerPixel; byte * PtrFirstPixel = ( byte *)data.Scan0; Parallel.For(0, heightInPixels, y => { byte * currentLine = PtrFirstPixel + (y * data.Stride); for ( int x = 0; x < widthInBytes; x = x + bytesPerPixel) { int b = currentLine[x]; int g = currentLine[x + 1]; int r = currentLine[x + 2]; var v = (b + g + r) / 3; if (v > upper_value || v < lower_value) { currentLine[x] = ( byte )0; currentLine[x + 1] = ( byte )0; currentLine[x + 2] = ( byte )255; } } }); targetBmp.UnlockBits(data); } return targetBmp; } |
参考:
Fast Pixel Operations in .NET (With and Without unsafe)
Why the use of GetPixel and SetPixel is so inefficient!
朱颜辞镜花辞树,敏捷开发靠得住!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理