生成xBD建筑灾害语义级变化检测/建筑分割提取可执行数据集结果展示:
TI TI label T2 T2 label 语义级变化检测 label
python脚本如下:
import os
import shutil
import json
import argparse
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw
from tqdm import tqdm
# ========================== 步骤1:文件处理 ==========================
def generate_new_filename(old_name):
"""生成去除_pre/_post后缀的新文件名"""
return (
old_name.replace("_pre_disaster", "")
.replace("_post_disaster", "")
.replace("_target", "")
)
def process_single_file(src_path, dest_root):
"""处理单个文件:复制并重命名"""
try:
new_name = generate_new_filename(src_path.name)
relative_path = src_path.parent.relative_to(src_path.parents[1])
if "_pre_disaster" in src_path.name:
relative_path = Path("A") / relative_path
elif "_post_disaster" in src_path.name:
relative_path = Path("B") / relative_path
else:
raise ValueError("文件名不符合预期格式")
dest_folder = Path(dest_root) / relative_path
dest_folder.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, dest_folder / new_name)
print(f"已处理:{src_path} -> {dest_folder/new_name}")
except Exception as e:
print(f"处理失败:{src_path} | 错误:{str(e)}")
def process_files(source_dirs, dest_root):
"""批量处理文件"""
for src_dir in source_dirs:
src_path = Path(src_dir)
if not src_path.is_dir():
print(f"警告:跳过无效目录 {src_dir}")
continue
for root, _, files in os.walk(src_dir):
current_dir = Path(root)
target_files = [
f for f in files if "_pre_disaster" in f or "_post_disaster" in f
]
for file in target_files:
process_single_file(current_dir / file, dest_root)
print("[步骤1完成] 文件处理完成!")
# ========================== 步骤2:掩码值转换 ==========================
def preprocess_masks(mask):
mask[(mask >= 0.0) & (mask <= 0.3)] = 0
mask[(mask > 0.3) & (mask <= 1)] = 255
mask = mask.astype(np.uint8)
return mask
def convert_mask_values(input_dir, output_dir):
"""
将0-1掩码转换为0-255
:param input_dir: 输入目录 (dest-root/targets)
:param output_dir: 输出目录 (dest-root/targets_255)
"""
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
processed = 0
for mask_file in tqdm(list(input_dir.glob("*.png")), desc="转换掩码值"):
try:
# 读取并验证图像
img = Image.open(mask_file)
mask = np.array(img)
# 转换为0-255并保存
scaled_mask = preprocess_masks(mask)
Image.fromarray(scaled_mask).save(output_dir / mask_file.name)
processed += 1
except Exception as e:
print(f"转换失败:{mask_file} | 错误:{str(e)}")
print(f"[步骤2完成] 已转换 {processed} 个掩码文件")
# ========================== 步骤3:JSON更新 ==========================
def update_json_file(file_path, backup=True):
"""处理单个JSON文件"""
try:
filename = Path(file_path).stem
new_img_name = (
filename.replace("_post_disaster", "").replace("_pre_disaster", "") + ".png"
)
with open(file_path, "r+", encoding="utf-8") as f:
data = json.load(f)
data["metadata"]["img_name"] = new_img_name
f.seek(0)
json.dump(data, f, indent=2)
f.truncate()
print(f"已更新:{file_path}")
return True
except Exception as e:
print(f"处理失败:{file_path} | 错误:{str(e)}")
return False
def update_json(json_dir, enable_backup=True):
"""批量更新JSON文件"""
processed = 0
json_dir = Path(json_dir)
for file in json_dir.rglob("*.json"):
if enable_backup:
backup_path = file.with_suffix(".json.bak")
shutil.copy2(file, backup_path)
if update_json_file(file):
processed += 1
print(f"[步骤2完成] 共更新 {processed} 个JSON文件")
# ========================== 步骤4:灾害标签生成 ==========================
DAMAGE_MAP = {"no-damage": 1, "minor-damage": 2, "major-damage": 3, "destroyed": 4}
def clamp_coordinates(coords, max_width, max_height):
"""限制坐标在图像边界内"""
return [
(max(0, min(x, max_width - 1)), max(0, min(y, max_height - 1)))
for (x, y) in coords
]
def parse_polygon(wkt_str, img_width, img_height):
"""解析多边形坐标"""
try:
coord_str = wkt_str.split("((")[-1].split("))")[0]
raw_coords = [tuple(map(float, p.split())) for p in coord_str.split(", ")]
return clamp_coordinates(raw_coords, img_width, img_height)
except Exception as e:
print(f"坐标解析错误: {str(e)}")
return []
def generate_damage_mask(json_path, pre_mask_path, post_mask_path, output_path):
"""生成灾害等级标签图"""
try:
pre_mask = np.array(Image.open(pre_mask_path))
post_mask = np.array(Image.open(post_mask_path))
assert pre_mask.shape == post_mask.shape, "掩码尺寸不一致"
height, width = pre_mask.shape
damage_mask = np.zeros((height, width), dtype=np.uint8)
with open(json_path) as f:
data = json.load(f)
for feature in data["features"].get("xy", []):
subtype = feature.get("properties", {}).get("subtype", "no-damage")
class_id = DAMAGE_MAP.get(subtype, 0)
polygon = parse_polygon(feature["wkt"], width, height)
if len(polygon) < 3:
continue
img = Image.new("L", (width, height), 0)
ImageDraw.Draw(img).polygon(polygon, fill=1)
building_area = np.array(img)
pre_building = pre_mask * building_area
if np.sum(pre_building) == 0:
continue
post_building = post_mask * building_area
if np.sum(post_building) == 0 and class_id == 0:
class_id = 1
damage_mask[building_area == 1] = class_id
Image.fromarray(damage_mask).save(output_path)
return True
except Exception as e:
print(f"处理失败: {json_path} | 错误: {str(e)}")
return False
def generate_damage_masks(root_dir):
"""批量生成灾害标签"""
root_dir = Path(root_dir)
json_files = list((root_dir / "B" / "labels").glob("*.json"))
for json_file in tqdm(json_files, desc="生成灾害标签"):
base_name = json_file.stem
pre_mask = root_dir / "A" / "targets" / f"{base_name}.png"
post_mask = root_dir / "B" / "targets" / f"{base_name}.png"
output_dir = root_dir / "labels_damage"
output_dir.mkdir(exist_ok=True)
if pre_mask.exists() and post_mask.exists():
generate_damage_mask(
json_file, pre_mask, post_mask, output_dir / f"{base_name}.png"
)
print("[步骤3完成] 语义级标签生成完成")
# ========================== 步骤5:伪彩色转换 ==========================
def mask_to_pseudocolor(mask):
"""单通道掩码转伪彩色"""
color_map = np.array(
[
[0, 0, 0], # 背景
[128, 128, 128], # 无损坏
[0, 255, 0], # 轻微损坏
[255, 165, 0], # 严重损坏
[255, 0, 0], # 完全损毁
],
dtype=np.uint8,
)
return color_map[mask]
def convert_to_color(input_dir, output_dir):
"""批量转换伪彩色"""
input_dir, output_dir = Path(input_dir), Path(output_dir)
output_dir.mkdir(exist_ok=True)
for file in tqdm(list(input_dir.glob("*.png")), desc="转换伪彩色"):
try:
mask = np.array(Image.open(file))
color_img = Image.fromarray(mask_to_pseudocolor(mask))
color_img.save(output_dir / file.name)
except Exception as e:
print(f"转换失败: {file} | 错误: {str(e)}")
print("[步骤4完成] 伪彩色转换完成")
# ========================== 主控制流程 ==========================
def main():
parser = argparse.ArgumentParser(description="灾害数据处理流水线")
parser.add_argument("--source-dirs", nargs="+", help="步骤1的源目录列表")
parser.add_argument("--dest-root", required=True, help="目标根目录")
parser.add_argument("--all-steps", action="store_true", help="执行所有处理步骤")
parser.add_argument("--step1", action="store_true", help="执行步骤1:文件整理")
parser.add_argument("--step2", action="store_true", help="执行步骤2:掩码值转换")
parser.add_argument("--step3", action="store_true", help="执行步骤3:JSON更新")
parser.add_argument("--step4", action="store_true", help="执行步骤4:灾害标签生成")
parser.add_argument("--step5", action="store_true", help="执行步骤5:伪彩色转换")
args = parser.parse_args()
if args.all_steps:
args.step1 = args.step2 = args.step3 = args.step4 = args.step5 = True
# 执行步骤判断
if args.step1:
if not args.source_dirs:
raise ValueError("步骤1需要指定--source-dirs参数")
process_files(args.source_dirs, args.dest_root)
if args.step2:
convert_mask_values(
input_dir=Path(args.dest_root) / "A" / "targets",
output_dir=Path(args.dest_root) / "A" / "targets_255",
)
convert_mask_values(
input_dir=Path(args.dest_root) / "B" / "targets",
output_dir=Path(args.dest_root) / "B" / "targets_255",
)
if args.step3:
update_json(Path(args.dest_root) / "A" / "labels")
if args.step4:
generate_damage_masks(args.dest_root)
if args.step5:
convert_to_color(
Path(args.dest_root) / "labels_damage",
Path(args.dest_root) / "labels_damage_color",
)
if __name__ == "__main__":
main()
启动命令行:
#train
IMAGE1="E:/DATA/CD/xBD/train/images"
LABEL1="E:/DATA/CD/xBD/train/labels"
TRAGE1T="E:/DATA/CD/xBD/train/targets"
DEST1="E:/DATA/CD/xBD_2/CD/train"
python make_xbd_sem_cd.py --all-steps --source-dirs $IMAGE1 $LABEL1 $TRAGET1 --dest-root $DEST1
#test
IMAGE2="E:/DATA/CD/xBD/test/images"
LABEL2="E:/DATA/CD/xBD/test/labels"
TRAGET2="E:/DATA/CD/xBD/test/targets"
DEST2="E:/DATA/CD/xBD_2/CD/test"
python make_xbd_sem_cd.py --all-steps --source-dirs $IMAGE2 $LABEL2 $TRAGET2 --dest-root $DEST2
#hold
IMAGE3="E:/DATA/CD/xBD/hold/images"
LABEL3="E:/DATA/CD/xBD/hold/labels"
TRAGET3="E:/DATA/CD/xBD/hold/targets"
DEST3="E:/DATA/CD/xBD_2/CD/hold"
python make_xbd_sem_cd.py --all-steps --source-dirs $IMAGE3 $LABEL3 $TRAGET3 --dest-root $DEST3