Как изменить массив в C # - PullRequest
11 голосов
/ 29 июня 2011

У меня есть трехмерный массив байтов в c #, который я прочитал из растрового изображения:

byte[w, h, 3]

Какой самый простой и более удобный способ преобразования этого массива в 2D (линейную) форму?

byte[w*h, 3]

Другими словами, я хочу сохранить количество каналов (объектов), но в линейной форме (а не в форме квадрата)

Позвольте мне попытаться проиллюстрировать ввод и желаемый вывод:

input:

|(r1,g1,b1)    (r2,g2,b2)    (r3,g3,b3)|
|(r4,g4,b4)    (r5,g5,b5)    (r6,g6,b6)|
|(r7,g7,b7)    (r8,g8,b8)    (r9,g9,b9)|

обратите внимание, что arr [0, 0, 0] = r1, arr [0, 0, 1] = g1, arr [0, 0, 2] =b1 и т. д.

и вывод:

|(r1,g1,b1)    (r2,g2,b2)    (r3,g3,b3)    (r4,g4,b4)    (r5,g5,b5)    (r6,g6,b6) ...|

Ответы [ 3 ]

9 голосов
/ 29 июня 2011

Кажется, это работает нормально, потому что массив уже находится в нужной форме в памяти :

var a = new byte[2,  2, 2] { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } };
var b = new byte[2 * 2, 2];

//sizeof(byte) is obviously 1 here, but I put it there for documentation
Buffer.BlockCopy(a, 0, b, 0, a.Length * sizeof(byte));

Для тех, кто заинтересован: Что делать, если вы действительно хотите транспонировать 2D-массив в 1D:

byte[,] a = {
    {1, 2},
    {3, 4},
    {5, 6},
};
var b = new byte[a.GetLength(1) * a.GetLength(0)]; //Transpose

const int R_STRIDE1 = 8; //Tune this for your CPU
const int C_STRIDE1 = 8; //Tune this for your CPU

//You should hoist the calls to GetLength() out of the loop unlike what I do here
for (int r1 = 0; r1 < a.GetLength(0); r1 += R_STRIDE1)
for (int c1 = 0; c1 < a.GetLength(1); c1 += C_STRIDE1)
    for (int r2 = 0; r2 < R_STRIDE1; r2++)
    for (int c2 = 0; c2 < C_STRIDE1; c2++)
    {
        var r = r1 + r2;
        var c = c1 + c2;
        if (r < a.GetLength(0) && c < a.GetLength(1))
            b[c * a.GetLength(0) + r] = a[r, c];
    }

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

2 голосов
/ 29 июня 2011

Buffer.BlockCopy сделает это.По крайней мере, это работает в этом простом тесте.

byte[, ,] src = new byte[10, 10, 3];
byte[,] dest = new byte[100, 3];

List<byte> srcList = new List<byte>();
Random rnd = new Random();
for (int i = 0; i < 10; ++i)
{
    for (int j = 0; j < 10; ++j)
    {
        for (int k = 0; k < 3; ++k)
        {
            byte b = (byte)rnd.Next();
            src[i, j, k] = b;
            srcList.Add(b);
        }
    }
}

Buffer.BlockCopy(src, 0, dest, 0, 300);

List<byte> destList = new List<byte>();
for (int i = 0; i < 100; ++i)
{
    for (int j = 0; j < 3; ++j)
    {
        destList.Add(dest[i, j]);
    }
}

// See if they're in the same order
for (int i = 0; i < srcList.Count; ++i)
{
    Console.WriteLine("{0,3:N0} - {1,3:N0}", srcList[i], destList[i]);
    if (srcList[i] != destList[i])
    {
        Console.WriteLine("ERROR!");
    }
}

Тем не менее, я бы не использовал Buffer.BlockCopy таким образом, если бы я не был абсолютно уверен , что не было ничего странногослучаи с проблемами заполнения и т. д. И хотя Buffer.BlockCopy, безусловно, быстрее, чем эквивалентный явный цикл, это не должно существенно повлиять на время выполнения вашей программы.Если вы не выполняете это преобразование внутри фрагмента кода, который вызывается очень, очень часто ... в этом случае у вас возникают большие проблемы.

Я бы предложил написать явный цикл.

0 голосов
/ 08 марта 2019

В качестве альтернативы вы можете использовать System.Numerics.Tensors :

Это делает изменение формы очень простым:

using System.Numerics.Tensors;
..
var src = new byte[3, w, h]
var dst = ArrayTensorExtensions
    .ToTensor(src)
    .Reshape(new[] { 3, w * h });
...