CT图像获取人体部分掩膜MASK(去除机床)

CT图像获取人体部分掩膜MASK(去除机床)

统计CT图像的均值标准差时,希望只针对人体部分进行统计,故需要从CT图像中获取人体部分的掩膜MASK,而人体又经常与机床部分相连,所以也需要去掉机床部分
在这里插入图片描述

简单实现方法

理想情况下,通过卡一个较低阈值(-150),能够使人体与机床部分完全分离。这种情况下,直接通过取最大连通域的方式,就可得到人体部分的mask。

代码:
import numpy as np
import os
import SimpleITK as sitk


def getmaxcomponent(mask_array, num_limit=10):
    # sitk方法获取连通域
    cca = sitk.ConnectedComponentImageFilter()
    cca.SetFullyConnected(True)
    cca.FullyConnectedOff()
    _input = sitk.GetImageFromArray(mask_array.astype(np.uint8))
    output_ex = cca.Execute(_input)
    labeled_img = sitk.GetArrayFromImage(output_ex)
    num = cca.GetObjectCount()
    max_label = 0
    max_num = 0
    # 不必遍历全部连通域,一般在前面就有对应全身mask的label,减少计算时间
    for i in range(1, num_limit):  	
        if np.sum(labeled_img == i) < 1e6:		# 全身mask的体素数量必然很大,小于设定值的不考虑
            continue
        if np.sum(labeled_img == i) > max_num:
            max_num = np.sum(labeled_img == i)
            max_label = i
    maxcomponent = np.array((labeled_img == max_label)).astype(np.uint8)
    print(str(max_label) + ' num:' + str(max_num))  	# 看第几个是最大的
    return maxcomponent


def get_body(CT_nii_array):
    """
    卡CT阈值获取身体(理想情况下够用了,不过多数情况下会包括到机床部分)
    """
    # 阈值二值化,获得最大的3d的连通域
    CT_array = np.copy(CT_nii_array)
    threshold_all = -200  # 卡的阈值,卡出整个身体以及机床部分
    CT_array[CT_array >= threshold_all] = 1
    CT_array[CT_array <= threshold_all] = 0
    body_mask1 = getmaxcomponent(CT_array, 10)
    return body_mask1.astype(np.uint8)

不理想情况的解决方法

不理想的情况指的是,人体与机床有相连,导致取连通域后得到的mask其实包含有机床部分,这种情况在实际中也经常碰到,因此需要去除机床部分,尝试了以下两种方法:

1.形态学方法:
思路:开操作、腐蚀,希望能断开人体与机床部分相连的地方,然后再取一次最大连通域
缺点:该方法在人和机床相连情况不太严重时有一定效果,但是容易导致获得的人体部分经过形态学处理后有点问题

2.先获取机床的mask:
思路:在有多例CT数据的情况下,每例CT图像取到最大连通域(可能包含机床)后,我们叠加所有连通域,由于人体与机床相连的位置各不相同,而机床的位置是固定的,因此相连的地方的重叠次数不高于机床部分,我们保留重叠次数大于设定阈值的区域,再去掉此时的最大连通域(即人体部分),最后经过一下形态学的简单处理,即可获得机床部分的mask
注意:需要保证这批数据由同样一台机器扫描得到,不然CT不能简单地进行重叠

后续思考:
针对同一批次数据的情况,其实可以直接取一例人体与机床分开的CT图像单独取得机床mask,后续批量处理中直接取这个mask进行处理即可。大部分情况下是work的,改后的代码如下。

获取机床mask的代码:
ct_path = os.path.join( '你的CT图像的nii文件路径')
ct_nii = sitk.ReadImage(ct_path)
ct_array = sitk.GetArrayFromImage(ct_nii)
ret = -150
body_machine_mask = (ct_array > ret).astype(np.uint8)	# 直接卡阈值得到包括身体和机床两个部分的掩膜

# 取最大连通域,即身体部分
machine_mask = body_machine_mask.copy()
maxcomponent = getmaxcomponent(machine_mask, 10)

# 去掉最大的身体部分
machine_mask = machine_mask - maxcomponent

# 膨胀3*3,并取最大连通域,这一步是防止机床有些漏洞,导致后续填洞不完整
machine_mask_d = dilate_ct(machine_mask, 12, other_axis=False)
machine_mask_s = getmaxcomponent(machine_mask_d, 1e5, 50, True)
# 填充孔洞
machine_final = fill_inter_3D(machine_mask_s)
# 保存
save_path = r"machine_mask.nii.gz"
save_sitk = sitk.GetImageFromArray(machine_final.astype(np.uint8))
save_sitk.CopyInformation(ct_nii)
sitk.WriteImage(save_sitk, save_path)

膨胀部分的代码有人问我也放一下:

def dilate_ct(CT_r, struct_size=5, other_axis=False):
    # 三个方向做两次闭操作
    CT_dilate = CT_r.copy()
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (struct_size, struct_size))
    for j in range(CT_dilate.shape[0]):
        CT_dilate[j, :, :] = cv2.morphologyEx(CT_dilate[j, :, :], cv2.MORPH_DILATE, kernel)
        CT_dilate[j, :, :] = cv2.morphologyEx(CT_dilate[j, :, :], cv2.MORPH_DILATE, kernel)
    # PS:其他方向也做
    if other_axis:
        for j in range(CT_dilate.shape[1]):
            CT_dilate[:, j, :] = cv2.morphologyEx(CT_dilate[:, j, :], cv2.MORPH_DILATE, kernel)
            CT_dilate[:, j, :] = cv2.morphologyEx(CT_dilate[:, j, :], cv2.MORPH_DILATE, kernel)
        # z轴上的要按spacing的大小改变kernel的大小
        struct_size_z = 1
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (struct_size_z, struct_size_z))
        for j in range(CT_dilate.shape[2]):
            CT_dilate[:, :, j] = cv2.morphologyEx(CT_dilate[:, :, j], cv2.MORPH_DILATE, kernel)
            CT_dilate[:, :, j] = cv2.morphologyEx(CT_dilate[:, :, j], cv2.MORPH_DILATE, kernel)
    return CT_dilate

机床MASK示例:

红色部分就是机床mask了,结果还不错,再配合最开始的方法,获得人体的mask

代码:
def cut_machine(CT_nii_array, CT_nii, nii_folder_path, id):
    """
    从CT图像中得到去掉机床部分后的人体部分
    """
    body_mask_file = nii_folder_path + sep + id + r'_body_mask.nii.gz'
    machine_mask_file = r'.\machine_mask.nii.gz'  # 机床mask文件
    # 阈值二值化,获得最大的3d的连通域
    body_mask1 = get_body(CT_nii_array)
    # 后处理
    # 去掉机床部分
    machine_mask = sitk.ReadImage(machine_mask_file)
    machine_mask_nii = sitk.GetArrayFromImage(machine_mask)
    body_mask2 = body_mask1 - machine_mask_nii
    body_mask2[body_mask2 == 255] = 0
    body_mask2[body_mask2 < 0] = 0
    body_mask3 = getmaxcomponent(body_mask2, 150)
    # 填充
    body_mask_fill = np.zeros(body_mask3.shape, dtype=np.uint8)
    for j in range(body_mask2.shape[0]):
        body_mask_fill[j, :, :] = fill_inter_bone(body_mask3[j, :, :])
    save_mask = body_mask_fill
    save_nii(save_mask, CT_nii, body_mask_file)
    # plt.imshow(save_nii[195, :, :])
    # plt.show()
    return save_mask.astype(np.uint8)

def save_nii(save_nii, CT_nii, save_path, save_mask=True):
    """
    保存nii
    :param save_nii: 需要保存的nii图像的array
    :param CT_nii: 配准的nii图像,用于获取同样的信息
    :param save_path: 保存路径
    :param save_mask: 保存的是否是mask,默认是True
    :return:
    """
    if save_mask:
        # 保存mask_nii
        save_sitk = sitk.GetImageFromArray(save_nii.astype(np.uint8))
    else:
        # 保存img_nii
        save_sitk = sitk.GetImageFromArray(save_nii.astype(np.float))
    save_sitk.CopyInformation(CT_nii)
    sitk.WriteImage(save_sitk, save_path)
    print(save_path + ' processing successfully!')

def fill_inter_bone(mask):
    # 对一张图像做孔洞填充,读入的是一层
    mask = mask_fill = mask.astype(np.uint8)
    if np.sum(mask[:]) != 0:  # 即读入图层有值
        contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
        len_contour = len(contours)
        contour_list = []
        for i in range(len_contour):
            drawing = np.zeros_like(mask, np.uint8)  # create a black image
            img_contour = cv2.drawContours(drawing, contours, i, (1, 1, 1), -1)
            contour_list.append(img_contour)
        mask_fill = sum(contour_list)
        mask_fill[mask_fill >= 1] = 1
    return mask_fill.astype(np.uint8)

人体MASK示例:
在这里插入图片描述

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值