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



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

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

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

原始 rgb565 原始图像:原始 rgb565 原始图像
图片框的屏幕截图(带有对角条纹):图片框的屏幕截图

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

我的测试图片可以在 Dropbox 上找到:测试图片:
测试图片


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 个"填充字节"。这就是我的对角线条纹的原因。

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

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

由于互联网上 99% 的示例代码假设他们的图像是 4 线宽的倍数(例如 320x240 或 680x480 的图像格式),它们都不会面临我的问题 - 但如果你像我一样向他们提供具有奇数行像素的 RGB565 图像,他们中的大多数人都会。

另外几行(标有"//***")足以添加"填充技巧"。 (下面的代码仅用于解释目的,在生产代码中,您可能需要添加一些优化)

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;

最新更新