以下链接是个人关于fast-reid(BoT行人重识别) 所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:17575010159 相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。 行人重识别02-00:fast-reid(BoT)-目录-史上最新无死角讲解
极度推荐的商业级项目: \color{red}{极度推荐的商业级项目:} 极度推荐的商业级项目:这是本人落地的行为分析项目,主要包含(1.行人检测,2.行人追踪,3.行为识别三大模块):行为分析(商用级别)00-目录-史上最新无死角讲解
前言我们在 fastreid/engine/defaults.py 文件中可以看到类 class DefaultTrainer(SimpleTrainer),然后找到如下代码:
def __init__(self, cfg):
......
# 创建训练数据及迭代器
data_loader = self.build_train_loader(cfg)
build_train_loader的实现如下:
@classmethod
def build_train_loader(cls, cfg):
"""
构建一个训练数据迭代器
Returns:
iterable
It now calls :func:`fastreid.data.build_detection_train_loader`.
Overwrite it if you'd like a different data loader.
"""
logger = logging.getLogger(__name__)
logger.info("Prepare training set")
return build_reid_train_loader(cfg)
其上的 build_reid_train_loader(cfg),就是该小节重点讲解的内容。本人注释如下(粗略注释,详细的在后面):
def build_reid_train_loader(cfg):
# 构建数据迭代器
cfg = cfg.clone()
# cfg配置解冻
cfg.defrost()
# 训练数目列表
train_items = list()
# 循环加载多个数据集
for d in cfg.DATASETS.NAMES:
# 根据数据集名称,创建对应数据集迭代的类,本人调试为类 fastreid.data.datasets.market1501.Market1501对象
dataset = DATASET_REGISTRY.get(d)(root=_root, combineall=cfg.DATASETS.COMBINEALL)
# 如果为主线程,则显示训练信息
if comm.is_main_process():
dataset.show_train()
# dataset.train包含的都是训练数据的信息,示例部分数据如下:
# 图片路径 身份ID 摄像头编号
#'datasets/Market-1501-v15.09.15/bounding_box_train/0309_c3s2_037562_02.jpg', 'market1501_309', 2
# 'datasets/Market-1501-v15.09.15/bounding_box_train/0208_c1s1_045426_08.jpg', 'market1501_208', 0
# 要知道具体的过程,需要查看类fastreid.data.datasets.market1501.Market1501
train_items.extend(dataset.train)
# 获得每个epoch需要迭代的次数
iters_per_epoch = len(train_items) // cfg.SOLVER.IMS_PER_BATCH
# 更改数据总共需要迭代的次数
cfg.SOLVER.MAX_ITER *= iters_per_epoch
# 构建train_transforms,其中包括了数据预处理,数据增强,剪切等等
train_transforms = build_transforms(cfg, is_train=True)
# CommDataset继承于Dataset,并且其中实现了__getitem__函数,
# train_items为一个列表,还包含了所有训练数据的信息
train_set = CommDataset(train_items, train_transforms, relabel=True)
# 获得线程数目
num_workers = cfg.DATALOADER.NUM_WORKERS
# 获得实例数目(每个身份采集多少张图片,论文中的K)
num_instance = cfg.DATALOADER.NUM_INSTANCE
# 获得每个GPU的batch_size
mini_batch_size = cfg.SOLVER.IMS_PER_BATCH // comm.get_world_size()
if cfg.DATALOADER.PK_SAMPLER:
# 如果使用简单的采样方式
if cfg.DATALOADER.NAIVE_WAY:
data_sampler = samplers.NaiveIdentitySampler(train_set.img_items,
cfg.SOLVER.IMS_PER_BATCH, num_instance)
# 如果使用平衡的采样方式
else:
data_sampler = samplers.BalancedIdentitySampler(train_set.img_items,
cfg.SOLVER.IMS_PER_BATCH, num_instance)
else:
data_sampler = samplers.TrainingSampler(len(train_set))
# 构建batch数据迭代采样器,指定加载数据的线程数目等
batch_sampler = torch.utils.data.sampler.BatchSampler(data_sampler, mini_batch_size, True)
train_loader = torch.utils.data.DataLoader(
train_set,
num_workers=num_workers,
batch_sampler=batch_sampler,
collate_fn=fast_batch_collator,
)
return train_loader
其上的代码中,有如下几个地方需要重点讲解:
for d in cfg.DATASETS.NAMES:
# 根据数据集名称,创建对应数据集迭代的类,本人调试为类 fastreid.data.datasets.market1501.Market1501对象
dataset = DATASET_REGISTRY.get(d)(root=_root, combineall=cfg.DATASETS.COMBINEALL)
train_items.extend(dataset.train)
train_set = CommDataset(train_items, train_transforms, relabel=True)
data_sampler = samplers.NaiveIdentitySampler(train_set.img_items,
cfg.SOLVER.IMS_PER_BATCH, num_instance)
CommDataset
其上的DATASET_REGISTRY.get(d)(root=_root, combineall=cfg.DATASETS.COMBINEALL),就是 fastreid/data/datasets/market1501.py文件中类class Market1501(ImageDataset)创建的对象,我们下个小节进行讲解。只要知道其中有个重要的属性 d a t a s e t . t r a i n \color{red}{dataset.train} dataset.train(一个列表) 包含了一个数据集所有训练的信息,然后多个数据的信息共同被添加到 train_items 之中。我们这篇博客先来看看:
train_set = CommDataset(train_items, train_transforms, relabel=True)
本人注释如下:
class CommDataset(Dataset):
"""Image Person ReID Dataset"""
def __init__(self, img_items, transform=None, relabel=True):
self.img_items = img_items
self.transform = transform
self.relabel = relabel
self.pid_dict = {}
# 如果重新刷新标签
if self.relabel:
pids = list()
# 获得每张图像的信息
for i, item in enumerate(img_items):
# 如果id已经出现在pids,表示重复,则跳过
if item[1] in pids: continue
# 否则添加到pids之中
pids.append(item[1])
# 获得所有的pids
self.pids = pids
# 为每个ID分配一个序列号
self.pid_dict = dict([(p, i) for i, p in enumerate(self.pids)])
pass
def __len__(self):
return len(self.img_items)
def __getitem__(self, index):
# 根据index,获得其图像的img_path, pid, camid信息
img_path, pid, camid = self.img_items[index]
# 根据图像路径读取图像像素
img = read_image(img_path)
# 如果self.transform不为none,则进行transform处理
if self.transform is not None: img = self.transform(img)
# 如果刷新了标签,则标签ID更改为self.pid_dict[pid]
if self.relabel: pid = self.pid_dict[pid]
# 返回数据,用于进行训练或者测试
return {
"images": img,
"targets": pid,
"camid": camid,
"img_path": img_path
}
@property
def num_classes(self):
return len(self.pids)
可以看到其实现过程还是很简单的,主要对融合的数据进行一个ID的更新,并且实现了__getitem__函数。
NaiveIdentitySampler在def build_reid_train_loader(cfg):函数中,可以看到如下代码:
data_sampler = samplers.NaiveIdentitySampler(train_set.img_items,cfg.SOLVER.IMS_PER_BATCH, num_instance)
data_sampler = samplers.BalancedIdentitySampler(train_set.img_items,cfg.SOLVER.IMS_PER_BATCH, num_instance)
data_sampler = samplers.TrainingSampler(len(train_set))
他们实现的过程都相差不大,这里就以NaiveIdentitySampler为例进行讲解:
class NaiveIdentitySampler(Sampler):
"""
# 首先随机采集N个ID,然后每个ID选择K个实例图像
Randomly sample N identities, then for each identity,
randomly sample K instances, therefore batch size is N*K.
Args:
# 训练数据的列表,包含了所有训练的数据,也就是多个数据源
- data_source (list): list of (img_path, pid, camid).
# 在每个batch中,对每个ID采集num_instances图像
- num_instances (int): number of instances per identity in a batch.
- batch_size (int): number of examples in a batch.
"""
def __init__(self, data_source: str, batch_size: int, num_instances: int, seed: Optional[int] = None):
# 包含了多个数据集的训练信息,如图片路径,身份ID,摄像头编号等等
self.data_source = data_source
self.batch_size = batch_size # 论文中的B
# 对每个身份采集的图像数目(论文中的K)
self.num_instances = num_instances
# 通过计算获得每个batch需要采集多少个身份ID(论文中的P)
self.num_pids_per_batch = batch_size // self.num_instances
# 用于存储该图像 序列号-身份ID 保存于字典,方便查找转换
self.index_pid = defaultdict(list)
# 用于存储该图像 身份ID-摄像头编号 保存于字典,方便查找转换
self.pid_cam = defaultdict(list)
# 用于存储该图像 身份ID-对应图片所有的序列号 保存于字典,方便查找转换
self.pid_index = defaultdict(list)
# 循环把数据保存于上述的三个字典之中
for index, info in enumerate(data_source):
pid = info[1]
camid = info[2]
self.index_pid[index] = pid
self.pid_cam[pid].append(camid)
self.pid_index[pid].append(index)
# 把pid_index的键值(身份ID)保存于self.pids之中
self.pids = list(self.pid_index.keys())
# 计算共多少个身份ID
self.num_identities = len(self.pids)
# 设置随机种子
if seed is None:
seed = comm.shared_random_seed()
self._seed = int(seed)
# 获得_rank,_world_size(主机数目)用于分布式训练
self._rank = comm.get_rank()
self._world_size = comm.get_world_size()
def __iter__(self):
start = self._rank
yield from itertools.islice(self._infinite_indices(), start, None, self._world_size)
def _infinite_indices(self):
"""
每次迭代,根据配置生成一个batch_size=B(论文中的)*(论文中的)个indx
"""
# 设定随机种子
np.random.seed(self._seed)
while True:
# 获得有效的身份ID,这里的self.pids已经进行过更新,包含多个数据集的ID
avai_pids = copy.deepcopy(self.pids)
# 保存一个batch的身份ID的索引
batch_idxs_dict = {}
# 保存一个batch的indx,该indx主要传给CommDataset的__getitem__函数
batch_indices = []
# 如果有效的ID数目大于self.num_pids_per_batch
while len(avai_pids) >= self.num_pids_per_batch:
# 随机从avai_pids中选择self.num_pids_per_batch个身份id
selected_pids = np.random.choice(avai_pids, self.num_pids_per_batch, replace=False)
# 循环对每个身份ID进行处理
for pid in selected_pids:
# Register pid in batch_idxs_dict if not,
# 如果pid这个ID在当前batch没有被采样过
if pid not in batch_idxs_dict:
# 获得pid这个身份ID对应所有图片的序列号
idxs = copy.deepcopy(self.pid_index[pid])
# 如果该身份ID图像的总数低于self.num_instances(论文中的K),则使用重复采样的方式
if len(idxs)
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?