目次
1. セマンティックセグメンテーション
第 3 世代の画像セグメンテーション: セマンティック セグメンテーション
画像セグメンテーション (Image Segmentation) は、コンピュータ ビジョンの分野における重要な基礎技術です。画像セグメンテーションは、デジタル画像を複数の画像サブ領域に再分割し、画像の表現を簡略化または変更することで画像を理解しやすくするプロセスです。より簡単に言うと、画像セグメンテーションとは、デジタル画像内の各ピクセルにラベルを付けることで、同じラベルを持つピクセルが共通の視覚的特徴を持つようにすることです。
医用画像のモダリティ(形式)はX線、CT、MRI、超音波など多岐にわたりますが、もちろん一般的なRGB画像(眼底網膜画像など)も含まれます。異なるモーダル画像応答の情報強調は異なります。たとえば、骨の X 線観察はより鮮明で、CT は組織や臓器の出血を反映でき、MRI は軟組織の観察に適しています。また、撮像装置の種類により得られる撮像結果は多少異なります。
最初の写真は、前処理後にグレースケール画像に変換された肝臓 CT 画像スライスの可視化結果であり、組織と臓器の境界線がぼやけています。
2 番目の写真は、さまざまな個人の肝臓 CT 画像を示していますが、その差は大きく、肝臓組織の抽出に大きな困難をもたらしています。
2. データセット
3D-IRCADb (アルゴリズム データベース比較のための 3D 画像再構成、アルゴリズム データベース比較のための 3D 画像再構成) のデータベースは、症例の 75% で肝腫瘍を有する女性 10 名と男性 10 名の CT スキャンで構成され、各フォルダーは患者に応じて、肝臓のサイズ (幅、深さ、高さ) や Couninurd セグメンテーションに基づく腫瘍の位置などの画像に関する情報が提供されます。また、肝臓のセグメンテーションが、隣接する臓器との接触、肝臓の異常な形状や密度、さらには画像内のアーティファクトによって重大な困難に直面する可能性があることも示しています。
リンク: https://pan.baidu.com/s/1P76AF-wvrFjElc6tR82tRA
抽出コード: 5el7
3. データの拡張
画像データの処理手順
1. データロード
2. CT画像の強調
3. 強化されたヒストグラムイコライゼーション
4. 腫瘍に対応するCT画像と肝腫瘍のマスク画像を取得します。
5. 画像を保存する
DICOMデータの読み取り
pydicom ライブラリを使用してファイルを読み取り、pydicom.dcmread 関数でファイルを読み取り、ファイルを並べ替え、pixel_array 属性で画像のピクセル情報を抽出し、画像を表示します。
バッチデータ読み取り
# 批量读取数据
img_slices = [pydicom.dcmread(os.path.join(data_path, file_name)) for file_name in os.listdir(data_path)]
os.listdir(data_path)
# 排序,避免CT图乱序
img_slices.sort(key=lambda x:x.InstanceNumber) # 顺序属性
img_array = np.array([i.pixel_array for i in img_slices]) # 提取像素值
CT画像強調法:ウィンドウ法
CT 画像の範囲が広いとコントラストが低下するため、特定の臓器については処理する必要があります。
CT値の物理的な意味は、 CT線が人体に照射され、放射線が人体を通過する際の放射線強度の減衰の度合いです。
CTは人間の組織のわずかな密度の違いを識別できるのが特徴で、X線に対する各種組織の線吸収係数(μ値)に応じて基準が定められています。
Hu ( CT ) 値に従って必要な部分の写真をフィルタリングし、画像のコントラストを高めるために、他のすべての部分を黒くするか白くします。ウィンドウ方式を使用します。観測された CT 値の範囲: ウィンドウ幅。観察された中心CT値をウィンドウレベルとして腫瘍部分を2値化します。
def windowing(img, window_width, window_center):
# params:需要增强的图片, 窗口宽度, 窗中心 通过窗口最小值来线性移动窗口增强
min_windows = float(window_center)-0.5*float(window_width)
new_img = (img-min_windows)/float(window_width)
new_img[new_img<0] = 0 # 二值化处理 抹白
new_img[new_img>1] = 1 # 抹黑
return (new_img * 255).astype('uint8') # 把数据整理成标准图像格式
img_ct = windowing(img_array, 500, 150)
ヒストグラムの等化
ヒストグラム均等化機能:画像全体を多数の小ブロック(例えば10*10を小ブロック)に分割し、各小ブロックを均等化します。画像ヒストグラムがそれほど単一ではない (たとえば、複数のピークがある) 画像の場合は、より実用的です。0pencv では、このメソッドは cv2.createCLAHE() です。
def clahe_equalized(imgs):
# 输入imgs的形状必须是3维
assert (len(imgs.shape) == 3)
# 定义均衡化函数
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
# 新数组存放均衡化后的数据
img_res = np.zeros_like(imgs)
for i in range(len(imgs)):
img_res[i,:,:] = clahe.apply(np.array(imgs[i,:,:], dtype=np.uint8))
return img_res/255
plt.hist(img_array.reshape(-1,),bins=50) # 1 次元に縮小し、50 点に分割
マスク画像の深度を取得する
トレーニング速度を高速化するために、カスタム関数は対応する CT 画像 (処理後) と肝腫瘍の対応するマスク画像の抽出を実現し、それらをそれぞれ異なるフォルダーに保存します。モデル。腫瘍 CT 画像を読み取る:
tumor_slice = [pydicom.dcmread(os.path.join(data_path_mask, file_name)) for file_name in os.listdir(data_path_mask)]
#避免CT图乱序,顺序属性
tumor_slice.sort(key=lambda x: x.InstanceNumber)
#提取像素值
tumor_array = np.array([i.pixel_array for i in tumor_slice])
print(tumor_array.shape) # (129, 512, 512)
白い部分は腫瘍マスクマップで、黒い部分に対応するピクセル配列はすべて0です。
腫瘍CT画像からの腫瘍抽出
index = [i.sum() > 0 for i in tumor_array] # 提取含肿瘤部分
# print(len(index)) # = tumor_array.shape[0] 129
# 提取掩模图的肿瘤部分
img_tumor = tumor_array[index]
# 对增强后的CT图提取肿瘤部分
img_patient = img_clahe[index]
腫瘍データを保存する
# 设置保存文件路径
patient_save_path = r'E:/datasets/liver/tmp/patient/'
tumor_save_path = r'E:/datasets/liver/tmp/tumor/'
for path in [patient_save_path, tumor_save_path]:
if os.path.exists(path): # 判断文件夹是否存在
shutil.rmtree(path) # 清空
os.mkdir(path)
# 保留一个肿瘤的数据
# plt.imsave(os.path.join(patient_save_path, '0.jpg'), img_patient[0],cmap='gray')
for i in range(len(img_patient)):
plt.imsave(os.path.join(patient_save_path, f'{i}.jpg'), img_patient[i], cmap='gray')
plt.imsave(os.path.join(tumor_save_path, f'{i}.jpg'), img_tumor[i], cmap='gray')
4. データロード
データのバッチ処理
3dircadb1 フォルダー内のデータを実験オブジェクトとして取得し、最初の 1 ~ 10 人の患者のデータをトレーニング サンプルとして取得し、10 ~ 20 人の患者のデータをテスト サンプルとして取得します。
- 1. CT画像の断片を読み取る
- 2. ピクセル値の抽出
- 3. CT画像の強調と等化
- 4. 腫瘍マスクマップの処理
- 5. 各患者の腫瘍マップを並べ替え、腫瘍セグメントのピクセル値を抽出します。
- 6. 腫瘍部分のピクセル番号を抽出する
- 7. CT画像内の対応する位置を見つけます
- 8. すべての腫瘍データを保存する
def processImage(start, end):
for num in range(start, end):
print('正在处理第%d号病人' % num)
data_path = fr'G:/dataPack/基于深度学习的肝脏肿瘤图像分割/3dircadb1/3dircadb1.{num}/PATIENT_DICOM'
# 读取CT图图像片段
image_slices = [pydicom.dcmread(os.path.join(data_path, file_name)) for file_name in os.listdir(data_path)]
os.listdir(data_path)
image_slices.sort(key=lambda x: x.InstanceNumber) # 排序
# 提取像素值
image_array = np.array([i.pixel_array for i in image_slices])
# CT图增强-windowing
img_ct = windowing(image_array, 250, 0)
# 直方图均衡化
img_clahe = clahe_equalized(img_ct)
# 肿瘤掩模图处理
livertumor_path = fr'G:/dataPack/基于深度学习的肝脏肿瘤图像分割/3dircadb1/3dircadb1.{num}/MASKS_DICOM'
tumor_paths = [os.path.join(livertumor_path, i) for i in os.listdir(livertumor_path) if 'livertumor' in i]
# 重新排序
tumor_paths.sort()
# 提取所有肿瘤数据
j = 0
for tumor_path in tumor_paths:
print("正在处理第%d个肿瘤" % j)
tumor_slices = [pydicom.dcmread(os.path.join(tumor_path, file_name)) for file_name in
os.listdir(tumor_path)]
# 重新对肿瘤片段图排序
tumor_slices.sort(key=lambda x: x.InstanceNumber)
# 提取像素值
tumor_array = np.array([i.pixel_array for i in tumor_slices])
# 没有肿瘤的掩模图全为黑色,对应像素全为0
index = [i.sum() > 0 for i in tumor_array] # 提取肿瘤部分编号
img_tumor = tumor_array[index]
# 对增强后的CT图提取肿瘤
img_patient = img_clahe[index]
# 保存所有肿瘤数据
for i in range(len(img_patient)):
plt.imsave(os.path.join(patient_save_path, f'{num}_{j}_{i}.jpg'), img_patient[i], cmap='gray') # 保存CT图
plt.imsave(os.path.join(tumor_save_path, f'{num}_{j}_{i}.jpg'), img_tumor[i], cmap='gray') # 保存肿瘤掩模图
j += 1
return img_patient, img_tumor
処理後に保存された拡張 CT 画像と腫瘍マスク画像
データセットの読み込み
Dataset データ ローダーを定義し、サンプル内の各ピクチャをさらに処理し、np 配列に変換し、UNet ネットワーク トレーニングを容易にするために次元を変換します。(トレーニングを改善するため) Dataloader で batch_size を定義して、データの各バッチのサイズを 2 に設定します。コンピューティングの観点から見た効率)
transform = transforms.Compose([
transforms.Resize((512, 512)),
transforms.ToTensor()
])
まずローカル ファイルを読み取り、トレーニング セットとテスト セットのファイル パスを設定し、torch.squeeze(anno_tensor).type(torch.long) を通じて腫瘍マップを単一チャネル配列に変換します。
そして腫瘍マップを二値化します
# 读取之前保存的处理后的病人CT图片与肿瘤图片
train_img_patient, train_img_tumor = processImage(1, 5)
test_img_patient, test_img_tumor = processImage(5, 7)
patient_images = glob.glob(r'E:\\datasets\liver\tmp\patient\*.jpg')
tumor_images = glob.glob(r'E:\\datasets\liver\tmp\tumor\*.jpg')
train_images = [p for p in patient_images if '1_' in p]
train_labels = [p for p in tumor_images if '1_' in p]
test_images = [p for p in patient_images if '2_' in p]
test_labels = [p for p in tumor_images if '2_' in p]
train_images = np.array(train_images)
train_labels = np.array(train_labels)
test_images = np.array(test_images)
test_labels = np.array(test_labels)
# img = Image.open(train_images[1])
# plt.imshow(img)
# plt.show()
class Portrait_dataset(data.Dataset):
def __init__(self, img_paths, anno_paths):
self.imgs = img_paths
self.annos = anno_paths
def __getitem__(self, index):
img = self.imgs[index]
anno = self.annos[index]
pil_img = Image.open(img)
img_tensor = transform(pil_img)
pil_anno = Image.open(anno)
anno_tensor = transform(pil_anno)
# 由于蒙版图都是黑白图,会产生channel为1的维度。经过转换后,256x256x1,这个1并不是我们需要的。
anno_tensor = torch.squeeze(anno_tensor).type(torch.long)
anno_tensor[anno_tensor > 0] = 1 # 语义分割。二分类。
return img_tensor, anno_tensor
def __len__(self):
return len(self.imgs)
BATCH_SIZE = 2
train_set = Portrait_dataset(train_images, train_labels)
test_set = Portrait_dataset(test_images, test_labels)
train_dataloader = data.DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader = data.DataLoader(test_set, batch_size=BATCH_SIZE)
img_batch, anno_batch = next(iter(train_dataloader))
DataSetを介してデータセットを読み取り、DataLoaderを介してバッチ処理ピクチャの数= 2に設定します(計算効率を向上させるため)
患者の CT 画像と腫瘍画像をランダムに選択して表示します。
5. UNetニューラルネットワークモデルの構築
Unetネットワーク構造は対称であり、青/白のボックスは特徴マップを表します。青の矢印は特徴抽出に使用される 3x3 畳み込みを表します。灰色の矢印は特徴融合に使用されるスキップ接続を表します。赤の矢印はプーリングは次元を減らすために使用され、緑色の矢印はアップサンプリング upsample を示し、次元を復元するために使用されます。シアンの矢印は 1x1 コンボリューションを示し、結果を出力するために使用されます。
class downSample(nn.Module):
def __init__(self, in_channels, out_channels):
super(downSample, self).__init__()
# 两层*(卷积+激活)
self.conv_relu = nn.Sequential(
# padding=1,希望图像经过卷积之后大小不变
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
)
# 下采样(池化)
self.pool = nn.MaxPool2d(kernel_size=2)
def forward(self, x, is_pool=True):
if is_pool:
x = self.pool(x)
print("downSample forward x.shape",x.shape)
x = self.conv_relu(x)
print("downSample forward after conv_relu(x) x.shape",x.shape)
return x
# 上采样模型。卷积、卷积、上采样(反卷积实现上采样)
class upSample(nn.Module):
def __init__(self, channels):
# 两层*(卷积层+激活层)
super(upSample, self).__init__()
self.conv_relu = nn.Sequential(
nn.Conv2d(2 * channels, channels, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(channels, channels, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
)
# 上采样激活层(ConvTransposed)将输出层的channel变成原来的一半
self.upConv_relu = nn.Sequential(
nn.ConvTranspose2d(channels, channels // 2,
kernel_size=3, stride=2,
padding=1, output_padding=1),
nn.ReLU(inplace=True)
)
def forward(self, x):
print("upSample - forward x.shape",x.shape)
x = self.conv_relu(x)
x = self.upConv_relu(x)
return x
# 创建Unet。要初始化上、下采样层,还有其他的一些层
class Unet(nn.Module):
def __init__(self):
super(Unet, self).__init__()
self.down1 = downSample(3, 64)
self.down2 = downSample(64, 128)
self.down3 = downSample(128, 256)
self.down4 = downSample(256, 512)
self.down5 = downSample(512, 1024)
self.up = nn.Sequential(
nn.ConvTranspose2d(1024, 512,
kernel_size=3,
stride=2,
padding=1,
output_padding=1),
nn.ReLU(inplace=True)
)
self.up1 = upSample(512)
self.up2 = upSample(256)
self.up3 = upSample(128)
self.conv_2 = downSample(128, 64) # 最后两层卷积
self.last = nn.Conv2d(64, 2, kernel_size=1) # 输出层为2分类
def forward(self, x):
x1 = self.down1(x, is_pool=False)
x2 = self.down2(x1)
x3 = self.down3(x2)
x4 = self.down4(x3) # ([512, 64, 64])
print("x4.shape",x4.shape) # x4.shape torch.Size([512, 64, 64])
x5 = self.down5(x4)
print("x5.shape",x5.shape) # x5.shape torch.Size([1024, 32, 32])
x6 = self.up(x5)
print("x6.shape",x6.shape) # x6.shape torch.Size([512, 64, 64])
# 将下采用过程x4的输出与上采样过程x5的输出做一个合并
x6 = torch.cat([x4, x6], dim=0) # dim=0
print("x6.shape",x6.shape) # x6.shape torch.Size([512, 128, 64])
x7 = self.up1(x6)
x7 = torch.cat([x3, x7], dim=0)
x8 = self.up2(x7)
x8 = torch.cat([x2, x8], dim=0)
x9 = self.up3(x8)
x9 = torch.cat([x1, x9], dim=0)
x10 = self.conv_2(x9, is_pool=False)
result = self.last(x10)
return result
ニューラルネットワークにおける画像形状変化グラフ: