欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ubuntu 下 tif(DEM)文件转换为opencv Mat 格式

程序员文章站 2022-04-14 07:49:32
...

ubuntu 下 C++ 利用 libtiff 库 tif(DEM)文件转换为opencv Mat 格式

写在前面:opencv 是支持打开 tif 格式彩图的,但是对于常见的 DEM 数据打开会报错。当然为了省事可以直接使用 MatLab 进行处理,但笔者偏爱 C++。这篇博客解决用 tif 数据转成 cv::Mat 格式问题。

libtiff 库编译

cd ${libtiff_path}
mkdir release
cd release
cmake ..
make -j4

顺便一题的是压缩包中有很多无用文件,使用时可以直接留下 libtiff 和release 文件夹,其他都可以删了。

libtiff 使用教程

http://www.libtiff.org/libtiff.html

这个教程太不友好了,并没有介绍 buf 如何去使用。

main()
{
    TIFF* tif = TIFFOpen("myfile.tif", "r");
    if (tif) {
	uint32 imageWidth, imageLength;
	uint32 tileWidth, tileLength;
	uint32 x, y;
	tdata_t buf;

	TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
	TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
	TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth);
	TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileLength);
	buf = _TIFFmalloc(TIFFTileSize(tif));
	for (y = 0; y < imageLength; y += tileLength)
	    for (x = 0; x < imageWidth; x += tileWidth)
		TIFFReadTile(tif, buf, x, y, 0);
	_TIFFfree(buf);
	TIFFClose(tif);
    }
}

DEM 数据 tif 文件是采用 tile model,所以选择这种方式。
其中 buf 类型是 tdata_t ,源码中类型年是 void * 类型,这是一种灵活的指针类型。

下面讲一下如何使用,这是一个将数据 value 提取出来的 demo。

TIFF* tif = TIFFOpen(tifPath.c_str(),"r");
    uint32 imageWidth, imageLength;
    uint32 tileWidth, tileLength;

    if (tif) {
        uint32 x, y, z;
        tdata_t buf;
        float* data;

        TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
        TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
        TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth);
        TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileLength);
        buf = _TIFFmalloc(TIFFTileSize(tif));

        for (y = 0; y < imageLength; y += tileLength){
            for (x = 0; x < imageWidth; x += tileWidth){
                TIFFReadTile(tif, buf, x, y, z, 0);
                data=(float*)buf;

                uint32 i;
                float value;
                vector<float> tile;
                for (i=0;i< tileLength*tileWidth ;i++)
                {
                    value =  data[i];
                }
            }
        }
        
        _TIFFfree(buf);
        TIFFClose(tif);
    }

接下来就是如何转换为 cv::Mat 类型。这里面有很多不知道哪来的坑,就是代码逻辑正确,但是结果输出却存在问题。经过我多次尝试不同写法,才最终成功。我怀疑是涉及内存指针操作赋值时出了问题,但我是业余C++选手,一点办法都没有。

void getTiledFile(string tifPath, vector< vector<float> > &mdem){

    TIFF* tif = TIFFOpen(tifPath.c_str(),"r");
    uint32 imageWidth, imageLength;
    uint32 tileWidth, tileLength;

    int tileNumW = 0;
    int tileNumL = 0;

    if (tif) {
        uint32 x, y, z;
        tdata_t buf;
        float* data;

        TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
        TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
        TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth);
        TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileLength);
        buf = _TIFFmalloc(TIFFTileSize(tif));

        for (y = 0; y < imageLength; y += tileLength){
            tileNumW = 0;
            for (x = 0; x < imageWidth; x += tileWidth){
                TIFFReadTile(tif, buf, x, y, z, 0);
                data=(float*)buf;

                uint32 i;
                float value;
                vector<float> tile;
                for (i=0;i< tileLength*tileWidth ;i++)
                {
                    value =  data[i];
                    tile.push_back(value);
                }
                mdem.push_back(tile);
                tileNumW ++;
            }
            tileNumL ++;
        }
        vector<float> demInfo;
        demInfo.push_back((float)imageWidth);
        demInfo.push_back((float)imageLength);
        demInfo.push_back((float)tileWidth);
        demInfo.push_back((float)tileLength);
        demInfo.push_back((float)tileNumW);
        demInfo.push_back((float)tileNumL);
        mdem.push_back(demInfo);

        _TIFFfree(buf);
        TIFFClose(tif);
    }

}

这是将数据读出来。

void getTileIMGs(vector< vector<float> > mdem, vector<cv::Mat> &TileIMG){
    DEMInfo mdemInfo;
    getDemInfo(mdem, mdemInfo);
    for(int row=0; row<mdemInfo.tileNumL; row++){
        for(int col=0; col<mdemInfo.tileNumW; col++){

            cv::Mat img(mdemInfo.tileLength, mdemInfo.tileWidth, CV_32FC1);
            int index = row*mdemInfo.tileNumW+col;

            for(int m1=0; m1<mdemInfo.tileLength; m1++){
                for(int m2=0; m2<mdemInfo.tileWidth; m2++){
                    float value = mdem[index][m1*mdemInfo.tileWidth+m2];
                    if(abs(value)>1e5){
                        value = -1e5;
                    }
                    img.at<float>(m1,m2) = value;
                }
            }
            TileIMG.push_back(img);
        }
    }
}

这里是将各个 Tile 数据分别存储成 cv::Mat , 注意类型 CV_32FC1。

void mergeTileIMGs(vector<cv::Mat> TileIMGscopy, DEMInfo mdemInfo, vector<cv::Mat> &demIMG){
    cv::Mat img(mdemInfo.tileNumL*mdemInfo.tileLength, mdemInfo.tileNumW*mdemInfo.tileWidth, CV_32FC1);
    for(int row=0; row<mdemInfo.tileNumL; row++){
        for(int col=0; col<mdemInfo.tileNumW; col++){
            int index = row*mdemInfo.tileNumW+col;
            for(int m1=0; m1<mdemInfo.tileLength; m1++){
                for(int m2=0; m2<mdemInfo.tileWidth; m2++){
                    float value = TileIMGscopy[index].at<float>(m1,m2);
                    img.at<float>(row*mdemInfo.tileLength+m1,col*mdemInfo.tileWidth+m2) = value;
                }
            }
        }
        int pp = 0;
    }
    drawTileIMG(img, "0");
    demIMG.push_back(img);
}

然后将 Tile 合成一张图。这里会发现 tile 大小乘以个数比原 tif 数据大小要大。这没关系的,我用代码测试相邻 tile 之间是没有重叠的。测试代码如下。

int calTileWidDrift(cv::Mat tile1, cv::Mat tile2){
    int drift = 0;
    for(int i=1; i<tile1.cols; i++){
        float dist = 0;
        for(int j=0; j<i; j++){
            for(int k=0; k<tile1.rows; k++){
                float diff = tile1.at<float>(k, tile1.cols-i+j) - tile1.at<float>(k, j);
                dist += abs(diff);
            }
        }
        if(dist < 1e-8){
            drift = i;
            break;
        }
    }
    drawTileIMG(tile1, "1");
    drawTileIMG(tile2, "2");
    return drift;
}

再贴一个简单显示 CV_32FC1 的代码吧,有效数据范围图

void drawTileIMG(cv::Mat tileIMG, string wname){
    cv::Mat img(tileIMG.rows, tileIMG.cols, CV_8UC1);
    for(int i=0; i<tileIMG.rows; i++){
        for(int j=0; j<tileIMG.cols; j++){
            float value = tileIMG.at<float>(i,j);
            if(value < -1e4){
                img.at<uchar>(i,j) = 0;
            }
            else{
                img.at<uchar>(i,j) = 255;
            }
        }
    }
    cv::namedWindow(wname,0);
    cv::resizeWindow(wname, 512,512);
    cv::imshow(wname, img);
}

最后显示效果图

ubuntu 下 tif(DEM)文件转换为opencv Mat 格式然后就可以用 opencv 做各种数据处理了。