SiameseFC-TensorFlow 代码详细注解(二):训练数据VID2015预处理
说明:该系列博客源码链接为:https://github.com/bilylee/SiamFC-TensorFlow,是实验室同小组的师兄用TensorFlow实现SiameseFC算法的最终公开版本,经过了长时间的打磨,各个模块功能明确,整体可读性和可移植性极好,我相信这对做Tracking的小伙伴来说,是个入门SiameseFC Tracker的特别好的选择。哈哈,觉得代码很棒的小伙伴们可以点个Star哦,也欢迎交流学习和指教。
上一篇介绍跑SiameseFC代码的主要流程,这一篇主要介绍开始训练自己的模型的第一步:训练数据预处理。
1:VID2015相关链接
ImageNet比赛官网 :http://image-net.org/challenges/LSVRC/,可以了解比赛信息,包含各比赛实验数据。
VID2015数据下载链接:http://bvisionweb1.cs.unc.edu/ilsvrc2015/download-videos-3j16.php#vid
2:ILSVRC2015 VID数据
解压缩之前,数据大小约86G,解压缩之后的文件夹目录如下:
ImageSets:包含一些.txt文件,子数据集的相关描述,预处理过程中用不到,可忽略。
Data :存储的所有数据信息,包括了图片(val,train,test)和视频片段(snippets)。
Annotations:对应的Data中图片的注释信息,包括val和train部分。
Data子文件夹VID下的文件目录如下:
查看当前文件目录下的文件数量命令:(R代表包含子目录)
ls -lR|grep"^-"| wc -l
可通过上面的指令进入到对应的文件夹中统计VID数据的详细信息如下:
Snippets : 3862+ 555 + 937 = 5354 videos
Train : 3862 videos ( 1122397 images )
Val : 555 videos ( 176126 images )
Test : 937 videos ( 315175 images )
Annotations子文件夹VID下的文件目录如下:
只包含了val和train部分,和上面Data/VID/val和train相对应,存储的是每个图片的.xml格式的标注信息。
挑出来一个.xml文件看看里面长什么样,如下:
.xml注释文件有我们需要用到的folder,filename,size,trackid,bndbox等信息,后面会解析出相关的信息的,说明一下这个trackid很重要,同一个image中有多个trackid说明包含该image的短视频存在着很多个目标,在后面就会解析出来更多的videos,记住这点对看后面的数据处理有帮助。
3:VID2015训练数据预处理
我们下载了VID数据之后也知道数据长什么样了,那如何处理这些数据使得可供SiameseFC训练呢?第一步就需要按照SiameseFC论文中Data Curation的部分对数据进行预处理,接着将训练数据存储为.pickle文件方便训练加载,最后还需要下载一些预训练模型和一个测试的视频,详细内容参见下面的注解。
3.1 Data Curation
核心文件:scripts/preprocess_VID_data.py
核心功能:根据SiamsesFC论文中的Curation方式进行图片预处理。
-
#! /usr/bin/env python
-
# -*- coding: utf-8 -*-
-
#
-
# Copyright © 2017 bily Huazhong University of Science and Technology
-
#
-
# Distributed under terms of the MIT license.
-
-
from __future__
import absolute_import
-
from __future__
import division
-
from __future__
import print_function
-
-
import os
-
import os.path
as osp
-
import sys
-
import xml.etree.ElementTree
as ET
# 解析xml文件的模块
-
from glob
import glob
-
from multiprocessing.pool
import ThreadPool
# 多进程加速
-
import cv2
-
from cv2
import imread, imwrite
# 使用 opencv 读写图像
-
-
CURRENT_DIR = osp.dirname(__file__)
-
ROOT_DIR = osp.join(CURRENT_DIR,
'..')
-
sys.path.append(ROOT_DIR)
# 添加搜索路径
-
-
from utils.infer_utils
import get_crops, Rectangle, convert_bbox_format
# 自己写的小函数
-
from utils.misc_utils
import mkdir_p
-
-
def get_track_save_directory(save_dir, split, subdir, video):
# 存储路径的一个简单映射函数,简化存储目录
-
subdir_map = {
'ILSVRC2015_VID_train_0000':
'a',
# 将train映射到(a,b,c,d),val映射到(e)
-
'ILSVRC2015_VID_train_0001':
'b',
-
'ILSVRC2015_VID_train_0002':
'c',
-
'ILSVRC2015_VID_train_0003':
'd',
-
'':
'e'}
-
return osp.join(save_dir,
'Data',
'VID', split, subdir_map[subdir], video)
-
-
-
def process_split(root_dir, save_dir, split, subdir='', ):
# 数据处理核心函数,spilt就是['val','train']中的值
-
data_dir = osp.join(root_dir,
'Data',
'VID', split)
# 待处理图片数据路径
-
anno_dir = osp.join(root_dir,
'Annotations',
'VID', split, subdir)
# 待处理图片的注释数据路径
-
video_names = os.listdir(anno_dir)
# train和val数据集所有的video names,因为我们只有这两个数据集的annotations
-
-
for idx, video
in enumerate(video_names):
-
print(
'{split}-{subdir} ({idx}/{total}): Processing {video}...'.format(split=split, subdir=subdir,
-
idx=idx, total=len(video_names),
-
video=video))
-
video_path = osp.join(anno_dir, video)
-
xml_files = glob(osp.join(video_path,
'*.xml'))
# 获得当前video的所有图片对应的.xml文件
-
-
for xml
in xml_files:
# 使用ET处理单个图片(.jpeg && .xml)
-
tree = ET.parse(xml)
-
root = tree.getroot()
-
-
folder = root.find(
'folder').text
# 解析.xml文件的folder
-
filename = root.find(
'filename').text
# 解析.xml文件中的filename
-
-
# Read image
-
img_file = osp.join(data_dir, folder, filename +
'.JPEG')
# 将.xml文件名对应到相同的图片文件名
-
img =
None
-
-
# Get all object bounding boxes
-
bboxs = []
-
for object
in root.iter(
'object'):
# 找到所有的objects
-
bbox = object.find(
'bndbox')
# 找到 box项,.xml文件中名称是bndbox
-
xmax = float(bbox.find(
'xmax').text)
# 找到对应的 xmax并转换为float类型的box数据,以下类似
-
xmin = float(bbox.find(
'xmin').text)
-
ymax = float(bbox.find(
'ymax').text)
-
ymin = float(bbox.find(
'ymin').text)
-
width = xmax - xmin +
1
# 计算width 和 height
-
height = ymax - ymin +
1
-
bboxs.append([xmin, ymin, width, height])
# 返回的box的形式是[xmin,ymin,wedth,height]
-
-
for idx, object
in enumerate(root.iter(
'object')):
-
id = object.find(
'trackid').text
# 获取object的trackid,因为同一个video中可能存在多个需要跟踪的目标,加以区分
-
class_name = object.find(
'name').text
# 所属类别名称(VID中的30个大类)
-
-
track_save_dir = get_track_save_directory(save_dir,
'train', subdir, video)
# 获取存储路径
-
mkdir_p(track_save_dir)
-
savename = osp.join(track_save_dir,
'{}.{:02d}.crop.x.jpg'.format(filename, int(id)))
-
if osp.isfile(savename):
continue
# skip existing images # 文件存在的情况下无需存储
-
-
if img
is
None:
-
img = imread(img_file)
# 读取image文件
-
-
# Get crop
-
target_box = convert_bbox_format(Rectangle(*bboxs[idx]),
'center-based')
# box格式转换
-
crop, _ = get_crops(img, target_box,
-
size_z=
127, size_x=
255,
-
context_amount=
0.5, )
-
-
imwrite(savename, crop, [int(cv2.IMWRITE_JPEG_QUALITY),
90])
# 图片存储,质量为90
-
-
-
if __name__ ==
'__main__':
-
vid_dir = osp.join(ROOT_DIR,
'data/ILSVRC2015')
# VID原始数据集存储的根目录,可以跟改为自己存储数据的绝对路径
-
-
# Or, you could save the actual curated data to a disk with sufficient space
-
# then create a soft link in `data/ILSVRC2015-VID-Curation`
-
save_dir =
'data/ILSVRC2015-VID-Curation'
# 处理之后的数据存储的位置,可以修改为绝对路径
-
-
pool = ThreadPool(processes=
5)
# 开启5个线程加快处理速度
-
-
one_work =
lambda a, b: process_split(vid_dir, save_dir, a, b)
# 执行函数,下面调用的时候每个会在每个线程中执行
-
-
results = []
-
results.append(pool.apply_async(one_work, [
'val',
'']))
# 非阻塞方式,线程1调用one_work处理val数据集,下类似
-
results.append(pool.apply_async(one_work, [
'train',
'ILSVRC2015_VID_train_0000']))
-
results.append(pool.apply_async(one_work, [
'train',
'ILSVRC2015_VID_train_0001']))
-
results.append(pool.apply_async(one_work, [
'train',
'ILSVRC2015_VID_train_0002']))
-
results.append(pool.apply_async(one_work, [
'train',
'ILSVRC2015_VID_train_0003']))
-
-
ans = [res.get()
for res
in results]
# 获取子线程结果,作用就是阻塞主线程,等待子线程执行完成
3.2 将训练数据保存为pickle格式
核心文件:scripts/build_VID2015_imdb.py
核心功能:将Data Curation处理后的训练数据存储为.pickle格式(train_imdb.pickle 和 validation_imdb.pickle)
-
#! /usr/bin/env python
-
# -*- coding: utf-8 -*-
-
#
-
# Copyright © 2017 bily Huazhong University of Science and Technology
-
#
-
# Distributed under terms of the MIT license.
-
-
"""Save the paths of crops from the ImageNet VID 2015 dataset in pickle format"""
-
from __future__
import absolute_import
-
from __future__
import division
-
from __future__
import print_function
-
-
import glob
-
import os
-
import os.path
as osp
-
import pickle
# 导入pickle模块,将VID数据存储为pickle格式
-
import sys
-
-
import numpy
as np
-
import tensorflow
as tf
-
-
CURRENT_DIR = osp.dirname(__file__)
-
sys.path.append(osp.join(CURRENT_DIR,
'..'))
# 添加搜索路径
-
-
from utils.misc_utils
import sort_nicely
-
-
-
class Config:
# 参数配置
-
### Dataset
-
# directory where curated dataset is stored
-
dataset_dir =
'/home/ssd_datasets/ILSVRC2015_curated'
# 之前curated数据测存储路径,可使用绝对路径或相对路径
-
save_dir =
'/workspace/czx/Projects/SiamFC-TensorFlow/assets/'
# imdb文件存储路径
-
-
# percentage of all videos for validation
-
validation_ratio =
0.1
# validation所占的数据比例
-
-
-
class DataIter:
-
"""Container for dataset of one iteration"""
-
pass
-
-
-
class Dataset:
# 数据集类
-
def __init__(self, config):
# 初始化参数设置
-
self.config = config
-
-
def _get_unique_trackids(self, video_dir):
# 根据video_dir来获取特定的跟踪目标
-
"""Get unique trackids within video_dir"""
-
x_image_paths = glob.glob(video_dir +
'/*.crop.x.jpg')
# 取得video_dir下所有crop图片存储的文件名
-
trackids = [os.path.basename(path).split(
'.')[
1]
for path
in x_image_paths]
# 根据文件名获取对应trackid
-
unique_trackids = set(trackids)
# 因为每个视频snippet都可能有很多段小视频,每个小视频里面又会有多个跟踪的目标
-
return unique_trackids
-
-
def dataset_iterator(self, video_dirs):
# 将所有的videos进行处理
-
video_num = len(video_dirs)
# videos的数量
-
iter_size =
150
# 每次循环处理的视频个数
-
iter_num = int(np.ceil(video_num / float(iter_size)))
-
for iter_
in range(iter_num):
-
iter_start = iter_ * iter_size
-
iter_videos = video_dirs[iter_start: iter_start + iter_size]
# 每次处理的videos数量为150
-
-
data_iter = DataIter()
-
num_videos = len(iter_videos)
-
instance_videos = []
-
for index
in range(num_videos):
-
print(
'Processing {}/{}...'.format(iter_start + index, video_num))
-
video_dir = iter_videos[index]
# 单个视频路径
-
trackids = self._get_unique_trackids(video_dir)
# 该视频对应到有多少个target trackid
-
-
for trackid
in trackids:
-
instance_image_paths = glob.glob(video_dir +
'/*' + trackid +
'.crop.x.jpg')
#trackid所有图片的路径
-
-
# sort image paths by frame number
-
instance_image_paths = sort_nicely(instance_image_paths)
# 根据image number排序
-
-
# get image absolute path # 获得排序好的图片计算对应的图片的绝对路径
-
instance_image_paths = [os.path.abspath(p)
for p
in instance_image_paths]
-
instance_videos.append(instance_image_paths)
-
data_iter.num_videos = len(instance_videos)
# 150个视频里面trackid的数量,>=150
-
data_iter.instance_videos = instance_videos
-
yield data_iter
# yield返回一个生成器
-
-
def get_all_video_dirs(self):
# 获取VID数据集中所有videos的路径
-
ann_dir = os.path.join(self.config.dataset_dir,
'Data',
'VID')
-
all_video_dirs = []
-
# 根据之前预处理的训练数据存储的文件结构进行解析获取所有trackid的路径
-
# We have already combined all train and val videos in ILSVRC2015 and put them in the `train` directory.
-
# The file structure is like:
-
# train
-
# |- a
-
# |- b
-
# |_ c
-
# |- ILSVRC2015_train_00024001
-
# |- ILSVRC2015_train_00024002
-
# |_ ILSVRC2015_train_00024003
-
# |- 000045.00.crop.x.jpg
-
# |- 000046.00.crop.x.jpg
-
# |- ...
-
train_dirs = os.listdir(os.path.join(ann_dir,
'train'))
-
for dir_
in train_dirs:
-
train_sub_dir = os.path.join(ann_dir,
'train', dir_)
-
video_names = os.listdir(train_sub_dir)
-
train_video_dirs = [os.path.join(train_sub_dir, name)
for name
in video_names]
-
all_video_dirs = all_video_dirs + train_video_dirs
-
-
return all_video_dirs
-
-
-
def main():
-
# Get the data.
-
config = Config()
# 加载参数设置
-
dataset = Dataset(config)
# dataset实例
-
all_video_dirs = dataset.get_all_video_dirs()
# 获取所有VID中videos的路径
-
num_validation = int(len(all_video_dirs) * config.validation_ratio)
# validation数据数量
-
-
### validation 数据
-
validation_dirs = all_video_dirs[:num_validation]
-
validation_imdb = dict()
-
validation_imdb[
'videos'] = []
-
for i, data_iter
in enumerate(dataset.dataset_iterator(validation_dirs)):
-
validation_imdb[
'videos'] += data_iter.instance_videos
-
-
validation_imdb[
'n_videos'] = len(validation_imdb[
'videos'])
# 视频数量
-
validation_imdb[
'image_shape'] = (
255,
255,
3)
# 图片大小
-
-
### train 数据
-
train_dirs = all_video_dirs[num_validation:]
-
train_imdb = dict()
-
train_imdb[
'videos'] = []
-
for i, data_iter
in enumerate(dataset.dataset_iterator(train_dirs)):
-
train_imdb[
'videos'] += data_iter.instance_videos
-
train_imdb[
'n_videos'] = len(train_imdb[
'videos'])
-
train_imdb[
'image_shape'] = (
255,
255,
3)
-
-
if
not tf.gfile.IsDirectory(config.save_dir):
# imdb数据存储路径
-
tf.logging.info(
'Creating training directory: %s', config.save_dir)
-
tf.gfile.MakeDirs(config.save_dir)
-
-
with open(os.path.join(config.save_dir,
'validation_imdb.pickle'),
'wb')
as f:
-
pickle.dump(validation_imdb, f)
-
with open(os.path.join(config.save_dir,
'train_imdb.pickle'),
'wb')
as f:
-
pickle.dump(train_imdb, f)
-
-
-
if __name__ ==
'__main__':
-
main()
4: 小结
1:核心介绍了VID2015数据集的结构是怎么样的,以及进行SiameseFC训练之前需要对该数据集如何处理。
2:这部分数据处理因为很早之前就已经处理好了,这里只贴一下一个样例吧。
原始图片样例:/Data/VID/train/ILSVRC2015_VID_train_0000/ILSVRC2015_train_00005004/000000.jpeg
原始的图片大小:1280*256。(下图是原图片截图)
Data Curation之后保存的图片:255*255,这就是训练过程中加载的search image。(也是截图)
这里还贴一个训练过程中crop出来的一个127*127的examplar吧。(还是截图)
3:训练数据VID2015的准备工作到这基本就做好了,接下来该聊聊如何构建模型了。要是觉得这份代码还不错,就顺手点个star吧,哈哈。