天天看點

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 做各種資料處理了。