增加坡度计算
This commit is contained in:
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -16,265 +16,57 @@ from Flexbrdf.hytools.masks import mask_create
|
||||
warnings.filterwarnings("ignore")
|
||||
np.seterr(divide='ignore', invalid='ignore')
|
||||
|
||||
# Default configuration values
|
||||
DEFAULT_CONFIG = {
|
||||
'file_type': 'envi',
|
||||
'num_cpus': 16,
|
||||
'bad_bands': [],
|
||||
'corrections': [],
|
||||
'resample': False,
|
||||
'resampler': {'type': 'cubic', 'out_waves': []},
|
||||
'export': {
|
||||
'coeffs':True,
|
||||
'image': True,
|
||||
'masks': False,
|
||||
'subset_waves': [],
|
||||
'output_dir': './output/',
|
||||
'suffix': 'BRDF'
|
||||
},
|
||||
'brdf': {
|
||||
'type': 'flex',
|
||||
'grouped': True,
|
||||
'geometric': 'li_dense_r',
|
||||
'volume': 'ross_thick',
|
||||
'b/r': 2.5,
|
||||
'h/b': 2.0,
|
||||
'sample_perc': 0.1,
|
||||
'interp_kind': 'linear',
|
||||
'calc_mask': [
|
||||
['water', {'band_1': 850, 'band_2': 660,"threshold": 290}],
|
||||
['kernel_finite', {}],
|
||||
['ancillary', {'name': 'sensor_zn', 'min': 0.03490658503988659, 'max': 'inf'}]
|
||||
],
|
||||
'apply_mask': [
|
||||
['water', {'band_1': 850, 'band_2': 660,"threshold": 290}]
|
||||
],
|
||||
'bin_type': 'dynamic',
|
||||
'num_bins': 18,
|
||||
'ndvi_bin_min': 0.05,
|
||||
'ndvi_bin_max': 1.0,
|
||||
'ndvi_perc_min': 10,
|
||||
'ndvi_perc_max': 95,
|
||||
'solar_zn_type': 'scene'
|
||||
},
|
||||
# 'topo': {
|
||||
# 'type': 'scs+c',
|
||||
# 'calc_mask': [
|
||||
# ['ndi', {'band_1': 850, 'band_2': 660, 'min': 0.05, 'max': 1.0}],
|
||||
# ['kernel_finite', {}],
|
||||
# ['ancillary', {'name': 'sensor_zn', 'min': 0.03490658503988659, 'max': 'inf'}]
|
||||
# ],
|
||||
# 'apply_mask': [
|
||||
# ['ndi', {'band_1': 850, 'band_2': 660, 'min': 0.05, 'max': 1.0}]
|
||||
# ],
|
||||
# 'sample_perc': 0.1,
|
||||
# 'subgrouped': False,
|
||||
# 'subgroup': {}
|
||||
# },
|
||||
# 'glint': {
|
||||
# 'type': 'hochberg',
|
||||
# 'correction_band': 560,
|
||||
# 'deep_water_sample': {},
|
||||
# 'calc_mask': [
|
||||
# ['ndi', {'band_1': 550, 'band_2': 2150, 'min': -1, 'max': 0}],
|
||||
# ['kernel_finite', {}]
|
||||
# ],
|
||||
# 'apply_mask': [
|
||||
# ['ndi', {'band_1': 550, 'band_2': 2150, 'min': -1, 'max': 0}]
|
||||
# ]
|
||||
# }
|
||||
}
|
||||
|
||||
def build_config_from_args(args):
|
||||
"""从命令行参数构建配置字典"""
|
||||
# 自动发现输入文件
|
||||
input_files = []
|
||||
if os.path.isdir(args.input_dir):
|
||||
# 支持的文件扩展名
|
||||
extensions = ['.tif', '.tiff', '.envi', '.img', '.dat']
|
||||
for file in os.listdir(args.input_dir):
|
||||
if any(file.lower().endswith(ext) for ext in extensions):
|
||||
input_files.append(os.path.join(args.input_dir, file))
|
||||
else:
|
||||
input_files = [args.input_dir] if os.path.isfile(args.input_dir) else []
|
||||
|
||||
if not input_files:
|
||||
raise ValueError(f"No input files found in {args.input_dir}")
|
||||
|
||||
# 自动生成anc_files
|
||||
anc_files = {}
|
||||
for input_file in input_files:
|
||||
base_name = os.path.splitext(os.path.basename(input_file))[0]
|
||||
# 根据文件名模式生成对应的angles文件路径
|
||||
# 例如: 2025_9_2_3_53_45_202592_35252_0_rad_geo_corrected_reflectance.dat
|
||||
# 对应的angles文件: 2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo_registered_angles.bip
|
||||
parts = base_name.split('_')
|
||||
if len(parts) >= 9: # 确保有足够的parts
|
||||
anc_base = f"{parts[0]}_{parts[1]}_{parts[2]}_{parts[3]}_{parts[4]}_{parts[5]}_{parts[6]}_{parts[7]}_{parts[8]}_rad_rgbxyz_geo_angles_registered"
|
||||
# 确保路径分隔符统一使用反斜杠(Windows)
|
||||
anc_dir_clean = args.anc_dir.replace('/', '\\')
|
||||
anc_path = os.path.join(anc_dir_clean, anc_base + ".bip")
|
||||
# 确保最终路径格式正确
|
||||
anc_path = os.path.normpath(anc_path)
|
||||
|
||||
if os.path.exists(anc_path):
|
||||
# 生成完整的anc_files字典结构
|
||||
anc_files[input_file] = {
|
||||
"path_length": [anc_path, 0],
|
||||
"sensor_az": [anc_path, 9],
|
||||
"sensor_zn": [anc_path, 8],
|
||||
"solar_az": [anc_path, 7],
|
||||
"solar_zn": [anc_path, 6],
|
||||
"phase": [anc_path, 0],
|
||||
"slope": [anc_path, 0],
|
||||
"aspect": [anc_path, 0],
|
||||
"cosine_i": [anc_path, 0],
|
||||
"utc_time": [anc_path, 0]
|
||||
}
|
||||
else:
|
||||
# 如果找不到对应的angles文件,使用默认的obs文件
|
||||
obs_path = os.path.join(args.anc_dir, base_name + "_obs")
|
||||
if os.path.exists(obs_path):
|
||||
anc_files[input_file] = obs_path
|
||||
|
||||
# 构建配置字典
|
||||
config_dict = DEFAULT_CONFIG.copy()
|
||||
config_dict.update({
|
||||
'input_files': input_files,
|
||||
'anc_files': anc_files,
|
||||
'file_type': 'envi', # 假设都是ENVI格式
|
||||
'num_cpus': args.num_cpus,
|
||||
'bad_bands': args.bad_bands if args.bad_bands else [],
|
||||
'corrections': args.corrections if args.corrections else [],
|
||||
'export': {
|
||||
'coeffs': args.export_coeffs,
|
||||
'image': args.export_image,
|
||||
'masks': args.export_masks,
|
||||
'subset_waves': args.subset_waves if args.subset_waves else [],
|
||||
'output_dir': args.output_dir,
|
||||
'suffix': args.suffix
|
||||
}
|
||||
})
|
||||
|
||||
# 根据命令行参数更新BRDF配置
|
||||
if 'brdf' in args.corrections:
|
||||
brdf_config = config_dict['brdf'].copy()
|
||||
if hasattr(args, 'brdf_type') and args.brdf_type:
|
||||
brdf_config['type'] = args.brdf_type
|
||||
if hasattr(args, 'grouped') and args.grouped is not None:
|
||||
brdf_config['grouped'] = args.grouped
|
||||
if hasattr(args, 'geometric') and args.geometric:
|
||||
brdf_config['geometric'] = args.geometric
|
||||
if hasattr(args, 'volume') and args.volume:
|
||||
brdf_config['volume'] = args.volume
|
||||
if hasattr(args, 'num_bins') and args.num_bins:
|
||||
brdf_config['num_bins'] = args.num_bins
|
||||
config_dict['brdf'] = brdf_config
|
||||
|
||||
# 根据命令行参数更新TOPO配置
|
||||
if 'topo' in args.corrections:
|
||||
topo_config = config_dict['topo'].copy()
|
||||
if hasattr(args, 'topo_type') and args.topo_type:
|
||||
topo_config['type'] = args.topo_type
|
||||
config_dict['topo'] = topo_config
|
||||
|
||||
# 根据命令行参数更新Glint配置
|
||||
if 'glint' in args.corrections:
|
||||
glint_config = config_dict['glint'].copy()
|
||||
if hasattr(args, 'glint_type') and args.glint_type:
|
||||
glint_config['type'] = args.glint_type
|
||||
config_dict['glint'] = glint_config
|
||||
|
||||
return config_dict
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="High-resolution image correction with automatic configuration")
|
||||
parser = argparse.ArgumentParser(description="FlexBRDF - 只接受JSON配置文件输入")
|
||||
parser.add_argument('config', help='JSON配置文件路径 (例如: config.json)')
|
||||
|
||||
# 必需参数
|
||||
parser.add_argument('input_dir', help='Input directory containing image files or single image file path')
|
||||
parser.add_argument('anc_dir', help='Ancillary directory containing angle/obs files')
|
||||
parser.add_argument('--output-dir', required=True, help='Output directory for corrected images')
|
||||
args = parser.parse_args()
|
||||
|
||||
# 可选参数 - 基本设置
|
||||
parser.add_argument('--num-cpus', type=int, default=16, help='Number of CPUs to use (default: 4)')
|
||||
parser.add_argument('--bad-bands', nargs='*', type=int, default=[], help='Bad band indices to exclude')
|
||||
# 检查配置文件是否存在
|
||||
if not os.path.exists(args.config):
|
||||
print(f"错误: 配置文件不存在: {args.config}")
|
||||
sys.exit(1)
|
||||
|
||||
# 校正类型
|
||||
parser.add_argument('--corrections', nargs='*', choices=['topo', 'brdf', 'glint'],
|
||||
default=['brdf'], help='Correction types to apply (default: brdf)')
|
||||
# 检查是否为JSON文件
|
||||
if not args.config.endswith('.json'):
|
||||
print("错误: 只接受JSON格式的配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
# BRDF参数
|
||||
parser.add_argument('--brdf-type', choices=['universal', 'flex'], default='flex',
|
||||
help='BRDF correction type (default: flex)')
|
||||
parser.add_argument('--grouped', action='store_true', default=True,
|
||||
help='Group images for BRDF correction (default: True)')
|
||||
parser.add_argument('--no-grouped', action='store_false', dest='grouped',
|
||||
help='Do not group images for BRDF correction')
|
||||
parser.add_argument('--geometric', default='li_dense_r',
|
||||
choices=['li_sparse', 'li_dense', 'li_dense_r', 'roujean'],
|
||||
help='Geometric kernel type (default: li_dense_r)')
|
||||
parser.add_argument('--volume', default='ross_thick',
|
||||
choices=['ross_thin', 'ross_thick', 'hotspot', 'roujean'],
|
||||
help='Volume kernel type (default: ross_thick)')
|
||||
parser.add_argument('--num-bins', type=int, default=18,
|
||||
help='Number of NDVI bins for FlexBRDF (default: 18)')
|
||||
# 加载JSON配置
|
||||
try:
|
||||
with open(args.config, 'r', encoding='utf-8') as f:
|
||||
config_dict = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"错误: JSON配置文件格式错误: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"错误: 无法读取配置文件: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# TOPO参数
|
||||
parser.add_argument('--topo-type', default='scs+c',
|
||||
choices=['scs', 'scs+c', 'c', 'cosine', 'mod_minneart'],
|
||||
help='TOPO correction type (default: scs+c)')
|
||||
# 验证必需的配置字段
|
||||
required_fields = ['input_files', 'anc_files', 'corrections', 'export']
|
||||
missing_fields = [f for f in required_fields if f not in config_dict]
|
||||
if missing_fields:
|
||||
print(f"错误: 配置文件缺少必需字段: {', '.join(missing_fields)}")
|
||||
print("必需字段包括: input_files, anc_files, corrections, export")
|
||||
sys.exit(1)
|
||||
|
||||
# Glint参数
|
||||
parser.add_argument('--glint-type', default='hochberg',
|
||||
choices=['hochberg', 'gao', 'hedley'],
|
||||
help='Glint correction type (default: hochberg)')
|
||||
|
||||
# 输出选项
|
||||
parser.add_argument('--export-coeffs', action='store_true', default=True,
|
||||
help='Export correction coefficients')
|
||||
parser.add_argument('--export-image', action='store_true', default=True,
|
||||
help='Export corrected images (default: True)')
|
||||
parser.add_argument('--export-masks', action='store_true', default=True,
|
||||
help='Export correction masks (default: True)')
|
||||
parser.add_argument('--subset-waves', nargs='*', type=float, default=[],
|
||||
help='Subset of wavelengths to export (empty for all)')
|
||||
parser.add_argument('--suffix', default='corrected',
|
||||
help='Suffix for output files (default: corrected)')
|
||||
parser.add_argument('--output-config', type=str, default=None,
|
||||
help='Output current configuration to JSON file')
|
||||
|
||||
# 向后兼容:如果只有一个参数且是JSON文件,则使用传统模式
|
||||
if len(sys.argv) == 2 and sys.argv[1].endswith('.json'):
|
||||
config_file = sys.argv[1]
|
||||
with open(config_file, 'r') as outfile:
|
||||
config_dict = json.load(outfile)
|
||||
else:
|
||||
args = parser.parse_args()
|
||||
config_dict = build_config_from_args(args)
|
||||
|
||||
# 如果指定了输出配置选项,则输出配置到JSON文件
|
||||
if hasattr(args, 'output_config') and args.output_config:
|
||||
print(f"Outputting configuration to {args.output_config}...")
|
||||
# 重新排序配置字典以匹配期望的格式
|
||||
ordered_config = {
|
||||
"bad_bands": config_dict.get("bad_bands", []),
|
||||
"file_type": config_dict.get("file_type", "envi"),
|
||||
"input_files": config_dict.get("input_files", []),
|
||||
"anc_files": config_dict.get("anc_files", {}),
|
||||
"num_cpus": config_dict.get("num_cpus", 4),
|
||||
"export": config_dict.get("export", {}),
|
||||
"corrections": config_dict.get("corrections", []),
|
||||
"brdf": config_dict.get("brdf", {}),
|
||||
"topo": config_dict.get("topo", {}),
|
||||
"glint": config_dict.get("glint", {}),
|
||||
"resample": config_dict.get("resample", False),
|
||||
"resampler": config_dict.get("resampler", {"type": "cubic", "out_waves": []})
|
||||
}
|
||||
with open(args.output_config, 'w', encoding='utf-8') as f:
|
||||
json.dump(ordered_config, f, indent=2, ensure_ascii=False)
|
||||
print("Configuration output complete.")
|
||||
return # 如果只是输出配置,则退出程序
|
||||
# 设置默认值
|
||||
config_dict.setdefault('file_type', 'envi')
|
||||
config_dict.setdefault('num_cpus', 16)
|
||||
config_dict.setdefault('bad_bands', [])
|
||||
config_dict.setdefault('resample', False)
|
||||
config_dict.setdefault('resampler', {'type': 'cubic', 'out_waves': []})
|
||||
|
||||
print(f"=" * 60)
|
||||
print(f"FlexBRDF 图像校正工具")
|
||||
print(f"=" * 60)
|
||||
print(f"配置文件: {args.config}")
|
||||
print(f"输入文件数量: {len(config_dict['input_files'])}")
|
||||
print(f"校正类型: {', '.join(config_dict['corrections'])}")
|
||||
print(f"输出目录: {config_dict['export']['output_dir']}")
|
||||
print(f"=" * 60)
|
||||
print("Starting image correction process...")
|
||||
total_start = time.time()
|
||||
|
||||
|
||||
167
Flexbrdf/examples/configs/1.json
Normal file
167
Flexbrdf/examples/configs/1.json
Normal file
@ -0,0 +1,167 @@
|
||||
{
|
||||
"input_files": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_0_rad_geo_corrected_reflectance.dat",
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_1_rad_geo_corrected_reflectance.dat"
|
||||
],
|
||||
"file_type": "envi",
|
||||
"bad_bands": [],
|
||||
"num_cpus": 2,
|
||||
"anc_files": {
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_0_rad_geo_corrected_reflectance.dat": {
|
||||
"path_length": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
0
|
||||
],
|
||||
"sensor_az": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
9
|
||||
],
|
||||
"sensor_zn": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
8
|
||||
],
|
||||
"solar_az": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
7
|
||||
],
|
||||
"solar_zn": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
6
|
||||
],
|
||||
"phase": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
0
|
||||
],
|
||||
"slope": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
0
|
||||
],
|
||||
"aspect": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
0
|
||||
],
|
||||
"cosine_i": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
0
|
||||
],
|
||||
"utc_time": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
|
||||
0
|
||||
]
|
||||
},
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_1_rad_geo_corrected_reflectance.dat": {
|
||||
"path_length": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
0
|
||||
],
|
||||
"sensor_az": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
9
|
||||
],
|
||||
"sensor_zn": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
8
|
||||
],
|
||||
"solar_az": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
7
|
||||
],
|
||||
"solar_zn": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
6
|
||||
],
|
||||
"phase": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
0
|
||||
],
|
||||
"slope": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
0
|
||||
],
|
||||
"aspect": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
0
|
||||
],
|
||||
"cosine_i": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
0
|
||||
],
|
||||
"utc_time": [
|
||||
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"corrections": [
|
||||
"brdf"
|
||||
],
|
||||
"topo": {
|
||||
"type": "scs+c",
|
||||
"calc_mask": [],
|
||||
"apply_mask": [],
|
||||
"c_fit_type": "nnls"
|
||||
},
|
||||
"brdf": {
|
||||
"type": "flex",
|
||||
"grouped": true,
|
||||
"geometric": "li_dense_r",
|
||||
"volume": "ross_thick",
|
||||
"b/r": 2.5,
|
||||
"h/b": 2.0,
|
||||
"sample_perc": 0.1,
|
||||
"interp_kind": "linear",
|
||||
"calc_mask": [
|
||||
[
|
||||
"water",
|
||||
{
|
||||
"band_1": 850,
|
||||
"band_2": 660,
|
||||
"threshold": 290.0
|
||||
}
|
||||
],
|
||||
[
|
||||
"kernel_finite",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"ancillary",
|
||||
{
|
||||
"name": "sensor_zn",
|
||||
"min": 0.034907,
|
||||
"max": 1000000.0
|
||||
}
|
||||
]
|
||||
],
|
||||
"apply_mask": [
|
||||
[
|
||||
"water",
|
||||
{
|
||||
"band_1": 850,
|
||||
"band_2": 660,
|
||||
"threshold": 290.0
|
||||
}
|
||||
]
|
||||
],
|
||||
"bin_type": "dynamic",
|
||||
"num_bins": 18,
|
||||
"ndvi_bin_min": 0.05,
|
||||
"ndvi_bin_max": 1.0,
|
||||
"ndvi_perc_min": 10,
|
||||
"ndvi_perc_max": 95,
|
||||
"solar_zn_type": "scene"
|
||||
},
|
||||
"glint": {
|
||||
"type": "hochberg",
|
||||
"correction_wave": 2150,
|
||||
"apply_mask": []
|
||||
},
|
||||
"export": {
|
||||
"coeffs": true,
|
||||
"image": true,
|
||||
"masks": true,
|
||||
"subset_waves": [],
|
||||
"output_dir": "E:/code/hytools-master/hytools-master/data/output",
|
||||
"suffix": "brdf_glint"
|
||||
},
|
||||
"resample": false
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 159 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 146 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 640 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 633 KiB |
186
Flexbrdf/hytools/topo/原理.md
Normal file
186
Flexbrdf/hytools/topo/原理.md
Normal file
@ -0,0 +1,186 @@
|
||||
基于 Flexbrdf/hytools/topo 目录下的代码,我来详细解释地形校正的原理。
|
||||
|
||||
地形校正原理详解
|
||||
地形校正(Topographic Correction)的目的是消除地形起伏(坡度、坡向)对高光谱图像反射率的影响。由于太阳光入射角度在不同坡度和坡向上的变化,同一地物在不同地形位置会呈现不同的辐射亮度,地形校正就是要将这种差异归一化到水平地表的条件。
|
||||
|
||||
一、核心概念:入射角余弦(cosine i)
|
||||
地形校正的核心是计算入射角余弦(cosine of incidence angle),即太阳光线与地表法线之间夹角的余弦值。
|
||||
|
||||
计算公式(在 topo.py:53):
|
||||
|
||||
cosine_i = np.cos(solar_zn)*np.cos(slope) + np.sin(solar_zn)*np.sin(slope)*np.cos(relative_az)
|
||||
其中:
|
||||
|
||||
solar_zn:太阳天顶角(太阳光线与垂直方向的夹角)
|
||||
solar_az:太阳方位角
|
||||
slope:地面坡度
|
||||
aspect:地面坡向
|
||||
relative_az = aspect - solar_az:相对方位角
|
||||
物理意义:
|
||||
|
||||
当 cosine_i = 1 时,太阳光垂直照射地面(正午水平地表)
|
||||
当 cosine_i < 1 时,太阳光斜射,照度降低
|
||||
背阴坡 cosine_i 较小,向阳坡 cosine_i 较大
|
||||
二、支持的5种地形校正方法
|
||||
1. Cosine 校正(余弦校正)
|
||||
原理:最简单的地形校正方法,假设地表为朗伯体(各向同性反射)。
|
||||
|
||||
校正公式(cosine.py:50):
|
||||
|
||||
c_factor = cos_solar_zn / cos_i
|
||||
cos_solar_zn:水平地表的太阳天顶角余弦
|
||||
cos_i:实际地表的入射角余弦
|
||||
校正效果:
|
||||
|
||||
corrected_reflectance = original_reflectance × (cos_solar_zn / cos_i)
|
||||
特点:
|
||||
|
||||
简单快速
|
||||
假设地表是理想朗伯体,对非朗伯体(如植被)校正过度
|
||||
2. SCS 校正(Sun-Canopy-Sensor)
|
||||
原理:考虑森林冠层对地形影响的简化模型,基于 "cosine i" 但增加坡度因子。
|
||||
|
||||
校正公式(scs.py:51):
|
||||
|
||||
c_factor = (cos_slope × cos_solar_zn) / cos_i
|
||||
与 Cosine 方法的区别:
|
||||
|
||||
增加了 cos_slope 项(坡度余弦)
|
||||
考虑了坡度对观测路径的影响
|
||||
适用场景:森林覆盖区域
|
||||
|
||||
3. C 校正(C-Correction)
|
||||
原理:在 Cosine 校正基础上增加一个经验系数 C,用于调整非朗伯体效应。
|
||||
|
||||
校正公式(c.py:177):
|
||||
|
||||
correction_factor = (cos_sz + C) / (cosine_i + C)
|
||||
其中 C 系数的计算过程(c.py:42-76):
|
||||
|
||||
对每个波段,建立反射率与 cosine_i 的线性关系:
|
||||
|
||||
ρ = slope × cos_i + intercept
|
||||
通过最小二乘拟合(OLS)或非负最小二乘(NNLS)求解
|
||||
|
||||
计算 C 系数(Soenen et al. 2005, Eq. 8):
|
||||
|
||||
C = intercept / slope
|
||||
物理意义:C 值越大,表示地表越接近朗伯体,校正强度越接近 Cosine 方法;C 值越小,校正越保守。
|
||||
|
||||
拟合方法选择(c_fit_type):
|
||||
|
||||
ols:普通最小二乘(Ordinary Least Squares)
|
||||
nnls:非负最小二乘(Non-Negative Least Squares)
|
||||
wls:加权最小二乘(Weighted Least Squares)
|
||||
4. SCS+C 校正(推荐用于森林)
|
||||
原理:结合 SCS 和 C 校正的优势,是目前森林地形校正的主流方法(Soenen et al. 2005, IEEE TGRS)。
|
||||
|
||||
计算步骤(scsc.py):
|
||||
|
||||
计算 c1 因子:
|
||||
|
||||
c1 = cos(solar_zn) × cos(slope)
|
||||
计算 C 系数(与 C 校正相同,通过线性回归)
|
||||
|
||||
综合校正因子(scsc.py:115-125):
|
||||
|
||||
correction_factor = (c1 + C × cos_slope) / (cos_i + C)
|
||||
为什么有效:
|
||||
|
||||
SCS 部分处理冠层-地形几何关系
|
||||
C 部分调整非朗伯体效应
|
||||
相比单独使用 SCS 或 C 校正,能更好地保持植被反射率的一致性
|
||||
5. Modified Minnaert 校正(改进的 Minnaert)
|
||||
原理:基于 Minnaert 指数的半经验方法,区分植被和非植被区域进行不同强度的校正(Richter et al. 2009)。
|
||||
|
||||
算法流程(modminn.py:46-83):
|
||||
|
||||
计算调整后的太阳天顶角:
|
||||
|
||||
if solar_zn < 45°: solar_zn_t = solar_zn + 20°
|
||||
if 45° ≤ solar_zn ≤ 55°: solar_zn_t = solar_zn + 15°
|
||||
if solar_zn > 55°: solar_zn_t = solar_zn + 10°
|
||||
NDVI 掩膜分类:
|
||||
|
||||
植被区(NDVI > 0.2)
|
||||
非植被区(NDVI ≤ 0.2)
|
||||
计算基础校正因子:
|
||||
|
||||
factor = cos_i / cos(solar_zn_t)
|
||||
分区应用不同指数:
|
||||
|
||||
区域 短波(<720nm) 长波(≥720nm)
|
||||
非植被
|
||||
factor^(1/2)
|
||||
factor^(1/2)
|
||||
植被
|
||||
factor^(3/4)
|
||||
factor^(1/3)
|
||||
限幅处理:
|
||||
|
||||
factor = np.clip(factor, 0.25, 1.0)
|
||||
阈值控制:当入射角小于调整后的太阳天顶角时不进行校正
|
||||
|
||||
特点:
|
||||
|
||||
自适应不同地物类型
|
||||
区分短波和长波有不同的散射特性
|
||||
通过指数控制校正强度,避免过度校正
|
||||
三、地形校正的工作流程
|
||||
从 topo.py:104-154 可以看出地形校正的完整流程:
|
||||
|
||||
1. 设置地形参数(set_topo)
|
||||
↓
|
||||
2. 生成计算掩膜(calc_mask)
|
||||
↓
|
||||
3. 计算校正系数(calc_*_coeffs)
|
||||
- 遍历每个波段(跳过坏波段)
|
||||
- 根据选定的算法类型计算校正因子
|
||||
↓
|
||||
4. 应用校正(apply_*)
|
||||
- 支持多种数据切分方式:line/column/band/chunk/pixels
|
||||
四、关键配置参数
|
||||
参数 类型 说明 适用算法
|
||||
type
|
||||
str
|
||||
校正算法类型
|
||||
所有
|
||||
calc_mask
|
||||
list
|
||||
计算校正系数的掩膜条件
|
||||
所有
|
||||
apply_mask
|
||||
list
|
||||
应用校正的掩膜条件
|
||||
所有
|
||||
c_fit_type
|
||||
str
|
||||
C系数拟合方法:ols/nnls/wls
|
||||
c, scs+c
|
||||
sample_perc
|
||||
float
|
||||
采样比例(用于分组计算)
|
||||
分组计算时
|
||||
五、算法选择建议
|
||||
场景 推荐算法 理由
|
||||
裸土/岩石
|
||||
Cosine 或 C
|
||||
地表接近朗伯体
|
||||
森林植被
|
||||
SCS+C
|
||||
考虑冠层几何,校正效果最好
|
||||
草地/灌木
|
||||
SCS
|
||||
简单有效
|
||||
混合地物
|
||||
Modified Minnaert
|
||||
自适应不同地物
|
||||
需要精确结果
|
||||
C 或 SCS+C + 分组计算
|
||||
按NDVI分组计算C系数
|
||||
六、参考文献
|
||||
SCS+C 方法:Soenen, S. A., Peddle, D. R., & Coburn, C. A. (2005). SCS+C: A Modified Sun-Canopy-Sensor Topographic Correction in Forested Terrain. IEEE TGRS, 43(9), 2148-2159.
|
||||
|
||||
方法比较:Richter, R., Kellenberger, T., & Kaufmann, H. (2009). Comparison of topographic correction methods. Remote Sensing, 1(3), 184-196.
|
||||
|
||||
C 校正:Teillet, P. M., Guindon, B., & Goodenough, D. G. (1982). On the slope-aspect correction of multispectral scanner data. Canadian Journal of Remote Sensing, 8(2), 84-106.
|
||||
1728
GUI/brdf_gui.py
1728
GUI/brdf_gui.py
File diff suppressed because it is too large
Load Diff
133
GUI/temp_flexbrdf_config.json
Normal file
133
GUI/temp_flexbrdf_config.json
Normal file
@ -0,0 +1,133 @@
|
||||
{
|
||||
"input_files": [
|
||||
"E:/is2/yaopu/output/before.dat"
|
||||
],
|
||||
"file_type": "envi",
|
||||
"bad_bands": [],
|
||||
"num_cpus": 10,
|
||||
"anc_files": {
|
||||
"E:/is2/yaopu/output/before.dat": {
|
||||
"path_length": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
0
|
||||
],
|
||||
"sensor_az": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
9
|
||||
],
|
||||
"sensor_zn": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
8
|
||||
],
|
||||
"solar_az": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
7
|
||||
],
|
||||
"solar_zn": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
6
|
||||
],
|
||||
"phase": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
0
|
||||
],
|
||||
"slope": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
0
|
||||
],
|
||||
"aspect": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
0
|
||||
],
|
||||
"cosine_i": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
0
|
||||
],
|
||||
"utc_time": [
|
||||
"E:/is2/yaopu/output/angel.dat",
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"corrections": [
|
||||
"brdf"
|
||||
],
|
||||
"topo": {
|
||||
"type": "mod_minneart",
|
||||
"calc_mask": [],
|
||||
"apply_mask": [],
|
||||
"c_fit_type": "nnls"
|
||||
},
|
||||
"brdf": {
|
||||
"type": "flex",
|
||||
"grouped": true,
|
||||
"geometric": "li_dense_r",
|
||||
"volume": "ross_thick",
|
||||
"b/r": 2.5,
|
||||
"h/b": 2.0,
|
||||
"sample_perc": 0.1,
|
||||
"interp_kind": "linear",
|
||||
"calc_mask": [
|
||||
[
|
||||
"water",
|
||||
{
|
||||
"band_1": 850,
|
||||
"band_2": 660,
|
||||
"threshold": 290.0
|
||||
}
|
||||
],
|
||||
[
|
||||
"kernel_finite",
|
||||
{}
|
||||
],
|
||||
[
|
||||
"ancillary",
|
||||
{
|
||||
"name": "sensor_zn",
|
||||
"min": 0.0349,
|
||||
"max": 1000000.0
|
||||
}
|
||||
],
|
||||
[
|
||||
"ndi",
|
||||
{
|
||||
"band_1": 550,
|
||||
"band_2": 2150,
|
||||
"min": -1.0,
|
||||
"max": 0.0
|
||||
}
|
||||
]
|
||||
],
|
||||
"apply_mask": [
|
||||
[
|
||||
"water",
|
||||
{
|
||||
"band_1": 850,
|
||||
"band_2": 660,
|
||||
"threshold": 290.0
|
||||
}
|
||||
]
|
||||
],
|
||||
"bin_type": "dynamic",
|
||||
"num_bins": 18,
|
||||
"ndvi_bin_min": 0.05,
|
||||
"ndvi_bin_max": 1.0,
|
||||
"ndvi_perc_min": 10,
|
||||
"ndvi_perc_max": 95,
|
||||
"solar_zn_type": "scene"
|
||||
},
|
||||
"glint": {
|
||||
"type": "hochberg",
|
||||
"correction_wave": 2150,
|
||||
"apply_mask": []
|
||||
},
|
||||
"export": {
|
||||
"coeffs": false,
|
||||
"image": true,
|
||||
"masks": true,
|
||||
"subset_waves": [],
|
||||
"output_dir": "E:/is2/yaopu/Flex",
|
||||
"suffix": "topo_brdf_glint"
|
||||
},
|
||||
"resample": false
|
||||
}
|
||||
451
GUI/软件使用说明.md
Normal file
451
GUI/软件使用说明.md
Normal file
@ -0,0 +1,451 @@
|
||||
# BRDF校正工具 - 使用说明文档
|
||||
|
||||
## 一、软件概述
|
||||
|
||||
BRDF校正工具是一款用于高光谱图像双向反射分布函数(BRDF)校正的统一GUI应用程序,支持两种场景:
|
||||
|
||||
1. **陆地植被BRDF校正**(FlexBRDF算法)
|
||||
- 适用于陆地植被和地表的BRDF、地形和镜面反射校正
|
||||
- 支持AVIRIS、AVIRIS-NG等机载高光谱数据
|
||||
|
||||
2. **水体BRDF校正**(Ocean BRDF Correction)
|
||||
- 专用于水体和海洋的BRDF校正
|
||||
- 支持多种BRDF模型和不确定性估算
|
||||
|
||||
---
|
||||
|
||||
## 二、快速开始
|
||||
|
||||
### 2.1 启动软件
|
||||
|
||||
```bash
|
||||
python brdf_gui.py
|
||||
```
|
||||
|
||||
启动后将显示主界面,选择需要使用的模块:
|
||||
- 点击"进入陆地模块"打开陆地植被BRDF校正
|
||||
- 点击"进入水体模块"打开水体BRDF校正
|
||||
|
||||
---
|
||||
|
||||
## 三、陆地植被BRDF校正模块(FlexBRDF)
|
||||
|
||||
### 3.1 界面布局
|
||||
|
||||
模块界面分为左右两部分:
|
||||
- **左侧**:配置选项卡(文件与波段映射、校正参数、输出设置)
|
||||
- **右侧**:执行日志和进度显示
|
||||
|
||||
### 3.2 文件与波段映射页
|
||||
|
||||
#### 3.2.1 文件夹设置
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| 输入文件夹 | 反射率文件所在目录(ENVI格式) | 空 |
|
||||
| 辅助文件夹 | 观测几何数据目录 | 空 |
|
||||
| CPU核心数 | 并行处理使用的CPU核心数 | 1 |
|
||||
|
||||
**操作步骤**:
|
||||
1. 点击"浏览..."按钮选择输入文件夹和辅助文件夹
|
||||
2. 点击"自动匹配文件"按钮,软件会自动匹配反射率文件(`_rfl`后缀)和观测几何文件(`_obs`后缀)
|
||||
3. 匹配成功的文件对将显示在列表中
|
||||
|
||||
#### 3.2.2 文件匹配规则
|
||||
|
||||
- 反射率文件命名格式:`{prefix}_rfl{ext}`
|
||||
- 观测几何文件命名格式:`{prefix}_obs{ext}`
|
||||
- 软件根据`{prefix}`部分自动匹配对应文件对
|
||||
|
||||
#### 3.2.3 手动添加文件对
|
||||
|
||||
如需手动指定文件对:
|
||||
1. 在"手动添加文件对"区域,分别选择反射率文件和观测几何文件
|
||||
2. 点击"添加文件对"按钮
|
||||
|
||||
#### 3.2.4 波段映射设置
|
||||
|
||||
每个输入文件需要映射以下观测几何参数到辅助文件的波段:
|
||||
|
||||
| 参数名称 | 说明 | 默认波段 |
|
||||
|----------|------|----------|
|
||||
| path_length | 路径长度 | 0 |
|
||||
| sensor_zn | 传感器天顶角 | 1 |
|
||||
| sensor_az | 传感器方位角 | 2 |
|
||||
| solar_zn | 太阳天顶角 | 3 |
|
||||
| solar_az | 太阳方位角 | 4 |
|
||||
| phase | 相位角 | 5 |
|
||||
| slope | 坡度 | 6 |
|
||||
| aspect | 坡向 | 7 |
|
||||
| cosine_i | 入射角余弦 | 8 |
|
||||
|
||||
**设置方法**:
|
||||
1. 在文件列表中选择一个文件
|
||||
2. 在右侧波段映射表格中设置各参数对应的波段索引(从0开始)
|
||||
3. 点击"应用当前映射"保存设置
|
||||
|
||||
### 3.3 校正参数页
|
||||
|
||||
#### 3.3.1 选择校正类型
|
||||
|
||||
勾选需要执行的校正类型(可单选或多选):
|
||||
|
||||
| 校正类型 | 说明 |
|
||||
|----------|------|
|
||||
| Topo (地形校正) | 消除地形起伏对反射率的影响 |
|
||||
| BRDF (双向反射分布函数) | 校正各向异性反射效应 |
|
||||
| Glint (耀斑校正) | 消除水体或湿润表面的镜面反射 |
|
||||
|
||||
#### 3.3.2 地形校正参数(Topo Correction)
|
||||
|
||||
| 参数 | 选项 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 类型 (type) | mod_minneart, scs+c, cosine, c, scs | mod_minneart | 地形校正算法类型 |
|
||||
| 拟合类型 (c_fit_type) | nnls, ols, wls | nnls | 系数拟合方法 |
|
||||
|
||||
**掩膜条件设置**:
|
||||
- **计算掩膜 (calc_mask)**:定义参与计算校正因子的像素条件
|
||||
- **应用掩膜 (apply_mask)**:定义应用校正的像素条件
|
||||
|
||||
掩膜条件类型详见[第5章 掩膜条件说明](#五掩膜条件说明)。
|
||||
|
||||
#### 3.3.3 BRDF校正参数(BRDF Correction)
|
||||
|
||||
| 参数 | 选项/范围 | 默认值 | 说明 |
|
||||
|------|-----------|--------|------|
|
||||
| 算法类型 (type) | flex, universal | flex | BRDF算法类型 |
|
||||
| 启用分组 (grouped) | 勾选/取消 | 勾选 | 是否按NDVI分组计算 |
|
||||
| 几何核 (geometric) | li_sparse, li_dense, li_sparse_r, li_dense_r, roujean | li_sparse | 几何光学核函数 |
|
||||
| 体积核 (volume) | ross_thin, ross_thick, hotspot, roujean | ross_thick | 体积散射核函数 |
|
||||
| b/r 比率 | 0.1 - 10.0 | 2.5 | 树冠宽度/高度比 |
|
||||
| h/b 比率 | 0.1 - 5.0 | 2.0 | 树高/树冠宽度比 |
|
||||
| 采样比例 (sample_perc) | 0.01 - 1.0 | 0.1 | 用于拟合的采样比例 |
|
||||
| 插值方法 (interp_kind) | linear, nearest, cubic | linear | 插值方法 |
|
||||
| 分箱类型 (bin_type) | dynamic, fixed | dynamic | NDVI分箱类型 |
|
||||
| 分箱数量 (num_bins) | 1 - 100 | 18 | NDVI分箱数量 |
|
||||
| NDVI最小值 (ndvi_bin_min) | 0.0 - 1.0 | 0.05 | 分箱NDVI下限 |
|
||||
| NDVI最大值 (ndvi_bin_max) | 0.0 - 1.0 | 1.0 | 分箱NDVI上限 |
|
||||
| NDVI百分比最小值 (ndvi_perc_min) | 0 - 100 | 10 | 有效样本百分比下限 |
|
||||
| NDVI百分比最大值 (ndvi_perc_max) | 0 - 100 | 95 | 有效样本百分比上限 |
|
||||
| 太阳天顶角类型 (solar_zn_type) | scene, fixed, mean, custom | scene | 太阳天顶角计算方法 |
|
||||
|
||||
**核函数组合推荐**:
|
||||
- 植被覆盖区:`li_sparse` + `ross_thick`
|
||||
- 稀疏植被区:`roujean` + `roujean`
|
||||
- 浓密植被区:`li_dense` + `ross_thick`
|
||||
|
||||
#### 3.3.4 耀斑校正参数(Glint Correction)
|
||||
|
||||
| 参数 | 选项 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 类型 (type) | mod_minneart, scs+c, cosine, c, scs, nir, mask | nir | 耀斑校正方法 |
|
||||
| 拟合类型 (c_fit_type) | nnls, ols, wls | nnls | 系数拟合方法 |
|
||||
|
||||
**掩膜条件设置**:
|
||||
- **计算掩膜 (calc_mask)**:定义参与计算耀斑因子的像素
|
||||
- **应用掩膜 (apply_mask)**:定义应用耀斑校正的像素
|
||||
|
||||
### 3.4 输出设置页
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| 输出目录 | 校正结果保存路径 | 空 |
|
||||
| 后缀 (Suffix) | 输出文件名后缀 | topo_brdf_glint |
|
||||
| 输出系数 (coeffs) | 输出校正系数文件 | 不勾选 |
|
||||
| 输出图像 (image) | 输出校正后图像 | 勾选 |
|
||||
| 输出掩膜 (masks) | 输出使用的掩膜 | 不勾选 |
|
||||
|
||||
**输出文件名格式**:
|
||||
```
|
||||
{原文件名}_{后缀}.dat
|
||||
```
|
||||
|
||||
### 3.5 操作按钮
|
||||
|
||||
| 按钮 | 功能 |
|
||||
|------|------|
|
||||
| 加载 JSON 配置 | 从JSON文件加载之前保存的配置 |
|
||||
| 保存 JSON 配置 | 将当前配置保存为JSON文件 |
|
||||
| 生成配置并运行 | 生成配置并启动BRDF校正处理 |
|
||||
|
||||
---
|
||||
|
||||
## 四、水体BRDF校正模块(Ocean BRDF)
|
||||
|
||||
### 4.1 输入文件设置
|
||||
|
||||
| 参数 | 文件格式 | 说明 |
|
||||
|------|----------|------|
|
||||
| 高光谱文件 | ENVI (.dat, .bsq, .bip, .bil, .hdr) | 高光谱遥感影像 |
|
||||
| 角度文件 | ENVI (.dat, .bsq, .bip, .bil, .hdr) | 观测几何角度数据 |
|
||||
| 水体掩膜 | GeoTIFF (.tif, .tiff) | 水体区域掩膜 |
|
||||
|
||||
### 4.2 BRDF模型参数
|
||||
|
||||
| 参数 | 选项 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| BRDF模型 | L11, M02, M02SeaDAS, O25 | L11 | BRDF校正模型 |
|
||||
| 处理块大小 | 1024 - 16384 | 4096 | 光谱维分块大小 |
|
||||
| 空间块大小 | 256 - 2048 | 512 | 空间维分块大小 |
|
||||
| 计算不确定性 | 勾选/取消 | 取消 | 是否计算BRDF不确定度 |
|
||||
|
||||
**BRDF模型说明**:
|
||||
- **L11**:Lee et al. (2011) 模型,适用于开阔大洋
|
||||
- **M02**:Morel et al. (2002) 模型,经典水体BRDF模型
|
||||
- **M02SeaDAS**:SeaDAS实现的M02模型
|
||||
- **O25**:最新优化模型
|
||||
|
||||
### 4.3 输出变量选择
|
||||
|
||||
支持多选(按住Ctrl键选择多个):
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| Rw_brdf | BRDF校正后的水体反射率 |
|
||||
| rho_ex_w | 水体反射率 (nrrs * π) |
|
||||
| nrrs | 归一化遥感反射率 |
|
||||
| C_brdf | BRDF校正因子 |
|
||||
| brdf_unc | BRDF不确定度(需勾选计算不确定性) |
|
||||
| nrrs_unc | nrrs不确定度(需勾选计算不确定性) |
|
||||
|
||||
**默认选中**:Rw_brdf, nrrs, C_brdf
|
||||
|
||||
### 4.4 输出设置
|
||||
|
||||
| 参数 | 选项 | 默认值 |
|
||||
|------|------|--------|
|
||||
| 输出路径 | 文件前缀路径 | 空 |
|
||||
| 输出格式 | ENVI | ENVI |
|
||||
|
||||
### 4.5 运行
|
||||
|
||||
点击"运行水体BRDF校正"按钮开始处理。
|
||||
|
||||
---
|
||||
|
||||
## 五、掩膜条件说明
|
||||
|
||||
掩膜用于定义参与计算或应用校正的像素范围。所有掩膜条件取交集(同时满足)。
|
||||
|
||||
### 5.1 支持的掩膜类型
|
||||
|
||||
| 类型 | 说明 | 参数 |
|
||||
|------|------|------|
|
||||
| **ndi** | 归一化差值指数 | band_1, band_2, min, max |
|
||||
| **ancillary** | 辅助数据掩膜 | name, min, max |
|
||||
| **neon_edge** | NEON数据边缘掩膜 | radius |
|
||||
| **kernel_finite** | 核函数有限性掩膜 | 无参数 |
|
||||
| **water** | 水体掩膜 | band_1, band_2, threshold |
|
||||
| **external** | 外部掩膜文件 | file_path, class |
|
||||
| **band** | 单波段阈值 | band, min, max |
|
||||
|
||||
### 5.2 各类型详细参数
|
||||
|
||||
#### ndi(归一化差值指数)
|
||||
|
||||
```
|
||||
ndi = (band_1 - band_2) / (band_1 + band_2)
|
||||
保留范围: [min, max] 内的像素
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| band_1 | 整数 | 550 | 第一个波段波长(nm) |
|
||||
| band_2 | 整数 | 2150 | 第二个波段波长(nm) |
|
||||
| min | 浮点数 | -1.0 | NDI最小值 |
|
||||
| max | 浮点数 | 0.0 | NDI最大值 |
|
||||
|
||||
**典型应用**:
|
||||
- NDVI植被掩膜:band_1=850, band_2=680, min=0.2, max=1.0
|
||||
- NDWI水体掩膜:band_1=860, band_2=1240, min=-1.0, max=0.0
|
||||
|
||||
#### ancillary(辅助数据掩膜)
|
||||
|
||||
基于观测几何参数创建掩膜。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 可选值 |
|
||||
|------|------|--------|--------|
|
||||
| name | 字符串 | slope | slope, aspect, cosine_i, sensor_zn, sensor_az, solar_zn, solar_az, phase, path_length, utc_time |
|
||||
| min | 浮点数 | 0.0 | - |
|
||||
| max | 浮点数 | 1.0 | - |
|
||||
|
||||
**典型应用**:
|
||||
- 坡度掩膜:只处理坡度小于30度的区域
|
||||
- name=slope, min=0, max=30
|
||||
- 太阳天顶角掩膜:排除大太阳天顶角
|
||||
- name=solar_zn, min=0, max=60
|
||||
|
||||
#### neon_edge(NEON边缘掩膜)
|
||||
|
||||
用于消除NEON高光谱数据图像边缘的噪声区域。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| radius | 整数 | 1 | 边缘裁剪像素数 |
|
||||
|
||||
#### kernel_finite(核函数有限性掩膜)
|
||||
|
||||
自动排除会导致核函数计算溢出或不稳定的像素。无需参数。
|
||||
|
||||
#### water(水体掩膜)
|
||||
|
||||
使用特定波段组合识别水体。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| band_1 | 整数 | 550 | 第一个波段波长(nm) |
|
||||
| band_2 | 整数 | 850 | 第二个波段波长(nm) |
|
||||
| threshold | 浮点数 | 0.0 | 水体识别阈值 |
|
||||
|
||||
#### external(外部掩膜文件)
|
||||
|
||||
使用外部分类或掩膜文件。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| file_path | 字符串 | 空 | 外部掩膜文件路径 |
|
||||
| class | 整数 | 1 | 要保留的类别值 |
|
||||
|
||||
#### band(单波段阈值)
|
||||
|
||||
基于单个波段的值创建掩膜。
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| band | 整数 | 650 | 波段波长(nm) |
|
||||
| min | 浮点数 | 0.0 | 最小值 |
|
||||
| max | 浮点数 | 1.0 | 最大值 |
|
||||
|
||||
### 5.3 掩膜条件设置步骤
|
||||
|
||||
1. 在校正参数页找到掩膜设置区域
|
||||
2. 点击"+ 添加条件"按钮
|
||||
3. 选择掩膜类型
|
||||
4. 设置对应参数值
|
||||
5. 可添加多个条件(取交集)
|
||||
6. 点击"删除"按钮移除不需要的条件
|
||||
|
||||
---
|
||||
|
||||
## 六、配置文件的保存与加载
|
||||
|
||||
### 6.1 保存配置
|
||||
|
||||
1. 完成所有参数设置
|
||||
2. 点击"保存 JSON 配置"按钮
|
||||
3. 选择保存路径和文件名
|
||||
4. 配置将以JSON格式保存,包含:
|
||||
- 输入文件列表
|
||||
- 波段映射关系
|
||||
- 校正参数设置
|
||||
- 掩膜条件
|
||||
- 输出设置
|
||||
|
||||
### 6.2 加载配置
|
||||
|
||||
1. 点击"加载 JSON 配置"按钮
|
||||
2. 选择之前保存的JSON配置文件
|
||||
3. 软件将自动恢复所有设置
|
||||
|
||||
**注意**:
|
||||
- 加载配置后会自动匹配文件对,请确保输入文件和辅助文件路径仍然有效
|
||||
- 如果文件路径已改变,需要重新手动设置文件夹路径
|
||||
|
||||
---
|
||||
|
||||
## 七、典型工作流程
|
||||
|
||||
### 7.1 陆地植被BRDF校正流程
|
||||
|
||||
1. **启动软件** → 选择"陆地植被BRDF校正"
|
||||
2. **设置文件夹** → 选择输入文件夹和辅助文件夹
|
||||
3. **匹配文件** → 点击"自动匹配文件"
|
||||
4. **检查映射** → 确认波段映射设置正确
|
||||
5. **选择校正** → 勾选需要的校正类型(Topo/BRDF/Glint)
|
||||
6. **设置参数** → 根据数据特点调整校正参数
|
||||
7. **设置掩膜** → 添加合适的掩膜条件(如NDVI>0.2)
|
||||
8. **设置输出** → 指定输出目录和后缀
|
||||
9. **保存配置** → (可选)保存当前配置
|
||||
10. **运行** → 点击"生成配置并运行"
|
||||
|
||||
### 7.2 水体BRDF校正流程
|
||||
|
||||
1. **启动软件** → 选择"水体BRDF校正"
|
||||
2. **选择文件** → 依次选择高光谱文件、角度文件、水体掩膜
|
||||
3. **选择模型** → 选择适合的BRDF模型(通常用L11)
|
||||
4. **选择变量** → 勾选需要的输出变量
|
||||
5. **调整参数** → (可选)调整块大小以优化性能
|
||||
6. **设置输出** → 指定输出文件前缀路径
|
||||
7. **运行** → 点击"运行水体BRDF校正"
|
||||
|
||||
---
|
||||
|
||||
## 八、常见问题与解决方案
|
||||
|
||||
### 8.1 文件匹配问题
|
||||
|
||||
**问题**:自动匹配找不到文件对
|
||||
|
||||
**解决**:
|
||||
- 检查文件命名是否符合 `{prefix}_rfl` 和 `{prefix}_obs` 格式
|
||||
- 确保输入文件夹和辅助文件夹路径正确
|
||||
- 使用手动添加功能指定文件对
|
||||
|
||||
### 8.2 波段映射问题
|
||||
|
||||
**问题**:提示波段索引超出范围
|
||||
|
||||
**解决**:
|
||||
- 检查辅助文件的实际波段数
|
||||
- 确认波段索引从0开始计数
|
||||
- 验证辅助文件格式正确
|
||||
|
||||
### 8.3 内存不足问题
|
||||
|
||||
**问题**:处理大文件时内存溢出
|
||||
|
||||
**解决**:
|
||||
- 减少CPU核心数设置
|
||||
- 对于水体模块,减小处理块大小(chunk_size)
|
||||
- 分块处理(FlexBRDF会自动分块)
|
||||
|
||||
### 8.4 掩膜问题
|
||||
|
||||
**问题**:校正后结果为空或过少
|
||||
|
||||
**解决**:
|
||||
- 检查掩膜条件是否过于严格
|
||||
- 验证NDVI或波段阈值设置是否合理
|
||||
- 查看掩膜类型是否与数据匹配
|
||||
|
||||
---
|
||||
|
||||
## 九、技术参数参考
|
||||
|
||||
### 9.1 支持的文件格式
|
||||
|
||||
| 类型 | 格式 |
|
||||
|------|------|
|
||||
| 输入反射率 | ENVI标准格式 (.dat + .hdr) |
|
||||
| 观测几何 | ENVI标准格式 (.dat + .hdr) |
|
||||
| 水体掩膜 | GeoTIFF (.tif) |
|
||||
| 输出结果 | ENVI标准格式 |
|
||||
| 配置文件 | JSON (.json) |
|
||||
|
||||
### 9.2 系统要求
|
||||
|
||||
- **操作系统**:Windows 10/11, Linux, macOS
|
||||
- **Python版本**:3.8+
|
||||
- **内存**:建议8GB以上(处理大图像需要更多)
|
||||
- **依赖库**:PyQt5, numpy, scipy等(详见requirements.txt)
|
||||
|
||||
---
|
||||
|
||||
## 十、联系与支持
|
||||
|
||||
如有问题或建议,请查看项目文档或联系开发团队。
|
||||
|
||||
---
|
||||
|
||||
*文档版本:v1.0*
|
||||
*最后更新:2026年4月*
|
||||
1417
angle/angle_compute.py
Normal file
1417
angle/angle_compute.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -177,11 +177,11 @@ def _block_windows(src, block_size=512):
|
||||
|
||||
def _resolve_output_envi_path(output_file):
|
||||
"""Return the ENVI image path used for rasterio writes."""
|
||||
if output_file.lower().endswith(".img"):
|
||||
if output_file.lower().endswith(".dat"):
|
||||
return output_file
|
||||
if output_file.lower().endswith(".hdr"):
|
||||
return output_file[:-4] + ".img"
|
||||
return output_file + ".img"
|
||||
return output_file[:-4] + ".dat"
|
||||
return output_file + ".dat"
|
||||
|
||||
|
||||
def _read_wavelengths_from_header(hyperspectral_file):
|
||||
@ -259,7 +259,7 @@ def _get_output_filename(base_output_file: str, var_name: str) -> str:
|
||||
input.hdr + brdf_unc -> input_brdf_unc.hdr
|
||||
"""
|
||||
base, ext = os.path.splitext(base_output_file)
|
||||
if ext.lower() in ('.hdr', '.img'):
|
||||
if ext.lower() in ('.hdr', '.dat'):
|
||||
base = base # keep base without extension
|
||||
suffix = f"_{var_name}" if var_name.lower() != "rw_brdf" else ""
|
||||
return f"{base}{suffix}{ext}"
|
||||
@ -486,6 +486,29 @@ def run_brdf_correction_block(
|
||||
for dst in output_dsts.values():
|
||||
dst.close()
|
||||
|
||||
# Write ENVI header files with wavelength information from input
|
||||
for var_name, out_path in output_paths.items():
|
||||
hdr_path = out_path[:-4] + ".hdr" if out_path.lower().endswith(".dat") else out_path + ".hdr"
|
||||
# Prepare ENVI header content with wavelengths from input file
|
||||
header_lines = [
|
||||
"ENVI",
|
||||
f"description = {{BRDF corrected result: {var_name}}}",
|
||||
f"samples = {n_cols}",
|
||||
f"lines = {n_rows}",
|
||||
f"bands = {n_bands}",
|
||||
"header offset = 0",
|
||||
"file type = ENVI Standard",
|
||||
"data type = 4", # float32
|
||||
"interleave = bsq",
|
||||
"byte order = 0", # little-endian
|
||||
f"wavelength = {{{', '.join(str(v) for v in wavelengths.tolist())}}}",
|
||||
"wavelength units = nm",
|
||||
]
|
||||
# Write header file
|
||||
with open(hdr_path, 'w') as f:
|
||||
f.write('\n'.join(header_lines))
|
||||
print(f" Written header: {hdr_path}")
|
||||
|
||||
# Print performance statistics
|
||||
total_blocks = blocks_processed + blocks_skipped
|
||||
if total_blocks > 0:
|
||||
@ -864,7 +887,7 @@ def main():
|
||||
parser.add_argument("hyperspectral_file", help="Input ENVI BSQ hyperspectral file or .hdr path.")
|
||||
parser.add_argument("angle_file", help="Input ENVI BIP angle file or .hdr path.")
|
||||
parser.add_argument("mask_file", help="Input water mask GeoTIFF; pixels == 1 are corrected.")
|
||||
parser.add_argument("output_file", help="Output path prefix. ENVI writes .hdr/.img, NetCDF writes .nc.")
|
||||
parser.add_argument("output_file", help="Output path prefix. ENVI writes .hdr/.dat, NetCDF writes .nc.")
|
||||
parser.add_argument("--brdf-model", default="L11", choices=["L11", "M02", "M02SeaDAS", "O25"])
|
||||
parser.add_argument("--output-var", nargs='+', default=["Rw_brdf"],
|
||||
help="Output variables to save (one or more). "
|
||||
|
||||
@ -1,14 +1,42 @@
|
||||
# BRDF_GUI 项目依赖
|
||||
# 安装命令: pip install -r requirements.txt
|
||||
#
|
||||
# 本项目包含两个主要模块:
|
||||
# 1. 陆地植被BRDF校正 (FlexBRDF) - 使用 HyTools 库
|
||||
# 2. 水体BRDF校正 (Ocean BRDF) - 使用 ocbrdf 模块
|
||||
|
||||
# ========== 核心科学计算 ==========
|
||||
numpy>=1.21.0
|
||||
ray>=2.0.0
|
||||
h5py>=3.0.0
|
||||
h5netcdf>=0.13.0
|
||||
matplotlib>=3.5.0
|
||||
pandas>=1.3.0
|
||||
scikit-learn>=1.0.0
|
||||
scipy>=1.7.0
|
||||
pandas>=1.3.0
|
||||
xarray>=0.20.0
|
||||
rasterio>=1.2.0
|
||||
spectral>=0.22.0
|
||||
|
||||
# ========== GUI 界面 ==========
|
||||
PyQt5>=5.15.0
|
||||
|
||||
# ========== 高光谱数据处理 ==========
|
||||
spectral>=0.22.0 # ENVI文件读写
|
||||
rasterio>=1.3.0 # 栅格数据处理 (GeoTIFF/ENVI)
|
||||
geopandas>=0.10.0 # 地理空间数据处理
|
||||
shapely>=2.0.0 # 几何形状处理
|
||||
|
||||
# ========== HDF5/NetCDF 支持 ==========
|
||||
h5py>=3.0.0 # HDF5文件支持
|
||||
h5netcdf>=0.13.0 # NetCDF via HDF5
|
||||
netCDF4>=1.6.0 # NetCDF文件支持 (可选)
|
||||
|
||||
# ========== 并行处理 ==========
|
||||
ray>=2.0.0
|
||||
|
||||
# ========== 机器学习 ==========
|
||||
scikit-learn>=1.0.0
|
||||
|
||||
# ========== 可视化 ==========
|
||||
matplotlib>=3.5.0
|
||||
|
||||
# ========== 观测几何计算 ==========
|
||||
pvlib>=0.10.0 # 太阳位置计算
|
||||
pyproj>=3.4.0 # 坐标投影转换
|
||||
|
||||
# ========== 进度条/工具 ==========
|
||||
tqdm>=4.60.0
|
||||
geopandas>=0.10.0
|
||||
shapely>=2.0.0
|
||||
|
||||
103
slope/caijian.py
Normal file
103
slope/caijian.py
Normal file
@ -0,0 +1,103 @@
|
||||
import rasterio
|
||||
from rasterio.warp import reproject, Resampling
|
||||
import numpy as np
|
||||
|
||||
def merge_angles_with_slope_aspect(angle_file, slope_aspect_file, output_file,
|
||||
resampling=Resampling.bilinear):
|
||||
"""
|
||||
将坡度坡向波段添加到角度文件波段之后,输出合并的ENVI DAT文件。
|
||||
输出文件的空间范围、分辨率、投影与角度文件完全一致。
|
||||
掩膜基于角度文件的忽略值生成:只有所有角度波段均为忽略值(0.0)的像素才被设为忽略值。
|
||||
输出文件的忽略值统一设为0.0。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
angle_file : str
|
||||
包含角度信息的ENVI DAT文件(如传感器角度、太阳角度等,至少1个波段)。
|
||||
该文件定义了输出的目标空间参数和掩膜基准。
|
||||
slope_aspect_file : str
|
||||
包含坡度坡向的ENVI DAT文件(通常2个波段)。
|
||||
output_file : str
|
||||
输出合并后的ENVI DAT文件路径(将自动生成.hdr头文件)。
|
||||
resampling : rasterio.warp.Resampling, optional
|
||||
重采样方法,默认为双线性(适用于连续参数)。
|
||||
"""
|
||||
# 1. 从角度文件获取目标空间参数、数据、掩膜
|
||||
with rasterio.open(angle_file) as src_angle:
|
||||
# 目标空间参数
|
||||
dst_transform = src_angle.transform
|
||||
dst_width = src_angle.width
|
||||
dst_height = src_angle.height
|
||||
dst_crs = src_angle.crs
|
||||
|
||||
# 读取角度文件所有波段
|
||||
angle_data = src_angle.read() # shape: (bands, rows, cols)
|
||||
angle_count = src_angle.count
|
||||
|
||||
# 获取角度文件的忽略值(如果未定义,默认为0.0,但应给出警告)
|
||||
angle_nodata = src_angle.nodata
|
||||
if angle_nodata is None:
|
||||
print("警告:角度文件未定义忽略值,将使用0.0作为默认忽略值。")
|
||||
angle_nodata = 0.0
|
||||
|
||||
# 构建掩膜:所有波段均不等于忽略值视为有效
|
||||
mask = np.all(angle_data != angle_nodata, axis=0)
|
||||
|
||||
# 2. 处理坡度坡向文件:重采样到目标空间
|
||||
with rasterio.open(slope_aspect_file) as src_sa:
|
||||
src_sa_data = src_sa.read() # shape: (bands, src_sa_height, src_sa_width)
|
||||
src_sa_count = src_sa.count
|
||||
src_sa_crs = src_sa.crs
|
||||
src_sa_transform = src_sa.transform
|
||||
|
||||
sa_resampled = np.empty((src_sa_count, dst_height, dst_width),
|
||||
dtype=src_sa_data.dtype)
|
||||
|
||||
reproject(
|
||||
source=src_sa_data,
|
||||
destination=sa_resampled,
|
||||
src_transform=src_sa_transform,
|
||||
src_crs=src_sa_crs,
|
||||
dst_transform=dst_transform,
|
||||
dst_crs=dst_crs,
|
||||
resampling=resampling
|
||||
)
|
||||
|
||||
# 3. 合并波段:角度在前,坡度坡向在后
|
||||
merged_data = np.concatenate([angle_data, sa_resampled], axis=0)
|
||||
merged_count = merged_data.shape[0]
|
||||
|
||||
# 4. 准备输出元数据(基于角度文件的profile)
|
||||
with rasterio.open(angle_file) as src_angle:
|
||||
out_profile = src_angle.profile.copy()
|
||||
output_nodata = 0.0 # 强制输出忽略值为0.0(用户要求)
|
||||
out_profile.update({
|
||||
'driver': 'ENVI',
|
||||
'height': dst_height,
|
||||
'width': dst_width,
|
||||
'transform': dst_transform,
|
||||
'crs': dst_crs,
|
||||
'count': merged_count,
|
||||
'dtype': merged_data.dtype,
|
||||
'nodata': output_nodata
|
||||
})
|
||||
|
||||
# 5. 应用掩膜:将无效区域的像素设为忽略值0.0
|
||||
for b in range(merged_count):
|
||||
merged_data[b, ~mask] = output_nodata
|
||||
|
||||
# 6. 写入输出文件
|
||||
with rasterio.open(output_file, 'w', **out_profile) as dst:
|
||||
dst.write(merged_data)
|
||||
|
||||
print(f"处理完成!合并后的文件已保存至:{output_file}")
|
||||
print(f"总波段数:{merged_count}(角度 {angle_count} 波段 + 坡度坡向 {src_sa_count} 波段)")
|
||||
print(f"有效像素区域基于角度文件所有波段判断,忽略值统一设为 0.0。")
|
||||
|
||||
# 示例用法
|
||||
if __name__ == "__main__":
|
||||
angle_file = r"E:\is2\yaopu\output\angel.dat" # 角度DAT文件
|
||||
slope_aspect_file = r"E:\is2\yaopu\DEM\slope_aspect_cosine.dat" # 原始坡度坡向DAT
|
||||
out_file = r"E:\is2\yaopu\DEM\angles_with_slope_aspect.dat" # 合并输出
|
||||
|
||||
merge_angles_with_slope_aspect(angle_file, slope_aspect_file, out_file)
|
||||
197
slope/readme.txt
Normal file
197
slope/readme.txt
Normal file
@ -0,0 +1,197 @@
|
||||
================================================================================
|
||||
地形校正模块 - 已知问题与改进方向
|
||||
================================================================================
|
||||
|
||||
【问题描述】
|
||||
-----------
|
||||
当前版本的 slope_aspectV2.py 在处理推扫式无人机高光谱图像时,存在一个关键假设:
|
||||
整幅图像使用单一的太阳天顶角和方位角来计算 cosine_i(入射角余弦)。
|
||||
|
||||
然而,实际数据采集情况是:
|
||||
- 高光谱图像由3个架次的无人机飞行拼接而成
|
||||
- 每个架次的飞行时间不同(通常间隔数分钟至数十分钟)
|
||||
- 因此,不同扫描线(或不同图像区域)对应的太阳角度是不同的
|
||||
- 但目前代码仅基于DEM中心点坐标计算一组太阳角度,并应用于整幅图像
|
||||
|
||||
【技术影响】
|
||||
-----------
|
||||
这个简化会导致以下问题:
|
||||
|
||||
1. 时间跨度误差
|
||||
- 假设3个架次间隔30分钟,太阳方位角可能变化7-8度(取决于纬度)
|
||||
- 在日出/日落时段,太阳高度角变化可达10-15度
|
||||
- 这会导致不同架次交界处的 cosine_i 出现系统性偏差
|
||||
|
||||
2. 地形校正不准确
|
||||
- 东向坡面:过早架次会低估照明,过晚架次会高估照明
|
||||
- 西向坡面:与东向坡面相反
|
||||
- 交界区域会出现明显的"拼接缝"现象
|
||||
|
||||
3. BRDF校正累积误差
|
||||
- cosine_i 是许多BRDF模型的关键输入参数
|
||||
- 角度误差会传播到后续的BRDF地形校正结果
|
||||
|
||||
【当前代码实现】
|
||||
---------------
|
||||
文件: slope/slope_aspectV2.py
|
||||
|
||||
当前流程:
|
||||
1. 读取DEM -> 2. 计算坡度/坡向 -> 3. 获取DEM中心点坐标(lat, lon)
|
||||
4. 根据单一时间计算一组太阳角度(solar_zn, solar_az)
|
||||
5. 将这组角度应用于整幅图像的所有像素
|
||||
|
||||
关键代码段(约110-130行):
|
||||
```python
|
||||
# 获取DEM中心点坐标
|
||||
latitude, longitude = get_dem_center_coords(dem) # 仅一个中心点!
|
||||
|
||||
# 计算太阳角度(仅一组)
|
||||
solar_zn_deg, solar_az_deg = calc_solar_angles(latitude, longitude, observation_time)
|
||||
|
||||
# 应用于整幅图像
|
||||
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
|
||||
```
|
||||
|
||||
【需要的改进方向】
|
||||
-----------------
|
||||
接手人员需要进行以下修改:
|
||||
|
||||
方案A: 分架次处理(推荐)
|
||||
----------------------------
|
||||
如果已知3个架次的地理分界线或时间范围:
|
||||
|
||||
1. 输入格式修改
|
||||
- 接收3组时间参数(或从飞行日志自动提取)
|
||||
- 例如: -t1 "2024-06-15 08:30:00" -t2 "2024-06-15 09:00:00" -t3 "2024-06-15 09:30:00"
|
||||
|
||||
2. 空间分区处理
|
||||
- 根据每架次的覆盖范围(可从高光谱图像的GLT或地理坐标确定)
|
||||
- 将DEM分割为3个区域
|
||||
- 每个区域使用对应架次的时间计算太阳角度
|
||||
|
||||
3. 修改后的伪代码示例:
|
||||
```python
|
||||
# 定义3个架次的时间和地理范围
|
||||
flight_segments = [
|
||||
{'time': '2024-06-15 08:30:00', 'row_range': (0, 1200)}, # 架次1: 行0-1199
|
||||
{'time': '2024-06-15 09:00:00', 'row_range': (1200, 2800)}, # 架次2: 行1200-2799
|
||||
{'time': '2024-06-15 09:30:00', 'row_range': (2800, 4000)}, # 架次3: 行2800-3999
|
||||
]
|
||||
|
||||
# 初始化cosine_i数组
|
||||
cosine_i = np.zeros_like(slope_data)
|
||||
|
||||
# 分段计算
|
||||
for segment in flight_segments:
|
||||
dt = parse_datetime(segment['time'])
|
||||
solar_zn, solar_az = calc_solar_angles(lat, lon, dt)
|
||||
|
||||
y1, y2 = segment['row_range']
|
||||
cosine_i[y1:y2, :] = calc_cosine_i(
|
||||
np.deg2rad(solar_zn), np.deg2rad(solar_az),
|
||||
aspect_rad[y1:y2, :], slope_rad[y1:y2, :]
|
||||
)
|
||||
```
|
||||
|
||||
方案B: 逐行/逐像素太阳角度(更精确)
|
||||
-------------------------------------
|
||||
如果需要更高精度(考虑太阳角度在单架次内的缓慢变化):
|
||||
|
||||
1. 输入要求
|
||||
- 高光谱图像的每一行对应的时间戳(可从IMU/GPS数据提取)
|
||||
- 或者:每个像素的精确UTC时间
|
||||
|
||||
2. 逐行计算
|
||||
```python
|
||||
# 假设times是一个与图像行数相同长度的一维数组
|
||||
# times[i] = 第i行的观测时间(datetime对象)
|
||||
|
||||
for row in range(n_rows):
|
||||
dt = observation_times[row] # 该行的具体时间
|
||||
solar_zn, solar_az = calc_solar_angles(lat, lon, dt)
|
||||
|
||||
cosine_i[row, :] = calc_cosine_i(
|
||||
np.deg2rad(solar_zn), np.deg2rad(solar_az),
|
||||
aspect_rad[row, :], slope_rad[row, :]
|
||||
)
|
||||
```
|
||||
|
||||
3. 性能优化
|
||||
- 由于pvlib计算较慢,可对时间相近的行批量计算
|
||||
- 例如:每10行计算一次太阳角度,中间行插值
|
||||
|
||||
方案C: 从飞行日志自动提取(最自动化)
|
||||
--------------------------------------
|
||||
如果飞行平台记录了GPS/IMU数据:
|
||||
|
||||
1. 输入数据
|
||||
- 读取POS数据(位置、姿态、时间)
|
||||
- 或从高光谱图像的GLT(地理位置查找表)提取
|
||||
|
||||
2. 建立时间-位置映射
|
||||
- 根据无人机位置和时间建立每行图像的观测时间
|
||||
|
||||
3. 分区域计算cosine_i
|
||||
|
||||
【数据结构参考】
|
||||
---------------
|
||||
典型的推扫式高光谱数据结构:
|
||||
|
||||
高光谱图像 (lines, samples, bands)
|
||||
- lines: 扫描线数(飞行方向的像素数)
|
||||
- samples: 每行的像素数(垂直飞行方向)
|
||||
- 第i行对应无人机的第i个位置
|
||||
|
||||
关键信息来源:
|
||||
1. 图像头文件 (.hdr) - 通常包含起始时间和积分时间
|
||||
2. GLT文件 (Geographic Lookup Table) - 每个像素的地理坐标
|
||||
3. 飞行日志/POS数据 - 每行对应的时间戳
|
||||
4. 帧辅助数据 (Frame Auxiliary Data) - 部分传感器自带每帧时间
|
||||
|
||||
【建议的输入接口修改】
|
||||
----------------------
|
||||
当前命令行接口:
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:00:00"
|
||||
|
||||
建议扩展为:
|
||||
# 方式1: 手动指定3个架次的时间和行范围
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat \
|
||||
--segments "0,1200,2024-06-15T08:30:00" \
|
||||
--segments "1200,2800,2024-06-15T09:00:00" \
|
||||
--segments "2800,4000,2024-06-15T09:30:00"
|
||||
|
||||
# 方式2: 从飞行日志文件自动读取
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat --time-log flight_log.csv
|
||||
|
||||
# 方式3: 从高光谱图像头文件提取时间信息
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat --hyperspectral-ref image.hdr
|
||||
|
||||
【验证方法】
|
||||
-----------
|
||||
修改后应验证以下指标:
|
||||
|
||||
1. 连续性检查
|
||||
- 目视检查3个架次交界处的cosine_i是否连续
|
||||
- 统计交界行前后10行的cosine_i差异
|
||||
|
||||
2. 物理合理性
|
||||
- 东向坡的cosine_i应随时间增加而增加(上午飞行)
|
||||
- 西向坡的cosine_i应随时间增加而减小(上午飞行)
|
||||
|
||||
3. 与GLT对比
|
||||
- 如果高光谱有GLT,可根据每像素的太阳角度公式验证
|
||||
|
||||
【相关文件】
|
||||
-----------
|
||||
- slope_aspectV2.py - 当前主程序(需要修改)
|
||||
- angle_compute.py - 角度计算工具(可能包含相关逻辑)
|
||||
- 高光谱图像的.hdr文件 - 可能包含时间元数据
|
||||
- 飞行日志/POS数据 - 外部时间参考
|
||||
|
||||
【优先级建议】
|
||||
--------------
|
||||
1. 【高】首先实现方案A(分架次处理)- 工作量适中,效果显著
|
||||
2. 【中】评估是否需要方案B(逐行)- 取决于太阳角度变化幅度
|
||||
3. 【低】长期可考虑方案C(全自动)- 需要整合飞行数据解析
|
||||
|
||||
简单来说在计算的时候,需要加入角度文件,进行逐像素的cosi计算
|
||||
83
slope/slope_aspect.py
Normal file
83
slope/slope_aspect.py
Normal file
@ -0,0 +1,83 @@
|
||||
import xdem
|
||||
import rasterio
|
||||
import numpy as np
|
||||
|
||||
def calc_cosine_i(solar_zn, solar_az, aspect, slope):
|
||||
"""Generate cosine i image. The cosine of the incidence angle (i) is
|
||||
defined as the angle between the normal to the pixel surface
|
||||
and the solar zenith direction.
|
||||
All input geometry units must be in radians.
|
||||
|
||||
Args:
|
||||
solar_az (numpy.ndarray): Solar azimuth angle.
|
||||
solar_zn (numpy.ndarray): Solar zenith angle.
|
||||
aspect (numpy.ndarray): Ground aspect.
|
||||
slope (numpy.ndarray): Ground slope.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: Cosine i image.
|
||||
"""
|
||||
relative_az = aspect - solar_az
|
||||
cosine_i = (np.cos(solar_zn) * np.cos(slope) +
|
||||
np.sin(solar_zn) * np.sin(slope) * np.cos(relative_az))
|
||||
return cosine_i
|
||||
|
||||
# ========== 1. 读取 DEM ==========
|
||||
dem = xdem.DEM("E:/is2/yaopu/DEM/dsm.tif")
|
||||
|
||||
# ========== 2. 计算坡度、坡向 ==========
|
||||
slope = xdem.terrain.slope(dem, method='ZevenbergThorne')
|
||||
aspect = xdem.terrain.aspect(dem)
|
||||
|
||||
# ========== 3. 获取空间参考信息 ==========
|
||||
transform = dem.transform
|
||||
crs = dem.crs
|
||||
|
||||
# ========== 4. 转换为 numpy 数组 ==========
|
||||
slope_data = slope.data
|
||||
aspect_data = aspect.data
|
||||
|
||||
# ========== 5. 太阳角度(单位:度,根据实际飞行时间和地理位置设置) ==========
|
||||
solar_zn_deg = 30.0 # 太阳天顶角(从天顶到太阳的夹角),例如 30°
|
||||
solar_az_deg = 180.0 # 太阳方位角(从北顺时针),例如 180°(正南)
|
||||
|
||||
# 转换为弧度(因为三角函数需要弧度)
|
||||
solar_zn_rad = np.deg2rad(solar_zn_deg)
|
||||
solar_az_rad = np.deg2rad(solar_az_deg)
|
||||
|
||||
# 坡度和坡向也转换为弧度
|
||||
slope_rad = np.deg2rad(slope_data)
|
||||
aspect_rad = np.deg2rad(aspect_data)
|
||||
|
||||
# ========== 6. 计算 cosine_i ==========
|
||||
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
|
||||
|
||||
# ========== 7. 处理无效值(NaN) ==========
|
||||
# 注意:将 NaN 替换为 0 可能会与真实值为 0 的像元混淆(如水平面坡度为0,阴影边缘cosine_i=0)。
|
||||
# 如需严格区分,建议使用特殊值(如 -9999)作为 NoData。
|
||||
slope_data = np.nan_to_num(slope_data, nan=0.0)
|
||||
aspect_data = np.nan_to_num(aspect_data, nan=0.0)
|
||||
cosine_i = np.nan_to_num(cosine_i, nan=0.0)
|
||||
|
||||
# ========== 8. 堆叠为多波段数组(坡度、坡向、cosine_i) ==========
|
||||
stacked = np.stack([slope_data, aspect_data, cosine_i], axis=0)
|
||||
|
||||
# ========== 9. 输出 ENVI 格式文件 ==========
|
||||
output_path = "E:/is2/yaopu/DEM/slope_aspect_cosine.dat"
|
||||
|
||||
with rasterio.open(
|
||||
output_path,
|
||||
'w',
|
||||
driver='ENVI',
|
||||
height=stacked.shape[1],
|
||||
width=stacked.shape[2],
|
||||
count=stacked.shape[0],
|
||||
dtype=stacked.dtype,
|
||||
crs=crs,
|
||||
transform=transform,
|
||||
nodata=0.0, # 与上面替换的 NaN 值一致
|
||||
) as dst:
|
||||
dst.write(stacked)
|
||||
|
||||
print(f"已保存多波段 ENVI 文件:{output_path}")
|
||||
print(f"波段顺序:1-坡度(°), 2-坡向(°), 3-cosine_i")
|
||||
381
slope/slope_aspectV2.py
Normal file
381
slope/slope_aspectV2.py
Normal file
@ -0,0 +1,381 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
DEM地形处理工具 - 计算坡度、坡向和太阳入射角余弦
|
||||
|
||||
该工具从DEM文件中提取坐标信息,根据给定时间计算太阳位置,
|
||||
并生成坡度、坡向和cosine_i的多波段ENVI文件。
|
||||
|
||||
作者: BRDF_GUI Team
|
||||
版本: 2.0.0
|
||||
"""
|
||||
|
||||
import xdem
|
||||
import rasterio
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import argparse
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import pvlib.solarposition as solarposition
|
||||
from rasterio.crs import CRS
|
||||
import pyproj
|
||||
|
||||
|
||||
def calc_cosine_i(solar_zn, solar_az, aspect, slope):
|
||||
"""
|
||||
计算入射角余弦(cosine i)。
|
||||
|
||||
入射角余弦定义为地表法线与太阳天顶方向之间夹角的余弦值。
|
||||
所有角度输入必须为弧度。
|
||||
|
||||
Args:
|
||||
solar_az (numpy.ndarray): 太阳方位角(弧度)
|
||||
solar_zn (numpy.ndarray): 太阳天顶角(弧度)
|
||||
aspect (numpy.ndarray): 地面坡向(弧度)
|
||||
slope (numpy.ndarray): 地面坡度(弧度)
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: 入射角余弦图像
|
||||
"""
|
||||
relative_az = aspect - solar_az
|
||||
cosine_i = (np.cos(solar_zn) * np.cos(slope) +
|
||||
np.sin(solar_zn) * np.sin(slope) * np.cos(relative_az))
|
||||
return cosine_i
|
||||
|
||||
|
||||
def get_dem_center_coords(dem):
|
||||
"""
|
||||
获取DEM图像中心点的经纬度坐标。
|
||||
|
||||
Args:
|
||||
dem: xdem.DEM对象
|
||||
|
||||
Returns:
|
||||
tuple: (纬度, 经度) - 单位:度
|
||||
"""
|
||||
# 获取DEM的范围边界
|
||||
bounds = dem.bounds # (left, bottom, right, top)
|
||||
center_x = (bounds.left + bounds.right) / 2
|
||||
center_y = (bounds.bottom + bounds.top) / 2
|
||||
|
||||
# 如果DEM是投影坐标系,需要转换为WGS84经纬度
|
||||
if dem.crs is not None and not dem.crs.is_geographic:
|
||||
# 创建从投影坐标系到WGS84的转换
|
||||
transformer = pyproj.Transformer.from_crs(dem.crs, CRS.from_epsg(4326), always_xy=True)
|
||||
lon, lat = transformer.transform(center_x, center_y)
|
||||
else:
|
||||
# 已经是地理坐标系(经纬度)
|
||||
lon, lat = center_x, center_y
|
||||
|
||||
return lat, lon
|
||||
|
||||
|
||||
def parse_datetime(datetime_str):
|
||||
"""
|
||||
解析日期时间字符串。
|
||||
|
||||
支持的格式:
|
||||
- YYYY-MM-DD HH:MM:SS
|
||||
- YYYY-MM-DDTHH:MM:SS
|
||||
- YYYYMMDD_HHMMSS
|
||||
|
||||
Args:
|
||||
datetime_str (str): 日期时间字符串
|
||||
|
||||
Returns:
|
||||
datetime: datetime对象(UTC时间)
|
||||
"""
|
||||
formats = [
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y%m%d_%H%M%S",
|
||||
"%Y-%m-%d",
|
||||
]
|
||||
|
||||
for fmt in formats:
|
||||
try:
|
||||
dt = datetime.strptime(datetime_str, fmt)
|
||||
# 如果只输入了日期,默认时间为正午12:00
|
||||
if fmt == "%Y-%m-%d":
|
||||
dt = dt.replace(hour=12, minute=0, second=0)
|
||||
return dt
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
raise ValueError(f"无法解析日期时间: {datetime_str}. 支持的格式: YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS, YYYYMMDD_HHMMSS")
|
||||
|
||||
|
||||
def get_user_input_datetime():
|
||||
"""
|
||||
交互式获取用户输入的日期和时间。
|
||||
|
||||
Returns:
|
||||
datetime: datetime对象(UTC时间)
|
||||
"""
|
||||
print("请输入观测日期和时间(UTC时间):")
|
||||
|
||||
try:
|
||||
year = int(input(" 年份 (YYYY): "))
|
||||
month = int(input(" 月份 (MM): "))
|
||||
day = int(input(" 日期 (DD): "))
|
||||
hour = int(input(" 小时 (HH, 24小时制): "))
|
||||
minute = int(input(" 分钟 (MM): "))
|
||||
second = int(input(" 秒 (SS): "))
|
||||
|
||||
dt = datetime(year, month, day, hour, minute, second)
|
||||
print(f"\n使用的时间: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
||||
return dt
|
||||
except ValueError as e:
|
||||
print(f"输入错误: {e}")
|
||||
print("将使用默认时间 2024-06-15 10:00:00 UTC")
|
||||
return datetime(2024, 6, 15, 10, 0, 0)
|
||||
|
||||
|
||||
def calc_solar_angles(lat, lon, dt):
|
||||
"""
|
||||
使用pvlib计算太阳天顶角和方位角。
|
||||
|
||||
Args:
|
||||
lat (float): 纬度(度)
|
||||
lon (float): 经度(度)
|
||||
dt (datetime): datetime对象(UTC时间)
|
||||
|
||||
Returns:
|
||||
tuple: (太阳天顶角, 太阳方位角) - 单位:度
|
||||
天顶角: 0=天顶, 90=地平线
|
||||
方位角: 从北顺时针,0=北,90=东,180=南,270=西
|
||||
"""
|
||||
# 创建时间索引
|
||||
times = pd.DatetimeIndex([dt])
|
||||
|
||||
# 使用pvlib计算太阳位置
|
||||
solpos = solarposition.get_solarposition(times, lat, lon)
|
||||
|
||||
solar_zn_deg = solpos['zenith'].values[0] # 天顶角
|
||||
solar_az_deg = solpos['azimuth'].values[0] # 方位角
|
||||
|
||||
return solar_zn_deg, solar_az_deg
|
||||
|
||||
|
||||
def write_envi_output(output_path, data, transform, crs, metadata=None):
|
||||
"""
|
||||
将数据写入ENVI格式文件。
|
||||
|
||||
Args:
|
||||
output_path (str): 输出文件路径
|
||||
data (numpy.ndarray): 数据数组 (bands, rows, cols)
|
||||
transform: 地理变换参数
|
||||
crs: 坐标参考系
|
||||
metadata (dict, optional): 额外的元数据
|
||||
"""
|
||||
with rasterio.open(
|
||||
output_path,
|
||||
'w',
|
||||
driver='ENVI',
|
||||
height=data.shape[1],
|
||||
width=data.shape[2],
|
||||
count=data.shape[0],
|
||||
dtype=data.dtype,
|
||||
crs=crs,
|
||||
transform=transform,
|
||||
nodata=0.0,
|
||||
) as dst:
|
||||
dst.write(data)
|
||||
|
||||
# 写入ENVI头文件元数据
|
||||
if metadata:
|
||||
dst.update_tags(**metadata)
|
||||
|
||||
|
||||
def process_dem(dem_path, output_path, datetime_input=None, interactive=False):
|
||||
"""
|
||||
主处理函数:读取DEM,计算坡度坡向,计算太阳角度,输出结果。
|
||||
|
||||
Args:
|
||||
dem_path (str): DEM输入文件路径
|
||||
output_path (str): 输出文件路径
|
||||
datetime_input (str, optional): 日期时间字符串,如果为None则使用交互模式
|
||||
interactive (bool): 是否使用交互式时间输入
|
||||
|
||||
Returns:
|
||||
dict: 处理结果信息
|
||||
"""
|
||||
# ========== 1. 读取 DEM ==========
|
||||
print(f"读取DEM文件: {dem_path}")
|
||||
dem = xdem.DEM(dem_path)
|
||||
|
||||
# ========== 2. 计算坡度、坡向 ==========
|
||||
print("计算坡度和坡向...")
|
||||
slope = xdem.terrain.slope(dem, method='ZevenbergThorne')
|
||||
aspect = xdem.terrain.aspect(dem)
|
||||
|
||||
# ========== 3. 获取空间参考信息 ==========
|
||||
transform = dem.transform
|
||||
crs = dem.crs
|
||||
|
||||
# ========== 4. 转换为 numpy 数组 ==========
|
||||
slope_data = slope.data
|
||||
aspect_data = aspect.data
|
||||
|
||||
# ========== 5. 获取DEM中心点坐标并计算太阳角度 ==========
|
||||
print("\n获取DEM坐标信息...")
|
||||
latitude, longitude = get_dem_center_coords(dem)
|
||||
print(f" 中心点纬度: {latitude:.6f}°")
|
||||
print(f" 中心点经度: {longitude:.6f}°")
|
||||
print(f" 坐标系: WGS84")
|
||||
|
||||
# 获取观测时间
|
||||
if datetime_input:
|
||||
observation_time = parse_datetime(datetime_input)
|
||||
print(f"\n使用命令行提供的时间: {observation_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
||||
elif interactive or not sys.stdin.isatty():
|
||||
observation_time = get_user_input_datetime()
|
||||
else:
|
||||
# 默认时间(如果非交互式且未提供时间)
|
||||
observation_time = datetime(2024, 6, 15, 10, 0, 0)
|
||||
print(f"\n使用默认时间: {observation_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
||||
|
||||
# 计算太阳角度
|
||||
print("\n计算太阳位置...")
|
||||
solar_zn_deg, solar_az_deg = calc_solar_angles(latitude, longitude, observation_time)
|
||||
|
||||
print(f" 太阳天顶角: {solar_zn_deg:.2f}°")
|
||||
print(f" 太阳高度角: {90 - solar_zn_deg:.2f}°")
|
||||
print(f" 太阳方位角: {solar_az_deg:.2f}°")
|
||||
|
||||
# 转换为弧度
|
||||
solar_zn_rad = np.deg2rad(solar_zn_deg)
|
||||
solar_az_rad = np.deg2rad(solar_az_deg)
|
||||
|
||||
# 坡度和坡向也转换为弧度
|
||||
slope_rad = np.deg2rad(slope_data)
|
||||
aspect_rad = np.deg2rad(aspect_data)
|
||||
|
||||
# ========== 6. 计算 cosine_i ==========
|
||||
print("\n计算入射角余弦 (cosine_i)...")
|
||||
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
|
||||
|
||||
# ========== 7. 处理无效值(NaN) ==========
|
||||
slope_data = np.nan_to_num(slope_data, nan=0.0)
|
||||
aspect_data = np.nan_to_num(aspect_data, nan=0.0)
|
||||
cosine_i = np.nan_to_num(cosine_i, nan=0.0)
|
||||
|
||||
# ========== 8. 堆叠为多波段数组 ==========
|
||||
stacked = np.stack([slope_data, aspect_data, cosine_i], axis=0)
|
||||
|
||||
# ========== 9. 准备元数据 ==========
|
||||
metadata = {
|
||||
'description': 'Slope, Aspect, and Cosine_i derived from DEM',
|
||||
'band_names': 'slope(deg),aspect(deg),cosine_i',
|
||||
'solar_zenith_angle': str(solar_zn_deg),
|
||||
'solar_azimuth_angle': str(solar_az_deg),
|
||||
'solar_elevation_angle': str(90 - solar_zn_deg),
|
||||
'observation_time_utc': observation_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'dem_center_lat': str(latitude),
|
||||
'dem_center_lon': str(longitude),
|
||||
}
|
||||
|
||||
# ========== 10. 输出 ENVI 格式文件 ==========
|
||||
print(f"\n保存结果到: {output_path}")
|
||||
write_envi_output(output_path, stacked, transform, crs, metadata)
|
||||
|
||||
print("\n处理完成!")
|
||||
print(f" 输出文件: {output_path}")
|
||||
print(f" 波段顺序: 1-坡度(°), 2-坡向(°), 3-cosine_i")
|
||||
|
||||
return {
|
||||
'output_path': output_path,
|
||||
'solar_zenith': solar_zn_deg,
|
||||
'solar_azimuth': solar_az_deg,
|
||||
'center_lat': latitude,
|
||||
'center_lon': longitude,
|
||||
'observation_time': observation_time,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数 - 命令行入口"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='DEM地形处理工具 - 计算坡度、坡向和太阳入射角余弦',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
使用示例:
|
||||
# 基本用法(交互式输入时间)
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat
|
||||
|
||||
# 通过命令行指定时间
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:30:00"
|
||||
|
||||
# 仅指定日期(默认使用正午12:00)
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15"
|
||||
|
||||
# 显示详细处理信息
|
||||
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:30:00" -v
|
||||
|
||||
支持的日期时间格式:
|
||||
- "YYYY-MM-DD HH:MM:SS" (推荐)
|
||||
- "YYYY-MM-DDTHH:MM:SS" (ISO格式)
|
||||
- "YYYYMMDD_HHMMSS"
|
||||
- "YYYY-MM-DD" (默认使用12:00:00)
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument('-i', '--input', required=True,
|
||||
help='输入DEM文件路径 (支持GeoTIFF等格式)')
|
||||
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
help='输出ENVI文件路径 (.dat)')
|
||||
|
||||
parser.add_argument('-t', '--time',
|
||||
help='观测日期时间 (UTC),格式: "YYYY-MM-DD HH:MM:SS"。'
|
||||
'如果不提供,将进入交互式输入模式')
|
||||
|
||||
parser.add_argument('--interactive', action='store_true',
|
||||
help='强制使用交互式时间输入模式')
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='显示详细处理信息')
|
||||
|
||||
parser.add_argument('--version', action='version', version='%(prog)s 2.0.0')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 验证输入文件存在
|
||||
if not Path(args.input).exists():
|
||||
print(f"错误: 输入文件不存在: {args.input}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 创建输出目录(如果不存在)
|
||||
output_dir = Path(args.output).parent
|
||||
if not output_dir.exists():
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"创建输出目录: {output_dir}")
|
||||
|
||||
try:
|
||||
# 执行处理
|
||||
result = process_dem(
|
||||
dem_path=args.input,
|
||||
output_path=args.output,
|
||||
datetime_input=args.time,
|
||||
interactive=args.interactive
|
||||
)
|
||||
|
||||
if args.verbose:
|
||||
print("\n详细结果:")
|
||||
for key, value in result.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n处理失败: {e}", file=sys.stderr)
|
||||
if args.verbose:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user