C#:如何将 RAW 图像(格式:rgb565)加载到位图中?

C#: How to load a RAW image (format: rgb565) into a Bitmap?

我的目标:
在我的 Windows Forms 程序中显示一张以 rgb565 原始格式出现的图像。 (题外话:数据来源于一个OV7670摄像头模块)

我的做法:
首先,我创建一个空位图。接下来,我将图像数据(原始格式:rgb565)插入空位图的有效负载部分。最后,我在 PictureBox 中显示修改后的位图。

我的问题:
一切正常,但测试图像显示为对角条纹而不是垂直条纹(参见下面的链接)。

原始 rgb565 原始图像:Original rgb565 raw image
PictureBox 的屏幕截图(斜条纹):Screenshot of PictureBox

我确实设法通过提取 R、G、B 并使用 SetPixel() 来显示图像,但这对我来说太慢了。这就是为什么我想获得下面的代码以正确的方式显示图像。

我的测试图像可以在此处的保管箱中找到:
测试图像:Testimage


MemoryStream memoryStream = new MemoryStream(1000000);

    // Read raw image into byte array
    string imgpath = "rgb565_LSB-first_313x240.raw";
    FileStream fs = new FileStream(imgpath, FileMode.Open);
    fs.CopyTo(memoryStream);
    Byte[] buffer = memoryStream.ToArray();

    // Create empty Bitmep and inject byte arrays data into bitmap's data area
    Bitmap bmp = new Bitmap(313, 240, PixelFormat.Format16bppRgb565);

    // Lock the bitmap's bits.  
    Rectangle rect = new Rectangle(0, 0, 313, 240);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite,
                                      PixelFormat.Format16bppRgb565);
    IntPtr ptrToFirstPixel = bmpData.Scan0;

    // Inject the rgb565 data (stored in the buffer array)
    Marshal.Copy(buffer, 0, ptrToFirstPixel, buffer.Length);
    bmp.UnlockBits(bmpData);

    // Diplay Bitmap in my PictureBox
    pbImage.Image = bmp;

预期结果:竖条纹:-)
实际结果:斜条纹 :-(

大海捞针10个小时,终于找到了 原因,这绝对不是陈词滥调(至少对我而言不是)。

来了: 位图规范要求,将行大小填充为 4 字节的倍数! https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png

因为我的 colorbar-testimage 有 313 像素的线宽,并且因为每个像素都被编码为 rgb565,所以我每行有 626 个字节。

但是 626 不是 4 的倍数。这就是为什么我应该在每行末尾再添加 2 "padding bytes"。这就是我斜条纹的原因。

添加这 2 个填充字节 (0x00 0x00) 后,我得到了一个位图图像,其中 header 告诉您:该图像的宽度为 313 像素,但实际图像数据包含 314每行像素 - 这有点奇怪,但这是由规范定义的。

一旦我修改了我的位图以符合规范的这一要求,斜条纹就消失了,预期的垂直条纹变成了黑暗。

由于 Internet 上 99% 的示例代码都假定其图像的线宽是 4 的倍数(例如 320x240 或 680x480 的图像格式),因此它们都不会遇到我的问题 - 但如果您提供给它们,它们中的大多数都会遇到rgb565 图像具有奇数 line-pixels,因为我必须这样做。

几行(标有“// ***”)足以添加 "padding trick"。 (以下代码仅用于解释目的,在生产代码中您可能需要添加一些优化)

MemoryStream memoryStream = new MemoryStream(1000000);

// Read raw image into byte array
string imgpath = "rgb565_LSB-first_313x240.raw";
FileStream fs = new FileStream(imgpath, FileMode.Open);
fs.CopyTo(memoryStream);
Byte[] buffer = memoryStream.ToArray();

// Create empty Bitmep and inject byte arrays data into bitmap's data area
Bitmap bmp = new Bitmap(313, 240, PixelFormat.Format16bppRgb565);
// Lock the bitmap's bits.  
Rectangle rect = new Rectangle(0, 0, 313, 240);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format16bppRgb565);
IntPtr ptrToFirstPixel = bmpData.Scan0;


// *** Attention:  Bitmap specification requires, to pad row size to a multiple of 4 Bytes 
// *** See:        https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png
// *** Solution:   Copy buffer[] to buffer2[] and pay attention to padding (!!) at the end of each row
Byte[] buffer2 = new Byte[240 * bmpData.Stride];
for (int y = 0; y < 240; y++)
{
    Buffer.BlockCopy(buffer, y * 313 * 2, buffer2, y * bmpData.Stride, 313 * 2);
}

Marshal.Copy(buffer2, 0, ptrToFirstPixel, buffer2.Length);  // *** Use padded buffer2 instead of buffer1
bmp.UnlockBits(bmpData);
// Diplay Bitmap in my PictureBox
pbImage.Image = bmp;