在 C# 中的图像处理中使用 Marshal.Copy 和 LockBits
using Marshal.Copy and LockBits in image processing in C#
我正在尝试写一个灰度 code.I 基本上写了但是我不明白我怎么能在这段代码中使用 Marshal 或 lockbits 来提高速度。(我选择这种方法是因为使用指针更复杂对我来说)我写了一些东西,但我知道有太多或遗漏了 codes.I 知道我应该使用 lockbits 而不是 getpixel 来提高速度,但我 confused.It 对我来说很复杂,可能我正在使用它们在错误的地方。
编辑:我正在尝试将照片变成灰色 color.It 正在编译,但它没有变灰,我不明白代码有什么问题。
编辑:我知道下面有答案,但我想学习用 Lockbites 和 Marshal.Copy 函数编写这个。
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace dnm2709normalimg
{
class GrayFilter
{
public static Bitmap DoGray(Bitmap bmp)
{
for (int i = 0; i < bmp.Height; i++)
{
for (int j = 0; j < bmp.Width; j++)
{
int value = (bmp.GetPixel(j, i).R + bmp.GetPixel(j, i).G + bmp.GetPixel(j, i).B) / 3;
Color clr;
clr = Color.FromArgb(value, value, value);
BitmapData bmpData =bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),ImageLockMode.ReadOnly, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter += 3)
rgbValues[counter] = 255;
// Copy the RGB values back to the bitmap
Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
}
}
return bmp;
}
}
}
首先,您遍历每个像素,每次都处理整个图像。所以去掉外层循环。
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter += 3)
不正确。它假定每一行都包含精确数量的像素,没有剩余的字节。由于对齐,图像的每一行可能包含一些额外的字节,这就是为什么我们需要一个“步幅”而不仅仅是每个像素的宽度 + 字节数。
另外,在你的例子中,没有计算灰度值。这应该是每个颜色通道的某种平均,通常使用一些加权来更加强调绿色通道。
因此转换为灰度的代码应如下所示:
for(var y = 0; y < bmp.Height; y++){
var row = y * bmpData.Stride;
for(var x = 0; x < bmp.Width; x++){
var i = row + x * bytesPerPixel;
var grayscaleValue = rgbValues[i] * 0.11 + rgbValues[i + 1]*0.59 + rgbValues[i + 2] * 0.3;
rgbValues[i] = grayscaleValue ;
rgbValues[i + 1] = grayscaleValue ;
rgbValues[i + 2] = grayscaleValue ;
}
}
另请注意,这并不真正关心 rgbValues
是指针还是复制的数组。困难的部分不是使用 pointer/unsafe 代码,而是让索引正确。
如果您像@JonasH 在他的回答中那样访问原始数据,那么您必须知道输入 Bitmap
的实际 PixelFormat
是什么。他的回答适用于 24bpp RGB 图像。
适用于所有位图的更通用的答案:
private const float RLum = 0.299f;
private const float GLum = 0.587f;
private const float BLum = 0.114f;
public static Bitmap DoGray(Bitmap bmp)
{
using var result = new Bitmap(bmp.Width, bmp.Height);
using (Graphics g = Graphics.FromImage(result))
{
// Grayscale color matrix
var colorMatrix = new ColorMatrix(new float[][]
{
new float[] { RLum, RLum, RLum, 0, 0 },
new float[] { GLum, GLum, GLum, 0, 0 },
new float[] { BLum, BLum, BLum, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 0, 0, 1 }
});
using (var attrs = new ImageAttributes())
{
attrs.SetColorMatrix(colorMatrix);
g.DrawImage(bmp, new Rectangle(0, 0, result.Width, result.Height), 0, 0, result.Width, result.Height, GraphicsUnit.Pixel, attrs);
}
}
return result;
}
但是如果您不介意使用隐藏了解释实际像素格式的复杂性的 library (shameless self promotion alert) it offers you fast bitmap data manipulation,那么您可以像这样使用它:
public static Bitmap DoGray(Bitmap bmp)
{
using var result = new Bitmap(bmp.Width, bmp.Height);
using IReadableBitmapData src = bmp.GetReadableBitmapData();
using IWritableBitmapData dst = result.GetWritableBitmapData();
IReadableBitmapDataRow rowSrc = src.FirstRow;
IWritableBitmapDataRow rowDst = dst.FirstRow;
do
{
for (int x = 0; x < src.Width; x++)
rowDst[x] = rowSrc[x].ToGray();
} while (rowSrc.MoveNextRow() && rowDst.MoveNextRow());
return result;
}
但其实图书馆有一个ToGrayscale extension method that is even much faster because it uses parallel processing (the test cases are taken from here):
==[Performance Test Results]================================================
Test Time: 2,000 ms
Warming up: Yes
Test cases: 3
Calling GC.Collect: Yes
Forced CPU Affinity: No
Cases are sorted by fulfilled iterations (the most first)
--------------------------------------------------
1. ImageExtensions.ToGrayscale: 4,549 iterations in 2,000.20 ms. Adjusted for 2,000 ms: 4,548.55
2. Sequential processing: 1,651 iterations in 2,001.54 ms. Adjusted for 2,000 ms: 1,649.73 (-2,898.82 / 36.27 %)
3. Graphics.DrawImage(..., ImageAttributes): 964 iterations in 2,000.17 ms. Adjusted for 2,000 ms: 963.92 (-3,584.63 / 21.19 %)
更新: 以上所有三个选项 return 一个新的 Bitmap
32bpp ARGB 像素格式。要就地制作位图灰度,您可以使用上述库中的 MakeGrayscale
扩展方法。
第二个例子可以很容易地重写以修改原来的Bitmap
,只需通过GetReadWriteBitmapData
扩展方法获得一个IReadWriteBitmapData
。 link 实际上包含一个关于如何就地制作 Bitmap
灰度的示例。
我正在尝试写一个灰度 code.I 基本上写了但是我不明白我怎么能在这段代码中使用 Marshal 或 lockbits 来提高速度。(我选择这种方法是因为使用指针更复杂对我来说)我写了一些东西,但我知道有太多或遗漏了 codes.I 知道我应该使用 lockbits 而不是 getpixel 来提高速度,但我 confused.It 对我来说很复杂,可能我正在使用它们在错误的地方。
编辑:我正在尝试将照片变成灰色 color.It 正在编译,但它没有变灰,我不明白代码有什么问题。
编辑:我知道下面有答案,但我想学习用 Lockbites 和 Marshal.Copy 函数编写这个。
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace dnm2709normalimg
{
class GrayFilter
{
public static Bitmap DoGray(Bitmap bmp)
{
for (int i = 0; i < bmp.Height; i++)
{
for (int j = 0; j < bmp.Width; j++)
{
int value = (bmp.GetPixel(j, i).R + bmp.GetPixel(j, i).G + bmp.GetPixel(j, i).B) / 3;
Color clr;
clr = Color.FromArgb(value, value, value);
BitmapData bmpData =bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),ImageLockMode.ReadOnly, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter += 3)
rgbValues[counter] = 255;
// Copy the RGB values back to the bitmap
Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
}
}
return bmp;
}
}
}
首先,您遍历每个像素,每次都处理整个图像。所以去掉外层循环。
// Set every third value to 255. A 24bpp bitmap will look red.
for (int counter = 2; counter < rgbValues.Length; counter += 3)
不正确。它假定每一行都包含精确数量的像素,没有剩余的字节。由于对齐,图像的每一行可能包含一些额外的字节,这就是为什么我们需要一个“步幅”而不仅仅是每个像素的宽度 + 字节数。
另外,在你的例子中,没有计算灰度值。这应该是每个颜色通道的某种平均,通常使用一些加权来更加强调绿色通道。
因此转换为灰度的代码应如下所示:
for(var y = 0; y < bmp.Height; y++){
var row = y * bmpData.Stride;
for(var x = 0; x < bmp.Width; x++){
var i = row + x * bytesPerPixel;
var grayscaleValue = rgbValues[i] * 0.11 + rgbValues[i + 1]*0.59 + rgbValues[i + 2] * 0.3;
rgbValues[i] = grayscaleValue ;
rgbValues[i + 1] = grayscaleValue ;
rgbValues[i + 2] = grayscaleValue ;
}
}
另请注意,这并不真正关心 rgbValues
是指针还是复制的数组。困难的部分不是使用 pointer/unsafe 代码,而是让索引正确。
如果您像@JonasH 在他的回答中那样访问原始数据,那么您必须知道输入 Bitmap
的实际 PixelFormat
是什么。他的回答适用于 24bpp RGB 图像。
适用于所有位图的更通用的答案:
private const float RLum = 0.299f;
private const float GLum = 0.587f;
private const float BLum = 0.114f;
public static Bitmap DoGray(Bitmap bmp)
{
using var result = new Bitmap(bmp.Width, bmp.Height);
using (Graphics g = Graphics.FromImage(result))
{
// Grayscale color matrix
var colorMatrix = new ColorMatrix(new float[][]
{
new float[] { RLum, RLum, RLum, 0, 0 },
new float[] { GLum, GLum, GLum, 0, 0 },
new float[] { BLum, BLum, BLum, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 0, 0, 1 }
});
using (var attrs = new ImageAttributes())
{
attrs.SetColorMatrix(colorMatrix);
g.DrawImage(bmp, new Rectangle(0, 0, result.Width, result.Height), 0, 0, result.Width, result.Height, GraphicsUnit.Pixel, attrs);
}
}
return result;
}
但是如果您不介意使用隐藏了解释实际像素格式的复杂性的 library (shameless self promotion alert) it offers you fast bitmap data manipulation,那么您可以像这样使用它:
public static Bitmap DoGray(Bitmap bmp)
{
using var result = new Bitmap(bmp.Width, bmp.Height);
using IReadableBitmapData src = bmp.GetReadableBitmapData();
using IWritableBitmapData dst = result.GetWritableBitmapData();
IReadableBitmapDataRow rowSrc = src.FirstRow;
IWritableBitmapDataRow rowDst = dst.FirstRow;
do
{
for (int x = 0; x < src.Width; x++)
rowDst[x] = rowSrc[x].ToGray();
} while (rowSrc.MoveNextRow() && rowDst.MoveNextRow());
return result;
}
但其实图书馆有一个ToGrayscale extension method that is even much faster because it uses parallel processing (the test cases are taken from here):
==[Performance Test Results]================================================
Test Time: 2,000 ms
Warming up: Yes
Test cases: 3
Calling GC.Collect: Yes
Forced CPU Affinity: No
Cases are sorted by fulfilled iterations (the most first)
--------------------------------------------------
1. ImageExtensions.ToGrayscale: 4,549 iterations in 2,000.20 ms. Adjusted for 2,000 ms: 4,548.55
2. Sequential processing: 1,651 iterations in 2,001.54 ms. Adjusted for 2,000 ms: 1,649.73 (-2,898.82 / 36.27 %)
3. Graphics.DrawImage(..., ImageAttributes): 964 iterations in 2,000.17 ms. Adjusted for 2,000 ms: 963.92 (-3,584.63 / 21.19 %)
更新: 以上所有三个选项 return 一个新的 Bitmap
32bpp ARGB 像素格式。要就地制作位图灰度,您可以使用上述库中的 MakeGrayscale
扩展方法。
第二个例子可以很容易地重写以修改原来的Bitmap
,只需通过GetReadWriteBitmapData
扩展方法获得一个IReadWriteBitmapData
。 link 实际上包含一个关于如何就地制作 Bitmap
灰度的示例。