471 lines
17 KiB
C#
471 lines
17 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.Concurrent;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace JoyD.Windows.CS.Toprie
|
||
{
|
||
/// <summary>
|
||
/// 温度数据模型类,用于存储和处理红外热像仪采集的温度数据
|
||
/// </summary>
|
||
public class TemperatureData
|
||
{
|
||
/// <summary>
|
||
/// 静态温度数据映射表,线程安全
|
||
/// 键:宽_高
|
||
/// 值:x_y -> x,y
|
||
/// </summary>
|
||
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string,List<int>>> _temperatureMap = new ConcurrentDictionary<string, ConcurrentDictionary<string, List<int>>>();
|
||
|
||
/// <summary>
|
||
/// 原始温度数据矩阵(未温补)
|
||
/// 格式: [行][列]
|
||
/// </summary>
|
||
public float[,] TemperatureMatrix { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 实际温度数据矩阵(温补后的512×384矩阵)
|
||
/// 格式: [行][列]
|
||
/// </summary>
|
||
public float[,] RealTemperatureMatrix { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 温度数据的宽度(列数)
|
||
/// </summary>
|
||
public int Width { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 温度数据的高度(行数)
|
||
/// </summary>
|
||
public int Height { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 温度采集时间戳
|
||
/// </summary>
|
||
public DateTime Timestamp { get; set; }
|
||
|
||
/// <summary>
|
||
/// 最低温度值
|
||
/// </summary>
|
||
public float MinTemperature { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 最高温度值
|
||
/// </summary>
|
||
public float MaxTemperature { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 平均温度值
|
||
/// </summary>
|
||
public float AverageTemperature { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 根据原始数据创建温度数据实例
|
||
/// </summary>
|
||
/// <param name="rawData">原始温度数据(每个温度点为2字节)</param>
|
||
/// <param name="width">温度矩阵宽度</param>
|
||
/// <param name="height">温度矩阵高度</param>
|
||
/// <param name="compensationValue">温度补偿值(从sdk_get_comp_temp获取)</param>
|
||
public TemperatureData(byte[] rawData, int width, int height, float compensationValue = 0)
|
||
{
|
||
if (rawData == null)
|
||
throw new ArgumentNullException(nameof(rawData));
|
||
|
||
if (width <= 0 || height <= 0)
|
||
throw new ArgumentException("宽度和高度必须大于0");
|
||
|
||
Width = width;
|
||
Height = height;
|
||
TemperatureMatrix = new float[height, width];
|
||
// 创建512×384的实际温度矩阵
|
||
RealTemperatureMatrix = new float[384, 512];
|
||
Timestamp = DateTime.Now;
|
||
String key = $"{width}_{height}";
|
||
ConcurrentDictionary<String,List<int>> map = _temperatureMap.GetOrAdd(key, new ConcurrentDictionary<string, List<int>>());
|
||
if (map.IsEmpty)
|
||
{
|
||
// 计算映射比例(从原始分辨率映射到512×384)
|
||
float rowRatio = 384.0f / Height;
|
||
float colRatio = 512.0f / Width;
|
||
for (int row = 0; row < 384; row++)
|
||
{
|
||
for (int col = 0; col < 512; col++)
|
||
{
|
||
int x = (int)(col / colRatio);
|
||
int y = (int)(row / rowRatio);
|
||
key = $"{x}_{y}";
|
||
List<int> toXY = map.GetOrAdd(key, new List<int>());
|
||
toXY.Add(row);
|
||
toXY.Add(col);
|
||
}
|
||
}
|
||
}
|
||
ParseRawData(rawData, compensationValue);
|
||
CalculateStatistics();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析原始温度数据
|
||
/// </summary>
|
||
/// <param name="rawData">原始温度数据</param>
|
||
/// <param name="compensationValue">温度补偿值</param>
|
||
private void ParseRawData(byte[] rawData, float compensationValue)
|
||
{
|
||
// 检查数据是否有效
|
||
if (rawData == null || rawData.Length == 0)
|
||
{
|
||
throw new ArgumentNullException(nameof(rawData), "原始温度数据为空");
|
||
}
|
||
|
||
// 根据SDK文档要求,使用24字节头部
|
||
const int HEADER_SIZE = 24;
|
||
|
||
// 确保数据长度足够(至少包含头部)
|
||
if (rawData.Length < HEADER_SIZE)
|
||
{
|
||
throw new ArgumentException("数据长度不足,无法解析");
|
||
}
|
||
|
||
String key = $"{Width}_{Height}";
|
||
bool hasMap = _temperatureMap.TryGetValue(key, out ConcurrentDictionary<String, List<int>> map);
|
||
// 计算温度数据长度和像素总数
|
||
int dataLength = rawData.Length - HEADER_SIZE;
|
||
int pixelCount = dataLength / 2; // 每个像素2字节
|
||
|
||
// 计算最大可处理的像素数
|
||
int maxPixels = Math.Min(pixelCount, Width * Height);
|
||
|
||
// 跳过24字节头部,按照行优先顺序解析温度数据
|
||
for (int i = 0; i < maxPixels; i++)
|
||
{
|
||
// 计算行列索引
|
||
int row = i / Width;
|
||
int col = i % Width;
|
||
|
||
if (row < Height && col < Width)
|
||
{
|
||
// 计算数据偏移量(跳过24字节头部)
|
||
int offset = HEADER_SIZE + i * 2;
|
||
|
||
if (offset + 1 < rawData.Length)
|
||
{
|
||
// 根据SDK文档要求,温度值计算方法:(H×256+L)/10,单位为摄氏度
|
||
// H为高8位,L为低8位
|
||
int highByte = rawData[offset + 1]; // 高8位
|
||
int lowByte = rawData[offset]; // 低8位
|
||
int tempValue = (highByte << 8) | lowByte;
|
||
|
||
// 计算实际温度值:(H×256+L)/10
|
||
float rawTemperature = tempValue / 10.0f;
|
||
|
||
// 应用温度补偿值
|
||
float compensatedTemperature = rawTemperature + compensationValue;
|
||
|
||
// 原始温度存入TemperatureMatrix
|
||
TemperatureMatrix[row, col] = compensatedTemperature;
|
||
if (hasMap)
|
||
{
|
||
// 温补后的温度存入RealTemperatureMatrix(映射到512×384矩阵)
|
||
MapToRealTemperatureMatrix($"{col}_{row}", map, compensatedTemperature);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将原始温度数据映射到512×384的实际温度矩阵
|
||
/// </summary>
|
||
/// <param name="key">映射键</param>
|
||
/// <param name="map">映射表</param>
|
||
/// <param name="temperature">温度值</param>
|
||
private void MapToRealTemperatureMatrix(String key, ConcurrentDictionary<String, List<int>> map, float temperature)
|
||
{
|
||
if (!map.TryGetValue(key, out List<int> xy)) return;
|
||
for (int idx = 0; idx < xy.Count; idx += 2)
|
||
{
|
||
// 设置实际温度值
|
||
RealTemperatureMatrix[xy[idx], xy[idx+1]] = temperature;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算温度统计信息
|
||
/// </summary>
|
||
private void CalculateStatistics()
|
||
{
|
||
float sum = 0;
|
||
float min = float.MaxValue;
|
||
float max = float.MinValue;
|
||
int count = 0;
|
||
|
||
for (int row = 0; row < Height; row++)
|
||
{
|
||
for (int col = 0; col < Width; col++)
|
||
{
|
||
float temp = TemperatureMatrix[row, col];
|
||
sum += temp;
|
||
min = Math.Min(min, temp);
|
||
max = Math.Max(max, temp);
|
||
count++;
|
||
}
|
||
}
|
||
|
||
MinTemperature = min;
|
||
MaxTemperature = max;
|
||
AverageTemperature = count > 0 ? sum / count : 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定区域内的温度统计信息
|
||
/// </summary>
|
||
/// <param name="startRow">起始行</param>
|
||
/// <param name="startCol">起始列</param>
|
||
/// <param name="endRow">结束行</param>
|
||
/// <param name="endCol">结束列</param>
|
||
/// <returns>区域温度统计信息</returns>
|
||
public RegionTemperatureStats GetRegionStatistics(int startRow, int startCol, int endRow, int endCol)
|
||
{
|
||
// 参数验证和边界检查
|
||
startRow = Math.Max(0, startRow);
|
||
startCol = Math.Max(0, startCol);
|
||
endRow = Math.Min(Height - 1, endRow);
|
||
endCol = Math.Min(Width - 1, endCol);
|
||
|
||
if (startRow > endRow || startCol > endCol)
|
||
return new RegionTemperatureStats(0, 0, 0, 0);
|
||
|
||
float sum = 0;
|
||
float min = float.MaxValue;
|
||
float max = float.MinValue;
|
||
int count = 0;
|
||
|
||
for (int row = startRow; row <= endRow; row++)
|
||
{
|
||
for (int col = startCol; col <= endCol; col++)
|
||
{
|
||
float temp = TemperatureMatrix[row, col];
|
||
sum += temp;
|
||
min = Math.Min(min, temp);
|
||
max = Math.Max(max, temp);
|
||
count++;
|
||
}
|
||
}
|
||
|
||
float avg = count > 0 ? sum / count : 0;
|
||
return new RegionTemperatureStats(min, max, avg, count);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将温度矩阵转换为字节数组(用于保存或传输)
|
||
/// </summary>
|
||
/// <returns>温度数据的字节数组表示</returns>
|
||
public byte[] ToByteArray()
|
||
{
|
||
// 创建一个包含所有必要信息的字节数组
|
||
// 包括宽度、高度、时间戳和温度数据
|
||
int headerSize = 4 + 4 + 8; // width(4) + height(4) + timestamp(8)
|
||
int dataSize = Width * Height * 4; // 每个温度值使用4字节float
|
||
byte[] result = new byte[headerSize + dataSize];
|
||
|
||
// 写入头信息
|
||
Buffer.BlockCopy(BitConverter.GetBytes(Width), 0, result, 0, 4);
|
||
Buffer.BlockCopy(BitConverter.GetBytes(Height), 0, result, 4, 4);
|
||
Buffer.BlockCopy(BitConverter.GetBytes(Timestamp.Ticks), 0, result, 8, 8);
|
||
|
||
// 写入温度数据
|
||
int dataIndex = headerSize;
|
||
for (int row = 0; row < Height; row++)
|
||
{
|
||
for (int col = 0; col < Width; col++)
|
||
{
|
||
byte[] tempBytes = BitConverter.GetBytes(TemperatureMatrix[row, col]);
|
||
Buffer.BlockCopy(tempBytes, 0, result, dataIndex, 4);
|
||
dataIndex += 4;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从字节数组创建TemperatureData实例
|
||
/// </summary>
|
||
/// <param name="data">包含温度数据的字节数组</param>
|
||
/// <returns>TemperatureData实例</returns>
|
||
public static TemperatureData FromByteArray(byte[] data)
|
||
{
|
||
if (data == null || data.Length < 16) // 至少需要头信息的长度
|
||
throw new ArgumentException("无效的数据格式");
|
||
|
||
int width = BitConverter.ToInt32(data, 0);
|
||
int height = BitConverter.ToInt32(data, 4);
|
||
long ticks = BitConverter.ToInt64(data, 8);
|
||
DateTime timestamp = new DateTime(ticks);
|
||
|
||
// 验证数据完整性
|
||
int expectedSize = 16 + width * height * 4;
|
||
if (data.Length != expectedSize)
|
||
throw new ArgumentException("数据长度不匹配");
|
||
|
||
// 创建温度矩阵
|
||
float[,] matrix = new float[height, width];
|
||
int dataIndex = 16;
|
||
|
||
for (int row = 0; row < height; row++)
|
||
{
|
||
for (int col = 0; col < width; col++)
|
||
{
|
||
matrix[row, col] = BitConverter.ToSingle(data, dataIndex);
|
||
dataIndex += 4;
|
||
}
|
||
}
|
||
|
||
// 创建TemperatureData实例
|
||
TemperatureData tempData = new TemperatureData
|
||
{
|
||
Width = width,
|
||
Height = height,
|
||
TemperatureMatrix = matrix,
|
||
Timestamp = timestamp
|
||
};
|
||
tempData.CalculateStatistics();
|
||
|
||
return tempData;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 私有构造函数,用于FromByteArray方法
|
||
/// </summary>
|
||
private TemperatureData()
|
||
{
|
||
// 初始化实际温度矩阵
|
||
RealTemperatureMatrix = new float[384, 512];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证温度数据的头部信息
|
||
/// </summary>
|
||
/// <param name="rawData">包含头部信息的原始数据</param>
|
||
/// <returns>如果头部信息有效返回true,否则返回false</returns>
|
||
private bool ValidateHeader(byte[] rawData)
|
||
{
|
||
// 根据SDK实际实现,头部信息的前几个字节包含标识字符和数据长度
|
||
|
||
// 检查原始数据不为空且长度足够
|
||
if (rawData == null || rawData.Length < 9)
|
||
return false;
|
||
|
||
// 1. 验证头部标识字符是否为"+TEMP"
|
||
string headerMark = Encoding.ASCII.GetString(rawData, 0, 5).TrimEnd('\0');
|
||
if (headerMark != "+TEMP")
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"警告: 头部标识不匹配,期望'+TEMP',实际为'{headerMark}'");
|
||
return false;
|
||
}
|
||
|
||
// 2. 检查数据长度是否合理(至少包含头部+最小温度数据)
|
||
if (rawData.Length < 9 + 2) // 9字节头部 + 至少1个温度点(2字节)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从头部解析温度数据长度
|
||
/// </summary>
|
||
/// <param name="rawData">包含头部信息的原始数据</param>
|
||
/// <returns>温度数据长度(字节数)</returns>
|
||
private int GetPayloadLengthFromHeader(byte[] rawData)
|
||
{
|
||
// 根据SDK实际实现,payload_length在头部的第5-8字节
|
||
// 注意:采用低字节在前的存储方式
|
||
if (rawData.Length >= 9)
|
||
{
|
||
return BitConverter.ToInt32(rawData, 5);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据像素数量和已知分辨率比例推算宽高
|
||
/// </summary>
|
||
/// <param name="pixelCount">像素总数</param>
|
||
/// <returns>宽高数组,格式为[宽度, 高度]</returns>
|
||
private int[] GetWidthHeightByPixelCount(int pixelCount)
|
||
{
|
||
// 已知设备支持的分辨率比例为4:3
|
||
// 检查是否为标准分辨率:
|
||
// 240×180 = 43200像素
|
||
// 384×288 = 110592像素
|
||
|
||
// 标准分辨率映射
|
||
var standardResolutions = new Dictionary<int, int[]> {
|
||
{ 43200, new int[] { 240, 180 } },
|
||
{ 110592, new int[] { 384, 288 } }
|
||
};
|
||
|
||
// 如果匹配标准分辨率,安全地返回对应值
|
||
if (standardResolutions.TryGetValue(pixelCount, out int[] resolution))
|
||
{
|
||
return resolution;
|
||
}
|
||
|
||
// 否则尝试根据像素数推算最接近4:3比例的宽高
|
||
float ratio = 4.0f / 3.0f;
|
||
int width = (int)Math.Sqrt(pixelCount * ratio);
|
||
int height = (int)(width / ratio);
|
||
|
||
// 确保宽高相乘尽可能接近像素数
|
||
while (width * height < pixelCount && width * (height + 1) <= pixelCount)
|
||
{
|
||
height++;
|
||
}
|
||
|
||
return new int[] { width, height };
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 区域温度统计信息
|
||
/// </summary>
|
||
public struct RegionTemperatureStats
|
||
{
|
||
/// <summary>
|
||
/// 区域最低温度
|
||
/// </summary>
|
||
public float MinTemperature { get; }
|
||
|
||
/// <summary>
|
||
/// 区域最高温度
|
||
/// </summary>
|
||
public float MaxTemperature { get; }
|
||
|
||
/// <summary>
|
||
/// 区域平均温度
|
||
/// </summary>
|
||
public float AverageTemperature { get; }
|
||
|
||
/// <summary>
|
||
/// 统计的温度点数量
|
||
/// </summary>
|
||
public int PointCount { get; }
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="minTemp">最低温度</param>
|
||
/// <param name="maxTemp">最高温度</param>
|
||
/// <param name="avgTemp">平均温度</param>
|
||
/// <param name="count">温度点数量</param>
|
||
public RegionTemperatureStats(float minTemp, float maxTemp, float avgTemp, int count)
|
||
{
|
||
MinTemperature = minTemp;
|
||
MaxTemperature = maxTemp;
|
||
AverageTemperature = avgTemp;
|
||
PointCount = count;
|
||
}
|
||
}
|
||
} |