适用于 大疆禅思 Zenmuse H20 系列拍摄的红外图片,依赖于大疆发布的TSDK(DJI Thermal SDK),可用于视频的动态测温。
其实对TSDK并未深入研究,只是某一个小项目要使用TSDK处理H20拍摄的红外照片,利用TSDK中的sample又进行了一些处理,能够实现将对应像素、指定区域的温度进行读取。
大疆社区对这个TSDK的使用描述较少,为了任务只好硬着头皮上。
首先,对TSDK中的sample进行编译,这里有一点,默认编译脚本使用vs2015,但是我这里只安装了vs2017,只需要找到build.bat,将"Visual Studio 14 2015"改为"Visual Studio 15 2017"保存即可。
编译生成的程序中,我只用到了“dji_irp.exe”这一个,使用的参数为
"dji_irp.exe" -s "image/file/path" -a measure -o "path/of/raw/output"
其他命令和参数可以使用-help查看,上边省略的参数是INT32,即使用16位数据保存一个温度*10的整数,对得到的数值*0.1即为0.1精度的真实温度。
它可以将红外图片生成为16位raw文件,原来没有很明白,后来才渐渐搞懂,也就是说,每16位(2字节)对应原始图片的一个像素。起初对jpg格式的保存方式也不太了解,只好一点一点尝试。
好在方向没有搞错,大致保存方式为从左上角0,0开始,一行一行的存储,所以raw文件中也是如此保存的了。
这里也要说明一下,我确认没有搞错方向的依据是与大疆发布的大疆红外热分析工具的像素温度、最高最低温度点保持一致。
我定义了一个类RawParser来解析生成的raw文件数据,主要功能有:
1、获取指定点坐标的温度
2、获取指定区域的平均温度
3、获取指定区域最高、最低温度及其坐标
先上代码。
RawParser.h
#pragma once
#include <string>
#define IN
#define OUT
struct RectArea
{
int x;
int y;
int w;
int h;
};
struct PixPoint
{
int x;
int y;
};
struct PixTemperature
{
PixPoint point;
int temperature;//tenfold
};
class RawParser
{
public:
RawParser(const char* filePath, int width, int height);
~RawParser();
//将raw文件内容加载到内存中再进行操作和计算
bool preloadRaw();
//将像素点的坐标转换为内存索引
long pointToIndex(int x, int y);
//通过像素的坐标xy获取对应的温度,temperature为10倍温度的整数(温度取1位有效位)
bool getPointTemperature(int x IN, int y IN, int &temperature OUT);//tenfold
//计算指定范围内像素点温度的平均值、最高值、最高值坐标、最低值、最低值坐标
bool rangeCaculate(RectArea rect IN, float &temperature OUT, PixTemperature &maxPT OUT, PixTemperature &minPT OUT);
//rect的宽高应大于0
bool rangeCaculate(RectArea rect IN, float &temperature OUT);
private:
FILE *m_imageFP;
int m_imageW;
int m_imageH;
size_t m_fileSize;
bool m_bHasPreload;
std::string m_rawData;
};
RawParser.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "RawParser.h"
#define RAW_LENGTH 16
typedef unsigned char BYTE;
RawParser::RawParser(const char* filePath, int width, int height)
:m_imageW(width)
,m_imageH(height)
,m_bHasPreload(false)
{
m_imageFP = nullptr;
if ((m_imageFP = fopen(filePath, "rb")) == nullptr)
{
m_fileSize = 0;
printf("can not open the raw image");
return;
}
else
{
//读取文件总大小
fseek(m_imageFP, 0, SEEK_END);
m_fileSize = ftell(m_imageFP);
fseek(m_imageFP, 0, SEEK_SET);
}
}
RawParser::~RawParser()
{
if (m_imageFP)
{
fclose(m_imageFP);
}
}
bool RawParser::preloadRaw()
{
char* _content = (char*)malloc(m_fileSize);
if (!_content)
{
return false;
}
size_t _cnt = fread(_content, 1, m_fileSize, m_imageFP);
if (_cnt != m_fileSize)
{
free(_content);
return false;
}
m_rawData.assign(_content, _cnt);
m_bHasPreload = true;
return true;
}
bool RawParser::getPointTemperature(int x, int y, int &temperature)
{
if (m_bHasPreload)
{
if (m_rawData.empty())
{
return false;
}
}
else
{
if (!m_imageFP)
{
return false;
}
}
if (m_imageW <= x)
{
return false;
}
if (m_imageH <= y)
{
return false;
}
unsigned char tmpValue[2];
long _offset = (m_imageW*y + x) * 2;//每个温度数据占用两个字节16位
if (m_fileSize < _offset)
{
return false;
}
if(m_bHasPreload)
{
tmpValue[0] = m_rawData.at(_offset);
tmpValue[1] = m_rawData.at(_offset + 1);
}
else
{
fseek(m_imageFP, _offset, 0);
//从像素坐标对应的文件位置读出两个字节数据
size_t _ret = fread(tmpValue, 1, 2, m_imageFP);
if (_ret != 2)
{
return false;
}
}
temperature = tmpValue[1];
//字节序转换
temperature = temperature << 8;
temperature = temperature + (tmpValue[0]);
return true;
}
bool RawParser::rangeCaculate(RectArea rect IN, float &temperature OUT, PixTemperature &maxPT OUT, PixTemperature &minPT OUT)
{
if (m_bHasPreload)
{
if (m_rawData.empty())
{
return false;
}
}
else
{
if (!m_imageFP)
{
return false;
}
}
if ( rect.x < 0
|| rect.y < 0
|| rect.w < 0
|| rect.h < 0
|| rect.x > m_imageW
|| rect.y > m_imageH
|| (rect.x+rect.w) > m_imageW
|| (rect.y+rect.h) > m_imageH)
{
return false;
}
PixPoint maxTPP, minTPP,bottomRight;
int maxTemp, minTemp;
bool pointsReady = false;//最高最低温度初始化标记
//fseek(m_imageFP, 0, 0);
long pointCount = 0;
unsigned char _temp[2];
//所有点温度之和。使用__int64保存结果足够保证计算时不会溢出,
//温度保存16位,范围是±32767,_int64表示范围是±9,223,372,036,854,775,808
//按照最大值计算,可保存281,474,976,710,656个数据之和
//一张1080*1080的图片只有1,166,400个像素
__int64 _temperature = 0;
int pointTemp;//点温度
bottomRight.x = rect.x + rect.w;
bottomRight.y = rect.y + rect.h;
for (int x = rect.x; x < bottomRight.x;++x)
{
for (int y = rect.y; y < bottomRight.y; ++y)
{
long _index = pointToIndex(x, y);
if (m_fileSize < _index)
{
return false;
}
if (m_bHasPreload)
{
_temp[0] = m_rawData.at(_index);
_temp[1] = m_rawData.at(_index + 1);
}
else
{
fseek(m_imageFP, _index, 0);
fread(_temp, 1, 2, m_imageFP);
}
pointTemp = ((_temp[1] << 8) & 0xFF00);
pointTemp += _temp[0];
//判断最高、最低温度并记录坐标
if (pointsReady)
{
if (pointTemp < minTemp)
{
minTemp = pointTemp;
minTPP.x = x;
minTPP.y = y;
}
else if (pointTemp > maxTemp)
{
maxTemp = pointTemp;
maxTPP.x = x;
maxTPP.y = y;
}
}
else
{
//最高、最低温度初始化为第一个点的温度
minTemp = pointTemp;
minTPP.x = x;
minTPP.y = y;
maxTemp = pointTemp;
maxTPP.x = x;
maxTPP.y = y;
pointsReady = true;
}
_temperature += pointTemp;
++pointCount;
}
}
if (pointCount == 0)
{
return false;
}
temperature = _temperature / pointCount * 0.1;
maxPT.point = maxTPP;
maxPT.temperature = maxTemp;
minPT.point = minTPP;
minPT.temperature = minTemp;
return true;
}
bool RawParser::rangeCaculate(RectArea rect IN, float &temperature OUT)
{
PixTemperature maxPT, minPT;
return rangeCaculate(rect, temperature, maxPT, minPT);
}
long RawParser::pointToIndex(int x, int y)
{
if (m_imageW <= x)
{
return false;
}
return ((m_imageW*y + x) * 2);//每个温度数据占用两个字节16位
}
起初摸索的时候采用了文件指针偏移的方法实时从文件读取,效率非常低,后来直接把文件全部读到内存中再计算,效率就提高很多,640*512大小的图片,一次全图计算大概只需要500ms。
其中关键的部分就是找对像素坐标对应的数据位置,其他就简单多了。
后边附上Qt写的调用Demo,感兴趣的可以参考一下。
===
补充一下,RawParser这个类中并未使用依赖平台的库或头文件,只是简单的数学和逻辑运算,基本上可以轻松移植。写这个东西本来就是研究使用方法,给其他同事参考的,他是用的java,没有遇到什么问题。