当前位置: 首页 > news >正文

CV 医学影像分类、分割、目标检测,之【肺结节目标检测】项目拆解

CV 医学影像分类、分割、目标检测,之【肺结节目标检测】项目拆解

  • 项目流程梳理 - 数据源阶段
  • 数据探索阶段,了解数据结构
    • 第一部分:文件遍历收集
    • 第二部分:XML标注文件收集
    • 第三部分:DICOM文件读取
    • 第四部分:UID提取与匹配
    • 第五部分:XML与DICOM配对
    • 第六部分:XML解析函数
    • 第七部分:可视化
    • 核心概念关联图
  • 数据准备阶段,为模型训练做准备
    • 新增部分1:分布式训练准备
    • 新增部分2:文件复制操作
    • 新增部分3:DICOM文件保存
    • 新增部分4:张量转换处理
    • 新增部分5:不同的图像转换方式
    • 新增部分6:图像保存操作
    • 代码问题分析
    • 核心差异总结:从看数据 到 用数据
  • 模型训练阶段:训练一个SSD目标检测模型来自动识别CT图像中的肺结节
    • 一、整体架构理解
    • 二、核心组件:Dataset类
      • **自定义数据集类**
      • **数据获取逻辑**
      • **标签处理**
    • 三、数据加载器配置
    • 四、模型选择与初始化
    • 五、训练循环深度解析
      • **前向传播与损失计算**
      • **评估指标:IoU**
    • 六、训练策略分析
      • **优化器配置**
      • **训练细节**
    • 七、模型保存策略
    • 八、代码问题诊断
      • **严重问题**
      • **逻辑错误**
      • **内存泄漏风险**
    • 九、完整训练流程图

 


项目流程梳理 - 数据源阶段

医生的经验(看片子识别肺结节)↓标注数据(框出位置)↓数据预处理(数据源阶段工作)↓模型学习(训练模型阶段工作)↓
AI的能力(自动识别肺结节)
原始数据├── CT影像文件(DICOM格式)│   └── 包含:像素数据 + 病人信息 + 扫描参数│└── 标注文件(XML格式)└── 包含:肺结节位置坐标 + 类型标签↓数据预处理↓┌──────────────────────────┐│  1. 文件收集与整理        ││  2. 影像与标注配对        ││  3. 数据验证与可视化      ││  4. 格式转换与标准化      │└──────────────────────────┘↓训练数据集↓深度学习模型训练↓目标检测模型

问4:为什么有两种不同的文件(DICOM和XML)?

答4:DICOM是CT机器生成的原始影像,XML是医生后期添加的标注。

问5:这两种文件是怎么对应的?

答5:通过唯一标识符(UID)——每张CT图像都有独一无二的ID。

问6:为什么不把标注直接写在DICOM里?

答6:DICOM格式固定,不便修改;XML灵活,便于多人协作标注。

数据探索阶段,了解数据结构

第一部分:文件遍历收集

dcm_file=[]
for root,dirs,files in os.walk(r'E:\肺结节目标检测\manifest-1626051497651\Lung-PET-CT-Dx'):for file in files:file_path=os.path.join(root,file)if 'dcm' in file_path:dcm_file.append(file_path)

问1:这段代码的目的是什么?
答1:收集所有DICOM格式的医学影像文件路径。

问2:什么是DICOM文件?
答2:医学数字成像和通信标准文件,存储CT、MRI等医学影像数据。

问3:os.walk做什么?
答3:递归遍历目录树,返回(当前目录路径,子目录列表,文件列表)三元组。

问4:为什么要递归遍历?
答4:因为医学影像通常按病人/检查日期/序列分层存储在多级目录中。

问5:os.path.join的作用是什么?
答5:将路径组件智能地拼接成完整路径,自动处理斜杠问题。


第二部分:XML标注文件收集

xml_file=[]
for root,dirs,files in os.walk(r'E:\肺结节目标检测\Lung-PET-CT-Dx-Annotations-XML-Files-rev12222020'):for file in files:file_path=os.path.join(root,file)        if 'xml' in file_path:xml_file.append(file_path)

问6:为什么需要XML文件?
答6:XML文件包含了医生标注的肺结节位置信息(边界框坐标)。

问7:什么是标注(Annotation)?
答7:人工标记的真实答案,用于训练深度学习模型识别病灶。


第三部分:DICOM文件读取

import pydicom
im=pydicom.read_file('E:/肺结节目标检测/manifest-1626051497651/Lung-PET-CT-Dx/Lung_Dx-A0001/04-04-2007-Chest-07990/2.000000-5mm-40805/1-01.dcm')

问8:pydicom是什么?
答8:专门读取DICOM医学影像格式的Python库。

问9:为什么不用普通图像库如PIL?
答9:DICOM包含元数据(病人信息、扫描参数等),需要专门解析。


第四部分:UID提取与匹配

im_name=[]
for dcm in dcm_file:im=pydicom.read_file(dcm)dcm_uid=im.SOPInstanceUIDim_name.append(dcm_uid)

问10:SOPInstanceUID是什么?
答10:Service-Object Pair Instance Unique Identifier,每个DICOM图像的唯一标识符。

问11:为什么要提取UID?
答11:用于将影像文件与对应的标注文件精确匹配。


第五部分:XML与DICOM配对

xml_dcm=[]
for xml in xml_file:xml_file_name=xml[77:-4]  # 提取文件名(去掉路径和扩展名)if xml_file_name in im_name:xml_dcm.append(xml_file_name)

问12:为什么用[77:-4]切片?
答12:77是路径长度,-4去掉".xml"扩展名,得到纯UID。

问13:这种硬编码有什么问题?
答13:路径改变就会出错,应该用os.path.basename等方法。


第六部分:XML解析函数

def get_labelFromXml(xml_file):     tree=ET.parse(an_file)root = tree.getroot()for object in root.findall('object'):    cancer_type=object.find('name').text.upper()xmin=object.find('bndbox').find('xmin').text# ... 获取坐标bbox=[int(xmin),int(ymin),int(xmax),int(ymax)]

问14:ET.parse做什么?
答14:将XML文件解析成树形结构,便于提取标注信息。

问15:bndbox是什么?
答15:bounding box(边界框),用矩形框住病灶的最小矩形。

问16:为什么要检查xmin0或xminxmax?
答16:过滤无效标注(零值或退化成点/线的框)。


第七部分:可视化

im_array=np.array(im.pixel_array,dtype=np.float32)
image=cv2.cvtColor(im_array,cv2.COLOR_GRAY2RGB)
src_img=cv2.convertScaleAbs(image,alpha=(0.2))
cv2.rectangle(src_img,(x1,y1),(x2,y2),(0,255,0),2)

问17:pixel_array是什么?
答17:DICOM图像的像素数据矩阵,通常是灰度值。

问18:为什么COLOR_GRAY2RGB?
答18:CT图像是灰度的,但绘制彩色框需要RGB格式。

问19:convertScaleAbs的alpha=0.2做什么?
答19:调整亮度,CT原始值范围大,需要缩放到0-255显示。

问20:(0,255,0)代表什么?
答20:BGR格式的纯绿色,用于绘制检测框。


核心概念关联图

医学影像处理├── 数据格式│   ├── DICOM(影像+元数据)│   └── XML(标注信息)├── 数据组织│   ├── 文件遍历(os.walk)│   └── UID匹配(唯一标识)└── 目标检测├── 边界框(坐标定位)├── 标签(病灶类型)└── 可视化(验证标注)

这段代码本质是数据预处理管道:将散乱的医学影像和标注文件整理配对,为深度学习训练准备数据。每个步骤都是这个管道的必要环节。

数据准备阶段,为模型训练做准备

这份代码与之前的代码主体框架相似,但有几个关键差异。让我用连续提问法帮你理解这些不同之处

新增部分1:分布式训练准备

import torchvision
import torch.distributed as dist

问1:为什么导入torch.distributed?
答1:准备进行分布式训练,可以在多GPU或多机器上并行训练。

问2:分布式训练的目的是什么?
答2:加速训练大规模医学影像模型,单GPU训练太慢。


新增部分2:文件复制操作

shutil.copy(file_path,'E:/肺结节目标检测/data/new_xml/R{}'.format(file))

问3:这行与之前注释掉的代码有何不同?
答3:这里直接执行了复制,将XML文件集中到新目录管理。

问4:为什么要复制而不是直接使用原路径?
答4:创建工作副本,避免修改原始标注文件。


新增部分3:DICOM文件保存

im.save_as('E:\肺结节目标检测\data\image\R{}.dcm'.format(dcm_uid))

问5:save_as与之前只读取有何区别?
答5:这里将DICOM文件重新保存,可能进行了格式标准化。

问6:为什么要重新保存DICOM文件?
答6:统一文件命名规则,用UID作为文件名便于配对。


新增部分4:张量转换处理

im_tensor=torch.from_numpy(im_array/255.)
im_tensor=torch.unsqueeze(im_tensor,0)

问7:为什么除以255?
答7:归一化到[0,1]范围,这是深度学习的标准预处理。

问8:unsqueeze(0)做什么?
答8:增加batch维度,从[H,W]变成[1,H,W],符合PyTorch输入格式。

问9:为什么需要batch维度?
答9:神经网络期望批量输入,即使只有一张图也要保持维度一致。


新增部分5:不同的图像转换方式

src_img=(im_array*255.).astype(np.uint8)  # 新代码
# vs
src_img=cv2.convertScaleAbs(image,alpha=(0.2))  # 旧代码

问10:这两种方式有何区别?
答10:新代码直接乘255转uint8,旧代码用alpha=0.2降低亮度。

问11:为什么新代码不降低亮度?
答11:可能原始数据已经在合适范围,不需要额外调整。


新增部分6:图像保存操作

outputImg = Image.fromarray(outputImg*255.0)
outputImg = outputImg.convert('L')
outputImg.save('/Users/86187/Desktop/change_detection/YR-A-result.bmp')

问12:Image.fromarray做什么?
答12:将numpy数组转换为PIL Image对象。

问13:convert(‘L’)的作用?
答13:L表示Luminance(亮度),转换为8位灰度图。

问14:为什么保存为.bmp格式?
答14:BMP是无损格式,保留所有像素信息,适合医学影像。


代码问题分析

cv2.rectangle(src_img,(x1,y1),(x2,y2),random_color(),thickness=1)

问15:random_color()函数在哪里定义?
答15:代码中没有定义,这会导致运行错误。

问16:如何修复?
答16:需要定义:def random_color(): return (random.randint(0,255), random.randint(0,255), random.randint(0,255))


核心差异总结:从看数据 到 用数据

数据探索阶段流程:
读取 → 匹配 → 可视化数据准备阶段流程:
读取 → 复制整理 → 保存重组 → 张量转换 → 可视化 → 导出结果↓           ↓           ↓           ↓         ↓(备份)    (标准化)   (深度学习)  (验证)   (持久化)

模型训练阶段:训练一个SSD目标检测模型来自动识别CT图像中的肺结节

一、整体架构理解

问1:这段代码在做什么?
答1:训练一个SSD目标检测模型来自动识别CT图像中的肺结节。

问2:为什么之前是数据准备,现在是模型训练?
答2:机器学习流程:数据准备→模型构建→训练→评估,这是第2-3步。


二、核心组件:Dataset类

自定义数据集类

class CellDetection(Dataset):def __init__(self,img,label,transform=None):self.img = imgself.label=labelself.transform = transform

问3:为什么要继承Dataset类?
答3:PyTorch要求的标准接口,让数据能被DataLoader自动批处理。

问4:Dataset必须实现哪些方法?
答4:__getitem__(获取单个样本)和__len__(数据集大小)。


数据获取逻辑

def __getitem__(self, index):img_open=pydicom.read_file(img)img_array=img_open.pixel_arrayimg_array = np.array(img_array, dtype=np.float32)img_pic=Image.fromarray(img_array)img_tensor=self.transform(img_pic)

问5:为什么要经历这么多格式转换?
答5:

  • DICOM → numpy:提取像素数据
  • numpy → PIL.Image:为了使用transforms
  • PIL → Tensor:模型需要的格式

问6:dtype=np.float32的作用?
答6:统一数据类型,避免整数运算丢失精度,GPU计算需要浮点数。


标签处理

class_to_id={'A':1}
cell=object.find('name').text.upper()
cell_id=class_to_id[cell]

问7:为什么要把’A’转成1?
答7:神经网络只认识数字,不认识字符串,这叫标签编码。

问8:为什么是{‘A’:1}而不是{‘A’:0}?
答8:0通常预留给背景类,1开始才是真实目标类别。


三、数据加载器配置

def detection_collate(x):return list(tuple(zip(*x)))dl_train=DataLoader(train_data,batch_size=4,shuffle=True,collate_fn=detection_collate)

问9:collate_fn是什么?
答9:自定义批处理函数,因为目标检测每张图的框数量不同,不能简单堆叠。

*问10:zip(x)在做什么魔法?
答10:转置操作——把[(img1,label1),(img2,label2)]变成([img1,img2],[label1,label2])。

问11:为什么batch_size=4这么小?
答11:医学图像分辨率高(512×512),显存有限,大batch会爆显存。


四、模型选择与初始化

model=torchvision.models.detection.ssd300_vgg16(pretrained=False, progress=True, num_classes=2)

问12:什么是SSD?
答12:Single Shot MultiBox Detector,单次前向传播就能检测多个目标的模型。

问13:为什么选SSD而不是YOLO或Faster R-CNN?
答13:SSD速度快,精度适中,适合医学图像这种目标相对规则的场景。

问14:num_classes=2代表什么?
答14:背景(0) + 肺结节(1) = 2个类别。

问15:pretrained=False的影响?
答15:从随机权重开始训练,没有利用ImageNet预训练,可能需要更多数据。


五、训练循环深度解析

前向传播与损失计算

model.train()
loss_dict=model(img,label)
losses = sum(loss for loss in loss_dict.values())

问16:model.train()做了什么?
答16:启用BatchNorm和Dropout层,让模型处于训练模式。

问17:loss_dict包含什么?
答17:SSD的多个损失——分类损失(classification_loss)和定位损失(bbox_regression_loss)。

问18:为什么要sum所有损失?
答18:多任务学习,同时优化分类和定位,需要联合优化。


评估指标:IoU

iou_tensor=torchvision.ops.box_iou(pic_boxes,label_boxes)
iou_total=np.mean(torch.max(iou_tensor,dim=1)[0].detach().numpy())

问19:IoU是什么?
答19:Intersection over Union,交并比,衡量预测框和真实框的重叠程度。

问20:IoU怎么计算?
答20:交集面积÷并集面积,完全重合=1,不重合=0。

问21:torch.max(iou_tensor,dim=1)[0]在做什么?
答21:每个预测框找最匹配的真实框,取最大IoU值。


六、训练策略分析

优化器配置

optimizer=torch.optim.Adam(model.parameters(),lr=0.0001)
# 后期:
optimizer=torch.optim.Adam(model.parameters(),lr=0.00001)

问22:为什么用Adam而不是SGD?
答22:Adam自适应学习率,对医学图像这种数据量少的任务更稳定。

问23:为什么学习率从0.0001降到0.00001?
答23:学习率衰减策略——前期快速下降,后期精细调整。


训练细节

try:pred=model(images)# IoU计算
except:continue

问24:为什么要try-except?
答24:某些批次可能没有目标,会导致IoU计算失败,需要跳过。

问25:这种处理方式好吗?
答25:不好,应该明确捕获特定异常,否则会掩盖真正的bug。


七、模型保存策略

torch.save(static_dict,'./data/checkpoint/{}_train_mIou_{}_test_mIou_{}.pth'.format(epoch,round(mIou, 3),round(test_mIou,3)))

问26:为什么文件名包含mIoU?
答26:方便直接从文件名看出模型性能,快速找到最佳模型。

问27:为什么每个epoch都保存?
答27:防止过拟合——后期模型可能变差,需要回溯到最佳点。


八、代码问题诊断

严重问题

# 问题1:训练集测试集划分
train_img,train_xml=new_img[:2000],new_xml[:2000]
test_img,test_xml=new_img[2000:],new_xml[2000:]

问28:这种划分有什么问题?
答28:没有打乱,可能导致数据分布不均(如前2000张都是某一个病人的)。

逻辑错误

# 问题2:测试时不应该计算梯度
for images, targets in tqdm(dl_test):model.train()  # ← 错误!测试时应该model.eval()

问29:测试时用model.train()会怎样?
答29:BatchNorm使用批次统计而非全局统计,导致测试结果不稳定。

内存泄漏风险

loss_epoch.append(losses.cpu().numpy())  # losses仍保留计算图

问30:为什么需要.detach()?
答30:不detach会保留整个计算图在内存,导致内存泄漏。


九、完整训练流程图

数据准备↓
Dataset封装 → DataLoader批处理↓
模型初始化(SSD)↓
训练循环 ┌─────────────┐├─→ │ 前向传播     │├─→ │ 计算损失     │├─→ │ 反向传播     │├─→ │ 更新权重     │├─→ │ 评估IoU      │└─→ │ 保存checkpoint│└─────────────┘↓
学习率调整 → 继续训练

http://www.dtcms.com/a/330622.html

相关文章:

  • pytorch学习笔记-加载现有的网络模型(VGG16)、增加/修改其中的网络层(修改为10分类)
  • AI测试自动化:智能软件质量守护者
  • 观察者模式(C++)
  • CV 医学影像分类、分割、目标检测,之【3D肝脏分割】项目拆解
  • Flutter 顶部导航标签组件Tab + TabBar + TabController
  • 汽车生产线白皮书:稳联技术Profinet转Ethernet IP网关通信高效性
  • 中介者模式和观察者模式的区别是什么
  • 三同步舆情处置原则对政务管理有什么影响作用
  • 从实验室到落地:飞算JavaAI水位监测系统的工程化实践
  • 4.2 Vue3中reactive与ref详解及区别
  • 【企业架构】TOGAF概念之四(终结)
  • Day20 Linux 文件 I/O、目录操作及文件链接与 EDID
  • 小杰python(six day)——网络编程
  • 前端Vite介绍(现代化前端构建工具,由尤雨溪开发,旨在显著提升开发体验和构建效率)ES模块(ESM)、与传统Webpack对比、Rollup打包
  • 20250814 最小生成树总结
  • Vue 3 + TypeScript:package.json 示例 / 详细注释说明
  • Linux 上手 UDP Socket 程序编写(含完整具体demo)
  • 如何通过WiFi将文件从安卓设备传输到电脑
  • 计算机视觉(opencv)实战二——图像边界扩展cv2.copyMakeBorder()
  • 机器学习 - Kaggle项目实践(3)Digit Recognizer 手写数字识别
  • 分布式事务、锁、链路追踪
  • 读取数据excel
  • 高效TypeScript开发:VSCode终极配置指南
  • 待办事项小程序开发
  • (第十六期)HTML布局标签详解:div与span的深度解析
  • 【读代码】深度解析 context-engineering-intro:开源上下文工程实践原理与应用
  • 群晖 NAS 影音访问:通过 cpolar 内网穿透服务实现 Nastool 远程管理
  • java集合 之 多列集合
  • Python/Node.js 调用taobao API:构建实时商品详情数据采集服务
  • 使用HalconDotNet实现异步多相机采集与实时处理