使用简单方法在图像中检测血细胞

磐创AI 2022-03-14

torch

4894 字丨阅读本文需 25 分钟

对象检测问题的基础是数据的外观。现在,本文将介绍可用于解决对象检测问题的不同深度学习架构。让我们首先讨论我们将要处理的问题陈述。

目录

1. 了解问题陈述:血细胞检测

2. 数据集链接

3. 解决对象检测问题的简单方法

4. 实施简单方法的步骤

· 加载数据集

· 数据探索

· 为简单方法准备数据集

· 创建训练和验证集

· 定义分类模型架构

· 训练模型

· 作出预测

5. 结论

了解问题陈述血细胞检测

问题陈述

对于一组给定的血细胞图像,我们必须检测图像中的白细胞。现在,这是来自数据集的示例图像。如你所见,你可以看到有一些红色阴影区域和蓝色或紫色区域。

在上图中,红色阴影区域是红细胞(RBC),紫色阴影区域是白细胞(WBC),还有一些小的黑色突出部分是血小板。

正如你在此图像中看到的那样,我们有多个对象和多个类。

为简单起见,我们将其转换为单类单对象问题。这意味着我们将只考虑白细胞。

因此,只有一个类,即白细胞,而忽略其余的类。此外,我们将只保留具有单个白细胞的图像。

因此,具有多个白细胞的图像将从该数据集中删除。

以下是我们将从该数据集中选择图像的方法。

因此,我们删除了图像 2 和图像 5,因为图像 5 没有白细胞,而图像 2 有 2 个白细胞,其他图像保留在数据集中。同样,测试集也将只有一个白细胞。

现在,对于每个图像,我们在白细胞周围都有一个边界框。正如你在这张图片中看到的,我们的文件名为 1.jpg,这些是白细胞周围边界框的边界框坐标。

在下一节中,我们将介绍解决此对象检测问题的简单方法。

解决对象检测问题的简单方法

在本节中,我们将讨论一种解决对象检测问题的简单方法。所以让我们首先了解任务,我们必须在血细胞图像中检测白细胞,可以看到下图。

现在,最简单的方法是将图像划分为多个块,因此对于此图像,将图像划分为四个块。

我们对这些块中的每一个进行分类,因此第一个块没有白细胞,第二个块有一个白细胞,同样第三个和第四个没有任何白细胞。

我们已经熟悉分类过程以及如何构建分类算法。因此,我们可以轻松地将这些单独的块中的每一个分类为 yes 和 no,以表示白细胞。

现在,在下图中,具有白细胞的块(绿色框)可以表示为边界框,因此在这种情况下,我们将取这个块的坐标值,并将其返回为白细胞的边界框。

现在为了实施这种方法,我们首先需要准备我们的训练数据。

现在可能有一个问题,为什么我们需要准备训练数据?我们已经有了这些图像和边界框。

我们的训练数据采用以下格式,其中我们有白细胞边界框和边界框坐标。

现在,请注意我们有完整图像的这些边界框坐标,但我们将把这个图像分成四个块。我们需要所有这四个块的边界框坐标。下一个问题是我们如何做到这一点?

我们必须定义一个新的训练数据,我们有文件名,如下图所示。

我们有不同的块,对于每个块,我们有 Xmin、Xmax、Ymin 和 Ymax 值,它们表示这些块的坐标,最后,我们的目标变量是白细胞。图像中是否存在白细胞?

现在在这种情况下,它将成为一个简单的分类问题。因此,对于每个图像,我们将其划分为四个不同的块,并为每个块创建边界框坐标。

现在下一个问题是我们如何创建这些边界框坐标?这真的很简单。

考虑到我们有一个大小为 (640*480) 的图像。所以原点是(0,0)。上图有 x 轴和 y 轴,这里我们的坐标值为 (640, 480)。

现在,我们找出中点,它是 (320,240)。一旦我们有了这些值,我们就可以很容易地找出每个块的坐标。所以对于第一个块,我们的 Xmin 和 Ymin 将是 (0,0) ,而 Xmax, Ymax 将是 (320,240)。

同样,我们可以在第二个、第三个和第四个块中找到它。一旦我们有了这些块中的每一个的坐标值或边界框值。下一个任务是确定此块中是否存在白细胞。

在这里我们可以清楚地看到块 2 有白细胞,而其他块没有,但是我们不能在数据集中的每个块上对每个图像都手动标注白细胞。

现在在下一节中,我们将实现简单的方法。

实施简单方法的步骤

在上一节中,我们讨论了用于对象检测的简单方法。现在让我们定义在血细胞检测问题上实施这种方法的步骤。

这些是将要遵循的步骤:

1. 加载数据集

2. 数据探索

3. 为简单方法准备数据集

4. 创建训练和验证集

5. 定义分类模型架构

6. 训练模型

7. 作出预测

让我们进入下一节,实现上述步骤。

1.加载所需的库和数据集

因此,让我们首先从加载所需的库开始。numpy和pandas,matplotlib用来可视化数据,我们已经加载了一些库来处理图像并调整图像大小,最后是torch库。

# Importing Required Libraries

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

%matplotlib inline

import os

from PIL import Image

from skimage.transform import resize

import torch

from torch import nn

现在我们将修复一个随机种子值。

# Fixing a random seed values to stop potential randomness
seed = 42

rng = np.random.RandomState(seed)

在这里,我们将安装驱动器,因为数据集存储在驱动器上。

# mount the drive

from google.colab import drive

drive.mount('/content/drive')

现在因为驱动器上的数据以 zip 格式提供。我们必须解压缩这些数据,在这里我们将解压缩数据。

所以我们可以看到所有的图像都被加载并存储在一个名为 images 的文件夹中。在这个文件夹的末尾,我们有一个 CSV 文件,它是trained.csv。

# unzip the dataset from drive

!unzip /content/drive/My Drive/train_zedkk38.zip

2.数据探索

阅读 CSV 文件并找出存储在这个“train.csv”文件中的信息是什么。

## Reading target file

data = pd.read_csv('train.csv')

data.shape

打印 CSV 文件的前几行,我们可以看到该文件具有 image_names 以及 cell_type,它将表示红细胞或白细胞等等。最后是此特定图像中此特定对象的边界框坐标。data.head()

因此,如果我们检查红细胞、白细胞和血小板的计数值。我们将看到红细胞具有最大计数值,其次是白细胞和血小板。

data.cell_type.value_counts()

现在为简单起见,我们将只考虑白细胞。因此,我们选择了只有白细胞的数据。

现在我们针对这些图像有 image_names和 cell_type WBC。还有边界框坐标。

(data.loc[data['cell_type'] =='WBC']).head()

让我们看看原始数据集中的几张图像以及这些图像的形状。

我们可以看到这些图像的形状是(480,640,3)。这是一个具有三个通道的 RGB 图像,这是数据集中的第一张图像。

image = plt.imread('images/' + '1.jpg')

print(image.shape)

plt.imshow(image)

下一步是用这个图像创建块。我们要学习如何把这张图片分成四个块。现在我们知道图像的形状是 (640, 480)。因此这张图片的中间点是 (320,240),中心是 (0, 0)。

因此,我们有图像中所有这些块的坐标,在这里我们将利用这些坐标并创建块。

这些坐标的格式将是 Ymin、Ymax、Xmin 和 Xmax。这里我们的 (Ymin, Ymax) 是 (0, 240) 并且 (Xmin, Xmax) 是 (0,320)。这基本上表示第一个块。

同样,对于随后的第二个第三个和第四个块,我们有 image_2、image_3、image_4。这是一个我们可以从图像创建块的过程。

# creating 4 patches from the image

# format ymin, ymax, xmin, xmax

image_1 = image[0:240, 0:320, :]

image_2 = image[0:240, 320:640, :]

image_3 = image[240:480, 0:320, :]

image_4 = image[240:480, 320:640, :]

现在我们需要为这些块分配一个目标值。为了做到这一点,我们计算并集的交集,我们必须找出交集区域和并集区域。

所以交集区域就是这个特定的矩形,要找出面积,我们需要找出这个矩形的 Xmin、Xmax 和 Ymin、Ymax 坐标。

def iou(box1, box2):

   Irect_xmin, Irect_ymin = max(box1[0],box2[0]), max(box1[2],box2[2])

   Irect_xmax, Irect_ymax = min(box1[1],box2[1]), min(box1[3],box2[3])

   if Irect_xmax < Irect_xmin or Irect_ymax < Irect_ymin:

       target = inter_area = 0

   else:

     inter_area = np.abs((Irect_xmax - Irect_xmin) * (Irect_ymax - Irect_ymin))

     box1_area = (box1[1]-box1[0])*(box1[3]-box1[2])

     box2_area = (box2[1]-box2[0])*(box2[3]-box2[2])

     union_area = box1_area+box2_area-inter_area

     iou = inter_area/union_area

     target = int(iou > 0.1)

   return target

我们有来自训练 CSV 文件的原始边界框坐标。当我将这两个值用作我们定义的“ iou”函数的输入时,目标为 1。

你也可以尝试使用不同的块,也可以基于你将得到的目标值。

box1= [320, 640, 0, 240]

box2= [93,    296, 1, 173]

iou(box1, box2)

输出为 0。现在下一步是准备数据集。

3.为简单方法准备数据集

我们只考虑并探索了数据集中的单个图像。因此,让我们对数据集中的所有图像执行这些步骤。这里是我们拥有的完整数据。

data.head()

现在,我们正在转换这些细胞类型,红细胞为0,白细胞为 1,血小板为 2。

data['cell_type'] = data['cell_type'].replace({'RBC': 0, 'WBC': 1, 'Platelets': 2})

现在我们必须选择只有一个白细胞的图像。

因此,首先我们创建数据集的副本,然后仅保留白细胞并删除任何具有多个白细胞的图像。

## keep only Single WBCs

data_wbc = data.loc[data.cell_type == 1].copy()

data_wbc = data_wbc.drop_duplicates(subset=['image_names', 'cell_type'], keep=False)

现在我们已经选择了图像。我们将根据输入图像大小设置块坐标。

我们正在逐一读取图像并存储该特定图像的白细胞边界框坐标,使用我们在此处定义的块坐标从该图像中提取块。

然后我们使用自定义的 IoU 函数找出每个块的目标值。最后,在这里我们将块大小调整为标准大小 (224, 224, 3)。在这里,我们正在为每个块创建最终输入数据和目标数据。

# create empty lists

X = []

Y = []

# set patch co-ordinates

patch_1_coordinates = [0, 320, 0, 240]

patch_2_coordinates = [320, 640, 0, 240]

patch_3_coordinates = [0, 320, 240, 480]

patch_4_coordinates = [320, 640, 240, 480]

for idx, row in data_wbc.iterrows():

   # read image

   image = plt.imread('images/' + row.image_names)

   bb_coordinates = [row.xmin, row.xmax, row.ymin, row.ymax]

   # extract patches

   patch_1 = image[patch_1_coordinates[2]:patch_1_coordinates[3],
                            patch_1_coordinates[0]:patch_1_coordinates[1], :]

   patch_2 = image[patch_2_coordinates[2]:patch_2_coordinates[3],
                              patch_2_coordinates[0]:patch_2_coordinates[1], :]

   patch_3 = image[patch_3_coordinates[2]:patch_3_coordinates[3],
                           patch_3_coordinates[0]:patch_3_coordinates[1], :]

   patch_4 = image[patch_4_coordinates[2]:patch_4_coordinates[3],
                           patch_4_coordinates[0]:patch_4_coordinates[1], :]

   # set default values

   target_1 = target_2 = target_3 = target_4 = inter_area = 0

   # figure out if the patch contains the object

   ## for patch_1

   target_1 = iou(patch_1_coordinates, bb_coordinates )

   ## for patch_2

   target_2 = iou(patch_2_coordinates, bb_coordinates)

   ## for patch_3

   target_3 = iou(patch_3_coordinates, bb_coordinates)

   ## for patch_4

   target_4 = iou(patch_4_coordinates, bb_coordinates)

   # resize the patches

   patch_1 = resize(patch_1, (224, 224, 3), preserve_range=True)

   patch_2 = resize(patch_2, (224, 224, 3), preserve_range=True)

   patch_3 = resize(patch_3, (224, 224, 3), preserve_range=True)

   patch_4 = resize(patch_4, (224, 224, 3), preserve_range=True)

   # create final input data

   X.extend([patch_1, patch_2, patch_3, patch_4])

   # create target data

   Y.extend([target_1, target_2, target_3, target_4])

# convert these lists to single numpy array

X = np.array(X)

Y = np.array(Y)

现在,让我们打印原始数据和刚刚创建的新数据的形状。我们可以看到我们最初有 240 张图像。

现在我们将这些图像分成四部分, 即(960,224,224,3)。这是图像的形状。

# 4 patches for every image

data_wbc.shape, X.shape, Y.shape

让我们快速看一下我们刚刚创建的这些图像之一。这是我们的原始图像,这是原始图像的最后一个块或第四个块。我们可以看到分配的目标是1。

image = plt.imread('images/' + '1.jpg')

plt.imshow(image)

如果我们检查任何其他块,假设我要检查此图像的第一个块,这里会将目标设为0。你将获得第一个块。

同样,你可以确保将所有图像转换为块并相应地分配目标。

plt.imshow(X[0].astype('uint8')), Y[0]

4.准备训练和验证集

现在我们有了数据集。我们将准备我们的训练和验证集。现在请注意,这里我们的图像形状为 (224,224,3)。

# 4 patches for every image

data_wbc.shape, X.shape, Y.shape

输出是:

((240, 6), (960, 224, 224, 3), (960,))

在 PyTorch 中,我们首先需要拥有通道。因此,我们将移动具有形状 (3,224,224) 的轴。

X = np.moveaxis(X, -1, 1)

X.shape

输出是:

(960, 3, 224, 224)

现在,我们对图像像素值进行归一化。

X = X / X.max()

使用训练测试拆分功能,我们将创建一个训练集和验证集。

from sklearn.model_selection import train_test_split

X_train, X_valid, Y_train, Y_valid=train_test_split(X, Y, test_size=0.1,
                                           random_state=seed)

X_train.shape, X_valid.shape, Y_train.shape, Y_valid.shape

上述代码的输出是:

((864, 3, 224, 224), (96, 3, 224, 224), (864,), (96,))

现在,我们要将训练集和验证集都转换为张量,因为它们是“ numpy”数组。

X_train = torch.FloatTensor(X_train)

Y_train = torch.FloatTensor(Y_train)

X_valid = torch.FloatTensor(X_valid)

Y_valid = torch.FloatTensor(Y_valid)

5.模型构建

现在,我们要构建我们的模型,在这里我们安装了一个库,它是 PyTorch 模型摘要。

!pip install pytorch-model-summary

这仅用于在 PyTorch 中打印模型摘要。现在我们从这里导入汇总函数。

from pytorch_model_summary import summary

这是我们为方法定义的架构。

我们定义了一个顺序模型,其中有Conv2d 层,输入通道数为 3,过滤器数量为 64,过滤器的大小为 5,步幅设置为 2。对于这个 Conv2d 层有 ReLU 激活函数。一个池化层,窗口大小为 4,步幅为 2,然后是卷积层。

现在展平 Conv2d 层的输出,最后是全连接层和 sigmoid 激活函数。

## model architecture

model = nn.Sequential(

       nn.Conv2d(in_channels=3, out_channels=64, kernel_size=5, stride=2),  

       nn.ReLU(),  

       nn.MaxPool2d(kernel_size=4,stride=2),  

       nn.Conv2d(in_channels=64, out_channels=64, kernel_size=5, stride=2),    

       nn.Flatten(),

       nn.Linear(40000, 1),

       nn.Sigmoid()

在这里打印模型,将是我们定义的模型架构。

print(model)

使用summary函数,我们可以查看模型摘要。因此,这将为我们返回每个层的输出形状,每个层的可训练参数的数量。现在我们的模型已经准备好了。

print(summary(model, X_train[:1]))

现在模型已经准备好训练了。

6.训练模型

让我们训练这个模型。所以我们要定义我们的损失函数和优化函数。我们将二元交叉熵定义为损失和Adam优化器。然后我们将模型传输到 GPU。

在这里,我们从输入图像中提取批次来训练这个模型。

## loss and optimizer

criterion = torch.nn.BCELoss()

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

## GPU device

if torch.cuda.is_available():

 model = model.cuda()

 criterion = criterion.cuda()

因此,我们从 x_train 中提取了批次并使用了这些批次。我们将对该模型进行总共 15 个 epoch 的训练。我们还设置了一个 optimizer.Zero_grad() 并将输出存储在这里。

现在我们正在计算损失并存储所有损失并执行反向传播和更新参数。此外,我们在每个 epoch 之后打印损失。

在输出中,我们可以看到每个时期的损失都在减少。所以这个模型的训练就完成了。

# batch size of the model

batch_size = 32

# defining the training phase

model.train()

for epoch in range(15):

   # setting initial loss as 0

   train_loss = 0.0        

   # to randomly pick the images without replacement in batches

   permutation = torch.randperm(X_train.size()[0])

   # to keep track of training loss

   training_loss = []

   # for loop for training on batches

   for i in range(0,X_train.size()[0], batch_size):

       # taking the indices from randomly generated values

       indices = permutation[i:i+batch_size]

      # getting the images and labels for a batch

       batch_x, batch_y = X_train[indices], Y_train[indices]

       if torch.cuda.is_available():

           batch_x, batch_y = batch_x.cuda().float(), batch_y.cuda().float()

       # clearing all the accumulated gradients

       optimizer.zero_grad()

       # mini batch computation

       outputs = model(batch_x)

       # calculating the loss for a mini batch

       loss = criterion(outputs.squeeze(),batch_y)

       # storing the loss for every mini batch

       training_loss.append(loss.item())

       # calculating the gradients

       loss.backward()

       # updating the parameters

       optimizer.step()

   training_loss = np.average(training_loss)

   print('epoch: t', epoch, 't training loss: t', training_loss)

7.做出预测

现在让我们使用这个模型来进行预测。所以在这里我只从验证集中获取前五个输入并将它们传输到 Cuda。

output = model(X_valid[:5].to('cuda')).cpu().detach().numpy()

这是我们拍摄的前五张图像的输出。现在我们可以看到前两个的输出是没有白细胞或有白细胞。

output

这是输出:

array([[0.00641595],

      [0.01172841],

      [0.99919134],

      [0.01065345],

      [0.00520921]], dtype=float32)

绘制图像。我们可以看到这是第三张图片,这里的模型说有一个白细胞,我们可以看到这张图片中有一个白细胞。

plt.imshow(np.transpose(X_valid[2]))

同样,我们可以检查另一张图像,因此将获取第一张图像。

你可以看到输出图像,这个图像是我们的输入块,这个块中没有白细胞。

plt.imshow(np.transpose(X_valid[1]))

这是一种非常简单的方法,可以进行预测或识别具有白细胞的图像的块或部分。

结论

使用简单方法了解使用图像数据集进行血细胞检测的实际实现。这是解决业务问题和开发模型的真正挑战。

在处理图像数据时,你必须分析一些任务,例如边界框、计算 IoU 值、评估指标。本文的下一个级别(未来任务)是一个图像可以有多个对象。任务是检测每个图像中的对象。希望这些文章能帮助你了解如何使用图像数据检测血细胞,如何建立检测模型,我们将使用这种技术,并将其应用于医学分析领域。

免责声明:凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处本网。非本网作品均来自其他媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如您发现有任何侵权内容,请依照下方联系方式进行沟通,我们将第一时间进行处理。

0赞 好资讯,需要你的鼓励
来自:磐创AI
0

参与评论

登录后参与讨论 0/1000