开发者

How to reshape an Array in c#

I have a 3D array of bytes in c# which I have read from a bitmap:

byte[w, h, 3]

What is the easiest and more performance-friendly way of reshaping this array into 2D (linear) form?

byte[w*h, 3]

In other words I want to keep number of channels (features) but in a linear shape (rather than a square shape)

Let me try to illustrate input and desired output:

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)|

note that arr[0, 0, 0] = r1, arr[0, 0, 1] = g1, arr[0, 0, 2] = b1, etc..

and output:

|(r1开发者_Python百科,g1,b1)    (r2,g2,b2)    (r3,g3,b3)    (r4,g4,b4)    (r5,g5,b5)    (r6,g6,b6) ...|


This seems to work fine, because the array is already in the right shape in memory:

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));

For those interested: As for what to do if you really want to transpose a 2D array into 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];
    }

This should take advantage of caching in the CPU. I have only performed limited testing on this -- it could still be slow. Try tweaking it if it is.
You can (somewhat non-trivially) extend this to a 3D array.


Buffer.BlockCopy will do it. At least, it works in this simple test.

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!");
    }
}

That said, I wouldn't use Buffer.BlockCopy in this way unless I was absolutely sure that there weren't odd cases with padding issues, etc. And although Buffer.BlockCopy is certainly faster than the equivalent explicit loop, it shouldn't materially affect the runtime of your program. Unless you're doing this conversion inside a piece of code that's called very, very often ... in which case you have bigger problems.

I would suggest writing the explicit loop.


As an alternative you might want to use System.Numerics.Tensors:

It makes reshaping very easy:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜