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);
}
最后显示效果图
然后就可以用 opencv 做各种数据处理了。