Как перевести Tiff.ReadEncodedTile в матрицу рельефа местности с карты высот в C#?

Я новичок в работе с изображениями TIFF и пытаюсь получить значения рельефа местности из карты TIFF с помощью LibTiff. Карты, которые мне нужно декодировать, организованы по тайлам. Ниже приведен фрагмент кода, который я сейчас использую для получения этих значений, основываясь на документации библиотеки и исследованиях в Интернете:

    private void getBytes()
    {
        int numBytes = bitsPerSample / 8;           //Number of bytes depending the tiff map
        int stride = numBytes * height;
        byte[] bufferTiff = new byte[stride * height];  // this is the buffer with the tiles data

        int offset = 0;

        for (int i = 0; i < tif.NumberOfTiles() - 1; i++)
        {
            int rawTileSize = (int)tif.RawTileSize(i);
            offset += tif.ReadEncodedTile(i, bufferTiff, offset, rawTileSize); 

        }

        values = new double[height, width];         // this is the matrix to save the heigth values in meters

        int ptr = 0;                    // pointer for saving the each data bytes
        int m = 0;
        int n = 0;

        byte[] byteValues = new byte[numBytes];     // bytes of each height data

        for (int i = 0; i < bufferTiff.Length; i++)
        {
            byteValues[ptr] = bufferTiff[i];

            ptr++;
            if (ptr % numBytes == 0)
            {
                ptr = 0;

                    if (n == height) // tiff map Y pixels
                    {
                        n = 0;
                        m++;
                        if (m == width) // tiff map X pixels
                        {
                            m = 0;
                        }
                    }

                    values[m, n] = BitConverter.ToDouble(byteValues, 0);    // Converts each byte data to the height value in meters. If the map is 32 bps the method I use is BitConverter.ToFloat

                    if (n == height - 1 && m == width - 1)
                        break;
                    n++;

            }
        }
        SaveArrayAsCSV(values, "values.txt");               
    }

    //Only to show results in a cvs file:
    public void SaveArrayAsCSV(double[,] arrayToSave, string fileName)  // source: http://stackru.com/questions/8666518/how-can-i-write-a-general-array-to-csv-file
    {
        using (StreamWriter file = new StreamWriter(fileName))
        {
            WriteItemsToFile(arrayToSave, file);
        }
    }

   //Only to show results in a cvs file:
    private void WriteItemsToFile(Array items, TextWriter file)     // source: http://stackru.com/questions/8666518/how-can-i-write-a-general-array-to-csv-file
    {
        int cont = 0;
        foreach (object item in items)
        {
            if (item is Array)
            {
                WriteItemsToFile(item as Array, file);
                file.Write(Environment.NewLine);
            }
            else {
                file.Write(item + " | ");
                cont++;
                if(cont == width)                       
                {
                    file.Write("\n");
                    cont = 0;
                }
            }
        }
    }

Я тестировал две разные карты (32 и 64 бита на выборку), и результаты схожи: в начале данные кажутся согласованными, но есть момент, когда все другие значения искажаются (даже ноль при конец данных результатов). Я делаю вывод, что есть некоторые байты, которые нужно игнорировать, но я не знаю, как их идентифицировать, чтобы лишить моего кода смысла. Метод Tiff.ReadScanline не работает для меня, потому что карты, которые мне нужно декодировать, организованы по тайлам, и этот метод не предназначен для работы с изображениями такого типа (согласно документации BitMiracle.LibTiff). Метод Tiff.ReadRGBATile также недопустим, потому что изображения tiff не являются RGB. Я могу прочитать эти значения с помощью Matlab, но мой проект должен быть построен на C#, поэтому я могу сравнить ожидаемые результаты с моими. Для справки (я думаю, что это может быть полезно), это некоторые данные, извлеченные из одного из файлов tiff с помощью методов чтения тегов LibTiff:

  • Ширина изображения: 2001
  • ImageLength: 2001
  • BitsPerSample: 32
  • Сжатие: PackBits (также известный как Macintosh RLE)
  • Фотометрический: MinIsBlack
  • SamplesPerPixel: 1
  • ПланарКонфиг: Контиг
  • TileWidth: 208
  • Длина плитки: 208
  • SampleFormat: 3

Спасибо заранее за вашу помощь, ребята!

2 ответа

Решение

Хорошо, наконец-то я нашел решение: моей ошибкой был параметр "count" в функции Tiff.ReadEncodedTile(плитка, буфер, смещение, счет). Функция Tiff.RawTileSize(int) возвращает размер сжатых байтов плитки (различный для каждой плитки в зависимости от алгоритма сжатия), но Tiff.ReadEncodedTile возвращает распакованные байты (больше и постояннее для всех плиток). Вот почему не вся информация была сохранена должным образом, а только часть данных. Ниже правильный код с матрицей рельефа местности (нужна оптимизация, но она работает, я думаю, что это может быть полезно)

 private void getBytes()
    {
        int numBytes = bitsPerSample / 8;
        int numTiles = tif.NumberOfTiles();
        int stride = numBytes * height;
        int bufferSize = tileWidth * tileHeight * numBytes * numTiles;
        int bytesSavedPerTile = tileWidth * tileHeight * numBytes; //this is the real size of the decompressed bytes
        byte[] bufferTiff = new byte[bufferSize];

        FieldValue[] value = tif.GetField(TiffTag.TILEWIDTH);
        int tilewidth = value[0].ToInt();

        value = tif.GetField(TiffTag.TILELENGTH);
        int tileHeigth = value[0].ToInt();

        int matrixSide = (int)Math.Sqrt(numTiles); // this works for a square image (for example a tiles organized tiff image)
        int bytesWidth = matrixSide * tilewidth;
        int bytesHeigth = matrixSide * tileHeigth;

        int offset = 0;

        for (int j = 0; j < numTiles; j++)
        {
            offset += tif.ReadEncodedTile(j, bufferTiff, offset, bytesSavedPerTile); //Here was the mistake. Now it works!
        }

        double[,] aux = new double[bytesHeigth, bytesWidth]; //Double for a 64 bps tiff image. This matrix will save the alldata, including the transparency (the "blank zone" I was talking before)

        terrainElevation = new double[height, width]; // Double for a 64 bps tiff image. This matrix will save only the elevation values, without transparency

        int ptr = 0;
        int m = 0;
        int n = -1;
        int contNumTile = 1;
        int contBytesPerTile = 0;
        int i = 0;
        int tileHeigthReference = tileHeigth;
        int tileWidthReference = tileWidth;
        int row = 1;
        int col = 1;

        byte[] bytesHeigthMeters = new byte[numBytes]; // Buffer to save each one elevation value to parse

        while (i < bufferTiff.Length && contNumTile < numTiles + 1)
        {
            for (contBytesPerTile = 0; contBytesPerTile < bytesSavedPerTile; contBytesPerTile++)
            {
                bytesHeigthMeters[ptr] = bufferTiff[i];
                ptr++;
                if (ptr % numBytes == 0 && ptr != 0)
                {
                    ptr = 0;
                    n++;

                    if (n == tileHeigthReference)
                    {
                        n = tileHeigthReference - tileHeigth;
                        m++;
                        if (m == tileWidthReference)
                        {
                            m = tileWidthReference - tileWidth;
                        }
                    }
                    double heigthMeters = BitConverter.ToDouble(bytesHeigthMeters, 0);

                    if (n < bytesWidth)
                    {
                        aux[m, n] = heigthMeters;
                    }
                    else
                    {
                        n = -1;
                    }

                }
                i++;
            }

            if (i % tilewidth == 0)
            {
                col++;
                if (col == matrixSide + 1)
                {
                    col = 1;
                }
            }

            if (contNumTile % matrixSide == 0)
            {
                row++;
                n = -1;
                if (row == matrixSide + 1)
                {
                    row = 1;

                }
            }

            contNumTile++;
            tileHeigthReference = tileHeight * (col);
            tileWidthReference = tileWidth * (row);

            m = tileWidth * (row - 1);

        }

        for (int x = 0; x < height; x++)
        {
            for (int y = 0; y < width; y++)
            {
                terrainElevation[x, y] = aux[x, y]; // Final result. Each position of matrix has saved each pixel terrain elevation of the map
            }
        }

    }

С уважением!

Вот улучшенный код, работающий с неквадратными плитками:

int imageWidth = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
                int imageHeight = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
                int bytesPerSample = (int)tiff.GetField(TiffTag.BITSPERSAMPLE)[0].ToInt() / 8;
                SampleFormat format = (SampleFormat)tiff.GetField(TiffTag.SAMPLEFORMAT)[0].ToInt();

                //Array to return
                float[,] decoded = new float[imageHeight, imageWidth];

                //Get decode function (I only want a float array)
                Func<byte[], int, float> decode = GetConversionFunction(format, bytesPerSample);
                if (decode == null)
                {
                    throw new ArgumentException("Unsupported TIFF format:"+format);
                }

                if(tiff.IsTiled())
                {
                    //tile dimensions in pixels - the image dimensions MAY NOT be a multiple of these dimensions
                    int tileWidth = tiff.GetField(TiffTag.TILEWIDTH)[0].ToInt();
                    int tileHeight = tiff.GetField(TiffTag.TILELENGTH)[0].ToInt();

                    //tile matrix size
                    int numTiles = tiff.NumberOfTiles();
                    int tileMatrixWidth = (int)Math.Ceiling(imageWidth / (float)tileWidth);
                    int tileMatrixHeight = (int)Math.Ceiling(imageHeight / (float)tileHeight);

                    //tile dimensions in bytes
                    int tileBytesWidth = tileWidth * bytesPerSample;
                    int tileBytesHeight = tileHeight * bytesPerSample;

                    //tile buffer
                    int tileBufferSize = tiff.TileSize();
                    byte[] tileBuffer = new byte[tileBufferSize];

                    int imageHeightMinus1 = imageHeight - 1;

                    for (int tileIndex = 0 ; tileIndex < numTiles; tileIndex++)
                    {
                        int tileX = tileIndex / tileMatrixWidth;
                        int tileY = tileIndex % tileMatrixHeight;

                        tiff.ReadTile(tileBuffer, 0, tileX*tileWidth, tileY*tileHeight, 0, 0);

                        int xImageOffset = tileX * tileWidth;
                        int yImageOffset = tileY * tileHeight;

                        for (int col = 0; col < tileWidth && xImageOffset+col < imageWidth; col++ )
                        {
                            for(int row = 0; row < tileHeight && yImageOffset+row < imageHeight; row++)
                            {
                                decoded[imageHeightMinus1-(yImageOffset+row), xImageOffset+col] = decode(tileBuffer, row * tileBytesWidth + col * bytesPerSample);

                            }
                        }
                    }
                }
Другие вопросы по тегам