UE4产生的motion vector是后向运动矢量,具体来说,第 i i i帧的运动矢量 M V i MV_i MVi表示第 i i i帧(当前帧)到第 i − 1 i-1 i−1帧(前一帧)的运动关系,换句话说,对于像素 p p p, M V i [ p ] = π i → i − 1 ( p ) MV_i[p]=\pi_{i\rightarrow i-1}(p) MVi[p]=πi→i−1(p),其中 π i → i − 1 ( p ) \pi_{i\rightarrow i-1}(p) πi→i−1(p)表示像素 p p p从当前帧 i i i位置到前一帧 i − 1 i-1 i−1的运动变换值。
使用产生的后向运动矢量,分别给出帧
i
−
1
→
i
i-1\rightarrow i
i−1→i的图像扭曲(forwardWarping()
),与帧
i
→
i
−
1
i\rightarrow i-1
i→i−1的图像扭曲(backwardWarping()
),其中后者写法并不优美,进行了一定的舍入而造成像素错位。
还有就是numpy与UE4产生/处理图像时均以从左向右为x轴(图宽width);而原点方面,UE4在图像左下角,numpy在图像左上角,因此UE4以从下到上为y轴(图长height),而numpy则是从上到下,所以在计算时需要注意y坐标的正负转化。并且opencv读入图片是BGR通道,而motion vector文件中R通道存储x轴(宽width)上的偏移,G通道存储y轴(高height)上的偏移,故还要注意通道交换。
import cv2 as cv
import numpy as np
def forwardWarping(img,mv):#img[i-1],mv[i]->img[i]
h,w,c= img.shape
ch=np.arange(0,h*w).reshape(h,w)//w+mv[...,0]
cw=np.arange(0,h*w).reshape(h,w)%w-mv[...,1]
ch=ch.astype(np.float32)
cw=cw.astype(np.float32)
res=cv.remap(img,cw,ch,cv.INTER_LINEAR)
return res
def backWarping(img,mv,depth):#img[i],mv[i],depth[i]->img[i-1]
h,w,c=img.shape
res=np.zeros_like(img)
mv_oneaxis=mv.reshape(-1,2)
I=np.arange(0,h*w)//w
J=np.arange(0,h*w)%w
#很粗的写法,直接进行四舍五入,会造成微小的错位
II=(I+mv_oneaxis[:,0]+0.5).clip(0,h)
JJ=(J-mv_oneaxis[:,1]+0.5).clip(0,w)
II=II.astype(np.int32)
JJ=JJ.astype(np.int32)
id=np.argsort(-depth.reshape(-1))#考虑深度测试,当后一帧有多个位置映射到前一帧的相同位置时,选择最靠近相机(深度最小)的那个
res[II[id],JJ[id]]=img[I[id],J[id]]
return res
在UE中修改增加x,y均为正值的jitter offset时,渲染得到的画面会向左下角移动,此时jx,jy>0
而画面往左下角移动时,新画面(同时也是LR)的左上角原点实际在老画面(同时也是HR)坐标系的-x,+y处,
因此对老画面重建时,采样的坐标系是基于新画面的,故应该减去(+jx,-jy)