计算机系统应用教程网站

网站首页 > 技术文章 正文

Yolo-V5目标检测 项目实战(上篇)

btikc 2024-09-02 16:49:36 技术文章 14 ℃ 0 评论

引言

本文将一步一步的指导训练 Yolo-v5并进行推断来计算血细胞并定位它们。

我曾试图用 Yolo v3-v4做一个目标检测模型,在显微镜下用血液涂抹的图像上计算红细胞、白细胞和血小板,但是我没有得到我想要的准确度,而且这个模型也没有投入生产。

最近,我偶然看到了来自 Ultralytics 的 Yolo-v5模型的发布,它使用 PyTorch 构建。由于之前的失败,我一开始有点怀疑,但是在阅读了他们 Github repo 中的使用手册后,这次我非常自信,我想试一试。

而且它的效果很神奇,Yolo-v5很容易训练,也很容易推理。

所以这篇文章总结了我在血细胞计数数据集上使用 Yolo-v5模型的实践经验。

Ultralytics 最近推出了 Yolo-v5。目前,Yolo 的前三个版本是由 Joseph Redmon 创造的。但是较新的版本比其他版本有更高的平均精度和更快的推理时间。与此同时,它是在 PyTorch 的基础上构建的,这使得训练和推理过程非常快,结果也非常好。

那么,让我们来分解一下我们训练过程中的步骤。

  1. 数据ー预处理(Yolo-v5兼容)
  2. 模型ー训练
  3. 推理

1. 数据 - 预处理(Yolo-v5兼容)

我使用了 Github 上提供的数据集 BCCD 数据集,这个数据集有血迹模糊的显微镜图像,并且在 XML 文件中提供了相应的边界框标注。

Dataset Structure:
- BCCD
  - Annotations
    - BloodImage_00000.xml
    - BloodImage_00001.xml
    ...
- JpegImages
    - BloodImage_00001.jpg
    - BloodImage_00001.jpg
    ...

示例图像及其标注:

样本输入图像

XML 文件中的标签

将标注映射为图像中的边界框后,将得到如下结果:

但是为了训练 Yolo-v5模型,我们需要组织我们的数据集结构,它需要图像(.jpg/.png,等等)和它以.txt保存的相应标签。

Yolo-v5 Dataset Structure:
- BCCD
  - Images
    - Train (.jpg files)
    - Valid (.jpg files)
- Labels
     - Train (.txt files)
     - Valid (.txt files)

然后. txt 文件格式应该是:

txt 文件的结构:

- 每个对象一行。

- 每行为 class x_center y_center width height 格式。

- Box 坐标必须是标准化的 xywh 格式(从0-1开始)。如果你的 box 是以像素为单位,用图像宽度除以 x_center 和 width,用图像 height 除以 y_center 和 height。

- 类号为零索引(从0开始)。

一个示例标签,包括类1(RBC)和类2(WBC) ,以及它们各自的 x_center、 y_center、width 和 height (全部标准化为0-1) ,看起来像下面这个。

Yolo-v5 Dataset Structure:
- BCCD
  - Images
    - Train (.jpg files)
    - Valid (.jpg files)
- Labels
     - Train (.txt files)
     - Valid (.txt files)

labels.txt 示例

因此,让我们看看如何在上面指定的结构中预处理数据。

我们的第一步应该是解析所有 XML 文件中的数据,并将它们存储在数据框架中以便进一步处理。因此,我们运行下面的代码来实现它。

# Dataset Extraction from github
!git clone 'https://github.com/Shenggan/BCCD_Dataset.git'

import os, sys, random, shutil
import xml.etree.ElementTree as ET
from glob import glob
import pandas as pd
from shutil import copyfile
import pandas as pd
from sklearn import preprocessing, model_selection
import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib import patches
import numpy as np

annotations = sorted(glob('/content/BCCD_Dataset/BCCD/Annotations/*.xml'))

df = []
cnt = 0
for file in annotations:
  prev_filename = file.split('/')[-1].split('.')[0] + '.jpg'
  filename = str(cnt) + '.jpg'
  row = []
  parsedXML = ET.parse(file)
  for node in parsedXML.getroot().iter('object'):
    blood_cells = node.find('name').text
    xmin = int(node.find('bndbox/xmin').text)
    xmax = int(node.find('bndbox/xmax').text)
    ymin = int(node.find('bndbox/ymin').text)
    ymax = int(node.find('bndbox/ymax').text)

    row = [prev_filename, filename, blood_cells, xmin, xmax, ymin, ymax]
    df.append(row)
  cnt += 1

data = pd.DataFrame(df, columns=['prev_filename', 'filename', 'cell_type', 'xmin', 'xmax', 'ymin', 'ymax'])

data[['prev_filename','filename', 'cell_type', 'xmin', 'xmax', 'ymin', 'ymax']].to_csv('/content/blood_cell_detection.csv', index=False)
data.head(10)

数据帧应该是这样的:

保存此文件后,我们需要进行更改,以将其转换为与 Yolo-v5兼容的格式。

REQUIRED DATAFRAME STRUCTURE
- filename : contains the name of the image
- cell_type: denotes the type of the cell
- xmin: x-coordinate of the bottom left part of the image
- xmax: x-coordinate of the top right part of the image
- ymin: y-coordinate of the bottom left part of the image
- ymax: y-coordinate of the top right part of the image
- labels : Encoded cell-type (Yolo - label input-1)
- width : width of that bbox
- height : height of that bbox
- x_center : bbox center (x-axis)
- y_center : bbox center (y-axis)
- x_center_norm : x_center normalized (0-1) (Yolo - label input-2)
- y_center_norm : y_center normalized (0-1) (Yolo - label input-3)
- width_norm : width normalized (0-1) (Yolo - label input-4)
- height_norm : height normalized (0-1) (Yolo - label input-5)

我编写了一些代码,将现有的数据帧转换为上面代码片段中指定的结构。

img_width = 640
img_height = 480

def width(df):
  return int(df.xmax - df.xmin)
def height(df):
  return int(df.ymax - df.ymin)
def x_center(df):
  return int(df.xmin + (df.width/2))
def y_center(df):
  return int(df.ymin + (df.height/2))
def w_norm(df):
  return df/img_width
def h_norm(df):
  return df/img_height

df = pd.read_csv('/content/blood_cell_detection.csv')

le = preprocessing.LabelEncoder()
le.fit(df['cell_type'])
print(le.classes_)
labels = le.transform(df['cell_type'])
df['labels'] = labels

df['width'] = df.apply(width, axis=1)
df['height'] = df.apply(height, axis=1)

df['x_center'] = df.apply(x_center, axis=1)
df['y_center'] = df.apply(y_center, axis=1)

df['x_center_norm'] = df['x_center'].apply(w_norm)
df['width_norm'] = df['width'].apply(w_norm)

df['y_center_norm'] = df['y_center'].apply(h_norm)
df['height_norm'] = df['height'].apply(h_norm)

df.head(30)

经过预处理我们的数据帧看起来像这样,这里我们可以看到一个图像文件存在许多行(例如 blooddimage _ 0000.jpg) ,现在我们需要收集所有的(labels,x_center_norm,y_center_norm,width_norm,height_norm)值为单个图像文件,并将其保存为.txt 文件。

预处理

现在,我们将数据集分成训练集和验证集,并保存相应的图像,以及保存在.txt文件中的标签。为此,我写了一小段代码片段。

df_train, df_valid = model_selection.train_test_split(df, test_size=0.1, random_state=13, shuffle=True)
print(df_train.shape, df_valid.shape)

os.mkdir('/content/bcc/')
os.mkdir('/content/bcc/images/')
os.mkdir('/content/bcc/images/train/')
os.mkdir('/content/bcc/images/valid/')

os.mkdir('/content/bcc/labels/')
os.mkdir('/content/bcc/labels/train/')
os.mkdir('/content/bcc/labels/valid/')

def segregate_data(df, img_path, label_path, train_img_path, train_label_path):
  filenames = []
  for filename in df.filename:
    filenames.append(filename)
  filenames = set(filenames)

  for filename in filenames:
    yolo_list = []

    for _,row in df[df.filename == filename].iterrows():
      yolo_list.append([row.labels, row.x_center_norm, row.y_center_norm, row.width_norm, row.height_norm])

    yolo_list = np.array(yolo_list)
    txt_filename = os.path.join(train_label_path,str(row.prev_filename.split('.')[0])+".txt")
    # Save the .img & .txt files to the corresponding train and validation folders
    np.savetxt(txt_filename, yolo_list, fmt=["%d", "%f", "%f", "%f", "%f"])
    shutil.copyfile(os.path.join(img_path,row.prev_filename), os.path.join(train_img_path,row.prev_filename))

## Apply function ## 
src_img_path = "/content/BCCD_Dataset/BCCD/JPEGImages/"
src_label_path = "/content/BCCD_Dataset/BCCD/Annotations/"

train_img_path = "/content/bcc/images/train"
train_label_path = "/content/bcc/labels/train"

valid_img_path = "/content/bcc/images/valid"
valid_label_path = "/content/bcc/labels/valid"

segregate_data(df_train, src_img_path, src_label_path, train_img_path, train_label_path)
segregate_data(df_valid, src_img_path, src_label_path, valid_img_path, valid_label_path)

print("No. of Training images", len(os.listdir('/content/bcc/images/train')))
print("No. of Training labels", len(os.listdir('/content/bcc/labels/train')))

print("No. of valid images", len(os.listdir('/content/bcc/images/valid')))
print("No. of valid labels", len(os.listdir('/content/bcc/labels/valid')))

在运行代码之后,我们应该像预期的那样有了文件夹结构,并准备好对模型进行训练。

No. of Training images 364 
No. of Training labels 364
No. of valid images 270 
No. of valid labels 270
&&
- BCCD
  - Images
    - Train (364 .jpg files)
    - Valid (270 .jpg files)
- Labels
     - Train (364 .txt files)
     - Valid (270 .txt files)

数据预处理已完成。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表