ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大疆热红外R_JPEG解析到温度TIF拼接全流程指南

大疆热红外R_JPEG解析到温度TIF拼接全流程指南 每次拿到大疆无人机拍回来的热红外数据我首先会做的事就是打开目录看一眼文件名。如果你的M300 RTK或Mavic 3T拍完之后是一堆DJI_20230701_T.JPG那基本可以确定我们面对的是同一类问题这些文件就是所谓的R_JPEG格式热红外图里面有真实温度信息但用普通看图软件打开只能看到一张灰蒙蒙或伪彩色的图距离交成果差的不是一星半点。这篇博文我直接把这套流程完整拆开——从大疆TSDK怎么解析R_JPEG到生成带地理坐标的温度TIF再到把零散影像拼成一张可分析的温度索引图全程走一遍适合无人机巡检、光伏电站检测、建筑渗漏排查这块儿需要处理大量热红外数据的人参考。1. 项目背景与整体思路1.1 为什么非要从R_JPEG转成温度TIF大疆的R_JPEG格式在行业内说得直白一点就是“辐射JPEG”它在标准JPEG画面的基础上额外嵌入了每个像素对应的辐射温度数据。也就是说你拿到的每一个JPG文件本质上是一个装了“热成像数据普通图像”的容器。这类文件的元数据里还有发射率、反射温度、大气温度、相对湿度等定标参数这些参数都能直接影响最终温度的准确性。平时我们用DJI Thermal Viewer或FLIR工具打开R_JPEG能直接看到伪彩图和点温、区域测温功能但真正要把几百张图做成一张大范围温度正射图靠手工一张张看是不可能的。必须先把R_JPEG转换成带有地理位置信息的TIF格式再做拼接。为什么选择TIF因为TIF能无损保存16bit甚至32bit的数据JPEG是8bit有损压缩存不了高精度的温度值同时PNG虽然无损但缺少GIS行业标准的地理标签支持后续在QGIS、GlobalMapper、ArcGIS里做二次分析也不方便。TIF不仅支持浮点型单波段温度数据还能嵌入坐标系统在影像分析软件里可以直接进行温度直方图统计、阈值分割、异常区域提取。1.2 从R_JPEG到温度TIF的流程设计整个处理链路拆成四段其实很清晰用大疆TSDK解析R_JPEG获得每个像素的绝对温度矩阵读取R_JPEG自带的定位定姿信息经度、纬度、高度、机头朝向等把温度矩阵配合地理信息写成GeoTIFF完成数据格式和坐标系统的落地对全部温度TIF做镶嵌拼接最终输出一张带真实温度值、可直接量算的TIF成果。这个流程的核心是第一步和第三步。第一步决定了温度和实际物体的偏差有多大第三步决定了后续拼接和分析能不能正确对位。我见过很多人在后天处理时偷懒直接把伪彩JPEG转成TIF再拼接结果看似有温度分布其实里面存的是RGB色彩映射值完全无法做真实温度计算。所以我建议宁可前期麻烦一点也要把R_JPEG先解析成温度矩阵后面所有分析都在这套数据上进行返工成本最低。2. 大疆TSDK环境与R_JPEG温度解析2.1 TSDK的下载与环境配置TSDK全称DJI Thermal SDK是大疆官方提供的热红外软件开发工具包用于从大疆各型号热红外相机拍出的R_JPEG文件中解析温度数据和辐射参数。它支持Windows、Linux和部分嵌入式平台提供C/C接口目前主流的版本是TSDK 3.x。下载TSDK需要在大疆开发者平台注册开发者账号然后下载对应平台的SDK包。解压后的目录一般包含include、lib、samples和docs等文件夹。我在Windows上的实际配置方法是把include目录添加到项目的附加包含目录把对应架构x64热红外数据量通常很大32位程序容易溢出的lib目录添加到附加库目录把SDK运行时要加载的DLL文件复制到exe同目录下或者直接加入PATH编译选项注意使用C11或更高标准部分示例用到了C11特性。Linux环境下如果编译不过优先检查g版本和依赖库是否齐全SDK文档里明确说需要特定版本的libc和libstdc用apt装全build-essential通常就够了。配置这块没有什么玄学严格按官方README走一遍就成真正花时间的地方在写解析逻辑。2.2 核心API与最小解析示例下面这段C代码是我基于TSDK 3.x接口整理出来的核心解析示例不同版本API命名可能有微调思路完全一致。它做的事情非常直接加载R_JPEG文件自动分析出热红外数据取出温度矩阵为后面写TIF做准备。#include iostream #include thermal_sdk.h int main() { // 初始化TSDK dji::thermal::ThermalSDK sdk; { auto res sdk.Initialize(); if (res ! dji::thermal::ThermalResult::Success) { std::cerr TSDK init failed std::endl; return -1; } } // 加载R_JPEG热红外文件 dji::thermal::ThermalImage thermalImage; auto resLoad thermalImage.LoadFromFile(DJI_20230701_T.JPG); if (resLoad ! dji::thermal::ThermalResult::Success) { std::cerr load image failed std::endl; return -1; } // 自动分析获取温度数据 thermalImage.Analyse(); // 获取温度图像对象 dji::thermal::ThermalImageTemperature tempImage; thermalImage.GetTemperature(tempImage); int width tempImage.GetWidth(); int height tempImage.GetHeight(); // 这里拿到的是绝对温度浮点数组单位可能是开尔文需根据SDK版本确认 float* tempArray tempImage.GetTemperatureData(); if (!tempArray) { std::cerr temperature data is null std::endl; return -1; } // 示例转成摄氏度并输出中心点温度 int centerIdx (height / 2) * width (width / 2); double celsius tempArray[centerIdx] - 273.15; std::cout image size: width x height , center temperature: celsius C std::endl; // TODO: 把tempArray写成GeoTIFF return 0; }很多新手第一次跑这段代码容易犯一个错加载图像后忘了调用Analyse。不调这一步GetTemperatureData()拿到的数据可能是空的或未经过辐射定标温度值完全不可用。另外要注意温度数据的内存生命周期TSDK对温度矩阵有内部管理你拿到的是缓冲区指针不要手动delete用完即可。如果你想把温度数据复制出来建议直接用memcpy到自己的容器里后面写TIF时更灵活。2.3 温度定标原理与数据精度关键点为什么不能直接从JPEG像素值估温度因为R_JPEG文件里同时保存了可见光图像数据和热红外感光元件数据而感光元件的原始数字值DN值经过复杂的辐射校正后才变成温度。TSDK内部的定标流程大致是读取相机参数和当前环境参数结合非均匀性校正系数、大气透射率、反射温度等把DN值映射成绝对辐射亮度再通过普朗克公式的反函数换算成温度。在用TSDK解析时有几个参数的实际影响非常大。第一是发射率大疆H20T默认发射率一般是0.95这在很多材质上都够用但如果你测的是抛光金属或水面发射率会明显偏低测出来的温度会偏差很大第二是反射温度在室外晴朗夏天和阴天差异很大如果影像上出现明显的高反光物体需要检查这个参数的设置第三是拍摄距离远距离测量时大气吸收会降低温度读数TSDK会尝试用大气参数修正但极端情况下仍建议在同距离范围做校准。我在实际项目里处理R_JPEG有个习惯拿到一批数据后先用TSDK输出十来张图的中心点温度再和现场用接触式测温仪或者黑体校温仪测得的真实温度做对比看看系统误差记录下来。这个偏差会因为你设置的目标距离、发射率不同而不同做好标定记录比任何后期算法都管用。3. 生成带地理坐标的温度TIF3.1 为什么用TIF存温度数据这一步是整条流水线的中转站也是最容易被忽略的一步。生成温度TIF不仅为了拼接更是为了让整张影像具备“物理温度量纲”。一台普通电脑上的看图软件无法直接显示32位浮点TIF的像素值但GIS和遥感软件可以所以这个TIF天生就是给二次分析用的。相比源文件R_JPEG温度TIF的优势非常明显单波段存温度数据量更小更规整可以无损保存16bit或32bit数值不会因为二次压缩损失温度精度可以内嵌地理坐标方便后续与可见光正射影像叠加、与DEM对齐甚至在在线地图上进行坐标核对。3.2 用GDAL将温度矩阵写成GeoTIFF在上面TSDK解析得到tempArray之后我习惯用PythonGDAL完成温度矩阵落地为GeoTIFF的工作。原因是GDAL对GeoTIFF的写入非常简单可以在写文件时直接指定投影和地理变换参数不需要自己拼TIF标签。温度矩阵从C导出成二进制文件后在Python里用numpy读入再配合R_JPEG元数据里的GPS信息写入GeoTIFFimport numpy as np from osgeo import gdal, osr # 假设tempArray已由C程序导出成raw文件或直接用numpy生成 width 640 height 512 temp np.fromfile(temp_float32.raw, dtypenp.float32).reshape(height, width) # 使用R_JPEG里解析出的中心点经纬度 lon 116.392822 # 示例经度 lat 39.907563 # 示例纬度 # 估算地面分辨率飞行高度100mH20T热红外FOV约40°×30° alt_m 100.0 hfov_deg 40.0 vfov_deg 30.0 gsd_x 2.0 * alt_m * np.tan(np.deg2rad(hfov_deg / 2)) / width gsd_y 2.0 * alt_m * np.tan(np.deg2rad(vfov_deg / 2)) / height # 这里用中心点坐标和粗略分辨率构建仿射六参数完整严谨做法需结合云台姿态 # 实际项目建议用飞行器POS做严格几何校正后再输出 geotransform ( lon - width / 2.0 * gsd_x, # 左上角经度 gsd_x, # 东西方向分辨率 0.0, lat height / 2.0 * gsd_y, # 左上角纬度 0.0, -gsd_y # 南北方向分辨率注意为负 ) crs osr.SpatialReference() crs.ImportFromEPSG(4326) # WGS84 driver gdal.GetDriverByName(GTiff) out_path output_temperature.tif ds driver.Create(out_path, width, height, 1, gdal.GDT_Float32) ds.SetGeoTransform(geotransform) ds.SetProjection(crs.ExportToWkt()) band ds.GetRasterBand(1) band.WriteArray(temp) # 温度单位信息方便后续理解 band.SetMetadata({UNITS: Celsius, SOURCE: DJI_TSDK}) band.FlushCache() ds None这里需要多说一句上面代码里用中心点经纬度和粗略地面分辨率来构建仿射参数是“够用不求精”的做法。如果飞行姿态角度较大或地形起伏明显一定要用R_JPEG元数据里的云台偏航角、俯仰角、翻滚角做严格几何校正否则后面拼接会错位。对于平缓区域、正射拍摄的巡检数据这种简化定位通常可以接受。更严谨的做法是用Pix4D或DJI Terra做空中三角测量把每一张影像的精确外方位元素解算出来再写GeoTIFF。3.3 导出分辨率与像元尺寸的选择很多人刚接触GlobalMapper的时候会困惑“导出TIF分辨率怎么选”其实这个问题背后是采样间隔GSD的选择。热红外相机的传感器分辨率本身不高H20T的热红外镜头是640×512像素面积覆盖不大导出时如果设置跨量级的高分辨率比如1cm系统会强行插值放大影像看起来平滑了但本质上没增加任何有效信息反而让文件体积暴涨处理速度也变慢。我的经验是如果只做热红外温度分析导出分辨率设为源影像GSD即可比如飞行高度100m时H20T热红外GSD大约在20-25cm/像素如果需要和可见光正射影像叠加对比则需要把热红外TIF重采样到可见光的GSD比如5cm这样像素对齐最方便。下表是我常用的一组参考使用场景推荐导出分辨率说明热红外独立温度分析原始GSD20-25cm100m保留原始信息文件小计算快与5cm可见光正射叠加5cm重采样对齐便于像素级对比大面积巡检快速出图0.5m或1m缩小数据量适合整体趋势判断GlobalMapper自动拼接保持源分辨率镶嵌时统一分辨率避免变形3.4 其他数据跨格式转换的经验处理热红外数据时很多时候还需要和点云数据做融合。有人问过“CloudCompare怎么把点云保存成TIF格式”实际上CloudCompare本身不能直接写出标准的GeoTIFF温度栅格但它有一个变通做法先用Tools - Rasterize工具把点云插值成规则格网生成一张带高程或者其他属性的栅格图然后通过File - Export - Raster导出成GeoTIFF。如果你手里有带温度属性的点云比如热红外相机和LiDAR融合后的数据也可以用这个思路导出一张温度TIF。要点是导出前在Rasterize面板里设置好网格分辨率这个分辨率不能小于点云平均点间距否则大量空值会污染插值结果。4. 热红外影像的拼接实战4.1 温度拼接与视觉拼接的区别热红外影像拼接和普通可见光拼接最大的区别在于你不能只看“看起来对齐了”就行你要保证每一处的温度数值仍然代表真实的物理温度。所以我一直强调先把R_JPEG全部转成温度TIF再拼接而不是拿一张伪彩JPG去做视觉拼接。视觉拼接导出的是RGB图温度值已经变成了色板的映射后期无论如何都无法恢复真实温度。另外热红外图像的特征点往往比可见光少得多。地面纹理、颜色变化在热红外里体现得都不明显尤其是在均匀温度场下两张图的接缝处肉眼很难判断是否对齐这也是很多自动拼接算法在热红外数据上效果差的原因。所以热红外拼接的优先级应该是有POS/地理坐标就用地理坐标拼接没有可靠POS再考虑特征匹配千万不要反过来。4.2 GlobalMapper手工拼接我处理少量或中等数量的温度TIF时GlobalMapper是比较顺手的工具。操作路径大概是打开所有温度TIF注意确认每张图都带坐标在图层列表里全选右键选择“Mosaic”或使用图层菜单中的“拼接/镶嵌”工具设置输出投影类型和目标坐标系如果范围不大建议用UTM量距更准确设置输出分辨率为原始GSD级别这是前面强调过的关键点重叠区域的处理方式选“平均”或“加权平均”具体取决于数据情况如果只是气温测量平均融合能降低噪声。GlobalMapper的拼接会生成一个相对平滑的单张TIF如果拼接后接缝还是很明显可以尝试在每个图层的透明度上做细微调整或者在重叠区使用羽化。但这种“视觉接缝”在温度分析里通常不影响总体制图只要温度TIF本身是对的就行。4.3 DJI Terra与Pix4D自动化重建如果数据量很大比如一个完整的光伏电站几千张热红外照片建议直接用DJI Terra或者Pix4D做自动化重建。DJI Terra对R_JPEG格式的支持很好导入后能自动识别热红外传感器模块通过空中三角测量解算相机位姿输出正射影像。Pix4D同样可以导入DJI热红外数据做处理生成的DOM精度更高也支持辐射温度信息的保留。用DJI Terra时我会建议在“建图航拍”或“倾斜摄影”项目里单独导入热红外影像不要和可见光混在一起跑空三因为热红外影像纹理弱混在一起容易导致匹配点不足空三容易断裂。先把热红外单独跑一遍生成热红外DOM再和可见光DOM在GIS里做配准叠加这是比较稳的路线。需要特别注意DJI Terra导出的DOM默认是可视化图像温度信息可能被映射成8bit色板。如果你要做真实温度分析确认一下是否有开启辐射热红外输出模式。如果只是需要一张用于展示的温度分布图可以接受如果后续要统计最高温位置、计算异常点数量还是建议回到温度TIF基础上做。4.4 Python OpenCV无地理参考拼接方案在没有GPS/RTK信息或者飞行姿态数据缺失的情况下只能靠图像特征来拼接。这时OpenCV是一个可行的方案。整体思路是先提取每张图像的特征点找到相邻图像之间的特征匹配计算单应性矩阵然后透视变换叠加。代码骨架大概是这样import cv2 import numpy as np def load_temperature_as_gray(path): # 温度TIF如果是Float32先归一化到0-255用于特征提取 from osgeo import gdal ds gdal.Open(path) band ds.GetRasterBand(1).ReadAsArray().astype(np.float32) band (band - band.min()) / (band.max() - band.min()) * 255 return band.astype(np.uint8) img1 load_temperature_as_gray(frame1.tif) img2 load_temperature_as_gray(frame2.tif) sift cv2.SIFT_create() kp1, des1 sift.detectAndCompute(img1, None) kp2, des2 sift.detectAndCompute(img2, None) bf cv2.BFMatcher(cv2.NORM_L2) matches bf.knnMatch(des1, des2, k2) good [m for m, n in matches if m.distance 0.75 * n.distance] if len(good) 10: src_pts np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2) dst_pts np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2) M, mask cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) result cv2.warpPerspective(img1, M, (img2.shape[1] img1.shape[1], img2.shape[0])) result[0:img2.shape[0], 0:img2.shape[1]] img2但热红外影像的特征点提取效果取决于画面里有没有明显的温度差异区域。我在工厂房顶热红外数据上试过纯热红外影像之间匹配点经常只有个位数这时候可以尝试增强对比度、使用更宽松的阈值或者先和可见光影像做匹配再反演热红外影像的位置关系。后一种办法在工程上更稳定因为可见光纹理丰富特征匹配成功率更高。5. 常见问题与排查经验5.1 打不开、全灰、温度异常我在实际操作中积累了一份高频问题排查表直接给到大家现象可能原因解决办法R_JPEG用看图软件打开是全灰看图软件不支持辐射信息显示的是原始红外数组低8位换用TSDK或DJI Thermal Viewer查看TSDK读取后温度全为0忘了调Analyse()或内存被释放确认Analyse后立刻访问温度数据温度整体偏高/偏低十几度发射率、反射温度设置不匹配结合现场材料重新设置发射率温差大时重新标定TIF在GIS里显示全黑/全白32位浮点在普通渲染器里默认拉伸范围不对QGIS里设置渲染为最小/最大拉伸或转到16bit同一高温物体在两张图里温度不同拍摄距离、环境温度、大气路径差异尽量在同高度、同角度下拍摄保证数据一致性拼接结果存在明显接缝单应性变换只做了粗略对齐或融合权重简单改用地理坐标拼接或做多频段融合5.2 拼接错位与色差拼接错位第一个要排查的不是算法而是坐标系统是否一致。比如前期生成的GeoTIFF有些是WGS84经纬度有些误用了Web墨卡托坐标系跑到GlobalMapper里会叠不齐。检查办法很简单把任意两张TIF放进QGIS打开坐标显示看看经纬度是否合理如果跨了几个数量级马上查投影定义。另一个容易翻车的是色差问题热红外影像相邻两张图因为拍摄角度不同反射温度也不同导致同一目标在两张图上的温度值出现明显跳变。解决这类色差一是尽量让飞行航向和旁向重叠率足够大二是在拼接时选择“平均”而不是“首选图层”。如果叠加重叠区域的平均温度还是波动比较大建议用直方图匹配把相邻帧的温度分布拉到一致。5.3 TIF后期做温度分析tem分析实操把TIF拼好以后做温度分析其实就变得很直接了。在QGIS里打开GeoTIFF用栅格计算器可以快速提取超过阈值的区域例如from osgeo import gdal import numpy as np ds gdal.Open(mosaic_temperature.tif) band ds.GetRasterBand(1).ReadAsArray() # 查看温度统计特征 print(min:, np.nanmin(band), max:, np.nanmax(band), mean:, np.nanmean(band)) # 提取温度大于70℃的异常区域 hot_mask (band 70.0).astype(np.uint8) out_ds gdal.GetDriverByName(GTiff).Create( hot_areas.tif, band.shape[1], band.shape[0], 1, gdal.GDT_Byte) out_ds.GetRasterBand(1).WriteArray(hot_mask) out_ds.SetGeoTransform(ds.GetGeoTransform()) out_ds.SetProjection(ds.GetProjection()) out_ds None这样得到的hot_areas.tif就是一张0/1掩膜可以直接转成矢量面圈出需要检修的位置。整个流程从R_JPEG到温度TIF再到高温异常圈定完全跑通在本地电脑上就能完成适合巡检团队快速出结果。我在光伏巡检项目里就是这么处理的把高温组件挨个圈出来生成KML发到现场手机上导航效率比手工翻图高很多。5.4 几条避坑经验最后整理几条经验教训都是踩过的坑换来的。第一R_JPEG原文件一定要做双重备份它包含的温度参数是不可再生的如果你把它另存为普通JPEG再导出温度TIF很可能丢失辐射元数据再想重新解析就必须重新飞行了。第二多光谱和热红外相机在采集数据时尽量保持镜头清洁灰尘和划痕在热红外图像上一样会产生固定影响而且这种影响在后期很难通过软件消除。第三在处理大量温度TIF拼接时建议按航带分块拼接最后再合并一次性处理上千张影像很容易出现内存溢出或者软件崩溃的问题分块处理不仅稳定出错时定位问题也方便得多。我在实际项目中踩过最大的坑是数字温漂电池电压下降后热红外相机的背景噪声会略有变化反映在温度数据上就是同一目标早晚温差能差出1-2℃。这个问题不用追求完美修正只要在数据采集任务前后各记录一次参考温度做线性修正就能明显提升拼接成果的一致性。无人机电池电压变化导致的热红外温度漂移虽然是小概率事件但真碰到了会非常棘手提前做好现场记录是最好的保险。
返回列表