【问题标题】:How to split byte array of an image into multiple byte arrays using Tasks如何使用任务将图像的字节数组拆分为多个字节数组
【发布时间】:2020-04-14 20:51:57
【问题描述】:

目标:

我正在制作一个简单的图像处理应用程序,它使用 Tasks 进行多线程处理。用户从文件夹中选择图像并将其显示在 PicBox 中。当他输入线程数、要改变的colorType和该colorType(R,G,B)的Value(0-255),然后点击编辑按钮,图像为:

程序

  1. 转换为字节数组
  2. 返回字节数组,并根据线程号计算主元。
  3. 创建任务列表,并为每个任务分配一个较大字节数组的开始和结束索引
  4. 在该方法中,将较大字节的小部分(从开始到结束索引)保存到较小的字节数组中
  5. 该方法然后将小字节数组转换为Image并返回图像

问题

一切都很好,直到第五步我尝试将字节数组转换为图像。它特别发生在开始索引大于 0 时,即在第二个任务执行期间。它适用于第一个任务。难道是它不能接受开始索引> 0?

请查看以下代码:

代码

                List<Task<Image>> processImgTask = new List<Task<Image>>(threadCount);

                threadCount = Convert.ToInt32(threadCombox.SelectedItem);
                interval = imgArray.Length / threadCount;

                for (int i = 0; i < threadCount; i++)
                {
                    Start = End;
                    End += interval;
                    if (i == threadCount - 1)
                    {
                        End = imgArray.Length;
                    }
                    object data = new object[3] { Start, End, imgArray };
                    processImgTask.Add(new Task<Image>(ImgProcess, data));
                }
                //Task.WaitAll(processImgTask);

                //EDIT followed by comments and answer
                Parallel.ForEach(processImgTask, task =>
                {
                    task.Start();
                    taskPicbox.Image = task.Result;
                });



    private Image ImgProcess(object data)
    {
        object[] indexes = (object[])data;
        int Start=(int)indexes[0];
        int End = (int)indexes[1];            
        byte[] img = (byte[])indexes[2];

        List<byte> splitArray = new List<byte>();
        for (int i =Start;i<End;i++)
        {
            splitArray.Add(img[i]);
        }
        byte[] b = splitArray.ToArray();

        //Error occurs here when task 2 (thread 2) is being executed->
            Image x = (Bitmap)((new ImageConverter()).ConvertFrom(b));
        //System.ArgumentException: 'Parameter is not valid.'                    
        return x;
    }

【问题讨论】:

  • 您知道任务是串行执行而不是并行执行吗? foreach (Task&lt;Image&gt; task in processImgTask) { task.Start(); taskPicbox.Image = task.Result; // &lt;----- blocking }?
  • 我会为此使用Parallel.Foreach()。它受 CPU 限制。
  • 所以你正在将较小的字节数组转换成多个图像? Image x = (Bitmap)((new ImageConverter()).ConvertFrom(b)); data 变量从何而来?我认为第 5 步不清楚 “该方法然后将小字节数组转换为 Image 并返回图像” 如果要创建较小的图像,则应为任务提供小图像。
  • object data = new object[3] { Start, End, imgArray }; 这里的 imgArray 是原始图像转换为 byte[] 数组的大数组。在 ImgProcess 方法中,从开始到结束索引遍历大数组以创建较小的 byte[]。然后将此数组转换为图像并重新生成。

标签: c# multithreading image-processing task


【解决方案1】:

how to convert a byte array with raw pixeldata to an bitmap 上查看此答案。

我还强烈建议使用 Parallel.For 而不是任务。任务旨在异步运行代码,即允许计算机在等待数据的同时执行操作。 Parallel.For/Foreach 旨在同时运行代码,即使用多个内核以获得更好的性能。 Async and parallel is not the same.

我还建议您从一个简单的单线程实现开始,无论您尝试做什么。处理器快速,除非您正在做一些要求很高的事情,否则开销可能很大。虽然并行化可能使您的代码运行速度提高四倍(或者无论您拥有多少 CPU 内核),但其他优化通常可以将性能提高数百倍或更多。如果需要,您可以随时并行化。

对于图像,进行并行化的典型方法是对图像中的每一行进行并行处理。

【讨论】:

  • 感谢您提供的信息。我已经编辑了代码。虽然我很欣赏使用并行化的建议,但我更感兴趣的是使用每个任务从原始字节 [] 创建一个新字节 [],然后将其转换为图像。此外,如果我将这个 int Start=(int)indexes[0]; 更改为 int Start=0;我的代码执行完美,但没有达到预期
  • 我想要实现的是将 byte[] 转换为图像,然后在 PicBox 中显示。休眠 2 秒,然后继续执行下一个线程。直到最后一个任务执行完毕,并且 PicBox 显示的是最后一个任务所转换的图像
  • 如果目标是制作某种动画,简单的解决方案是异步方法中的简单循环。通过任何方法更新帧,然后运行 ​​await Task.Delay(delayBetweenFrames)
【解决方案2】:

作为对 JonasH 的回应,我为你做了一个例子。此示例使用 Span 类型。可以直接使用数组或ArraySegment&lt;byte&gt;

这是一个如何处理多线程行的示例:

private void Main()
{
    int width = 1024;
    int height = 768;
    int channelCount = 3;

    // create a buffer
    var data = new byte[width * height * channelCount];

    // fill with some data
    // example: 0, 1, 2, 3, 4, 5, 6
    PutSomeValuesInThatBuffer(data);

    // process the image and specify a line-edit callback
    // transforms to: 255, 254, 253, 252, 251, 250
    ProcessImage(data, width, height, channelCount, linePixels =>
    {
        int offset = 0;

        // we need to loop all pixel on this line
        while (offset < linePixels.Length)
        {
            // for RGB | R = channel[0], G = channel[1], B = channel[2], etc...

            // lets invert the colors, this loop isn't quite necessary
            // but it shows the use of channels  (R, G, B)  
            for (int i = 0; i < channelCount; i++)
            {
                linePixels[offset] = 255 - linePixels[offset];
                offset++;
            }
        }
    });
}

public delegate void LineProcessorAction(Span<byte> line);

// this is the process method which will split the data into lines 
// and process them over multiple threads.
private void ProcessImage(
    byte[] data,
    int width, int height, int channelCount,
    LineProcessorAction lineProcessor)
{
    var rowSizeInBytes = width * channelCount;

    Parallel.For(0, height, index => 
        lineProcessor(new Span<byte>(data, index * rowSizeInBytes, rowSizeInBytes)));
}

private static void PutSomeValuesInThatBuffer(byte[] data)
{
    for (int i = 0; i < data.Length; i++)
        data[i] = (byte)i;
}

【讨论】:

    猜你喜欢
    • 2023-03-15
    • 1970-01-01
    • 2010-09-06
    • 1970-01-01
    • 2012-03-23
    • 2018-08-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-30
    相关资源
    最近更新 更多