如何从 C# 中的高度图将 Tiff.ReadEncodedTile 转换为高程地形矩阵?

How to translate Tiff.ReadEncodedTile to elevation terrain matrix from height map in C#?

我是读取 tiff 图像的新手,我正在尝试使用 LibTiff 从 tiff 地图获取高程地形值。我需要解码的地图是平铺组织的。根据库文档和网络研究,在我当前用于获取这些值的代码片段下方:

    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: 
    {
        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: 
    {
        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# 构建,因此我可以将预期结果与我的进行比较。作为参考(我认为它可能会有所帮助),这些是使用 LibTiff 标签读取方法从一个 tiff 文件中提取的一些数据:

在此先感谢您的帮助!

好吧,终于找到了解决办法:我的错误是函数Tiff.ReadEncodedTile(tile, buffer, offset, count)中的参数"count"。 Tiff.RawTileSize(int) 函数,returns 瓦片的压缩字节大小(每个瓦片不同,取决于压缩算法),但 Tiff.ReadEncodedTile returns 解压缩字节(所有瓷砖更大且恒定)。这就是为什么没有正确保存所有信息,而是只保存了一部分数据的原因。在带有地形高程矩阵的正确代码下方(需要优化但它有效,我认为它可能会有所帮助)

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

    }

此致!

这是一个改进的代码,适用于 non-square 个图块:

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

                            }
                        }
                    }
                }