2025-05-27 15:46:31 +08:00

365 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import cv2
import numpy as np
import pandas as pd
from scipy import ndimage
class ThermalProcessor:
"""
热成像数据处理类
将CSV格式的热成像数据转换为热图
"""
def __init__(self):
"""初始化热成像处理器"""
# 定义默认的颜色映射
self.default_colors = [
(0, 0, 0), # 黑色
(0, 0, 0.5), # 深蓝
(0, 0.7, 1), # 亮蓝
(0.2, 1, 1), # 青蓝
(0.4, 1, 0.6), # 青绿
(0.6, 1, 0.2), # 黄绿
(1, 1, 0), # 黄色
(1, 0.7, 0), # 橙色
(0.8, 0.4, 0.2), # 棕色
(0.7, 0.2, 0.5), # 紫红
(1, 0, 0) # 红色
]
def read_csv(self, file_path):
"""
读取CSV文件并解析数据
参数:
file_path: CSV文件路径
返回:
metadata: 元数据字典
data: 温度数据矩阵
"""
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
metadata = {}
start_index = 0
for i, line in enumerate(lines):
line = line.strip()
if line.startswith("#"):
# 解析元数据
if "宽度" in line:
metadata["width"] = int(line.split(":")[1].strip())
elif "高度" in line:
metadata["height"] = int(line.split(":")[1].strip())
elif "最小温度" in line:
metadata["min_temp"] = float(line.split(":")[1].strip().replace("°C", ""))
elif "最大温度" in line:
metadata["max_temp"] = float(line.split(":")[1].strip().replace("°C", ""))
elif "中心温度" in line:
metadata["center_temp"] = float(line.split(":")[1].strip().replace("°C", ""))
else:
start_index = i
break # 找到数据部分
# 读取表格数据
df = pd.read_csv(file_path, skiprows=start_index)
# 转换为numpy数组
data = df.to_numpy(dtype=np.float32)
return metadata, data
def enhance_continuity(self, data):
"""
通过形态学操作增强区域连续性
参数:
data: 热数据矩阵
返回:
result: 增强后的热数据矩阵
"""
# 先对数据进行中值滤波减少噪点
data_filtered = ndimage.median_filter(data, size=3)
# 归一化处理
norm_data = cv2.normalize(data_filtered, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# 使用自适应阈值以获得更好的边缘细节
mask = cv2.adaptiveThreshold(norm_data, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
# 使用核进行闭操作,更好地连接邻近区域
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=3)
# 增加开操作,消除小噪点
opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel, iterations=1)
# 保留原数据的连续性,使用形态学梯度增强边缘
result = cv2.bitwise_and(data, data, mask=opened)
# 应用高斯滤波以平滑过渡
return cv2.GaussianBlur(result, (5,5), 0)
def generate_temp_ranges(self, min_temp, max_temp, focus_temp):
"""
生成温度区间
参数:
min_temp: 最小温度值
max_temp: 最大温度值
focus_temp: 集中温度值,在该温度附近的区间将更精细
返回:
temp_ranges: 温度区间列表
"""
# 创建一个函数来计算到焦点的距离与区间大小的关系
def interval_size(distance):
"""根据到焦点的距离计算区间大小"""
# 基准区间大小(最小值,在焦点处)
base_size = 0.5
# 增长率
growth_rate = 3.0
# 计算区间大小
size = base_size * (1 + growth_rate * (abs(distance) ** 1.5))
# 限制最大区间大小
return min(size, (max_temp - min_temp) / 5)
# 从焦点开始,向两边生成温度点
ranges = [focus_temp]
# 向上生成温度点,直到超过最大温度
current = focus_temp
while current < max_temp:
distance = (current - focus_temp) / (max_temp - min_temp)
step = interval_size(distance)
current += step
if current < max_temp:
ranges.append(current)
# 向下生成温度点,直到低于最小温度
current = focus_temp
while current > min_temp:
distance = (current - focus_temp) / (max_temp - min_temp)
step = interval_size(distance)
current -= step
if current > min_temp:
ranges.append(current)
# 确保包含最小值和最大值
if min_temp not in ranges:
ranges.append(min_temp)
if max_temp not in ranges:
ranges.append(max_temp)
# 对温度点排序并去重
ranges = sorted(list(set(ranges)))
# 需要的温度点数量(颜色数量-1因为颜色数比区间数少1
required_points = 10 # 11个颜色需要10个温度点不包括无穷值
# 确保生成的区间数量正确
while len(ranges) > required_points:
# 如果温度点太多,移除距离焦点最远的点,但保留最小值和最大值
distances = []
for i, r in enumerate(ranges):
if r == min_temp or r == max_temp:
distances.append(float('-inf')) # 确保不会删除最小和最大值
else:
distances.append(abs(r - focus_temp))
furthest_idx = distances.index(max(distances))
ranges.pop(furthest_idx)
# 如果温度点太少,在最大间隔处插入新的点
while len(ranges) < required_points:
intervals = [ranges[i+1] - ranges[i] for i in range(len(ranges)-1)]
largest_interval_idx = intervals.index(max(intervals))
mid_point = (ranges[largest_interval_idx] + ranges[largest_interval_idx+1]) / 2
ranges.insert(largest_interval_idx + 1, mid_point)
# 添加无穷区间
ranges = [-np.inf] + ranges + [np.inf]
return ranges
def create_heatmap(self, data, min_temp=None, max_temp=None, focus_temp=None,
colors=None, scale_factor=6):
"""
根据温度数据创建热图
参数:
data: 温度数据矩阵
min_temp: 最小温度值若为None则使用数据中的最小值
max_temp: 最大温度值若为None则使用数据中的最大值
focus_temp: 集中温度值若为None则使用max_temp的值
colors: 自定义颜色映射若为None则使用默认颜色
scale_factor: 图像缩放倍数默认为6
返回:
heatmap: 生成的热图图像
"""
# 确保数据为numpy数组
data = np.array(data, dtype=np.float32)
# 对原始数据应用高斯平滑,减少离散温度值的跳变
data = cv2.GaussianBlur(data, (3,3), 0.8)
# 增强数据连续性
data = self.enhance_continuity(data)
# 如果未指定温度范围,则使用数据的实际范围
if min_temp is None:
min_temp = np.min(data)
if max_temp is None:
max_temp = np.max(data)
if focus_temp is None:
focus_temp = max_temp
# 使用自定义颜色或默认颜色
if colors is None:
colors = self.default_colors
# 根据参数动态生成温度区间
temp_ranges = self.generate_temp_ranges(min_temp, max_temp, focus_temp)
# 实现平滑过渡
interpolated_colors = np.zeros((data.shape[0], data.shape[1], 3), dtype=np.float32)
# 对每个像素计算相对距离,用于颜色插值
for i in range(len(temp_ranges) - 1):
if i == 0 or i == len(temp_ranges) - 2:
# 边界区域直接使用对应颜色
mask = (data > temp_ranges[i]) & (data <= temp_ranges[i+1])
if np.any(mask):
interpolated_colors[mask] = colors[i]
continue
lower_bound = temp_ranges[i]
upper_bound = temp_ranges[i+1]
# 找出在当前区间内的所有像素
mask = (data > lower_bound) & (data <= upper_bound)
if not np.any(mask):
continue
# 计算相对位置并插值
pixels_in_range = data[mask]
relative_pos = (pixels_in_range - lower_bound) / (upper_bound - lower_bound)
# 在两个颜色之间进行线性插值
for c in range(3): # RGB通道
color_values = colors[i][c] * (1 - relative_pos) + colors[i+1][c] * relative_pos
interpolated_colors[mask, c] = color_values
# 将插值后的颜色转换为字节类型
heatmap = (interpolated_colors * 255).astype(np.uint8)
# 将RGB转换为BGROpenCV使用BGR格式
heatmap = cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR)
# 图像放大和增强
original_height, original_width = heatmap.shape[:2]
heatmap = cv2.resize(heatmap,
(original_width * scale_factor,
original_height * scale_factor),
interpolation=cv2.INTER_LANCZOS4)
# 应用滤波
heatmap = cv2.GaussianBlur(heatmap, (9, 9), 2.0)
heatmap = cv2.bilateralFilter(heatmap, 11, 85, 85)
heatmap = cv2.bilateralFilter(heatmap, 9, 60, 60)
# 对比度增强
heatmap = cv2.convertScaleAbs(heatmap, alpha=1.08, beta=6)
# 提高饱和度
hsv = cv2.cvtColor(heatmap, cv2.COLOR_BGR2HSV)
hsv[:,:,1] = np.clip(hsv[:,:,1] * 1.1, 0, 255).astype(np.uint8)
heatmap = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
# 最终旋转
return cv2.rotate(heatmap, cv2.ROTATE_180)
def process_csv(self, file_path, min_temp=None, max_temp=None, focus_temp=None,
colors=None, scale_factor=6):
"""
处理CSV文件并生成热图
参数:
file_path: CSV文件路径
min_temp: 最小温度值若为None则使用数据中的最小值
max_temp: 最大温度值若为None则使用数据中的最大值
focus_temp: 集中温度值若为None则使用max_temp的值
colors: 自定义颜色映射若为None则使用默认颜色
scale_factor: 图像缩放倍数默认为6
返回:
heatmap: 生成的热图图像
metadata: 元数据字典
"""
# 读取CSV数据
metadata, data = self.read_csv(file_path)
# 使用元数据中的温度范围(如果未指定)
if min_temp is None and "min_temp" in metadata:
min_temp = metadata["min_temp"]
if max_temp is None and "max_temp" in metadata:
max_temp = metadata["max_temp"]
if focus_temp is None and "center_temp" in metadata:
focus_temp = metadata["center_temp"]
# 如果仍然没有温度范围,使用数据中的实际范围
if min_temp is None:
min_temp = np.min(data)
if max_temp is None:
max_temp = np.max(data)
if focus_temp is None:
focus_temp = max_temp
# 生成热图
heatmap = self.create_heatmap(
data,
min_temp=min_temp,
max_temp=max_temp,
focus_temp=focus_temp,
colors=colors,
scale_factor=scale_factor
)
return heatmap, metadata
def save_image(self, image, file_path):
"""
保存图像到文件
参数:
image: 图像
file_path: 输出文件路径
返回:
success: 是否保存成功
"""
return cv2.imwrite(file_path, image)
# 基础使用示例
if __name__ == "__main__":
# 创建处理器实例
processor = ThermalProcessor()
# 处理CSV文件
csv_path = "thermal_data_20250318_161047.csv" # 替换为你的CSV文件
heatmap, metadata = processor.process_csv(csv_path)
# 显示元数据
print("文件元数据:")
for key, value in metadata.items():
print(f" {key}: {value}")
# 保存热图
output_path = "output_heatmap.png"
processor.save_image(heatmap, output_path)
print(f"热图已保存为: {output_path}")