Compare commits
1 Commits
804f671e3e
...
0184263fce
| Author | SHA1 | Date | |
|---|---|---|---|
| 0184263fce |
|
|
@ -1 +0,0 @@
|
|||
3.11
|
||||
|
|
@ -22,11 +22,11 @@ class Calculate():
|
|||
DataFrame = pd.DataFrame(
|
||||
{
|
||||
"loss" : "{:.2f}".format(Loss),
|
||||
"precision" : "{:.2f}".format(Precision),
|
||||
"recall" : "{:.2f}".format(Recall),
|
||||
"accuracy" : "{:.2f}".format(Accuracy),
|
||||
"f1" : "{:.2f}".format(F1),
|
||||
"AUC" : "{:.2f}".format(AUC)
|
||||
"precision" : "{:.2f}".format(Precision * 100),
|
||||
"recall" : "{:.2f}".format(Recall * 100),
|
||||
"accuracy" : "{:.2f}".format(Accuracy * 100),
|
||||
"f1" : "{:.2f}".format(F1 * 100),
|
||||
"AUC" : "{:.2f}".format(AUC * 100)
|
||||
}, index = [0]
|
||||
)
|
||||
self.History.append(DataFrame)
|
||||
|
|
@ -40,7 +40,7 @@ class Calculate():
|
|||
F1_Mean = np.mean(self.F1_Record)
|
||||
AUC_Mean = np.mean(self.AUC_Record)
|
||||
|
||||
Mean_DataFram = self.Construction_To_DataFrame(Loss_Mean, Accuracy_Mean * 100, Precision_Mean * 100, Recall_Mean * 100, F1_Mean * 100, AUC_Mean * 100)
|
||||
Mean_DataFram = self.Construction_To_DataFrame(Loss_Mean, Accuracy_Mean, Precision_Mean, Recall_Mean, F1_Mean, AUC_Mean)
|
||||
|
||||
return Mean_DataFram
|
||||
|
||||
|
|
@ -58,12 +58,12 @@ class Calculate():
|
|||
def Output_Style(self):
|
||||
Result = pd.DataFrame(
|
||||
{
|
||||
"loss" : "{}%±{}".format(self.History[0]["loss"][0], self.History[1]["loss"][0]),
|
||||
"precision" : "{}%±{}".format(self.History[0]["precision"][0], self.History[1]["precision"][0]),
|
||||
"recall" : "{}%±{}".format(self.History[0]["recall"][0], self.History[1]["recall"][0]),
|
||||
"accuracy" : "{}%±{}".format(self.History[0]["accuracy"][0], self.History[1]["accuracy"][0]),
|
||||
"f1" : "{}%±{}".format(self.History[0]["f1"][0], self.History[1]["f1"][0]),
|
||||
"AUC" : "{}%±{}".format(self.History[0]["AUC"][0], self.History[1]["AUC"][0])
|
||||
"loss" : "{}±{}".format(self.History[0]["loss"][0], self.History[1]["loss"][0]),
|
||||
"precision" : "{}±{}".format(self.History[0]["precision"][0], self.History[1]["precision"][0]),
|
||||
"recall" : "{}±{}".format(self.History[0]["recall"][0], self.History[1]["recall"][0]),
|
||||
"accuracy" : "{}±{}".format(self.History[0]["accuracy"][0], self.History[1]["accuracy"][0]),
|
||||
"f1" : "{}±{}".format(self.History[0]["f1"][0], self.History[1]["f1"][0]),
|
||||
"AUC" : "{}±{}".format(self.History[0]["AUC"][0], self.History[1]["AUC"][0])
|
||||
}, index = [0]
|
||||
)
|
||||
return Result
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,51 +1,38 @@
|
|||
from model_data_processing.processing import make_label_list
|
||||
from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image
|
||||
from _validation.ValidationTheEnterData import validation_the_enter_data
|
||||
from Load_process.file_processing import Process_File
|
||||
from Load_process.LoadData import Load_Data_Prepare, Load_Data_Tools
|
||||
from Training_Tools.PreProcess import Training_Precesses
|
||||
|
||||
from Load_process.LoadData import Load_Data_Prepare
|
||||
from torchvision import transforms
|
||||
from Training_Tools.Tools import Tool
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
import cv2
|
||||
|
||||
class Image_generator():
|
||||
'''製作資料強化'''
|
||||
def __init__(self, Training_Root, Generator_Root, Labels, Image_Size, Class_Count) -> None:
|
||||
def __init__(self, Generator_Root, Labels, Image_Size) -> None:
|
||||
self._validation = validation_the_enter_data()
|
||||
self.stop = 0
|
||||
self.Labels = Labels
|
||||
self.Training_Root = Training_Root
|
||||
self.Generator_Root = Generator_Root
|
||||
self.Image_Size = Image_Size
|
||||
self.Class_Count = Class_Count
|
||||
pass
|
||||
|
||||
def Processing_Main(self):
|
||||
data_size = 2712
|
||||
File = Process_File()
|
||||
Prepare = Load_Data_Prepare()
|
||||
Load_Tool = Load_Data_Tools()
|
||||
def Processing_Main(self, Training_Dict_Data_Root):
|
||||
data_size = 0
|
||||
|
||||
if not File.Judge_File_Exist(self.Generator_Root): # 檔案若不存在
|
||||
# 確定我要多少個List
|
||||
Prepare.Set_Data_Content([], len(self.Labels))
|
||||
# 製作標準資料增強
|
||||
'''
|
||||
這裡我想要做的是依照paper上的資料強化IMAGE DATA COLLECTION AND IMPLEMENTATION OF DEEP LEARNING-BASED MODEL IN DETECTING MONKEYPOX DISEASE USING MODIFIED VGG16
|
||||
產生出資料強化後的影像
|
||||
'''
|
||||
for i in range(1, 5, 1):
|
||||
print("\nAugmentation one Generator image")
|
||||
data_size = self.get_processing_Augmentation(Training_Dict_Data_Root, i, data_size)
|
||||
self.stop += data_size
|
||||
|
||||
# 製作讀檔字典並回傳檔案路徑
|
||||
Prepare.Set_Label_List(self.Labels)
|
||||
Prepare.Set_Data_Dictionary(Prepare.Get_Label_List(), Prepare.Get_Data_Content(), len(self.Labels))
|
||||
Original_Dict_Data_Root = Prepare.Get_Data_Dict()
|
||||
get_all_original_image_data = Load_Tool.get_data_root(self.Training_Root, Original_Dict_Data_Root, Prepare.Get_Label_List())
|
||||
|
||||
# 儲存資料強化後資料
|
||||
# 製作標準資料增強
|
||||
'''
|
||||
這裡我想要做的是依照paper上的資料強化IMAGE DATA COLLECTION AND IMPLEMENTATION OF DEEP LEARNING-BASED MODEL IN DETECTING MONKEYPOX DISEASE USING MODIFIED VGG16
|
||||
產生出資料強化後的影像
|
||||
'''
|
||||
for i in range(1, 5, 1):
|
||||
print(f"\nAugmentation {i} Generator image")
|
||||
data_size = self.get_processing_Augmentation(get_all_original_image_data, i, data_size)
|
||||
self.stop += data_size
|
||||
else: # 若檔案存在
|
||||
print("standard data and myself data are exist\n")
|
||||
print()
|
||||
|
||||
def get_processing_Augmentation(self, original_image_root : dict, Augment_choose, data_size):
|
||||
Prepaer = Load_Data_Prepare()
|
||||
|
|
@ -63,75 +50,84 @@ class Image_generator():
|
|||
strardand = 要使用哪種Image Augmentation
|
||||
'''
|
||||
File = Process_File()
|
||||
tool = Training_Precesses(self.Image_Size)
|
||||
image_processing = Read_image_and_Process_image(self.Image_Size)
|
||||
tool = Tool()
|
||||
Classes = []
|
||||
Transform = self.Generator_Content(stardand)
|
||||
|
||||
for label in self.Labels: # 分別對所有類別進行資料強化
|
||||
Image_Roots = self.get_data_roots[label]
|
||||
image = self.load_data(label) # 取的資料
|
||||
save_root = File.Make_Save_Root(label, save_roots) # 合併路徑
|
||||
|
||||
Classes = make_label_list(len(Image_Roots), "1")
|
||||
Training_Dataset = tool.Setting_DataSet(Image_Roots, Classes, "Generator")
|
||||
Training_DataLoader = tool.Dataloader_Sampler(Training_Dataset, 1, False)
|
||||
Classes = image_processing.make_label_list(len(image), "1")
|
||||
Training_Dataset = tool.Convert_Data_To_DataSet_And_Put_To_Dataloader(image, Classes, 1, False)
|
||||
|
||||
if File.JudgeRoot_MakeDir(save_root): # 判斷要存的資料夾存不存在,不存在則創立
|
||||
print("The file is exist.This Script is not creating new fold.")
|
||||
|
||||
for i in range(1, int(self.Class_Count / len(Image_Roots)) + 1, 1):
|
||||
for batch_idx, (images, labels, File_Name, File_Classes) in enumerate(Training_DataLoader):
|
||||
for j, img in enumerate(images):
|
||||
# if i == self.stop:
|
||||
# break
|
||||
for batch_idx, (images, labels) in enumerate(Training_Dataset):
|
||||
for i, img in enumerate(images):
|
||||
if i == self.stop:
|
||||
break
|
||||
|
||||
img = img.permute(2, 0, 1)
|
||||
img = Transform(img)
|
||||
img = img.permute(2, 0, 1)
|
||||
img = Transform(img)
|
||||
|
||||
# 轉換為 NumPy 陣列並從 BGR 轉為 RGB
|
||||
img_np = img.numpy().transpose(1, 2, 0) # 轉回 HWC 格式
|
||||
# 轉換為 NumPy 陣列並從 BGR 轉為 RGB
|
||||
img_np = img.numpy().transpose(1, 2, 0) # 轉回 HWC 格式
|
||||
img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB) # BGR 轉 RGB
|
||||
|
||||
img_pil = transforms.ToPILImage()(img_np)
|
||||
File.Save_PIL_File("image_" + label + str(data_size) + ".png", save_root, img_pil) # 存檔
|
||||
data_size += 1
|
||||
img_pil = transforms.ToPILImage()(img_np)
|
||||
File.Save_PIL_File("image_" + label + str(data_size) + ".png", save_root, img_pil) # 存檔
|
||||
data_size += 1
|
||||
|
||||
return data_size
|
||||
|
||||
def load_data(self, label):
|
||||
'''Images is readed by myself'''
|
||||
image_processing = Read_image_and_Process_image(self.Image_Size)
|
||||
img = image_processing.Data_Augmentation_Image(self.get_data_roots[label])
|
||||
img = torch.tensor(img)
|
||||
|
||||
self.stop = len(img) * 5
|
||||
return img
|
||||
|
||||
def Generator_Content(self, judge): # 影像資料增強
|
||||
'''
|
||||
## Parameters:
|
||||
<b>featurewise_center</b> : 布爾值。將輸入數據的均值設置為0,逐特徵進行。<br/>
|
||||
<b>samplewise_center</b> : 布爾值。將每個樣本的均值設置為0。<br/>
|
||||
<b>featurewise_std_normalization</b> : Boolean. 布爾值。將輸入除以數據標準差,逐特徵進行。<br/>
|
||||
<b>samplewise_std_normalization</b> : 布爾值。將每個輸入除以其標準差。<br/>
|
||||
<b>zca_epsilon</b> : ZCA 白化的epsilon 值,默認為1e-6。<br/>
|
||||
<b>zca_whitening</b> : 布爾值。是否應用ZCA 白化。<br/>
|
||||
<b>rotation_range</b> : 整數。隨機旋轉的度數範圍。<br/>
|
||||
<b>width_shift_range</b> : 浮點數、一維數組或整數<br/>
|
||||
float: 如果<1,則是除以總寬度的值,或者如果>=1,則為像素值。
|
||||
1-D 數組: 數組中的隨機元素。
|
||||
int: 來自間隔 (-width_shift_range, +width_shift_range) 之間的整數個像素。
|
||||
width_shift_range=2時,可能值是整數[-1, 0, +1],與 width_shift_range=[-1, 0, +1] 相同;而 width_shift_range=1.0 時,可能值是 [-1.0, +1.0) 之間的浮點數。
|
||||
<b>height_shift_range</b> : 浮點數、一維數組或整數<br/>
|
||||
float: 如果<1,則是除以總寬度的值,或者如果>=1,則為像素值。
|
||||
1-D array-like: 數組中的隨機元素。
|
||||
int: 來自間隔 (-height_shift_range, +height_shift_range) 之間的整數個像素。
|
||||
height_shift_range=2時,可能值是整數[-1, 0, +1],與 height_shift_range=[-1, 0, +1] 相同;而 height_shift_range=1.0 時,可能值是 [-1.0, +1.0) 之間的浮點數。
|
||||
<b>shear_range</b> : 浮點數。剪切強度(以弧度逆時針方向剪切角度)。<br/>
|
||||
<b>zoom_range</b> : 浮點數或[lower, upper]。隨機縮放範圍。如果是浮點數,[lower, upper] = [1-zoom_range, 1+zoom_range]。<br/>
|
||||
<b>channel_shift_range</b> : 浮點數。隨機通道轉換的範圍。<br/>
|
||||
<b>fill_mode</b> : {"constant", "nearest", "reflect" or "wrap"} 之一。默認為'nearest'。輸入邊界以外的點根據給定的模式填充:<br/>
|
||||
'constant': kkkkkkkk|abcd|kkkkkkkk (cval=k)
|
||||
'nearest': aaaaaaaa|abcd|dddddddd
|
||||
'reflect': abcddcba|abcd|dcbaabcd
|
||||
'wrap': abcdabcd|abcd|abcdabcd
|
||||
<b>cval</b> : 浮點數或整數。用於邊界之外的點的值,當 fill_mode = "constant" 時。<br/>
|
||||
<b>horizontal_flip</b> : 布爾值。隨機水平翻轉。<br/>
|
||||
<b>vertical_flip</b> : 布爾值。隨機垂直翻轉。<br/>
|
||||
<b>rescale</b> : 重縮放因子。默認為None。如果是None 或0,不進行縮放,否則將數據乘以所提供的值(在應用任何其他轉換之前)。<br/>
|
||||
<b>preprocessing_function</b> : 應用於每個輸入的函數。這個函數會在任何其他改變之前運行。這個函數需要一個參數:一張圖像(秩為3 的Numpy 張量),並且應該輸出一個同尺寸的Numpy 張量。<br/>
|
||||
<b>data_format</b> : 圖像數據格式,{"channels_first", "channels_last"} 之一。"channels_last" 模式表示圖像輸入尺寸應該為(samples, height, width, channels),"channels_first" 模式表示輸入尺寸應該為(samples, channels, height, width)。默認為在Keras 配置文件 ~/.keras/keras.json 中的 image_data_format 值。如果你從未設置它,那它就是"channels_last"。<br/>
|
||||
<b>validation_split</b> : 浮點數。Float. 保留用於驗證的圖像的比例(嚴格在0和1之間)。<br/>
|
||||
<b>dtype</b> : 生成數組使用的數據類型。<br/>
|
||||
ImageGenerator的參數:
|
||||
featurewise_center : 布爾值。將輸入數據的均值設置為0,逐特徵進行。
|
||||
samplewise_center : 布爾值。將每個樣本的均值設置為0。
|
||||
featurewise_std_normalization : Boolean. 布爾值。將輸入除以數據標準差,逐特徵進行。
|
||||
samplewise_std_normalization : 布爾值。將每個輸入除以其標準差。
|
||||
zca_epsilon : ZCA 白化的epsilon 值,默認為1e-6。
|
||||
zca_whitening : 布爾值。是否應用ZCA 白化。
|
||||
rotation_range : 整數。隨機旋轉的度數範圍。
|
||||
width_shift_range : 浮點數、一維數組或整數
|
||||
float: 如果<1,則是除以總寬度的值,或者如果>=1,則為像素值。
|
||||
1-D 數組: 數組中的隨機元素。
|
||||
int: 來自間隔 (-width_shift_range, +width_shift_range) 之間的整數個像素。
|
||||
width_shift_range=2時,可能值是整數[-1, 0, +1],與 width_shift_range=[-1, 0, +1] 相同;而 width_shift_range=1.0 時,可能值是 [-1.0, +1.0) 之間的浮點數。
|
||||
height_shift_range : 浮點數、一維數組或整數
|
||||
float: 如果<1,則是除以總寬度的值,或者如果>=1,則為像素值。
|
||||
1-D array-like: 數組中的隨機元素。
|
||||
int: 來自間隔 (-height_shift_range, +height_shift_range) 之間的整數個像素。
|
||||
height_shift_range=2時,可能值是整數[-1, 0, +1],與 height_shift_range=[-1, 0, +1] 相同;而 height_shift_range=1.0 時,可能值是 [-1.0, +1.0) 之間的浮點數。
|
||||
shear_range : 浮點數。剪切強度(以弧度逆時針方向剪切角度)。
|
||||
zoom_range : 浮點數或[lower, upper]。隨機縮放範圍。如果是浮點數,[lower, upper] = [1-zoom_range, 1+zoom_range]。
|
||||
channel_shift_range : 浮點數。隨機通道轉換的範圍。
|
||||
fill_mode : {"constant", "nearest", "reflect" or "wrap"} 之一。默認為'nearest'。輸入邊界以外的點根據給定的模式填充:
|
||||
'constant': kkkkkkkk|abcd|kkkkkkkk (cval=k)
|
||||
'nearest': aaaaaaaa|abcd|dddddddd
|
||||
'reflect': abcddcba|abcd|dcbaabcd
|
||||
'wrap': abcdabcd|abcd|abcdabcd
|
||||
cval : 浮點數或整數。用於邊界之外的點的值,當 fill_mode = "constant" 時。
|
||||
horizontal_flip : 布爾值。隨機水平翻轉。
|
||||
vertical_flip : 布爾值。隨機垂直翻轉。
|
||||
rescale : 重縮放因子。默認為None。如果是None 或0,不進行縮放,否則將數據乘以所提供的值(在應用任何其他轉換之前)。
|
||||
preprocessing_function : 應用於每個輸入的函數。這個函數會在任何其他改變之前運行。這個函數需要一個參數:一張圖像(秩為3 的Numpy 張量),並且應該輸出一個同尺寸的Numpy 張量。
|
||||
data_format : 圖像數據格式,{"channels_first", "channels_last"} 之一。"channels_last" 模式表示圖像輸入尺寸應該為(samples, height, width, channels),"channels_first" 模式表示輸入尺寸應該為(samples, channels, height, width)。默認為在Keras 配置文件 ~/.keras/keras.json 中的 image_data_format 值。如果你從未設置它,那它就是"channels_last"。
|
||||
validation_split : 浮點數。Float. 保留用於驗證的圖像的比例(嚴格在0和1之間)。
|
||||
dtype : 生成數組使用的數據類型。
|
||||
'''
|
||||
if judge == 1:
|
||||
return transforms.Compose([
|
||||
|
|
@ -164,3 +160,5 @@ class Image_generator():
|
|||
transforms.RandomHorizontalFlip(),
|
||||
transforms.RandomVerticalFlip(),
|
||||
])
|
||||
else:
|
||||
return transforms.ToTensor() # 將數值歸一化到[0, 1]之間
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
import xml.etree.ElementTree as ET
|
||||
import cv2
|
||||
import os
|
||||
import numpy as np
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from utils.Stomach_Config import Loading_Config
|
||||
|
||||
class XMLAnnotationProcessor:
|
||||
"""
|
||||
XML標註檔案處理器
|
||||
專門處理包含bounding box資訊的XML檔案,並在對應圖片上繪製邊界框
|
||||
"""
|
||||
|
||||
def __init__(self, dataset_root: str):
|
||||
"""
|
||||
初始化XML處理器
|
||||
|
||||
Args:
|
||||
dataset_root: 圖片資料集根目錄
|
||||
output_folder: 輸出資料夾
|
||||
"""
|
||||
self.dataset_root = dataset_root
|
||||
self.box_color = (0, 255, 0) # 綠色邊界框
|
||||
self.text_color = (0, 255, 0) # 綠色文字
|
||||
self.box_thickness = 2
|
||||
self.font_scale = 0.5
|
||||
self.font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
|
||||
def _ensure_output_folder(self, Save_Root: str) -> None:
|
||||
"""確保輸出資料夾存在"""
|
||||
if not os.path.exists(Save_Root):
|
||||
os.makedirs(Save_Root)
|
||||
|
||||
def parse_xml(self, xml_file_path: str, Label: str) -> Optional[Dict]:
|
||||
"""
|
||||
解析XML檔案並提取所有相關資訊
|
||||
|
||||
Args:
|
||||
xml_file_path: XML檔案路徑
|
||||
|
||||
Returns:
|
||||
Dict: 包含檔案資訊和bounding box的字典,解析失敗時返回None
|
||||
"""
|
||||
try:
|
||||
tree = ET.parse(xml_file_path)
|
||||
root = tree.getroot()
|
||||
|
||||
# 提取基本資訊
|
||||
filename_element = root.find('filename')
|
||||
|
||||
if filename_element is None:
|
||||
print(f"找不到path元素在 {xml_file_path}")
|
||||
return None
|
||||
|
||||
filename = filename_element.text if filename_element is not None else "Unknown"
|
||||
Original_Image_Data_Root = os.path.join(self.dataset_root, Label)
|
||||
Original_Image_Data_Root = os.path.join(Original_Image_Data_Root, filename)
|
||||
|
||||
# 提取圖片尺寸
|
||||
size_element = root.find('size')
|
||||
width = int(size_element.find('width').text) if size_element is not None else 0
|
||||
height = int(size_element.find('height').text) if size_element is not None else 0
|
||||
depth = int(size_element.find('depth').text) if size_element is not None else 3
|
||||
|
||||
# 提取所有bounding box
|
||||
bounding_boxes = []
|
||||
objects = root.findall('object')
|
||||
|
||||
for obj in objects:
|
||||
bndbox = obj.find('bndbox')
|
||||
if bndbox is not None:
|
||||
bbox_info = {
|
||||
'name': obj.find('name').text if obj.find('name') is not None else "Unknown",
|
||||
'pose': obj.find('pose').text if obj.find('pose') is not None else "Unspecified",
|
||||
'truncated': int(obj.find('truncated').text) if obj.find('truncated') is not None else 0,
|
||||
'difficult': int(obj.find('difficult').text) if obj.find('difficult') is not None else 0,
|
||||
'xmin': int(bndbox.find('xmin').text),
|
||||
'ymin': int(bndbox.find('ymin').text),
|
||||
'xmax': int(bndbox.find('xmax').text),
|
||||
'ymax': int(bndbox.find('ymax').text)
|
||||
}
|
||||
bounding_boxes.append(bbox_info)
|
||||
|
||||
return {
|
||||
'filename': filename,
|
||||
'image_path': Original_Image_Data_Root,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'depth': depth,
|
||||
'bounding_boxes': bounding_boxes
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"解析XML檔案 {xml_file_path} 時發生錯誤: {str(e)}")
|
||||
return None
|
||||
|
||||
def load_image(self, image_path: str) -> Optional[np.ndarray]:
|
||||
"""
|
||||
載入圖片檔案
|
||||
|
||||
Args:
|
||||
image_path: 圖片檔案路徑
|
||||
|
||||
Returns:
|
||||
np.ndarray: 圖片陣列,載入失敗時返回None
|
||||
"""
|
||||
if not os.path.exists(image_path):
|
||||
print(f"圖片檔案不存在: {image_path}")
|
||||
return None
|
||||
|
||||
image = cv2.imread(image_path)
|
||||
if image is None:
|
||||
print(f"無法讀取圖片: {image_path}")
|
||||
return None
|
||||
|
||||
return image
|
||||
|
||||
def draw_bounding_boxes(self, image: np.ndarray, bounding_boxes: List[Dict]) -> np.ndarray:
|
||||
"""
|
||||
創建遮罩圖片:bounding box內保持原圖,外部為黑色
|
||||
|
||||
Args:
|
||||
image: 圖片陣列
|
||||
bounding_boxes: bounding box資訊列表
|
||||
|
||||
Returns:
|
||||
np.ndarray: 處理後的遮罩圖片陣列
|
||||
"""
|
||||
# 創建黑色背景圖片
|
||||
height, width = image.shape[:2]
|
||||
result_image = np.zeros((height, width, 3), dtype=np.uint8)
|
||||
|
||||
for i, bbox in enumerate(bounding_boxes):
|
||||
xmin, ymin = bbox['xmin'], bbox['ymin']
|
||||
xmax, ymax = bbox['xmax'], bbox['ymax']
|
||||
object_name = bbox['name']
|
||||
|
||||
# 確保座標在圖片範圍內
|
||||
xmin = max(0, min(xmin, width-1))
|
||||
ymin = max(0, min(ymin, height-1))
|
||||
xmax = max(0, min(xmax, width-1))
|
||||
ymax = max(0, min(ymax, height-1))
|
||||
|
||||
# 將bounding box範圍內的原圖複製到結果圖像中
|
||||
result_image[ymin:ymax, xmin:xmax] = image[ymin:ymax, xmin:xmax]
|
||||
|
||||
print(f"Object {i+1}: {object_name} - 座標: ({xmin}, {ymin}, {xmax}, {ymax})")
|
||||
|
||||
return result_image
|
||||
|
||||
def save_annotated_image(self, image: np.ndarray, original_filename: str, Annotation_Root : str, Label : str) -> str:
|
||||
"""
|
||||
儲存標註後的圖片
|
||||
|
||||
Args:
|
||||
image: 標註後的圖片陣列
|
||||
original_filename: 原始檔案名稱
|
||||
|
||||
Returns:
|
||||
str: 儲存的檔案路徑
|
||||
"""
|
||||
output_filename = f"annotated_{original_filename}"
|
||||
output_path = os.path.join(Annotation_Root, Label)
|
||||
Save_Image_Roots = os.path.join(output_path, output_filename)
|
||||
# 確保輸出資料夾存在
|
||||
self._ensure_output_folder(output_path)
|
||||
|
||||
cv2.imwrite(Save_Image_Roots, image)
|
||||
print(f"已儲存標註圖片至: {Save_Image_Roots}")
|
||||
return Save_Image_Roots
|
||||
|
||||
def process_single_xml(self, xml_file_path: str, Annotation_Root : str, Label : str) -> Optional[Tuple[np.ndarray, str]]:
|
||||
"""
|
||||
處理單一XML檔案
|
||||
|
||||
Args:
|
||||
xml_file_path: XML檔案路徑
|
||||
|
||||
Returns:
|
||||
Tuple[np.ndarray, str]: (標註後的圖片, 輸出路徑),處理失敗時返回None
|
||||
"""
|
||||
# 解析XML
|
||||
xml_data = self.parse_xml(xml_file_path, Label)
|
||||
if xml_data is None:
|
||||
return None
|
||||
|
||||
# 載入圖片
|
||||
image = self.load_image(xml_data['image_path'])
|
||||
if image is None:
|
||||
return None
|
||||
|
||||
# 繪製bounding box
|
||||
annotated_image = self.draw_bounding_boxes(image, xml_data['bounding_boxes'])
|
||||
|
||||
# 儲存結果
|
||||
output_path = self.save_annotated_image(annotated_image, xml_data['filename'], Annotation_Root, Label)
|
||||
|
||||
return annotated_image, output_path
|
||||
|
||||
def process_multiple_xml(self, xml_folder_path: str, Annotation_Root : str, Label : str) -> List[Tuple[str, bool]]:
|
||||
"""
|
||||
批量處理多個XML檔案
|
||||
|
||||
Args:
|
||||
xml_folder_path: 包含XML檔案的資料夾路徑
|
||||
|
||||
Returns:
|
||||
List[Tuple[str, bool]]: [(檔案名稱, 處理成功與否), ...]
|
||||
"""
|
||||
if not os.path.exists(xml_folder_path):
|
||||
print(f"XML資料夾不存在: {xml_folder_path}")
|
||||
return []
|
||||
|
||||
xml_files = [f for f in os.listdir(xml_folder_path) if f.endswith('.xml')]
|
||||
|
||||
if not xml_files:
|
||||
print(f"在 {xml_folder_path} 中找不到XML檔案")
|
||||
return []
|
||||
|
||||
print(f"找到 {len(xml_files)} 個XML檔案")
|
||||
for xml_file in xml_files:
|
||||
try:
|
||||
Read_XML_File = os.path.join(xml_folder_path, xml_file)
|
||||
self.process_single_xml(Read_XML_File, Annotation_Root, Label)
|
||||
print(f"\n處理檔案: {xml_file}")
|
||||
except Exception as e:
|
||||
print(f"處理 {xml_file} 時發生錯誤: {str(e)}")
|
||||
return
|
||||
|
||||
def get_bounding_boxes_info(self, xml_file_path: str) -> Optional[Dict]:
|
||||
"""
|
||||
僅提取XML中的bounding box資訊,不進行圖片處理
|
||||
|
||||
Args:
|
||||
xml_file_path: XML檔案路徑
|
||||
|
||||
Returns:
|
||||
Dict: 包含檔案資訊和bounding box座標的字典
|
||||
"""
|
||||
return self.parse_xml(xml_file_path)
|
||||
|
||||
def set_drawing_style(self, box_color: Tuple[int, int, int] = None,
|
||||
text_color: Tuple[int, int, int] = None,
|
||||
box_thickness: int = None,
|
||||
font_scale: float = None) -> None:
|
||||
"""
|
||||
設定繪圖樣式
|
||||
|
||||
Args:
|
||||
box_color: 邊界框顏色 (B, G, R)
|
||||
text_color: 文字顏色 (B, G, R)
|
||||
box_thickness: 邊界框粗細
|
||||
font_scale: 字體大小
|
||||
"""
|
||||
if box_color is not None:
|
||||
self.box_color = box_color
|
||||
if text_color is not None:
|
||||
self.text_color = text_color
|
||||
if box_thickness is not None:
|
||||
self.box_thickness = box_thickness
|
||||
if font_scale is not None:
|
||||
self.font_scale = font_scale
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,309 +1,87 @@
|
|||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image
|
||||
import torchvision
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
# 套用裝飾器到現有函數
|
||||
def unsharp_mask(image, kernel_size=(5, 5), sigma=1.0, amount=1.0, threshold=0):
|
||||
"""使用OpenCV實現的Unsharp Mask銳化處理
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
kernel_size: 高斯模糊的核大小,必須是奇數
|
||||
sigma: 高斯模糊的標準差
|
||||
amount: 銳化程度,值越大效果越強
|
||||
threshold: 邊緣檢測閾值,僅在邊緣處進行銳化
|
||||
返回:
|
||||
銳化後的PIL.Image對象
|
||||
"""
|
||||
# 轉換PIL圖像為numpy數組
|
||||
numpy_img = np.array(image, dtype=np.uint8)
|
||||
|
||||
# 對原圖進行高斯模糊
|
||||
blurred = cv2.GaussianBlur(numpy_img, kernel_size, sigma)
|
||||
|
||||
# 計算銳化後的圖像
|
||||
sharpened = cv2.addWeighted(numpy_img, 1 + amount, blurred, -amount, 0)
|
||||
|
||||
# 如果設置了threshold,只在邊緣處應用銳化
|
||||
if threshold > 0:
|
||||
low_contrast_mask = np.absolute(numpy_img - blurred) < threshold
|
||||
np.copyto(sharpened, numpy_img, where=low_contrast_mask)
|
||||
|
||||
# 確保像素值在有效範圍內
|
||||
sharpened = np.clip(sharpened, 0, 255).astype(np.uint8)
|
||||
|
||||
# 轉回PIL圖像
|
||||
return Image.fromarray(sharpened)
|
||||
|
||||
def histogram_equalization(image):
|
||||
"""GPU加速的一般直方圖等化
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
返回:
|
||||
直方圖等化後的PIL.Image對象
|
||||
"""
|
||||
# 轉換為numpy數組並轉為PyTorch張量
|
||||
numpy_img = np.array(image)
|
||||
tensor_img = torch.from_numpy(numpy_img).float().to('cuda')
|
||||
|
||||
# 分離通道並進行直方圖等化
|
||||
result = torch.zeros_like(tensor_img)
|
||||
for i in range(3): # 對RGB三個通道分別處理
|
||||
channel = tensor_img[..., i]
|
||||
|
||||
# 計算直方圖
|
||||
hist = torch.histc(channel, bins=256, min=0, max=255)
|
||||
|
||||
# 計算累積分布函數(CDF)
|
||||
cdf = torch.cumsum(hist, dim=0)
|
||||
cdf_normalized = ((cdf - cdf.min()) * 255) / (cdf.max() - cdf.min())
|
||||
|
||||
# 應用直方圖等化
|
||||
result[..., i] = cdf_normalized[channel.long()]
|
||||
|
||||
# 轉回CPU和numpy數組
|
||||
result = torch.clamp(result, 0, 255).byte()
|
||||
result_np = result.cpu().numpy()
|
||||
return Image.fromarray(result_np)
|
||||
|
||||
def Contrast_Limited_Adaptive_Histogram_Equalization(image, clip_limit=3.0, tile_size=(8, 8)):
|
||||
"""使用OpenCV實現的對比度限制自適應直方圖均衡化(CLAHE)
|
||||
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
clip_limit: 剪切限制,用於限制對比度增強的程度,較大的值會產生更強的對比度
|
||||
tile_size: 圖像分塊大小的元組(height, width),較小的值會產生更局部的增強效果
|
||||
|
||||
返回:
|
||||
CLAHE處理後的PIL.Image對象
|
||||
"""
|
||||
# 將PIL圖像轉換為OpenCV格式(BGR)
|
||||
numpy_img = np.array(image)
|
||||
bgr_img = cv2.cvtColor(numpy_img, cv2.COLOR_RGB2BGR)
|
||||
|
||||
# 轉換到LAB色彩空間
|
||||
lab_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2LAB)
|
||||
|
||||
# 創建CLAHE對象
|
||||
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_size)
|
||||
|
||||
# 分離LAB通道
|
||||
l, a, b = cv2.split(lab_img)
|
||||
|
||||
# 對L通道應用CLAHE
|
||||
l_clahe = clahe.apply(l)
|
||||
|
||||
# 合併處理後的L通道與原始的a和b通道
|
||||
lab_output = cv2.merge([l_clahe, a, b])
|
||||
|
||||
# 將LAB轉回BGR,然後轉換為RGB
|
||||
bgr_output = cv2.cvtColor(lab_output, cv2.COLOR_LAB2BGR)
|
||||
rgb_output = cv2.cvtColor(bgr_output, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# 轉換為PIL圖像並返回
|
||||
return Image.fromarray(rgb_output)
|
||||
|
||||
def adaptive_histogram_equalization_without_limit(image, tile_size=(8, 8)):
|
||||
"""使用OpenCV實現的自適應直方圖均衡化(AHE)
|
||||
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
tile_size: 圖像分塊大小的元組(height, width),較小的值會產生更局部的增強效果
|
||||
|
||||
返回:
|
||||
AHE處理後的PIL.Image對象
|
||||
"""
|
||||
# 將PIL圖像轉換為OpenCV格式(BGR)
|
||||
numpy_img = np.array(image)
|
||||
bgr_img = cv2.cvtColor(numpy_img, cv2.COLOR_RGB2BGR)
|
||||
|
||||
# 轉換到LAB色彩空間
|
||||
lab_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2LAB)
|
||||
|
||||
# 分離LAB通道
|
||||
l, a, b = cv2.split(lab_img)
|
||||
|
||||
# 創建AHE對象(不設置clip limit)
|
||||
clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=tile_size)
|
||||
|
||||
# 對L通道應用AHE
|
||||
l_ahe = clahe.apply(l)
|
||||
|
||||
# 合併處理後的L通道與原始的a和b通道
|
||||
lab_output = cv2.merge([l_ahe, a, b])
|
||||
|
||||
# 將LAB轉回BGR,然後轉換為RGB
|
||||
bgr_output = cv2.cvtColor(lab_output, cv2.COLOR_LAB2BGR)
|
||||
rgb_output = cv2.cvtColor(bgr_output, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# 轉換為PIL圖像並返回
|
||||
return Image.fromarray(rgb_output)
|
||||
|
||||
def laplacian_sharpen(image):
|
||||
"""
|
||||
GPU加速的拉普拉斯銳化處理函數
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
返回:
|
||||
銳化後的PIL.Image對象
|
||||
"""
|
||||
# 轉換為numpy數組並轉為PyTorch張量
|
||||
numpy_img = np.array(image)
|
||||
tensor_img = torch.from_numpy(numpy_img).float().to('cuda')
|
||||
|
||||
# 創建拉普拉斯算子
|
||||
laplacian_kernel = torch.tensor([
|
||||
[0, 1, 0],
|
||||
[1, -4, 1],
|
||||
[0, 1, 0]
|
||||
], dtype=torch.float32, device='cuda').unsqueeze(0).unsqueeze(0)
|
||||
|
||||
# 對每個通道進行處理
|
||||
result = torch.zeros_like(tensor_img)
|
||||
for i in range(3): # RGB三個通道
|
||||
channel = tensor_img[..., i]
|
||||
# 添加批次和通道維度
|
||||
channel = channel.unsqueeze(0).unsqueeze(0)
|
||||
# 應用拉普拉斯算子
|
||||
laplacian = torch.nn.functional.conv2d(channel, laplacian_kernel, padding=1)
|
||||
# 移除批次和通道維度
|
||||
laplacian = laplacian.squeeze()
|
||||
# 銳化處理:原圖 - 拉普拉斯
|
||||
result[..., i] = channel.squeeze() - laplacian
|
||||
|
||||
# 確保像素值在合理範圍內
|
||||
result = torch.clamp(result, 0, 255).byte()
|
||||
|
||||
# 轉回CPU和numpy數組
|
||||
result_np = result.cpu().numpy()
|
||||
return Image.fromarray(result_np)
|
||||
|
||||
def adjust_hsv(image, v_adjustment=0):
|
||||
"""調整圖像的HSV色彩空間中的H和V通道
|
||||
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
v_adjustment: V通道的調整值,範圍建議在[-255, 255]之間
|
||||
|
||||
返回:
|
||||
HSV調整後的PIL.Image對象
|
||||
"""
|
||||
# 將PIL圖像轉換為OpenCV格式(BGR)
|
||||
numpy_img = np.array(image)
|
||||
bgr_img = cv2.cvtColor(numpy_img, cv2.COLOR_RGB2BGR)
|
||||
|
||||
# 轉換到HSV色彩空間
|
||||
hsv_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# 調整V通道
|
||||
hsv_img[..., 2] = np.clip(hsv_img[..., 2] + v_adjustment, 0, 255)
|
||||
|
||||
# 將HSV轉回BGR,然後轉換為RGB
|
||||
bgr_output = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2BGR)
|
||||
rgb_output = cv2.cvtColor(bgr_output, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# 轉換為PIL圖像並返回
|
||||
return Image.fromarray(rgb_output)
|
||||
|
||||
def gamma_correction(image, gamma=1.0):
|
||||
"""對圖像進行伽馬校正
|
||||
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
gamma: 伽馬值,gamma > 1 時圖像變暗,gamma < 1 時圖像變亮,gamma = 1 時保持不變
|
||||
|
||||
返回:
|
||||
伽馬校正後的PIL.Image對象
|
||||
"""
|
||||
# 將PIL圖像轉換為numpy數組
|
||||
numpy_img = np.array(image)
|
||||
|
||||
# 將像素值歸一化到[0, 1]範圍
|
||||
normalized = numpy_img.astype(float) / 255.0
|
||||
|
||||
# 應用伽馬校正
|
||||
corrected = np.power(normalized, gamma)
|
||||
|
||||
# 將值縮放回[0, 255]範圍
|
||||
output = np.clip(corrected * 255.0, 0, 255).astype(np.uint8)
|
||||
|
||||
# 轉換回PIL圖像並返回
|
||||
return Image.fromarray(output)
|
||||
|
||||
def Hight_Light(image, Threshold):
|
||||
image = np.array(image)
|
||||
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
||||
# 使用閾值檢測高光點(白色液滴)
|
||||
_, thresh = cv2.threshold(gray, Threshold, 255, cv2.THRESH_BINARY)
|
||||
# 使用形態學操作(膨脹)來擴大遮罩區域
|
||||
kernel = np.ones((5, 5), np.uint8)
|
||||
dilated = cv2.dilate(thresh, kernel, iterations=1)
|
||||
# 使用 inpaint 修復高光點
|
||||
image_inpaint = cv2.inpaint(image, dilated, 3, cv2.INPAINT_TELEA)
|
||||
|
||||
return Image.fromarray(image_inpaint)
|
||||
|
||||
def median_filter(image: Image.Image, kernel_size: int = 3):
|
||||
"""
|
||||
中值濾波(Median Filter)實現
|
||||
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
kernel_size: 濾波核大小,必須是奇數
|
||||
|
||||
返回:
|
||||
濾波後的PIL.Image對象
|
||||
"""
|
||||
# 確保kernel_size是奇數
|
||||
if kernel_size % 2 == 0:
|
||||
kernel_size += 1
|
||||
|
||||
# 轉換PIL圖像為numpy數組
|
||||
numpy_img = np.array(image, dtype=np.uint8)
|
||||
|
||||
# 對每個通道應用中值濾波
|
||||
result = np.zeros_like(numpy_img)
|
||||
for i in range(3): # 對RGB三個通道分別處理
|
||||
result[:, :, i] = cv2.medianBlur(numpy_img[:, :, i], kernel_size)
|
||||
|
||||
# 確保像素值在有效範圍內
|
||||
result = np.clip(result, 0, 255).astype(np.uint8)
|
||||
|
||||
# 轉回PIL圖像
|
||||
return Image.fromarray(result)
|
||||
|
||||
def mean_filter(image: Image.Image, kernel_size: int = 3):
|
||||
"""
|
||||
均質濾波(Mean Filter)實現
|
||||
|
||||
參數:
|
||||
image: PIL.Image對象(RGB格式)
|
||||
kernel_size: 濾波核大小,必須是奇數
|
||||
|
||||
返回:
|
||||
濾波後的PIL.Image對象
|
||||
"""
|
||||
# 確保kernel_size是奇數
|
||||
if kernel_size % 2 == 0:
|
||||
kernel_size += 1
|
||||
|
||||
# 轉換PIL圖像為numpy數組
|
||||
numpy_img = np.array(image, dtype=np.uint8)
|
||||
|
||||
# 創建均質濾波核(所有元素都是1/(kernel_size*kernel_size))
|
||||
kernel = np.ones((kernel_size, kernel_size), np.float32) / (kernel_size * kernel_size)
|
||||
|
||||
# 對每個通道應用均質濾波
|
||||
result = np.zeros_like(numpy_img)
|
||||
for i in range(3): # 對RGB三個通道分別處理
|
||||
result[:, :, i] = cv2.filter2D(numpy_img[:, :, i], -1, kernel)
|
||||
|
||||
# 確保像素值在有效範圍內
|
||||
result = np.clip(result, 0, 255).astype(np.uint8)
|
||||
|
||||
# 轉回PIL圖像
|
||||
return Image.fromarray(result)
|
||||
def shapen(image): # 銳化處理
|
||||
sigma = 100
|
||||
blur_img = cv2.GaussianBlur(image, (0, 0), sigma)
|
||||
usm = cv2.addWeighted(image, 1.5, blur_img, -0.5, 0)
|
||||
|
||||
return usm
|
||||
|
||||
def increase_contrast(image): # 增加資料對比度
|
||||
output = image # 建立 output 變數
|
||||
alpha = 2
|
||||
beta = 10
|
||||
cv2.convertScaleAbs(image, output, alpha, beta) # 套用 convertScaleAbs
|
||||
|
||||
return output
|
||||
|
||||
def adaptive_histogram_equalization(image):
|
||||
ycrcb = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)
|
||||
channels = cv2.split(ycrcb)
|
||||
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
|
||||
clahe.apply(channels[0], channels[0])
|
||||
|
||||
ycrcb = cv2.merge(channels)
|
||||
Change_image = cv2.cvtColor(ycrcb, cv2.COLOR_YCR_CB2BGR)
|
||||
|
||||
return Change_image
|
||||
|
||||
def Remove_Background(image, Matrix_Size):
|
||||
skinCrCbHist = np.zeros((256,256), dtype= np.uint8)
|
||||
cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255, 255, 255), -1) #繪製橢圓弧線
|
||||
|
||||
img_ycrcb = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)
|
||||
y,cr,cb = cv2.split(img_ycrcb) #拆分出Y,Cr,Cb值
|
||||
|
||||
skin = np.zeros(cr.shape, dtype = np.uint8) #掩膜
|
||||
(x,y) = cr.shape
|
||||
|
||||
# 依序取出圖片中每個像素
|
||||
for i in range(x):
|
||||
for j in range(y):
|
||||
if skinCrCbHist [cr[i][j], cb[i][j]] > 0: #若不在橢圓區間中
|
||||
skin[i][j] = 255
|
||||
# 如果該像素的灰階度大於 200,調整該像素的透明度
|
||||
# 使用 255 - gray[y, x] 可以將一些邊緣的像素變成半透明,避免太過鋸齒的邊緣
|
||||
# img_change = cv2.cvtColor(img_change, cv2.COLOR_BGRA2BGR)
|
||||
img = cv2.bitwise_and(image, image, mask = skin)
|
||||
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
h = image.shape[0] # 取得圖片高度
|
||||
w = image.shape[1] # 取得圖片寬度
|
||||
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
if img_gray[y, x] == 0:
|
||||
# if x == 0 and y == 0: # 當X Y都在左上角時
|
||||
# image[y, x] = Add(1, Matrix_Size, image[y, x]) / Matrix_Size
|
||||
# if x == w - 1 and y == 0: # 當X Y都在右上角時
|
||||
# image[y, x] = Add(w - Matrix_Size, w, image[y, x]) / Matrix_Size
|
||||
# if x == 0 and y == h - 1: # 當X Y都在左下角時
|
||||
# image[y, x] = (image[y - 1, x] + image[y - 1, x + 1] + image[y, x + 1]) / 3
|
||||
# if x == w - 1 and y == h - 1: # 當X Y都在右下角時
|
||||
# image[y, x] = (image[y, x - 1] + image[y - 1, x - 1] + image[y - 1, x]) / 3
|
||||
|
||||
# if (x > 0 and x < w - 1) and y == 0: # 當上面的X Y從左到右
|
||||
# image[y, x] = (image[y, x - 1] + image[y + 1, x - 1] + image[y + 1, x] + image[y, x + 1] + image[y + 1, x + 1]) / 5
|
||||
# if (x > 0 and x < w - 1) and y == h - 1: # 當下面的X Y從左到右
|
||||
# image[y, x] = (image[y, x - 1] + image[y - 1, x - 1] + image[y - 1, x] + image[y, x + 1] + image[y - 1, x + 1]) / 5
|
||||
# if x == 0 and (y > 0 and y < h - 1): # 當左邊的X Y從上到下
|
||||
# image[y, x] = (image[y - 1, x] + image[y - 1, x + 1] + image[y, x + 1] + image[y + 1, x + 1] + image[y + 1, x]) / 5
|
||||
# if x == w - 1 and (y > 0 and y < h - 1): # 當右邊X Y從上到下
|
||||
# image[y, x] = (image[y - 1, x] + image[y - 1, x - 1] + image[y, x - 1] + image[y + 1, x - 1] + image[y + 1, x]) / 5
|
||||
|
||||
if (x >= 1 and x < w - 1) and (y >= 1 and y < h - 1): # 當y >= 2 且 X >= 2
|
||||
image[y, x] = Add(x, y, image, Matrix_Size) / Matrix_Size
|
||||
# BGRA_image[y, x, 3] = 255 - gray[y, x]
|
||||
return image
|
||||
|
||||
|
||||
def Add(width_Center, Height_Center, image, Mask_Size):
|
||||
total = 0
|
||||
for i in range(Mask_Size):
|
||||
for j in range(Mask_Size):
|
||||
total += image[width_Center - ((Mask_Size - 1) / 2) + j, Height_Center - ((Mask_Size - 1) / 2) + i]
|
||||
|
||||
return total
|
||||
63
Image_Process/load_and_ImageGenerator.py
Normal file
63
Image_Process/load_and_ImageGenerator.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from Load_process.LoadData import Loding_Data_Root
|
||||
from Image_Process.Image_Generator import Image_generator
|
||||
from Load_process.file_processing import Process_File
|
||||
from model_data_processing.processing_for_cut_image import Cut_Indepentend_Data
|
||||
from Load_process.Loading_Tools import Load_Data_Prepare, Load_Data_Tools
|
||||
|
||||
class Load_ImageGenerator():
|
||||
'''
|
||||
這是一個拿來進行資料強化的物件,最主要結合了學姊給的資料強化與我自行設定的資料強化。
|
||||
藉由此物件先將資料讀取出來,並將資料分別進行資料強化,利用資料強化來迷部資料的不平衡
|
||||
這只是其中一個實驗
|
||||
|
||||
Parmeter
|
||||
standard_root: 做跟學姊給的資料強化同一種的資料強化
|
||||
myself_root: 資料強化的內容參數是我自己設定的
|
||||
IndependentDataRoot: 要存回去的資料夾路徑
|
||||
Herpeslabels: 皰疹的類別
|
||||
MonKeyPoxlabels: 猴痘的類別(猴痘、水痘、正常)
|
||||
herpes_data: 合併herpes Dataset的資料成一個List
|
||||
MonkeyPox_data: 合併MonkeyPox DataSet 的資料成一個List
|
||||
'''
|
||||
def __init__(self, Training_Root,Test_Root, Validation_Root, Generator_Root, Labels, Image_Size) -> None:
|
||||
self.Training_Root = Training_Root
|
||||
self.TestRoot = Test_Root
|
||||
self.ValidationRoot = Validation_Root
|
||||
self.GeneratoRoot = Generator_Root
|
||||
self.Labels = Labels
|
||||
self.Image_Size = Image_Size
|
||||
pass
|
||||
|
||||
def process_main(self, Data_Length : int):
|
||||
File = Process_File()
|
||||
Prepare = Load_Data_Prepare()
|
||||
load = Loding_Data_Root(self.Labels, self.Training_Root, self.GeneratoRoot)
|
||||
Indepentend = Cut_Indepentend_Data(self.Training_Root, self.Labels)
|
||||
Load_Tool = Load_Data_Tools()
|
||||
Generator = Image_generator(self.GeneratoRoot, self.Labels, self.Image_Size)
|
||||
|
||||
# 將測試資料獨立出來
|
||||
test_size = 0.1
|
||||
Indepentend.IndependentData_main(self.TestRoot, test_size)
|
||||
|
||||
# 將驗證資料獨立出來
|
||||
test_size = 0.1
|
||||
Indepentend.IndependentData_main(self.ValidationRoot, test_size)
|
||||
|
||||
if not File.Judge_File_Exist(self.GeneratoRoot): # 檔案若不存在
|
||||
# 確定我要多少個List
|
||||
Prepare.Set_Data_Content([], Data_Length)
|
||||
|
||||
# 製作讀檔字典並回傳檔案路徑
|
||||
Prepare.Set_Label_List(self.Labels)
|
||||
Prepare.Set_Data_Dictionary(Prepare.Get_Label_List(), Prepare.Get_Data_Content(), Data_Length)
|
||||
Original_Dict_Data_Root = Prepare.Get_Data_Dict()
|
||||
get_all_original_image_data = Load_Tool.get_data_root(self.Training_Root, Original_Dict_Data_Root, Prepare.Get_Label_List())
|
||||
|
||||
# 儲存資料強化後資料
|
||||
Generator.Processing_Main(get_all_original_image_data) # 執行資料強化
|
||||
else: # 若檔案存在
|
||||
print("standard data and myself data are exist\n")
|
||||
|
||||
# 執行讀檔
|
||||
return load.process_main()
|
||||
|
|
@ -11,18 +11,16 @@ class Loding_Data_Root(Process_File):
|
|||
super().__init__()
|
||||
pass
|
||||
|
||||
def process_main(self, status):
|
||||
def process_main(self):
|
||||
'''處理讀Training、Image Generator檔資料'''
|
||||
Merge = merge()
|
||||
|
||||
get_Image_Data = self.get_Image_data_roots(self.Train_Root)
|
||||
Get_ImageGenerator_Image_Data = self.get_Image_data_roots(self.Generator_Root)
|
||||
|
||||
if status:
|
||||
Get_ImageGenerator_Image_Data = self.get_Image_data_roots(self.Generator_Root)
|
||||
Get_Total_Image_Data_Root = Merge.merge_dict_to_dict(get_Image_Data, Get_ImageGenerator_Image_Data)
|
||||
return Get_Total_Image_Data_Root
|
||||
Get_Total_Image_Data_Root = Merge.merge_dict_to_dict(get_Image_Data, Get_ImageGenerator_Image_Data)
|
||||
|
||||
return get_Image_Data
|
||||
return Get_Total_Image_Data_Root
|
||||
|
||||
def get_Image_data_roots(self, DataRoot) -> dict:
|
||||
Prepare = Load_Data_Prepare()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image
|
||||
from merge_class.merge import merge
|
||||
from Read_and_process_image.ReadAndProcess import Read_image_and_Process_image
|
||||
from Load_process.LoadData import Load_Data_Prepare, Load_Data_Tools
|
||||
from model_data_processing.processing import make_label_list
|
||||
from utils.Stomach_Config import Loading_Config
|
||||
from model_data_processing.processing import Balance_Process
|
||||
|
||||
class Load_Indepentend_Data():
|
||||
def __init__(self, OneHot_Encording):
|
||||
def __init__(self, Labels, OneHot_Encording):
|
||||
'''
|
||||
影像切割物件
|
||||
label有2類,會將其轉成one-hot-encoding的形式
|
||||
|
|
@ -12,62 +13,55 @@ class Load_Indepentend_Data():
|
|||
[1, 0] = NPC_positive
|
||||
'''
|
||||
self.merge = merge()
|
||||
self.Labels = Labels
|
||||
self.OneHot_Encording = OneHot_Encording
|
||||
pass
|
||||
|
||||
def process_main(self, Test_data_root, Test_mask_root):
|
||||
self.test, self.test_label, self.test_mask = self.get_Independent_image(Test_data_root, Test_mask_root)
|
||||
def process_main(self, Test_data_root, Validation_data_root):
|
||||
self.test, self.test_label = self.get_Independent_image(Test_data_root)
|
||||
print("\ntest_labels有" + str(len(self.test_label)) + "筆資料\n")
|
||||
|
||||
def get_Independent_image(self, independent_DataRoot, independent_MaskRoot):
|
||||
Total_Size_List = []
|
||||
Total_Dict_Data_Root = self.Get_Independent_data_Root(independent_DataRoot, Loading_Config["Training_Labels"], len(Loading_Config["Training_Labels"])) # 讀取測試資料集的資料
|
||||
Total_Dict_Mask_Root = self.Get_Independent_data_Root(independent_MaskRoot, Loading_Config["XML_Loading_Label"], len(Loading_Config["XML_Loading_Label"])) # 讀取測試資料集的mask資料
|
||||
# self.validation, self.validation_label = self.get_Independent_image(Validation_data_root)
|
||||
# print("validation_labels有 " + str(len(self.validation_label)) + " 筆資料\n")
|
||||
|
||||
def get_Independent_image(self, independent_DataRoot):
|
||||
image_processing = Read_image_and_Process_image(123)
|
||||
|
||||
classify_image = []
|
||||
Total_Dict_Data_Root = self.Get_Independent_data_Root(independent_DataRoot) # 讀取測試資料集的資料
|
||||
Total_Dict_Data_Root, Size = Balance_Process(Total_Dict_Data_Root, self.Labels) # 打亂並取出指定資料筆數的資料
|
||||
|
||||
# 將測試資料字典轉成列表,並且將其排序
|
||||
Total_List_Data_Root = []
|
||||
for Label in Loading_Config["Training_Labels"]:
|
||||
for Label in self.Labels:
|
||||
Total_List_Data_Root.append(Total_Dict_Data_Root[Label])
|
||||
|
||||
# 將測試資料字典轉成列表,並且將其排序
|
||||
Total_List_Mask_Data_Root = []
|
||||
for Label in Loading_Config["XML_Loading_Label"]:
|
||||
Total_List_Mask_Data_Root.append(Total_Dict_Mask_Root[Label])
|
||||
|
||||
classify_image, Classify_Label = [], []
|
||||
test_label, Classify_Label = [], []
|
||||
i = 0 # 計算classify_image的counter,且計算總共有幾筆資料
|
||||
for test_title in Total_List_Data_Root: # 藉由讀取所有路徑來進行讀檔
|
||||
test_label = make_label_list(len(test_title), self.OneHot_Encording[i]) # 製作對應圖片數量的label出來+
|
||||
print(Loading_Config["Training_Labels"][i] + " 有 " + str(len(test_label)) + " 筆資料 ")
|
||||
|
||||
Total_Size_List.append(len(test_label))
|
||||
test_label = image_processing.make_label_list(len(test_title), self.OneHot_Encording[i]) # 製作對應圖片數量的label出來+
|
||||
print(self.Labels[i] + " 有 " + str(len(test_label)) + " 筆資料 ")
|
||||
|
||||
classify_image.append(test_title)
|
||||
Classify_Label.append(test_label)
|
||||
i += 1
|
||||
|
||||
classify_Mask_image = []
|
||||
i = 0 # 計算classify_image的counter,且計算總共有幾筆資料
|
||||
for test_title in Total_List_Mask_Data_Root: # 藉由讀取所有路徑來進行讀檔
|
||||
print(Loading_Config["XML_Loading_Label"][i] + " 有 " + str(len(test_title)) + " 筆資料 ")
|
||||
original_test_root = self.merge.merge_data_main(classify_image, 0)
|
||||
original_test_label = self.merge.merge_data_main(Classify_Label, 0)
|
||||
|
||||
classify_Mask_image.append(test_title)
|
||||
i += 1
|
||||
test = []
|
||||
test = image_processing.Data_Augmentation_Image(original_test_root)
|
||||
test, test_label = image_processing.image_data_processing(test, original_test_label)
|
||||
|
||||
test = self.merge.merge_data_main(classify_image, 0, len(Loading_Config["Training_Labels"]))
|
||||
test_label = self.merge.merge_data_main(Classify_Label, 0, len(Loading_Config["Training_Labels"]))
|
||||
test_Mask = self.merge.merge_data_main(classify_Mask_image, 0, len(Loading_Config["XML_Loading_Label"]))
|
||||
|
||||
return test, test_label, test_Mask
|
||||
return test, test_label
|
||||
|
||||
|
||||
def Get_Independent_data_Root(self, load_data_root, Dictory_Keys, Length):
|
||||
def Get_Independent_data_Root(self, load_data_root):
|
||||
Prepare = Load_Data_Prepare()
|
||||
Load_Tool = Load_Data_Tools()
|
||||
|
||||
Prepare.Set_Data_Content([], Length)
|
||||
Prepare.Set_Data_Dictionary(Dictory_Keys, Prepare.Get_Data_Content(), Length)
|
||||
Prepare.Set_Data_Content([], len(self.Labels))
|
||||
Prepare.Set_Data_Dictionary(self.Labels, Prepare.Get_Data_Content(), 2)
|
||||
Get_Data_Dict_Content = Prepare.Get_Data_Dict()
|
||||
Total_Data_Roots = Load_Tool.get_data_root(load_data_root, Get_Data_Dict_Content, Dictory_Keys)
|
||||
Total_Data_Roots = Load_Tool.get_data_root(load_data_root, Get_Data_Dict_Content, self.Labels)
|
||||
|
||||
return Total_Data_Roots
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -35,14 +35,16 @@ class Process_File():
|
|||
save_root = self.Make_Save_Root(FileName, save_root)
|
||||
np.save(save_root, image)
|
||||
|
||||
def Save_CSV_File(self, Save_Root, file_name, data): # 儲存訓練結果
|
||||
def Save_CSV_File(self, file_name, data): # 儲存訓練結果
|
||||
Save_Root = '../Result/save_the_train_result(' + str(datetime.date.today()) + ")"
|
||||
self.JudgeRoot_MakeDir(Save_Root)
|
||||
modelfiles = self.Make_Save_Root(file_name + ".csv", Save_Root) # 將檔案名稱及路徑字串合併成完整路徑
|
||||
data.to_csv(modelfiles, mode = "a")
|
||||
|
||||
def Save_TXT_File(self, content, Save_Root, File_Name):
|
||||
self.JudgeRoot_MakeDir(Save_Root)
|
||||
modelfiles = self.Make_Save_Root(f"{File_Name}.txt", Save_Root) # 將檔案名稱及路徑字串合併成完整路徑
|
||||
def Save_TXT_File(self, content, File_Name):
|
||||
model_dir = '../Result/save_the_train_result(' + str(datetime.date.today()) + ")" # 儲存的檔案路徑,由save_the_train_result + 當天日期
|
||||
self.JudgeRoot_MakeDir(model_dir)
|
||||
modelfiles = self.Make_Save_Root(File_Name + ".txt", model_dir) # 將檔案名稱及路徑字串合併成完整路徑
|
||||
with open(modelfiles, mode = 'a') as file:
|
||||
file.write(content)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,315 +0,0 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import math
|
||||
|
||||
class CIOULoss(nn.Module):
|
||||
"""
|
||||
Complete Intersection over Union (CIOU) Loss
|
||||
適用於目標檢測中的邊界框回歸任務
|
||||
|
||||
CIOU Loss 考慮了三個幾何因子:
|
||||
1. 重疊面積 (Overlap area)
|
||||
2. 中心點距離 (Central point distance)
|
||||
3. 長寬比一致性 (Aspect ratio consistency)
|
||||
"""
|
||||
|
||||
def __init__(self, eps=1e-7):
|
||||
super(CIOULoss, self).__init__()
|
||||
self.eps = eps
|
||||
|
||||
def forward(self, pred_boxes, target_boxes):
|
||||
"""
|
||||
計算 CIOU Loss
|
||||
|
||||
Args:
|
||||
pred_boxes: 預測邊界框 [N, 4] (x1, y1, x2, y2) 或 [N, 4] (cx, cy, w, h) 或分割掩碼 [B, 1, H, W]
|
||||
target_boxes: 真實邊界框 [N, 4] (x1, y1, x2, y2) 或 [N, 4] (cx, cy, w, h) 或分割掩碼 [B, 1, H, W]
|
||||
|
||||
Returns:
|
||||
CIOU loss value
|
||||
"""
|
||||
# 檢查輸入是否為分割掩碼格式
|
||||
if len(pred_boxes.shape) == 4 and pred_boxes.shape[1] == 1:
|
||||
# 將分割掩碼轉換為邊界框格式
|
||||
pred_boxes = self._mask_to_boxes(pred_boxes)
|
||||
target_boxes = self._mask_to_boxes(target_boxes)
|
||||
|
||||
# 如果無法從掩碼中提取有效的邊界框,則返回一個小的損失值
|
||||
if pred_boxes is None or target_boxes is None:
|
||||
return torch.tensor(0.01, device=pred_boxes.device if pred_boxes is not None else target_boxes.device)
|
||||
|
||||
# 確保輸入為浮點數
|
||||
pred_boxes = pred_boxes.float()
|
||||
target_boxes = target_boxes.float()
|
||||
|
||||
# 檢查邊界框維度是否正確
|
||||
if pred_boxes.dim() == 1:
|
||||
# 如果是單個邊界框,擴展為批次大小為1的張量
|
||||
pred_boxes = pred_boxes.unsqueeze(0)
|
||||
if target_boxes.dim() == 1:
|
||||
target_boxes = target_boxes.unsqueeze(0)
|
||||
|
||||
# 確保邊界框有4個坐標
|
||||
if pred_boxes.shape[1] != 4 or target_boxes.shape[1] != 4:
|
||||
# 如果坐標數量不正確,返回一個小的損失值
|
||||
return torch.tensor(0.01, device=pred_boxes.device)
|
||||
|
||||
# 如果輸入是 (cx, cy, w, h) 格式,轉換為 (x1, y1, x2, y2)
|
||||
if self._is_center_format(pred_boxes, target_boxes):
|
||||
pred_boxes = self._center_to_corner(pred_boxes)
|
||||
target_boxes = self._center_to_corner(target_boxes)
|
||||
|
||||
# 計算交集區域
|
||||
intersection = self._calculate_intersection(pred_boxes, target_boxes)
|
||||
|
||||
# 計算各自的面積
|
||||
pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1])
|
||||
target_area = (target_boxes[:, 2] - target_boxes[:, 0]) * (target_boxes[:, 3] - target_boxes[:, 1])
|
||||
|
||||
# 計算聯集面積
|
||||
union = pred_area + target_area - intersection + self.eps
|
||||
|
||||
# 計算 IoU
|
||||
iou = intersection / union
|
||||
|
||||
# 計算最小外接矩形
|
||||
enclose_x1 = torch.min(pred_boxes[:, 0], target_boxes[:, 0])
|
||||
enclose_y1 = torch.min(pred_boxes[:, 1], target_boxes[:, 1])
|
||||
enclose_x2 = torch.max(pred_boxes[:, 2], target_boxes[:, 2])
|
||||
enclose_y2 = torch.max(pred_boxes[:, 3], target_boxes[:, 3])
|
||||
|
||||
# 計算最小外接矩形的對角線距離平方
|
||||
enclose_diagonal_sq = (enclose_x2 - enclose_x1) ** 2 + (enclose_y2 - enclose_y1) ** 2 + self.eps
|
||||
|
||||
# 計算兩個邊界框中心點之間的距離平方
|
||||
pred_center_x = (pred_boxes[:, 0] + pred_boxes[:, 2]) / 2
|
||||
pred_center_y = (pred_boxes[:, 1] + pred_boxes[:, 3]) / 2
|
||||
target_center_x = (target_boxes[:, 0] + target_boxes[:, 2]) / 2
|
||||
target_center_y = (target_boxes[:, 1] + target_boxes[:, 3]) / 2
|
||||
|
||||
center_distance_sq = (pred_center_x - target_center_x) ** 2 + (pred_center_y - target_center_y) ** 2
|
||||
|
||||
# 計算長寬比一致性項
|
||||
pred_w = pred_boxes[:, 2] - pred_boxes[:, 0]
|
||||
pred_h = pred_boxes[:, 3] - pred_boxes[:, 1]
|
||||
target_w = target_boxes[:, 2] - target_boxes[:, 0]
|
||||
target_h = target_boxes[:, 3] - target_boxes[:, 1]
|
||||
|
||||
# 避免除零
|
||||
pred_w = torch.clamp(pred_w, min=self.eps)
|
||||
pred_h = torch.clamp(pred_h, min=self.eps)
|
||||
target_w = torch.clamp(target_w, min=self.eps)
|
||||
target_h = torch.clamp(target_h, min=self.eps)
|
||||
|
||||
v = (4 / (math.pi ** 2)) * torch.pow(torch.atan(target_w / target_h) - torch.atan(pred_w / pred_h), 2)
|
||||
|
||||
# 計算 alpha 參數
|
||||
with torch.no_grad():
|
||||
alpha = v / (1 - iou + v + self.eps)
|
||||
|
||||
# 計算 CIOU
|
||||
ciou = iou - (center_distance_sq / enclose_diagonal_sq) - alpha * v
|
||||
|
||||
# 返回 CIOU Loss (1 - CIOU)
|
||||
ciou_loss = 1 - ciou
|
||||
|
||||
return ciou_loss.mean()
|
||||
|
||||
def _is_center_format(self, pred_boxes, target_boxes):
|
||||
"""
|
||||
判斷輸入格式是否為中心點格式 (cx, cy, w, h)
|
||||
簡單的啟發式判斷:如果第三、四列的值都是正數且相對較小,可能是寬高
|
||||
"""
|
||||
# 這裡使用簡單的判斷邏輯,實際使用時可能需要更精確的判斷
|
||||
return False # 預設假設輸入為 (x1, y1, x2, y2) 格式
|
||||
|
||||
def _center_to_corner(self, boxes):
|
||||
"""
|
||||
將中心點格式 (cx, cy, w, h) 轉換為角點格式 (x1, y1, x2, y2)
|
||||
"""
|
||||
cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
|
||||
x1 = cx - w / 2
|
||||
y1 = cy - h / 2
|
||||
x2 = cx + w / 2
|
||||
y2 = cy + h / 2
|
||||
return torch.stack([x1, y1, x2, y2], dim=1)
|
||||
|
||||
def _mask_to_boxes(self, masks):
|
||||
"""
|
||||
將分割掩碼轉換為邊界框格式 [N, 4] (x1, y1, x2, y2)
|
||||
|
||||
Args:
|
||||
masks: 分割掩碼 [B, 1, H, W]
|
||||
|
||||
Returns:
|
||||
boxes: 邊界框 [B, 4] (x1, y1, x2, y2)
|
||||
"""
|
||||
batch_size = masks.size(0)
|
||||
device = masks.device
|
||||
|
||||
# 將掩碼轉換為二值掩碼
|
||||
binary_masks = (torch.sigmoid(masks) > 0.5).float()
|
||||
|
||||
# 初始化邊界框張量
|
||||
boxes = torch.zeros(batch_size, 4, device=device)
|
||||
|
||||
# 對每個批次處理
|
||||
for b in range(batch_size):
|
||||
mask = binary_masks[b, 0] # [H, W]
|
||||
|
||||
# 找出非零元素的索引
|
||||
non_zero_indices = torch.nonzero(mask, as_tuple=True)
|
||||
|
||||
# 如果掩碼中沒有非零元素,則使用默認的小邊界框
|
||||
if len(non_zero_indices[0]) == 0:
|
||||
# 返回一個默認的小邊界框
|
||||
boxes[b] = torch.tensor([0, 0, 1, 1], device=device)
|
||||
continue
|
||||
|
||||
# 計算邊界框坐標
|
||||
y_min = torch.min(non_zero_indices[0])
|
||||
y_max = torch.max(non_zero_indices[0])
|
||||
x_min = torch.min(non_zero_indices[1])
|
||||
x_max = torch.max(non_zero_indices[1])
|
||||
|
||||
# 存儲邊界框 [x1, y1, x2, y2]
|
||||
boxes[b] = torch.tensor([x_min, y_min, x_max, y_max], device=device)
|
||||
|
||||
return boxes
|
||||
|
||||
def _calculate_intersection(self, pred_boxes, target_boxes):
|
||||
"""
|
||||
計算兩個邊界框的交集面積
|
||||
"""
|
||||
x1 = torch.max(pred_boxes[:, 0], target_boxes[:, 0])
|
||||
y1 = torch.max(pred_boxes[:, 1], target_boxes[:, 1])
|
||||
x2 = torch.min(pred_boxes[:, 2], target_boxes[:, 2])
|
||||
y2 = torch.min(pred_boxes[:, 3], target_boxes[:, 3])
|
||||
|
||||
# 計算交集的寬度和高度
|
||||
intersection_w = torch.clamp(x2 - x1, min=0)
|
||||
intersection_h = torch.clamp(y2 - y1, min=0)
|
||||
|
||||
return intersection_w * intersection_h
|
||||
|
||||
|
||||
class DIoULoss(nn.Module):
|
||||
"""
|
||||
Distance Intersection over Union (DIoU) Loss
|
||||
CIOU 的簡化版本,只考慮重疊面積和中心點距離
|
||||
"""
|
||||
|
||||
def __init__(self, eps=1e-7):
|
||||
super(DIoULoss, self).__init__()
|
||||
self.eps = eps
|
||||
|
||||
def forward(self, pred_boxes, target_boxes):
|
||||
# 確保輸入為浮點數
|
||||
pred_boxes = pred_boxes.float()
|
||||
target_boxes = target_boxes.float()
|
||||
|
||||
# 計算交集區域
|
||||
intersection = self._calculate_intersection(pred_boxes, target_boxes)
|
||||
|
||||
# 計算各自的面積
|
||||
pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1])
|
||||
target_area = (target_boxes[:, 2] - target_boxes[:, 0]) * (target_boxes[:, 3] - target_boxes[:, 1])
|
||||
|
||||
# 計算聯集面積
|
||||
union = pred_area + target_area - intersection + self.eps
|
||||
|
||||
# 計算 IoU
|
||||
iou = intersection / union
|
||||
|
||||
# 計算最小外接矩形的對角線距離平方
|
||||
enclose_x1 = torch.min(pred_boxes[:, 0], target_boxes[:, 0])
|
||||
enclose_y1 = torch.min(pred_boxes[:, 1], target_boxes[:, 1])
|
||||
enclose_x2 = torch.max(pred_boxes[:, 2], target_boxes[:, 2])
|
||||
enclose_y2 = torch.max(pred_boxes[:, 3], target_boxes[:, 3])
|
||||
|
||||
enclose_diagonal_sq = (enclose_x2 - enclose_x1) ** 2 + (enclose_y2 - enclose_y1) ** 2 + self.eps
|
||||
|
||||
# 計算中心點距離平方
|
||||
pred_center_x = (pred_boxes[:, 0] + pred_boxes[:, 2]) / 2
|
||||
pred_center_y = (pred_boxes[:, 1] + pred_boxes[:, 3]) / 2
|
||||
target_center_x = (target_boxes[:, 0] + target_boxes[:, 2]) / 2
|
||||
target_center_y = (target_boxes[:, 1] + target_boxes[:, 3]) / 2
|
||||
|
||||
center_distance_sq = (pred_center_x - target_center_x) ** 2 + (pred_center_y - target_center_y) ** 2
|
||||
|
||||
# 計算 DIoU
|
||||
diou = iou - (center_distance_sq / enclose_diagonal_sq)
|
||||
|
||||
# 返回 DIoU Loss
|
||||
diou_loss = 1 - diou
|
||||
|
||||
return diou_loss.mean()
|
||||
|
||||
def _calculate_intersection(self, pred_boxes, target_boxes):
|
||||
"""計算交集面積"""
|
||||
x1 = torch.max(pred_boxes[:, 0], target_boxes[:, 0])
|
||||
y1 = torch.max(pred_boxes[:, 1], target_boxes[:, 1])
|
||||
x2 = torch.min(pred_boxes[:, 2], target_boxes[:, 2])
|
||||
y2 = torch.min(pred_boxes[:, 3], target_boxes[:, 3])
|
||||
|
||||
intersection_w = torch.clamp(x2 - x1, min=0)
|
||||
intersection_h = torch.clamp(y2 - y1, min=0)
|
||||
|
||||
return intersection_w * intersection_h
|
||||
|
||||
|
||||
class GIoULoss(nn.Module):
|
||||
"""
|
||||
Generalized Intersection over Union (GIoU) Loss
|
||||
IoU 的泛化版本,考慮了最小外接矩形
|
||||
"""
|
||||
|
||||
def __init__(self, eps=1e-7):
|
||||
super(GIoULoss, self).__init__()
|
||||
self.eps = eps
|
||||
|
||||
def forward(self, pred_boxes, target_boxes):
|
||||
# 確保輸入為浮點數
|
||||
pred_boxes = pred_boxes.float()
|
||||
target_boxes = target_boxes.float()
|
||||
|
||||
# 計算交集
|
||||
intersection = self._calculate_intersection(pred_boxes, target_boxes)
|
||||
|
||||
# 計算各自面積
|
||||
pred_area = (pred_boxes[:, 2] - pred_boxes[:, 0]) * (pred_boxes[:, 3] - pred_boxes[:, 1])
|
||||
target_area = (target_boxes[:, 2] - target_boxes[:, 0]) * (target_boxes[:, 3] - target_boxes[:, 1])
|
||||
|
||||
# 計算聯集
|
||||
union = pred_area + target_area - intersection + self.eps
|
||||
|
||||
# 計算 IoU
|
||||
iou = intersection / union
|
||||
|
||||
# 計算最小外接矩形面積
|
||||
enclose_x1 = torch.min(pred_boxes[:, 0], target_boxes[:, 0])
|
||||
enclose_y1 = torch.min(pred_boxes[:, 1], target_boxes[:, 1])
|
||||
enclose_x2 = torch.max(pred_boxes[:, 2], target_boxes[:, 2])
|
||||
enclose_y2 = torch.max(pred_boxes[:, 3], target_boxes[:, 3])
|
||||
|
||||
enclose_area = (enclose_x2 - enclose_x1) * (enclose_y2 - enclose_y1) + self.eps
|
||||
|
||||
# 計算 GIoU
|
||||
giou = iou - (enclose_area - union) / enclose_area
|
||||
|
||||
# 返回 GIoU Loss
|
||||
giou_loss = 1 - giou
|
||||
|
||||
return giou_loss.mean()
|
||||
|
||||
def _calculate_intersection(self, pred_boxes, target_boxes):
|
||||
"""計算交集面積"""
|
||||
x1 = torch.max(pred_boxes[:, 0], target_boxes[:, 0])
|
||||
y1 = torch.max(pred_boxes[:, 1], target_boxes[:, 1])
|
||||
x2 = torch.min(pred_boxes[:, 2], target_boxes[:, 2])
|
||||
y2 = torch.min(pred_boxes[:, 3], target_boxes[:, 3])
|
||||
|
||||
intersection_w = torch.clamp(x2 - x1, min=0)
|
||||
intersection_h = torch.clamp(y2 - y1, min=0)
|
||||
|
||||
return intersection_w * intersection_h
|
||||
|
|
@ -8,20 +8,10 @@ class Entropy_Loss(nn.Module):
|
|||
super(Entropy_Loss, self).__init__()
|
||||
|
||||
def forward(self, outputs, labels):
|
||||
# 转换为张量
|
||||
# 範例: 使用均方誤差作為損失計算
|
||||
# outputs = torch.argmax(outputs, 1)
|
||||
outputs_New = torch.as_tensor(outputs, dtype=torch.float32)
|
||||
labels_New = torch.as_tensor(labels, dtype=torch.float32)
|
||||
|
||||
# 检查输出和标签的维度是否匹配
|
||||
if outputs_New.shape[1] != labels_New.shape[1]:
|
||||
# 如果维度不匹配,使用交叉熵损失函数
|
||||
# 对于交叉熵损失,标签需要是类别索引而不是one-hot编码
|
||||
# 将one-hot编码转换为类别索引
|
||||
_, labels_indices = torch.max(labels_New, dim=1)
|
||||
loss = functional.cross_entropy(outputs_New, labels_indices)
|
||||
else:
|
||||
# 如果维度匹配,始终使用binary_cross_entropy_with_logits
|
||||
# 它会自动应用sigmoid函数,避免输入值超出[0,1]范围
|
||||
loss = functional.binary_cross_entropy_with_logits(outputs_New, labels_New)
|
||||
|
||||
loss = functional.cross_entropy(outputs_New, labels_New)
|
||||
return torch.as_tensor(loss, dtype = torch.float32)
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torchvision import models, transforms
|
||||
|
||||
class VGGPerceptualLoss(nn.Module):
|
||||
"""
|
||||
基於VGG19的感知損失函數
|
||||
使用預訓練的VGG19網絡提取特徵,計算特徵空間中的損失
|
||||
"""
|
||||
def __init__(self, feature_layers=[2, 7, 12, 21, 30], use_normalization=True):
|
||||
super(VGGPerceptualLoss, self).__init__()
|
||||
|
||||
# 載入預訓練的VGG19模型
|
||||
vgg = models.vgg19(pretrained=True).features
|
||||
|
||||
# 凍結VGG參數
|
||||
for param in vgg.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
# 將模型移到與輸入相同的設備上(在forward中處理)
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
|
||||
# 選擇要使用的特徵層
|
||||
self.feature_layers = feature_layers
|
||||
self.vgg_layers = nn.ModuleList()
|
||||
|
||||
# 分割VGG網絡到指定層
|
||||
layer_idx = 0
|
||||
current_layer = 0
|
||||
|
||||
for i, layer in enumerate(vgg):
|
||||
if layer_idx < len(feature_layers) and i <= feature_layers[layer_idx]:
|
||||
self.vgg_layers.append(layer)
|
||||
if i == feature_layers[layer_idx]:
|
||||
layer_idx += 1
|
||||
else:
|
||||
break
|
||||
|
||||
# 是否使用ImageNet標準化
|
||||
self.use_normalization = use_normalization
|
||||
if use_normalization:
|
||||
self.normalize = transforms.Normalize(
|
||||
mean=[0.485, 0.456, 0.406],
|
||||
std=[0.229, 0.224, 0.225]
|
||||
)
|
||||
|
||||
# 損失權重
|
||||
self.weights = [1.0, 1.0, 1.0, 1.0, 1.0] # 可以調整不同層的權重
|
||||
|
||||
def extract_features(self, x):
|
||||
"""
|
||||
提取VGG特徵
|
||||
"""
|
||||
# 確保輸入在[0,1]範圍內
|
||||
if x.min() < 0 or x.max() > 1:
|
||||
x = torch.clamp(x, 0, 1)
|
||||
|
||||
# 標準化
|
||||
if self.use_normalization:
|
||||
# 確保normalize在與輸入相同的設備上
|
||||
if hasattr(self, 'normalize') and not isinstance(self.normalize, torch.nn.Module):
|
||||
self.normalize = transforms.Normalize(
|
||||
mean=[0.485, 0.456, 0.406],
|
||||
std=[0.229, 0.224, 0.225]
|
||||
).to(x.device)
|
||||
x = self.normalize(x)
|
||||
|
||||
features = []
|
||||
layer_idx = 0
|
||||
|
||||
# 確保所有VGG層都在與輸入相同的設備上
|
||||
device = x.device
|
||||
for i, layer in enumerate(self.vgg_layers):
|
||||
layer = layer.to(device) # 確保層在正確的設備上
|
||||
x = layer(x)
|
||||
|
||||
# 檢查是否到達目標特徵層
|
||||
if layer_idx < len(self.feature_layers) and i == self.feature_layers[layer_idx]:
|
||||
features.append(x)
|
||||
layer_idx += 1
|
||||
|
||||
return features
|
||||
|
||||
def forward(self, pred, target):
|
||||
"""
|
||||
計算感知損失
|
||||
pred: 預測圖像 [B, C, H, W]
|
||||
target: 目標圖像 [B, C, H, W]
|
||||
"""
|
||||
# 確保模型在與輸入相同的設備上
|
||||
device = pred.device
|
||||
self.vgg_layers = nn.ModuleList([layer.to(device) for layer in self.vgg_layers])
|
||||
|
||||
# 確保輸入尺寸匹配
|
||||
if pred.shape != target.shape:
|
||||
pred = F.interpolate(pred, size=target.shape[2:], mode='bilinear', align_corners=False)
|
||||
|
||||
# 如果是單通道,轉換為三通道
|
||||
if pred.shape[1] == 1:
|
||||
pred = pred.repeat(1, 3, 1, 1)
|
||||
if target.shape[1] == 1:
|
||||
target = target.repeat(1, 3, 1, 1)
|
||||
|
||||
# 提取特徵
|
||||
pred_features = self.extract_features(pred)
|
||||
target_features = self.extract_features(target)
|
||||
|
||||
# 計算特徵損失
|
||||
perceptual_loss = 0
|
||||
for i, (pred_feat, target_feat) in enumerate(zip(pred_features, target_features)):
|
||||
# 使用MSE計算特徵差異
|
||||
feat_loss = F.mse_loss(pred_feat, target_feat)
|
||||
perceptual_loss += self.weights[i] * feat_loss
|
||||
|
||||
return perceptual_loss
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
from multiprocessing import Value
|
||||
import pstats
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torchvision.models as models
|
||||
from torchvision import transforms
|
||||
from Model_Loss.CIOU_Loss import CIOULoss
|
||||
from Model_Loss.Perceptual_Loss import VGGPerceptualLoss
|
||||
|
||||
class Segmentation_Loss(nn.Module):
|
||||
def __init__(self) -> None:
|
||||
super(Segmentation_Loss, self).__init__()
|
||||
self.Perceptual_Loss = VGGPerceptualLoss()
|
||||
self.CIOU = CIOULoss()
|
||||
pass
|
||||
|
||||
def forward(self, Output_Result, GroundTruth_Result):
|
||||
Perceptual_Loss = self.Perceptual_Loss(Output_Result, GroundTruth_Result)
|
||||
CIOU_Loss = self.CIOU(Output_Result, GroundTruth_Result)
|
||||
|
||||
return Perceptual_Loss + CIOU_Loss
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,145 +0,0 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class BinaryCrossEntropy(nn.Module):
|
||||
"""
|
||||
基本的二元交叉熵損失函數
|
||||
"""
|
||||
def __init__(self, reduction='mean'):
|
||||
"""
|
||||
初始化
|
||||
|
||||
Args:
|
||||
reduction (str): 'mean', 'sum' 或 'none',指定如何減少損失
|
||||
"""
|
||||
super(BinaryCrossEntropy, self).__init__()
|
||||
|
||||
def forward(self, predictions, targets):
|
||||
"""
|
||||
計算二元交叉熵損失
|
||||
|
||||
Args:
|
||||
predictions (torch.Tensor): 模型的預測輸出,形狀為 [batch_size, ...]
|
||||
targets (torch.Tensor): 目標標籤,形狀與 predictions 相同
|
||||
|
||||
Returns:
|
||||
torch.Tensor: 計算得到的損失值
|
||||
"""
|
||||
# 確保輸入是張量
|
||||
predictions = torch.as_tensor(predictions, dtype=torch.float32)
|
||||
targets = torch.as_tensor(targets, dtype=torch.float32)
|
||||
|
||||
return F.binary_cross_entropy_with_logits(predictions, targets)
|
||||
|
||||
# # 檢查輸出和標籤的維度是否匹配
|
||||
# if predictions.shape[1] != targets.shape[1]:
|
||||
# # 如果維度不匹配,使用交叉熵損失函數
|
||||
# # 對於交叉熵損失,標籤需要是類別索引而不是one-hot編碼
|
||||
# # 將one-hot編碼轉換為類別索引
|
||||
# _, targets_indices = torch.max(targets, dim=1)
|
||||
# return F.cross_entropy(predictions, targets_indices, reduction=self.reduction)
|
||||
# else:
|
||||
# # 如果維度匹配,使用二元交叉熵損失函數
|
||||
# # 使用 PyTorch 內建的 binary_cross_entropy_with_logits 函數
|
||||
# # 它會自動應用 sigmoid 函數,避免輸入值超出 [0,1] 範圍
|
||||
# return F.binary_cross_entropy_with_logits(predictions, targets, reduction=self.reduction)
|
||||
|
||||
|
||||
class WeightedBinaryCrossEntropy(nn.Module):
|
||||
"""
|
||||
帶權重的二元交叉熵損失函數
|
||||
"""
|
||||
def __init__(self, pos_weight=1.0, neg_weight=1.0, reduction='mean'):
|
||||
"""
|
||||
初始化
|
||||
|
||||
Args:
|
||||
pos_weight (float): 正樣本的權重
|
||||
neg_weight (float): 負樣本的權重
|
||||
reduction (str): 'mean', 'sum' 或 'none',指定如何減少損失
|
||||
"""
|
||||
super(WeightedBinaryCrossEntropy, self).__init__()
|
||||
self.pos_weight = pos_weight
|
||||
self.neg_weight = neg_weight
|
||||
self.reduction = reduction
|
||||
|
||||
def forward(self, predictions, targets):
|
||||
"""
|
||||
計算帶權重的二元交叉熵損失
|
||||
|
||||
Args:
|
||||
predictions (torch.Tensor): 模型的預測輸出,形狀為 [batch_size, ...]
|
||||
targets (torch.Tensor): 目標標籤,形狀與 predictions 相同
|
||||
|
||||
Returns:
|
||||
torch.Tensor: 計算得到的損失值
|
||||
"""
|
||||
# 確保輸入是張量
|
||||
predictions = torch.as_tensor(predictions, dtype=torch.float32)
|
||||
targets = torch.as_tensor(targets, dtype=torch.float32)
|
||||
|
||||
# 使用 sigmoid 確保預測值在 [0,1] 範圍內
|
||||
predictions = torch.sigmoid(predictions)
|
||||
|
||||
# 計算帶權重的二元交叉熵損失
|
||||
loss = -self.pos_weight * targets * torch.log(predictions + 1e-7) - \
|
||||
self.neg_weight * (1 - targets) * torch.log(1 - predictions + 1e-7)
|
||||
|
||||
# 根據 reduction 方式返回損失
|
||||
if self.reduction == 'mean':
|
||||
return loss.mean()
|
||||
elif self.reduction == 'sum':
|
||||
return loss.sum()
|
||||
else: # 'none'
|
||||
return loss
|
||||
|
||||
|
||||
class LabelSmoothingBCE(nn.Module):
|
||||
"""
|
||||
帶標籤平滑的二元交叉熵損失函數
|
||||
"""
|
||||
def __init__(self, smoothing=0.1, reduction='mean'):
|
||||
"""
|
||||
初始化
|
||||
|
||||
Args:
|
||||
smoothing (float): 標籤平滑係數,範圍 [0, 1]
|
||||
reduction (str): 'mean', 'sum' 或 'none',指定如何減少損失
|
||||
"""
|
||||
super(LabelSmoothingBCE, self).__init__()
|
||||
self.smoothing = smoothing
|
||||
self.reduction = reduction
|
||||
|
||||
def forward(self, predictions, targets):
|
||||
"""
|
||||
計算帶標籤平滑的二元交叉熵損失
|
||||
|
||||
Args:
|
||||
predictions (torch.Tensor): 模型的預測輸出,形狀為 [batch_size, ...]
|
||||
targets (torch.Tensor): 目標標籤,形狀與 predictions 相同
|
||||
|
||||
Returns:
|
||||
torch.Tensor: 計算得到的損失值
|
||||
"""
|
||||
# 確保輸入是張量
|
||||
predictions = torch.as_tensor(predictions, dtype=torch.float32)
|
||||
targets = torch.as_tensor(targets, dtype=torch.float32)
|
||||
|
||||
# 應用標籤平滑
|
||||
targets = targets * (1 - self.smoothing) + 0.5 * self.smoothing
|
||||
|
||||
# 使用 sigmoid 確保預測值在 [0,1] 範圍內
|
||||
predictions = torch.sigmoid(predictions)
|
||||
|
||||
# 計算二元交叉熵損失
|
||||
loss = -targets * torch.log(predictions + 1e-7) - (1 - targets) * torch.log(1 - predictions + 1e-7)
|
||||
|
||||
# 根據 reduction 方式返回損失
|
||||
if self.reduction == 'mean':
|
||||
return loss.mean()
|
||||
elif self.reduction == 'sum':
|
||||
return loss.sum()
|
||||
else: # 'none'
|
||||
return loss
|
||||
|
|
@ -14,7 +14,7 @@ if __name__ == "__main__":
|
|||
tool.Set_Labels()
|
||||
tool.Set_Save_Roots()
|
||||
Labels = tool.Get_Data_Label()
|
||||
Trainig_Root, Testing_Root = tool.Get_Save_Roots(2)
|
||||
Trainig_Root, Testing_Root, Validation_Root = tool.Get_Save_Roots(2)
|
||||
|
||||
load = Loding_Data_Root(Labels, Trainig_Root, "")
|
||||
Data_Root = load.get_Image_data_roots(Trainig_Root)
|
||||
|
|
|
|||
122
README.md
122
README.md
|
|
@ -1,116 +1,16 @@
|
|||
# 胃試鏡疾病判斷系統
|
||||
|
||||
## 項目概述
|
||||
|
||||
本項目是一個基於深度學習的胃試鏡疾病自動診斷系統,主要用於檢測和分類胃部疾病,特別是胃癌(CA)。系統採用了分割和分類的兩階段方法,首先對胃試鏡圖像進行分割以識別可疑區域,然後對這些區域進行分類以確定是否存在疾病。
|
||||
|
||||
* 資料集類別: 3分類(胃癌、非胃癌但有病、正常資料)
|
||||
* 基礎模型: Xception
|
||||
|
||||
## 主要功能
|
||||
|
||||
- **圖像預處理**:包括直方圖均衡化、自適應直方圖均衡化、銳化、HSV調整、伽馬校正等多種圖像增強方法
|
||||
- **數據增強**:通過圖像生成器擴充訓練數據集
|
||||
- **疾病分割**:使用GastroSegNet模型對胃試鏡圖像中的可疑區域進行分割
|
||||
- **疾病分類**:使用修改版Xception模型對分割後的區域進行分類,分為正常(Normal)、胃癌(CA)和待確認(Have_Question)三類
|
||||
- **結果可視化**:提供混淆矩陣、訓練曲線等可視化工具
|
||||
|
||||
## 系統架構
|
||||
|
||||
系統採用了兩階段的處理流程:
|
||||
|
||||
1. **分割階段**:使用GastroSegNet模型對輸入圖像進行分割,識別可疑區域
|
||||
2. **分類階段**:
|
||||
- 第一組分類器:區分正常(Normal)和其他(Others)
|
||||
- 第二組分類器:區分胃癌(CA)和待確認(Have_Question)
|
||||
|
||||
## 環境要求
|
||||
|
||||
- Python 3.8+
|
||||
- PyTorch 1.8+
|
||||
- CUDA(推薦用於加速訓練)
|
||||
- 其他依賴庫:torchvision, numpy, opencv-python, scikit-image, pandas等
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 數據準備
|
||||
|
||||
1. 將訓練數據放置在`../Dataset/Training`目錄下
|
||||
2. 將測試數據放置在`../Dataset/Testing`目錄下
|
||||
3. 標註數據(XML格式)放置在`../Label_Image`目錄下
|
||||
|
||||
### 訓練模型
|
||||
|
||||
```bash
|
||||
uv run main.py
|
||||
```
|
||||
|
||||
訓練過程將自動執行以下步驟:
|
||||
1. 數據預處理和增強
|
||||
2. 訓練分割模型(GastroSegNet)
|
||||
3. 使用分割模型處理圖像
|
||||
4. 訓練分類模型(修改版Xception)
|
||||
|
||||
### 結果查看
|
||||
|
||||
訓練結果將保存在`../Result`目錄下,包括:
|
||||
- 訓練曲線圖:`../Result/Training_Image`
|
||||
- 混淆矩陣:`../Result/Matrix_Image`
|
||||
- 訓練結果數據:`../Result/Training_Result`
|
||||
- 最佳模型:`../Result/save_the_best_model`
|
||||
|
||||
## 項目結構
|
||||
|
||||
- `main.py`:程序入口點
|
||||
- `experiments/`:實驗相關代碼
|
||||
- `experiment.py`:實驗主流程
|
||||
- `Training/`:訓練相關代碼
|
||||
- `Models/`:模型定義
|
||||
- `Image_Process/`:圖像處理相關代碼
|
||||
- `Model_Loss/`:損失函數定義
|
||||
- `Training_Tools/`:訓練工具
|
||||
- `utils/`:工具函數和配置
|
||||
|
||||
## 配置說明
|
||||
|
||||
系統配置在`utils/Stomach_Config.py`中定義,主要包括:
|
||||
|
||||
- `Image_Enhance`:圖像增強方法
|
||||
- `Loading_Config`:數據加載配置
|
||||
- `Training_Config`:訓練參數配置
|
||||
- `Model_Config`:模型參數配置
|
||||
- `Save_Result_File_Config`:結果保存路徑配置
|
||||
|
||||
## 模型說明
|
||||
|
||||
### 分割模型(GastroSegNet)
|
||||
|
||||
用於識別胃試鏡圖像中的可疑區域,輸出分割掩碼。
|
||||
|
||||
### 分類模型(修改版Xception)
|
||||
|
||||
基於Xception架構,針對胃試鏡圖像分類任務進行了修改,主要用於區分正常、胃癌和待確認三類。
|
||||
* 主執行檔: main.py
|
||||
main.py: 主程式檔
|
||||
|
||||
## load_process
|
||||
### 負責讀取影像檔案、分割獨立資料(測試、驗證)、讀取獨立資料、一般檔案的操作
|
||||
* File_Process : 檔案操作的主程式,包含開檔、創立檔案、判斷檔案是否存在等都是他負責的範圍。是一般物件也是LoadData的父物件
|
||||
* LoadData : 讀檔主程式,一切讀檔動作由他開始。繼承File_Process(子物件)
|
||||
* Cutting_Indepentend_Image : 讀取獨立資料(testing、Validation)的物件
|
||||
|
||||
## Calculate_Process
|
||||
### 計算模型的評估指標的內容
|
||||
* Calculate: 計算模型的評估指標的平均跟標準差,並將結果儲存到檔案中
|
||||
File_Process : 檔案操作的主程式,包含開檔、創立檔案、判斷檔案是否存在等都是他負責的範圍。是一般物件也是LoadData的父物件
|
||||
LoadData : 讀檔主程式,一切讀檔動作由他開始。繼承File_Process(子物件)
|
||||
Cutting_Indepentend_Image : 讀取獨立資料(testing、Validation)的物件
|
||||
|
||||
## Image_Process
|
||||
### 負責進行資料擴增、影像處理等的操作
|
||||
* Image_Generator : 負責製造資料擴增的資料,並將資料存到檔案中。
|
||||
* image_enhancement : 負責進行影像處理將資料強化。
|
||||
*
|
||||
|
||||
## all_models_tools
|
||||
### 模型的調控細節,如early stop、降低學習率和儲存最佳模型
|
||||
* all_model_tools: call back的方法
|
||||
* Generator_Content : 負責建立基礎Generator項目,為Image_Generator的父類別
|
||||
* Image_Generator : 負責製造資料擴增的資料,並將資料存到檔案中。繼承Generator_Content(子物件)
|
||||
* image_enhancement : 負責進行影像處理並將資料回傳
|
||||
|
||||
## Model_Tools
|
||||
### 負責進行模型的基礎架構,包含Convolution、Dense、以及其他模型的配件
|
||||
|
|
@ -142,13 +42,11 @@ uv run main.py
|
|||
### 負責驗證程式碼內的資料型態或輸入錯誤等問題
|
||||
* Validation : 驗證程式碼錯誤
|
||||
|
||||
## draw_tools
|
||||
## Draw
|
||||
### 負責畫圖的工具
|
||||
* draw : 畫出混淆矩陣、走勢圖的工具
|
||||
* Draw_Tools : 畫出混淆矩陣、走勢圖的工具
|
||||
* Grad_CAM : 畫出模型可視化的熱力圖的工具
|
||||
|
||||
## Experiment
|
||||
### 執行實驗的主程式
|
||||
* Experiment : 負責執行讀檔、設定模型與實驗的細節、執行訓練、驗證結果等功能
|
||||
* Model_All_Step : 執行模型的訓練流程設定與細節參數設定
|
||||
* pytorch_Model: 設定模型的架構
|
||||
* Experiment : 負責執行讀檔、設定模型Compile的細節、執行訓練、驗證結果等功能
|
||||
|
|
|
|||
74
Read_and_process_image/ReadAndProcess.py
Normal file
74
Read_and_process_image/ReadAndProcess.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
class Read_image_and_Process_image:
|
||||
def __init__(self, Image_Size) -> None:
|
||||
self.Image_Size = Image_Size
|
||||
pass
|
||||
def get_data(self, path):
|
||||
'''讀檔'''
|
||||
try:
|
||||
img_arr = cv2.imread(path, cv2.IMREAD_COLOR) # 讀檔(彩色)
|
||||
# img_arr = cv2.imread(path, cv2.IMREAD_GRAYSCALE) # 讀檔(灰階)
|
||||
resized_arr = cv2.resize(img_arr, (self.Image_Size, self.Image_Size)) # 濤整圖片大小
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return resized_arr
|
||||
|
||||
def Data_Augmentation_Image(self, path):
|
||||
resized_arr = []
|
||||
|
||||
for p in path:
|
||||
try:
|
||||
img_arr = cv2.imread(p, cv2.IMREAD_COLOR) # 讀檔(彩色)
|
||||
# img_arr = cv2.imread(path, cv2.IMREAD_GRAYSCALE) # 讀檔(灰階)
|
||||
resized_arr.append(cv2.resize(img_arr, (self.Image_Size, self.Image_Size))) # 調整圖片大小
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return np.array(resized_arr)
|
||||
|
||||
def image_data_processing(self, data, label):
|
||||
'''讀檔後處理圖片'''
|
||||
data = np.asarray(data).astype(np.float32) # 將圖list轉成np.array
|
||||
data = data.reshape(-1, self.Image_Size, self.Image_Size, 3) # 更改陣列形狀
|
||||
label = np.array(label) # 將label從list型態轉成 numpy array
|
||||
return data, label
|
||||
|
||||
def normalization(self, images):
|
||||
imgs = []
|
||||
for img in images:
|
||||
img = np.asarray(img).astype(np.float32) # 將圖list轉成np.array
|
||||
img = img / 255 # 標準化影像資料
|
||||
imgs.append(img)
|
||||
|
||||
return np.array(imgs)
|
||||
|
||||
# def load_numpy_data(self, file_names):
|
||||
# '''載入numpy圖檔,並執行影像處理提高特徵擷取'''
|
||||
# i = 0
|
||||
# numpy_image = []
|
||||
# original_image = []
|
||||
# for file_name in file_names:
|
||||
# compare = str(file_name).split(".")
|
||||
# if compare[-1] == "npy":
|
||||
# image = np.load(file_name) # 讀圖片檔
|
||||
# numpy_image.append(image) # 合併成一個陣列
|
||||
# else:
|
||||
# original_image.append(file_name)
|
||||
|
||||
# original_image = self.get_data(original_image)
|
||||
|
||||
# for file in original_image:
|
||||
# numpy_image.append(file)
|
||||
|
||||
# return numpy_image
|
||||
|
||||
def make_label_list(self, length, content):
|
||||
'''製作label的列表'''
|
||||
label_list = []
|
||||
for i in range(length):
|
||||
label_list.append(content)
|
||||
return label_list
|
||||
Binary file not shown.
Binary file not shown.
BIN
Read_and_process_image/__pycache__/ReadAndProcess.cpython-39.pyc
Normal file
BIN
Read_and_process_image/__pycache__/ReadAndProcess.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Read_and_process_image/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
Read_and_process_image/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
Read_and_process_image/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
Read_and_process_image/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
Read_and_process_image/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Read_and_process_image/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
|
|
@ -1,163 +1,53 @@
|
|||
from torch.utils.data import Dataset, DataLoader, RandomSampler, WeightedRandomSampler, SubsetRandomSampler, Subset
|
||||
from torchvision.datasets import ImageFolder
|
||||
from torch.utils.data import Dataset, DataLoader, RandomSampler
|
||||
import torchvision.transforms as transforms
|
||||
from PIL import Image
|
||||
import torch
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
class ListDataset(Dataset):
|
||||
def __init__(self, data_list, labels_list, Mask_List, transform):
|
||||
def __init__(self, data_list, labels_list, status):
|
||||
self.data = data_list
|
||||
self.labels = labels_list
|
||||
self.Mask_Truth_List = Mask_List
|
||||
self.transform = transform
|
||||
self.roots = []
|
||||
self.status = status
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
Image_Root = self.data[idx]
|
||||
sample = self.data[idx]
|
||||
|
||||
Mask_Ground_Truth = None
|
||||
if self.Mask_Truth_List is not None:
|
||||
mask_path = self.Mask_Truth_List[idx]
|
||||
if mask_path is not None: # 確保掩碼路徑不為None
|
||||
try:
|
||||
Mask_Ground_Truth = Image.open(mask_path).convert("RGB")
|
||||
# 先不轉換為 tensor,等待 transform 處理完後再轉換
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
Split_Roots = Image_Root.split("/")
|
||||
# Split_Roots = Split_Roots[-1].split("\\")
|
||||
File_Name = Split_Roots[-1]
|
||||
classes = Split_Roots[-2]
|
||||
|
||||
try:
|
||||
Images = Image.open(Image_Root).convert("RGB")
|
||||
except Exception as e:
|
||||
assert e is not None, f"Error loading image {Image_Root}: {e}"
|
||||
|
||||
if self.transform != "Generator":
|
||||
Images = self.transform(Images)
|
||||
if self.Mask_Truth_List is not None and Mask_Ground_Truth is not None and not isinstance(Mask_Ground_Truth, torch.Tensor):
|
||||
Mask_Ground_Truth = self.transform(Mask_Ground_Truth)
|
||||
|
||||
# 確保 Images 是 tensor
|
||||
if not isinstance(Images, torch.Tensor):
|
||||
Images = torch.tensor(np.array(Images))
|
||||
|
||||
# 確保 Mask_Ground_Truth 是 tensor
|
||||
if self.Mask_Truth_List is not None and Mask_Ground_Truth is not None and not isinstance(Mask_Ground_Truth, torch.Tensor):
|
||||
Mask_Ground_Truth = torch.tensor(np.array(Mask_Ground_Truth))
|
||||
if self.status:
|
||||
from Image_Process.Image_Generator import Image_generator
|
||||
ImageGenerator = Image_generator("", "", 12)
|
||||
Transform = ImageGenerator.Generator_Content(5)
|
||||
sample = Transform(sample)
|
||||
|
||||
label = self.labels[idx]
|
||||
|
||||
if self.Mask_Truth_List is not None:
|
||||
# 如果掩碼為None,創建一個與圖像相同大小的空掩碼
|
||||
if Mask_Ground_Truth is None:
|
||||
if isinstance(Images, torch.Tensor):
|
||||
# 創建與圖像相同大小的空掩碼張量
|
||||
Mask_Ground_Truth = torch.zeros_like(Images)
|
||||
else:
|
||||
# 如果圖像不是張量,創建一個空的PIL圖像
|
||||
Mask_Ground_Truth = Image.new('RGB', Images.size, (0, 0, 0))
|
||||
if self.transform != "Generator":
|
||||
Mask_Ground_Truth = self.transform(Mask_Ground_Truth)
|
||||
return Images, Mask_Ground_Truth, label, File_Name, classes
|
||||
# print(f"Dataset_Data: \n{sample}\n")
|
||||
return Images, label, File_Name, classes
|
||||
return sample, label
|
||||
|
||||
class Training_Precesses:
|
||||
def __init__(self, ImageSize):
|
||||
seed = 42 # Set an arbitrary integer as the seed
|
||||
self.ImageSize = ImageSize
|
||||
self.generator = torch.Generator()
|
||||
self.generator.manual_seed(seed)
|
||||
def __init__(self, Training_Datas, Training_Labels, Testing_Datas, Testing_Labels):
|
||||
self.Training_Datas = Training_Datas
|
||||
self.Training_Labels = Training_Labels
|
||||
self.Testing_Datas = Testing_Datas
|
||||
self.Testing_Labels = Testing_Labels
|
||||
pass
|
||||
|
||||
def Dataloader_Sampler(self, SubDataSet, Batch_Size, Sampler=True):
|
||||
if Sampler:
|
||||
Data_Loader = DataLoader(
|
||||
dataset=SubDataSet,
|
||||
batch_size=Batch_Size,
|
||||
num_workers=0,
|
||||
pin_memory=True,
|
||||
sampler=self.Setting_WeightedRandomSampler_Content(SubDataSet)
|
||||
)
|
||||
else:
|
||||
Data_Loader = DataLoader(
|
||||
dataset=SubDataSet,
|
||||
batch_size=Batch_Size,
|
||||
num_workers=0,
|
||||
pin_memory=True
|
||||
)
|
||||
return Data_Loader
|
||||
def Total_Data_Combine_To_DataLoader(self, Batch_Size):
|
||||
Training_Dataset = self.Convert_Data_To_DataSet(self.Training_Datas, self.Training_Labels)
|
||||
Testing_Dataset = self.Convert_Data_To_DataSet(self.Testing_Datas, self.Testing_Labels)
|
||||
|
||||
def Setting_WeightedRandomSampler_Content(self, SubDataSet):
|
||||
# Check if SubDataSet is a Subset or a full dataset
|
||||
if isinstance(SubDataSet, Subset):
|
||||
# Get the underlying dataset and subset indices
|
||||
base_dataset = SubDataSet.dataset
|
||||
subset_indices = SubDataSet.indices
|
||||
# Extract labels for the subset
|
||||
labels = [base_dataset.labels[i] for i in subset_indices]
|
||||
else:
|
||||
# Assume SubDataSet is a ListDataset or similar
|
||||
labels = SubDataSet.labels
|
||||
Training_DataLoader = DataLoader(dataset = Training_Dataset, batch_size = Batch_Size, num_workers = 0, pin_memory=True, shuffle = True)
|
||||
Testing_DataLoader = DataLoader(dataset = Testing_Dataset, batch_size = 1, num_workers = 0, pin_memory=True, shuffle = True)
|
||||
|
||||
# Convert labels to class indices if they are one-hot encoded
|
||||
labels = np.array(labels)
|
||||
if labels.ndim > 1: # If one-hot encoded
|
||||
labels = np.argmax(labels, axis=1)
|
||||
return Training_DataLoader, Testing_DataLoader
|
||||
|
||||
# 確保標籤是整數類型
|
||||
try:
|
||||
# 嘗試將標籤轉換為整數
|
||||
labels = labels.astype(np.int64)
|
||||
except ValueError:
|
||||
# 如果標籤是字符串,先將其映射到整數
|
||||
unique_labels = np.unique(labels)
|
||||
label_to_idx = {label: idx for idx, label in enumerate(unique_labels)}
|
||||
labels = np.array([label_to_idx[label] for label in labels])
|
||||
def Convert_Data_To_DataSet(self, Datas : list, Labels : list, status : bool = True):
|
||||
seed = 42 # 設定任意整數作為種子
|
||||
# 產生隨機種子產生器
|
||||
generator = torch.Generator()
|
||||
generator.manual_seed(seed)
|
||||
|
||||
# Count occurrences of each class
|
||||
class_counts = np.bincount(labels)
|
||||
class_weights = 1.0 / class_counts # Inverse frequency as weight
|
||||
sample_weights = class_weights[labels] # Assign weight to each sample
|
||||
# 創建 Dataset
|
||||
list_dataset = ListDataset(Datas, Labels, status)
|
||||
# sampler = RandomSampler(list_dataset, generator = generator) # 創建Sampler
|
||||
|
||||
return WeightedRandomSampler(
|
||||
weights=sample_weights,
|
||||
num_samples=len(sample_weights),
|
||||
replacement=True
|
||||
)
|
||||
|
||||
def Setting_RandomSampler_Content(self, Dataset):
|
||||
return RandomSampler(Dataset, generator = self.generator)
|
||||
|
||||
def Setting_DataSet(self, Datas, Labels, Mask_List, transform = None):
|
||||
# 資料預處理
|
||||
if transform == None:
|
||||
transform = transforms.Compose([
|
||||
transforms.Resize((self.ImageSize, self.ImageSize))
|
||||
])
|
||||
elif transform == "Transform":
|
||||
transform = transforms.Compose([
|
||||
transforms.Resize((self.ImageSize, self.ImageSize)),
|
||||
transforms.ToTensor()
|
||||
])
|
||||
elif transform == "Generator":
|
||||
transform = "Generator"
|
||||
|
||||
# Create Dataset
|
||||
list_dataset = ListDataset(Datas, Labels, Mask_List, transform)
|
||||
return list_dataset
|
||||
|
||||
def Setting_SubsetRandomSampler_Content(self, SubDataSet):
|
||||
# Calculate subset indices (example: using a fraction of the dataset)
|
||||
dataset_size = len(SubDataSet)
|
||||
subset_size = int(0.8 * dataset_size) # Use 80% of the dataset as an example
|
||||
subset_indices = torch.randperm(dataset_size, generator=self.generator)[:subset_size]
|
||||
|
||||
return SubsetRandomSampler(subset_indices, generator=self.generator)
|
||||
|
|
@ -5,9 +5,46 @@ import torch
|
|||
|
||||
class Tool:
|
||||
def __init__(self) -> None:
|
||||
self.__ICG_Training_Root = ""
|
||||
self.__Normal_Training_Root = ""
|
||||
self.__Comprehensive_Training_Root = ""
|
||||
|
||||
self.__ICG_Test_Data_Root = ""
|
||||
self.__Normal_Test_Data_Root = ""
|
||||
self.__Comprehensive_Testing_Root = ""
|
||||
|
||||
self.__ICG_Validation_Data_Root = ""
|
||||
self.__Normal_Validation_Data_Root = ""
|
||||
self.__Comprehensive_Validation_Root = ""
|
||||
|
||||
self.__ICG_ImageGenerator_Data_Root = ""
|
||||
self.__Normal_ImageGenerator_Data_Root = ""
|
||||
self.__Comprehensive_Generator_Root = ""
|
||||
|
||||
self.__Labels = []
|
||||
self.__OneHot_Encording = []
|
||||
pass
|
||||
|
||||
def Set_Labels(self):
|
||||
self.__Labels = ["stomach_cancer_Crop", "Normal_Crop", "Have_Question_Crop"]
|
||||
|
||||
def Set_Save_Roots(self):
|
||||
self.__ICG_Training_Root = "../Dataset/Training/CA_ICG"
|
||||
self.__Normal_Training_Root = "../Dataset/Training/CA"
|
||||
self.__Comprehensive_Training_Root = "../Dataset/Training/Mixed"
|
||||
|
||||
self.__ICG_Test_Data_Root = "../Dataset/Training/CA_ICG_TestData"
|
||||
self.__Normal_Test_Data_Root = "../Dataset/Training/Normal_TestData"
|
||||
self.__Comprehensive_Testing_Root = "../Dataset/Training/Comprehensive_TestData"
|
||||
|
||||
self.__ICG_Validation_Data_Root = "../Dataset/Training/CA_ICG_ValidationData"
|
||||
self.__Normal_Validation_Data_Root = "../Dataset/Training/Normal_ValidationData"
|
||||
self.__Comprehensive_Validation_Root = "../Dataset/Training/Comprehensive_ValidationData"
|
||||
|
||||
self.__ICG_ImageGenerator_Data_Root = "../Dataset/Training/ICG_ImageGenerator"
|
||||
self.__Normal_ImageGenerator_Data_Root = "../Dataset/Training/Normal_ImageGenerator"
|
||||
self.__Comprehensive_Generator_Root = "../Dataset/Training/Comprehensive_ImageGenerator"
|
||||
|
||||
def Set_OneHotEncording(self, content):
|
||||
Counter = []
|
||||
for i in range(len(content)):
|
||||
|
|
@ -17,5 +54,34 @@ class Tool:
|
|||
self.__OneHot_Encording = functional.one_hot(Counter, len(content))
|
||||
pass
|
||||
|
||||
def Get_Data_Label(self):
|
||||
'''
|
||||
取得所需資料的Labels
|
||||
'''
|
||||
return self.__Labels
|
||||
|
||||
def Get_Save_Roots(self, choose):
|
||||
'''回傳結果為Train, test, validation
|
||||
choose = 1 => 取白光 Label
|
||||
else => 取濾光 Label
|
||||
|
||||
若choose != 1 || choose != 2 => 會回傳四個結果
|
||||
'''
|
||||
if choose == 1:
|
||||
return self.__ICG_Training_Root, self.__ICG_Test_Data_Root, self.__ICG_Validation_Data_Root
|
||||
if choose == 2:
|
||||
return self.__Normal_Training_Root, self.__Normal_Test_Data_Root, self.__Normal_Validation_Data_Root
|
||||
else:
|
||||
return self.__Comprehensive_Training_Root, self.__Comprehensive_Testing_Root, self.__Comprehensive_Validation_Root
|
||||
|
||||
def Get_Generator_Save_Roots(self, choose):
|
||||
'''回傳結果為Train, test, validation'''
|
||||
if choose == 1:
|
||||
return self.__ICG_ImageGenerator_Data_Root
|
||||
if choose == 2:
|
||||
return self.__Normal_ImageGenerator_Data_Root
|
||||
else:
|
||||
return self.__Comprehensive_Generator_Root
|
||||
|
||||
def Get_OneHot_Encording_Label(self):
|
||||
return self.__OneHot_Encording
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
26
a00.xml
26
a00.xml
|
|
@ -1,26 +0,0 @@
|
|||
<annotation>
|
||||
<folder>Processing_Image</folder>
|
||||
<filename>a00.jpg</filename>
|
||||
<path>D:\Programing\stomach_cancer\Processing_Image\a00.jpg</path>
|
||||
<source>
|
||||
<database>Unknown</database>
|
||||
</source>
|
||||
<size>
|
||||
<width>1074</width>
|
||||
<height>1074</height>
|
||||
<depth>3</depth>
|
||||
</size>
|
||||
<segmented>0</segmented>
|
||||
<object>
|
||||
<name>Have_Question</name>
|
||||
<pose>Unspecified</pose>
|
||||
<truncated>0</truncated>
|
||||
<difficult>0</difficult>
|
||||
<bndbox>
|
||||
<xmin>263</xmin>
|
||||
<ymin>740</ymin>
|
||||
<xmax>333</xmax>
|
||||
<ymax>814</ymax>
|
||||
</bndbox>
|
||||
</object>
|
||||
</annotation>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -2,6 +2,19 @@ from Load_process.file_processing import Process_File
|
|||
import datetime
|
||||
import torch
|
||||
|
||||
# def attention_block(input):
|
||||
# channel = input.shape[-1]
|
||||
|
||||
# GAP = GlobalAveragePooling2D()(input)
|
||||
|
||||
# block = Dense(units = channel // 16, activation = "relu")(GAP)
|
||||
# block = Dense(units = channel, activation = "sigmoid")(block)
|
||||
# block = Reshape((1, 1, channel))(block)
|
||||
|
||||
# block = Multiply()([input, block])
|
||||
|
||||
# return block
|
||||
|
||||
class EarlyStopping:
|
||||
def __init__(self, patience=74, verbose=False, delta=0):
|
||||
self.patience = patience
|
||||
|
|
@ -32,11 +45,12 @@ class EarlyStopping:
|
|||
print(f"Validation loss decreased ({self.best_loss:.6f} --> {val_loss:.6f}). Saving model to {save_path}")
|
||||
|
||||
|
||||
def call_back(Save_Root, index, optimizer):
|
||||
def call_back(model_name, index, optimizer):
|
||||
File = Process_File()
|
||||
|
||||
File.JudgeRoot_MakeDir(Save_Root)
|
||||
modelfiles = File.Make_Save_Root('best_model( ' + str(datetime.date.today()) + " )-" + index + ".pt", Save_Root)
|
||||
model_dir = '../Result/save_the_best_model/' + model_name
|
||||
File.JudgeRoot_MakeDir(model_dir)
|
||||
modelfiles = File.Make_Save_Root('best_model( ' + str(datetime.date.today()) + " )-" + str(index) + ".pt", model_dir)
|
||||
|
||||
# model_mckp = ModelCheckpoint(modelfiles, monitor='val_loss', save_best_only=True, save_weights_only = True, mode='auto')
|
||||
|
||||
|
|
@ -46,6 +60,8 @@ def call_back(Save_Root, index, optimizer):
|
|||
optimizer,
|
||||
factor = 0.94, # 學習率降低的量。 new_lr = lr * factor
|
||||
patience = 2, # 沒有改進的時期數,之後學習率將降低
|
||||
verbose = 0,
|
||||
mode = 'min',
|
||||
min_lr = 0 # 學習率下限
|
||||
)
|
||||
|
||||
|
|
|
|||
116
all_models_tools/pre_train_model_construction.py
Normal file
116
all_models_tools/pre_train_model_construction.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
from all_models_tools.all_model_tools import attention_block
|
||||
from keras.activations import softmax, sigmoid
|
||||
from keras.applications import VGG16,VGG19, ResNet50, ResNet50V2, ResNet101, ResNet101V2, ResNet152, ResNet152V2, InceptionV3, InceptionResNetV2, MobileNet, MobileNetV2, DenseNet121, NASNetLarge, Xception
|
||||
from keras.layers import GlobalAveragePooling2D, Dense, Flatten
|
||||
from keras import regularizers
|
||||
from keras.layers import Add
|
||||
from application.Xception_indepentment import Xception_indepentment
|
||||
|
||||
def Original_VGG19_Model():
|
||||
vgg19 = VGG19(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(vgg19.output)
|
||||
dense = Dense(units = 4096, activation = "relu")(GAP)
|
||||
dense = Dense(units = 4096, activation = "relu")(dense)
|
||||
output = Dense(units = 2, activation = "softmax")(dense)
|
||||
|
||||
return vgg19.input, output
|
||||
|
||||
def Original_ResNet50_model():
|
||||
xception = ResNet50(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(xception.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return xception.input, dense
|
||||
|
||||
def Original_NASNetLarge_model():
|
||||
nasnetlarge = NASNetLarge(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(nasnetlarge.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return nasnetlarge.input, dense
|
||||
|
||||
def Original_DenseNet121_model():
|
||||
Densenet201 = DenseNet121(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(Densenet201.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return Densenet201.input, dense
|
||||
|
||||
def Original_Xception_model():
|
||||
xception = Xception(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(xception.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return xception.input, dense
|
||||
|
||||
def Original_VGG16_Model():
|
||||
vgg16 = VGG16(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
flatten = Flatten()(vgg16.output)
|
||||
dense = Dense(units = 4096, activation = "relu")(flatten)
|
||||
dense = Dense(units = 4096, activation = "relu")(dense)
|
||||
output = Dense(units = 2, activation = "softmax")(dense)
|
||||
|
||||
return vgg16.input, output
|
||||
|
||||
def Original_ResNet50v2_model():
|
||||
resnet50v2 = ResNet50V2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(resnet50v2.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return resnet50v2.input, dense
|
||||
|
||||
def Original_ResNet101_model():
|
||||
resnet101 = ResNet101(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(resnet101.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return resnet101.input, dense
|
||||
|
||||
def Original_ResNet101V2_model():
|
||||
resnet101v2 = ResNet101V2(include_top = False, weights = "imagenet", input_shape = (512, 512, 3))
|
||||
GAP = GlobalAveragePooling2D()(resnet101v2.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return resnet101v2.input, dense
|
||||
|
||||
def Original_ResNet152_model():
|
||||
resnet152 = ResNet152(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(resnet152.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return resnet152.input, dense
|
||||
|
||||
def Original_ResNet152V2_model():
|
||||
resnet152v2 = ResNet152V2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(resnet152v2.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return resnet152v2.input, dense
|
||||
|
||||
def Original_InceptionV3_model():
|
||||
inceptionv3 = InceptionV3(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(inceptionv3.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return inceptionv3.input, dense
|
||||
|
||||
def Original_InceptionResNetV2_model():
|
||||
inceptionResnetv2 = InceptionResNetV2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(inceptionResnetv2.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return inceptionResnetv2.input, dense
|
||||
|
||||
def Original_MobileNet_model():
|
||||
mobilenet = MobileNet(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(mobilenet.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return mobilenet.input, dense
|
||||
|
||||
def Original_MobileNetV2_model():
|
||||
mobilenetv2 = MobileNetV2(include_top = False, weights = "imagenet", input_shape = (200, 200, 3))
|
||||
GAP = GlobalAveragePooling2D()(mobilenetv2.output)
|
||||
dense = Dense(units = 2, activation = "softmax")(GAP)
|
||||
|
||||
return mobilenetv2.input, dense
|
||||
Binary file not shown.
Binary file not shown.
256
application/Xception_indepentment.py
Normal file
256
application/Xception_indepentment.py
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
|
||||
# SENet
|
||||
# block = layers.GlobalAveragePooling2D()(residual)
|
||||
# block = layers.Dense(units = residual.shape[-1] // 16, activation = "relu")(block)
|
||||
# block = layers.Dense(units = residual.shape[-1], activation = "sigmoid")(block)
|
||||
# block = Reshape((1, 1, residual.shape[-1]))(block)
|
||||
# residual = Multiply()([residual, block])
|
||||
|
||||
|
||||
|
||||
from keras import backend
|
||||
from keras import layers
|
||||
from keras.layers import Reshape, Multiply, Conv1D
|
||||
import math
|
||||
|
||||
def Xception_indepentment(input_shape=None):
|
||||
|
||||
channel_axis = 1 if backend.image_data_format() == "channels_first" else -1
|
||||
|
||||
img_input = layers.Input(shape=input_shape)
|
||||
x = layers.Conv2D(
|
||||
32, (3, 3), strides=(2, 2), use_bias=False, name="block1_conv1"
|
||||
)(img_input)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block1_conv1_bn")(x)
|
||||
x = layers.Activation("relu", name="block1_conv1_act")(x
|
||||
)
|
||||
x = layers.Conv2D(64, (3, 3), use_bias=False, name="block1_conv2")(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block1_conv2_bn")(x)
|
||||
x = layers.Activation("relu", name="block1_conv2_act")(x)
|
||||
|
||||
residual = layers.Conv2D(
|
||||
128, (1, 1), strides=(2, 2), padding="same", use_bias=False
|
||||
)(x)
|
||||
residual = layers.BatchNormalization(axis=channel_axis)(residual)
|
||||
|
||||
# 注意力機制區域
|
||||
kernel = int(abs((math.log(residual.shape[-1], 2) + 1) / 2))
|
||||
if kernel % 2:
|
||||
kernel_size = kernel
|
||||
else:
|
||||
kernel_size = kernel + 1
|
||||
|
||||
block = layers.GlobalAveragePooling2D()(residual)
|
||||
block = Reshape(target_shape = (residual.shape[-1], 1))(block)
|
||||
block = Conv1D(filters = 1, kernel_size = kernel_size, padding = "same", use_bias = False, activation = "sigmoid")(block)
|
||||
block = Reshape((1, 1, residual.shape[-1]))(block)
|
||||
residual = Multiply()([residual, block])
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
128, (3, 3), padding="same", use_bias=False, name="block2_sepconv1"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block2_sepconv1_bn")(
|
||||
x
|
||||
)
|
||||
x = layers.Activation("relu", name="block2_sepconv2_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
128, (3, 3), padding="same", use_bias=False, name="block2_sepconv2"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block2_sepconv2_bn")(
|
||||
x
|
||||
)
|
||||
|
||||
x = layers.MaxPooling2D(
|
||||
(3, 3), strides=(2, 2), padding="same", name="block2_pool"
|
||||
)(x)
|
||||
x = layers.add([x, residual])
|
||||
|
||||
residual = layers.Conv2D(
|
||||
256, (1, 1), strides=(2, 2), padding="same", use_bias=False
|
||||
)(x)
|
||||
residual = layers.BatchNormalization(axis=channel_axis)(residual)
|
||||
|
||||
# 注意力機制區域
|
||||
kernel = int(abs((math.log(residual.shape[-1], 2) + 1) / 2))
|
||||
if kernel % 2:
|
||||
kernel_size = kernel
|
||||
else:
|
||||
kernel_size = kernel + 1
|
||||
|
||||
block = layers.GlobalAveragePooling2D()(residual)
|
||||
block = Reshape(target_shape = (residual.shape[-1], 1))(block)
|
||||
block = Conv1D(filters = 1, kernel_size = kernel_size, padding = "same", use_bias = False, activation = "sigmoid")(block)
|
||||
block = Reshape((1, 1, residual.shape[-1]))(block)
|
||||
residual = Multiply()([residual, block])
|
||||
|
||||
x = layers.Activation("relu", name="block3_sepconv1_act")(x)
|
||||
x = layers.SeparableConv2D(
|
||||
256, (3, 3), padding="same", use_bias=False, name="block3_sepconv1"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block3_sepconv1_bn")(
|
||||
x
|
||||
)
|
||||
x = layers.Activation("relu", name="block3_sepconv2_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
256, (3, 3), padding="same", use_bias=False, name="block3_sepconv2"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block3_sepconv2_bn")(x)
|
||||
|
||||
x = layers.MaxPooling2D(
|
||||
(3, 3), strides=(2, 2), padding="same", name="block3_pool"
|
||||
)(x)
|
||||
x = layers.add([x, residual])
|
||||
|
||||
residual = layers.Conv2D(
|
||||
728, (1, 1), strides=(2, 2), padding="same", use_bias=False
|
||||
)(x)
|
||||
residual = layers.BatchNormalization(axis=channel_axis)(residual)
|
||||
|
||||
# 注意力機制區域
|
||||
kernel = int(abs((math.log(residual.shape[-1], 2) + 1) / 2))
|
||||
if kernel % 2:
|
||||
kernel_size = kernel
|
||||
else:
|
||||
kernel_size = kernel + 1
|
||||
|
||||
block = layers.GlobalAveragePooling2D()(residual)
|
||||
block = Reshape(target_shape = (residual.shape[-1], 1))(block)
|
||||
block = Conv1D(filters = 1, kernel_size = kernel_size, padding = "same", use_bias = False, activation = "sigmoid")(block)
|
||||
block = Reshape((1, 1, residual.shape[-1]))(block)
|
||||
residual = Multiply()([residual, block])
|
||||
|
||||
x = layers.Activation("relu", name="block4_sepconv1_act")(x)
|
||||
x = layers.SeparableConv2D(
|
||||
728, (3, 3), padding="same", use_bias=False, name="block4_sepconv1"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block4_sepconv1_bn")(
|
||||
x
|
||||
)
|
||||
x = layers.Activation("relu", name="block4_sepconv2_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
728, (3, 3), padding="same", use_bias=False, name="block4_sepconv2"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(axis=channel_axis, name="block4_sepconv2_bn")(
|
||||
x
|
||||
)
|
||||
|
||||
x = layers.MaxPooling2D(
|
||||
(3, 3), strides=(2, 2), padding="same", name="block4_pool"
|
||||
)(x)
|
||||
x = layers.add([x, residual])
|
||||
|
||||
for i in range(8):
|
||||
residual = x
|
||||
prefix = "block" + str(i + 5)
|
||||
|
||||
x = layers.Activation("relu", name=prefix + "_sepconv1_act")(x)
|
||||
x = layers.SeparableConv2D(
|
||||
728,
|
||||
(3, 3),
|
||||
padding="same",
|
||||
use_bias=False,
|
||||
name=prefix + "_sepconv1",
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name=prefix + "_sepconv1_bn"
|
||||
)(x)
|
||||
x = layers.Activation("relu", name=prefix + "_sepconv2_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
728,
|
||||
(3, 3),
|
||||
padding="same",
|
||||
use_bias=False,
|
||||
name=prefix + "_sepconv2",
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name=prefix + "_sepconv2_bn"
|
||||
)(x)
|
||||
x = layers.Activation("relu", name=prefix + "_sepconv3_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
728,
|
||||
(3, 3),
|
||||
padding="same",
|
||||
use_bias=False,
|
||||
name=prefix + "_sepconv3",
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name=prefix + "_sepconv3_bn"
|
||||
)(x)
|
||||
|
||||
x = layers.add([x, residual])
|
||||
|
||||
residual = layers.Conv2D(
|
||||
1024, (1, 1), strides=(2, 2), padding="same", use_bias=False
|
||||
)(x)
|
||||
residual = layers.BatchNormalization(axis=channel_axis)(residual)
|
||||
|
||||
# 注意力機制區域
|
||||
kernel = int(abs((math.log(residual.shape[-1], 2) + 1) / 2))
|
||||
if kernel % 2:
|
||||
kernel_size = kernel
|
||||
else:
|
||||
kernel_size = kernel + 1
|
||||
|
||||
block = layers.GlobalAveragePooling2D()(residual)
|
||||
block = Reshape(target_shape = (residual.shape[-1], 1))(block)
|
||||
block = Conv1D(filters = 1, kernel_size = kernel_size, padding = "same", use_bias = False, activation = "sigmoid")(block)
|
||||
block = Reshape((1, 1, residual.shape[-1]))(block)
|
||||
residual = Multiply()([residual, block])
|
||||
|
||||
x = layers.Activation("relu", name="block13_sepconv1_act")(x)
|
||||
x = layers.SeparableConv2D(
|
||||
728, (3, 3), padding="same", use_bias=False, name="block13_sepconv1"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name="block13_sepconv1_bn"
|
||||
)(x)
|
||||
x = layers.Activation("relu", name="block13_sepconv2_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
1024, (3, 3), padding="same", use_bias=False, name="block13_sepconv2"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name="block13_sepconv2_bn"
|
||||
)(x)
|
||||
|
||||
x = layers.MaxPooling2D(
|
||||
(3, 3), strides=(2, 2), padding="same", name="block13_pool"
|
||||
)(x)
|
||||
x = layers.add([x, residual])
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
1536, (3, 3), padding="same", use_bias=False, name="block14_sepconv1"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name="block14_sepconv1_bn"
|
||||
)(x)
|
||||
x = layers.Activation("relu", name="block14_sepconv1_act")(x)
|
||||
|
||||
x = layers.SeparableConv2D(
|
||||
2048, (3, 3), padding="same", use_bias=False, name="block14_sepconv2"
|
||||
)(x)
|
||||
x = layers.BatchNormalization(
|
||||
axis=channel_axis, name="block14_sepconv2_bn"
|
||||
)(x)
|
||||
x = layers.Activation("relu", name="block14_sepconv2_act")(x)
|
||||
|
||||
return img_input, block
|
||||
BIN
application/__pycache__/Xception.cpython-39.pyc
Normal file
BIN
application/__pycache__/Xception.cpython-39.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/Xception_indepentment.cpython-310.pyc
Normal file
BIN
application/__pycache__/Xception_indepentment.cpython-310.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/Xception_indepentment.cpython-311.pyc
Normal file
BIN
application/__pycache__/Xception_indepentment.cpython-311.pyc
Normal file
Binary file not shown.
BIN
application/__pycache__/Xception_indepentment.cpython-39.pyc
Normal file
BIN
application/__pycache__/Xception_indepentment.cpython-39.pyc
Normal file
Binary file not shown.
|
|
@ -10,109 +10,142 @@ from Load_process.file_processing import Process_File
|
|||
|
||||
class GradCAM:
|
||||
def __init__(self, model, target_layer):
|
||||
"""
|
||||
初始化 Grad-CAM
|
||||
Args:
|
||||
model: 訓練好的 ModifiedXception 模型
|
||||
target_layer: 要計算 Grad-CAM 的目標層名稱 (例如 'base_model')
|
||||
"""
|
||||
self.model = model
|
||||
self.target_layer = target_layer
|
||||
self.activations = None
|
||||
self.gradients = None
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
self.model.to(self.device) # Ensure model is on the correct device
|
||||
|
||||
# Register hooks
|
||||
self.target_layer.register_forward_hook(self.save_activations)
|
||||
self.target_layer.register_backward_hook(self.save_gradients)
|
||||
|
||||
def Processing_Main(self, Test_Dataloader, File_Path):
|
||||
File = Process_File()
|
||||
for batch_idx, (images, labels, File_Name, File_Classes) in enumerate(Test_Dataloader):
|
||||
# Move data to device
|
||||
images = images.to(self.device, dtype=torch.float32) # [64, C, H, W]
|
||||
labels = labels.to(self.device, dtype=torch.float32) # [64, num_classes]
|
||||
|
||||
# Get ground-truth class indices
|
||||
label_classes = torch.argmax(labels, dim=1).cpu().numpy() # [64]
|
||||
|
||||
# Generate Grad-CAM heatmaps for the entire batch
|
||||
heatmaps = self.generate(images, label_classes)
|
||||
|
||||
# Process each image in the batch
|
||||
for i in range(images.size(0)): # Loop over batch size (64)
|
||||
heatmap = heatmaps[i] # Extract heatmap for this image
|
||||
overlaid_image = self.overlay_heatmap(heatmap, images[i], alpha=0.5)
|
||||
|
||||
# Create file path based on class
|
||||
path = f"{File_Path}/{File_Classes[i]}"
|
||||
File.JudgeRoot_MakeDir(path)
|
||||
# Save overlaid image
|
||||
File.Save_CV2_File(f"batch_{batch_idx}_{File_Name[i]}", path, overlaid_image)
|
||||
# # Save raw heatmap separately
|
||||
# heatmap_resized = cv2.resize(heatmap, (images[i].shape[2], images[i].shape[1]), interpolation=cv2.INTER_CUBIC)
|
||||
# heatmap_colored = (plt.cm.viridis(heatmap_resized)[:, :, :3] * 255).astype(np.uint8)
|
||||
# File.Save_CV2_File(f"batch_{batch_idx}_img_{i}_heatmap.png", path, heatmap_colored)
|
||||
|
||||
def save_activations(self, module, input, output):
|
||||
self.activations = output.detach() # [64, C, H', W']
|
||||
|
||||
def save_gradients(self, module, grad_input, grad_output):
|
||||
self.gradients = grad_output[0].detach() # [64, C, H', W']
|
||||
|
||||
def generate(self, input_images, class_indices=None):
|
||||
self.model.eval()
|
||||
input_images.requires_grad = True # [64, C, H, W]
|
||||
self.model.to(self.device)
|
||||
|
||||
# Forward pass
|
||||
outputs = self.model(input_images) # [64, num_classes]
|
||||
# 用於儲存特徵圖和梯度
|
||||
self.features = None
|
||||
self.gradients = None
|
||||
|
||||
if class_indices is None:
|
||||
class_indices = torch.argmax(outputs, dim=1).cpu().numpy() # [64]
|
||||
# 註冊 hook
|
||||
self._register_hooks()
|
||||
|
||||
# Zero gradients
|
||||
def _register_hooks(self):
|
||||
"""註冊前向和反向傳播的 hook"""
|
||||
def forward_hook(module, input, output):
|
||||
self.features = output
|
||||
|
||||
def backward_hook(module, grad_in, grad_out):
|
||||
self.gradients = grad_out[0]
|
||||
|
||||
# 獲取目標層
|
||||
target_module = dict(self.model.named_modules())[self.target_layer]
|
||||
target_module.register_forward_hook(forward_hook)
|
||||
target_module.register_backward_hook(backward_hook)
|
||||
|
||||
def generate_cam(self, input_image, target_class=None):
|
||||
"""
|
||||
生成 Grad-CAM 熱力圖
|
||||
Args:
|
||||
input_image: 輸入影像 (torch.Tensor, shape: [1, C, H, W])
|
||||
target_class: 目標類別索引 (若為 None,使用預測最高分數的類別)
|
||||
Returns:
|
||||
cam: Grad-CAM 熱力圖 (numpy array)
|
||||
"""
|
||||
input_image = input_image.to(self.device)
|
||||
|
||||
# 前向傳播
|
||||
output = self.model(input_image)
|
||||
|
||||
if target_class is None:
|
||||
target_class = torch.argmax(output, dim=1).item()
|
||||
|
||||
# 清除梯度
|
||||
self.model.zero_grad()
|
||||
|
||||
# Backward pass for each image in the batch
|
||||
heatmaps = []
|
||||
for i in range(input_images.size(0)):
|
||||
self.model.zero_grad()
|
||||
outputs[i, class_indices[i]].backward(retain_graph=True) # Backward for specific image/class
|
||||
heatmap = self._compute_heatmap()
|
||||
heatmaps.append(heatmap)
|
||||
# 反向傳播計算梯度
|
||||
one_hot = torch.zeros_like(output)
|
||||
one_hot[0][target_class] = 1
|
||||
output.backward(gradient=one_hot, retain_graph=True)
|
||||
|
||||
return np.stack(heatmaps) # [64, H', W']
|
||||
# 計算 Grad-CAM
|
||||
gradients = self.gradients.data.cpu().numpy()[0]
|
||||
features = self.features.data.cpu().numpy()[0]
|
||||
|
||||
def _compute_heatmap(self):
|
||||
# Get gradients and activations
|
||||
gradients = self.gradients # [64, C, H', W']
|
||||
activations = self.activations # [64, C, H', W']
|
||||
# 全局平均池化梯度
|
||||
weights = np.mean(gradients, axis=(1, 2))
|
||||
|
||||
# Compute weights (global average pooling of gradients)
|
||||
weights = torch.mean(gradients, dim=[2, 3], keepdim=True) # [64, C, 1, 1]
|
||||
# 計算加權和
|
||||
cam = np.zeros(features.shape[1:], dtype=np.float32)
|
||||
for i, w in enumerate(weights):
|
||||
cam += w * features[i]
|
||||
|
||||
# Compute Grad-CAM heatmap for one image (after single backward)
|
||||
grad_cam = torch.sum(weights * activations, dim=1)[0] # [64, H', W'] -> [H', W']
|
||||
grad_cam = F.relu(grad_cam) # Apply ReLU
|
||||
grad_cam = grad_cam / (grad_cam.max() + 1e-8) # Normalize to [0, 1]
|
||||
# ReLU 激活
|
||||
cam = np.maximum(cam, 0)
|
||||
|
||||
# Apply Gaussian smoothing to reduce artifacts
|
||||
grad_cam_np = grad_cam.cpu().numpy()
|
||||
grad_cam_np = cv2.GaussianBlur(grad_cam_np, (5, 5), 0)
|
||||
# Re-normalize after blur
|
||||
grad_cam_np = grad_cam_np / (grad_cam_np.max() + 1e-8)
|
||||
# 歸一化到 0-1
|
||||
cam = cam - np.min(cam)
|
||||
cam = cam / np.max(cam)
|
||||
|
||||
return grad_cam_np
|
||||
# 調整大小到輸入影像尺寸
|
||||
h, w = input_image.shape[2:]
|
||||
cam = cv2.resize(cam, (w, h))
|
||||
|
||||
def overlay_heatmap(self, heatmap, image, alpha=0.5):
|
||||
# Resize heatmap to match input image spatial dimensions using INTER_CUBIC for smoother results
|
||||
heatmap = np.uint8(255 * heatmap) # Scale to [0, 255]
|
||||
heatmap = cv2.resize(heatmap, (image.shape[2], image.shape[1]), interpolation=cv2.INTER_CUBIC)
|
||||
# Use viridis colormap for better interpretability
|
||||
heatmap = plt.cm.viridis(heatmap)[:, :, :3] # Apply viridis colormap
|
||||
return cam
|
||||
|
||||
# Convert image tensor to numpy and denormalize (assuming ImageNet stats)
|
||||
image_np = image.detach().cpu().permute(1, 2, 0).numpy() # [H, W, C]
|
||||
# Ensure image is in [0, 1] range (if not already)
|
||||
if image_np.max() > 1.0:
|
||||
image_np = (image_np - image_np.min()) / (image_np.max() - image_np.min())
|
||||
def overlay_cam(self, original_image, cam, alpha=0.5):
|
||||
"""
|
||||
將 Grad-CAM 熱力圖疊加到原始影像上
|
||||
Args:
|
||||
original_image: 原始影像 (numpy array, shape: [H, W, C])
|
||||
cam: Grad-CAM 熱力圖
|
||||
alpha: 透明度
|
||||
Returns:
|
||||
overlay_img: 疊加後的影像
|
||||
"""
|
||||
# 將熱力圖轉為 RGB
|
||||
heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)
|
||||
heatmap = np.float32(heatmap) / 255
|
||||
|
||||
# Overlay heatmap on the image
|
||||
overlay = alpha * heatmap + (1 - alpha) * image_np
|
||||
overlay = np.clip(overlay, 0, 1) * 255
|
||||
return overlay.astype(np.uint8) # Return uint8 for cv2
|
||||
# 確保原始影像格式正確
|
||||
if original_image.max() > 1:
|
||||
original_image = original_image / 255.0
|
||||
|
||||
# 疊加熱力圖
|
||||
overlay_img = heatmap * alpha + original_image * (1 - alpha)
|
||||
overlay_img = np.clip(overlay_img, 0, 1)
|
||||
|
||||
return overlay_img
|
||||
|
||||
def visualize(self, input_image, original_image, target_class=None, File_Name=None, model_name = None):
|
||||
"""
|
||||
可視化 Grad-CAM 結果
|
||||
Args:
|
||||
input_image: 輸入影像 (torch.Tensor)
|
||||
original_image: 原始影像 (numpy array)
|
||||
target_class: 目標類別索引
|
||||
save_path: 保存路徑 (可選)
|
||||
"""
|
||||
File = Process_File()
|
||||
# 生成 CAM
|
||||
cam = self.generate_cam(input_image, target_class)
|
||||
|
||||
# 疊加到原始影像
|
||||
overlay = self.overlay_cam(original_image, cam)
|
||||
|
||||
# 顯示結果
|
||||
plt.figure(figsize=(10, 5))
|
||||
plt.subplot(1, 2, 1)
|
||||
plt.imshow(original_image)
|
||||
plt.title('Original Image')
|
||||
plt.axis('off')
|
||||
|
||||
plt.subplot(1, 2, 2)
|
||||
plt.imshow(overlay)
|
||||
plt.title(f'Grad-CAM (Class {target_class})')
|
||||
plt.axis('off')
|
||||
|
||||
model_dir = '../Result/Grad-CAM( ' + str(datetime.date.today()) + " )"
|
||||
File.JudgeRoot_MakeDir(model_dir)
|
||||
modelfiles = File.Make_Save_Root(str(model_name) + " " + File_Name + ".png", model_dir)
|
||||
plt.savefig(modelfiles)
|
||||
plt.close("all") # 關閉圖表
|
||||
|
|
|
|||
|
|
@ -1,195 +0,0 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
import cv2
|
||||
import matplotlib.pyplot as plt
|
||||
from Load_process.file_processing import Process_File
|
||||
|
||||
class SaliencyMap:
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
self.model.to(self.device)
|
||||
self.model.eval() # 設置為評估模式
|
||||
|
||||
def Processing_Main(self, Test_Dataloader, File_Path):
|
||||
"""處理測試數據集並生成顯著性圖"""
|
||||
File = Process_File()
|
||||
|
||||
for batch_idx, (images, labels, File_Name, File_Classes) in enumerate(Test_Dataloader):
|
||||
# 將數據移至設備
|
||||
images = images.to(self.device, dtype=torch.float32)
|
||||
labels = labels.to(self.device, dtype=torch.float32)
|
||||
|
||||
# 獲取真實類別索引
|
||||
label_classes = torch.argmax(labels, dim=1).cpu().numpy()
|
||||
|
||||
# 為批次中的每個圖像生成顯著性圖
|
||||
for i in range(images.size(0)):
|
||||
# 獲取單個圖像和類別
|
||||
image = images[i:i+1] # 保持批次維度
|
||||
target_class = label_classes[i]
|
||||
|
||||
# 生成顯著性圖
|
||||
saliency_map = self.generate_saliency(image, target_class)
|
||||
|
||||
# 將顯著性圖疊加到原始圖像上
|
||||
overlaid_image = self.overlay_saliency(saliency_map, image[0])
|
||||
|
||||
# 創建保存路徑
|
||||
path = f"{File_Path}/{File_Classes[i]}"
|
||||
File.JudgeRoot_MakeDir(path)
|
||||
|
||||
# 保存結果
|
||||
File.Save_CV2_File(f"saliency_{batch_idx}_{File_Name[i]}", path, overlaid_image)
|
||||
|
||||
def generate_saliency(self, image, target_class):
|
||||
"""生成單個圖像的顯著性圖"""
|
||||
# 確保需要梯度
|
||||
image.requires_grad_(True)
|
||||
|
||||
# 前向傳播
|
||||
output = self.model(image)
|
||||
|
||||
# 清除之前的梯度
|
||||
self.model.zero_grad()
|
||||
|
||||
# 創建one-hot編碼的目標
|
||||
one_hot = torch.zeros_like(output)
|
||||
one_hot[0, target_class] = 1
|
||||
|
||||
# 反向傳播
|
||||
output.backward(gradient=one_hot)
|
||||
|
||||
# 獲取梯度
|
||||
gradients = image.grad.data
|
||||
|
||||
# 計算顯著性圖 (取絕對值並在通道維度上取最大值)
|
||||
saliency, _ = torch.max(gradients.abs(), dim=1)
|
||||
|
||||
# 轉換為numpy並歸一化
|
||||
saliency_np = saliency.cpu().numpy()[0]
|
||||
saliency_np = self._normalize(saliency_np)
|
||||
|
||||
# 應用平滑處理以減少噪聲
|
||||
saliency_np = cv2.GaussianBlur(saliency_np, (5, 5), 0)
|
||||
saliency_np = self._normalize(saliency_np) # 再次歸一化
|
||||
|
||||
return saliency_np
|
||||
|
||||
def _normalize(self, x):
|
||||
"""將數組歸一化到[0,1]範圍"""
|
||||
# 添加小的epsilon以避免除以零
|
||||
return (x - x.min()) / (x.max() - x.min() + 1e-8)
|
||||
|
||||
def overlay_saliency(self, saliency, image, alpha=0.5):
|
||||
"""將顯著性圖疊加到原始圖像上"""
|
||||
# 將顯著性圖縮放到[0,255]範圍
|
||||
saliency_uint8 = np.uint8(255 * saliency)
|
||||
|
||||
# 應用顏色映射
|
||||
heatmap = cv2.applyColorMap(saliency_uint8, cv2.COLORMAP_JET)
|
||||
|
||||
# 將圖像張量轉換為numpy數組
|
||||
image_np = image.detach().cpu().permute(1, 2, 0).numpy()
|
||||
|
||||
# 確保圖像在[0,1]範圍內
|
||||
if image_np.max() > 1.0:
|
||||
image_np = (image_np - image_np.min()) / (image_np.max() - image_np.min())
|
||||
|
||||
# 將圖像轉換為uint8
|
||||
image_uint8 = np.uint8(255 * image_np)
|
||||
|
||||
# 如果圖像是單通道的,轉換為3通道
|
||||
if len(image_uint8.shape) == 2 or image_uint8.shape[2] == 1:
|
||||
image_uint8 = cv2.cvtColor(image_uint8, cv2.COLOR_GRAY2BGR)
|
||||
|
||||
# 疊加顯著性圖和原始圖像
|
||||
overlaid = cv2.addWeighted(heatmap, alpha, image_uint8, 1-alpha, 0)
|
||||
|
||||
return overlaid
|
||||
|
||||
def generate_smooth_saliency(self, image, target_class, n_samples=20, noise_level=0.1):
|
||||
"""使用SmoothGrad技術生成更平滑的顯著性圖"""
|
||||
# 獲取輸入圖像的標準差
|
||||
stdev = noise_level * (torch.max(image) - torch.min(image)).item()
|
||||
|
||||
# 累積梯度
|
||||
accumulated_gradients = None
|
||||
|
||||
# 生成多個帶噪聲的樣本並計算梯度
|
||||
for _ in range(n_samples):
|
||||
# 添加高斯噪聲
|
||||
noisy_image = image + torch.randn_like(image) * stdev
|
||||
noisy_image.requires_grad_(True)
|
||||
|
||||
# 前向傳播
|
||||
output = self.model(noisy_image)
|
||||
|
||||
# 反向傳播
|
||||
self.model.zero_grad()
|
||||
one_hot = torch.zeros_like(output)
|
||||
one_hot[0, target_class] = 1
|
||||
output.backward(gradient=one_hot)
|
||||
|
||||
# 獲取梯度
|
||||
gradients = noisy_image.grad.data
|
||||
|
||||
# 累積梯度
|
||||
if accumulated_gradients is None:
|
||||
accumulated_gradients = gradients
|
||||
else:
|
||||
accumulated_gradients += gradients
|
||||
|
||||
# 計算平均梯度
|
||||
avg_gradients = accumulated_gradients / n_samples
|
||||
|
||||
# 計算顯著性圖
|
||||
saliency, _ = torch.max(avg_gradients.abs(), dim=1)
|
||||
|
||||
# 轉換為numpy並歸一化
|
||||
saliency_np = saliency.cpu().numpy()[0]
|
||||
saliency_np = self._normalize(saliency_np)
|
||||
|
||||
return saliency_np
|
||||
|
||||
def generate_guided_saliency(self, image, target_class):
|
||||
"""使用Guided Backpropagation生成顯著性圖"""
|
||||
# 保存原始ReLU反向傳播函數
|
||||
relu_backward_functions = {}
|
||||
for module in self.model.modules():
|
||||
if isinstance(module, nn.ReLU):
|
||||
relu_backward_functions[module] = module.backward
|
||||
module.backward = self._guided_relu_backward
|
||||
|
||||
# 生成顯著性圖
|
||||
image.requires_grad_(True)
|
||||
output = self.model(image)
|
||||
|
||||
self.model.zero_grad()
|
||||
one_hot = torch.zeros_like(output)
|
||||
one_hot[0, target_class] = 1
|
||||
output.backward(gradient=one_hot)
|
||||
|
||||
# 獲取梯度
|
||||
gradients = image.grad.data
|
||||
|
||||
# 恢復原始ReLU反向傳播函數
|
||||
for module in relu_backward_functions:
|
||||
module.backward = relu_backward_functions[module]
|
||||
|
||||
# 計算顯著性圖 (只保留正梯度)
|
||||
saliency = torch.clamp(gradients, min=0)
|
||||
saliency, _ = torch.max(saliency, dim=1)
|
||||
|
||||
# 轉換為numpy並歸一化
|
||||
saliency_np = saliency.cpu().numpy()[0]
|
||||
saliency_np = self._normalize(saliency_np)
|
||||
|
||||
return saliency_np
|
||||
|
||||
def _guided_relu_backward(self, grad_output):
|
||||
"""Guided ReLU的反向傳播函數"""
|
||||
# 只允許正梯度流過
|
||||
positive_grad_output = torch.clamp(grad_output, min=0)
|
||||
return positive_grad_output
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -5,51 +5,33 @@ import matplotlib.figure as figure
|
|||
import matplotlib.backends.backend_agg as agg
|
||||
from Load_process.file_processing import Process_File
|
||||
|
||||
def plot_history(Losses, Accuracys, Save_Root, File_Name):
|
||||
def plot_history(Epochs, Losses, Accuracys, file_name, model_name):
|
||||
File = Process_File()
|
||||
|
||||
plt.figure(figsize=(16,4))
|
||||
plt.subplot(1,2,1)
|
||||
|
||||
# 修正維度不匹配問題
|
||||
train_losses = Losses[0]
|
||||
val_losses = Losses[1]
|
||||
|
||||
# 分別繪製訓練損失和驗證損失
|
||||
train_epochs = range(1, len(train_losses) + 1)
|
||||
plt.plot(train_epochs, train_losses, label='Train')
|
||||
|
||||
val_epochs = range(1, len(val_losses) + 1)
|
||||
plt.plot(val_epochs, val_losses, label='Validation')
|
||||
|
||||
plt.plot(range(1, Epochs + 1), Losses[0])
|
||||
plt.plot(range(1, Epochs + 1), Losses[1])
|
||||
plt.ylabel('Losses')
|
||||
plt.xlabel('epoch')
|
||||
plt.legend(loc='upper left')
|
||||
plt.legend(['Train','Validation'], loc='upper left')
|
||||
plt.title('Model Loss')
|
||||
|
||||
if Accuracys is not None:
|
||||
plt.subplot(1,2,2)
|
||||
train_acc = Accuracys[0]
|
||||
val_acc = Accuracys[1]
|
||||
plt.subplot(1,2,2)
|
||||
plt.plot(range(1, Epochs + 1), Accuracys[0])
|
||||
plt.plot(range(1, Epochs + 1), Accuracys[1])
|
||||
plt.ylabel('Accuracies')
|
||||
plt.xlabel('epoch')
|
||||
plt.legend(['Train','Validation'], loc='upper left')
|
||||
plt.title('Model Accuracy')
|
||||
|
||||
# 分別繪製訓練準確率和驗證準確率
|
||||
train_epochs_acc = range(1, len(train_acc) + 1)
|
||||
plt.plot(train_epochs_acc, train_acc, label='Train')
|
||||
|
||||
val_epochs_acc = range(1, len(val_acc) + 1)
|
||||
plt.plot(val_epochs_acc, val_acc, label='Validation')
|
||||
|
||||
plt.ylabel('Accuracies')
|
||||
plt.xlabel('epoch')
|
||||
plt.legend(loc='upper left')
|
||||
plt.title('Model Accuracy')
|
||||
|
||||
File.JudgeRoot_MakeDir(Save_Root)
|
||||
modelfiles = File.Make_Save_Root(f"{str(File_Name)}.png", Save_Root)
|
||||
model_dir = '../Result/save_the_train_image( ' + str(datetime.date.today()) + " )"
|
||||
File.JudgeRoot_MakeDir(model_dir)
|
||||
modelfiles = File.Make_Save_Root(str(model_name) + " " + str(file_name) + ".png", model_dir)
|
||||
plt.savefig(modelfiles)
|
||||
plt.close("all") # 關閉圖表
|
||||
|
||||
def draw_heatmap(matrix, Save_Root, File_Name, index): # 二分類以上混淆矩陣做法
|
||||
def draw_heatmap(matrix, model_name, index): # 二分類以上混淆矩陣做法
|
||||
File = Process_File()
|
||||
|
||||
# 创建热图
|
||||
|
|
@ -58,19 +40,20 @@ def draw_heatmap(matrix, Save_Root, File_Name, index): # 二分類以上混淆
|
|||
Ax = fig.add_subplot(111)
|
||||
sns.heatmap(matrix, square = True, annot = True, fmt = 'd', linecolor = 'white', cmap = "Purples", ax = Ax)#画热力图,cmap表示设定的颜色集
|
||||
|
||||
File.JudgeRoot_MakeDir(Save_Root)
|
||||
modelfiles = File.Make_Save_Root(f"{File_Name}-{str(index)}.png", Save_Root)
|
||||
model_dir = '../Result/model_matrix_image ( ' + str(datetime.date.today()) + " )"
|
||||
File.JudgeRoot_MakeDir(model_dir)
|
||||
modelfiles = File.Make_Save_Root(str(model_name) + "-" + str(index) + ".png", model_dir)
|
||||
|
||||
# confusion.figure.savefig(modelfiles)
|
||||
# 设置图像参数
|
||||
Ax.set_title(f"{File_Name} confusion matrix")
|
||||
Ax.set_title(str(model_name) + " confusion matrix")
|
||||
Ax.set_xlabel("X-Predict label of the model")
|
||||
Ax.set_ylabel("Y-True label of the model")
|
||||
|
||||
# 保存图像到文件中
|
||||
canvas.print_figure(modelfiles)
|
||||
|
||||
def Confusion_Matrix_of_Two_Classification(Matrix, Save_Root, File_Name, index):
|
||||
def Confusion_Matrix_of_Two_Classification(Model_Name, Matrix, index):
|
||||
File = Process_File()
|
||||
|
||||
fx = sns.heatmap(Matrix, annot=True, cmap='turbo')
|
||||
|
|
@ -80,20 +63,13 @@ def Confusion_Matrix_of_Two_Classification(Matrix, Save_Root, File_Name, index):
|
|||
fx.set_xlabel('answer Values ')
|
||||
fx.set_ylabel('Predicted Values')
|
||||
|
||||
# 根据矩阵维度动态设置标签
|
||||
n_classes = Matrix.shape[0]
|
||||
# 如果是2类问题,使用False/True标签
|
||||
if n_classes == 2:
|
||||
labels = ['False', 'True']
|
||||
else:
|
||||
# 对于多类问题,使用数字标签
|
||||
labels = [str(i) for i in range(n_classes)]
|
||||
# labels the boxes
|
||||
fx.xaxis.set_ticklabels(['False','True'])
|
||||
fx.yaxis.set_ticklabels(['False','True'])
|
||||
|
||||
fx.xaxis.set_ticklabels(labels)
|
||||
fx.yaxis.set_ticklabels(labels)
|
||||
|
||||
File.JudgeRoot_MakeDir(Save_Root)
|
||||
modelfiles = File.Make_Save_Root(f"{File_Name}-{str(index)}.png", Save_Root)
|
||||
model_dir = '../Result/model_matrix_image ( ' + str(datetime.date.today()) + " )"
|
||||
File.JudgeRoot_MakeDir(model_dir)
|
||||
modelfiles = File.Make_Save_Root(str(Model_Name) + "-" + str(index) + ".png", model_dir)
|
||||
|
||||
plt.savefig(modelfiles)
|
||||
plt.close("all") # 關閉圖表
|
||||
|
|
|
|||
194
experiments/Model_All_Step.py
Normal file
194
experiments/Model_All_Step.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
from tqdm import tqdm
|
||||
import torch
|
||||
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
|
||||
from sklearn.model_selection import KFold
|
||||
from torchmetrics.functional import auroc
|
||||
import torch.optim as optim
|
||||
import numpy as np
|
||||
from all_models_tools.all_model_tools import call_back
|
||||
from Model_Loss.Loss import Entropy_Loss
|
||||
from merge_class.merge import merge
|
||||
from draw_tools.Grad_cam import GradCAM
|
||||
from torch.utils.data import Subset, DataLoader
|
||||
import time
|
||||
|
||||
|
||||
class All_Step:
|
||||
def __init__(self, PreProcess_Classes_Data, Batch, Model, Epoch, Number_Of_Classes, Model_Name):
|
||||
self.PreProcess_Classes_Data = PreProcess_Classes_Data
|
||||
self.Training_DataLoader, self.Test_Dataloader = self.PreProcess_Classes_Data.Total_Data_Combine_To_DataLoader(Batch)
|
||||
|
||||
self.Model = Model
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
|
||||
self.Epoch = Epoch
|
||||
self.Number_Of_Classes = Number_Of_Classes
|
||||
|
||||
self.Model_Name = Model_Name
|
||||
|
||||
pass
|
||||
|
||||
def Training_Step(self, model_name, counter):
|
||||
# 定義優化器,並設定 weight_decay 參數來加入 L2 正則化
|
||||
Optimizer = optim.SGD(self.Model.parameters(), lr=0.045, momentum = 0.9, weight_decay=0.1)
|
||||
model_path, early_stopping, scheduler = call_back(model_name, counter, Optimizer)
|
||||
|
||||
criterion = Entropy_Loss() # 使用自定義的損失函數
|
||||
Merge_Function = merge()
|
||||
train_losses = []
|
||||
val_losses = []
|
||||
train_accuracies = []
|
||||
val_accuracies = []
|
||||
Total_Epoch = 0
|
||||
|
||||
K_Flod = KFold(n_splits = 5, shuffle = True, random_state = 42)
|
||||
|
||||
for epoch in range(self.Epoch): # 訓練迴圈
|
||||
self.Model.train() # 開始訓練
|
||||
running_loss = 0.0
|
||||
all_train_preds = []
|
||||
all_train_labels = []
|
||||
processed_samples = 0
|
||||
|
||||
# 計算每個 epoch 的起始時間
|
||||
start_time = time.time()
|
||||
total_samples = len(self.Training_DataLoader)
|
||||
train_subset = ""
|
||||
val_subset = ""
|
||||
|
||||
for fold, (train_idx, vali_idx) in enumerate( K_Flod.split(self.PreProcess_Classes_Data.Training_Datas)):
|
||||
# Create training and validation subsets for this fold
|
||||
train_subset = Subset(self.Training_DataLoader, train_idx)
|
||||
val_subset = Subset(self.Training_DataLoader, vali_idx)
|
||||
|
||||
Training_Data = DataLoader(train_subset, self.Training_DataLoader.batch_size, num_workers = 0, pin_memory=True, shuffle = True)
|
||||
|
||||
epoch_iterator = tqdm(Training_Data, desc=f"Epoch [{epoch}/{self.Epoch}]")
|
||||
|
||||
for inputs, labels in epoch_iterator:
|
||||
inputs, labels = torch.as_tensor(inputs).to(self.device), torch.as_tensor(labels).to(self.device)
|
||||
|
||||
Optimizer.zero_grad()
|
||||
outputs = self.Model(inputs)
|
||||
loss = criterion(outputs, labels)
|
||||
loss.backward()
|
||||
Optimizer.step()
|
||||
running_loss += loss.item()
|
||||
|
||||
# 收集訓練預測和標籤
|
||||
Output_Values, Output_Indexs = torch.max(outputs, dim = 1)
|
||||
True_Indexs = np.argmax(labels.cpu().numpy(), 1)
|
||||
|
||||
all_train_preds.append(Output_Indexs.cpu().numpy())
|
||||
all_train_labels.append(True_Indexs)
|
||||
|
||||
processed_samples += len(inputs)
|
||||
|
||||
# 計算當前進度
|
||||
progress = (processed_samples / total_samples) * 100
|
||||
|
||||
# 計算經過時間和剩餘時間
|
||||
elapsed_time = time.time() - start_time
|
||||
iterations_per_second = processed_samples / elapsed_time if elapsed_time > 0 else 0
|
||||
eta = (total_samples - processed_samples) / iterations_per_second if iterations_per_second > 0 else 0
|
||||
time_str = f"{int(elapsed_time//60):02d}:{int(elapsed_time%60):02d}<{int(eta//60):02d}:{int(eta%60):02d}"
|
||||
|
||||
# 計算當前批次的精確度(這裡需要根據你的具體需求調整)
|
||||
batch_accuracy = (Output_Indexs.cpu().numpy() == True_Indexs).mean()
|
||||
|
||||
# 更新進度條顯示
|
||||
epoch_iterator.set_description(f"Epoch [{epoch}/{self.Epoch}]")
|
||||
epoch_iterator.set_postfix_str(
|
||||
f"{processed_samples}/{total_samples} [{time_str}, {iterations_per_second:.2f}it/s, " +
|
||||
f"acc={batch_accuracy:.3f}, loss={loss.item():.3f}, ]"
|
||||
)
|
||||
|
||||
epoch_iterator.close()
|
||||
|
||||
all_train_preds = Merge_Function.merge_data_main(all_train_preds, 0, len(all_train_preds))
|
||||
all_train_labels = Merge_Function.merge_data_main(all_train_labels, 0, len(all_train_labels))
|
||||
|
||||
Training_Loss = running_loss / len(self.Training_DataLoader)
|
||||
train_accuracy = accuracy_score(all_train_labels, all_train_preds)
|
||||
|
||||
train_losses.append(Training_Loss)
|
||||
train_accuracies.append(train_accuracy)
|
||||
|
||||
self.Model.eval()
|
||||
val_loss = 0.0
|
||||
all_val_preds = []
|
||||
all_val_labels = []
|
||||
|
||||
with torch.no_grad():
|
||||
for inputs, labels in val_subset:
|
||||
inputs, labels = torch.as_tensor(inputs).to(self.device), torch.as_tensor(labels).to(self.device)
|
||||
|
||||
outputs = self.Model(inputs)
|
||||
loss = criterion(outputs, labels)
|
||||
val_loss += loss.item()
|
||||
|
||||
# 收集訓練預測和標籤
|
||||
Output_Values, Output_Indexs = torch.max(outputs, dim = 1)
|
||||
True_Indexs = np.argmax(labels.cpu().numpy(), 1)
|
||||
|
||||
all_val_preds.append(Output_Indexs.cpu().numpy())
|
||||
all_val_labels.append(True_Indexs)
|
||||
|
||||
val_loss /= len(val_subset)
|
||||
val_accuracy = accuracy_score(all_val_labels, all_val_preds)
|
||||
|
||||
val_losses.append(val_loss)
|
||||
val_accuracies.append(val_accuracy)
|
||||
# print(f"Val_loss: {val_loss:.4f}, Val_accuracy: {val_accuracy:0.2f}\n")
|
||||
|
||||
early_stopping(val_loss, self.Model, model_path)
|
||||
if early_stopping.early_stop:
|
||||
print("Early stopping triggered. Training stopped.")
|
||||
Total_Epoch = epoch
|
||||
break
|
||||
|
||||
# 學習率調整
|
||||
scheduler.step(val_loss)
|
||||
|
||||
return train_losses, val_losses, train_accuracies, val_accuracies, Total_Epoch
|
||||
|
||||
def Evaluate_Model(self, cnn_model, counter):
|
||||
# 測試模型
|
||||
cnn_model.eval()
|
||||
True_Label, Predict_Label = [], []
|
||||
True_Label_OneHot, Predict_Label_OneHot = [], []
|
||||
loss = 0.0
|
||||
|
||||
with torch.no_grad():
|
||||
for images, labels in self.Test_Dataloader:
|
||||
images, labels = torch.tensor(images).to(self.device), torch.tensor(labels).to(self.device)
|
||||
|
||||
outputs = cnn_model(images)
|
||||
|
||||
# 收集訓練預測和標籤
|
||||
Output_Values, Output_Indexs = torch.max(outputs, 1)
|
||||
True_Indexs = np.argmax(labels.cpu().numpy(), 1)
|
||||
|
||||
True_Label.append(Output_Indexs.cpu().numpy())
|
||||
Predict_Label.append(True_Indexs)
|
||||
|
||||
Predict_Label_OneHot.append(torch.tensor(outputs, dtype = torch.float32).cpu().numpy()[0])
|
||||
True_Label_OneHot.append(torch.tensor(labels, dtype = torch.int).cpu().numpy()[0])
|
||||
|
||||
# # 創建 GradCAM 實例
|
||||
# Layers = cnn_model.base_model.body.conv4.pointwise
|
||||
# grad_cam = GradCAM(cnn_model, target_layer="base_model")
|
||||
# # 可視化 Grad-CAM
|
||||
# grad_cam.visualize(outputs, images, target_class = 3, File_Name = counter, model_name = self.Model_Name)
|
||||
|
||||
loss /= len(self.Test_Dataloader)
|
||||
|
||||
True_Label_OneHot = torch.tensor(True_Label_OneHot, dtype = torch.int)
|
||||
Predict_Label_OneHot = torch.tensor(Predict_Label_OneHot, dtype = torch.float32)
|
||||
|
||||
accuracy = accuracy_score(True_Label, Predict_Label)
|
||||
precision = precision_score(True_Label, Predict_Label, average = "macro")
|
||||
recall = recall_score(True_Label, Predict_Label, average = "macro")
|
||||
AUC = auroc(Predict_Label_OneHot, True_Label_OneHot, num_labels = self.Number_Of_Classes, task = "multilabel", average = "macro")
|
||||
f1 = f1_score(True_Label, Predict_Label, average = "macro")
|
||||
return loss, accuracy, precision, recall, AUC, f1, True_Label, Predict_Label
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class Encode_Block(nn.Module):
|
||||
"""基本的卷積塊:Conv2d + BatchNorm + ReLU"""
|
||||
def __init__(self, in_channels, out_channels):
|
||||
super(Encode_Block, self).__init__()
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
|
||||
nn.BatchNorm2d(out_channels),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
|
||||
nn.BatchNorm2d(out_channels),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.conv(x)
|
||||
|
||||
class GastroSegNet(nn.Module):
|
||||
"""簡單的U-Net實現"""
|
||||
def __init__(self, in_channels=3, out_channels=3, features=[32, 64, 128, 256]):
|
||||
super(GastroSegNet, self).__init__()
|
||||
|
||||
# 編碼器(下採樣路徑)
|
||||
self.encoder = nn.ModuleList()
|
||||
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
|
||||
|
||||
# 第一層
|
||||
self.encoder.append(Encode_Block(in_channels, features[0]))
|
||||
|
||||
# 其他編碼層
|
||||
for i in range(1, len(features)):
|
||||
self.encoder.append(Encode_Block(features[i-1], features[i]))
|
||||
|
||||
# 瓶頸層(最底層)
|
||||
self.bottleneck = Encode_Block(features[-1], features[-1] * 2)
|
||||
|
||||
# 解碼器(上採樣路徑)
|
||||
self.decoder = nn.ModuleList()
|
||||
self.upconv = nn.ModuleList()
|
||||
|
||||
# 創建上採樣和解碼層
|
||||
for i in range(len(features)):
|
||||
self.upconv.append(
|
||||
nn.ConvTranspose2d(features[-1-i] * 2, features[-1-i], kernel_size=2, stride=2)
|
||||
)
|
||||
self.decoder.append(
|
||||
Encode_Block(features[-1-i] * 2, features[-1-i])
|
||||
)
|
||||
|
||||
# 最終輸出層
|
||||
self.final_conv = nn.Conv2d(features[0], out_channels, kernel_size=1)
|
||||
|
||||
def forward(self, x):
|
||||
# 存儲跳躍連接
|
||||
skip_connections = []
|
||||
|
||||
# 編碼器路徑
|
||||
for encoder_layer in self.encoder:
|
||||
x = encoder_layer(x)
|
||||
skip_connections.append(x)
|
||||
x = self.pool(x)
|
||||
|
||||
# 瓶頸層
|
||||
x = self.bottleneck(x)
|
||||
|
||||
# 反轉跳躍連接列表
|
||||
skip_connections = skip_connections[::-1]
|
||||
|
||||
# 解碼器路徑
|
||||
for i, (upconv, decoder) in enumerate(zip(self.upconv, self.decoder)):
|
||||
# 上採樣
|
||||
x = upconv(x)
|
||||
|
||||
# 獲取對應的跳躍連接
|
||||
skip = skip_connections[i]
|
||||
|
||||
# 如果尺寸不匹配,調整大小
|
||||
if x.shape != skip.shape:
|
||||
x = F.interpolate(x, size=skip.shape[2:], mode='bilinear', align_corners=False)
|
||||
|
||||
# 連接跳躍連接
|
||||
x = torch.cat([skip, x], dim=1)
|
||||
|
||||
# 通過解碼塊
|
||||
x = decoder(x)
|
||||
|
||||
# 最終輸出
|
||||
return self.final_conv(x)
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch
|
||||
|
||||
class SeparableConv2d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True):
|
||||
super(SeparableConv2d, self).__init__()
|
||||
self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size=kernel_size, stride=stride,
|
||||
padding=padding, groups=in_channels, bias=bias)
|
||||
self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1,
|
||||
padding=0, bias=bias)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.depthwise(x)
|
||||
x = self.pointwise(x)
|
||||
return x
|
||||
|
||||
class EntryFlow(nn.Module):
|
||||
def __init__(self, in_channels=3):
|
||||
super(EntryFlow, self).__init__()
|
||||
self.conv1 = nn.Conv2d(in_channels, 32, 3, stride=2, padding=1, bias=False, dilation = 2)
|
||||
self.bn1 = nn.BatchNorm2d(32)
|
||||
self.conv2 = nn.Conv2d(32, 64, 3, padding=1, bias=False, dilation = 2)
|
||||
self.bn2 = nn.BatchNorm2d(64)
|
||||
|
||||
self.conv3_residual = nn.Sequential(
|
||||
SeparableConv2d(64, 128, 3, padding=1),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(128, 128, 3, padding=1),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.MaxPool2d(3, stride=2, padding=1)
|
||||
)
|
||||
self.conv3_shortcut = nn.Conv2d(64, 128, 1, stride=2, bias=False)
|
||||
self.bn3 = nn.BatchNorm2d(128)
|
||||
|
||||
self.conv4_residual = nn.Sequential(
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(128, 256, 3, padding=1),
|
||||
nn.BatchNorm2d(256),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(256, 256, 3, padding=1),
|
||||
nn.BatchNorm2d(256),
|
||||
nn.MaxPool2d(3, stride=2, padding=1)
|
||||
)
|
||||
self.conv4_shortcut = nn.Conv2d(128, 256, 1, stride=2, bias=False)
|
||||
self.bn4 = nn.BatchNorm2d(256)
|
||||
|
||||
self.conv5_residual = nn.Sequential(
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(256, 728, 3, padding=1),
|
||||
nn.BatchNorm2d(728),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(728, 728, 3, padding=1),
|
||||
nn.BatchNorm2d(728),
|
||||
nn.MaxPool2d(3, stride=2, padding=1)
|
||||
)
|
||||
self.conv5_shortcut = nn.Conv2d(256, 728, 1, stride=2, bias=False)
|
||||
self.bn5 = nn.BatchNorm2d(728)
|
||||
|
||||
def forward(self, x):
|
||||
x = F.relu(self.bn1(self.conv1(x)))
|
||||
x = F.relu(self.bn2(self.conv2(x)))
|
||||
|
||||
residual = self.conv3_residual(x)
|
||||
shortcut = self.conv3_shortcut(x)
|
||||
x = F.relu(self.bn3(residual + shortcut))
|
||||
|
||||
residual = self.conv4_residual(x)
|
||||
shortcut = self.conv4_shortcut(x)
|
||||
x = F.relu(self.bn4(residual + shortcut))
|
||||
|
||||
residual = self.conv5_residual(x)
|
||||
shortcut = self.conv5_shortcut(x)
|
||||
x = F.relu(self.bn5(residual + shortcut))
|
||||
return x
|
||||
|
||||
class MiddleFlow(nn.Module):
|
||||
def __init__(self):
|
||||
super(MiddleFlow, self).__init__()
|
||||
self.conv_residual = nn.Sequential(
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(728, 728, 3, padding=1),
|
||||
nn.BatchNorm2d(728),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(728, 728, 3, padding=1),
|
||||
nn.BatchNorm2d(728),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(728, 728, 3, padding=1),
|
||||
nn.BatchNorm2d(728)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.conv_residual(x) + x
|
||||
|
||||
class ExitFlow(nn.Module):
|
||||
def __init__(self, num_classes=2):
|
||||
super(ExitFlow, self).__init__()
|
||||
self.conv1_residual = nn.Sequential(
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(728, 1024, 3, padding=1),
|
||||
nn.BatchNorm2d(1024),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(1024, 1024, 3, padding=1),
|
||||
nn.BatchNorm2d(1024),
|
||||
nn.MaxPool2d(3, stride=2, padding=1)
|
||||
)
|
||||
self.conv1_shortcut = nn.Conv2d(728, 1024, 1, stride=2, bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(1024)
|
||||
|
||||
self.conv2 = nn.Sequential(
|
||||
SeparableConv2d(1024, 1536, 3, padding=1),
|
||||
nn.BatchNorm2d(1536),
|
||||
nn.ReLU(inplace=False), # 修改這裡
|
||||
SeparableConv2d(1536, 2048, 3, padding=1),
|
||||
nn.BatchNorm2d(2048),
|
||||
nn.ReLU(inplace=False) # 修改這裡
|
||||
)
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
||||
self.Hidden = nn.Linear(2048, 1025)
|
||||
self.fc = nn.Linear(1025, num_classes)
|
||||
|
||||
def forward(self, x):
|
||||
residual = self.conv1_residual(x)
|
||||
shortcut = self.conv1_shortcut(x)
|
||||
x = F.relu(self.bn1(residual + shortcut))
|
||||
|
||||
x = self.conv2(x)
|
||||
x = self.avgpool(x)
|
||||
x = x.view(x.size(0), -1)
|
||||
x = self.Hidden(x)
|
||||
x = self.fc(x)
|
||||
return x
|
||||
|
||||
class Xception(nn.Module):
|
||||
def __init__(self, num_classes=2):
|
||||
super(Xception, self).__init__()
|
||||
self.entry_flow = EntryFlow(in_channels=3) # 默认输入通道为3
|
||||
self.middle_flow = nn.Sequential(*[MiddleFlow() for _ in range(8)])
|
||||
self.exit_flow = ExitFlow(num_classes)
|
||||
|
||||
def forward(self, x):
|
||||
# 正常的前向傳播
|
||||
x = self.entry_flow(x)
|
||||
x = self.middle_flow(x)
|
||||
x = self.exit_flow(x)
|
||||
|
||||
return x
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user