增加坡度计算
This commit is contained in:
@ -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)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 检查配置文件是否存在
|
||||
if not os.path.exists(args.config):
|
||||
print(f"错误: 配置文件不存在: {args.config}")
|
||||
sys.exit(1)
|
||||
|
||||
# 检查是否为JSON文件
|
||||
if not args.config.endswith('.json'):
|
||||
print("错误: 只接受JSON格式的配置文件")
|
||||
sys.exit(1)
|
||||
|
||||
# 加载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)
|
||||
|
||||
# 必需参数
|
||||
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')
|
||||
# 验证必需的配置字段
|
||||
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)
|
||||
|
||||
# 可选参数 - 基本设置
|
||||
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')
|
||||
|
||||
# 校正类型
|
||||
parser.add_argument('--corrections', nargs='*', choices=['topo', 'brdf', 'glint'],
|
||||
default=['brdf'], help='Correction types to apply (default: brdf)')
|
||||
|
||||
# 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)')
|
||||
|
||||
# TOPO参数
|
||||
parser.add_argument('--topo-type', default='scs+c',
|
||||
choices=['scs', 'scs+c', 'c', 'cosine', 'mod_minneart'],
|
||||
help='TOPO correction type (default: scs+c)')
|
||||
|
||||
# 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.
|
||||
Reference in New Issue
Block a user