天天看点

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

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 做各种数据处理了。