十一届软件杯遥感解译赛道——预赛第四名方案分享
本Notebook为基于软件杯官方Baseline更改的项目,结合个人理解,进行了一些优化和更改,分数会比Baseline要高一点,并且将可以继续优化的方向都写在项目里了。
总的来说,个人认为这个比赛关键在与如何解决过拟合和用于训练的数据集和测评的数据集相差过大的问题。而更改 络结构这些事情,并不是我们需要关心的。
本人之前并没有接触过遥感相关的任务,也不是相关专业,有很多地方也不太懂,所以也是通过这个比赛开始学习相关的内容,也欢迎大家和我进行交流讨论。
这里也感谢林大佬公布官方的Baseline和在群里的答疑。
看了一下大致预赛过榜0.85+到0.86应该是没有问题的,所以这个Baseline大致的分数也是如此, 另外说明,下面提及有关分数的地方,会根实际结果有一定的出入
任务说明
变化检测部分要求参赛者利用提供的训练数据,实现对多时相图像中的建筑变化检测。具体而言,多时相遥感图像建筑物变化检测任务是给定两张不同时间拍摄的相同位置(地理配准)的遥感图像,要求定位出其中建筑变化的区域。
Loss Loss比例 使用 络 F1分数
BCE 1.0 BIT+Resnet18 0.845
BCE+Dice 0.8:0.2 BIT+Resnet18 0.848
BCE+Dice 0.5:0.5 BIT+Resnet18 0.842
OhemBCE+Dice 0.8:0.2 BIT+Resnet18 0.840
有关BIT backbone的选择,个人觉得HRnet是一个优选,有关HRnet的介绍或者说为什么在这个任务上优于其他 络,可以参考下面的博客: HRnet介绍
具体怎么改HRnet,后面如果有空的话,会提PR给RS
class WindowGenerator:
def init(self, h, w, ch, cw, si=1, sj=1):
self.h = h
self.w = w
self.ch = ch
self.cw = cw
if self.h raise NotImplementedError
self.si = si
self.sj = sj
self._i, self._j = 0, 0
def crop_patches(dataloader, ori_size, window_size, stride):
“””
将中的数据裁块。
def recons_prob_map(patches, ori_size, window_size, stride):
“”“从裁块结果重建原始尺寸影像,与相对应”“”
# NOTE: 目前只能处理batch size为1的情况
h, w = ori_size
win_gen = WindowGenerator(h, w, window_size, window_size, stride, stride)
prob_map = np.zeros((h,w), dtype=np.float)
cnt = np.zeros((h,w), dtype=np.float)
# XXX: 需要保证win_gen与patches具有相同长度。此处未做检查
for (rows, cols), patch in zip(win_gen, patches):
prob_map[rows, cols] += patch
cnt[rows, cols] += 1
prob_map /= cnt
return prob_map
定义一些辅助函数
def info(msg, **kwargs):
print(msg, **kwargs)
def warn(msg, **kwargs):
print(‘ 33[0;31m’+msg, **kwargs)
def quantize(arr):
return (arr*255).astype(‘uint8’)
In [5]
重新载入一遍模型
%cd PaddleRS
import paddlers as pdrs
%cd …
调用PaddleRS API一键构建模型
model = pdrs.tasks.BIT(
# 模型输出类别数
num_classes=2,
# 是否使用混合损失函数,默认使用交叉熵损失函数训练
use_mixed_loss=False,
# 模型输入通道数
in_channels=3,
# 模型使用的骨干 络,支持’resnet18’或’resnet34’
backbone=‘resnet18’,
# 骨干 络中的resnet stage数量
n_stages=4,
# 是否使用tokenizer获取语义token
use_tokenizer=True,
# token的长度
token_len=4,
# 若不使用tokenizer,则使用池化方式获取token。此参数设置池化模式,有’max’和’avg’两种选项,分别对应最大池化与平均池化
pool_mode=‘max’,
# 池化操作输出特征图的宽和高(池化方式得到的token的长度为pool_size的平方)
pool_size=2,
# 是否在Transformer编码器中加入位置编码(positional embedding)
enc_with_pos=True,
# Transformer编码器使用的注意力模块(attention block)个数
enc_depth=1,
# Transformer编码器中每个注意力头的嵌入维度(embedding dimension)
enc_head_dim=64,
# Transformer解码器使用的注意力模块个数
dec_depth=8,
# Transformer解码器中每个注意力头的嵌入维度
dec_head_dim=8
)
/home/aistudio/PaddleRS
/home/aistudio
In [ ]
import os.path as osp
import os
from paddlers import transforms as T
from PIL import Image
from skimage.io import imread, imsave
from tqdm import tqdm
from matplotlib import pyplot as plt
from copy import deepcopy
实验路径。实验目录下保存输出的模型权重和结果
EXP_DIR = ‘/home/aistudio/exp/’
裁块大小
CROP_SIZE = 256
模型推理阶段使用的滑窗步长
STRIDE = 64
影像原始大小
ORIGINAL_SIZE = (1024, 1024)
若输出目录不存在,则新建之(递归创建目录)
out_dir = osp.join(EXP_DIR, ‘out’)
if not osp.exists(out_dir):
os.makedirs(out_dir)
选择需要的权重
BEST_CKP_PATH = ‘./exp/best_model/model.pdparams’
#测试集的路径
DATA_DIR = ‘./datasets/test’
为模型加载历史最佳权重
state_dict = paddle.load(BEST_CKP_PATH)
同样通过net属性访问组 对象
model.net.set_state_dict(state_dict)
实例化测试集
test_dataset = InferDataset(
DATA_DIR,
# 注意,测试阶段使用的归一化方式需与训练时相同
# T.FloatTrans()
T.Compose([
T.Normalize()
])
)
创建DataLoader
test_dataloader = paddle.io.DataLoader(
test_dataset,
batch_size=1,
shuffle=False,
num_workers=0,
drop_last=False,
return_list=True
)
test_dataloader = crop_patches(
test_dataloader,
ORIGINAL_SIZE,
CROP_SIZE,
STRIDE
)
In [ ]
from functools import partial
import cv2 as cv
import numpy as np
from skimage import morphology
每次推理载入的Batch_size
INFER_BATCH_SIZE = 32
Minsize 定义最小区域阈值
MIN_SIZE = 400
MinArea 定义最小内面积
MIN_AREA = 50000
推理过程主循环
info(“模型推理开始”)
model.net.eval()
len_test = len(test_dataset.names)
with paddle.no_grad():
for name, (t1, t2) in tqdm(zip(test_dataset.names, test_dataloader), total=len_test):
shape = paddle.shape(t1)
pred = paddle.zeros(shape=(shape[0],2,*shape[2:]))
for i in range(0, shape[0], INFER_BATCH_SIZE):
pred[i:i+INFER_BATCH_SIZE] = model.net(t1[i:i+INFER_BATCH_SIZE], t2[i:i+INFER_BATCH_SIZE])[0]
# 取softmax结果的第1(从0开始计数)个通道的输出作为变化概率
prob = paddle.nn.functional.softmax(pred, axis=1)[:,1]
# 由patch重建完整概率图
prob = recons_prob_map(prob.numpy(), ORIGINAL_SIZE, CROP_SIZE, STRIDE)
# 默认将阈值设置为0.5,即,将变化概率大于0.5的像素点分为变化类
out = quantize(prob>0.4)
ret, thresh = cv.threshold(out, 127, 255, cv.THRESH_BINARY)
thresh = thresh>1
stage = morphology.remove_small_objects(thresh, min_size=MIN_SIZE, connectivity=2)
stage2 = morphology.remove_small_holes(stage, area_threshold=MIN_AREA, connectivity=1)
stage2 = np.array(stage2, dtype=‘uint8’)
stage2[stage2True]=255
stage2[stage2False]=0
imsave(osp.join(out_dir, name), stage2, check_contrast=False)
info(“模型推理完成”)
生成比赛文件,并保存权重文件
保存最好的权重文件,还是比较必要的,要不然,下次有可能就没有这么好的结果了。
下载在目录下的submission文件,就可以提交啦
In [ ]
压缩提交文件
!zip -j submission.zip /home/aistudio/exp/out/* > /dev/null
In [ ]
保存最好结果
!cp -r exp/best_model save_model_1
模型融合
上面在数据集划分上也提到了集成,其实集成不同模型的结果对于变化检测来说,是比较有用的,可以有效的去除伪变化,和挖掘出一些没被检测到的建筑物。
所以说在我原先的数据集划分的基础上,进行3次左右的交叉验证,然后集成,分数会提不少哦~
整体思路也很简单,就是把不同预测结果的图片在维度上拼接,然后在维度层面求众数就好了
In [ ]
import cv2 as cv
import os
import numpy as np
result1_root = ‘results_1’
result2_root = ‘results_2’
result3_root = ‘results_3’
final_root = ‘final_output’
file_list = os.listdir(result1_root)
for image_name in file_list:
image_1_path = os.path.join(result1_root, image_name)
image_2_path = os.path.join(result2_root, image_name)
image_3_path = os.path.join(result3_root, image_name)
#
image_1 = cv.imread(image_1_path, cv.IMREAD_GRAYSCALE)
image_2 = cv.imread(image_2_path, cv.IMREAD_GRAYSCALE)
image_3 = cv.imread(image_3_path, cv.IMREAD_GRAYSCALE)
if image_
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!