一键构建xBD建筑灾害语义级变化检测/建筑分割提取可执行数据集(可直接用于模型训练/验证)

生成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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卖报的大地主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值