深度光流估计

光流估计

概念

1、光流描述了第一帧到第二帧所有的像素点移动情况。假设一个像素点,在第一帧的坐标是(x1, y1),在第二帧的坐标是(x2, y2),则该点的光流设为(△x, △y),满足如下公式:
x2 = x1 + △x
y2 = y1 + △y

2、光流法也分为dense(右)和sparse(左)两个方向,简单来说sparse方法预测关键点的光流,dense方法预测全图像素的光流。
左sparse右dense

原理

光流的约束公式
参考:https://aistudio.baidu.com/projectdetail/4597614

参考:https://aistudio.baidu.com/projectdetail/4597614

说实话这个公式粗略了解下来真的很像雅可比矩阵。
f ( x + h ) = h ∗ J f ( x ) + f ( x ) f(x+h) = h * J_{f}(x) + f(x) f(x+h)=hJf(x)+f(x)
雅可比矩阵的物理意义表示多元向量的改变量(坐标warp的方向和大小,是不是可以这么理解),也就是DaGan以及Fomm中使用雅可比矩阵warp关键点得到目标图像的原理。而光流估计是来估计像素值的warp方向以及大小

算法

FlowNet

开山之作

RAFT

data部分

dataloader抛出:两张邻帧输入,光流真值flow gt,有效像素valid mask
spatial transform:针对dense方法的变换,主要是resize flow gt的方法

    def resize_sparse_flow_map(self, flow, valid, fx=1.0, fy=1.0):
        ht, wd = flow.shape[:2]
        ### 生成输入图片的每个像素的坐标点
        coords = np.meshgrid(np.arange(wd), np.arange(ht))
        coords = np.stack(coords, axis=-1)
		
		### 把(H, W, 2) ->(H * W, 2)
        coords = coords.reshape(-1, 2).astype(np.float32)
        flow = flow.reshape(-1, 2).astype(np.float32)
        valid = valid.reshape(-1).astype(np.float32)
		
		### 筛选掉无效坐标
        coords0 = coords[valid>=1]
        flow0 = flow[valid>=1]

        ht1 = int(round(ht * fy))
        wd1 = int(round(wd * fx))
		
		### 坐标点直接 * scale_factor
        coords1 = coords0 * [fx, fy]
        flow1 = flow0 * [fx, fy]

        xx = np.round(coords1[:,0]).astype(np.int32)
        yy = np.round(coords1[:,1]).astype(np.int32)
		
		### 过滤越界点
        v = (xx > 0) & (xx < wd1) & (yy > 0) & (yy < ht1)
        xx = xx[v]
        yy = yy[v]
        flow1 = flow1[v]
		
		### 把变换后的flow gt根据坐标点填到全0 flow图中
        flow_img = np.zeros([ht1, wd1, 2], dtype=np.float32)
        valid_img = np.zeros([ht1, wd1], dtype=np.int32)

        flow_img[yy, xx] = flow1
        valid_img[yy, xx] = 1

        return flow_img, valid_img

涉及到坐标变换的:flow gt其实可以理解为向量,上一帧该像素坐标指向下一帧该像素坐标的向量,所以坐标变换可以直接作用于flow gt。同理的翻转和裁剪都可以直接操作,只要保持输入图片对和flow gt一一对应
仅光照颜色变换等:源码中是把两张图纵向拼接2 * (H,W,3)->(2H,W,3),统一做完变换再拆开即可。也就是需要保持邻帧图像对应的像素保持变化一致。

    def spatial_transform(self, img1, img2, flow, valid):
        # randomly sample scale

        ht, wd = img1.shape[:2]
        min_scale = np.maximum(
            (self.crop_size[0] + 1) / float(ht), 
            (self.crop_size[1] + 1) / float(wd))

        scale = 2 ** np.random.uniform(self.min_scale, self.max_scale)
        scale_x = np.clip(scale, min_scale, None)
        scale_y = np.clip(scale, min_scale, None)

        if np.random.rand() < self.spatial_aug_prob:
            # rescale the images
            img1 = cv2.resize(img1, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR)
            img2 = cv2.resize(img2, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR)
            flow, valid = self.resize_sparse_flow_map(flow, valid, fx=scale_x, fy=scale_y)

        if self.do_flip:
            if np.random.rand() < 0.5: # h-flip
                img1 = img1[:, ::-1]
                img2 = img2[:, ::-1]
                flow = flow[:, ::-1] * [-1.0, 1.0]
                valid = valid[:, ::-1]

        margin_y = 20
        margin_x = 50

        y0 = np.random.randint(0, img1.shape[0] - self.crop_size[0] + margin_y)
        x0 = np.random.randint(-margin_x, img1.shape[1] - self.crop_size[1] + margin_x)

        y0 = np.clip(y0, 0, img1.shape[0] - self.crop_size[0])
        x0 = np.clip(x0, 0, img1.shape[1] - self.crop_size[1])

        img1 = img1[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]]
        img2 = img2[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]]
        flow = flow[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]]
        valid = valid[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]]
        return img1, img2, flow, valid

消除变化(eraser_transform):不太理解,源码里会单独对另一帧的图像做消除,但是并不会修改valid mask的内容,也就是需要模型自己做补全。

    def eraser_transform(self, img1, img2):
        ht, wd = img1.shape[:2]
        if np.random.rand() < self.eraser_aug_prob:
            mean_color = np.mean(img2.reshape(-1, 3), axis=0)
            for _ in range(np.random.randint(1, 3)):
                x0 = np.random.randint(0, wd)
                y0 = np.random.randint(0, ht)
                dx = np.random.randint(50, 100)
                dy = np.random.randint(50, 100)
                img2[y0:y0+dy, x0:x0+dx, :] = mean_color

        return img1, img2
model

1、特征提取部分
先CNN网络提特征,然后做矩阵相乘(相似度计算)
PS:做完矩阵相乘一样除以 sqrt(dim),和transformer一样防止尺度变大。
2、后续部分用的GRU门控单元,挺像LSTM的结构的,只是对内部的门控做了一些改动。
PS:
tanh和sigmoid的运用,tanh和sigmoid分别可以把输入非线性映射到[-1, 1], [0, 1],sigmoid映射到[0, 1]的特性作为门控开关,tanh映射到[-1, 1]的特性作为内部状态位。
为什么要这么映射?
(1)非线性激活函数强化学习能力
(2)因为是类似RNN、LSTM的循环结构,需要参数映射到x<|1|保证叠乘不会爆炸,解决梯度爆炸梯度消失问题
https://zhuanlan.zhihu.com/p/350613342
3、上采样部分
上采样中主要解释下shape代表的意义

    def upsample_flow(self, flow, mask):
        """ Upsample flow field [H/8, W/8, 2] -> [H, W, 2] using convex combination """
        ''' 
        flow->(b, 2, H/8, W/8)
        mask->(b, 576, H/8, W/8) 576 = 8*8*9 (1/8下采样,每个坐标xy各9个偏移量[-4, 4])
        '''
        N, _, H, W = flow.shape
        mask = mask.view(N, 1, 9, 8, 8, H, W)
        ''' 对dim 2做softmax,即对当前坐标的9个偏移量加权(下面mask会乘进flow里)'''
        mask = torch.softmax(mask, dim=2)

        up_flow = F.unfold(8 * flow, [3,3], padding=1)
        up_flow = up_flow.view(N, 2, 9, 1, 1, H, W)

        up_flow = torch.sum(mask * up_flow, dim=2)
        up_flow = up_flow.permute(0, 1, 4, 2, 5, 3)
        return up_flow.reshape(N, 2, 8*H, 8*W)

评价以及可视化

光流可视化
光流可视化的图片或者视频中出现的颜色代表对应的变化方向以及幅度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值