Upload 23 files
Browse files- utils/__init__.py +1 -0
- utils/__pycache__/__init__.cpython-310.pyc +0 -0
- utils/__pycache__/__init__.cpython-311.pyc +0 -0
- utils/__pycache__/__init__.cpython-38.pyc +0 -0
- utils/__pycache__/create_exp_folder.cpython-310.pyc +0 -0
- utils/__pycache__/create_exp_folder.cpython-311.pyc +0 -0
- utils/__pycache__/create_exp_folder.cpython-38.pyc +0 -0
- utils/__pycache__/dataloader.cpython-310.pyc +0 -0
- utils/__pycache__/dataloader.cpython-311.pyc +0 -0
- utils/__pycache__/dataloader.cpython-38.pyc +0 -0
- utils/__pycache__/plot_results.cpython-310.pyc +0 -0
- utils/__pycache__/plot_results.cpython-311.pyc +0 -0
- utils/__pycache__/plot_results.cpython-38.pyc +0 -0
- utils/__pycache__/train_and_eval.cpython-310.pyc +0 -0
- utils/__pycache__/train_and_eval.cpython-311.pyc +0 -0
- utils/__pycache__/utils.cpython-310.pyc +0 -0
- utils/__pycache__/utils.cpython-311.pyc +0 -0
- utils/__pycache__/utils.cpython-38.pyc +0 -0
- utils/create_exp_folder.py +59 -0
- utils/dataloader.py +157 -0
- utils/plot_results.py +46 -0
- utils/train_and_eval.py +312 -0
- utils/utils.py +72 -0
utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
#
|
utils/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (149 Bytes). View file
|
|
|
utils/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (150 Bytes). View file
|
|
|
utils/__pycache__/__init__.cpython-38.pyc
ADDED
|
Binary file (149 Bytes). View file
|
|
|
utils/__pycache__/create_exp_folder.cpython-310.pyc
ADDED
|
Binary file (989 Bytes). View file
|
|
|
utils/__pycache__/create_exp_folder.cpython-311.pyc
ADDED
|
Binary file (2.79 kB). View file
|
|
|
utils/__pycache__/create_exp_folder.cpython-38.pyc
ADDED
|
Binary file (971 Bytes). View file
|
|
|
utils/__pycache__/dataloader.cpython-310.pyc
ADDED
|
Binary file (3.84 kB). View file
|
|
|
utils/__pycache__/dataloader.cpython-311.pyc
ADDED
|
Binary file (8.99 kB). View file
|
|
|
utils/__pycache__/dataloader.cpython-38.pyc
ADDED
|
Binary file (3.81 kB). View file
|
|
|
utils/__pycache__/plot_results.cpython-310.pyc
ADDED
|
Binary file (1.82 kB). View file
|
|
|
utils/__pycache__/plot_results.cpython-311.pyc
ADDED
|
Binary file (3.83 kB). View file
|
|
|
utils/__pycache__/plot_results.cpython-38.pyc
ADDED
|
Binary file (1.89 kB). View file
|
|
|
utils/__pycache__/train_and_eval.cpython-310.pyc
ADDED
|
Binary file (6.73 kB). View file
|
|
|
utils/__pycache__/train_and_eval.cpython-311.pyc
ADDED
|
Binary file (16.2 kB). View file
|
|
|
utils/__pycache__/utils.cpython-310.pyc
ADDED
|
Binary file (1.61 kB). View file
|
|
|
utils/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (2.94 kB). View file
|
|
|
utils/__pycache__/utils.cpython-38.pyc
ADDED
|
Binary file (1.98 kB). View file
|
|
|
utils/create_exp_folder.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
def create_exp_folder():
|
| 4 |
+
# Step 1: 创建run文件夹(如果不存在)
|
| 5 |
+
if not os.path.exists("run"):
|
| 6 |
+
os.mkdir("run")
|
| 7 |
+
|
| 8 |
+
# Step 2: 创建train文件夹(如果不存在)
|
| 9 |
+
train_folder = os.path.join("run", "train")
|
| 10 |
+
if not os.path.exists(train_folder):
|
| 11 |
+
os.mkdir(train_folder)
|
| 12 |
+
|
| 13 |
+
# Step 3: 创建exp文件夹(检查是否存在)
|
| 14 |
+
exp_folder = os.path.join(train_folder, "exp")
|
| 15 |
+
if not os.path.exists(exp_folder):
|
| 16 |
+
os.mkdir(exp_folder)
|
| 17 |
+
os.mkdir(os.path.join(exp_folder, "weights")) # 创建weights文件夹
|
| 18 |
+
return exp_folder, os.path.join(exp_folder, "weights") # 返回exp和weights文件夹路径
|
| 19 |
+
|
| 20 |
+
# 如果exp文件夹已存在,则查找exp1, exp2, 等
|
| 21 |
+
exp_num = 1
|
| 22 |
+
while True:
|
| 23 |
+
# 动态命名exp1, exp2, ...
|
| 24 |
+
exp_folder_name = f"exp{exp_num}"
|
| 25 |
+
exp_folder = os.path.join(train_folder, exp_folder_name)
|
| 26 |
+
if not os.path.exists(exp_folder):
|
| 27 |
+
os.mkdir(exp_folder) # 创建新的exp文件夹
|
| 28 |
+
os.mkdir(os.path.join(exp_folder, "weights")) # 创建weights文件夹
|
| 29 |
+
return exp_folder, os.path.join(exp_folder, "weights") # 返回exp和weights文件夹路径
|
| 30 |
+
exp_num += 1 # 如果文件夹已存在,增加数字,继续查找下一个文件夹
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def create_val_exp_folder():
|
| 34 |
+
# Step 1: 创建run文件夹(如果不存在)
|
| 35 |
+
if not os.path.exists("run"):
|
| 36 |
+
os.mkdir("run")
|
| 37 |
+
|
| 38 |
+
# Step 2: 创建train文件夹(如果不存在)
|
| 39 |
+
train_folder = os.path.join("run", "predict")
|
| 40 |
+
if not os.path.exists(train_folder):
|
| 41 |
+
os.mkdir(train_folder)
|
| 42 |
+
|
| 43 |
+
# Step 3: 创建exp文件夹(检查是否存在)
|
| 44 |
+
exp_folder = os.path.join(train_folder, "exp")
|
| 45 |
+
if not os.path.exists(exp_folder):
|
| 46 |
+
os.mkdir(exp_folder)
|
| 47 |
+
|
| 48 |
+
# 如果exp文件夹已存在,则查找exp1, exp2, 等
|
| 49 |
+
exp_num = 1
|
| 50 |
+
while True:
|
| 51 |
+
# 动态命名exp1, exp2, ...
|
| 52 |
+
exp_folder_name = f"exp{exp_num}"
|
| 53 |
+
exp_folder = os.path.join(train_folder, exp_folder_name)
|
| 54 |
+
if not os.path.exists(exp_folder):
|
| 55 |
+
os.mkdir(exp_folder) # 创建新的exp文件夹
|
| 56 |
+
return exp_folder # 返回新创建的文件夹路径
|
| 57 |
+
exp_num += 1 # 如果文件夹已存在,增加数字,继续查找下一个文件夹
|
| 58 |
+
|
| 59 |
+
|
utils/dataloader.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from utils.utils import cvtColor, preprocess_input
|
| 3 |
+
import os
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import numpy as np
|
| 6 |
+
from torch.utils.data import Dataset, DataLoader
|
| 7 |
+
import cv2
|
| 8 |
+
|
| 9 |
+
class UnetDataset(Dataset):
|
| 10 |
+
def __init__(self, data_path, input_shape, num_classes, augmentation=True ,txt_name: str = "train.txt"):
|
| 11 |
+
# 读取train.txt和val.txt,test.txt文件,获取训练和验证集的图像ID
|
| 12 |
+
with open(os.path.join(data_path, "VOC2012/ImageSets/Segmentation", txt_name), "r") as f:
|
| 13 |
+
self.annotation_lines = f.readlines()
|
| 14 |
+
|
| 15 |
+
# 初始化其他参数
|
| 16 |
+
self.length = len(self.annotation_lines) # 数据集的长度
|
| 17 |
+
self.input_shape = input_shape # 输入图像的形状(宽和高)
|
| 18 |
+
self.num_classes = num_classes # 类别数目
|
| 19 |
+
self.augmentation = augmentation # 是否在训练阶段,用来控制是否使用数据增强
|
| 20 |
+
self.data_path = data_path # 数据集路径
|
| 21 |
+
|
| 22 |
+
def __len__(self):
|
| 23 |
+
# 返回数据集的大小
|
| 24 |
+
return self.length
|
| 25 |
+
|
| 26 |
+
def __getitem__(self, index):
|
| 27 |
+
# 读取单个样本
|
| 28 |
+
annotation_line = self.annotation_lines[index] # 获取对应的annotation
|
| 29 |
+
name = annotation_line.split()[0] # 获取文件名,通常是图像文件的名称
|
| 30 |
+
|
| 31 |
+
# 读取JPEG图像
|
| 32 |
+
jpg = Image.open(os.path.join(self.data_path, "VOC2012/JPEGImages", name + ".png"))
|
| 33 |
+
# 读取PNG标签图像
|
| 34 |
+
png = Image.open(os.path.join(self.data_path, "VOC2012/SegmentationClass", name + ".png"))
|
| 35 |
+
|
| 36 |
+
# 如果是训练阶段,进行随机数据增强
|
| 37 |
+
jpg, png = self.get_random_data(jpg, png, self.input_shape, random=self.augmentation)
|
| 38 |
+
|
| 39 |
+
# 图像预处理
|
| 40 |
+
jpg = np.transpose(preprocess_input(np.array(jpg, np.float64)), [2, 0, 1])
|
| 41 |
+
# 标签转换成numpy数组
|
| 42 |
+
png = np.array(png)
|
| 43 |
+
|
| 44 |
+
# 将标签值大于类别数的部分设置为类别数(忽略这些区域)
|
| 45 |
+
png[png >= self.num_classes] = self.num_classes
|
| 46 |
+
|
| 47 |
+
# 将标签转换为one-hot编码
|
| 48 |
+
seg_labels = np.eye(self.num_classes + 1)[png.reshape([-1])]
|
| 49 |
+
|
| 50 |
+
# 重塑标签为目标形状
|
| 51 |
+
seg_labels = seg_labels.reshape((int(self.input_shape[0]), int(self.input_shape[1]), self.num_classes + 1))
|
| 52 |
+
|
| 53 |
+
# 返回图像、标签以及one-hot编码的标签
|
| 54 |
+
return jpg, png, seg_labels
|
| 55 |
+
|
| 56 |
+
# 生成随机数的函数
|
| 57 |
+
def rand(self, a=0, b=1):
|
| 58 |
+
return np.random.rand() * (b - a) + a
|
| 59 |
+
|
| 60 |
+
# 对图像和标签进行随机数据增强的函数
|
| 61 |
+
def get_random_data(self, image, label, input_shape, jitter=.3, hue=.1, sat=0.7, val=0.3, random=True):
|
| 62 |
+
# 将图像转为RGB格式
|
| 63 |
+
image = cvtColor(image)
|
| 64 |
+
label = Image.fromarray(np.array(label))
|
| 65 |
+
|
| 66 |
+
iw, ih = image.size # 获取图像的宽和高
|
| 67 |
+
h, w = input_shape # 获取目标图像的高和宽
|
| 68 |
+
|
| 69 |
+
if not random:
|
| 70 |
+
# 如果不进行随机增强(例如在验证阶段)
|
| 71 |
+
iw, ih = image.size
|
| 72 |
+
scale = min(w / iw, h / ih) # 计算缩放比例
|
| 73 |
+
nw = int(iw * scale) # 根据比例计算缩放后的宽
|
| 74 |
+
nh = int(ih * scale) # 根据比例计算缩放后的高
|
| 75 |
+
|
| 76 |
+
# 缩放图像并进行中心裁剪
|
| 77 |
+
image = image.resize((nw, nh), Image.BICUBIC)
|
| 78 |
+
new_image = Image.new('RGB', [w, h], (128, 128, 128)) # 创建一个灰色背景的图像
|
| 79 |
+
new_image.paste(image, ((w - nw) // 2, (h - nh) // 2)) # 将缩放后的图像粘贴到目标图像中
|
| 80 |
+
|
| 81 |
+
# 缩放标签图像并进行中心裁剪
|
| 82 |
+
label = label.resize((nw, nh), Image.NEAREST) # 标签使用最近邻插值
|
| 83 |
+
new_label = Image.new('L', [w, h], (0)) # 创建一个空白标签图像
|
| 84 |
+
new_label.paste(label, ((w - nw) // 2, (h - nh) // 2)) # 将缩放后的标签图像粘贴到目标图像中
|
| 85 |
+
return new_image, new_label
|
| 86 |
+
|
| 87 |
+
# 获取一个新的宽高比(通过调整宽和高的比例)
|
| 88 |
+
new_ar = iw / ih * self.rand(1 - jitter, 1 + jitter) / self.rand(1 - jitter, 1 + jitter)
|
| 89 |
+
scale = self.rand(0.25, 2) # 随机缩放比例
|
| 90 |
+
if new_ar < 1:
|
| 91 |
+
nh = int(scale * h)
|
| 92 |
+
nw = int(nh * new_ar)
|
| 93 |
+
else:
|
| 94 |
+
nw = int(scale * w)
|
| 95 |
+
nh = int(nw / new_ar)
|
| 96 |
+
image = image.resize((nw, nh), Image.BICUBIC)
|
| 97 |
+
label = label.resize((nw, nh), Image.NEAREST)
|
| 98 |
+
|
| 99 |
+
# 随机翻转图像
|
| 100 |
+
flip = self.rand() < .5
|
| 101 |
+
if flip:
|
| 102 |
+
image = image.transpose(Image.FLIP_LEFT_RIGHT)
|
| 103 |
+
label = label.transpose(Image.FLIP_LEFT_RIGHT)
|
| 104 |
+
|
| 105 |
+
# 在图像周围随机添加灰色边框
|
| 106 |
+
dx = int(self.rand(0, w - nw))
|
| 107 |
+
dy = int(self.rand(0, h - nh))
|
| 108 |
+
new_image = Image.new('RGB', (w, h), (128, 128, 128))
|
| 109 |
+
new_label = Image.new('L', (w, h), (0))
|
| 110 |
+
new_image.paste(image, (dx, dy)) # 将图像粘贴到新图像中
|
| 111 |
+
new_label.paste(label, (dx, dy)) # 将标签粘贴到新标签中
|
| 112 |
+
image = new_image
|
| 113 |
+
label = new_label
|
| 114 |
+
|
| 115 |
+
# 转换图像为数组
|
| 116 |
+
image_data = np.array(image, np.uint8)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
r = np.random.uniform(-1, 1, 3) * [hue, sat, val] + 1 # 随机调整色调、饱和度和亮度
|
| 120 |
+
hue, sat, val = cv2.split(cv2.cvtColor(image_data, cv2.COLOR_RGB2HSV)) # 转为HSV色域
|
| 121 |
+
dtype = image_data.dtype # 获取数据类型
|
| 122 |
+
x = np.arange(0, 256, dtype=r.dtype) # 获取颜色值范围
|
| 123 |
+
lut_hue = ((x * r[0]) % 180).astype(dtype) # 应用色调变换
|
| 124 |
+
lut_sat = np.clip(x * r[1], 0, 255).astype(dtype) # 应用饱和度变换
|
| 125 |
+
lut_val = np.clip(x * r[2], 0, 255).astype(dtype) # 应用亮度变换
|
| 126 |
+
|
| 127 |
+
# 使用查找表(LUT)应用变换
|
| 128 |
+
image_data = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
|
| 129 |
+
image_data = cv2.cvtColor(image_data, cv2.COLOR_HSV2RGB) # 转换回RGB色域
|
| 130 |
+
|
| 131 |
+
return image_data, label # 返回经过增强的图像和标签
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# DataLoader中collate_fn使用
|
| 135 |
+
def unet_dataset_collate(batch):
|
| 136 |
+
# 初始化三个列表,用于存储每个批次中的图像、标签和one-hot编码标签
|
| 137 |
+
images = [] # 用来存储图像数据
|
| 138 |
+
pngs = [] # 用来存储原始标签(通常是类别标签)
|
| 139 |
+
seg_labels = [] # 用来存储one-hot编码的标签
|
| 140 |
+
|
| 141 |
+
# 遍历当前批次中的每个样本(img, png, labels)
|
| 142 |
+
for img, png, labels in batch:
|
| 143 |
+
images.append(img) # 将图像添加到images列表中
|
| 144 |
+
pngs.append(png) # 将原始标签添加到pngs列表中
|
| 145 |
+
seg_labels.append(labels) # 将one-hot标签添加到seg_labels列表中
|
| 146 |
+
|
| 147 |
+
# 将列表转换为NumPy数组,然后转换为torch张量
|
| 148 |
+
# images的张量需要是float类型,通常用于输入图像
|
| 149 |
+
images = torch.from_numpy(np.array(images)).type(torch.FloatTensor)
|
| 150 |
+
# pngs的张量需要是long类型,通常用于标签索引
|
| 151 |
+
pngs = torch.from_numpy(np.array(pngs)).long()
|
| 152 |
+
# seg_labels的张量需要是float类型,通常用于标签的one-hot编码
|
| 153 |
+
seg_labels = torch.from_numpy(np.array(seg_labels)).type(torch.FloatTensor)
|
| 154 |
+
|
| 155 |
+
# 返回三个张量,分别对应图像、原始标签和one-hot标签
|
| 156 |
+
return images, pngs, seg_labels
|
| 157 |
+
|
utils/plot_results.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import matplotlib.pyplot as plt
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
def plot_training_curves(train_losses, val_losses, val_metrics_history, weights_folder):
|
| 5 |
+
# 准备数据
|
| 6 |
+
epochs = range(1, len(train_losses) + 1)
|
| 7 |
+
pixel_acc_list = [m["Pixel Accuracy"] for m in val_metrics_history]
|
| 8 |
+
mean_acc_list = [m["Mean Accuracy"] for m in val_metrics_history]
|
| 9 |
+
mean_iou_list = [m["Mean IoU"] for m in val_metrics_history]
|
| 10 |
+
fw_iou_list = [m["Frequency Weighted IoU"] for m in val_metrics_history]
|
| 11 |
+
|
| 12 |
+
# ========================
|
| 13 |
+
# 📈 绘制 Loss 曲线
|
| 14 |
+
# ========================
|
| 15 |
+
plt.figure(figsize=(8,6))
|
| 16 |
+
plt.plot(epochs, train_losses, label="Train Loss", linewidth=2)
|
| 17 |
+
plt.plot(epochs, val_losses, label="Val Loss", linewidth=2)
|
| 18 |
+
|
| 19 |
+
plt.xlabel("Epoch", fontsize=14, fontname='Times New Roman')
|
| 20 |
+
plt.ylabel("Loss", fontsize=14, fontname='Times New Roman')
|
| 21 |
+
plt.xticks(fontsize=12, fontname='Times New Roman')
|
| 22 |
+
plt.yticks(fontsize=12, fontname='Times New Roman')
|
| 23 |
+
plt.grid(True, which='both', linestyle='--', alpha=0.5)
|
| 24 |
+
plt.legend(prop={'family':'Times New Roman', 'size':12})
|
| 25 |
+
plt.tight_layout()
|
| 26 |
+
plt.savefig(os.path.join(weights_folder, "loss_curve.png"), dpi=300)
|
| 27 |
+
plt.close()
|
| 28 |
+
|
| 29 |
+
# =========================
|
| 30 |
+
# 📈 绘制指标曲线
|
| 31 |
+
# =========================
|
| 32 |
+
plt.figure(figsize=(8,6))
|
| 33 |
+
plt.plot(epochs, pixel_acc_list, label="Pixel Accuracy", linewidth=2)
|
| 34 |
+
plt.plot(epochs, mean_acc_list, label="Mean Accuracy", linewidth=2)
|
| 35 |
+
plt.plot(epochs, mean_iou_list, label="Mean IoU", linewidth=2)
|
| 36 |
+
plt.plot(epochs, fw_iou_list, label="FWIoU", linewidth=2)
|
| 37 |
+
|
| 38 |
+
plt.xlabel("Epoch", fontsize=14, fontname='Times New Roman')
|
| 39 |
+
plt.ylabel("Score", fontsize=14, fontname='Times New Roman')
|
| 40 |
+
plt.xticks(fontsize=12, fontname='Times New Roman')
|
| 41 |
+
plt.yticks(fontsize=12, fontname='Times New Roman')
|
| 42 |
+
plt.grid(True, which='both', linestyle='--', alpha=0.5)
|
| 43 |
+
plt.legend(prop={'family':'Times New Roman', 'size':12})
|
| 44 |
+
plt.tight_layout()
|
| 45 |
+
plt.savefig(os.path.join(weights_folder, "metrics_curve.png"), dpi=300)
|
| 46 |
+
plt.close()
|
utils/train_and_eval.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import numpy as np
|
| 5 |
+
from model.unet_training import CE_Loss, Dice_loss, Focal_Loss
|
| 6 |
+
|
| 7 |
+
from utils.utils import get_lr
|
| 8 |
+
from torch.cuda.amp import autocast, GradScaler
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class LogColor:
|
| 13 |
+
GREEN = "\033[1;32m"
|
| 14 |
+
YELLOW = "\033[1;33m"
|
| 15 |
+
RED = "\033[1;31m"
|
| 16 |
+
RESET = "\033[0m"
|
| 17 |
+
BLUE = "\033[1;34m"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def pixel_accuracy(output, target):
|
| 21 |
+
with torch.no_grad():
|
| 22 |
+
_, predicted = torch.max(output, 1)
|
| 23 |
+
correct = (predicted == target).float()
|
| 24 |
+
correct_pixels = correct.sum().item()
|
| 25 |
+
total_pixels = target.numel()
|
| 26 |
+
return correct_pixels / total_pixels
|
| 27 |
+
|
| 28 |
+
def mean_accuracy(output, target, num_classes):
|
| 29 |
+
"""
|
| 30 |
+
计算 Mean Pixel Accuracy (MPA).
|
| 31 |
+
:param output: torch.Tensor, shape [N, C, H, W]
|
| 32 |
+
:param target: torch.Tensor, shape [N, H, W]
|
| 33 |
+
:param num_classes: int
|
| 34 |
+
:return: float, mean pixel accuracy over valid classes
|
| 35 |
+
"""
|
| 36 |
+
with torch.no_grad():
|
| 37 |
+
# 取出每个像素的预测类别索引
|
| 38 |
+
_, predicted = torch.max(output, dim=1) # shape [N, H, W]
|
| 39 |
+
|
| 40 |
+
accuracies = []
|
| 41 |
+
for i in range(num_classes):
|
| 42 |
+
# 找到该类别在标签和预测中的位置
|
| 43 |
+
target_mask = (target == i)
|
| 44 |
+
predicted_mask = (predicted == i)
|
| 45 |
+
|
| 46 |
+
# 交集:预测正确的像素数(即 TP)
|
| 47 |
+
intersection = torch.logical_and(target_mask, predicted_mask).sum().item()
|
| 48 |
+
total = target_mask.sum().item() # 标签中该类的总像素数
|
| 49 |
+
|
| 50 |
+
if total > 0:
|
| 51 |
+
acc = intersection / total
|
| 52 |
+
accuracies.append(acc)
|
| 53 |
+
# 如果该类别在 GT 中没有出现,则跳过,不计入平均
|
| 54 |
+
|
| 55 |
+
# 防止所有类别都未出现
|
| 56 |
+
if len(accuracies) == 0:
|
| 57 |
+
return 0.0
|
| 58 |
+
else:
|
| 59 |
+
return sum(accuracies) / len(accuracies)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# 计算Mean IoU
|
| 63 |
+
def mean_iou(output, target, num_classes):
|
| 64 |
+
"""
|
| 65 |
+
计算 mean IoU,只在 target 出现的类别中取平均
|
| 66 |
+
"""
|
| 67 |
+
with torch.no_grad():
|
| 68 |
+
_, predicted = torch.max(output, dim=1) # (N, H, W)
|
| 69 |
+
ious = []
|
| 70 |
+
for i in range(num_classes):
|
| 71 |
+
target_mask = (target == i)
|
| 72 |
+
pred_mask = (predicted == i)
|
| 73 |
+
|
| 74 |
+
intersection = torch.logical_and(target_mask, pred_mask).sum().item()
|
| 75 |
+
union = torch.logical_or(target_mask, pred_mask).sum().item()
|
| 76 |
+
|
| 77 |
+
if target_mask.sum().item() > 0: # 只对 target 中存在的类求 IoU
|
| 78 |
+
ious.append(intersection / union if union > 0 else 0.0)
|
| 79 |
+
if len(ious) == 0:
|
| 80 |
+
return 0.0
|
| 81 |
+
return sum(ious) / len(ious)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# 计算Frequency Weighted IoU
|
| 85 |
+
def frequency_weighted_iou(output, target, num_classes):
|
| 86 |
+
with torch.no_grad():
|
| 87 |
+
_, predicted = torch.max(output, 1)
|
| 88 |
+
ious = []
|
| 89 |
+
frequencies = []
|
| 90 |
+
for i in range(num_classes):
|
| 91 |
+
target_mask = (target == i)
|
| 92 |
+
pred_mask = (predicted == i)
|
| 93 |
+
intersection = torch.logical_and(target_mask, pred_mask).sum().item()
|
| 94 |
+
union = torch.logical_or(target_mask, pred_mask).sum().item()
|
| 95 |
+
freq = target_mask.sum().item()
|
| 96 |
+
frequencies.append(freq)
|
| 97 |
+
ious.append((intersection / union) if union > 0 else 0.0)
|
| 98 |
+
|
| 99 |
+
total = sum(frequencies)
|
| 100 |
+
if total == 0:
|
| 101 |
+
return 0.0
|
| 102 |
+
fw_iou = sum(f * iou for f, iou in zip(frequencies, ious)) / total
|
| 103 |
+
return fw_iou
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def train_one_epoch(model, optimizer, train_loader, device, dice_loss, focal_loss,
|
| 107 |
+
gpu_used, num_classes, scaler, epoch, train_epoch):
|
| 108 |
+
|
| 109 |
+
# 设置类别权重参数。它是用来处理类别不平衡的问题的
|
| 110 |
+
cls_weights = np.ones([num_classes], np.float32)
|
| 111 |
+
epoch_loss = 0.0 # 总的训练损失
|
| 112 |
+
|
| 113 |
+
# 设置模型为训练模式
|
| 114 |
+
model_train = model.train()
|
| 115 |
+
model_train = model_train.cuda()
|
| 116 |
+
|
| 117 |
+
# 遍历训练数据
|
| 118 |
+
for iteration, batch in enumerate(train_loader):
|
| 119 |
+
imgs, pngs, labels = batch # 获取输入图像、标签和分割目标
|
| 120 |
+
print(f"图像像素值范围: {pngs.min().item()} ~ {pngs.max().item()}") # 仍为0/1/2/3
|
| 121 |
+
|
| 122 |
+
# 数据准备阶段:使用 `.to(device)` 自动将数据移到设备上
|
| 123 |
+
weights = torch.tensor(cls_weights).to(device) # 转换类别权重并移动到GPU
|
| 124 |
+
imgs = imgs.to(device)
|
| 125 |
+
pngs = pngs.to(device)
|
| 126 |
+
labels = labels.to(device)
|
| 127 |
+
|
| 128 |
+
optimizer.zero_grad() # 清除之前的梯度
|
| 129 |
+
|
| 130 |
+
# 混合精度训练
|
| 131 |
+
if scaler is None:
|
| 132 |
+
# 前向传播
|
| 133 |
+
outputs = model_train(imgs)
|
| 134 |
+
|
| 135 |
+
# 损失计算
|
| 136 |
+
if focal_loss:
|
| 137 |
+
loss = Focal_Loss(outputs, pngs, weights, num_classes=num_classes)
|
| 138 |
+
else:
|
| 139 |
+
loss = CE_Loss(outputs, pngs, weights, num_classes=num_classes)
|
| 140 |
+
|
| 141 |
+
if dice_loss:
|
| 142 |
+
# 如果使用Dice Loss,则加上Dice损失
|
| 143 |
+
main_dice = Dice_loss(outputs, labels)
|
| 144 |
+
# main_dice = Dice_loss(outputs, pngs)
|
| 145 |
+
loss = loss + main_dice
|
| 146 |
+
|
| 147 |
+
# 反向传播
|
| 148 |
+
loss.backward()
|
| 149 |
+
optimizer.step() # 更新模型参数
|
| 150 |
+
else:
|
| 151 |
+
with autocast():
|
| 152 |
+
outputs = model_train(imgs) # 通过模型获取预测结果
|
| 153 |
+
|
| 154 |
+
# 损失计算
|
| 155 |
+
if focal_loss:
|
| 156 |
+
loss = Focal_Loss(outputs, pngs, weights, num_classes=num_classes)
|
| 157 |
+
else:
|
| 158 |
+
loss = CE_Loss(outputs, pngs, weights, num_classes=num_classes)
|
| 159 |
+
|
| 160 |
+
if dice_loss:
|
| 161 |
+
main_dice = Dice_loss(outputs, labels)
|
| 162 |
+
loss = loss + main_dice
|
| 163 |
+
|
| 164 |
+
# 反向传播
|
| 165 |
+
scaler.scale(loss).backward()
|
| 166 |
+
scaler.step(optimizer) # 使用混合精度更新梯度
|
| 167 |
+
scaler.update() # 更新scaler
|
| 168 |
+
|
| 169 |
+
# 累加训练损失和F-score
|
| 170 |
+
epoch_loss += loss.item()
|
| 171 |
+
|
| 172 |
+
# 打印标题(每个epoch开始时打印一次)
|
| 173 |
+
if iteration == 0: # 只在第一个 batch 打印标题
|
| 174 |
+
print(f"{LogColor.GREEN}Epoch{LogColor.RESET}{' ' * 12}"
|
| 175 |
+
f"{LogColor.YELLOW}data_num{LogColor.RESET}{' ' * 12}"
|
| 176 |
+
f"{LogColor.YELLOW}GPU Mem{LogColor.RESET}{' ' * 12}"
|
| 177 |
+
f"{LogColor.YELLOW}Loss{LogColor.RESET}{' ' * 12}"
|
| 178 |
+
f"{LogColor.YELLOW}LR{LogColor.RESET}{' ' * 12}"
|
| 179 |
+
f"{LogColor.YELLOW}Image_size{LogColor.RESET}{' ' * 12}"
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
# 每10个batch打印一次信息
|
| 183 |
+
if iteration % 1 == 0:
|
| 184 |
+
if len(train_loader) < 1:
|
| 185 |
+
a = len(train_loader)
|
| 186 |
+
else:
|
| 187 |
+
a = 1
|
| 188 |
+
|
| 189 |
+
Epoch_len = len("Epoch") + 12 - len(str(f"{epoch + 1}/{train_epoch}"))
|
| 190 |
+
batch_len = len("data_num") + 12 - len(str(f"{iteration + a}/{len(train_loader)}"))
|
| 191 |
+
GPU_len = len("GPU Mem") + 12 - len(str(f"{gpu_used:.2f} MB"))
|
| 192 |
+
Loss_len = len("Loss") + 12 - len(str(f"{loss.item():.8f}"))
|
| 193 |
+
LR_len = len("LR") + 12 - len(str(f"{get_lr(optimizer):.8f}"))
|
| 194 |
+
|
| 195 |
+
# 使用 \r 在同一行更新输出
|
| 196 |
+
print(f"\r{epoch + 1}/{train_epoch}{' ' * Epoch_len}"
|
| 197 |
+
f"{iteration + a}/{len(train_loader)}{' ' * batch_len}"
|
| 198 |
+
f"{gpu_used:.2f} MB{' ' * GPU_len}"
|
| 199 |
+
f"{loss.item():.8f}{' ' * Loss_len}"
|
| 200 |
+
f"{get_lr(optimizer):.8f}{' ' * LR_len}"
|
| 201 |
+
f"{imgs.shape[2]}", end='', flush=True)
|
| 202 |
+
|
| 203 |
+
# 每个epoch结束后打印一次
|
| 204 |
+
print(f"{LogColor.GREEN}")
|
| 205 |
+
time.sleep(1) # 加一点延迟,防止输出闪烁过快
|
| 206 |
+
|
| 207 |
+
# ➕ 返回平均loss
|
| 208 |
+
return epoch_loss / len(train_loader)
|
| 209 |
+
|
| 210 |
+
def evaluate(model, val_loader, device, dice_loss, focal_loss, num_classes):
|
| 211 |
+
|
| 212 |
+
cls_weights = np.ones([num_classes], np.float32)
|
| 213 |
+
val_loss = 0 # 记录总的验证损失
|
| 214 |
+
|
| 215 |
+
# 设置模型为验证模式
|
| 216 |
+
model_eval = model.eval()
|
| 217 |
+
model_eval = model_eval.cuda()
|
| 218 |
+
|
| 219 |
+
# 初始化累积变量
|
| 220 |
+
total_pixel_acc = 0
|
| 221 |
+
total_mean_acc = 0
|
| 222 |
+
total_mean_iou = 0
|
| 223 |
+
total_fw_iou = 0
|
| 224 |
+
num_batches = len(val_loader)
|
| 225 |
+
|
| 226 |
+
# 遍历验证数据,前向传播
|
| 227 |
+
with torch.no_grad():
|
| 228 |
+
for iteration, batch in enumerate(val_loader):
|
| 229 |
+
imgs, pngs, labels = batch # 获取验证数据
|
| 230 |
+
|
| 231 |
+
# 数据准备阶段
|
| 232 |
+
weights = torch.tensor(cls_weights).to(device) # 转换类别权重并移动到GPU
|
| 233 |
+
imgs = imgs.to(device)
|
| 234 |
+
pngs = pngs.to(device)
|
| 235 |
+
labels = labels.to(device)
|
| 236 |
+
outputs = model_eval(imgs)
|
| 237 |
+
|
| 238 |
+
# print("outputs", outputs.shape)
|
| 239 |
+
# print("pngs", pngs.shape)
|
| 240 |
+
# 损失计算
|
| 241 |
+
if focal_loss:
|
| 242 |
+
loss = Focal_Loss(outputs, pngs, weights, num_classes=num_classes)
|
| 243 |
+
else:
|
| 244 |
+
loss = CE_Loss(outputs, pngs, weights, num_classes=num_classes)
|
| 245 |
+
|
| 246 |
+
if dice_loss:
|
| 247 |
+
main_dice = Dice_loss(outputs, labels)
|
| 248 |
+
# main_dice = Dice_loss(outputs, pngs)
|
| 249 |
+
loss = loss + main_dice
|
| 250 |
+
|
| 251 |
+
# 计算各个指标
|
| 252 |
+
pixel_acc = pixel_accuracy(outputs, pngs)
|
| 253 |
+
mean_acc = mean_accuracy(outputs, pngs, num_classes)
|
| 254 |
+
mean_iou_value = mean_iou(outputs, pngs, num_classes)
|
| 255 |
+
fw_iou = frequency_weighted_iou(outputs, pngs, num_classes)
|
| 256 |
+
|
| 257 |
+
# 累加到总结果
|
| 258 |
+
total_pixel_acc += pixel_acc
|
| 259 |
+
total_mean_acc += mean_acc
|
| 260 |
+
total_mean_iou += mean_iou_value
|
| 261 |
+
total_fw_iou += fw_iou
|
| 262 |
+
val_loss += loss.item()
|
| 263 |
+
|
| 264 |
+
# 打印标题(每个epoch开始时打印一次)
|
| 265 |
+
if iteration == 0: # 只在第一个 batch 打印标题
|
| 266 |
+
epoch_len = len("Epoch") + 12
|
| 267 |
+
data_num_len = len("data_num") - len("data_num") + 12
|
| 268 |
+
Pixelacc_len = len("GPU Mem") - len("Pixelacc") + 12
|
| 269 |
+
Meanacc_len = len("Loss") - len("Meanacc") + 12
|
| 270 |
+
Meaniou_len = len("LR") - len("Meaniou") + 12
|
| 271 |
+
|
| 272 |
+
print(f"{' ' * epoch_len}"
|
| 273 |
+
f"{LogColor.RED}data_num{LogColor.RESET}{' ' * data_num_len}"
|
| 274 |
+
f"{LogColor.RED}Pixelacc{LogColor.RESET}{' ' * Pixelacc_len}"
|
| 275 |
+
f"{LogColor.RED}Meanacc{LogColor.RESET}{' ' * Meanacc_len}"
|
| 276 |
+
f"{LogColor.RED}Meaniou{LogColor.RESET}{' ' * Meaniou_len}"
|
| 277 |
+
f"{LogColor.RED}Fwiou{LogColor.RESET}")
|
| 278 |
+
|
| 279 |
+
# 计算平均值
|
| 280 |
+
avg_pixel_acc = total_pixel_acc / num_batches
|
| 281 |
+
avg_mean_acc = total_mean_acc / num_batches
|
| 282 |
+
avg_mean_iou = total_mean_iou / num_batches
|
| 283 |
+
avg_fw_iou = total_fw_iou / num_batches
|
| 284 |
+
avg_loss = val_loss / num_batches # ➕ 平均 loss
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
# 将结果保存到字典中
|
| 288 |
+
metrics = {
|
| 289 |
+
'Pixel Accuracy': avg_pixel_acc,
|
| 290 |
+
'Mean Accuracy': avg_mean_acc,
|
| 291 |
+
'Mean IoU': avg_mean_iou,
|
| 292 |
+
'Frequency Weighted IoU': avg_fw_iou,
|
| 293 |
+
'Loss': avg_loss # ➕ 加入字典
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
epoch_len = len("Epoch") + 12
|
| 297 |
+
batch_len = data_num_len + len("data_num") - len(str(f"{iteration + 1}/{len(val_loader)}"))
|
| 298 |
+
avg_pixel_acc_len = Pixelacc_len + len("Pixelacc") - len(str(f"{avg_pixel_acc:.2f}"))
|
| 299 |
+
avg_mean_acc_len = Meanacc_len + len("Meanacc") - len(str(f"{avg_mean_acc:.2f}"))
|
| 300 |
+
avg_Mean_iou_len = Meaniou_len + len("Meaniou") - len(str(f"{avg_mean_iou:.2f}"))
|
| 301 |
+
|
| 302 |
+
# 使用 \r 在同一行更新输出
|
| 303 |
+
print(f"{' ' * (epoch_len)}"
|
| 304 |
+
f"{iteration + 1}/{len(val_loader)}{' ' * batch_len}"
|
| 305 |
+
f"{avg_pixel_acc:.2f}{' ' * avg_pixel_acc_len}"
|
| 306 |
+
f"{avg_mean_acc:.2f}{' ' * avg_mean_acc_len}"
|
| 307 |
+
f"{avg_mean_iou:.2f}{' ' * avg_Mean_iou_len}"
|
| 308 |
+
f"{avg_fw_iou:.2f}", end='', flush=True)
|
| 309 |
+
print(f"\n{LogColor.GREEN}")
|
| 310 |
+
time.sleep(1) # 加一点延迟,防止输出闪烁过快
|
| 311 |
+
|
| 312 |
+
return metrics
|
utils/utils.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
from PIL import Image
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
# ---------------------------------------------------------#
|
| 9 |
+
# 将图像转换成RGB图像,防止灰度图在预测时报错。
|
| 10 |
+
# 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
|
| 11 |
+
# ---------------------------------------------------------#
|
| 12 |
+
def cvtColor(image):
|
| 13 |
+
if len(np.shape(image)) == 3 and np.shape(image)[2] == 3:
|
| 14 |
+
return image
|
| 15 |
+
else:
|
| 16 |
+
image = image.convert('RGB')
|
| 17 |
+
return image
|
| 18 |
+
|
| 19 |
+
# ---------------------------------------------------#
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# 对输入图像进行resize
|
| 23 |
+
# ---------------------------------------------------#
|
| 24 |
+
def resize_image(image, size):
|
| 25 |
+
iw, ih = image.size
|
| 26 |
+
w, h = size
|
| 27 |
+
|
| 28 |
+
scale = min(w / iw, h / ih)
|
| 29 |
+
nw = int(iw * scale)
|
| 30 |
+
nh = int(ih * scale)
|
| 31 |
+
|
| 32 |
+
image = image.resize((nw, nh), Image.BICUBIC)
|
| 33 |
+
new_image = Image.new('RGB', size, (128, 128, 128))
|
| 34 |
+
new_image.paste(image, ((w - nw) // 2, (h - nh) // 2))
|
| 35 |
+
|
| 36 |
+
return new_image, nw, nh
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# ---------------------------------------------------#
|
| 40 |
+
# 获得学习率
|
| 41 |
+
# ---------------------------------------------------#
|
| 42 |
+
def get_lr(optimizer):
|
| 43 |
+
for param_group in optimizer.param_groups:
|
| 44 |
+
return param_group['lr']
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# ---------------------------------------------------#
|
| 48 |
+
# 设置种子
|
| 49 |
+
# ---------------------------------------------------#
|
| 50 |
+
def seed_everything(seed=11):
|
| 51 |
+
random.seed(seed)
|
| 52 |
+
np.random.seed(seed)
|
| 53 |
+
torch.manual_seed(seed)
|
| 54 |
+
torch.cuda.manual_seed(seed)
|
| 55 |
+
torch.cuda.manual_seed_all(seed)
|
| 56 |
+
torch.backends.cudnn.deterministic = True
|
| 57 |
+
torch.backends.cudnn.benchmark = False
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# ---------------------------------------------------#
|
| 61 |
+
# 设置Dataloader的种子
|
| 62 |
+
# ---------------------------------------------------#
|
| 63 |
+
def worker_init_fn(rank, seed):
|
| 64 |
+
worker_seed = rank + seed
|
| 65 |
+
random.seed(worker_seed)
|
| 66 |
+
np.random.seed(worker_seed)
|
| 67 |
+
torch.manual_seed(worker_seed)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def preprocess_input(image):
|
| 71 |
+
image /= 255.0
|
| 72 |
+
return image
|