未验证 提交 5df30f99 编写于 作者: L LutaoChu 提交者: GitHub

Optimize RemoteSensing doc

* modify label tool

* fix label tools bug

* add RemoteSensing

* optimize data clip and normalize method for remote sensing

* fix label problem using mobile devices

* add README.md

* polish README.md, optimize main.py and transforms.py, add transforms.md

* add demo dataset

* add create_dataset_list tool, requirements.txt

* add channel-by-channel normalize and clip, remove RemoteSensing import

* update vdl to 2.0

* polish README

* add tool to randomly split dataset and generate file list

* open eval_best_metric while traing, fix typo

* remove python2.7 ci
上级 5d1e84ed
language: python
python:
- '2.7'
- '3.5'
- '3.6'
......
......@@ -602,7 +602,7 @@ class SegModel(object):
return_details (bool): 是否返回详细信息。默认False。
Returns:
dict: 当return_details为False时,返回dict。包含关键字:'miou'、'categore_iou'、'macc'、
dict: 当return_details为False时,返回dict。包含关键字:'miou'、'category_iou'、'macc'、
'category_acc'和'kappa',分别表示平均iou、各类别iou、平均准确率、各类别准确率和kappa系数。
tuple (metrics, eval_details):当return_details为True时,增加返回dict (eval_details),
包含关键字:'confusion_matrix',表示评估的混淆矩阵。
......
# 遥感分割(RemoteSensing)
遥感影像分割是图像分割领域中的重要应用场景,广泛应用于土地测绘、环境监测、城市建设等领域。遥感影像分割的目标多种多样,有诸如积雪、农作物、道路、建筑、水源等地物目标,也有例如云层的空中目标。
PaddleSeg提供了针对遥感专题的语义分割库RemoteSensing,涵盖图像预处理、数据增强、模型训练、预测流程,帮助大家利用深度学习技术解决遥感影像分割问题。
PaddleSeg提供了针对遥感专题的语义分割库RemoteSensing,涵盖图像预处理、数据增强、模型训练、预测流程,帮助用户利用深度学习技术解决遥感影像分割问题。
## 特点
针对遥感数据多通道、分布范围大、分布不均的特点,我们支持多通道训练预测,内置一系列多通道预处理和数据增强的策略,可结合实际业务场景进行定制组合,提升模型泛化能力和鲁棒性。
**Note:** 所有命令需要在`PaddleSeg/contrib/RemoteSensing/`目录下执行。
......@@ -62,8 +63,8 @@ RemoteSensing # 根目录
```
其中,相应的文件名可根据需要自行定义。
由于遥感领域图像格式多种多样,不同传感器产生的数据格式可能不同。本分割库目前采用npy格式作为遥感数据的格式,采用png无损压缩格式作为标注图片格式。
遥感领域图像格式多种多样,不同传感器产生的数据格式可能不同。为方便数据加载,本分割库统一采用numpy存储格式`npy`作为原图格式,采用`png`无损压缩格式作为标注图片格式。
原图的前两维是图像的尺寸,第3维是图像的通道数。
标注图像为单通道图像,像素值即为对应的类别,像素标注类别需要从0开始递增,
例如0,1,2,3表示有4种类别,标注类别最多为256类。其中可以指定特定的像素值用于表示该值的像素不参与训练和评估(默认为255)。
......@@ -173,6 +174,7 @@ model.train(
pretrain_weights=None,
optimizer=None,
learning_rate=lr,
use_vdl=True
)
```
......@@ -233,7 +235,7 @@ export CUDA_VISIBLE_DEVICES=0
```
> 在RemoteSensing目录下运行`predict_demo.py`即可开始训练。
```shell script
python predict_demo.py --data_dir dataset/demo/ --load_model_dir saved_model/unet/
python predict_demo.py --data_dir dataset/demo/ --load_model_dir saved_model/unet/best_model/
```
......
......@@ -213,6 +213,7 @@ class BaseAPI:
train_reader,
train_batch_size,
eval_reader=None,
eval_best_metric=None,
save_interval_epochs=1,
log_interval_steps=10,
save_dir='output',
......@@ -261,11 +262,8 @@ class BaseAPI:
if use_vdl:
# VisualDL component
log_writer = LogWriter(vdl_logdir, sync_cycle=20)
train_step_component = OrderedDict()
eval_component = OrderedDict()
log_writer = LogWriter(vdl_logdir)
best_accuracy_key = ""
best_accuracy = -1.0
best_model_epoch = 1
for i in range(num_epochs):
......@@ -302,14 +300,10 @@ class BaseAPI:
if use_vdl:
for k, v in step_metrics.items():
if k not in train_step_component.keys():
with log_writer.mode('Each_Step_while_Training'
) as step_logger:
train_step_component[
k] = step_logger.scalar(
'Training: {}'.format(k))
train_step_component[k].add_record(num_steps, v)
log_writer.add_scalar(
tag="Training: {}".format(k),
value=v,
step=num_steps)
logging.info(
"[TRAIN] Epoch={}/{}, Step={}/{}, {}, eta={}".format(
i + 1, num_epochs, step + 1, total_num_steps,
......@@ -336,10 +330,9 @@ class BaseAPI:
logging.info('[EVAL] Finished, Epoch={}, {} .'.format(
i + 1, dict2str(self.eval_metrics)))
# 保存最优模型
best_accuracy_key = list(self.eval_metrics.keys())[0]
current_accuracy = self.eval_metrics[best_accuracy_key]
if current_accuracy > best_accuracy:
best_accuracy = current_accuracy
current_metric = self.eval_metrics[eval_best_metric]
if current_metric > best_accuracy:
best_accuracy = current_metric
best_model_epoch = i + 1
best_model_dir = osp.join(save_dir, "best_model")
self.save_model(save_dir=best_model_dir)
......@@ -350,13 +343,11 @@ class BaseAPI:
if isinstance(v, np.ndarray):
if v.size > 1:
continue
if k not in eval_component:
with log_writer.mode('Each_Epoch_on_Eval_Data'
) as eval_logger:
eval_component[k] = eval_logger.scalar(
'Evaluation: {}'.format(k))
eval_component[k].add_record(i + 1, v)
log_writer.add_scalar(
tag="Evaluation: {}".format(k),
step=i + 1,
value=v)
self.save_model(save_dir=current_save_dir)
logging.info(
'Current evaluated best model in eval_reader is epoch_{}, {}={}'
.format(best_model_epoch, best_accuracy_key, best_accuracy))
.format(best_model_epoch, eval_best_metric, best_accuracy))
......@@ -138,6 +138,7 @@ class UNet(BaseAPI):
train_reader,
train_batch_size=2,
eval_reader=None,
eval_best_metric='kappa',
save_interval_epochs=1,
log_interval_steps=2,
save_dir='output',
......@@ -154,7 +155,8 @@ class UNet(BaseAPI):
num_epochs (int): 训练迭代轮数。
train_reader (readers): 训练数据读取器。
train_batch_size (int): 训练数据batch大小。同时作为验证数据batch大小。默认2。
eval_reader (readers): 评估数据读取器。
eval_reader (readers): 边训边评估的评估数据读取器。
eval_best_metric (str): 边训边评估保存最好模型的指标。默认为'kappa'。
save_interval_epochs (int): 模型保存间隔(单位:迭代轮数)。默认为1。
log_interval_steps (int): 训练日志输出间隔(单位:迭代次数)。默认为2。
save_dir (str): 模型保存路径。默认'output'。
......@@ -202,6 +204,7 @@ class UNet(BaseAPI):
train_reader=train_reader,
train_batch_size=train_batch_size,
eval_reader=eval_reader,
eval_best_metric=eval_best_metric,
save_interval_epochs=save_interval_epochs,
log_interval_steps=log_interval_steps,
save_dir=save_dir,
......@@ -223,7 +226,7 @@ class UNet(BaseAPI):
return_details (bool): 是否返回详细信息。默认False。
Returns:
dict: 当return_details为False时,返回dict。包含关键字:'miou'、'categore_iou'、'macc'、
dict: 当return_details为False时,返回dict。包含关键字:'miou'、'category_iou'、'macc'、
'category_acc'和'kappa',分别表示平均iou、各类别iou、平均准确率、各类别准确率和kappa系数。
tuple (metrics, eval_details):当return_details为True时,增加返回dict (eval_details),
包含关键字:'confusion_matrix',表示评估的混淆矩阵。
......@@ -271,7 +274,7 @@ class UNet(BaseAPI):
category_acc, macc = conf_mat.accuracy()
metrics = OrderedDict(
zip(['miou', 'categore_iou', 'macc', 'category_acc', 'kappa'],
zip(['miou', 'category_iou', 'macc', 'category_acc', 'kappa'],
[miou, category_iou, macc, category_acc,
conf_mat.kappa()]))
if return_details:
......
......@@ -29,8 +29,8 @@ data_dir = args.data_dir
load_model_dir = args.load_model_dir
# predict
model = load_model(osp.join(load_model_dir, 'best_model'))
pred_dir = osp.join(load_model_dir, 'pred')
model = load_model(load_model_dir)
pred_dir = osp.join(load_model_dir, 'predict')
if not osp.exists(pred_dir):
os.mkdir(pred_dir)
......
pre-commit
yapf == 0.26.0
flake8
pyyaml >= 5.1
Pillow
numpy
six
opencv-python
tqdm
\ No newline at end of file
visualdl >= 2.0.0-alpha.2
......@@ -21,7 +21,9 @@ import warnings
def parse_args():
parser = argparse.ArgumentParser(
description='PaddleSeg generate file list on your customized dataset.')
description=
'A tool for dividing dataset and generating file lists by file directory structure.'
)
parser.add_argument('dataset_root', help='dataset root directory', type=str)
parser.add_argument(
'--separator',
......
# coding: utf8
# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import glob
import os.path
import argparse
import warnings
import numpy as np
def parse_args():
parser = argparse.ArgumentParser(
description=
'A tool for proportionally randomizing dataset to produce file lists.')
parser.add_argument('dataset_root', help='the dataset root path', type=str)
parser.add_argument('images', help='the directory name of images', type=str)
parser.add_argument('labels', help='the directory name of labels', type=str)
parser.add_argument(
'--split', help='', nargs=3, type=float, default=[0.7, 0.3, 0])
parser.add_argument(
'--label_class',
help='label class names',
type=str,
nargs='*',
default=['__background__', '__foreground__'])
parser.add_argument(
'--separator',
dest='separator',
help='file list separator',
default=" ",
type=str)
parser.add_argument(
'--format',
help='data format of images and labels, e.g. jpg, npy or png.',
type=str,
nargs=2,
default=['npy', 'png'])
parser.add_argument(
'--postfix',
help='postfix of images or labels',
type=str,
nargs=2,
default=['', ''])
return parser.parse_args()
def get_files(path, format, postfix):
pattern = '*%s.%s' % (postfix, format)
search_files = os.path.join(path, pattern)
search_files2 = os.path.join(path, "*", pattern) # 包含子目录
search_files3 = os.path.join(path, "*", "*", pattern) # 包含三级目录
filenames = glob.glob(search_files)
filenames2 = glob.glob(search_files2)
filenames3 = glob.glob(search_files3)
filenames = filenames + filenames2 + filenames3
return sorted(filenames)
def generate_list(args):
separator = args.separator
dataset_root = args.dataset_root
if sum(args.split) != 1.0:
raise ValueError("划分比例之和必须为1")
file_list = os.path.join(dataset_root, 'labels.txt')
with open(file_list, "w") as f:
for label_class in args.label_class:
f.write(label_class + '\n')
image_dir = os.path.join(dataset_root, args.images)
label_dir = os.path.join(dataset_root, args.labels)
image_files = get_files(image_dir, args.format[0], args.postfix[0])
label_files = get_files(label_dir, args.format[1], args.postfix[1])
if not image_files:
warnings.warn("No files in {}".format(image_dir))
num_images = len(image_files)
if not label_files:
warnings.warn("No files in {}".format(label_dir))
num_label = len(label_files)
if num_images != num_label and num_label > 0:
raise Exception("Number of images = {} number of labels = {} \n"
"Either number of images is equal to number of labels, "
"or number of labels is equal to 0.\n"
"Please check your dataset!".format(
num_images, num_label))
image_files = np.array(image_files)
label_files = np.array(label_files)
state = np.random.get_state()
np.random.shuffle(image_files)
np.random.set_state(state)
np.random.shuffle(label_files)
start = 0
num_split = len(args.split)
dataset_name = ['train', 'val', 'test']
for i in range(num_split):
dataset_split = dataset_name[i]
print("Creating {}.txt...".format(dataset_split))
if args.split[i] > 1.0 or args.split[i] < 0:
raise ValueError(
"{} dataset percentage should be 0~1.".format(dataset_split))
file_list = os.path.join(dataset_root, dataset_split + '.txt')
with open(file_list, "w") as f:
num = round(args.split[i] * num_images)
end = start + num
if i == num_split - 1:
end = num_images
for item in range(start, end):
left = image_files[item].replace(dataset_root, '')
if left[0] == os.path.sep:
left = left.lstrip(os.path.sep)
try:
right = label_files[item].replace(dataset_root, '')
if right[0] == os.path.sep:
right = right.lstrip(os.path.sep)
line = left + separator + right + '\n'
except:
line = left + '\n'
f.write(line)
print(line)
start = end
if __name__ == '__main__':
args = parse_args()
generate_list(args)
......@@ -103,4 +103,4 @@ model.train(
pretrain_weights=None,
optimizer=None,
learning_rate=lr,
)
use_vdl=True)
......@@ -17,10 +17,8 @@ from gray2pseudo_color import get_color_map_list
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('input_dir',
help='input annotated directory')
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('input_dir', help='input annotated directory')
return parser.parse_args()
......@@ -62,8 +60,7 @@ def main(args):
print('Generating dataset from:', label_file)
with open(label_file) as f:
base = osp.splitext(osp.basename(label_file))[0]
out_png_file = osp.join(
output_dir, base + '.png')
out_png_file = osp.join(output_dir, base + '.png')
data = json.load(f)
......@@ -77,14 +74,20 @@ def main(args):
# convert jingling format to labelme format
points = []
for i in range(1, int(len(polygon) / 2) + 1):
points.append([polygon['x' + str(i)], polygon['y' + str(i)]])
shape = {'label': name, 'points': points, 'shape_type': 'polygon'}
points.append(
[polygon['x' + str(i)], polygon['y' + str(i)]])
shape = {
'label': name,
'points': points,
'shape_type': 'polygon'
}
data_shapes.append(shape)
if 'size' not in data:
continue
data_size = data['size']
img_shape = (data_size['height'], data_size['width'], data_size['depth'])
img_shape = (data_size['height'], data_size['width'],
data_size['depth'])
lbl = labelme.utils.shapes_to_label(
img_shape=img_shape,
......@@ -102,8 +105,7 @@ def main(args):
else:
raise ValueError(
'[%s] Cannot save the pixel-wise class label as PNG. '
'Please consider using the .npy format.' % out_png_file
)
'Please consider using the .npy format.' % out_png_file)
if __name__ == '__main__':
......
......@@ -17,10 +17,8 @@ from gray2pseudo_color import get_color_map_list
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('input_dir',
help='input annotated directory')
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('input_dir', help='input annotated directory')
return parser.parse_args()
......@@ -61,8 +59,7 @@ def main(args):
print('Generating dataset from:', label_file)
with open(label_file) as f:
base = osp.splitext(osp.basename(label_file))[0]
out_png_file = osp.join(
output_dir, base + '.png')
out_png_file = osp.join(output_dir, base + '.png')
data = json.load(f)
......@@ -85,8 +82,7 @@ def main(args):
else:
raise ValueError(
'[%s] Cannot save the pixel-wise class label as PNG. '
'Please consider using the .npy format.' % out_png_file
)
'Please consider using the .npy format.' % out_png_file)
if __name__ == '__main__':
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册