|
@@ -0,0 +1,188 @@
|
|
|
+% OnePointDriftCorrection.m
|
|
|
+%
|
|
|
+% This function applies drift correction for 360 degree videos. For correcting
|
|
|
+% the drift it has to know when the point was displayed and at which video
|
|
|
+% position (equirectangular) was displayed. It then calculates the mean head and
|
|
|
+% gaze directions during this time. It basically follows the inverse of the drift
|
|
|
+% correction during recording and applies the new displacements.
|
|
|
+%
|
|
|
+% NOTE: Be careful to provide a time interval in which gaze is not noisy
|
|
|
+%
|
|
|
+% input:
|
|
|
+% arffFile - path to ARFF file
|
|
|
+% targetStartTime - target diplay starting time in us
|
|
|
+% targetEndTime - target diplay starting time in us
|
|
|
+% targetPos - [x, y] equirectangular position
|
|
|
+% saveFile - file to save data
|
|
|
+
|
|
|
+function OnePointDriftCorrection(arffFile, targetStartTime, targetEndTime, targetPos, saveFile)
|
|
|
+ c_timeName = 'time';
|
|
|
+ c_xName = 'x';
|
|
|
+ c_yName = 'y';
|
|
|
+ c_confName = 'confidence';
|
|
|
+ c_xHeadName = 'x_head';
|
|
|
+ c_yHeadName = 'y_head';
|
|
|
+ c_angleHeadName = 'angle_deg_head';
|
|
|
+ c_confThreshold = 0.8;
|
|
|
+
|
|
|
+ [data, metadata, attributes, relation, comments] = LoadArff(arffFile);
|
|
|
+
|
|
|
+ timeInd = GetAttPositionArff(attributes, c_timeName);
|
|
|
+ xInd = GetAttPositionArff(attributes, c_xName);
|
|
|
+ yInd = GetAttPositionArff(attributes, c_yName);
|
|
|
+ confInd = GetAttPositionArff(attributes, c_confName);
|
|
|
+ xHeadInd = GetAttPositionArff(attributes, c_xHeadName);
|
|
|
+ yHeadInd = GetAttPositionArff(attributes, c_yHeadName);
|
|
|
+
|
|
|
+ % convert target position to cartesian
|
|
|
+ [horTargetRads, verTargetRads] = EquirectToSpherical(targetPos(1), targetPos(2), metadata.width_px, metadata.height_px);
|
|
|
+ targetVec = SphericalToCart(horTargetRads, verTargetRads);
|
|
|
+
|
|
|
+ % find start/end ime position
|
|
|
+ indStart = find(data(:,timeInd) > targetStartTime);
|
|
|
+ assert(~isempty(indStart), 'Could not find provided start time');
|
|
|
+ indStart = indStart(1);
|
|
|
+
|
|
|
+ indEnd = find(data(:,timeInd) > targetEndTime);
|
|
|
+ assert(~isempty(indEnd), 'Could not find provided end time');
|
|
|
+ indEnd = indEnd(1);
|
|
|
+
|
|
|
+ % find mean head and gaze vector
|
|
|
+ gazeVecMean = zeros(3,1);
|
|
|
+ for ind=indStart:indEnd
|
|
|
+ if (data(ind, confInd) < c_confThreshold)
|
|
|
+ continue;
|
|
|
+ end
|
|
|
+
|
|
|
+ % convert gaze to reference vector
|
|
|
+ [horGazeRads, verGazeRads] = EquirectToSpherical(data(ind, xInd), data(ind, yInd), metadata.width_px, metadata.height_px);
|
|
|
+ gazeVec = SphericalToCart(horGazeRads, verGazeRads);
|
|
|
+ gazeVecMean = gazeVecMean + gazeVec;
|
|
|
+ end
|
|
|
+ gazeVecMean = gazeVecMean / norm(gazeVecMean);
|
|
|
+
|
|
|
+ [horRads, verRads] = CartToSpherical(gazeVecMean);
|
|
|
+ [xMean, yMean] = SphericalToEquirect(horRads, verRads, metadata.width_px, metadata.height_px);
|
|
|
+
|
|
|
+ dispX = targetPos(1) - xMean;
|
|
|
+ dispY = targetPos(2) - yMean;
|
|
|
+
|
|
|
+ dispXInd = GetMetaExtraPosArff(metadata, 'displacement_x');
|
|
|
+ newDispX = str2num(metadata.extra{dispXInd,2}) + dispX;
|
|
|
+ metadata.extra{dispXInd,2} = num2str(newDispX, '%.2f');
|
|
|
+ dispYInd = GetMetaExtraPosArff(metadata, 'displacement_y');
|
|
|
+ newDispY = str2num(metadata.extra{dispYInd,2}) + dispY;
|
|
|
+ metadata.extra{dispYInd,2} = num2str(newDispY, '%.2f');
|
|
|
+
|
|
|
+ [data(:,xInd), data(:,yInd)] = UndoLimits(data(:,xInd), data(:,yInd), data(:,confInd));
|
|
|
+ [data(:,xHeadInd), data(:,yHeadInd)] = UndoLimits(data(:,xHeadInd), data(:,yHeadInd), data(:,confInd));
|
|
|
+ for ind=1:size(data,1)
|
|
|
+ % Check if the limits were wrongly undone
|
|
|
+ angle = GetAngle(data(ind,xInd), data(ind,xHeadInd));
|
|
|
+
|
|
|
+ if (angle > 360/3)
|
|
|
+ [data(ind,xInd), data(ind,yInd)] = BringWithinLimits(data(ind,xInd), data(ind,yInd));
|
|
|
+ end
|
|
|
+
|
|
|
+ % Check if difference comes from errors during recordings
|
|
|
+ angle = GetAngle(data(ind,xInd), data(ind,xHeadInd));
|
|
|
+ if (angle > 360/3 && angle < 2*360/3)
|
|
|
+ data(ind,xInd) = data(ind,xInd) - metadata.width_px/2;
|
|
|
+ [data(ind,xInd), data(ind,yInd)] = BringWithinLimits(data(ind,xInd), data(ind,yInd));
|
|
|
+ end
|
|
|
+
|
|
|
+ data(ind,xInd) = data(ind,xInd) + dispX;
|
|
|
+ data(ind,yInd) = data(ind,yInd) + dispY;
|
|
|
+ [data(ind,xInd), data(ind,yInd)] = BringWithinLimits(data(ind,xInd), data(ind,yInd));
|
|
|
+
|
|
|
+ data(ind,xHeadInd) = data(ind,xHeadInd) + dispX;
|
|
|
+ data(ind,yHeadInd) = data(ind,yHeadInd) + dispY;
|
|
|
+ [data(ind,xHeadInd), data(ind,yHeadInd)] = BringWithinLimits(data(ind,xHeadInd), data(ind,yHeadInd));
|
|
|
+ if(data(ind, confInd) < 0.3)
|
|
|
+ data(ind,xInd) = 0;
|
|
|
+ data(ind,yInd) = 0;
|
|
|
+ end
|
|
|
+
|
|
|
+ end
|
|
|
+
|
|
|
+ SaveArff(saveFile, data, metadata, attributes, relation, comments);
|
|
|
+
|
|
|
+ % Returns angle between two x coordinates
|
|
|
+ function angle = GetAngle(x1, x2)
|
|
|
+ horRads1 = EquirectToSpherical(x1, 0, metadata.width_px, 1);
|
|
|
+ horRads2 = EquirectToSpherical(x2, 0, metadata.width_px, 1);
|
|
|
+
|
|
|
+ angle = abs(horRads1 - horRads2) * 180 / pi;
|
|
|
+ end
|
|
|
+
|
|
|
+ function [x, y] = BringWithinLimits(x, y)
|
|
|
+ if (y < 0)
|
|
|
+ y = -y;
|
|
|
+ x = x + metadata.width_px / 2;
|
|
|
+ end
|
|
|
+
|
|
|
+ if (y > metadata.height_px)
|
|
|
+ y = 2 * metadata.height_px - y - 1;
|
|
|
+ x = x + metadata.width_px / 2;
|
|
|
+ end
|
|
|
+
|
|
|
+ if (x < 0)
|
|
|
+ x = ceil(abs(x)/metadata.width_px)*metadata.width_px + x - 1;
|
|
|
+ %x = metadata.width_px + x - 1;
|
|
|
+ elseif (x > metadata.width_px)
|
|
|
+ x = x - floor(x/metadata.width_px)*metadata.width_px;
|
|
|
+ %x = x - metadata.width_px;
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ % x,y are list vectors
|
|
|
+ function [xConv, yConv] = UndoLimits(x, y, confidence)
|
|
|
+ xConv = zeros(size(x));
|
|
|
+ yConv = zeros(size(y));
|
|
|
+ countHor = 0;
|
|
|
+ xConv(1) = x(1);
|
|
|
+ yConv(1) = y(1);
|
|
|
+ % remove horizontal transitions
|
|
|
+ for i=2:size(x,1)
|
|
|
+ totConf = confidence(i) + confidence(i-1);
|
|
|
+ if (totConf > 1.7)
|
|
|
+ if (x(i) - x(i-1) > metadata.width_px - 100)
|
|
|
+ countHor = countHor - 1;
|
|
|
+ elseif (x(i) - x(i-1) < -(metadata.width_px - 100))
|
|
|
+ countHor = countHor + 1;
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ if (countHor > 0)
|
|
|
+ xConv(i) = x(i) + metadata.width_px;
|
|
|
+ elseif (countHor < 0)
|
|
|
+ xConv(i) = x(i) - metadata.width_px;
|
|
|
+ else
|
|
|
+ xConv(i) = x(i);
|
|
|
+ end
|
|
|
+ yConv(i) = y(i);
|
|
|
+ end
|
|
|
+
|
|
|
+ % remove vertical transitions
|
|
|
+ countVert = 0;
|
|
|
+ c_perc = 0.1;
|
|
|
+ for i=2:size(xConv,1)
|
|
|
+ diff = abs(x(i) - x(i-1));
|
|
|
+ yCloseToLim = y(i) > metadata.height_px - 100 | y(i) < 100;
|
|
|
+
|
|
|
+ if (diff > (1-c_perc)*metadata.width_px/2 && diff < (1+c_perc)*metadata.width_px/2 && yCloseToLim)
|
|
|
+ countVert = countVert + 1;
|
|
|
+ end
|
|
|
+
|
|
|
+ if (mod(countVert,2) == 1)
|
|
|
+ xConv(i) = xConv(i) + metadata.width_px/2;
|
|
|
+ if (yConv(i) >= metadata.height_px/2)
|
|
|
+ yConv(i) = 2*metadata.height_px - yConv(i) - 1;
|
|
|
+ else
|
|
|
+ yConv(i) = -yConv(i);
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|