C # не может отобразить изображение в градациях серого RAW16 с правильным отображением цвета - PullRequest
2 голосов
/ 29 октября 2019

Изменение кода, предоставленного по этой ссылке:

Оригинальный код

Я написал это:

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

Но все же результатпреобразование не удовлетворяет / правильное. Вот изображение, которое я должен получить:

Original picture

Преобразование связанного здесь файла:

Пример файла изображения RAW16

Это результат использования Convert16BitGrayScaleToRgb48:

enter image description here

Это результат использования Convert16BitGrayScaleToRgb16:

GrayScaleToRgb16

Это результат использования Convert16BitGrayScaleToRgb24:

GrayScaleToRgb24

Совершенно очевидно, что переназначение цветанеправильно, но я не могу понять, где проблема.

Кроме того, я также обнаружил, что picturebox не показывает точно, что он хранит. Второе изображение сверху (Convert16BitGrayScaleToRgb48 результат) - это то, что показано на картинке, тогда как следующее изображение я получу, если сохраню изображение, показанное в формате PNG:

Image saved

Я считал, что шкала серого RAW16 должна означать 2 байта, содержащие либо 16-битное значение серого, либо значение RGB серого, закодированное на карте 565 или 555. Но ни один из этих гипотез, похоже, не соответствует действительности.

Кто-то имеет подсказку о том, как преобразовать значение, предоставленное в исходном файле, для получения изображения, подобного первому (полученного из того же источника с использованием ImageJ)

1 Ответ

1 голос
/ 30 октября 2019

Я нашел возможную подсказку, используя 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 приведет к ухудшению изображения на некоторых графических картах (по крайней мере, на моем). КСТАТИ GetMinMax рассчитать минимальное максимальное значение для текущего кадра независимо от истории окружающей среды. Это означает, что если вы собираетесь использовать эти функции для отображения последовательности изображений (как у меня), сильное изменение средней температуры в поле зрения приведет общее изображение к другой экспозиции , что приведет к потере некоторых деталей. картины. В таких случаях я предлагаю вычислять минимальное и максимальное значения для текущего кадра, но НЕ использовать его в Convert16BitGrayScaleToRgb24, используя вместо этого скользящее среднее для обоих значений.

...