import pandas as pd
import os
import torch as t
import numpy as np
import torchvision.transforms.functional as ff
from torch.utils.data import Dataset
from PIL import Image
import torchvision.transforms as transforms
import numpy as np
np.set_printoptions(threshold=np.inf)# threshold表示: Total number of array elements to be print(输出数组的元素数目)classLoadDataset(Dataset):def__init__(self, file_path=[], crop_size=None):"""para:
file_path(list): 数据和标签路径,列表元素第一个为图片路径,第二个为标签路径
"""# 1 正确读入图片和标签路径iflen(file_path)!=2:raise ValueError("同时需要图片和标签文件夹的路径,图片路径在前")
self.img_path = file_path[0]
self.label_path = file_path[1]# 2 从路径中取出图片和标签数据的文件名保持到两个列表当中(程序中的数据来源)
self.imgs = self.read_file(self.img_path)
self.labels = self.read_file(self.label_path)# 3 初始化数据处理函数设置
self.crop_size = crop_size
def__getitem__(self, index):
img = self.imgs[index]
label = self.labels[index]# 从文件名中读取数据(图片和标签都是png格式的图像数据)
img = Image.open(img)
label = Image.open(label)
img, label = self.center_crop(img, label, self.crop_size)
img, label = self.img_transform(img, label)# print('处理后的图片和标签大小:',img.shape, label.shape)
sample ={'img': img,'label': label}# arr_img = img.numpy()# arr_label = label.numpy()# print("arr_img:::::",arr_img)# print("arr_label::::",arr_label)# print('处理后的图片和标签大小:', img.shape, label.shape)return sample
def__len__(self):returnlen(self.imgs)defread_file(self, path):# 图片的完整路径"""从文件夹中读取数据"""
files_list = os.listdir(path)
file_path_list =[os.path.join(path, img)for img in files_list]
file_path_list.sort()return file_path_list
defcenter_crop(self, data, label, crop_size):"""裁剪输入的图片和标签大小"""
data = ff.center_crop(data, crop_size)
label = ff.center_crop(label, crop_size)return data, label
defimg_transform(self, img, label):"""对图片和标签做一些数值处理"""
label = np.array(label)# 以免不是np格式的数据
label = label/100#label = Image.fromarray(label.astype('uint8'))
transform_img = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])])
img = transform_img(img)
label = t.from_numpy(label)
label = label.long()return img, label
3.模型
在这里插入代码片
4. 评测函数
from __future__ import division
import numpy as np
import six
defcalc_semantic_segmentation_confusion(pred_labels, gt_labels):"""Collect a confusion matrix.
The number of classes :math:`n\_class` is
:math:`max(pred\_labels, gt\_labels) + 1`, which is
the maximum class id of the inputs added by one.
Args:
pred_labels (iterable of numpy.ndarray): A collection of predicted
labels. The shape of a label array
is :math:`(H, W)`. :math:`H` and :math:`W`
are height and width of the label.
gt_labels (iterable of numpy.ndarray): A collection of ground
truth labels. The shape of a ground truth label array is
:math:`(H, W)`, and its corresponding prediction label should
have the same shape.
A pixel with value :obj:`-1` will be ignored during evaluation.
Returns:
numpy.ndarray:
A confusion matrix. Its shape is :math:`(n\_class, n\_class)`.
The :math:`(i, j)` th element corresponds to the number of pixels
that are labeled as class :math:`i` by the ground truth and
class :math:`j` by the prediction.
"""
pred_labels =iter(pred_labels)# (352, 480)
gt_labels =iter(gt_labels)# (352, 480)
n_class = DATASET_Num_Class
confusion = np.zeros((n_class, n_class), dtype=np.int64)# (12, 12)for pred_label, gt_label in six.moves.zip(pred_labels, gt_labels):if pred_label.ndim !=2or gt_label.ndim !=2:raise ValueError('ndim of labels should be two.')if pred_label.shape != gt_label.shape:raise ValueError('Shape of ground truth and prediction should'' be same.')
pred_label = pred_label.flatten()# (168960, )
gt_label = gt_label.flatten()# (168960, )# Dynamically expand the confusion matrix if necessary.
lb_max = np.max((pred_label, gt_label))# print(lb_max)if lb_max >= n_class:
expanded_confusion = np.zeros((lb_max +1, lb_max +1), dtype=np.int64)
expanded_confusion[0:n_class,0:n_class]= confusion
n_class = lb_max +1
confusion = expanded_confusion
# Count statistics from valid pixels. 极度巧妙 × class_nums 正好使得每个ij能够对应.
mask = gt_label >=0
confusion += np.bincount(
n_class * gt_label[mask].astype(int)+ pred_label[mask],
minlength=n_class **2)\
.reshape((n_class, n_class))for iter_ in(pred_labels, gt_labels):# This code assumes any iterator does not contain None as its items.ifnext(iter_,None)isnotNone:raise ValueError('Length of input iterables need to be same')return confusion
defcalc_semantic_segmentation_iou(confusion):"""Calculate Intersection over Union with a given confusion matrix.
The definition of Intersection over Union (IoU) is as follows,
where :math:`N_{ij}` is the number of pixels
that are labeled as class :math:`i` by the ground truth and
class :math:`j` by the prediction.
* :math:`\\text{IoU of the i-th class} = \
\\frac{N_{ii}}{\\sum_{j=1}^k N_{ij} + \\sum_{j=1}^k N_{ji} - N_{ii}}`
Args:
confusion (numpy.ndarray): A confusion matrix. Its shape is
:math:`(n\_class, n\_class)`.
The :math:`(i, j)` th element corresponds to the number of pixels
that are labeled as class :math:`i` by the ground truth and
class :math:`j` by the prediction.
Returns:
numpy.ndarray:
An array of IoUs for the :math:`n\_class` classes. Its shape is
:math:`(n\_class,)`.
"""
iou_denominator =(confusion.sum(axis=1)+ confusion.sum(axis=0)- np.diag(confusion))
iou = np.diag(confusion)/ iou_denominator
return iou
# return ioudefFrequency_Weighted_Intersection_over_Union(confusion_matrix):
freq = np.sum(confusion_matrix, axis=1)/ np.sum(confusion_matrix)
iu = np.diag(confusion_matrix)/(
np.sum(confusion_matrix, axis=1)+ np.sum(confusion_matrix, axis=0)-
np.diag(confusion_matrix))
FWIoU =(freq[freq >0]* iu[freq >0]).sum()return FWIoU
defeval_semantic_segmentation(pred_labels, gt_labels):"""Evaluate metrics used in Semantic Segmentation.
This function calculates Intersection over Union (IoU), Pixel Accuracy
and Class Accuracy for the task of semantic segmentation.
The definition of metrics calculated by this function is as follows,
where :math:`N_{ij}` is the number of pixels
that are labeled as class :math:`i` by the ground truth and
class :math:`j` by the prediction.
* :math:`\\text{IoU of the i-th class} = \
\\frac{N_{ii}}{\\sum_{j=1}^k N_{ij} + \\sum_{j=1}^k N_{ji} - N_{ii}}`
* :math:`\\text{mIoU} = \\frac{1}{k} \
\\sum_{i=1}^k \
\\frac{N_{ii}}{\\sum_{j=1}^k N_{ij} + \\sum_{j=1}^k N_{ji} - N_{ii}}`
* :math:`\\text{Pixel Accuracy} = \
\\frac \
{\\sum_{i=1}^k N_{ii}} \
{\\sum_{i=1}^k \\sum_{j=1}^k N_{ij}}`
* :math:`\\text{Class Accuracy} = \
\\frac{N_{ii}}{\\sum_{j=1}^k N_{ij}}`
* :math:`\\text{Mean Class Accuracy} = \\frac{1}{k} \
\\sum_{i=1}^k \
\\frac{N_{ii}}{\\sum_{j=1}^k N_{ij}}`
The more detailed description of the above metrics can be found in a
review on semantic segmentation [#]_.
The number of classes :math:`n\_class` is
:math:`max(pred\_labels, gt\_labels) + 1`, which is
the maximum class id of the inputs added by one.
.. [#] Alberto Garcia-Garcia, Sergio Orts-Escolano, Sergiu Oprea, \
Victor Villena-Martinez, Jose Garcia-Rodriguez. \
`A Review on Deep Learning Techniques Applied to Semantic Segmentation \
<https://arxiv.org/abs/1704.06857>`_. arXiv 2017.
Args:
pred_labels (iterable of numpy.ndarray): A collection of predicted
labels. The shape of a label array
is :math:`(H, W)`. :math:`H` and :math:`W`
are height and width of the label.
For example, this is a list of labels
:obj:`[label_0, label_1, ...]`, where
:obj:`label_i.shape = (H_i, W_i)`.
gt_labels (iterable of numpy.ndarray): A collection of ground
truth labels. The shape of a ground truth label array is
:math:`(H, W)`, and its corresponding prediction label should
have the same shape.
A pixel with value :obj:`-1` will be ignored during evaluation.
Returns:
dict:
The keys, value-types and the description of the values are listed
below.
* **iou** (*numpy.ndarray*): An array of IoUs for the \
:math:`n\_class` classes. Its shape is :math:`(n\_class,)`.
* **miou** (*float*): The average of IoUs over classes.
* **pixel_accuracy** (*float*): The computed pixel accuracy.
* **class_accuracy** (*numpy.ndarray*): An array of class accuracies \
for the :math:`n\_class` classes. \
Its shape is :math:`(n\_class,)`.
* **mean_class_accuracy** (*float*): The average of class accuracies.
# Evaluation code is based on
# https://github.com/shelhamer/fcn.berkeleyvision.org/blob/master/
# score.py#L37
"""
confusion = calc_semantic_segmentation_confusion(
pred_labels, gt_labels)
iou = calc_semantic_segmentation_iou(confusion)# (8, )
pixel_accuracy = np.diag(confusion).sum()/ confusion.sum()
class_accuracy = np.diag(confusion)/(np.sum(confusion, axis=1)+1e-10)# (12, )
fwiou = Frequency_Weighted_Intersection_over_Union(confusion)return{'iou': iou,'miou': np.nanmean(iou),'pixel_accuracy': pixel_accuracy,'class_accuracy': class_accuracy,'mean_class_accuracy': np.nanmean(class_accuracy),'fwiou':fwiou
}# 'mean_class_accuracy': np.nanmean(class_accuracy)}
5.训练
import torch as t
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from datetime import datetime
# 指定设备
device = t.device('cuda')if t.cuda.is_available()else t.device('cpu')# 分类数
num_class = DATASET_Num_Class
# ****************************************step1 数据处理**********************************************#
Load_train = LoadDataset([TRAIN_ROOT, TRAIN_LABEL], crop_size)
Load_val = LoadDataset([VAL_ROOT, VAL_LABEL], crop_size)
train_data = DataLoader(Load_train, batch_size=BATCH_SIZE, shuffle=True, num_workers=1)
val_data = DataLoader(Load_val, batch_size=BATCH_SIZE, shuffle=True, num_workers=1)# *****************************************step2 模型*********************************************#
net = FCN(8)
net = net.to(device)# depv3p = DeepLabv3_plus(nInputChannels=3, n_classes=12, os=16, _print=True)# depv3p = depv3p.to(device)# ******************************************step3 损失函数********************************************#
criterion = nn.NLLLoss().to(device)# ******************************************step4 优化器********************************************#
optimizer = optim.Adam(net.parameters(), lr=1e-4)# ******************************************step5 训练********************************************#deftrain(model):
best =[0]
net = model.train()# 训练轮次for epoch inrange(EPOCH_NUMBER):print('Epoch is [{}/{}]'.format(epoch +1, EPOCH_NUMBER))# 每50次epoch,lr学习率降一半if epoch %50==0and epoch !=0:for group in optimizer.param_groups:
group['lr']*=0.5# 指标初始化
train_loss =0
train_acc =0
train_miou =0
train_class_acc =0
train_fwiou =0# 训练批次for i, sample inenumerate(train_data):# 载入数据
img_data = Variable(sample['img'].to(device))
img_label = Variable(sample['label'].to(device))# 训练
out = net(img_data)
out = F.log_softmax(out, dim=1)
loss = criterion(out, img_label)
optimizer.zero_grad()# *** # 需要梯度清零,再反向传播
loss.backward()
optimizer.step()
train_loss += loss.item()# 评估
pre_label = out.max(dim=1)[1].data.cpu().numpy()# [1] 返回索引
pre_label =[i for i in pre_label]
true_label = img_label.data.cpu().numpy()
true_label =[i for i in true_label]
eval_metrix = eval_semantic_segmentation(pre_label, true_label)
train_acc += eval_metrix['mean_class_accuracy']
train_miou += eval_metrix['miou']#train_class_acc += eval_metrix['class_accuracy']
train_fwiou += eval_metrix["fwiou"]print('|batch[{}/{}]|batch_loss {: .8f}|'.format(i +1,len(train_data), loss.item()))
metric_description ='|Train Acc|: {:.5f}|Train Mean IU|: {:.5f}\n|Train_class_acc|:{:}|FwIou|: {:.5f}|'.format(
train_acc /len(train_data),
train_miou /len(train_data),
train_class_acc /len(train_data),
train_fwiou /len(train_data),)print(metric_description)ifmax(best)<= train_miou /len(train_data):
best.append(train_miou /len(train_data))
t.save(net.state_dict(),'./Results/weights/{}.pth'.format(epoch))# ******************************************step6 评价********************************************#defevaluate(model):
net = model.eval()# 不进行反向传播
eval_loss =0
eval_acc =0
eval_miou =0
eval_class_acc =0
prec_time = datetime.now()for j, sample inenumerate(val_data):
valImg = Variable(sample['img'].to(device))
valLabel = Variable(sample['label'].long().to(device))
out = net(valImg)
out = F.log_softmax(out, dim=1)
loss = criterion(out, valLabel)
eval_loss = loss.item()+ eval_loss
pre_label = out.max(dim=1)[1].data.cpu().numpy()
pre_label =[i for i in pre_label]
true_label = valLabel.data.cpu().numpy()
true_label =[i for i in true_label]
eval_metrics = eval_semantic_segmentation(pre_label, true_label)
eval_acc = eval_metrics['mean_class_accuracy']+ eval_acc
eval_miou = eval_metrics['miou']+ eval_miou
cur_time = datetime.now()
h, remainder =divmod((cur_time - prec_time).seconds,3600)
m, s =divmod(remainder,60)
time_str ='Time: {:.0f}:{:.0f}:{:.0f}'.format(h, m, s)
val_str =('|Valid Loss|: {:.5f} \n|Valid Acc|: {:.5f} \n|Valid Mean IU|: {:.5f} \n|Valid Class Acc|:{:}'.format(
eval_loss /len(train_data),
eval_acc /len(val_data),
eval_miou /len(val_data),
eval_class_acc /len(val_data)))print(val_str)print(time_str)if __name__ =="__main__":
train(net)