À propos de DIMP : Compréhension du pipeline d'apprentissage de la prédiction de modèles discriminants pour le suivi

Littérature DIMP Cliquez pour voir
Si vous ne voulez voir que les points clés, regardez simplement la dernière page en rouge

1. Flux de données

La méthode d'échantillonnage ici est presque exactement la même que celle d'ATOM, vous pouvez vous référer à ATOM

La méthode d'échantillonnage des données se trouve dans "/ltr/train_settings/bbreg/atom.py", par dataset_train = sampler.ATOMSampler(*args)encapsulation.

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)

En héritant de la méthode torch.utils.data.dataloader.DataLoaderde la classe LTRLoader(), for i, data in enumerate(loader, 1)elle est utilisée pour . La fonction spécifique est de compresser les données échantillonnées selon certaines règles et de sortir un [batch, n_frames, channels, H, W] format Les données

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)

Encapsulé dans l'entraîneur, le rôle de l'entraîneur est de distribuer les données au modèle de réseau, puis de laisser le réseau suivre le processus de transfert, de calculer la perte, d'habiliter le réseau via la fonction de perte et de mettre à jour les données.

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

La raison pour laquelle le module de chargement des données est encapsulé [loader_train, loader_val]est que dans "/ltr/trainers/ltr_trainer.py", il est nécessaire d'implémenter la validation toutes les n époques. La méthode d'implémentation spécifique est :

# 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. Méthode d'échantillonnage des données

Pour DiMP, il n'y a qu'un seul Template et Search num_train_frames=3, num_test_frames=3, for ATOM . La fonction d'échantillonnage de données est dans "/ltr/data/sampler.py" , qui est hérité de . On peut comprendre que la fonction d'échantillonnage est en fait , et sa fonction est d'initialiser certains paramètres pour sa classe parent.num_train_frames=1, num_test_frames=1,
class ATOMSamlperclass TrackingSamplerclass TrackingSamplerclass ATOMSamlper

①Extraction aléatoire d'ensembles de données

Il y a un ensemble de listes dans la fonction, self.p_datasets = [4, 3, 2, 1]ce qui signifie 4 ensembles de données, selon 4 4 + 3 + 2 + 1 \frac{4}{4+3+2+1}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 + 11La probabilité d'extraire des données d'un certain ensemble de données.

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]

②Extraction aléatoire de séquences vidéo dans un jeu de données

Tout d'abord, en dataset.get_num_sequences()obtenant le nombre de séquences vidéo dans l'ensemble de données
, puis en extrayant une séquence vidéo de plusieurs séquences vidéo (une séquence vidéo contient de nombreuses images)

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

③ Échantillonnage dans une certaine séquence vidéo, comme l'ensemble de données Got-10k

❶ échantillonnage par intervalles :

intervalle

Dans une séquence vidéo, une image est sélectionnée au hasard comme base_frame ;
dans la plage de max_gap=50 avant et après base_frame (c'est-à-dire ± \pm± 50) Extrayez respectivement une train_frame et une test_frame ;
sinon, alors max_gap + gap_increase, élargissez la plage à extraire ;

Pourquoi dans ± \pmVous ne pouvez pas dessiner dans une plage aussi large de ± 50 ?

Parce que l'image vidéo contenant la cible doit être extraite, parfois la vidéo ne contient pas de cible visible et la plage de recherche doit être augmentée à ce moment

❷ échantillonnage occasionnel :

occasionnel

Prenez le point médian de la séquence vidéo comme image de référence, c'est-à-dire que la base_frame
se situe dans la plage de max_gap=50 avant et après la base_frame (c'est-à-dire ± \pm± 50) Extrayez respectivement une train_frame et une test_frame ;
sinon, alors max_gap + gap_increase, élargissez la plage à extraire ;

❸ échantillonnage par défaut :

Il n'y a pas de cadre de référence base_frame, extrayez directement de manière aléatoire train_frame dans la séquence vidéo
dans la plage de max_gap=50 avant et après train_frame (c'est-à-dire ± \pm± 50) Sélectionnez au hasard un test_frame ;
train_frame et test_frame peuvent être répétés
S'ils ne sont pas dessinés, alors max_gap + gap_increase, élargissez la plage à extraire ;

④ Échantillonnage dans une séquence non vidéo, telle que l'ensemble de données COCO

train_frame_ids = [1] * self.num_train_frames
test_frame_ids = [1] * self.num_test_frames
extraction directe

3. Méthode de traitement des données après échantillonnage

Classe de base pour l'héritage de la fonction de gestionnaire

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']traitement

C'est-à-dire que toutes les images sont ToTensor en premier, et il y a toujours une probabilité de 0,05 pour transformer l'image en une image en niveaux de gris.

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_boxDéranger la bbox

En utilisant _get_jittered_boxla bbox générée avec perturbation, la perturbation n'est valable que pour test_anno, train_anno ne produira pas de perturbation. Le contrôle des perturbations est réalisé via self.scale_jitter_factoret self.center_jitter_factor, où mode est le drapeau de contrôle.

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

La composition finale est :

    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)

Description de l'effet spécifique :

test_anno=[x, y, w, h] longueur et largeur [ w , h ] [w, h][ w ,h ] Zoom avant ou arrière aléatoire[ 1 e , e ] [\frac{1}{\sqrt{e}}, \sqrt{e}][e 1,e ] fois (obéir à la distribution normale, la probabilité du multiple est 1 est la plus grande, agrandisseze \sqrt{e}e ou rétrécir 1 e \frac{1}{\sqrt{e}}e 1La probabilité la plus faible), obtenir la nouvelle longueur et largeur [ wjittered , hjittered ] [w_{jittered}, h_{jittered}][ wji tt ere d,hji tt ere d]
 
test_anno=[x, y, w, h]coordonnées du point central [ x + w 2 , y + h 2 ] [x+\frac{w}{2}, y+\frac{h}{2}][ x+2w,y+2h]随机偏移[ − 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_{jittered}\times h_{jittered}}\times 4.5][ -21wji tt ere d×hji tt ere d ×4.5 ,+21wji tt ere d×hji tt ere d ×4,5 ] (obéir à la distribution normale, la probabilité de décalage est de 0 est la plus élevée, le décalage est de1 2 wjittered × hjittered × 4,5 \frac{1}{2}\sqrt{w_{jittered}\times h_{jittered}} \fois 4,521wji tt ere d×hji tt ere d ×4.5 a la probabilité la plus faible)
 
et se termine par[ xjittered , yjittered , wjittered , hjittered ] [x_{jittered}, y_{jittered}, w_{jittered}, h_{jittered}][ xji tt ere d,yji tt ere d,wji tt ere d,hji tt ere d]

③Couper prutils.jittered_center_cropselon les résultats du traitement ci-dessus

L'image d'entrée selon [ xjittered , yjittered , wjittered , hjittered ] [x_{jittered}, y_{jittered}, w_{jittered}, h_{jittered}][ xji tt ere d,yji tt ere d,wji tt ere d,hji tt ere d] , étiquette réelle, search_area_factor et output_size pour découper la taille requise et obtenir les coordonnées correspondantes du Boundingbox dans l'image recadrée.

④ encoretransform

Transformez le résultat du processus ci-dessus et encapsulez les paramètres dans "/ltr/train_settings/bbreg/atom.py".

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

A propos de tfm.ToTensorAndJitter(0.2), est la probabilité d'obéir à la distribution normale, soit l'image dans [ 0.8 , 1.2 ] [0.8, 1.2][ 0,8 ,1,2 ] pour ajuster la luminosité, c'est-à-dire que la probabilité d'absence de changement est la plus grande,× 0,8 \times 0,8× 0,8 et× 1,2 \fois 1,2× 1,2 a la probabilité la plus faible.

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)

À propos de , consiste à normaliser l'image tfm.Normalize(mean=settings.normalize_mean, std=settings.normalize_std)à l'aide de la moyenne settings.normalize_mean = [0.485, 0.456, 0.406]et de l'écart typesettings.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)

⑤ Utiliser self._generate_proposals()pour data['test_anno']ajouter du bruit

data['test_anno']C'est la bbox générée selon le processus de ① ② ③ ④ ⑤

	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

❶ La première méthode de perturbation :

calculé [ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, hdata['test_anno] ][ xcentre _ _ _,ycentre _ _ _,w ,h ] Extraire au hasardutiliserTensorpour calculer la valeur moyenne comme[ 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])[ xcentre _ _ _,ycentre _ _ _,w ,h ] , l'écart type est [0.1, 0.1, 0.1, 0.1], la langue vernaculaire est[ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, h][ xcentre _ _ _,ycentre _ _ _,w ,h ] a la probabilité la plus élevée, et le perturbé[ xperturbé , yperturbé , wperturbé , hperturbé ] [x_{perturbé}, y_{perturbé}, w_{perturbé}, h_{perturbé}][ xp er t u r lit _ _,yp er t u r lit _ _,wp er t u r lit _ _,hp er t u r lit _ _]
 
计算[ xperturbé , yperturbé , wperturbé , hperturbé ] [x_{perturbé}, y_{perturbé}, w_{perturbé}, h_{perturbé}][ xp er t u r lit _ _,yp er t u r lit _ _,wp er t u r lit _ _,hp er t u r lit _ _][ xcenter , ycenter , w , h ] [x_{center}, y_{center}, w, h][ xcentre _ _ _,ycentre _ _ _,w ,h ] l'IOU
 
va perturber le coefficientperturb_factor *= 0.9
 

Cyclez le processus ci-dessus 100 fois pour obtenir le résultat, si le résultat est obtenu dans les 100 fois box_iou > min_iou, sortez directement un ensemblebox_per, box_iou

Effectuez le processus ci-dessus 16 fois pour obtenir 16 groupes box_per, box_iou, c'est-à-direnum_proposals

❷ La deuxième méthode de perturbation (modèle de mélange gaussien) :
modèle de mélange gaussien, qui utilise plusieurs fonctions gaussiennes pour approximer la distribution de probabilité :
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 = 1Kunkp ( x μk,Sk)
parmi eux,KKK est le nombre de modèles (équivalent à num_proposals=16), c'est-à-dire le nombre de distributions gaussiennes simples utilisées ;α k \alpha_kunkest le kkthLa probabilité de k distribution gaussienne simple,Σ k = 1 K α k = 1 \Sigma^{K}_{k=1}\alpha_k = 1Sk = 1Kunk=1 0 p( X ∣ μ k , Σ k ) p(x|\mu_k, \Sigma_k)p ( x μk,Sk) est lekkthLa valeur moyenne de k est μ k \mu_kmk, la variance est Σ k \Sigma_kSkDensité de probabilité de distribution gaussienne
Implémentation du code :
variance Σ k \Sigma_kSk

# 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)

Nombre de modèles 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()

Les coordonnées du point central de la Bbox après échantillonnage GMM (voici l'écart des coordonnées du point central, qui équivaut à xi − x x_i - xXjex ), puis calculezxi x_iXje

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

⑥ Division d'étiquette gaussienne ( il n'y a pas une telle opération dans ATOM )

data["train_anno"]Il est utilisé pour la partie formation du réseau, data["test_anno"]mais pour predcalculer la perte avec la sortie du réseau, c'est-à-dire que l'étiquette gaussienne est utilisée comme Ground_Truth.

def gauss_1d(sz, sigma, center, end_pad=0, density=False):
    # K 决定了输出的尺寸,理解为meshgrid更合适
    k = torch.arange(-(sz - 1) / 2, (sz + 1) / 2 + end_pad).reshape(1, -1).cuda() - center.reshape(1, -1)
    gauss = torch.exp(-1.0 / (2 * sigma ** 2) * (k) ** 2)
    if density:
        gauss /= math.sqrt(2 * math.pi) * sigma
    return gauss


def gauss_2d(sz, sigma, center, end_pad=(0, 0), density=False):
    return gauss_1d(sz[0].item(), sigma[0], center[:, 0], end_pad[0], density).reshape(center.shape[0], 1, -1) * gauss_1d(sz[1].item(), sigma[1], center[:, 1], end_pad[1], density).reshape(center.shape[0], -1, 1)


def gaussian_label_function(target_bb, end_pad_if_even=False, density=False, uni_bias=0):
    # 为了直观,这里设置一下参数,作为展示
    uni_bias = torch.Tensor([uni_bias]).cuda()
    sigma_factor = torch.Tensor([0.25, 0.25]).cuda()
    feat_sz = torch.Tensor([18, 18]).cuda()
    image_sz = torch.Tensor([511, 511]).cuda()
    #target_bb = target_bb.view(-1, *target_bb.shape)

    # target_bb 包含了3个bbox, 也就是num_train_frames或者num_test_frames
    # target_bb=[3, 4]    target_center=[3, 2]
    # 注意,这里的target_bb是image_crop下的坐标,需要通过norm转换到feat_sz=[18, 18下面
    target_center = target_bb[:, 0:2] + 0.5 * target_bb[:, 2:4]
    target_center_norm = (target_center - image_sz / 2) / image_sz
    center = feat_sz * target_center_norm+ 0.5 * torch.Tensor([(kernel_sz[0] + 1) % 2, (kernel_sz[1] + 1) % 2])
    sigma = sigma_factor * feat_sz.prod().sqrt().item()

    if end_pad_if_even:
        end_pad = (1, 1)
    else:
        end_pad = (0, 0)
    gauss_label = gauss_2d(feat_sz, sigma, center, end_pad, density=density).float()
    if density:
        sz = (feat_sz + torch.Tensor(end_pad)).prod()
        label = (1.0 - uni_bias) * gauss_label + uni_bias / sz
    else:
        label = gauss_label + uni_bias

    return label

L'effet est comme indiqué sur la figureétiquette_gaussienne

⑦ Sortie combinée

data['test_images']Et data['train_images']les images (feuilles simples), les échantillons Batch seront empaquetés dans LTRLoader
data['test_anno']puis passés par self._generate_proposals, transformés en : l' IoU
    data['test_proposals']contenant 8 bboxes
    data['proposal_iou']dont 8 bboxes perturbées
data['train_anno']passées par ⑥ self._generate_label_function() bbox, envoyées au réseau
data['test_anno']par ⑥La bbox après self._generate_label_function() est utilisée pour calculer la perte

4. Modèle de réseau

Dimp_modelLa structure du modèle dans le document comprend deux parties, Train et Inference, et est incomplète. La partie IoUnet d'ATOM n'est pas affichée. L'épine dorsale est toujours constituée des deux parties de la couche de sortie de la structure ATOM2 et de la couche3 (indiquées dans la zone de ligne rouge de la figure). Le modèle complet se trouve dans la zone de ligne verte de la figure. Le modèle a deux sorties, l'une est Iou_pred et l'autre est le score_pred du filtre.
Alors à quoi servent ces deux sorties ?

Rappelons d'abord le classificateur dans ATOM :
f ( x ; w ) = ϕ 2 w 2 × ϕ 1 ( w 1 × x ) f(x;w) = \phi_{2}{w_{2} \times \phi_ {1}{(w_1 \fois x)}}f ( x ;w )=ϕ2w2×ϕ1( w1×x )
En fait, deux ensembles de filtres sont entraînés, mais la position d'entraînement est dans Inférence, utilisez les filtres pour déléguer l'exploit pour obtenir score_raw, et utilisez ce score pour trouver les informations de position.
Alors que dans DIMP, la procéduref ( x ; w ) f(x; w)f ( x ;w ) est mis dans l'étape d'apprentissage. Pour le dire franchement, c'est le même modèle de classificateur, il suffit de changer la position du module...
Le filtre dans DiMP est théoriquement toujoursf ( x ; w ) f(x;w)f ( x ;w ) , dans chaque Epoch, itérer 5 fois pour trouver un ensemble de filtres.
Dans l'inférence, cet ensemble de filtres roule feat pour devenir similaire à Score_raw dans ATOM, et la sortie du classificateur est la même, il suffit de la remplacer une par un.

    def track(self, image, info: dict=None) -> dict:
        self.debug_info = {
    
    }
        self.frame_num += 1
        self.debug_info['frame_num'] = self.frame_num

        # 图片从numpy格式转换成tensor
        im = numpy_to_torch(image)

        # 定位

        # 提取backbone特征,调用dimpnet中的extract_backbone_features返回layer2和layer3的featuremap
        backbone_feat, sample_coords, im_patches = self.extract_backbone_features(im,
                                                                                  self.get_centered_sample_pos(),
                                                                                  self.target_scale * self.params.scale_factors,
                                                                                  self.img_sample_sz)
        # 提取分类特征,调用dimpnet中的extract_classification_feat,返回的是经过Bottlenet的layer2和layer3的featuremap
        test_x = self.get_classification_features(backbone_feat)
        
        # 样本的定位, 就是featuremap中bbox的坐标
        sample_pos, sample_scales = self.get_sample_location(sample_coords)

        # 计算分类得分,不同于ATOM的Score_raw是在Inference中训出来的w1和w2计算出来的
        # 这里是直接训练出来了一组filters,然后拿来直接用了
        scores_raw = self.classify_target(test_x)
        # 这里是ATOM的做法:
        # scores_raw = self.apply_filter(test_x)
        # self.apply_filter = operation.conv2d(sample_x, self.filter, mode='same')

        # 定位目标
        translation_vec, scale_ind, s, flag = self.localize_target(scores_raw, sample_pos, sample_scales)
        new_pos = sample_pos[scale_ind, :] + translation_vec

        # 更新位置和缩放因子
        if flag != 'not_found':
            if self.params.get('use_iou_net', True):
                update_scale_flag = self.params.get('update_scale_when_uncertain', True) or flag != 'uncertain'
                if self.params.get('use_classifier', True):
                    self.update_state(new_pos)
                # ATOM中是用W1和W2计算的pos来迭代5次IoUnet,得到精确到bbox坐标
                # DIMP中是用模型训练的filters计算的pos,在IoUNet中迭代5次,得到精确到bbox坐标
                self.refine_target_box(backbone_feat, sample_pos[scale_ind, :], sample_scales[scale_ind], scale_ind, update_scale_flag)
            elif self.params.get('use_classifier', True):
                self.update_state(new_pos, sample_scales[scale_ind])

        # 更新
        update_flag = flag not in ['not_found', 'uncertain']
        hard_negative = (flag == 'hard_negative')
        learning_rate = self.params.get('hard_negative_learning_rate', None) if hard_negative else None

        if update_flag and self.params.get('update_classifier', False):
            # 获得训练样本
            train_x = test_x[scale_ind:scale_ind+1, ...]
            # 创建target_box和空间样本的标签
            target_box = self.get_iounet_box(self.pos, self.target_sz, sample_pos[scale_ind, :], sample_scales[scale_ind])
            # 更新分类模型
            """
            利用DiMP网络,把卷图片用的weights[...]算出来
                其中还有hard_negative:将难样本加入初始化的负例样本中进行训练
                    最后把得到的self.target_filter放入def classify_target中,
                        就是把 self.target_filter和图片卷一下,得到scores
            """
            self.update_classifier(train_x, target_box, learning_rate, s[scale_ind, ...])

        # 设置追踪器位置到IoUNet的位置
        if self.params.get('use_iou_net', True) and flag != 'not_found' and hasattr(self, 'pos_iounet'):
            self.pos = self.pos_iounet.clone()

        score_map = s[scale_ind, ...]
        max_score = torch.max(score_map).item()

        # 可视化和设置显示的debug标志
        self.search_area_box = torch.cat((sample_coords[scale_ind, [1, 0]], sample_coords[scale_ind, [3, 2]] - sample_coords[scale_ind, [1, 0]] - 1))
        self.debug_info['flag' + self.id_str] = flag
        self.debug_info['max_score' + self.id_str] = max_score
        if self.visdom is not None:
            self.visdom.register(score_map, 'heatmap', 2, 'Score Map' + self.id_str)
            self.visdom.register(self.debug_info, 'info_dict', 1, 'Status')
        elif self.params.debug >= 2:
            show_tensor(score_map, 5, title='Max Score = {:.2f}'.format(max_score))

        # 计算输出的BoundingBox,换句话说,就是下一帧track的pos
        new_state = torch.cat((self.pos[[1, 0]] - (self.target_sz[[1, 0]] - 1) / 2, self.target_sz[[1, 0]]))

        if self.params.get('output_not_found_box', False) and flag == 'not_found':
            output_state = [-1, -1, -1, -1]
        else:
            output_state = new_state.tolist()

        out = {
    
    'target_bbox': output_state}
        return out

Fonction de perte et méthode de mise à jour du filtre

  • 损失函数
    L ( f ) = 1 S train Σ ( X , c ) ∈ S train ( ∣ ∣ r ( X × f , c ) ∣ ∣ 2 + ∣ ∣ λ f ∣ ∣ 2 ) L(f) = \frac{ 1}{S_{train}} \underset {(x,c) \in S_{train}}{\Sigma} (|| r(x \times f, c) ||^2 + || \lambda f| |^2)L ( f )=St r ain1( X , c ) St r ainS( ∣∣ r ( X×f ,c ) 2+∣∣ λ f 2 )
    r ( x × f , c ) r(x \fois f, c)r ( x×f ,c ) est le résidu, la méthode de calcul spécifique est :
    r ( x × f , c ) = ν c ⋅ [ mc ⋅ x × f + ( 1 − mc ) max ( 0 , x × f ) − c ] r(x \ fois f, c) = \nu _c \cdot [m_c \cdot x \times f + (1 - m_c)max(0, x \times f) - c]r ( x×f ,c )=nc[ mcX×F+( 1mc) max ( 0 , _X×f )c ]
    ν c \nu _cncest le poids spatial, mc m_cmcC'est un masque, tout basé sur distmap dist_{map}d je s tma pLe résultat de la convolution.
    • distmap dist_{carte}d je s tma pLa méthode de calcul est obtenue en modifiant la distance spatiale euclidienne dist par le biais d'un changement de fonction, et dist utilise le centre de coordonnées de BoundingBox comme origine des coordonnées et la taille de Featuremap comme plage de coordonnées, comme indiqué sur la figure : la transformation fonction est : yc ( t ) =
      insérez la description de l'image iciΣ
      k = 0 N − 1 ϕ ky ρ k ( ∣ ∣ t − c ∣ ∣ ) y_c (t) = \Sigma^{N-1}_{k=0} \phi^ y_k \rho_k (|| t - c| |)yc( t )=Sk = 0N - 1ϕkyrk( ∣∣ tc ∣∣ )
      où fonctionρ k ( ∣ ∣ t − c ∣ ∣ ) \rho_k(|| t - c ||)rk( ∣∣ tc ∣∣ ) est : (t − c tctc aussi libre dist)
      ρ k ( ré ) = { max ( 0 , 1 - ∣ ré - k △ ∣ △ ) k < N - 1 max ( 0 , min ( 1 , 1 + ré - k △ △ ) ) k = N − 1 \rho_k(d) = \begin {cas} max(0, 1 - \frac{|d - k \triangle|}{\triangle}) & k<N-1 \\ max(0, min ( 1, 1+\frac{d - k\triangle}{\triangle})) & k=N-1\end{cas}rk( d )={ max ( 0 , _1k )max ( 0 , _mini ( 1 ,1+d k) )k<N1k=N1

    • ν c \nu _{c}ncMéthode de calcul, distmap dist_{map}d je s tma pAlimentez la couche convolutive :

    num_bin_dist = 100
    self.spatial_weight_predictor = nn.Conv2d(num_dist_bins, 1, kernel_size=1, bias=False) 
    spatial_weight = self.spatial_weight_predictor(dist_map)
    
    • mc m_cmcMéthode de calcul de distmap dist_{map}d je s tma pAlimentez la couche convolutive :
    num_bin_dist = 100
    mask_layers = [nn.Conv2d(num_dist_bins, 1, kernel_size=1, bias=False)]
    if mask_act == 'sigmoid':
    	mask_layers.append(nn.Sigmoid())
    	init_bias = 0.0
    elif mask_act == 'linear':
    	init_bias = 0.5
    else:
    	raise ValueError("激活函数未知")
    # 预测的就是损失中的 mc 被牵制在了[0, 1],还要经过sigmoid
    self.target_mask_predictor = nn.Sequential(*mask_layers)
    target_mask = self.target_mask_predictor(dist_map)
    
  • La descente du gradient et l'étape de mise à jour
    seront L (f) L(f)L ( f )进行泰勒展开:
    L ( f ) ≈ L ~ ( f ) = 1 2 ( f - f ( je ) ) TQ ( je ) ( f - f ( je ) ) + ( f - f ( je ) ) T ∇ L ( F ( je ) ) + L ( F ( je ) ) L(f) \approx \tilde{L}(f) = \frac{1}{2}(f - f^{(i)} )^TQ^{(i)}(f - f^{(i)}) + (f - f^{(i)})^T \nabla L(f^{(i)}) + L(f ^{(je)})L ( f )L~ (f)=21( fF( je ) )T Q( je ) (fF( je ) )+( fF( je ) )TL(f( je ) )+L ( f( i ) )
    taille du pasα \alphaα为:
    α = ∇ L ( F ( je ) ) T ∇ L ( F ( je ) ) ∇ L ( F ( je ) ) TQ ( je ) ∇ L ( F ( je ) ) \alpha = \frac{\nabla L(f^{(i)})^T \nabla L(f^{(i)})}{\nabla L(f^{(i)})^TQ^{(i)} \nabla L( f^{(i)})}un=L ( f( je ) )T Q( je )L(f( je ) )L ( f( je ) )TL(f( je ) )
    更新方式:
    f ( je + 1 ) = f ( je ) − α ∇ L ( f ( je ) ) f^(i+1) = f^{(i)} - \alpha \nabla L(f^{( je)})F( je+1 )=F( je )α L ( f( je ) )

Je suppose que tu aimes

Origine blog.csdn.net/Soonki/article/details/129478349
conseillé
Classement