昇腾AI行业案例(六):基于 PraNet 的医疗影像分割

简介: 欢迎学习《基于 PraNet 的医疗影像分割》实验。在本实验中,你将深入了解如何运用计算机视觉(CV)领域的 AI 模型,搭建一个高效精准的医疗影像分割系统,专注于息肉分割任务,并利用开源数据集对模型效果加以验证。

00 - 前言

欢迎学习《基于 PraNet 的医疗影像分割》实验。在本实验中,你将深入了解如何运用计算机视觉(CV)领域的 AI 模型,搭建一个高效精准的医疗影像分割系统,专注于息肉分割任务,并利用开源数据集对模型效果加以验证。

学习目标

在本课程中,您将学习一些与使用 AI 图像处理技术实现息肉影像分割有关的重要概念,包括:

  • 医疗影像数据的预处理方法
  • 采用 PraNet 模型对息肉区域进行分割的方法
  • 图像分割的后处理方法
  • 端到端深度学习工作流

目录

本实验分为四个核心部分。第一部分主要介绍案例的应用场景,阐述遥感影像地块分割的重要性及意义;第二部分会详细阐述端到端的解决方案,搭建起技术实现的整体框架;第三部分会手把手指导您完成代码编写与实现;最后一部分给出测试题,帮助您巩固学习内容。

  1. 场景介绍
  2. 解决方案
  3. 代码实战
  4. 课后测试

JupyterLab

在本实操实验中,我们将使用 JupyterLab 管理我们的环境。JupyterLab 界面是一个控制面板,可供您访问交互式 iPython Notebook、所用环境的文件夹结构,以及用于进入 Ubuntu 操作系统的终端窗口,只需要点击菜单栏的小三角就可以运行代码。

尝试执行下方单元中的一条简单的 print(打印)语句。

# DO NOT CHANGE THIS CELL
# activate this cell by selecting it with the mouse or arrow keys then use the keyboard shortcut [Shift+Enter] to execute
print('This is just a simple print statement')
This is just a simple print statement

01 场景介绍

医疗领域,息肉分割是一项至关重要的任务。息肉是肠道等器官表面的异常生长组织,其早期发现对于预防和治疗相关疾病(如结直肠癌)具有重要意义。传统的息肉检测方法依赖于医生的肉眼观察,容易受到主观因素的影响,且在处理大量影像数据时效率较低。因此,利用 AI 技术实现自动化的息肉分割,能够提高诊断的准确性和效率,为医生提供有力的辅助决策支持。

02 解决方案

本实验教程使用的解决方案如下,首先对原始图像进行预处理,使得图像数据符合 AI 模型的输入要求,然后使用图像分割模型进行推理,预测每个像素点属于息肉组织的置信度,最后进行后处理,把分割的区域可视化标记出来。

以下分别介绍这几个核心模块。

2.1 图像预处理模块

原始的医疗影像是通过胃镜等设备在患者体内拍摄得到的,包含着各种复杂的信息,直接用于分割检测可能会导致较低的准确性。因此,我们首先对其进行预处理操作,其中一项重要的处理是将图像转换为 YUV 格式。

YUV 格式相较于常见的 RGB 格式,在颜色信息的表示上具有独特的优势。通过将亮度(Y)与色度(U 和 V)信息分离,能够更好地突出图像中的物体轮廓和细节特征(息肉检测的重点就是要把它的轮廓特征检测出来),为后续的模型处理提供更加清晰和易于分析的数据基础。在转换过程中,我们运用专业的图像转换算法,确保图像的色彩信息得以准确保留,同时优化了亮度和色度的分布,使得息肉与周围环境的对比度增强,从而有效提升了后续模型对息肉特征的提取能力,为精准分割奠定了坚实的基础。

2.2 图像分割模型

在数据预处理之后,我们借助先进的息肉分割模型 PraNet 进行推理操作,以实现对每个像素点的准确分类,这是整个解决方案的核心环节。

PraNet(Parallel Reverse Attention Network for Polyp Segmentation)是一种针对息肉分割任务需求设计的、名为并行反向注意力的深度神经网络。主要包括两个部分: - 并行的部分解码器(PPD):这个部分的作用是聚合图像的高级特征,形成一个初始的引导区域。简单来说,它就像是一个“粗略定位器”,能够大致确定息肉在图像中的位置。它通过分析图像的全局信息,找出息肉的大致轮廓,为后续的精确分割打下基础。 - 反向注意模块(RA):这个部分的作用是挖掘边界线索,通过反向传播的方式,逐步细化息肉的边界。它就像是一个“精细雕刻师”,在PPD确定的粗略区域上,进一步精确地勾勒出息肉的边缘。它能够捕捉到息肉与周围组织的细微差别,使得分割结果更加准确。

2.3 后处理模块

二值化处理:经过模型推理得到的结果,是每个像素点分类为息肉组织的置信度,为了把息肉组织和周边组织区分开,我们需要对分类结果进行二值化,得到一个新的分割图,这个分割图上息肉组织的像素点的值是255(白色),其他组织像素点是0(黑色),这样就能清楚地看出息肉组织的位置了。

对比展示:得到分割结果图后,我们可以把它和原图并排放在一起,确认检测结果是否和实际情况一致。

03 动手实验

3.1 实验准备

数据集

实验所用的开源图像集是kvasir-seg数据集,该数据集包含了1000张内镜影像。

模型权重

本实验采用的 PraNet 模型需要从这个链接下载,里面包含了 onnx 格式的模型,我们后续将会用到。

3.2 图像预处理

参考原项目pranet.aippconfig 文件,可以知道模型的输入图像格式为 YUV420SP_U8 ,含义如下:

YUV:颜色编码方式,YUV 是一种颜色模型,它将颜色信息分为亮度(Y)和色度(U, V)两部分。这种编码方式在图像和视频压缩中非常常见,因为它可以更有效地表示颜色信息,并且允许单独压缩亮度和色度分量。

420:色度采样(Chroma Subsampling),420表示色度信息的采样率。在YUV420中,U和V分量的采样率是亮度分量的一半。具体来说,对于每四个Y值,只有一个U和一个V值。这种采样方式减少了色度信息的数量,可以在保持图像质量的同时减少数据量。

SP:Semi-Planar的缩写,指的是色度分量的存储方式。在YUV420SP格式中,U和V分量交错存储(交错是指在行或列上相邻存储),而不是分开存储。这意味着U和V分量不是完全独立的两个平面,而是交错在一起。

U8:数据类型,U8表示每个颜色分量(Y、U、V)使用8位无符号整数(0-255)来存储。这表示每个颜色值可以用一个字节来表示。

所以我们需要把原始的图像进行预处理,才能传给 AI 模型。 第一步,导入所需的三方库:

import numpy as np
import cv2

接着,创建图像预处理函数:

def img_process(img_path):
    image = cv2.imread(img_path)
    # resize成模型的输入shape
    image = cv2.resize(image, (352, 352), interpolation=cv2.INTER_AREA)
    # 将图片从BGR颜色空间转换为YUV颜色空间
    yuv_image = cv2.cvtColor(image, cv2.COLOR_BGR2YUV)
    # 获取图片的尺寸
    height, width = yuv_image.shape[:2]
    # 分离Y, U, V分量
    y, u, v = cv2.split(yuv_image)
    # 将U和V分量下采样到4:2:0
    u = cv2.resize(u, (width // 2, height // 2), interpolation=cv2.INTER_LINEAR)
    v = cv2.resize(v, (width // 2, height // 2), interpolation=cv2.INTER_LINEAR)
    # 交错UV分量,形成NV12格式
    uv = np.zeros((height // 2, width), dtype=yuv_image.dtype)
    uv[:, 0::2] = u
    uv[:, 1::2] = v
    # 将Y分量和交错的UV分量合并
    image = np.concatenate((y, uv), axis=0)
    return image

完成数据类的定义后,我们可以对其进行测试,读取并预处理图像:

source = "./kvasir_test_pics/cju2zrojo9kcd0878ld2epejq.jpg"
process_data = img_process(source)
print(process_data.shape)
(528, 352)

3.3 使用 PraNet 模型进行分割预测

对于一个长宽为 (w, h) 的图像,PraNet 模型的输出 shape 为 (1, w, h),代表每个像素点在分类为息肉组织的置信度,一般情况下,息肉组织对应的置信度大于0,其他正常组织的置信度小于0。

首先,我们需要把下载的 onnx 格式的模型转成能在昇腾硬件上运行的 om 格式的模型,命令如下:

# atc --model=./PraNet/PraNet-19.onnx --output=./PraNet-19_bs1 --framework=5 --input_shape="actual_input_1:1,3,352,352" --soc_version=Ascendxxx --input_format=NCHW --output_type=FP32 --insert_op_conf=./pranet.aippconfig
# config 文件下载地址: https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/contrib/PraNetSegmentation/model/pranet.aippconfig

然后我们构建 om 模型的推理类:

import acl
ACL_MEM_MALLOC_HUGE_FIRST = 0
ACL_MEMCPY_HOST_TO_DEVICE = 1
ACL_MEMCPY_DEVICE_TO_HOST = 2
class OmModel:
    def __init__(self, model_path):
        # 初始化函数
        self.device_id = 5
        # step1: 初始化
        ret = acl.init()
        # 指定运算的Device
        ret = acl.rt.set_device(self.device_id)
        # step2: 加载模型,本示例为pfld模型
        # 加载离线模型文件,返回标识模型的ID
        self.model_id, ret = acl.mdl.load_from_file(model_path)
        # 创建空白模型描述信息,获取模型描述信息的指针地址
        self.model_desc = acl.mdl.create_desc()
        # 通过模型的ID,将模型的描述信息填充到model_desc
        ret = acl.mdl.get_desc(self.model_desc, self.model_id)
        # step3:创建输入输出数据集
        # 创建输入数据集
        self.input_dataset, self.input_data = self.prepare_dataset('input')
        # 创建输出数据集
        self.output_dataset, self.output_data = self.prepare_dataset('output')
    def prepare_dataset(self, io_type):
        # 准备数据集
        if io_type == "input":
            # 获得模型输入的个数
            io_num = acl.mdl.get_num_inputs(self.model_desc)
            acl_mdl_get_size_by_index = acl.mdl.get_input_size_by_index
        else:
            # 获得模型输出的个数
            io_num = acl.mdl.get_num_outputs(self.model_desc)
            acl_mdl_get_size_by_index = acl.mdl.get_output_size_by_index
        # 创建aclmdlDataset类型的数据,描述模型推理的输入。
        dataset = acl.mdl.create_dataset()
        datas = []
        for i in range(io_num):
            # 获取所需的buffer内存大小
            buffer_size = acl_mdl_get_size_by_index(self.model_desc, i)
            # 申请buffer内存
            buffer, ret = acl.rt.malloc(buffer_size, ACL_MEM_MALLOC_HUGE_FIRST)
            # 从内存创建buffer数据
            data_buffer = acl.create_data_buffer(buffer, buffer_size)
            # 将buffer数据添加到数据集
            _, ret = acl.mdl.add_dataset_buffer(dataset, data_buffer)
            datas.append({"buffer": buffer, "data": data_buffer, "size": buffer_size})
        return dataset, datas
    def forward(self, inputs):
        # 执行推理任务
        # 遍历所有输入,拷贝到对应的buffer内存中
        input_num = len(inputs)
        for i in range(input_num):
            bytes_data = inputs[i].tobytes()
            bytes_ptr = acl.util.bytes_to_ptr(bytes_data)
            # 将图片数据从Host传输到Device。
            ret = acl.rt.memcpy(self.input_data[i]["buffer"],  # 目标地址 device
                                self.input_data[i]["size"],  # 目标地址大小
                                bytes_ptr,  # 源地址 host
                                len(bytes_data),  # 源地址大小
                                ACL_MEMCPY_HOST_TO_DEVICE)  # 模式:从host到device
        # 执行模型推理。
        ret = acl.mdl.execute(self.model_id, self.input_dataset, self.output_dataset)
        # 处理模型推理的输出数据,输出top5置信度的类别编号。
        inference_result = []
        for i, item in enumerate(self.output_data):
            buffer_host, ret = acl.rt.malloc_host(self.output_data[i]["size"])
            # 将推理输出数据从Device传输到Host。
            ret = acl.rt.memcpy(buffer_host,  # 目标地址 host
                                self.output_data[i]["size"],  # 目标地址大小
                                self.output_data[i]["buffer"],  # 源地址 device
                                self.output_data[i]["size"],  # 源地址大小
                                ACL_MEMCPY_DEVICE_TO_HOST)  # 模式:从device到host
            # 从内存地址获取bytes对象
            bytes_out = acl.util.ptr_to_bytes(buffer_host, self.output_data[i]["size"])
            # 按照float32格式将数据转为numpy数组
            data = np.frombuffer(bytes_out, dtype=np.float32)
            inference_result.append(data)
        return inference_result
    def __del__(self):
        # 析构函数 按照初始化资源的相反顺序释放资源。
        # 销毁输入输出数据集
        for dataset in [self.input_data, self.output_data]:
            while dataset:
                item = dataset.pop()
                ret = acl.destroy_data_buffer(item["data"])  # 销毁buffer数据
                ret = acl.rt.free(item["buffer"])  # 释放buffer内存
        ret = acl.mdl.destroy_dataset(self.input_dataset)  # 销毁输入数据集
        ret = acl.mdl.destroy_dataset(self.output_dataset)  # 销毁输出数据集
        # 销毁模型描述
        ret = acl.mdl.destroy_desc(self.model_desc)
        # 卸载模型
        ret = acl.mdl.unload(self.model_id)
        # 释放device
        ret = acl.rt.reset_device(self.device_id)
        # acl去初始化
        ret = acl.finalize()

现在测试一下模型的推理结果

# 加载模型
pranet_om_model_path = "./PraNet-19_bs1.om"
pranet_om_model = OmModel(pranet_om_model_path)
# 推理
output_res = pranet_om_model.forward([process_data])
tensor_res = output_res[0].reshape(1, 1, 352, 352)  # 模型的输出shape是 (1, 1, 352, 352)
print(tensor_res.shape)
(1, 1, 352, 352)

3.4 后处理函数

正如前面提到的,模型的输出结果是各个像素点分类为息肉组织的置信度,我们还需要把分类结果进行二值化,构建黑色和白色表示的区域分割图片。这一系列操作对应下面的后处理函数:

THRESHOLD_VALUE = 0.5  # 二值化置信度阈值,根据算法效果调试得到
def decode_seg_map(label_mask, save_path):
    # 获取2维的分类结果
    segment_result = label_mask[0][0]
    # 新建一个0矩阵,表示二值分割像素矩阵
    seg_map = np.zeros((segment_result.shape[0], segment_result.shape[1]), dtype=np.float32)
    # 二值化
    seg_map[segment_result > THRESHOLD_VALUE] = 255
    # 保存二值化结果
    cv2.imwrite(save_path, seg_map)
    return

此外,为了比较原图和分割结果,还需要保存原图和分割结果的对比图:

def enable_contrast_output(arr):
    """
        Enable comparison graph output
        Args:
            arr: arr[0] is the img one, arr[1] is the img two, arr[2] is the output directory of the comparison graph result
        Returns:
            null
        Output:
            a comparison graph result in arr[2]
    """
    img1 = Image.open(arr[0])
    img2 = Image.open(arr[1])
    img2 = img2.resize(img1.size, Image.NEAREST)
    # create a new image, set the width and height
    toImage = Image.new('RGB', (img1.width + img2.width + 35, img2.height), 'white')
    # paste image1 to the new image, and set the position
    toImage.paste(img1, (0, 0))
    # paste image2 to the new image, and set the position
    toImage.paste(img2, (img1.width + 35, 0))
    # save the result image, and the quality is 100
    toImage.save(arr[2], quality=100)

好了,完成这些后,我们就可以开始进行端到端的图像分割实验了!

3.5 整合代码实现端到端检测

我们把前面创建的预处理函数、模型推理代码和后处理函数组合起来,形成下面的流程:

import os
import glob
from pathlib import Path
from PIL import Image
img_dir = "./kvasir_test_pics/"
save_path = "./result/"
imgs_path = glob.glob(str(Path(img_dir).resolve()) + '/*.*')
if os.path.exists(img_dir) != 1:
    print("The test image " + str(img_dir) + " does not exist.")
    exit()
for img_path in imgs_path:
    img_name = img_path.split("/")[-1]
    segment_save_path = save_path + "seg_" + img_name
    compare_save_path = save_path + "compare_" + img_name
    image = img_process(img_path)
    # Get the result of the DANet model
    output_res_PraNet = pranet_om_model.forward([image])
    if output_res_PraNet == []:
        continue
    # reshape the matrix to (1, 1, 352, 352)
    tensor_res_PraNet = output_res_PraNet[0].reshape(1, 1, 352, 352)
    # The result is mapped to a picture
    decode_seg_map(tensor_res_PraNet, segment_save_path)
    # Enable comparison graph output if true
    enable_contrast_output([img_path, segment_save_path, compare_save_path])
    print('success!')
success!
success!
success!

查看一张分割结果对比图,可以看出基本上把息肉组织分割出来了:

恭喜你!至此,你已经成功完成了基于 PraNet 的医疗影像分割的全部实验流程,希望你能够熟练掌握这套技术方案,并将其应用到实际医疗影像分割项目中去!

3.6 依赖软件

本实验的依赖软件版本信息如下:

  1. Python:为了方便开发者进行学习,本课程采用Python代码实现,您可以在服务器上安装一个Conda,用于创建Python环境,本实验使用的是 python 3.10
  2. pillow:Python的图像处理库,本实验使用的是 11.0.0 版本;
  3. opencv-python:opencv-python 是 OpenCV 库的 Python 接口,它提供了对 OpenCV 功能的访问,包括图像处理、视频分析、计算机视觉算法和实时图像处理等,使得开发者能够在 Python 环境中轻松实现复杂的视觉任务,本实验使用的是 4.10.0.84 版本;
  4. numpy: 开源的Python科学计算库,用于进行大规模数值和矩阵运算,本实验使用的是 1.26.4 版本;
  5. CANN(Compute Architecture for Neural Networks):Ascend芯片的使能软件,本实验使用的是 ="https://www.hiascend.com/developer/download/community/result?module=cann&cann=8.0.RC2.alpha002">8.0.rc2 版本。

04 课后测试

  1. 尝试调整置信度阈值 THRESHOLD_VALUE ,观察分割预测结果如何变化;
  2. 尝试用其他医疗影像数据进行预测,观察预测效果如何。
目录
相关文章
|
23天前
|
机器学习/深度学习 人工智能 自然语言处理
Baichuan-M1-14B:AI 助力医疗推理,为患者提供专业的建议!百川智能开源业内首个医疗增强大模型,普及医学的新渠道!
Baichuan-M1-14B 是百川智能推出的首个开源医疗增强大模型,专为医疗场景优化,支持多语言、快速推理,具备强大的医疗推理能力和通用能力。
178 16
Baichuan-M1-14B:AI 助力医疗推理,为患者提供专业的建议!百川智能开源业内首个医疗增强大模型,普及医学的新渠道!
|
7天前
|
人工智能
科技赋能妇产医疗,钉钉联合打造小红 AI 患者助理
复旦大学附属妇产科医院与钉钉共同打造的 AI 助理“小红”上线。“小红”孵化于钉钉智能化底座,通过学习复旦大学附属妇产科医院的 400 多篇科普知识,涵盖妇科疾病宣教、专业产科指导、女性健康保健等问题,能够为患者提供妇科疾病、产科指导、女性健康保健等知识的专业解答。
60 10
|
30天前
|
机器学习/深度学习 数据采集 人工智能
昇腾AI行业案例(七):基于 Conformer 和 Transformer 模型的中文语音识别
欢迎学习《基于 Conformer 和 Transformer 模型的中文语音识别》实验。本案例旨在帮助你深入了解如何运用深度学习模型搭建一个高效精准的语音识别系统,将中文语音信号转换成文字,并利用开源数据集对模型效果加以验证。
52 12
|
30天前
|
人工智能 监控 安全
设计:智能医疗设备管理系统——AI医疗守护者
该系统将结合人工智能技术与区块链技术,实现对医疗设备的智能化管理。目标是提高医疗设备的管理效率,确保医疗设备的数据安全,优化医疗资源的配置,提升医疗服务质量。
|
1月前
|
存储 人工智能 数据可视化
昇腾AI行业案例(五):基于 DANet 和 Deeplabv3 模型的遥感图像分割
欢迎学习《基于 DANet 和 Deeplabv3 模型的遥感图像分割》实验。在本实验中,你将深入了解如何运用计算机视觉(CV)领域的 AI 模型,搭建一个高效精准的遥感地图区域分割系统,并利用开源数据集和昇腾 AI 芯片对模型效果加以验证。
32 0
昇腾AI行业案例(五):基于 DANet 和 Deeplabv3 模型的遥感图像分割
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
昇腾AI行业案例(四):基于 Bert 模型实现文本分类
欢迎学习《昇腾行业应用案例》的“基于 Bert 模型实现文本分类”实验。在本实验中,您将学习如何使用利用 NLP (natural language processing) 领域的AI模型来构建一个端到端的文本系统,并使用开源数据集进行效果验证。为此,我们将使用昇腾的AI硬件以及CANN等软件产品。
43 0
|
1月前
|
人工智能 算法 计算机视觉
昇腾AI行业案例(三):基于 AI 图像处理的铝板缺陷检测
欢迎学习《基于 AI 图像处理的铝板缺陷检测》实验。在本实验中,你将深入了解如何运用计算机视觉(CV)领域的 AI 模型,搭建一个高效精准的铝板缺陷检测系统,并利用开源数据集和昇腾 AI 芯片对模型效果加以验证。
50 0
|
机器学习/深度学习 人工智能 编解码
他用AI修复了志愿军影像
今天,第八批在韩中国人民志愿军烈士遗骸安葬仪式在沈阳举行,大谷也用一种独特的方法——用AI修复一段志愿军的真实影像,表达了自己的哀思。
786 0
|
1月前
|
人工智能 算法 前端开发
OmAgent:轻松构建在终端设备上运行的 AI 应用,赋能手机、穿戴设备、摄像头等多种设备
OmAgent 是 Om AI 与浙江大学联合开源的多模态语言代理框架,支持多设备连接、高效模型集成,助力开发者快速构建复杂的多模态代理应用。
197 72
OmAgent:轻松构建在终端设备上运行的 AI 应用,赋能手机、穿戴设备、摄像头等多种设备
|
17天前
|
人工智能 自然语言处理 搜索推荐
【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人
【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人
95 24
【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人

热门文章

最新文章