【问题标题】:C# unable to display RAW16 grayscale image with correct color mappingC# 无法使用正确的颜色映射显示 RAW16 灰度图像
【发布时间】:2019-10-31 18:17:58
【问题描述】:

修改此链接中提供的代码:

Original code

这是我写的:

    private void btnLoad_Click(object sender, EventArgs e)
    {
        if (System.IO.File.Exists(txtPicture.Text))
        {
            byte[] _data = System.IO.File.ReadAllBytes(txtPicture.Text);

            var _rgbData = Convert16BitGrayScaleToRgb16(_data, 160, 120);
            var _bmp = CreateBitmapFromBytes(_rgbData, 160, 120);

            pbFrame.Image = _bmp;
        }
    }

    private static void Convert16bitGSToRGB(UInt16 color, out byte red, out byte green, out byte blue)
    {
        red = (byte)(color & 0x31);
        green = (byte)((color & 0x7E0) >> 5);
        blue = (byte)((color & 0xF800) >> 11);
    }

    private static byte[] Convert16BitGrayScaleToRgb48(byte[] inBuffer, int width, int height)
    {
        int inBytesPerPixel = 2;
        int outBytesPerPixel = 6;

        byte[] outBuffer = new byte[width * height * outBytesPerPixel];
        int inStride = width * inBytesPerPixel;
        int outStride = width * outBytesPerPixel;

        // Step through the image by row
        for (int y = 0; y < height; y++)
        {
            // Step through the image by column
            for (int x = 0; x < width; x++)
            {
                // Get inbuffer index and outbuffer index
                int inIndex = (y * inStride) + (x * inBytesPerPixel);
                int outIndex = (y * outStride) + (x * outBytesPerPixel);

                byte hibyte = inBuffer[inIndex + 1];
                byte lobyte = inBuffer[inIndex];

                //R
                outBuffer[outIndex] = lobyte;
                outBuffer[outIndex + 1] = hibyte;

                //G
                outBuffer[outIndex + 2] = lobyte;
                outBuffer[outIndex + 3] = hibyte;

                //B
                outBuffer[outIndex + 4] = lobyte;
                outBuffer[outIndex + 5] = hibyte;
            }
        }
        return outBuffer;
    }

    private static byte[] Convert16BitGrayScaleToRgb16(byte[] inBuffer, int width, int height)
    {
        int inBytesPerPixel = 2;
        int outBytesPerPixel = 2;

        byte[] outBuffer = new byte[width * height * outBytesPerPixel];
        int inStride = width * inBytesPerPixel;
        int outStride = width * outBytesPerPixel;

        // Step through the image by row
        for (int y = 0; y < height; y++)
        {
            // Step through the image by column
            for (int x = 0; x < width; x++)
            {
                // Get inbuffer index and outbuffer index
                int inIndex = (y * inStride) + (x * inBytesPerPixel);
                int outIndex = (y * outStride) + (x * outBytesPerPixel);

                byte hibyte = inBuffer[inIndex];
                byte lobyte = inBuffer[inIndex+1];

                outBuffer[outIndex] = lobyte;
                outBuffer[outIndex+1] = hibyte;
            }
        }

        return outBuffer;
    }

    private static byte[] Convert16BitGrayScaleToRgb24(byte[] inBuffer, int width, int height)
    {
        int inBytesPerPixel = 2;
        int outBytesPerPixel = 3;

        byte[] outBuffer = new byte[width * height * outBytesPerPixel];
        int inStride = width * inBytesPerPixel;
        int outStride = width * outBytesPerPixel;

        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                int inIndex = (y * inStride) + (x * inBytesPerPixel);
                int outIndex = (y * outStride) + (x * outBytesPerPixel);

                byte hibyte = inBuffer[inIndex];
                byte lobyte = inBuffer[inIndex + 1];

                byte r, g, b;

                UInt16 color = (UInt16)(hibyte << 8 | lobyte);

                Convert16bitGSToRGB(color, out r, out g, out b);

                outBuffer[outIndex] = r;
                outBuffer[outIndex + 1] = g;
                outBuffer[outIndex + 2] = b;
            }
        }

        return outBuffer;
    }

    private static Bitmap CreateBitmapFromBytes(byte[] pixelValues, int width, int height)
    {
        //Create an image that will hold the image data

        Bitmap bmp = new Bitmap(width, height, PixelFormat.Format16bppRgb565);

        //Get a reference to the images pixel data
        Rectangle dimension = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData picData = bmp.LockBits(dimension, ImageLockMode.ReadWrite, bmp.PixelFormat);
        IntPtr pixelStartAddress = picData.Scan0;

        //Copy the pixel data into the bitmap structure
        Marshal.Copy(pixelValues, 0, pixelStartAddress, pixelValues.Length);

        bmp.UnlockBits(picData);
        return bmp;
    }

但转换的结果仍然不令人满意/不正确。这是我应该得到的图片:

转换此处链接的文件:

Example RAW16 picture file

这是使用Convert16BitGrayScaleToRgb48的结果:

这是使用Convert16BitGrayScaleToRgb16的结果:

这是使用Convert16BitGrayScaleToRgb24的结果:

很明显,颜色重映射是错误的,但我不明白问题出在哪里。

此外,我还发现图片框没有准确显示它存储的内容。顶部的第二张图片(Convert16BitGrayScaleToRgb48 结果)是图片框显示的内容,而以下图片是我保存为 PNG 格式显示的图像时获得的内容:

我认为 RAW16 灰度应该意味着 2 个字节,其中包含 16 位灰度值或在 565 或 555 映射上编码的 RGB 灰度值。但这些假设似乎都与真实情况不符。

有人提示,如何将源文件中提供的值转换成第一张一样的图片(使用ImageJ从同一来源获得)?

【问题讨论】:

  • 如果您将使用 PixelFormat.Format48bppRgb 构建的图像保存到 PNG 文件中,您可能会得到接近预期结果的结果。
  • @Jimi 不是。嗯……真的没有。保存的图片与图片框所描绘的有所不同,但无论如何都不正确:48bpp image
  • 是的,不完全支持原始 16 位灰度图像。这就是编码器可以做的,每通道转码为 8 位。色彩空间将无法正确调整。您必须自己执行标准化。顺便说一句,您发布的原始图像,用 PhotoShop 打开,给出了相同的结果(类似于过度曝光:局部分辨率损失)。它可能针对某些特定的解码器,类似于 DICOM 图像格式(用于显微镜或 X 射线)。
  • @Jimi 不!问题是 1) 字节序 2) 亮度范围 3) 图片框。特别是最后一个(可能取决于显卡)似乎是导致 48bpp 帧渲染失败的原因。
  • :) 这就是为什么我建议使用 PNG 编码器来执行转换。并标准化色彩空间。由于没有人知道这张图片是什么,它是由什么产生的以及使用什么参数 - 考虑到 PhotoShop 产生的结果与 PNG 编码器相同 - 没有太多可以说的了。

标签: c# .net image picturebox


【解决方案1】:

我使用 GIMP 找到了一个可能的提示。如果我通过这个应用程序加载原始文件(更改.data 中的扩展名和/或强制将其加载为RAW)并将其设置为160x120 16bpp BigEndian 我得到一个几乎黑框(!),但如果我改变压缩级别存在的唯一小峰周围的范围(大约 12,0 黑色 - 13,0 白色)图像结果正确。更改字节顺序非常简单,可以将动态范围压缩得更少,但我正在努力。

从这次经历中学到的第一课是“不要相信你的眼睛”:-)。

我努力的最终结果是这三种方法:

public static void GetMinMax(byte[] data, out UInt16 min, out UInt16 max, bool big_endian = true)
{
    if (big_endian)
        min = max = (UInt16)((data[0] << 8) | data[1]);
    else
        min = max = (UInt16)((data[1] << 8) | data[0]);

    for (int i = 0; i < (data.Length - 1); i += 2)
    {
        UInt16 _value;

        if (big_endian)
            _value = (UInt16)((data[i] << 8) | data[i + 1]);
        else
            _value = (UInt16)((data[i + 1] << 8) | data[i]);

        if (_value < min)
            min = _value;

        if (_value > max)
            max = _value;
    }
}

public static void CompressRange(byte MSB, byte LSB, UInt16 min, UInt16 max, out byte color, Polarity polarity)
{
    UInt16 _value = (UInt16)((MSB << 8) | LSB);

    _value -= min;

    switch (polarity)
    {
        case Polarity.BlackHot:
            _value = (UInt16)((_value * 255) / (max - min));
            _value = (UInt16)(255 - _value);
            break;

        default:
        case Polarity.WhiteHot:
            _value = (UInt16)((_value * 255) / (max - min));
            break;
    }

    color = (byte)(_value & 0xff);
}

public static byte[] Convert16BitGrayScaleToRgb24(byte[] inBuffer, int width, int height, UInt16 min, UInt16 max, bool big_endian = true, Polarity polarity = Polarity.WhiteHot)
{
    int inBytesPerPixel = 2;
    int outBytesPerPixel = 3;

    byte[] outBuffer = new byte[width * height * outBytesPerPixel];
    int inStride = width * inBytesPerPixel;
    int outStride = width * outBytesPerPixel;

    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            int inIndex = (y * inStride) + (x * inBytesPerPixel);
            int outIndex = (y * outStride) + (x * outBytesPerPixel);

            byte hibyte;
            byte lobyte;

            if (big_endian)
            {
                hibyte = inBuffer[inIndex];
                lobyte = inBuffer[inIndex + 1];
            }
            else
            {
                hibyte = inBuffer[inIndex + 1];
                lobyte = inBuffer[inIndex];
            }

            byte gray;

            CompressRange(hibyte, lobyte, min, max, out gray, polarity);

            outBuffer[outIndex] = gray;
            outBuffer[outIndex + 1] = gray;
            outBuffer[outIndex + 2] = gray;
        }
    }

    return outBuffer;
}

这些允许加载附加到原始问题的文件并将其显示在标准WindowsForm PictureBox 上。使用 48bpp 格式会导致某些显卡上的图像质量下降(至少在我的显卡上)。 BTW GetMinMax 计算当前帧的最小最大值,无论环境的历史如何。这意味着,如果您要使用此功能来显示图片序列(就像我一样),FOV 中平均温度的强烈变化会将整个图像驱动到不同的曝光,从而导致丢失一些图片的细节。在这种情况下,我建议在当前帧上计算 min-max,但不要在 Convert16BitGrayScaleToRgb24 中使用它,而不是使用 移动平均值 来计算两个值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-22
    • 2020-08-29
    • 2014-09-17
    • 2021-10-08
    • 2018-08-04
    • 1970-01-01
    • 2013-05-08
    • 1970-01-01
    相关资源
    最近更新 更多