wangwenguang commited on
Commit
5ea0d01
·
verified ·
1 Parent(s): aed9c73

Upload 23 files

Browse files
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