Eyediap Offical Code

headpose.txt 中头部的旋转参数和平移参数获取

使用[2012CVPRW]Gaze_estimation_from_multimodal_Kinect_data中的迭代最近邻方法ICP方法获得头部相对于相机坐标系的平移和旋转矩阵。

image-20220127120732209

image-20220129161714403

/data/EYEDIAP/EYEDIAP/Metadata/Participants/eyes_position_1.txt

image-20220127163338829

-0.03319 0.03518 0.08539
0.03198 0.03458 0.08539

第一个数值应该是人眼横向位置坐标,两个正负值之差大小符合平均瞳间距。

女生常见瞳距是58-66,男生常见瞳距是60-72。

代码中的标注说明是在头部坐标系中的眼睛中心位置# The estimated eyeball centers with respect to the head coordinate system?

头部姿势是变化的,每一个变化会得到一个R、T,使用中立位的eye center 进行相应的转化。

问题:头部坐标系中眼睛eyes_position的y、z值是怎么获得的呢?

image-20220129163911993

根据三停五眼大概猜测第二个值是鼻尖到瞳孔的高度。

image-20220129164643571

第三个值可能是人眼到人头中心的距离。

头部坐标系的原点可能建模在头部的中心位置。

查看头部坐标系,z指向

其实view中给出了右手系坐标,可以判断x,y,z。

在view 程序中找到这张图片,看眼睛所盯方向也可。

Eyediap 数据集中compute_etra_results.py程序的逻辑

1.获取用于评估算法性能的数据,使用头部姿势有效且目标有效且位于视频后半段的数据。

头部姿势是否有效根据headpose.txt中记录的头部的平移参数是不是全部为0来判断。目标是否有效根据ball_tracking中记录的小球在空间中的位置是否全部为0来判断。全部为0说明没有拍到目标。视频的前半段用于[2012CVPRW]Gaze from Multimodal Kinect Data中视线估计建模的训练,后半段没有用于训练的数据作为测试集。

2.对于头部姿势有效且目标有效的数据会把小球在空间中的位置转换到头部坐标系,并且在头部坐标系中用小球坐标减去眼球中心坐标获取注视方向。判断注视方向的pitch俯仰角和yaw航偏角是否在设定的评估范围内。对于在测试集且注视方向在范围内的数据进行算法验证。

20220422球和头部姿势的R和T都是在世界坐标系拍到的,eye ball center 是在头部坐标系中获得的

1
2
3
4
5
6
ball_HCS = np.dot(R.transpose(), ball_pos) - np.dot(R.transpose(), T)#CCS->HCS
gaze_vectors = ball_HCS.reshape(1,3) - eyeball_centers

angles = vector2angles(gaze_vectors[e,:].reshape(3,1))
valid_angles = angles[0] > -40*np.pi/180 and angles[0] < 40*np.pi/180 and angles[1] > -30*np.pi/180 and angles[1] < 30*np.pi/180
eval_set[e][frameIndex] = valid_angles and test_set[frameIndex]
1
2
3
4
5
6
def vector2angles(gaze_vector):
"""Transforms a gaze vector into the angles yaw and elevation."""
gaze_angles = np.empty((2,1),dtype=np.float32)
gaze_angles[0,0] = np.arctan(gaze_vector[0,0]/gaze_vector[2,0])# phi= arctan(x/z) yaw
gaze_angles[1,0] = np.arcsin(gaze_vector[1,0])# theta= arcsin(y) pitch
return gaze_angles

根据pitch俯仰角和yaw航偏角的计算公式以及右手系坐标猜测头部坐标系如下。

pitch_yaw

image-20220129145609020

问题:eyeball_centers 不用坐标转换?后边的代码中转换了o((>ω< ))o。感觉是不用转换的。

在此处把目标小球转化到了头部坐标系中,在头部坐标系中计算视线方向,在头部坐标系中计算出pitch和yaw。(只有在头部坐标系中计算pitch和yaw才有意义)

3.论文中的一组实验是使用头部方向作为估计的视线方向,与视线真值进行比较计算误差。

头部方向用头部坐标系的单位向量(0,0,1)定义,是上图中z轴方向上的单位向量。把头部坐标系中的头部方向乘以相机坐标系宗的头部旋转矩阵得到相机坐标系中的头部方向。把头部坐标系中的眼睛中心位置乘以相机坐标系中的头部旋转矩阵再加上平移量得到相机坐标系中的眼睛中心位置。

20220423,感觉头部姿态是在3d 世界坐标系中估计出来的。把两个向量都转换到了WCS坐标系中获得的。小球ball_tracking 3d也是WCS中的位置,这样才能计算啊。以下的代码是把头部坐标系中的计算的视线向量和视线原点有转换回了世界坐标系。

1
2
3
4
5
6
7
8
9
10
aux_vector = np.zeros((3,1), dtype=np.float32)
aux_vector[2,0] = 1.0#shizhou?
for e in range(2):
gaze_vector = np.empty((head_track[0].shape[0],3), dtype=np.float32)
gaze_origin = np.empty((head_track[0].shape[0],3), dtype=np.float32)
for frameIndex in range(head_track[0].shape[0]):
gaze_vector[frameIndex,:] = np.dot(head_track[0][frameIndex,:,:], aux_vector).flatten() #R
gaze_origin[frameIndex,:] = (np.dot(head_track[0][frameIndex,:,:], eyeball_centers[e,:].reshape(3,1)) + head_track[1][frameIndex,:].reshape(3,1)).flatten()#HCS->CCS
#eye ball center in heads
gaze.append((gaze_vector, gaze_origin))#r,l

在相机坐标系中计算目标点与视线原点的差值获得真值视线向量并转化为单位向量,比较真值视线向量gaze_gt与用头部方向粗略估计的视线方向gaze_vector比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def compute_session_angular_error(gaze_track, target_track, evaluation_set):
"""
Generates the average gaze angular error from the gaze tracking, the ground truth target position and the defined evaluation set
"""
target_track = target_track[evaluation_set, :]#zff: evaluation bool value to extract target_track
gaze_vector = gaze_track[0][evaluation_set, :]
gaze_origin = gaze_track[1][evaluation_set, :]
# Creates the ground truth gaze vector
gaze_gt = target_track - gaze_origin
# Makes the gaze ground truth vector unitary
gaze_gt = gaze_gt/np.sqrt(np.sum(gaze_gt**2,axis=1)).reshape(-1,1)
# Now compute the angle between the two vectors
gaze_angle_errors= np.arccos(np.clip(np.sum(gaze_gt*gaze_vector, axis=1),-1,1))
return np.mean(gaze_angle_errors)

4.论文中的另一组实验使用[2012CVPRW]Gaze_estimation_from_multimodal_Kinect_data给出的adaptive linear regression (ALR)方法。

np.savetxt(‘Is_gaze_as_alr.txt’, gaze_origin, delimiter=’,’, fmt=’%.6f’)

check gaze_origin is same as alr file

image-20220129175228190

使用eye_center和头部旋转和平移参数计算的相机坐标系中的视线原点

image-20220129175542887

文件中给出的alr方法使用的视线原点

从使用ALR方法获得的数据文件中读出相机坐标系中的视线方向和视线原点,与真值进行比较。

根据论文,使用ALR方法可以估计出视线,AlR记录文件中给出的gaze_origin视线原点和使用eye_center和头部旋转和平移参数计算的相机坐标系中的视线原点数据有一定的误差。

可能是计算误差?

相机坐标系CCS和头部坐标系HCS的转换

image-20220203162933131

此公式含义为w相机坐标系到c世界坐标系,r,t为相机外参,只是此处用了这种下角标来表示。

在Eyediap数据集中,headpose.txt中头部的旋转和平移矩阵的参考系是rgb_vga坐标系,ball_tracking.txt,Kinect_depth,hd_rgb相机外参的参考系也是rgb_vga坐标系。

头部姿势以及各个相机外参旋转矩阵都是正交阵,正交阵的的转置和逆是一样的。

1
2
ball_HCS = np.dot(R.transpose(), ball_pos) - np.dot(R.transpose(), T)#CCS->HCS
gaze_origin[frameIndex,:] = (np.dot(head_track[0][frameIndex,:,:], eyeball_centers[e,:].reshape(3,1)) + head_track[1][frameIndex,:].reshape(3,1)).flatten()#HCS->CCS

Eyediap 数据集中view_session.py程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def draw_head_pose(frames, vals, calibrations):#headHD_2D= draw_head_pose(frames, head_vals, calibrations),
# head_vals=head_pose.txt
"""
Draw a coordinate system describing the current head pose
"""
size = 0.05
points = [[0.0, 0.0, 0.0],
[size, 0.0, 0.0],
[0.0, size, 0.0],
[0.0, 0.0, size]]#(4,3)四个三维点
points = np.array(points)
points+= np.array([0.0, 0.0, 0.13]).reshape(1, 3)#相当于把4个点都在z轴上平移了。
R = np.array(vals[:9]).reshape(3, 3)
T = np.array(vals[9:12]).reshape(3, 1)
#rgb_vga的R矩阵时1,-1,-1
# Gets the points positions in 3D
#convert a point in head coordinate to VGA camera coordinate
#正交矩阵逆和转置是一样的
#此处的点是每一行的行向量,相当于等式两边同时转置
points = np.dot(points, R.transpose())+T.reshape(1, 3)#(4,3)headpose在WCS中
for frame, calibration in zip(frames, calibrations):
points_2D = np.int32(project_points(points, calibration))
draw_line(frame, points_2D[0, :], points_2D[1, :], color=(255,0,0))#blue
draw_line(frame, points_2D[0, :], points_2D[2, :], color=(0,255,0))#green
draw_line(frame, points_2D[0, :], points_2D[3, :], color=(0,0,255))#red
return points_2D[0,:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def project_points(points, calibration):#points_2D = np.int32(project_points(points, calibration))
"""
Projects a set of points into the cameras coordinate system
"""
R = calibration['R']
T = calibration['T']
intr = calibration['intrinsics']
# W.r.t. to the camera coordinate system
#zff: wierd,why do not use transpose
#此处的点是每一行的行向量,相当于等式两边同时转置,正交阵逆等于转置,R^-1*ps -R^-1*T,从WCS到某一个相机的坐标系
points_c = np.dot(points, R) - np.dot(R.transpose(), T).reshape(1, 3)
points_2D = np.dot(points_c, intr.transpose())#乘以内参到成像坐标系。

points_2D = points_2D[:,:2]/(points_2D[:, 2].reshape(-1,1))
return points_2D

1.读取数据。

2.在各个坐标系中画出浮动小球的中心。

3.把头部坐标系的坐标轴转化到Kinect_vga坐标系中,再转化到Kinect_vga,Kinect_depth,hd_rgb中。在各个相机拍到的画面中画出头部坐标系。

zff20220225 此处存疑

破案啦,headpose是在WCS中的。可以阅读FunesMora_Idiap-RR-08-2014.pdf

rgb_vga4219

rgb_hd4219

可以看到Kinect拍摄的深度图中有两个圆圈。可能是因为Kinect深度成像方式为IR结构光+双目。目标小球做单摆运动,双目相机同步不是特别好,两个双目不够同步就会这样。

img

Kinect数据 (geometryhub.net)

Kinect v1和Kinect v2的彻底比较https://www.cnblogs.com/traceplus/p/4136297.html