我正在尝试在C#中进行一些图像处理。 我想使用一些线程在我的图像中的几个区域上进行并行计算。 线程实际上是在Bitmap对象中获取和设置像素。两个线程绝对没有机会访问同一个像素,所以这不是问题所在。
问题是C#不允许我在同一个Bitmap对象上启动多个线程,即使我确定不会同时读取和修改相同的像素。
有没有办法避免C#引发此错误?或者在我的Bitmap对象上运行多个线程是不可能的?
谢谢,
皮埃尔奥利弗
答案 0 :(得分:16)
使用LockBits
(也比GetPixel
& SetPixel
快得多)您可以将图片的像素复制到缓冲区,运行并行线程它,然后复制缓冲区。
这是一个有效的例子。
void Test()
{
string inputFile = @"e:\temp\a.jpg";
string outputFile = @"e:\temp\b.jpg";
Bitmap bmp = Bitmap.FromFile(inputFile) as Bitmap;
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var data = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
var depth = Bitmap.GetPixelFormatSize(data.PixelFormat) / 8; //bytes per pixel
var buffer = new byte[data.Width * data.Height * depth];
//copy pixels to buffer
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
Parallel.Invoke(
() => {
//upper-left
Process(buffer, 0, 0, data.Width / 2, data.Height / 2, data.Width, depth);
},
() => {
//lower-right
Process(buffer, data.Width / 2, data.Height / 2, data.Width, data.Height, data.Width, depth);
}
);
//Copy the buffer back to image
Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
bmp.UnlockBits(data);
bmp.Save(outputFile, ImageFormat.Jpeg);
}
void Process(byte[] buffer, int x, int y, int endx, int endy, int width, int depth)
{
for (int i = x; i < endx; i++)
{
for (int j = y; j < endy; j++)
{
var offset = ((j * width) + i) * depth;
// Dummy work
// To grayscale (0.2126 R + 0.7152 G + 0.0722 B)
var b = 0.2126 * buffer[offset + 0] + 0.7152 * buffer[offset + 1] + 0.0722 * buffer[offset + 2];
buffer[offset + 0] = buffer[offset + 1] = buffer[offset + 2] = (byte)b;
}
}
}
输入图片:
输出图片:
一些粗略的测试:
在双核2.1GHz机器上将( 41兆像素,[7152x5368] )图像转换为灰度
答案 1 :(得分:0)
另一种解决方案是使用有关Bitmap
的信息(例如宽度,高度,步幅,缓冲区和像素格式)创建一个临时容器。一旦您需要并行访问Bitmap
,只需根据临时容器中的信息创建它即可。
为此,我们需要一个扩展方法来获取Bitmap
的缓冲区和步幅:
public static Tuple<IntPtr, int> ToBufferAndStride(this Bitmap bitmap)
{
BitmapData bitmapData = null;
try
{
bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, bitmap.PixelFormat);
return new Tuple<IntPtr, int>(bitmapData.Scan0, bitmapData.Stride);
}
finally
{
if (bitmapData != null)
bitmap.UnlockBits(bitmapData);
}
}
此扩展方法将在临时容器内使用:
public class BitmapContainer
{
public PixelFormat Format { get; }
public int Width { get; }
public int Height { get; }
public IntPtr Buffer { get; }
public int Stride { get; set; }
public BitmapContainer(Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException(nameof(bitmap));
Format = bitmap.PixelFormat;
Width = bitmap.Width;
Height = bitmap.Height;
var bufferAndStride = bitmap.ToBufferAndStride();
Buffer = bufferAndStride.Item1;
Stride = bufferAndStride.Item2;
}
public Bitmap ToBitmap()
{
return new Bitmap(Width, Height, Stride, Format, Buffer);
}
}
现在,您可以在并行执行的方法中使用BitmapContainer
:
BitmapContainer container = new BitmapContainer(bitmap);
Parallel.For(0, 10, i =>
{
Bitmap parallelBitmap = container.ToBitmap();
// ...
});