C#线程图像处理

时间:2011-12-28 00:50:30

标签: c# multithreading image-processing collections

                    for (int x = 0; x < blockCountX; x++)
                    {
                        for (int y = 0; y < blockCountY; y++)
                        {
                            //get blocks from image to new image and send to threaded processor
                            imageBlocks[x, y] = image.Clone(clipRectangle, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
                            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(ThreadedFromHeightMap));
                            t.Start(imageBlocks[x,y]);
                            clipRectangle.Offset(0, IMAGEBLOCKSIZE);
                        }
                        clipRectangle.Offset(IMAGEBLOCKSIZE, clipRectangle.Location.Y * -1);
                    }
                    break;
            }
        }
        private void ThreadedFromHeightMap(object Image)
        {
            Bitmap image = (Bitmap)Image;
            int width = image.Width;
            int height = image.Height;

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    map.Hexes.Add(new Point(x, y), new Hex(image.GetPixel(x, y).B));
                    //tempHexes.Enqueue(new Hex(image.GetPixel(x, y).B));
                }
            }
        }

我正在尝试从2048 x 2048 8bpp灰度高度图中获取像素数据,并构建具有相应高度值的十六进制地图。我将hexes存储在Dictionary集合中。总而言之,该系列中有大约400万个咒语。

为了有效地执行此操作,我将图像分成256 x 256个块并将该图像传递给另一个将解析它并将其添加到集合中的线程。这一切都是乱七八糟的。我现在有64个图像块,它们的左上角有(0,0),而不是一个图像的左上角为(0,0)。但是我使用像素位置作为字典的索引。当第二个线程尝试使用索引(0,0)添加另一个值时,这会崩溃。

如何缓解此问题?我已经考虑构建一个只有一个图像成员和一个块编号成员的类,并将其传递给线程,这样我就可以根据线程处理的块来调整像素位置,但这似乎不是最佳的。

(我意识到我使用的词典不是线程安全的。我已经解决了这个问题。)

3 个答案:

答案 0 :(得分:3)

我可以推荐几件事吗?

  1. 忘掉image.GetPixel(),它非常慢;直接使用位图数据,算法的性能将提高很多,您不需要运行并行线程来提高效率。请参阅MSDN:http://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.aspx

  2. 如果你坚持使用并行线程,请使用线程池,而不是产生64个线程。 (参见MSDN:http://msdn.microsoft.com/en-us/library/3dasc8as(v=vs.80).aspx

  3. 如果您坚持产生许多线程,请不要产生比CPU核心更多的线程。我不认为你的机器上有64个核心,对吗?

  4. 如果您坚持产生许多线程,您当然需要将每个图块的位置传递给线程,以便在重建整个图片时准确知道该图块的放置位置。这不是最优的,这是必要的。

答案 1 :(得分:2)

为什么你认为将大图像拆分成更小的块会更有效?大图像是否太大而无法放入系统内存? 400万像素x 8bpp(每像素1个字节)= 4兆字节。这是20年前的大量记忆。今天它发生了变化。

创建多个256x256子图像将需要将像素数据复制到内存中的新图像,以及每个新图像的图像头/描述符开销,以及每个扫描线的对齐填充。您的内存使用量将增加一倍以上,这可能会导致性能问题(虚拟交换)本身。

您还要为每个图像块启动一个新线程。分配线程非常昂贵,并且可能比您希望线程完成的工作花费更多时间。考虑至少使用ThreadPool.QueueUserWorkItem来使用已经可用的系统工作线程。使用.NET 4.0的Task类会更好,IMO。

忘记.GetPixel()。它比像素存储器访问慢一千倍。

如果要在多个CPU内核之间分配处理图像像素,请考虑将每个扫描线或一组扫描线处理到不同的任务或工作线程。

答案 2 :(得分:0)

  1. 我认为,在类中的图像数据中封装块位置并不是那么糟糕。我看不到任何其他选择。
  2. 作为优化,如果您对不安全操作没有任何限制,则可以从image.Scan0获取像素指针。
  3. 为每个块创建一个新图像并不是一个非常聪明的主意。将感兴趣的区域传递给线程。
  4. 如果您可以使用.NET Framework 4,请使用Parallel.ForEach进行此类使用。如果您无法使用它,则可以使用线程池。我猜你的电脑没有(2048 x 2048)/(256 x 256)= 64核CPU。
  5. 线程终止后更新全局十六进制映射可以显着提高性能。由于Dictionary不是线程安全的,因此在一个线程中锁定全局十六进制映射内部循环并不是一个好主意。