c++ envi(.hdr+.img)图像转换成tiff格式 纯c++

写在前面

我不是什么大佬,二周前我只是个去年把c++教程看了三分之一的萌新,甚至连if-else这种简单的语句都可以写错的萌新。这篇博客不是为了说明某种算法,而是为了记录下我这两周的努力。给与我有相同困境的人提供一个解决问题的思路。如果您有envi转tiff的需求,而且不使用软件来实现,ok,那我们继续~本文默认您对envi格式和tiff格式有一定的了解,如果您不想看我的废话,可以直接跳转到使用说明和库链接那儿

为什么要做这个

这个项目的原型是一个高光谱项目,我的任务是把我的数据源(.lg)转换成神经网络可以读取的tiff格式,我的相机厂商这边提供了两个demo,可以用于拍照(.lg)和转格式(.lg–>envi),相机底层框死了我只能用c++。我实际要做的,是把envi转换成tiff。导师要求的死线,是15天QAQ。试想一下,c++等于小白,没有什么代码经历(最多就是在python上跑鸢尾花,跟着python课本写小游戏),刚接到这个项目时,人都是傻的。当时的心态,置于死地而后生吧,拼两周,实在是做不出来,我就心甘情愿的滚。

怎么做这个

我最初的思路,是想着用既有的函数来解决我的实际问题。所以最初的着眼点,是MATLAB。MATLAB上有个multibandread()函数,这个函数可以实现读取envi转换成.mat .tiff等格式那我能不能用c++来调用这个函数呢?答案…不会,因为我确实在调用时解决不了mwArray这个数据类型的问题,就哪怕我按照multibandread()的形参要求填写每一个参数,函数也运行不了。有明白怎么用的大神…也可以教教我…

那会不会是matlab的问题呢?我用其他的库看看?网上还有GDAL库、arcgis软件等很多方法,但是涉及到调用人家的库,MATLAB没调通,让我很没信心。

覆写multibandread()可不可行呢?这是导师提出的思路。在与很多朋友讨论后,我认为这对我来说是不可能的。

这个时候,我看到了一篇文章C++读取并保存Tiff文件(纯C++,不需要配置opencv、boost.GIL等环境),这时候的我认为我能调通它,结果是确实调通了,这位大佬的代码实可以用的,但是对我来说这还远远不够。1.我的目的和他不一样,他是读tiff写tiff,我是读envi写tiff。 2.就哪怕是用tiff来做实验,我的tiff图不能被这个代码正确的识别转换,少了整整一半的数据。

这时候,我想起了导师的思路,我能不能重构它呢?

EnviToTiff.h的重构思路

此时的我,还对c++一无所知,但是我有动力,我不想让别人看不起,我觉得我能行。我觉得一无是处的我最大的优点,就是不服输了吧。基于C++读取并保存Tiff文件(纯C++,不需要配置opencv、boost.GIL等环境),我开始了我的重构之旅。

最初我想数据没读完,应该是原库里有什么参数设置不对吧,我设置对了,应该就能实现我的目标。但是在阅读原库的时候,我发现一个很大的问题,这个库写的是啥??????????我要改啥?我是谁我在哪我要做什么QAQ。我前面有说过,我c++的水平仅限于1/3的课本,或者说,仅限于知道一点语法吧。

在这里插入图片描述
此时的我,根本看不懂里面这些个函数(我那时甚至都不懂这个语法是什么,没学过)的意思,包括现在也还不懂template怎么用= =,可能未来有机会我会学到吧

而且根本性质上来说,我的目的和他不一样。如果他的写入没问题,那我只需要解决读的问题就行。关于读写什么,我花了4天的时间搞懂了数据源(envi)的构成,花了3天时间搞懂了tif的构成,这个以后再单独写一篇博客吧。可能对于大佬来说,我的速度很慢,但是对我来说,这是必须的探索(萌新是这样的)。

在明白了envi和tiff的构成之后,我有了实践的思路。也明白了上图中的结构体到底定义了什么,而且也明白了我和这位大佬的tif到底差在哪里。我的图片最大是128波段,1.2G每张图。通过enviclassic软件转换出的tif,有15个DE。我的数据源比这个大佬的数据源大,而且我的DE也比这个大佬的图多。所以这个大佬的库肯定不能正确转换我的图片,我要做就只能写出属于我自己库。这时候距离死线还有7天,而我也打包票说这玩意我一定在3.31之前做出来。

对我来说,在自己没有思路的时候,参考别人的东西就是最好的选择

我依据原库里的结构体构造方法,把结构体里的属性,换成了我要转出的tiff的属性(以enviclassic软件转出的为参考),15个DE,具体写了什么,在我的代码里注释写得很详细了,我就不赘述了。建议有转tiff格式需求的人都去把tiff格式的文档看一遍。在这里插入图片描述
其中还有个小插曲= = 我之前不知道为什么magic叫magic,不知道大佬把这个参数命名为magic有啥意思。魔术?我的理解就是标志位,后面有一个c++大佬告诉我magic的含义,我才改回来…(天真地以为自己比大佬懂命名…

在这里插入图片描述
析构函数我测试了很多次都不明白到底要干啥…后面就被我屏蔽了。直到我涉及到指针的时候出现bug,才明白为什么原函数要先把指针滞空,然后动态分配内存。

然后开始读,我参考原函数的读的方法,写了自己的读的方法。忘了说了,最终这个库是要在硬件上运行的,所以我得考虑一定程度的“auto”功能。

在这里插入图片描述
所以有了最初的get_need_file()、deweightng_vector_string()功能,这两个函数的出处我在代码里注释了。我的目的是,自动检索某个后缀名的文件,然后把文件名推到数组里。然后对数组去重。我的构想是,假如我的相机随时都在拍照,随时都能生成envi图像,我不希望我的函数运行一次检索到 1 2 3 4 5号文件处理成tiff,后面生成6 7 8 9 10号文件,函数把搜索到的10个文件再处理一次,而只处理6 7 8 9 10号文件。所以1.0的方法,是通过一个vector的全局变量,来作为对比数组,只要我检索该文件夹下所有的文件有和我这个数组重名的,去掉重名的文件,只留新的文件。

在这里插入图片描述
readEnviFileName()读取文件夹下的所有envi文件,使用了get_need_file()

读的最大前提是,首先你得懂往哪里读,读什么,这是我在读懂数据结构的时候的感悟。在明白数据构成以后,你自然会明白数据该怎么写。

analyseHdr()读hdr导出我需要的数据(图像的长,宽,波段数,数据的字节数)
anayseimg()读img出一个数据指针,用来导出img的数据
analyseEnvi()包含了以上两种方法,并把每一个我需要写入的数值赋值

自此,读的工作已经完成,中间遇到无数的坑。什么if-else写成if(){} else(){}; vs还TM没给我报错,语法是没问题了,我看着if else都执行了,还没想出为啥…最后还是一个同学指出的这个问题,这会儿我感觉我像个智障= =
fread 是我最初考虑的方法,但是大佬用的是fstream库里的方法,我为了测试学会使用这个库也花了很长时间。可能未来我会换成fread的方法吧,因为我看到有一篇博文说fread比fstream的read执行效率高。我的代码最初就是模仿,所以我也仿造大佬的库使用fstream的方法。
还有什么空指针,野指针这种,多的就不提了 ,反正是新手能犯的错误都可以踩一遍。等摔得遍体鳞伤了,知道痛了,下次还敢()

在开始想写这个问题的时候,我有一个疑问,如果我按照某个格式,把每一个字节的数据依次写入,是不是就能读成这个格式呢?我把tiff理解成 头文件 + 属性 + 目录 + 数据 + 属性 + 尾文件 的结构,如果我按照顺序写入数据,这玩意就是我要的tiff格式吗?后面一个朋友解答了我这个疑惑。既然tiff拥有 IFH IFD DE这三个结构,结构的顺序真的很重要吗?如果把tiff想象成一本书,我更希望它是 头文件(IFH + IFDoffset) + 目录 + 数据 + 尾文件(IFD + DE)的格式,不过这个想法我倒是没实践过= = 因为如果要实践,我的算法就要重构…挺麻烦的= =

扯远了,回到整体,我的朋友告诉我,我这个想法是对的,按格式顺序写入,理论上没问题。接下来就是实践。


这是原库的写入顺序,这也是我顺序写入想法的一个印证

我原来只是想拆原库里的save方法,但是照搬是不可能的了,我的DE比较多。所以我得有自己的算法。

通过对我的envi转出的tiff图像的二进制数据的不断比对(比对DE里出现的熟悉的字眼,比如长 宽 波段数 字节数,或者是数据字节数量),我得出了每个我要写入的每个数据的算法。从哪里读,读出什么,怎么算的。

我的读和写是密切相关的,以enviclassic转出的tiff文件为模板,一个一个写入我的参数和数据,一个一个比对。方法用的是大佬的方法。以前上课的时候,大学计算机基础讲过一个思想,黑盒思想。我不关心函数里到底怎么运作的,我只关心IO,关心进什么数据出什么数据。所以前面我甚至都看不懂强制转换这个算法是什么意思,我只知道我只需要输入什么数据,写出来的二进制数据就是我想要的结果= = 这也为我后面遇到一个对我而言非常底层的“bug”埋下了祸根,以后我也会单独水篇博客的。

写的思路就说这么多,读写完成了,接下来就是调用。

主函数mainEnviToTiff,我的库的1.0版本。思路就是,我检索一个文件夹下所有的envi图像,挨个分析,挨个处理。非常简单的一个思路。我需要一个对比数组vector,来实现我检索时的想法。1.0的方法会有个问题,即这个vector是固定不下来的,我每次运行c++ ,这个vector都会被清空。vector的生命周期只有一次运行c++(我不知道我这个说法对不对,我目前感觉是这样)。

这里头也发生一个特别有意思(傻逼)的事情。就是我曾经以为,我的vector是能固定的。然后在调试以后发现并不能,但是那会儿我有截图,我确实在那个时间段成功了。世界上最可怕的事情不是有bug,而是你不知道bug是怎么修好的。关键是你能用也行啊,特么突然又不能用了…我和朋友吐槽了很久这个事情= = 然后就在想,已知我cpp运行一次的生命周期内可以实现,那我如果在一次运行cpp周期内,多次调用主函数呢?

有了想法,接着到实践,怎么做?这时候,真的就是神来一笔,很突然的灵感,我想到了递归函数。

我甚至在想到递归这个词的时候,我都不知道这个词具体是什么意思。我大学计算机基础接近10年前学的,而去年看c++教程我也绝对没看到递归函数这里。

有一句话,老师上课说的:知识,不一定是让你这时候用的。而是未来有一天,你在遇到问题的时候,你可以想起你的知识,捡起你的知识,从而找到解决方法。

所以有这个递归调用主函数的方法在这里插入图片描述
问题解决了,在递归的情况下,我可以实现在递归时间内,只对新生成的envi转格式。

1.0版本完成。这个时候距离4月份还有几天,距离我说的一周之内还有一天。

2.0版本的迭代初衷,是朋友之前提到过的写日志的方法。我用txt作为日志,有两种txt。一个是保存着已经处理过的log文件名的日志A(对比数组),一个是保存着待处理文件的文件名的日志B。而且为了我一定程度上的“auto”功能,我也得使用这个方法,把厂商的两个函数,和我自己的函数连起来。

A
在这里插入图片描述
在这里插入图片描述

B
在这里插入图片描述
在这里插入图片描述

具体的我会在下个章节介绍

我的设想是
1.检索对比数组,得到已经处理过的log(.txt)的文件名,推入容器A中
2.检索目录下的所有log(.txt),将文件名推入容器B中
3.本次需要处理的log文件,等于A与B的差集(去重)
4.挨个检索log,得到log里的待处理文件(ENVI),然后根据文件名检索目录下的ENVI文件,挨个处理

在这里插入图片描述

在这里插入图片描述
这两个函数 改进自1.0的函数,为2.0主函数的铺垫

在这里插入图片描述
2.0主函数enviToTiff,采用log的方式检索文件,增加了基于文件创建时间的去重算法。其余算法涉及大体上和1.0相同。

在这里插入图片描述
这两个函数是我写的小脚本。dataProcess()检索目录下的所有hdr,通过hdr的数据来计算遍历范围,遍历对比enviclassic和我转换的tiff有什么不同。deProcess() 把de的二进制文本数据格式化成txt
在这里插入图片描述

从FE 00(de的数量)到结尾 00 00

在这里插入图片描述
复制到txt里。

格式化输出成txt

在这里插入图片描述
注释可以不用管= = 反正就是DE合适阅读的样子…懒得跑一次拿结果了

到此,过了差不多3周吧,2.0版本加上厂商的两个函数全部封装完成

使用说明

上个章节我有介绍每个函数的作用,我这里就不多说了。我想没有什么使用说明比程序流程图更加管用的吧。

2.0
enviToTiff():
在这里插入图片描述
而且我代码里也有对自己写的算法的注释,应该…能看懂…吧?

一个朋友说过,程序员最讨厌的事情,是别人的代码没注释和给自己的代码做注释(

接下来我说明一下我的文件夹构成

在这里插入图片描述
对enviToTiff函数而言,它需要两个文件夹,ENVI和TIFF

在这里插入图片描述
在这里插入图片描述
两个文件夹下都有LOG文件夹
在这里插入图片描述
在这里插入图片描述
h20230413_133604.txt
在这里插入图片描述
t20230413_133812.txt
在这里插入图片描述
LOG文件夹下都有SAVE文件夹

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用于保存已经处理过的log文件

使用本库时请注意相应修改路径!!!!1.0版本对我来说我自己目前是不用的,所以没有维护,使用时麻烦依次检查路径问题。
2.0版本目前我在使用,而且也在维护,使用时路径确认一次就好

为啥我这么强调路径…因为我犯过一个很蠢的错
2.0的运行逻辑是1.0改的,我在改2.0的时候有一个地方没注意,导致我img的数据没读出来,转换出来的tif打开都是黑色的。我还问管拍照的同学,啥情况= = 他说可能是因为晚上,天黑,拍不好…我也就信了= = 第二天才发现特么不是晚上的缘故…就是程序运行逻辑有BUG,没读出img的数据…而且我的tif对比脚本也骗了我,因为数据读取有问题,也没正确地做对比。然后这个错是什么呢? 1.0我检索出来的文件名是xxxxxxxxxx 不含后缀,所以我在输入路径的时候,要加个“.hdr”或“.img”。2.0我检索出来的文件名是带后缀的xxxxxxxxx.img,然后我还在路径那里加了“.img” 所以读取的时候变成.img.img,这图片不是黑的就有鬼了。而且这么小的错,我没发现是因为我hdr路径那里是改对的,所以hdr能读出来,我数据总长也能正确算出来…就img那里错了。这个故事告诉我们,多写flag()

使用时请关注您的tiff的DE数量,我目前选择15个DE是因为enviclassic转出来的tiff都是15个DE,不管我设置多少个波段,DE的数量是不变的,那我就认为我的数据只有15个DE,或者说enviclassic这个软件转出来的默认15个DE?多的咱也不会用。需要说明的是arcgis转出来的tiff是18个DE,不一定只有1个IFD,而且DE的内容和enviclassic不一样,就我对TIFF格式了解而言,我倾向于使用enviclassic的tiff格式,因为这种格式的书写更加标准,更符合我的需求。或许未来我需要做到打标签这一步,也许会更新3.0也不一定= = 可能到时候我就会明白arcgis的tiff的extra title是什么意思了吧

还有大佬原库里的大小端转换我实在是看不懂= = 所以删了…如果涉及到大小端转换的地方,可以参考大佬的方法

EnviToTiff.h链接

EnviToTiff.h

写在后面

有个学长给我说了一句话,我觉得很好:你们不是不问问题,很多时候你们是不知道你们有什么问题,无法清楚地将这个问题表述出来。

是的,几周前,我是个什么都不懂的小白。遇到的问题,可能我自以为是问题,实际上不是问题。我想每个人或多或少都会有这样的时期,我倒是想问,但是我根本就不知道要问什么。我是谁,我在哪,我要做什么?我不知道哪里是问题,哪里有问题。我是通过实践来度过这个时期的,有的只是一种冲劲。我不服输,我不想让别人看不起自己。尽人事,安天命。

这个学长还说过:如果导师让你三天出一个东西,那你不睡觉也得做完,我当年连着三天没回过宿舍,这是我进步最快的时间。

俗话说,有压力就有动力。两周时间,平均每天工作20个小时,遇到不懂的问题,那也不用睡觉了,最晚也几乎干通宵。这段时间让我受益匪浅,不光是复习了c++这个语言,关键是我觉得我找到了适合我个人的解决问题的思路,这对我来说是非常重要的。

两周时间,我从if-else都能写错,到研究每一个字节数据的流向;从只会机械地套用大佬的方法,到实践出适合自己的方法;从1/3的教程水平c++(还正好就准备看到指针那里),到会用结构体指针。我不是一个聪明人,也未曾想过自己能写出这么长的代码。举个可笑的例子吧,从结构体单拿出一个属性做对比排序(基于创建时间的文件检索排序,算法参考了几个大佬的文章),这个问题给任何一个大佬都是几分钟的事情吧,我想了两天,最后还是一个朋友教了我vector<_finddata_t>这个方法,我才知道vector可以自定义类型。

新人几乎所有会犯的错误几乎都犯了,坑都掉进去了,emmm 回头想想,我是真的菜啊。

我不在乎会遇到挫折,遇到问题,遇到失败,只要我能解决,无论花费多少时间,我都会去解决这个问题(不然晚上睡不了觉了…),除非解决不了…能力有限,我现在也没搞明白相机拍出来的.lg是个什么玩意QAQ,12M大小的图片最大能生成1个多G的ENVI…一种比较可能的说法是在外网查到的,ARCHANGEL压缩算法…如果有大佬知道的话…麻烦在评论区告诉小弟…

实践是最好的老师,没有什么东西比时间更能快速地学东西

写这篇博客的初衷,是为了记录下我这几周的心路历程,如果能帮助到同样在困惑的您,这是我的荣幸。也欢迎各位大佬的批评指导^ ^

鸣谢

感谢我身边的同学朋友,没有你们的帮助和支持,我一定走不到今天,没有你们解决我这个萌新的种种傻逼问题,我估计这时候已经滚了。

感谢原库的作者TerrencePai大佬C++读取并保存Tiff文件(纯C++,不需要配置opencv、boost.GIL等环境),在对c++的了解逐渐深化以后,我发现,错的是我,大佬的库写的挺好的()。大佬的种种方法,还有语言规范,教会了我很多,这也是我未来代码之路的template吧)。

感谢get_need_file()
deweightng_vector_string()
readEnviFileName()
的作者,非常漂亮的思路,函数很有用!

感谢我的导师,让我知道我还有这种可能性~

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 可以使用ENVI软件自带的图像处理工具Batch Converter来批量转换tiff格式ENVI标准格式。具体操作步骤如下: 1. 打开ENVI软件,点击菜单栏上的“Tools”,选择“Batch Converter”。 2. 在Batch Converter窗口中,点击“Add Files”按钮,添加所有需要转换的tiff格式文件。 3. 在“Input Format”栏中选择“TIFF”,在“Output Format”栏中选择“ENVI”,并设置输出文件夹路径。 4. 点击“Advanced”按钮,进入高级设置界面,在“Data Type”中选择影像数据类型。 5. 点击“Run”按钮开始批量转换。 完成以上步骤后,ENVI软件就会自动批量将tiff格式文件转换成ENVI标准格式,并保存到指定的输出文件夹中。 ### 回答2: 要将TIFF格式图像文件批量转化为ENVI标准格式,您可以按照以下步骤进行操作: 1. 首先,确保您已经安装了能够进行图像格式转换的软件,比如ENVI或其他支持ENVI格式的软件。如果您还没有安装,可以到相关的官方网站下载并安装。 2. 打开软件,在菜单栏中选择“文件”或类似选项,然后点击“导入”或“批量导入”选项。 3. 在导入对话框中,选择要转换的TIFF格式图像文件所在的文件夹。您可以使用文件浏览器浏览到目标文件夹,并选择所有要转换的文件。 4. 确认导入选项。在导入对话框中,您可以选择一些导入选项,如坐标系、像元大小等。根据您的需求进行选择,然后点击“导入”或“开始转换”按钮。 5. 等待转换完成。转换过程可能需要一些时间,取决于您要转换的文件数量和文件大小。 6. 将转换后的图像文件保存为ENVI格式。完成转换后,软件会自动打开一个保存对话框。在保存对话框中,选择保存位置和文件名,并将文件格式设置为ENVI格式。 7. 确认保存选项。在保存对话框中,您可以选择一些保存选项,如压缩类型、文件头信息等。根据您的需求进行选择,然后点击“保存”或“确定”按钮。 8. 等待保存完成。保存过程可能需要一些时间,取决于您要保存的文件大小和计算机性能。 通过以上步骤,您可以将TIFF格式图像文件批量转换为ENVI标准格式,并保存在您指定的位置。请注意,在操作过程中,确保您有足够的存储空间和计算资源来完成转换和保存。 ### 回答3: ENVI是一种常用的遥感图像处理软件,它支持多种图像格式,包括TIFF格式。如果要将TIFF格式图像批量转化为ENVI标准格式,可以采取以下步骤: 1. 首先,打开ENVI软件,并点击"File"菜单中的"Batch Convert"选项。 2. 在弹出的对话框中,点击"Add Files"按钮,选择要转换的TIFF图像文件。可以一次选中多个文件进行批量转换。 3. 选择输出文件夹,即转换后的ENVI文件保存的位置。 4. 在对话框中选择转换后的文件格式ENVI标准格式,可以根据需要调整其他参数,比如文件的投影信息、数据类型等。 5. 点击"Convert"按钮开始批量转换过程。 6. 程序会逐个对选中的TIFF文件进行转换,并在指定的输出文件夹中生成对应的ENVI文件。 7. 转换完成后,可以通过ENVI软件打开转换后的文件,进行进一步的处理和分析。 需要注意的是,在进行批量转换时,确保所选的TIFF文件格式ENVI所支持的,避免进行转换的文件格式不兼容的情况。 通过上述步骤,即可将TIFF格式图像批量转换为ENVI标准格式,并能够在ENVI软件中进行处理和分析。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值