ATOM について: オーバーラップの最大化による正確な追跡のパイプラインの理解

1. データの流れ

データのサンプリング方法は「/ltr/train_settings/bbreg/atom.py」にあり、dataset_train = sampler.ATOMSampler(*args)カプセル化されています。


dataset_train = sampler.ATOMSampler([lasot_train, got10k_train, trackingnet_train, coco_train],
                                   [1, 1, 1, 1],
                                   # 这里的samples_per_epoch=batch_size x n,如果batch是1,在训练中显示的就是
                                   # [train: num_epoch, x / batch_size * n] FPS: 0.3 (4.5)  ,  Loss/total: 43.69654  ,  Loss/segm: 43.69654  ,  Stats/acc: 0.56702
                                   # 由於batch_size * n構成了一個Epoch中所有的TensorDict形式的數據數量,通過LTRLoader包裝成batch結構後,就剩 "n" 個TensorDict,這裏就是1000個
                                   samples_per_epoch=1000*settings.batch_size,
                                   max_gap=50,
                                   processing=data_processing_train)

torch.utils.data.dataloader.DataLoaderクラスのメソッドを継承することでLTRLoader()、「/ltr/trainers/ltr_trainer.py」内のデータを走査するfor i, data in enumerate(loader, 1)ために使用されます。具体的な機能は、サンプリングされたデータを特定のルールに従ってパックし、[batch, n_frames, Channels, [H、W]形式のデータ

loader_train = LTRLoader('train',
                         dataset_train,
                         #
                         training=True,
                         batch_size=settings.batch_size,
                         # 数据读取线程数
                         num_workers=settings.num_workers,
                         # 在DDP模式下,没有这个参数
                         shuffle=True,
                         # 例如,99个数据,batch=30,最后会剩下9个数据,这时候就把这9个数据扔掉不用
                         drop_last=True,
                         # 在loader中,意思是:按batch_size抽取的数据,在第“1”维度上拼接起来,大概就是num_sequences
                         stack_dim=1)

トレーナーにカプセル化されたトレーナーの役割は、データをネットワーク モデルに配布し、ネットワークにフォワード プロセスを実行させ、損失を計算し、損失関数を通じてネットワークに電力を供給し、データを更新することです。

trainer = LTRTrainer(actor, [loader_train, loader_val], optimizer, settings, lr_scheduler)

データロードモジュールをカプセル化している理由は、[loader_train, loader_val]「/ltr/trainers/ltr_trainer.py」においてn Epochごとの検証を実装する必要があるためで、具体的な実装方法は以下の通りです。

# selfl.loaders就是[loader_train, loader_val]
for loader in self.loaders:
	if self.epoch % loader.epoch_interval == 0:
		# 这里就是利用 for i, data in enumerate(loader, 1)来把数据放入网络
		self.cycle_dataset(loader)

2. データのサンプリング方法

データ サンプリング関数はclass ATOMSamlper、 から継承された"/ltr/data/sampler.py" にあります。サンプリング関数は実際には でありその機能は親クラスのいくつかのパラメーターを初期化することであることがclass TrackingSamplerわかります。class TrackingSamplerclass ATOMSamlper

①データセットのランダム抽出

関数にはリストのセットがあり、これは4 4 + 3 + 2 + 1 \frac{4}{4+3+2+1}self.p_datasets = [4, 3, 2, 1]に従って 4 つのデータセットを意味します。4 + 3 + 2 + 143 4 + 3 + 2 + 1 \frac{3}{4+3+2+1}4 + 3 + 2 + 132 4 + 3 + 2 + 1 \frac{2}{4+3+2+1}4 + 3 + 2 + 121 4 + 3 + 2 + 1 \frac{1}{4+3+2+1}4 + 3 + 2 + 11特定のデータセットからデータを抽出する確率。

p_total = sum(p_datasets)
self.p_datasets = [x / p_total for x in p_datasets]
# 这里的self.datasets是在ltr/train_settings/bbreg/atom.py中封装的[lasot_train, got10k_train, trackingnet_train, coco_train]
# 这里的dataset返回的是ltr/dataset下面的各类函数,例如lasot.py中的class Lasot(BaseVideoDataset):
dataset = random.choices(self.datasets, self.p_datasets)[0]

②データセット内の動画シーケンスをランダムに抽出

まず、dataset.get_num_sequences()データ セットに含まれるビデオ シーケンスの数を取得し
、多くのビデオ シーケンスからビデオ シーケンスを抽出します (ビデオ シーケンスには多くの画像フレームが含まれます)。

seq_id = random.randint(0, dataset.get_num_sequences() - 1)

③ Got-10k データセットなどの特定のビデオシーケンスでのサンプリング

❶ インターバルサンプリング:

間隔

ビデオ シーケンスでは、
base_frame の前後の max_gap=50 の範囲内 (つまり± \pm)でピクチャがランダムにbase_frame として選択されます。± 50) train_frame と test_frame をそれぞれ抽出します。
そうでない場合は、max_gap + gap_increase で抽出範囲を拡張します。

なぜ±\pm以内なのか± 50という大きな範囲で描けないのですか

ターゲットを含むビデオ フレームを抽出する必要があるため、ビデオに目に見えるターゲットが含まれていない場合があり、このとき検索範囲を広げる必要があります。

❷ カジュアルサンプリング:

カジュアル

ビデオ シーケンスの中間点を参照フレームとして取得します。つまり、base_frame は、
base_frame の前後の max_gap=50 の範囲内にあります (つまり、± \pm)。± 50) train_frame と test_frame をそれぞれ抽出します。
そうでない場合は、max_gap + gap_increase で抽出範囲を拡張します。

❸ デフォルトのサンプリング:

参照フレームbase_frameはなく、
train_frameの前後max_gap=50の範囲内でビデオシーケンス内のtrain_frameを直接ランダムに抽出します(すなわち、±\pm)± 50) test_frame をランダムに選択;
train_frame と test_frame が繰り返される場合がある
描画されていない場合は、max_gap + gap_increase で抽出範囲を拡張します。

④ COCO データセットなどの非ビデオシーケンスでのサンプリング

train_frame_ids = [1] * self.num_train_frames
test_frame_ids = [1] * self.num_test_frames
直接抽出

3. サンプリング後のデータ処理方法

ハンドラー関数継承の基本クラス

class BaseProcessing:
    """
    处理类用于在传入网络之前,处理数据, 返回一个数据集
    例如,可以用于裁剪目标物体附近的搜索区域、用于不同的数据增强
    """
    def __init__(self, transform=transforms.ToTensor(), train_transform=None, test_transform=None, joint_transform=None):
        """
        参数:
            transform       : 用于图片的一系列变换操作
                              仅当train_transform或者test_transform是None的时候才用
            train_transform : 用于训练图片的一系列变换操作
                              如果为None, 取而代之的是'transform'值
            test_transform  : 用于测试图片的一系列变换操作
                              如果为None, 取而代之的是'transform'值
                              注意看,虽然在train_settings中设置的是transform_val,但是赋值的是transform_test=transform_val
                              所以,test_transform和transform_val是一回事
            joint_transform : 将'jointly'用于训练图片和测试图片的一系列变换操作
                              例如,可以转换测试和训练图片为灰度
        """
        self.transform = {
    
    'train': transform if train_transform is None else train_transform,
                          'test': transform if test_transform is None else test_transform,
                          'joint': joint_transform}

    def __call__(self, data: TensorDict):
        raise NotImplementedError

self.transform['joint']加工

つまり、最初はすべてのピクチャが ToTensor であり、ピクチャがグレースケール イメージに変換される確率はまだ 0.05 です。

transform_joint = tfm.Transform(tfm.ToGrayscale(probability=0.05))
# 这里的self.transform['joint']指向基类中的self.transform
data['train_images'], data['train_anno'] = self.transform['joint'](image=data['train_images'],
																   bbox=data['train_anno'])

self._get_jittered_boxボックスを妨害する

生成された bbox を外乱とともに使用すると_get_jittered_box、外乱は test_anno に対してのみ有効であり、train_anno は外乱を生成しません。外乱制御はself.scale_jitter_factorとによって実現されますself.center_jitter_factor。モードは制御フラグです。

self.scale_jitter_factor = {
    
    'train': 0, 'test': 0.5}
self.center_jitter_factor = {
    
    'train': 0, 'test': 4.5}

最終的な構成は次のとおりです。

    def _get_jittered_box(self, box, mode):
        """
        抖动一下输入box,box是相对坐标的(cx/sw, cy/sh, log(w), log(h))
        参数:
            box : 输入的bbox
            mode: 字符串'train'或者'test' 指的是训练或者测试数据
        返回值:
            torch.Tensor: jittered box
        """
        # randn(2) 生成两个服从(0,1)的数,范围是【-1,+1】前一个对应w,后一个对应h
        # 对于train,scale_jitter_factor=0,所以 jittered_size=box[2:4]
        jittered_size = box[2:4] * torch.exp(torch.randn(2) * self.scale_jitter_factor[mode])
        # 计算jitter_size后的x * y * w * h然后开方,乘以center_jitter_factor['train' or 'test'],作为最大偏移量
        # 对于train,center_jitter_factor=0,所以 max_offset=0
        max_offset = (jittered_size.prod().sqrt() * torch.tensor(self.center_jitter_factor[mode]).float())
        # 计算中心抖动 [x + w/2 + max_offset * (torch.randn(2)[0] - 0.5),  y + h/2 + max_offset * (torch.randn(2)[1] - 0.5)]
        jittered_center = box[0:2] + 0.5 * box[2:4] + max_offset * (torch.rand(2) - 0.5)
        return torch.cat((jittered_center - 0.5 * jittered_size, jittered_size), dim=0)

具体的な効果の説明:

test_anno=[x, y, w, h]長さと幅[ w , h ] [w, h][ w h ]ランダムにズームインまたはズームアウト[ 1 e , e ] [\frac{1}{\sqrt{e}}, \sqrt{e}][e 1e ]倍 (正規分布に従い、倍数の確率は 1 が最大です。e\sqrt{e}e または1 e \frac{1}{\sqrt{e}}を縮小しますe 1最も低い確率)、新しい長さと幅を取得します[ wjittered , hjittered ] [w_{jittered}, h_{jittered}][ wじっ_hじっ_]
 
test_anno=[x, y, w, h]中心点座標[ x + w 2 , y + h 2 ] [x+\frac{w}{2}, y+\frac{h}{2}][ ×+2y+2]随机偏移[ − 1 2 wjittered × hjittered × 4.5 , + 1 2 wjittered × hjittered × 4.5 ] [-\frac{1}{2}\sqrt{w_{jittered}\times h_{jittered}}\times 4.5 , +\frac{1}{2}\sqrt{w_{ジッタリング}\times h_{ジッタリング}}\times 4.5][ 21wじっ_×hじっ_ ×4.5 +21wじっ_×hじっ_ ×4.5 ] (正規分布に従い、オフセットの確率は 0 が最も高く、オフセットは1 2 wjittered × hjittered × 4.5 \frac{1}{2}\sqrt{w_{jittered}\times h_{jittered}} \4.5倍21wじっ_×hじっ_ ×4.5は確率が最も低く、[ xjittered , yjittered , wjittered , hjittered ] [x_{jittered}, y_{jittered}, w_{jittered}, h_{jittered}] になります
 
[ ×じっ_yじっ_wじっ_hじっ_

prutils.jittered_center_crop上記の加工結果に応じてカットします。

[ xjittered , yjittered , wjittered , hjittered ] [x_{jittered}, y_{jittered}, w_{jittered}, h_{jittered}] に従った入力画像[ ×じっ_yじっ_wじっ_hじっ_]、実ラベル、search_area_factor、output_size を使用して必要なサイズを切り出し、トリミングされた画像内の境界ボックスの対応する座標を取得します。

④またまたtransform

上記処理の結果を変換し、パラメータを「/ltr/train_settings/bbreg/atom.py」にカプセル化します。

transform_train = tfm.Transform(tfm.ToTensorAndJitter(0.2),
                                tfm.Normalize(mean=settings.normalize_mean,
                                                  std=settings.normalize_std))

tfm.ToTensorAndJitter(0.2)は正規分布に従う確率で、画像を[ 0.8 , 1.2 ] [0.8, 1.2]とします。[ 0.8 明るさを調整するには1.2 ]、つまり変化しない確率が最も大きく、× 0.8 \times 0.8× 0.8および× 1.2 \times 1.2× 1.2が最も低い確率です。

class ToTensorAndJitter(TransformBase):
    """
       继承了TransformBase,所有下面的transform_image和transform_mask会在TransformBase
       通过transform_func = getattr(self, 'transform_' + var_name),来调用具体用了哪个函数
       """
    def __init__(self, brightness_jitter=0.0, normalize=True):
        super().__init__()
        self.brightness_jitter = brightness_jitter
        self.normalize = normalize
    def roll(self):
        return np.random.uniform(max(0, 1 - self.brightness_jitter), 1 + self.brightness_jitter)
    def transform(self, img, brightness_factor):
        img = torch.from_numpy(img.transpose((2, 0, 1)))
        # 这里的brightness_factor是随机参数,其实就是roll的返回值
        return img.float().mul(brightness_factor / 255.0).clamp(0.0, 1.0)

については、tfm.Normalize(mean=settings.normalize_mean, std=settings.normalize_std)平均settings.normalize_mean = [0.485, 0.456, 0.406]と標準偏差を使用してsettings.normalize_std = [0.229, 0.224, 0.225]画像を正規化します

class Normalize(TransformBase):
    def __init__(self, mean, std, inplace=False):
        super().__init__()
        # settings.normalize_mean = [0.485, 0.456, 0.406]
        self.mean = mean
        # settings.normalize_std = [0.229, 0.224, 0.225]
        self.std = std
        # 计算得到的值不会覆盖之前的值
        self.inplace = inplace

    def transform_image(self, image):
        return tvisf.normalize(image, self.mean, self.std, self.inplace)

ノイズを加えるself._generate_proposals()場合に使用します。data['test_anno']

data['test_anno']① ② ③ ④ ⑤ の処理で生成されたbboxです

	self.proposal_params = {
    
    'min_iou': 0.1, 'boxes_per_frame': 16, 'sigma_factor': [0.01, 0.05, 0.1, 0.2, 0.3]}
	
    def _generate_proposals(self, box):
        """
        通过给输入的box添加噪音,生成proposal
        """
        # 生成proposal
        num_proposals = self.proposal_params['boxes_per_frame']
        # .get(key,'default')查找键值‘key’,如果不存在,则返回‘default’
        proposal_method = self.proposal_params.get('proposal_method', 'default')
        if proposal_method == 'default':
            proposals = torch.zeros((num_proposals, 4))
            gt_iou = torch.zeros(num_proposals)
            for i in range(num_proposals):
                proposals[i, :], gt_iou[i] = prutils.perturb_box(box,
                                                                 min_iou=self.proposal_params['min_iou'],
                                                                 sigma_factor=self.proposal_params['sigma_factor'])

        elif proposal_method == 'gmm':
            proposals, _, _ = prutils.sample_box_gmm(box,
                                                     self.proposal_params['proposal_sigma'],
                                                     num_samples=num_proposals)
            gt_iou = prutils.iou(box.view(1, 4), proposals.view(-1, 4))

        # map to [-1, 1]
        gt_iou = gt_iou * 2 - 1
        return proposals, gt_iou

❶ 最初の妨害方法:

計算[ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, hdata['test_anno] ][ ×中央_ _ _y中央_ _ _h ] 0.1 などの値をランダムにTensorを使用し平均値を[ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, h]
 
'sigma_factor': [0.01, 0.05, 0.1, 0.2, 0.3]perturb_factor=[0.1, 0.1, 0.1, 0.1]
 
random.gauss(bbox[0], perturb_factor[0])[ ×中央_ _ _y中央_ _ _h ]、標準偏差は [0.1, 0.1, 0.1, 0.1]、母語は[ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, h][ ×中央_ _ _y中央_ _ _h ]の確率が最も高く、摂動[ xperturbed , yperturbed , wperturbed , hperturbed ] [x_{perturbed}, y_{perturbed}, w_{perturbed}, h_{perturbed}][ ×ベッドごと_ _ _ _ _yベッドごと_ _ _ _ _wベッドごと_ _ _ _ _hベッドごと_ _ _ _ _]
 
計算[ xperturbed , yperturbed , wperturbed , hperturbed ] [x_{perturbed}, y_{perturbed}, w_{perturbed}, h_{perturbed}][ ×ベッドごと_ _ _ _ _yベッドごと_ _ _ _ _wベッドごと_ _ _ _ _hベッドごと_ _ _ _ _][ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, h][ ×中央_ _ _y中央_ _ _h ] IOU が
 
係数を乱すことになるperturb_factor *= 0.9
 

上記の処理を100回繰り返して結果を取得します。100回以内に結果が得られた場合はbox_iou > min_iou、セットを直接出力します。box_per, box_iou

上記の処理を 16 回実行して 16 個のグループを取得しますbox_per, box_iounum_proposals

❷ 2 番目の摂動法 (混合ガウス モデル):
複数のガウス関数を使用して確率分布を近似する混合ガウス モデル:
p GMM = Σ k = 1 K p ( k ) p ( x ∣ k ) = Σ k = 1 K α kp ( x ∣ μ k , Σ k ) p_{GMM} = \Sigma^{K}_{k=1}p(k)p(x|k) = \Sigma^{K}_{k= 1 }\alpha_k p(x|\mu_k, \Sigma_k)pGMM=Sk = 1Kp ( k ) p ( x k )=Sk = 1Kあるp ( x μS
その中で、KKKはモデルの数 (num_proposals=16 に相当)、つまり使用される単一ガウス分布の数です;α k \alpha_kあるk番目ですk個の単一ガウス分布の確率、Σ k = 1 K α k = 1 \Sigma^{K}_{k=1}\alpha_k = 1Sk = 1Kある=1 0 p( x ∣ μ k , Σ k ) p(x|\mu_k, \Sigma_k)p ( x μS)はkk 番目ですkの平均値はμ k \mu_kメートル、分散はΣ k \Sigma_kです。Sガウス分布の確率密度
コード実装:
分散Σ k \Sigma_kS

# proposal_sigma = [[a, b], [c, d]]
center_std = torch.Tensor([s[0] for s in proposal_sigma])
sz_std = torch.Tensor([s[1] for s in proposal_sigma])
# stack后维度[4,1,2]
std = torch.stack([center_std, center_std, sz_std, sz_std])
# 2
num_components = std.shape[-1]
# 4
num_dims = std.numel() // num_components
# (1,4,2)
std = std.view(1, num_dims, num_components)

機種数KKK

k = torch.randint(num_components, (num_samples,), dtype=torch.int64)
# 输出[16, 4], std=[1, 4, 2],由于这里k只有0和1,作用就是把最后一个维度复制成为16,索引方式就是index=0或1
std_samp = std[0, :, k].t()

GMM サンプリング後の Bbox の中心点座標 (ここに中心点座標の偏差があり、xi − x x_i - xに相当します)バツ私はx ) を計算し、xi x_iバツ私は

x_centered = std_samp * torch.randn(num_samples, num_dims)
# rel左上角和长宽的对数表示bbox
proposals_rel = x_centered + mean_box_rel

⑥ 合成出力

data['test_images']画像data['train_images'](単一シート)、バッチ サンプルは LTRLoader にパッケージ化され
data['test_anno']、self._generate_proposals を介して次のように変換されます:
    data['test_proposals']16 個
    data['proposal_iou']の摂動 bbox を含む 16 個の bbox を含むIoU
data['train_anno']実bbox、self._generate_proposals を渡さない bbox

4. ネットワークモデル

重要な点と難しい点:
train_inference_model
図の赤い点線のボックスの部分はモデルの学習部分、緑の実線のボックスはモデル推論時に共役勾配降下法によって生成されるフィルターのセットであり、全体像は以下を含む完全なモデル像です。訓練と推論。完全な推論部分は次のとおりです。
全体の推論

図には 5 つの反復が 2 つあり、分類の 5 つの反復はアルゴリズムの赤いワイヤーフレームに対応し、IoU_predict の対応するアルゴリズム図の緑色のワイヤーフレームに対応します。アルゴリズム図は次のとおりです。
アルゴリズム

おすすめ

転載: blog.csdn.net/Soonki/article/details/129407734