0 赛事背景说明

澳大利亚令人惊叹的美丽大堡礁是世界上最大的珊瑚礁,拥有 1,500 种鱼类、400 种珊瑚、130 种鲨鱼、鳐鱼和种类繁多的其他海洋生物。

不幸的是,珊瑚礁正受到威胁,部分原因是一种特定的海星——以珊瑚为食的棘冠海星(简称COTS)的数量过剩。科学家、旅游经营者和珊瑚礁管理者建立了一个大规模的干预计划,将COTS的爆发控制在生态可持续的水平。

为了知道COTS在哪里,一种传统的珊瑚礁调查方法,称为“蝠鲼拖曳”,由浮潜潜水员执行。在被船拖曳时,他们目视评估珊瑚礁,停下来记录每200米观察到的变量。虽然这种方法通常有效,但面临明显的局限性,包括操作可扩展性、数据分辨率、可靠性和可追溯性。

大堡礁基金会建立了一个创新计划,以开发新的调查和干预方法,以提供COTS控制的阶梯式变化。水下摄像机将收集数千张珊瑚礁图像,人工智能技术可以大大提高珊瑚礁管理者检测和控制COTS爆发的效率和规模。

为了扩大基于视频的测量系统,澳大利亚国家科学机构CSIRO与谷歌合作开发创新的机器学习技术,可以准确,高效,近乎实时地分析大型图像数据集。

1 赛题任务

赛题链接

赛题介绍视频(YouTube)

本次比赛的目标是通过构建在珊瑚礁水下视频上训练的物体检测模型,实时准确地识别海星。

您的工作将帮助研究人员识别威胁澳大利亚大堡礁的物种,并采取明智的行动为子孙后代保护珊瑚礁。

2 数据集介绍

下面给出数据集中六幅例图:

训练数据文件结构

将提供用于训练的图像数据和识别标签,文件夹结构:

|-- kaggle_dataset/train2017 # 存放训练图像数据,jpg编码图像文件

|-- kaggle_dataset/val2017 # 存放训练图像数据,jpg编码图像文件

|-- kaggle_dataset/annotations # 存放属性标签标注数据

数据标注文件的结构上,属于coco格式标注。

#设立随机种子
import random
random.seed(2) 
#先解压数据集
!unzip data/data179179/kaggle_dataset.zip
#下载PaddleDetection
!git clone https://gitee.com/PaddlePaddle/PaddleDetection.git #从gitee上下载速度会快一些
#安装PaddleDetection相关依赖
!pip install -r PaddleDetection/requirements.txt
!python  PaddleDetection/setup.py install

3可视化

# 调用一些需要的第三方库
import numpy as np
import pandas as pd
import shutil
import json
import os
import cv2
import glob
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import seaborn as sns
from matplotlib.font_manager import FontProperties
from PIL import Image
import random
###解决中文画图问题
myfont = FontProperties(fname=r"NotoSansCJKsc-Medium.otf", size=12)
plt.rcParams['figure.figsize'] = (12, 12)
plt.rcParams['font.family']= myfont.get_family()
plt.rcParams['font.sans-serif'] = myfont.get_name()
plt.rcParams['axes.unicode_minus'] = False
# 加载训练集路径
TRAIN_DIR = 'kaggle_dataset/train2017/'
TRAIN_CSV_PATH = 'TRAIN_CSV_PATH = kaggle_dataset/annotations/train.json'
# 加载训练集图片目录
train_fns = glob.glob(TRAIN_DIR + '*')
print('数据集图片数量: {}'.format(len(train_fns)))
数据集图片数量: 3974
def generate_anno_result(dataset_path, anno_file):
    with open(os.path.join(dataset_path, anno_file)) as f:
        anno = json.load(f)    
    total=[]
    for img in anno['images']:
        hw = (img['height'],img['width'])
        total.append(hw)
    unique = set(total)
    
    ids=[]
    images_id=[]
    for i in anno['annotations']:
        ids.append(i['id'])
        images_id.append(i['image_id'])
    
    # 创建类别标签字典
    category_dic=dict([(i['id'],i['name']) for i in anno['categories']])
    counts_label=dict([(i['name'],0) for i in anno['categories']])
    for i in anno['annotations']:
        counts_label[category_dic[i['category_id']]] += 1
    label_list = counts_label.keys()    # 各部分标签
    size = counts_label.values()    # 各部分大小

    train_fig = pd.DataFrame(anno['images'])
    train_anno = pd.DataFrame(anno['annotations'])
    df_train = pd.merge(left=train_fig, right=train_anno, how='inner', left_on='id', right_on='image_id')
    df_train['bbox_xmin'] = df_train['bbox'].apply(lambda x: x[0])
    df_train['bbox_ymin'] = df_train['bbox'].apply(lambda x: x[1])
    df_train['bbox_w'] = df_train['bbox'].apply(lambda x: x[2])
    df_train['bbox_h'] = df_train['bbox'].apply(lambda x: x[3])
    df_train['bbox_xcenter'] = df_train['bbox'].apply(lambda x: (x[0]+0.5*x[2]))
    df_train['bbox_ycenter'] = df_train['bbox'].apply(lambda x: (x[1]+0.5*x[3]))

    print('最小目标面积(像素):', min(df_train.area))

    balanced = ''
    small_object = ''
    densely = ''
    # 判断样本是否均衡,给出结论
    if max(size) > 5 * min(size):
        print('样本不均衡')
        balanced = 'c11'
    else:
        print('样本均衡')
        balanced = 'c10'
    # 判断样本是否存在小目标,给出结论
    if min(df_train.area) < 900:
        print('存在小目标')
        small_object = 'c21'
    else:
        print('不存在小目标')
        small_object = 'c20'
    arr1=[]
    arr2=[]
    x=[]
    y=[]
    w=[]
    h=[]
    for index, row in df_train.iterrows():
        if index < 1000:
            # 获取并记录坐标点
            x.append(row['bbox_xcenter'])
            y.append(row['bbox_ycenter'])
            w.append(row['bbox_w'])
            h.append(row['bbox_h'])
    for i in range(len(x)):
        l = np.sqrt(w[i]**2+h[i]**2)
        arr2.append(l)
        for j in range(len(x)):
            a=np.sqrt((x[i]-x[j])**2+(y[i]-y[j])**2)
            if a != 0:
                arr1.append(a)
    arr1=np.matrix(arr1)
    # print(arr1.min())
    # print(np.mean(arr2))
    # 判断是否密集型目标,具体逻辑还需优化
    if arr1.min() <  np.mean(arr2):
        print('密集型目标')
        densely = 'c31'
    else:
        print('非密集型目标')
        densely = 'c30'
    return balanced, small_object, densely
# 分析训练集数据
generate_anno_result('kaggle_dataset', 'annotations/train.json')
最小目标面积(像素): 288
样本均衡
存在小目标
密集型目标





('c10', 'c21', 'c31')

图片大小分布

# 读取训练集标注文件
with open('kaggle_dataset/annotations/train.json', 'r', encoding='utf-8') as f:
    train_data = json.load(f)
train_fig = pd.DataFrame(train_data['images'])
train_fig.head()
idlicensefile_nameheightwidthdate_captured
028510-878.jpg72012802021-11-30T15:01:26+00:00
128610-879.jpg72012802021-11-30T15:01:26+00:00
228710-880.jpg72012802021-11-30T15:01:26+00:00
328810-881.jpg72012802021-11-30T15:01:26+00:00
428910-882.jpg72012802021-11-30T15:01:26+00:00
ps = np.zeros(len(train_fig))
for i in range(len(train_fig)):
    ps[i]=train_fig['width'][i] * train_fig['height'][i]/1e6
plt.title('训练集图片大小分布', fontproperties=myfont)
sns.distplot(ps, bins=21,kde=False)
<matplotlib.axes._subplots.AxesSubplot at 0x7f3819e35290>



/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/font_manager.py:1331: UserWarning: findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans
  (prop.get_family(), self.defaultFamily[fontext]))

在这里插入图片描述

可以看出,所有图片大小都相同,都为720*1280

训练集目标大小分布

!python box_distribution.py --json_path kaggle_dataset/annotations/train.json
Median of ratio_w is 0.0341796875
Median of ratio_h is 0.055
all_img with box:  3974
all_ann:  10589
Distribution saved as box_distribution.jpg
Figure(640x480)

注意:

  • 当原始数据集全部有标注框的图片中,有1/2以上的图片标注框的平均宽高与原图宽高比例小于0.04时,建议进行切图训练。

所以根据上面的说明,海星检测这个数据集其实介于“可切可不切”之间。当然,我们可以继续试验下,看看切图是否可以取得更加优秀的效果

train_anno = pd.DataFrame(train_data['annotations'])
df_train = pd.merge(left=train_fig, right=train_anno, how='inner', left_on='id', right_on='image_id')
df_train['bbox_xmin'] = df_train['bbox'].apply(lambda x: x[0])
df_train['bbox_ymin'] = df_train['bbox'].apply(lambda x: x[1])
df_train['bbox_w'] = df_train['bbox'].apply(lambda x: x[2])
df_train['bbox_h'] = df_train['bbox'].apply(lambda x: x[3])
df_train['bbox_xcenter'] = df_train['bbox'].apply(lambda x: (x[0]+0.5*x[2]))
df_train['bbox_ycenter'] = df_train['bbox'].apply(lambda x: (x[1]+0.5*x[3]))
df_train['bbox_w'].max(),df_train['bbox_h'].max()
(243, 222)
ps = np.zeros(len(df_train))
for i in range(len(df_train)):
    ps[i]=df_train['area'][i]/1e6
    ps = np.zeros(len(df_train))
plt.title('训练集目标大小分布', fontproperties=myfont)
sns.distplot(ps, bins=21,kde=True)
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/seaborn/distributions.py:288: UserWarning: Data must have variance to compute a kernel density estimate.
  warnings.warn(msg, UserWarning)





<matplotlib.axes._subplots.AxesSubplot at 0x7f3819ce2290>

在这里插入图片描述

各类别目标形状分布

# 各类别目标形状分布
sns.set(rc={'figure.figsize':(12,6)})
sns.relplot(x="bbox_w", y="bbox_h", hue="category_id", col="category_id", data=df_train[0:1000])
<seaborn.axisgrid.FacetGrid at 0x7f3d8bb6f350>

在这里插入图片描述

各类别目标中心点形状分布

# 各类别目标中心点形状分布
sns.set(rc={'figure.figsize':(12,6)})
sns.relplot(x="bbox_xcenter", y="bbox_ycenter", hue="category_id", col="category_id", data=df_train[0:1000]);

在这里插入图片描述

# 训练集目标大小统计结果
df_train.area.describe()
count    10589.000000
mean      2169.974974
std       1897.813611
min        288.000000
25%       1152.000000
50%       1728.000000
75%       2850.000000
max      52170.000000
Name: area, dtype: float64

训练集目标个数分布

df_train['bbox_count'] = df_train.apply(lambda row: 1 if any(row.bbox) else 0, axis=1)
train_images_count = df_train.groupby('file_name').sum().reset_index()
plt.title('训练集目标个数分布', fontproperties=myfont)
sns.distplot(train_images_count['bbox_count'], bins=21,kde=True)
<matplotlib.axes._subplots.AxesSubplot at 0x7f3d8b493a90>

在这里插入图片描述

分析结论:海星检测的数据集图片分辨率相同,均为1280*720、小目标占比相当大。

总的来说,这是个比较典型的小目标检测场景。

4 方案

本项目使用飞桨目标检测开发套件PaddleDetection,它可以端到端地完成从训练到部署的全流程目标检测应用。

具体方案

算法选择

由于本项目是典型的小目标检测,所以选取针对小目标优化的PP-YOLOE-SOD模型,并采取切图方法

相比PP-YOLOE模型,PP-YOLOE-SOD改进点主要包括在neck中引入 Transformer全局注意力机制 以及在回归分支中使用 基于向量的DFL 。

数据增强

- RandomExpand: {fill_value: [123.675, 116.28, 103.53]}
- GridMask: {}
- Mixup: {}
- Cutmix: {}
- RandomCrop: {}
- RandomFlip: {}
# 安装`sahi`库,用来切图
!pip install sahi
# 对训练集标注进行切图
!python PaddleDetection/tools/slice_image.py --image_dir kaggle_dataset/train2017\
 --json_path kaggle_dataset/annotations/train.json --output_dir kaggle_dataset/train2017_400_sliced --slice_size 400 --overlap_ratio 0.25
# 对验证集标注进行切图
!python PaddleDetection/tools/slice_image.py --image_dir kaggle_dataset/val2017\
 --json_path kaggle_dataset/annotations/valid.json --output_dir kaggle_dataset/val2017_400_sliced --slice_size 400 --overlap_ratio 0.25
####参数说明
#image_dir 原始数据集图片文件夹的路径
#json_path coco标注数据文件地址
#output_dir 切图结果所在文件位置
#slice_size 切图大小(默认为正方形)
#overlap_ratio 切分时的子图之间的重叠率

模型训练

直接使用PaddleDetection进行模型训练的过程可以很简单,改改配置文件就好了。详细说明可查看PaddleDetection模型参数配置教程
本项目使用的模型是ppyoloe_p2_crn_l_80e_sliced_xview_400_025.yml,相关配置文件如下:

  • configs/ppyoloe/_base_/xview_sliced_400_025_detection.yml
    • 数据配置文件

configs/smalldet/ppyoloe_p2_crn_l_80e_sliced_xview_400_025.yml
- sniper模型配置文件

  • configs/runtime.yml
    • 运行时配置文件
  • configs/ppyoloe/_base_/ppyoloe_crn.yml
    • 模型配置文件
  • configs/ppyoloe/_base_/optimizer_300e.yml
    • 优化器配置文件
  • configs/ppyoloe/_base_/ppyoloe_reader.yml
    • 数据读取配置文件

在使用配置文件,尤其是数据配置文件时,读者请注意尽量使用绝对路径配置数据集,可以避免不少报错。

毫无疑问,切图后模型的训练是要基于切图数据集的,配置如下:

metric: COCO
num_classes: 1

TrainDataset:
  !COCODataSet
    image_dir: train_images_400_025
    anno_path: train_400_025.json
    dataset_dir: kaggle_dataset/train2017_400_sliced
    data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd']

EvalDataset:
  !COCODataSet
    image_dir: valid_images_400_025
    anno_path: valid_400_025.json
    dataset_dir: kaggle_dataset/val2017_400_sliced

# 开始训练,训练环境为单卡V100(32G)
!python PaddleDetection/tools/train.py -c configs/ppyoloe_p2_crn_l_80e_sliced_xview_400_025.yml --use_vdl=True -o worker_num=8 --eval

训练时间较长,一个epoch大约九分钟,评估需要约三分半钟,可以提交后台任务执行。相关设置可参考:后台任务使用说明

然后再将后台任务的输出加载回项目中,继续进行评估操作。

模型评估

训练过程可以点击左侧的可视化按钮查看趋势:

#模型评估
!python PaddleDetection/tools/eval.py -c configs/ppyoloe_p2_crn_l_80e_sliced_xview_400_025.yml
W1205 12:10:58.174906 17614 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 8.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1205 12:10:58.178177 17614 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
loading annotations into memory...
Done (t=0.09s)
creating index...
index created!
[12/05 12:11:03] ppdet.utils.checkpoint INFO: Finish loading model weights: bestmodel/best_model.pdparams
[12/05 12:11:05] ppdet.engine INFO: Eval iter: 0
[12/05 12:11:54] ppdet.engine INFO: Eval iter: 100
[12/05 12:13:01] ppdet.metrics.metrics INFO: The bbox result is saved to bbox.json.
loading annotations into memory...
Done (t=0.02s)
creating index...
index created!
[12/05 12:13:01] ppdet.metrics.coco_utils INFO: Start evaluate...
Loading and preparing results...
DONE (t=11.11s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *bbox*
DONE (t=15.44s).
Accumulating evaluation results...
DONE (t=2.56s).
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.502
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.810
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.574
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.172
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.574
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.760
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.499
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.591
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.619
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.408
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.670
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.800
[12/05 12:13:32] ppdet.engine INFO: Total sample number: 2663, averge FPS: 26.871781479080514

sample number: 2663, averge FPS: 26.871781479080514

训练结果





方案效果对比

方案ap @[ IoU=0.50:0.95]
ppyoloe0.315
切图+ppyoloe-sod0.490
切图+ppyoloe-sod +数据增强0.502

进一步改进的方向

  • 采用多模型融合的策略
  • 使用更大的backbone,如convnext
  • 可以尝试使用two_stage算法
  • 进一步扩展数据增强

5总结

本项目是一个典型的小目标检测场景,直接用yolo算法做简单的finetune并不能取得很好的结果,在本项目只取得了ap为0.315的成绩。
所以针对此场景,采用切图的方法,并选取了针对小目标场景优化后的算法,效果得到明显改善,ap达到了0.490。进一步采取数据增强的方式,模型ap值提高到0.502。
同时,本项目仍然有很大的优化空间。

关于作者

项目作者: 姓名:李灿 AI Studio昵称: Nefelibata0 个人主页

飞桨导师: 姓名:韩磊 AI Studio昵称: ninetailskim 个人主页

PS:本人菜鸟一只,欢迎互相关注


此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐