目标跟踪《一》--- MDnet论文笔记

目标跟踪《一》--- MDnet论文笔记

目标跟踪背景及其重要性

目标跟踪是AI中很重要的一个理论结合应用的方向,比如导航定位,安全监控,人机交互,无人驾驶等等,其中导航定位很容易理解,比如无人机,当它知道了目标了之后就变成了一个跟踪问题;视频监控中主要是多目标跟踪,比如商场中,火车站等等,这些主要是在安防领域。

目标跟踪是利用已知的目标位置信息和目标连续在一段序列图像上找到感兴趣的运动目标,这个过程打个不太恰当的比方就像是电视里看到的给警犬闻一下罪犯的东西,然后它就可以去找一样.

困难,有多目标的跟踪和单目标的跟踪,单目标的不见得比多目标的容易,比如在一堆人中跟踪一个人,可能会出现遮挡等问题, 还比如在一群狗中,跟踪某一只狗,这些其实都挺困难的,因为这些特征其实都差不多的.

这个paper的主要内容

  • 网络结构

MDnet是用cnn来做目标跟踪的一个经典之作,网络结构比较简单,即三个卷积层后面接上两个全连接层,然后后面接上一个Domain-specific Layers,代码如下


self.layers = nn.Sequential(OrderedDict([
                ('conv1', nn.Sequential(nn.Conv2d(3, 96, kernel_size=7, stride=2),
                                        nn.ReLU(),
                                        LRN(),
                                        nn.MaxPool2d(kernel_size=3, stride=2))),
                ('conv2', nn.Sequential(nn.Conv2d(96, 256, kernel_size=5, stride=2),
                                        nn.ReLU(),
                                        LRN(),
                                        nn.MaxPool2d(kernel_size=3, stride=2))),
                ('conv3', nn.Sequential(nn.Conv2d(256, 512, kernel_size=3, stride=1),
                                        nn.ReLU())),
                ('fc4',   nn.Sequential(nn.Dropout(0.5),
                                        nn.Linear(512 * 3 * 3, 512),
                                        nn.ReLU())),
                ('fc5',   nn.Sequential(nn.Dropout(0.5),
                                        nn.Linear(512, 512),
                                        nn.ReLU()))]))

        self.branches = nn.ModuleList([nn.Sequential(nn.Dropout(0.5),
                                                     nn.Linear(512, 2)) for _ in range(K)])


前面的几层叫做shared layers因为在训练的时候,不同的视频用的都是这个网络.

  • gt

根据下载的VOT数据集来看,gt实际上就是bbox,而且每个物体对应的图都是一个视频中不同的桢,比如有300桢的话,就会有300张图,和300个gt的bbox的信息,利用这些数据就可以制作train set,具体的制作是这样的: 对于训练的每一个cycle(所有的视频跑一遍叫一个cycle),针对每一种物体,比如ball, 在所有的桢里面随机地选取8桢(如果视频的总桢数小于8的话,就以视频的总桢数来算),然后从这8桢中的每一桢里面选取16个bbox,其中4个是与gt的bbox的IOU比较大的,12个是与gt的bbox的IOU比较小的,其中大的要大于0.7,小的要小于0.5,这样4个IOU较大的就当作正样本,12个IOU小的就当成是负样本,这样在每个小batch中就有32个正样本,96个负样本,其中正样本和负样本的大小是107的正方形,网络的输出有两个,即

pos_score, neg_score, shape是(36,2),(96,2)因为是有target和background需要判断,所以输出的shape是2, 然后便用这两个去做BCE,具体是用正样本的第二列和负样本的第一列去做的,然后两个加起来就是最终的loss 函数。

  • 具体是是如何产生正的和负的bbox的?

这个大致的过程是在gt的附近以”某种方式”来产生一些bbox,然后与gt算IOU,之后根据IOU的规则决定哪些是正的,哪些是负的,然后从正的里面选出一些,负的里面选出一些.

其中 有个类是SampleGenerator, 初始化的时候需要传入的参数有type, img_size, trans_f, scale_f, aspect_f, valid, 分别代表产生sample的方式(见下面),图像的大小(原图的), 平移量系数,scale量系数,valid指的是

if self.valid:
    samples[:,:2] = np.clip(samples[:,:2], samples[:,2:]/2, self.img_size-samples[:,2:]/2-1)
else:
    samples[:,:2] = np.clip(samples[:,:2], 0, self.img_size)

这里valid都是True,也就是sample的中心的范围是有限制的,并不是在整个图上都行,而是根据w和h而定的。

然后这个类有个__call__函数,每次可以返回不同的函数对象进行调用,这个__call__函数需要传入两个参数bb, n,其中bb是target box, n是要产生的数量.具体是

n_pos, n_neg 4 12

但是具体产生的时候并不是一下子就产生那么多,代码里是这样的

def gen_samples(generator, bbox, n, overlap_range=None, scale_range=None):
    if overlap_range is None and scale_range is None:
        return generator(bbox, n)
    else:
        samples = None
        remain = n
        factor = 2
        while remain > 0 and factor < 16:
            # 用下面的generateor来产生samples
            samples_ = generator(bbox, remain*factor)  # 8, 24

            idx = np.ones(len(samples_), dtype=bool) # 初始化一个
            if overlap_range is not None:
                r = overlap_ratio(samples_, bbox) # 计算iou的.
                idx *= (r >= overlap_range[0]) * (r <= overlap_range[1])
                # 找到iou在这个range范围内的.
            if scale_range is not None:  # scale的range也不为空的话.
                s = np.prod(samples_[:,2:], axis=1) / np.prod(bbox[2:])
                idx *= (s >= scale_range[0]) * (s <= scale_range[1])

            samples_ = samples_[idx,:]  # 选出有效的那些.
            samples_ = samples_[:min(remain, len(samples_))]
            if samples is None:
                samples = samples_
            else:
                samples = np.concatenate([samples, samples_])
            remain = n - len(samples) # 如果第一次没有产生够
            factor = factor*2

        return samples

这里的n就是上面的4和12, 这里有个factor初始化为2,即刚开始实际上产生的是会加倍,但是因为这些产生的samples中的IOU并不一定是在这个范围内的,所以每一次产生的都会过滤掉那些不符合的,终止产生的条件有两个,一个是确实产生够数了,即remain<=0了,还有一种情况就是factor>=16了,但是感觉每次的samples可能为空啊。。。这时候应该 就对应着没有跟踪结果.

产生sample的方式有三种gaussian ,uniform, whole前两种分别是以高斯分布和均匀分布随机产生偏移系数和scale系数,而最后一种方式类似于Faster-rcnn中的anchor想法,是在整个图上产生samples,而前两种只是在target的附近产生的.

  • 有个问题是怎么保证每次都能产生给定数量的正的样本和负的样本?

如何进行推理的

现在觉得几乎所有的paper最难的地方就是如何推理的,因为其他的部分看了paper之后多多少少能够明白或者能够想通,但是推理部分不看具体的代码的话,很难想明白。

大致的过程是这样的:用每一个视频流的第一桢作为作为训练集训出一个回归器,就像RCNN系列中的差不多,只不过这里用的回归器的loss是Ridge回归,即l2加了正则之后的,然后从第二桢开始真正的跟踪推理,具体的过程是在target附近产生一些bbox(这个target刚开始是第一桢的gt,但是随着时间往后推移,这个target会更新,这个后面再细说)然后针对这些bbox,根据正的得分选出topk(代码里面的k=5),然后这5个bbox的位置要经过刚才的Ridge回归器进行一个较正,较正完了之后这5个位置作一个平均就作为当前的跟踪结果(如果没有跟踪结果怎么办?)

这整个过程涉及到几个细节问题,它们分别是:

  • Ridge回归部分具体是怎样的?

训练bbox regressor分为下面的几步

# Train bbox regressor
bbreg_examples = gen_samples(SampleGenerator('uniform', image.size, 0.3, 1.5, 1.1),
                             target_bbox, opts['n_bbreg'], opts['overlap_bbreg'], opts['scale_bbreg'])
# 然后从框中采样
bbreg_feats = forward_samples(model, image, bbreg_examples)
bbreg = BBRegressor(image.size)
bbreg.train(bbreg_feats, bbreg_examples, target_bbox)

其中bbreg_examples的shape是(927,4), 传入的参数是产生1000个opts['n_bbreg'] = 1000,估计就是上面的factor>=16了导致break了。 这时候IOU不是太苛刻opts['overlap_bbreg'] = [0.6, 1], 即大于0.6就认为是正例了.采完样之后的bbreg_featsshape是(927,4608),4608=512×3×3.512是通道数,3是大小. 然后 就是获得拟合器的对象bbreg,然后开始训练, 其中传入的参数bbreg_examples框的信息要和target_bbox的信息去做回归的目标(类似于faster-rcnn中的),代码如下

def get_examples(self, bbox, gt):
        bbox[:,:2] = bbox[:,:2] + bbox[:,2:]/2   # 变到中心
        gt[:,:2] = gt[:,:2] + gt[:,2:]/2   # gt的也变到中心
        dst_xy = (gt[:,:2] - bbox[:,:2]) / bbox[:,2:]
        dst_wh = np.log(gt[:,2:] / bbox[:,2:])
        Y = np.concatenate((dst_xy, dst_wh), axis=1)
        return Y

然后训练的代码是

def train(self, X, bbox, gt):
        X = X.cpu().numpy()
        bbox = np.copy(bbox)
        gt = np.copy(gt)

        if gt.ndim==1:
            gt = gt[None,:]

        r = overlap_ratio(bbox, gt)  # 算IOU
        s = np.prod(bbox[:,2:], axis=1) / np.prod(gt[0,2:])
        # 过滤出IOU在范围内的,且scale在范围内的index
        idx = (r >= self.overlap_range[0]) * (r <= self.overlap_range[1]) * \
              (s >= self.scale_range[0]) * (s <= self.scale_range[1])
        # 得到这些过滤出的特征信息
        X = X[idx]
        # 得到它们的bbox
        bbox = bbox[idx]

        # Y相当于是Faster--rcnn中的gt
        Y = self.get_examples(bbox, gt)

        # 进行拟合
        self.model.fit(X, Y) # 即作为一个拟合的问题


这样就训练了回归器,有一点要明确,这里选择的Ridge回归是采用的经典的回归的方法,是最小二乘的变型,对数据应该挺敏感的,所以针对不同的视频和目标需要重新训练,而faster-rcnn中的bbox回归是一种黑盒子的形式.(其实是fc层).要注意区别这两种的区别.

  • target是如何进行更新的?

  • 如果当前的这一桢没有跟踪到怎么办?

是时候看一下整个在线跟踪的算法流程图了

avator

从上图可以看出来前面的3层shared层训练完了之后就没有再改变过,但是4,5,6层的则在不断地更新中,特别是每一段视频进来的时候都会先更新一下4,5,6层的参数,然后才开始进入主loop, 还要不断地把之前的结果给存起来,即”long-term”和”short-term”的方式,如果数量超了的话,就把最初的给删了,具体4,5,6层的更新规则是如果跟踪成功了,就每隔10桢更新一次,如果没有跟踪成功就立即更新.

然后target的更新方式是

avator

上面是paper中写的,即在估计每一桢的时候,用上一次的target在其附近产生了N个candidates,然后从中选出得正分最高的那个,作为target状态的更新,但是代码里面并不是这样弄的,代码里面是取得正分最高的5个,然后对这5个取平均得到target状态的更新.

打赏,谢谢~~

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,多谢支持~

打开微信扫一扫,即可进行扫码打赏哦