Python中如何为OpenCV提取的图像各轮廓区域生成直方图?
葡萄园热红外影像冠层误分像元剔除实现方案
环境准备
在PyCharm的终端执行以下命令安装依赖库:pip install numpy opencv-python matplotlib scipy
核心实现逻辑
逐独立冠层轮廓做单独统计,避免不同区域冠层温度差异干扰阈值判断,整体流程:
- 为每个检测到的冠层轮廓生成独立的填充掩膜
- 提取轮廓范围内的有效像元,排除DEM初筛阶段已经标记为0的地面像元
- 统计单区域内的温度值密度分布,自动划定温度阈值,剔除温度过高的误分地面像元
- 汇总所有区域的有效冠层像元,生成修正后的最终掩膜
- (可选)为每个轮廓单独输出温度密度直方图,方便校验阈值合理性
完整实现代码
首先确认你已经持有两个输入变量:
thermal_img:叠加DEM初筛掩膜后的热红外影像,为uint16类型的ndarray,尺寸6082×4922,已确认的地面像元值为0,冠层像元取值范围0-65535contours:通过OpenCVcv2.findContours接口提取得到的冠层轮廓列表
import cv2 import numpy as np import matplotlib.pyplot as plt from scipy.stats import gaussian_kde # 初始化修正后的冠层掩膜,尺寸与热影像一致,0代表地面,1代表冠层 refined_mask = np.zeros_like(thermal_img, dtype=np.uint8) # 逐轮廓遍历处理 for contour_idx, contour in enumerate(contours): # 生成当前轮廓的独立填充掩膜 single_contour_mask = np.zeros_like(thermal_img, dtype=np.uint8) cv2.drawContours(single_contour_mask, [contour], contourIdx=-1, color=1, thickness=-1) # 提取当前轮廓范围内的有效像元:在轮廓内、且值不为0(排除DEM初筛已识别的地面) roi_valid_pixels = thermal_img[(single_contour_mask == 1) & (thermal_img != 0)] # 跳过像元数不足10的噪点轮廓 if len(roi_valid_pixels) < 10: continue # 阈值计算二选一即可,优先用分位法处理大尺寸影像 # --- 方案1:分位阈值法(计算速度快,适合超大影像)--- # 95表示取区域内温度从低到高排95%位置的值作为阈值,高于该值判定为地面 # 该参数可根据实际误分情况调整:误分多就调低,误删冠层就调高 temp_threshold = np.percentile(roi_valid_pixels, 95) # --- 方案2:KDE密度峰值法(精度更高,适合温度双峰分布明显的场景,计算速度慢)--- # kde_model = gaussian_kde(roi_valid_pixels) # temp_series = np.arange(roi_valid_pixels.min(), roi_valid_pixels.max() + 1) # density_curve = kde_model(temp_series) # peak_pos = np.argmax(density_curve) # # 从密度峰值往高温方向遍历,取密度降到峰值10%的位置作为阈值 # threshold_candidate = np.where(density_curve[peak_pos:] < density_curve[peak_pos] * 0.1)[0] # temp_threshold = temp_series[peak_pos + threshold_candidate[0]] if len(threshold_candidate) > 0 else np.percentile(roi_valid_pixels, 95) # 提取当前轮廓内符合冠层温度要求的像元坐标,写入最终掩膜 roi_coord_y, roi_coord_x = np.where((single_contour_mask == 1) & (thermal_img != 0)) valid_pixel_flag = thermal_img[roi_coord_y, roi_coord_x] <= temp_threshold refined_mask[roi_coord_y[valid_pixel_flag], roi_coord_x[valid_pixel_flag]] = 1 # 可选:生成当前轮廓的温度密度直方图并保存 plt.figure(figsize=(8, 4)) plt.hist(roi_valid_pixels, bins=50, density=True, alpha=0.7, label="区域原始像元分布") plt.axvline(temp_threshold, color="red", linestyle="--", label=f"剔除阈值:{temp_threshold}") plt.title(f"冠层区域{contour_idx+1}温度分布") plt.xlabel("热影像DN值(对应温度)") plt.ylabel("像元密度") plt.legend() plt.savefig(f"contour_{contour_idx+1}_hist.png", dpi=150, bbox_inches="tight") plt.close() # 生成修正后的热影像:地面像元全部置0,仅保留冠层温度值 refined_thermal_img = thermal_img * refined_mask
使用说明
- 代码内存占用极低,常规16G内存设备即可流畅处理6082×4922尺寸的影像,不需要额外分块
- 优先使用分位阈值法,单张影像处理耗时通常在10秒以内;如果需要更高精度可以切换KDE法,处理耗时会提升数倍
- 阈值参数需要结合实地采样结果校准:如果结果里残留的误分地面像元多,可以把分位值从95下调到9093;如果发现冠层高温像元被误删,可以把分位值上调到9698
- 输出的
refined_mask是标准二值掩膜,可以直接用于后续温度统计、产量反演等后续分析
内容的提问来源于stack exchange,提问作者Thomas Hallemeesch
相关产品推荐
相关产品推荐

