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


    对象检测问题的基础是数据的外观。现在,本文将介绍可用于解决对象检测问题的不同深度学习架构。让我们首先讨论我们将要处理的问题陈述。
    目录
    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 值、评估指标。本文的下一个级别(未来任务)是一个图像可以有多个对象。任务是检测每个图像中的对象。希望这些文章能帮助你了解如何使用图像数据检测血细胞,如何建立检测模型,我们将使用这种技术,并将其应用于医学分析领域。