Advertisement

Faster RCNN 对肺结节 目标检测

阅读量:

本项目介绍了基于Faster R-CNN模型对肺结节进行目标检测的应用。以下是核心内容:
项目结构:

  • 数据集路径:data 包含训练图像和标签。
  • 工具函数:包括从XML文件读取类别和边界框(getlabelfrom_xml)、图像归一化(normalization)以及计算iou值(miou)。
  • 数据加载:使用自定义的数据加载器LungDetection处理DCM格式图像并进行预处理。
    模型与训练:
  • 模型选择:使用Faster R-CNN-Mobilenet V3 backbone架构。
  • 超参数:包括批量大小、学习率(0.00001)、设备设置等。
  • 训练流程:采用交叉熵损失+iou损失作为总损失函数,并通过Adam优化器进行优化。
    预测与结果展示:
  • 预测代码:加载预训练权重文件并进行推理。
  • 结果展示:通过绘制真实边界框和预测边界框对比效果,并展示了在测试集上的表现。
    评估指标:
  • 在验证集上获得平均iou为0.6524,在测试集中表现较好。
    总结而言,该模型能够有效识别肺结节并提供较为准确的边界框标注。

目录

1. 介绍

2. utils 工具函数

2.1 从xml获取类别+边界框函数 get_label_from_xml

2.2 图像归一化函数 normalization

2.3 计算iou函数 miou

2.4 绘制边界框函数 draw_bounding_box

3. dataset

4. train 函数

4.1 函数代码

4.2 可视化

5. 预测

5.1 预测代码

5.2 结果展示


项目地址:Fast R-CNN算法用于 lung nodule的目标识别,请访问下载地址:[Fast R-CNN algorithm for lung nodule detection]( "Fast R-CNN algorithm for lung nodule detection")

1. 介绍

如图为数据集展示,其中被选中的部分就是病状

以下是文件的目录:

data 代表项目的数据集。
dataset 实现了数据加载功能。
train 展现了核心训练逻辑。
utils 包含了一系列辅助功能模块。
predict 用于生成预测结果。
log 存储了训练过程中的各种日志信息。

2. utils 工具函数

这里是需要导入的头文件

2.1 从xml获取类别+边界框函数 get_label_from_xml

get_label_from_xml 函数,传入的是xml文件地址,返回的是类名+边界框

这里有的name 是小写的,所以用upper 转换为大写字母

复制代码
 # 读取 xml 文件中的描述信息,返回 label:类名 ,边界框

    
 def get_label_from_xml(xmlFile):
    
     an_file = open(xmlFile, encoding='utf-8')
    
     tree = ET.parse(an_file)
    
     root = tree.getroot()
    
  
    
     label = []          # 保存label
    
     bounding_box = []   # 保存边界框
    
  
    
     for objects in root.findall('object'):      # label 在 object 的标签下
    
     cancer_type = objects.find('name').text.upper()   # 返回目标的名字,转为大写
    
     cancer_type = class_to_id[str(cancer_type)]
    
     xmin = objects.find('bndbox').find('xmin').text     # 获取边界框
    
     xmax = objects.find('bndbox').find('xmax').text
    
     ymin = objects.find('bndbox').find('ymin').text
    
     ymax = objects.find('bndbox').find('ymax').text
    
  
    
     # 判断 bounding box 是否规范,错误的边界框不保存->continue
    
     if int(xmin) >= 512 or int(xmax) >= 512 or int(ymin) >=512 or int(ymax) >=512:    # 去除超出最大边界
    
         continue
    
     elif int(xmin) <=0 or int(xmax) <=0 or int(ymin) <=0 or int(ymax) <=0:          # 去除超出最小边界
    
         continue
    
     elif int(xmin) == int(xmax) or int(ymin) ==int(ymax):                           # 去除成线的边框
    
         continue
    
     else:
    
         label.append(cancer_type)
    
         bounding_box.append([int(xmin),int(ymin),int(xmax),int(ymax)])  # 左上角 + 右下角 坐标
    
  
    
     return label,bounding_box

2.2 图像归一化函数 normalization

由于所处理的图像为DCM格式,在这种情况下单个像素的灰度范围可能达到数千甚至更高数值。因此,在常规归一化方法难以适用的情况下,必须实施一个量身定制化的归一化处理流程。

复制代码
 # 图像归一化

    
 def normalization(x):
    
     max_pixel = np.max(x)
    
     min_pixel = np.min(x)
    
     x = (x - min_pixel) / (max_pixel - min_pixel)
    
     return x

加了normalization 函数之后图像的灰度范围

没加的灰度范围

以及

2.3 计算iou函数 miou

代码

复制代码
 # 返回一个 batch 的 平均 iou

    
 def miou(truth,prediction):
    
     miou_list = []      # 存放 batch 的 iou 列表
    
     for i in range(len(prediction)):
    
     pred_boxes = prediction[i]['boxes']                 # 预测的 bounding box
    
     label_boxes = truth[i]['boxes']                     # 真实的 bounding box
    
     iou = torchvision.ops.box_iou(label_boxes,pred_boxes)
    
     iou = torch.max(iou,dim = 1)[0].detach().cpu().numpy()
    
     mean_iou = np.mean(iou)                  # 单个 sample的 iou
    
     miou_list.append(mean_iou)
    
     return np.array(miou_list).mean()           # 一个 batch 的平均 iou

2.4 绘制边界框函数 draw_bounding_box

由于原始的DCM数据为灰度图象,在此将其转换为彩色图像以便于后续显示彩色边界框。

复制代码
 # 绘制边界框

    
 def draw_bounding_box(image,label,row = 2,col = 2):
    
     for batch in range(len(image)):
    
     img = np.transpose(image[batch].data.cpu().numpy(), (1, 2, 0))  # tensor -> numpy, 更改 channel
    
     img = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)                      # 转为彩色图像,否则,彩色边框会显示不出
    
  
    
     boxes = label[batch]['boxes'].data.cpu().numpy()                # 取边界框
    
     labels = label[batch]['labels'].data.cpu().numpy()              # 取类别
    
  
    
     for i in range(boxes.shape[0]):                                 # 逐个打印边界框
    
         x1,y1,x2,y2 = int(boxes[i][0]), int(boxes[i][1]), int(boxes[i][2]), int(boxes[i][3])    # 取点
    
         classes = id_to_class[str(labels[i])]                                                   # 将类别显示成名称
    
         cv2.rectangle(img,(x1,y1),(x2,y2),(255,0,0),2)                                     # 绘制矩形边界框
    
         cv2.putText(img, text=classes, org=(x1, y1+5), fontFace=cv2.FONT_HERSHEY_SIMPLEX,  # 显示文本
    
                     fontScale=1.5, thickness=1, lineType=cv2.LINE_AA, color=(0, 0, 255))
    
         #  绘制边框
    
         plt.subplot(row,col,batch+1)
    
         plt.imshow(img)
    
     plt.show()

3. dataset

代码如下

此处在处理过程中遇到的问题是原始的D_CM文件具有广泛的灰度范围。因此,在这种情况下,默认的ToTensor处理方式无法自动完成归一化。为了满足需求,在这种情况下必须自定义一个专门的归一化函数。值得注意的是,默认情况下(即使用ToTensor时),对于处于[0, 1]区间的数据不会再执行额外的归一化操作。因此,在这种情况下必须先将图像转换为浮点型数值类型

复制代码
 import os

    
 os.environ['KMP_DUPLICATE_LIB_OK']='True'
    
  
    
 import torch
    
 from torch.utils.data import Dataset
    
 from PIL import Image
    
 from utils import get_label_from_xml,normalization
    
 import pydicom
    
 import numpy as np
    
  
    
  
    
 class LungDetection(Dataset):   
    
     def __init__(self,img_path,label_path,transform):
    
     img_root = os.listdir(img_path)
    
     label_root = os.listdir(label_path)
    
     self.imgs = [os.path.join(img_path,img) for img in img_root]                # 图像的路径
    
     self.xmls = [os.path.join(label_path,label) for label in label_root]        # xml 标签的路径
    
     self.transform = transform
    
  
    
     def __getitem__(self, index):
    
  
    
     img_path = self.imgs[index]                     # 获取单个sample路径
    
     label_xml = self.xmls[index]
    
  
    
     img_open = pydicom.read_file(img_path)          # 读取图像
    
     img_array = img_open.pixel_array                # dcm -> np
    
     img_array = normalization(img_array)
    
     img_array = np.array(img_array, dtype=np.float32)
    
     img_pic = Image.fromarray(img_array)    # np -> PIL
    
     img_tensor = self.transform(img_pic)    # PIL -> tensor
    
  
    
     label,bbox = get_label_from_xml(label_xml)  # 获取 label
    
     bbox_tensor = torch.as_tensor(bbox,dtype=torch.float32)     # 边界框类型是float
    
     label_tensor = torch.as_tensor(label,dtype=torch.int64)     # 分类的类名是整型
    
  
    
     target = {}       # target是一个字典,包含边界框boxes和检测的类名labels
    
     target['boxes'] = bbox_tensor
    
     target['labels'] = label_tensor
    
  
    
     return img_tensor,target
    
  
    
     def __len__(self):
    
     return len(self.imgs)

4. train 函数

训练的过程大同小异

4.1 函数代码

复制代码
 from torchvision.transforms import transforms

    
 import torch
    
 import torchvision.models
    
 from dataset import LungDetection
    
 from torch.utils.data import DataLoader
    
 from utils import detection_collate, draw_bounding_box, miou
    
 import torch.optim as optim
    
 from tqdm import tqdm
    
 import numpy as np
    
  
    
  
    
 # 超参数设置
    
 TRAIN_IMAGE_PATH = './data/train/image'                # 训练集路径
    
 TRAIN_LABEL_PATH = './data/train/label'                # 训练集路径
    
 TEST_IMAGE_PATH = './data/test/image'                  # 训练集路径
    
 TEST_LABEL_PATH = './data/test/label'                  # 训练集路径
    
  
    
 BATCH_SIZE, ROW, COLUMN = 4, 2, 2                      # batch size + 可视化图像的行列数
    
 LEARNING_RATE = 0.00001
    
 DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
    
 EPOCHS = 50
    
  
    
 # 预处理
    
 transformer = transforms.Compose([transforms.ToTensor()])
    
  
    
 # 加载训练集
    
 trainSet = LungDetection(img_path=TRAIN_IMAGE_PATH,label_path=TRAIN_LABEL_PATH,transform=transformer)
    
 trainLoader = DataLoader(trainSet, BATCH_SIZE, shuffle=True, collate_fn=detection_collate)
    
  
    
 # 加载验证集
    
 testSet = LungDetection(img_path=TEST_IMAGE_PATH,label_path=TEST_LABEL_PATH,transform=transformer)
    
 testLoader = DataLoader(testSet, batch_size=BATCH_SIZE, shuffle=False, collate_fn=detection_collate)
    
  
    
 # 建立模型
    
 model = torchvision.models.detection.fasterrcnn_mobilenet_v3_large_fpn(weight=None, progress=True,num_classes=2)
    
 # model = torchvision.models.detection.retinanet_resnet50_fpn(pretrained=False, progress=True, num_classes=2)
    
 # model=torchvision.models.detection.ssd300_vgg16(pretrained=False, progress=True, num_classes=2)
    
 model.to(DEVICE)
    
  
    
 # 建立优化器
    
 optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
    
  
    
  
    
 # 训练函数
    
 def train_fn(model, optimizer, trainloader, testloader, device, epochs):
    
     for epoch in range(epochs):
    
     loss_epoch = []         # 一个epoch训练损失
    
     iou_epoch = []          # 一个epoch训练 iou
    
  
    
     for images, labels in tqdm(trainloader):    # 读取训练集
    
         model.train()
    
  
    
         images = list(image.to(device) for image in images)     # 将数据放到 cuda 上
    
         labels = [{k: v.to(device) for k, v in t.items()} for t in labels]
    
  
    
         loss_dict = model(images, labels)       # 前向传播
    
  
    
         losses = sum(loss for loss in loss_dict.values())  # 损失求和
    
         optimizer.zero_grad()  # 梯度清零
    
         losses.backward()  # 反向传播
    
         optimizer.step()  # 梯度下降
    
  
    
         losses = losses.data.cpu().numpy()
    
         loss_epoch.append(losses)  # 保留损失
    
  
    
         with torch.no_grad():
    
             model.eval()  # 测试模式
    
             try:
    
                 pred = model(images)
    
                 batch_iou = miou(truth=labels, prediction=pred)  # 计算一个batch的平均iou
    
                 iou_epoch.append(batch_iou)
    
             except:
    
                 continue
    
  
    
     test_loss_epoch = []        # 一个epoch 的 loss
    
     test_iou_epoch = []         # 一个epoch 的 iou
    
  
    
     with torch.no_grad():   # 验证的时候,不需要backward
    
         for images, labels in tqdm(testloader):
    
             model.train()  # 训练模式
    
             images = list(image.to(device) for image in images)
    
             labels = [{k: v.to(device) for k, v in t.items()} for t in labels]
    
  
    
             loss_dict = model(images, labels)
    
             losses = sum(loss for loss in loss_dict.values())
    
             losses = losses.data.cpu().numpy()
    
             test_loss_epoch.append(losses)      # 一个batch 的loss
    
  
    
             model.eval()  # 测试模式
    
             try:
    
                 pred = model(images)
    
                 test_batch_iou = miou(truth=labels, prediction=pred)  # 计算一个batch的平均 iou
    
                 test_iou_epoch.append(test_batch_iou)
    
             except:
    
                 continue
    
  
    
         train_iou = np.array(iou_epoch).mean()          # 将不同 batch 的 iou 求平均
    
         train_loss = np.array(loss_epoch).mean()        # 将不同 batch 的 loss 求均值
    
         test_iou = np.array(test_iou_epoch).mean()
    
         test_loss = np.array(test_loss_epoch).mean()
    
  
    
     # 保留权重文件
    
     if test_iou > 0.65:
    
         static_dict = model.state_dict()
    
         torch.save(static_dict, './log/pretrained_epoch_{},train_iou_{},test_iou_{}.pth'.format(epoch + 1, train_iou, test_iou))
    
  
    
     print('epoch:', epoch + 1)
    
     print('epoch_train_loss:', train_loss)
    
     print('epoch_train_iou:', train_iou)
    
     print('epoch_test_loss:', test_loss)
    
     print('epoch_test_iou:', test_iou)
    
  
    
  
    
 if __name__ == '__main__':
    
     #model.load_state_dict(torch.load('./log/epoch_35,train_iou_0.9122412204742432,test_iou_0.6948733925819397.pth'))
    
     train_fn(model, optimizer, trainLoader, testLoader, DEVICE, EPOCHS)
    
     print(' training over !!!! ')

4.2 可视化

测试代码

复制代码
 #load a batch data + 可视化

    
 img,label = next(iter(testLoader))
    
 draw_bounding_box(img,label)

显示结果:

5. 预测

5.1 预测代码

复制代码
 from torchvision.transforms import transforms

    
 import torch
    
 import torchvision.models
    
 from dataset import LungDetection
    
 from torch.utils.data import DataLoader
    
 from utils import detection_collate, draw_bounding_box,normalization
    
 import cv2
    
 import numpy as np
    
 import matplotlib.pyplot as plt
    
  
    
  
    
 # 参数设定
    
 DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
    
 TEST_IMAGE_PATH = './data/train/image'                  # 训练集路径
    
 TEST_LABEL_PATH = './data/train/label'                  # 训练集路径
    
  
    
 WIGHT = './log/pretrained_epoch_16,train_iou_0.978933572769165,test_iou_0.652420699596405.pth'        # 权重文件
    
 BATCH_SIZE = 4
    
 ROW, COLUMN = 2, BATCH_SIZE             # 结果展示2行,第一行为 truth,第二行为 predict
    
  
    
 id_to_class = {'1':'A'}
    
  
    
 # 预处理
    
 transformer = transforms.Compose([transforms.ToTensor()])
    
  
    
 # 加载数据
    
 testSet = LungDetection(img_path=TEST_IMAGE_PATH,label_path=TEST_LABEL_PATH,transform=transformer)
    
 testLoader = DataLoader(testSet, batch_size=BATCH_SIZE, shuffle=True, collate_fn=detection_collate)
    
  
    
 # 取出一个batch 的数据
    
 test_images,test_labels = next(iter(testLoader))
    
  
    
 # 加载模型
    
 model = torchvision.models.detection.fasterrcnn_mobilenet_v3_large_fpn(weight=None, progress=True, num_classes=2)
    
 model.load_state_dict(torch.load(WIGHT))
    
 model.to(DEVICE)
    
  
    
 # 网络预测
    
 model.eval()
    
 images = list(image.to(DEVICE) for image in test_images)
    
 pred = model(images)
    
  
    
 # 显示预测结果
    
 for batch in range(BATCH_SIZE):  # 遍历 batch 图像
    
     img = np.transpose(images[batch].data.cpu().numpy(), (1, 2, 0))  # tensor -> numpy, 更改 channel
    
     img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)                  # 转为彩色图像,否则,彩色边框会显示不出
    
  
    
     boxes = pred[batch]['boxes'].data.cpu().numpy()  # 取边界
    
     labels = pred[batch]['labels'].data.cpu().numpy()  # 取类别
    
     scores = pred[batch]['scores'].data.cpu().numpy()  # 显示置信度
    
  
    
     for i in range(boxes.shape[0]):  # 逐个打印边界框
    
     if scores[i] > 0.5:
    
         x1, y1, x2, y2 = int(boxes[i][0]), int(boxes[i][1]), int(boxes[i][2]), int(boxes[i][3])  # 取点
    
         classes = id_to_class[str(labels[i])]  # 取类别
    
         cv2.rectangle(img, (x1, y1), (x2, y2), (255, 0, 0), 2)  # 绘制矩形边界框
    
         cv2.putText(img, text=classes, org=(x1, y1 + 10), fontFace=cv2.FONT_HERSHEY_SIMPLEX,  # 显示文本
    
                     fontScale=1, thickness=1, lineType=cv2.LINE_AA, color=(0, 0, 255))
    
  
    
     #  绘制边框
    
     plt.subplot(ROW, COLUMN, batch + 1 + COLUMN)        # 将预测结果放到第二行
    
     plt.imshow(img)
    
  
    
  
    
 # 显示真实的结果
    
 draw_bounding_box(images,test_labels,row=ROW,col=COLUMN)

5.2 结果展示

这里呈现的是(训练数据集合)通过以下链接可获得:

第一行为truth,第二行为prediction

该测试集在测试集中的表现较为突出,在评估指标中呈现出良好的效果。

可能因为数据的原因吧,iou虽然不高,但是在testSet 的表现也比较好

全部评论 (0)

还没有任何评论哟~