Как разбить массив байтов изображения на несколько массивов байтов с помощью задач

Цель:

Я делаю простое приложение для обработки изображений, которое использует задачи для многопоточности. Пользователь выбирает изображение из папки, и оно отображается в PicBox. Когда он вводит количество потоков, тип цвета для изменения и значение (0-255) этого типа цвета (R, G, B) и нажимает кнопку редактирования, изображение выглядит следующим образом:

Процедура

  1. Преобразуется в байтовый массив
  2. Возвращается массив байтов, и точка поворота вычисляется в соответствии с номером потока.
  3. Создается список задач, и каждой задаче назначается начальный и конечный индекс большего массива байтов.
  4. В методе небольшая часть большего байта (от начала до конца индекса) сохраняется в меньший массив байтов.
  5. Затем метод преобразует небольшой массив байтов в изображение и возвращает изображение.

Проблема:

Все идет нормально, пока на 5-м шаге я не пытаюсь преобразовать массив байтов в изображение. В частности, это происходит, когда начальный индекс больше 0, что происходит во время выполнения 2-й задачи. Он работает ine для 1-й задачи. Может быть, он не может принять начальный индекс> 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;
    }

person Zextro    schedule 14.04.2020    source источник
comment
Известно ли вам, что задачи выполняются последовательно, а не параллельно? foreach (Task<Image> task in processImgTask) { task.Start(); taskPicbox.Image = task.Result; // <----- blocking } ?   -  person Jeroen van Langen    schedule 14.04.2020
comment
Я бы использовал Parallel.Foreach() для этого. Это связано с процессором.   -  person Jeroen van Langen    schedule 15.04.2020
comment
Итак, вы конвертируете меньшие массивы байтов в несколько изображений? Image x = (Bitmap)((new ImageConverter()).ConvertFrom(b)); откуда взялась переменная data? Я думаю, что шаг 5 неясен. Затем метод преобразует небольшой массив байтов в изображение и возвращает изображение. Если вы хотите создать изображения меньшего размера, вы должны передать задаче небольшое изображение.   -  person Jeroen van Langen    schedule 15.04.2020
comment
object data = new object[3] { Start, End, imgArray }; здесь imgArray — это большой массив исходного изображения, преобразованный в массив byte[]. В методе ImgProcess большой массив проходится от начального до конечного индекса, чтобы создать меньший byte[]. затем этот массив преобразуется в изображение и возвращается.   -  person Zextro    schedule 15.04.2020


Ответы (2)


См. этот ответ на как преобразовать массив байтов с необработанными данными пикселей в растровое изображение.

Я также настоятельно рекомендую использовать Parallel.For вместо задач. Задачи предназначены для асинхронного запуска кода, т. е. позволяют компьютеру выполнять действия, пока он ожидает данных. Parallel.For/Foreach предназначен для одновременного запуска кода, т. е. использования нескольких ядер для повышения производительности. Асинхронный и параллельный — это не одно и то же.

Я бы также рекомендовал начать с простой однопоточной реализации того, что вы пытаетесь сделать. Процессоры быстры, если вы не делаете что-то очень требовательное, накладные расходы могут быть значительными. И хотя распараллеливание может заставить ваш код работать в четыре раза быстрее (или независимо от того, сколько ядер ЦП у вас есть), довольно часто бывает так, что другие оптимизации могут повысить производительность в сто раз или более. И вы всегда можете распараллелить позже, если это необходимо.

Для изображений типичным способом параллелизации будет выполнение parallel.For над каждой строкой изображения.

person JonasH    schedule 14.04.2020
comment
спасибо за эту информацию. Я отредактировал код. Хотя я ценю предложение использовать распараллеливание, меня больше интересует использование каждой задачи для создания нового байта [] из исходного байта [] и последующего преобразования его в изображение. Кроме того, если я изменю это int Start=(int)indexes[0]; на int Start=0; мой код выполняется отлично, но не так, как хотелось бы - person Zextro; 15.04.2020
comment
Чего я пытаюсь добиться, так это преобразовать byte[] в изображение, а затем показать его в PicBox. Переждите 2 секунды, а затем продолжите выполнение следующего потока. Пока последняя задача не будет выполнена и PicBox не покажет изображение, созданное этой последней задачей. - person Zextro; 15.04.2020
comment
Если цель состоит в том, чтобы сделать какую-то анимацию, простым решением будет простой цикл в асинхронном методе. Обновите кадр любым способом, а затем запустите await Task.Delay(delayBetweenFrames) - person JonasH; 15.04.2020

В ответ на JonasH я сделал для вас пример. В этом примере используется тип Span. Это можно сделать непосредственно с массивом или ArraySegment<byte>.

Это пример того, как вы можете обрабатывать многопоточные строки:

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;
}
person Jeroen van Langen    schedule 14.04.2020