-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJoint.pde
More file actions
285 lines (267 loc) · 17.3 KB
/
Joint.pde
File metadata and controls
285 lines (267 loc) · 17.3 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/**
* A Joint object contains information about its present and past orientation and position. It also knows who is its parent joint and bone.
*/
public class Joint{
private Skeleton skeleton;
private int id;
private boolean isEndJoint = false;
private Joint parentJoint;
private Bone parentBone;
private ArrayList<Bone> childBones = new ArrayList<Bone>(); // the joint sits at the beginning of these bones. (closer to SpineMid)
private int trackingState;
private PVector measuredPosition;
private float averageDistanceBetweenMeasuredPositionAndEstimatedPosition;
private float distanceBetweenMeasuredPositionAndEstimatedPositionStandardDeviation;
private PVector estimatedPosition;
private PVector estimatedVelocity = new PVector(0,0,0);
private PVector estimatedAcceleration = new PVector(0,0,0);
private Quaternion measuredOrientation;
private float averageAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation; // theta
private float angleBetweenMeasuredOrientationAndPredictedCurrentOrientationStandardDeviation; // std of theta: angle between measuredOrientation and estimatedOrientation.
private Quaternion estimatedOrientation;
private Quaternion previousEstimatedOrientation;
private PVector estimatedDirectionX;
private PVector estimatedDirectionY;
private PVector estimatedDirectionZ;
private PVector measuredDirectionX;
private PVector measuredDirectionY;
private PVector measuredDirectionZ;
public Joint(int id, KJoint kJoint, Skeleton skeleton){
this.skeleton = skeleton;
this.id = id;
if(this.id==3 || this.id==15 || this.id==19 || this.id>20){
this.isEndJoint = true;
}
this.receiveNewMeasurements(kJoint);
this.estimatedPosition = this.measuredPosition;
this.averageDistanceBetweenMeasuredPositionAndEstimatedPosition = 0.05; // measurement error in meters
this.distanceBetweenMeasuredPositionAndEstimatedPositionStandardDeviation = 0.025; // in meters. Pure guess!
this.estimatedOrientation = this.measuredOrientation;
this.previousEstimatedOrientation = this.estimatedOrientation;
this.averageAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation = 1; // in radians. Pure guess!
this.angleBetweenMeasuredOrientationAndPredictedCurrentOrientationStandardDeviation = 1; // in radians. Pure guess!
}
public void addChildBone(Bone childBone){
this.childBones.add(childBone);
}
public void setParentJoint(Joint parentJoint){
this.parentJoint = parentJoint;
}
public void setParentBone(Bone parentBone){
this.parentBone = parentBone;
}
/**
* Get new joint measurements from kinect. Discard it if is an impossible measurement and replace it by an educated prediction.
*/
public void receiveNewMeasurements(KJoint kjoint){
this.trackingState = kjoint.getState();
if(kjoint.getPosition().z > 0.01){ // discard impossible measurements
this.measuredPosition = kjoint.getPosition();
this.measuredOrientation = new Quaternion(kjoint.getOrientation());
} else { // replace impossible measurements with prediction.
this.measuredPosition = PVector.add(this.estimatedPosition, PVector.mult(this.estimatedVelocity, this.skeleton.scene.currentDeltaT));
this.measuredOrientation = qSlerp(this.previousEstimatedOrientation, this.estimatedOrientation, 1 + this.skeleton.scene.currentDeltaT/this.skeleton.scene.previousDeltaT);
println("impossible measurement of joint "+ this.id+" received and discarded (too close to kinect): "+kjoint.getPosition());
}
}
/**
* Updates itself and call next bones to be updated.
* @param confidenceParameters (alpha, beta, gamma).
*/
public void update(float[] confidenceParameters){
float[] adjustedConfidenceParameters = this.adjustConfidenceParametersByTrackingState(confidenceParameters, this.trackingState);
this.updatePosition(adjustedConfidenceParameters);
if(!this.isEndJoint){
this.updateOrientation(adjustedConfidenceParameters);
this.calculateCoordinateSystemOrientation();
}
for(Bone childBone:this.childBones){// Continue Chained Update, calling next bones:
childBone.update(confidenceParameters);
}
}
/**
* Iterates one step on the estimation of the current position.
* First, it judges if the new measurement is an outlier or not, by calculating how close to the mean it is.
* Then it uses this step size to calculate the new estimatedPosition using the previousEstimatedPosition, velocity and the current measurement.
* @param confidenceParameters (alpha, beta, gamma).
*/
private void updatePosition(float[] confidenceParameters){
PVector newEstimatedPosition;
float[] adjustedConfidenceParametersForresponseTradeoff = adjustConfidenceParametersByResponseTradeoff(confidenceParameters);
if(this.id == 1){ // SpineMid
newEstimatedPosition = PVector.add(this.estimatedPosition, PVector.mult(this.estimatedVelocity, this.skeleton.scene.currentDeltaT));
}
else{ // Common Joints
newEstimatedPosition = PVector.add(this.estimatedPosition, PVector.mult(this.estimatedVelocity, this.skeleton.dampingFactor*this.skeleton.scene.currentDeltaT)).mult(adjustedConfidenceParametersForresponseTradeoff[1])
.add(PVector.add(this.parentJoint.estimatedPosition, PVector.mult(this.parentBone.currentEstimatedDirection, this.parentBone.estimatedLength)).mult(adjustedConfidenceParametersForresponseTradeoff[2]))
.add(PVector.mult(this.measuredPosition, adjustedConfidenceParametersForresponseTradeoff[0]));
}
float distanceBetweenMeasuredPositionAndEstimatedPosition = PVector.sub(this.measuredPosition, newEstimatedPosition).mag();
this.averageDistanceBetweenMeasuredPositionAndEstimatedPosition = lerp(this.averageDistanceBetweenMeasuredPositionAndEstimatedPosition, distanceBetweenMeasuredPositionAndEstimatedPosition, pow(confidenceParameters[0], this.skeleton.responseTradeoff));
this.distanceBetweenMeasuredPositionAndEstimatedPositionStandardDeviation = lerp(this.distanceBetweenMeasuredPositionAndEstimatedPositionStandardDeviation, abs(distanceBetweenMeasuredPositionAndEstimatedPosition-this.averageDistanceBetweenMeasuredPositionAndEstimatedPosition), pow(confidenceParameters[0], this.skeleton.responseTradeoff));
float measuredPositionConfidence = howCloseToTheMean(distanceBetweenMeasuredPositionAndEstimatedPosition, this.averageDistanceBetweenMeasuredPositionAndEstimatedPosition, this.distanceBetweenMeasuredPositionAndEstimatedPositionStandardDeviation);
float[] adjustedConfidenceParameters = adjustConfidenceParametersByAlphaMultiplier(confidenceParameters, measuredPositionConfidence);
if(this.id == 1){ // SpineMid
newEstimatedPosition = PVector.lerp(PVector.add(this.estimatedPosition, PVector.mult(this.estimatedVelocity, this.skeleton.dampingFactor*this.skeleton.scene.currentDeltaT)), this.measuredPosition, adjustedConfidenceParameters[0]);
}
else{ // Common Joints
adjustedConfidenceParameters = adjustConfidenceParametersByAlphaMultiplier(adjustedConfidenceParameters, this.parentBone.measuredLengthConfidence);
newEstimatedPosition = PVector.add(this.estimatedPosition, PVector.mult(this.estimatedVelocity, this.skeleton.dampingFactor*this.skeleton.scene.currentDeltaT)).mult(adjustedConfidenceParameters[1])
.add(PVector.add(this.parentJoint.estimatedPosition, PVector.mult(this.parentBone.currentEstimatedDirection, this.parentBone.estimatedLength)).mult(adjustedConfidenceParameters[2]))
.add(PVector.mult(this.measuredPosition, adjustedConfidenceParameters[0]));
}
PVector newEstimatedVelocity = PVector.sub(newEstimatedPosition, this.estimatedPosition).div(this.skeleton.scene.currentDeltaT);
this.estimatedAcceleration = PVector.sub(newEstimatedVelocity, this.estimatedVelocity).div(this.skeleton.scene.currentDeltaT);
this.estimatedVelocity = newEstimatedVelocity;
this.estimatedPosition = newEstimatedPosition;
/*
if(this.id == FOOT_LEFT){
println("LeftFoot accel: "+this.estimatedAcceleration.mag());
println("LeftFoot velocity: "+this.estimatedVelocity.mag());
}*/
}
/**
* Iterates one step on the estimation of the current orientation.
* First, it judges if the new measurement is an outlier or not, by calculating how close to the mean it is.
* Then it uses this step size to calculate the new estimatedOrientation using the previousEstimatedOrientation and the current measurement.
* It uses quaternion extrapolation (qSlerp with step>1) to account for orientation velocity.
* @param confidenceParameters (alpha, beta, gamma).
*/
private void updateOrientation(float[] confidenceParameters){
Quaternion predictedCurrentOrientation = qSlerp(this.previousEstimatedOrientation, this.estimatedOrientation, 1 + this.skeleton.dampingFactor*this.skeleton.scene.currentDeltaT/this.skeleton.scene.previousDeltaT);
float measuredAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation = angleBetweenQuaternions(this.measuredOrientation, predictedCurrentOrientation);
this.averageAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation = lerp(this.averageAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation, measuredAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation, pow(confidenceParameters[0], this.skeleton.responseTradeoff));
this.angleBetweenMeasuredOrientationAndPredictedCurrentOrientationStandardDeviation = lerp(this.angleBetweenMeasuredOrientationAndPredictedCurrentOrientationStandardDeviation, abs(measuredAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation-this.averageAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation), pow(confidenceParameters[0], this.skeleton.responseTradeoff));
float orientationStep = howCloseToTheMean(measuredAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation, this.averageAngleBetweenMeasuredOrientationAndPredictedCurrentOrientation, this.angleBetweenMeasuredOrientationAndPredictedCurrentOrientationStandardDeviation);
Quaternion newEstimatedOrientation = qSlerp(predictedCurrentOrientation, this.measuredOrientation, orientationStep*confidenceParameters[0]);
this.previousEstimatedOrientation = this.estimatedOrientation;
this.estimatedOrientation = newEstimatedOrientation;
}
/**
* Adjust the confidence of the new measurements (alpha) of the joint based on its trackingState received from Kinect.
* If inferred, confidence = confidence/3.
* If not tracked, confidence = confidence/9.
* Then it proportionally redistributes that confidence removed from alpha to beta and gamma.
* @param confidenceParameters array of: alpha, beta, gamma.
* @param trackingState received from kinect.
* @return adjustedConfidenceParameters (alpha, beta, gamma).
*/
private float[] adjustConfidenceParametersByTrackingState(float[] confidenceParameters, int trackingState){
float alphaAdjusted;
if(trackingState == 2){ // If joint is tracked
alphaAdjusted = confidenceParameters[0];
}
else if(trackingState == 1){ // If joint is inferred
alphaAdjusted = confidenceParameters[0]/3;
}
else{ //(trackingState == 0) // If joint is not tracked
alphaAdjusted = confidenceParameters[0]/9;
}
float betaAdjusted = confidenceParameters[1] + (confidenceParameters[0]-alphaAdjusted)*confidenceParameters[1]/(confidenceParameters[1]+confidenceParameters[2]);
float gammaAdjusted = confidenceParameters[2] + (confidenceParameters[0]-alphaAdjusted)*confidenceParameters[2]/(confidenceParameters[1]+confidenceParameters[2]);
float[] adjustedConfidenceParameters = {alphaAdjusted, betaAdjusted, gammaAdjusted};
return adjustedConfidenceParameters;
}
/**
* Remove confidence of new measurements to get only the estimated position.
* Then it proportionally redistributes that confidence removed from alpha to beta and gamma.
* @param confidenceParameters array of: alpha, beta, gamma.
* @return adjustedConfidenceParameters (alpha, beta, gamma).
*/
private float[] adjustConfidenceParametersByResponseTradeoff(float[] confidenceParameters){
float alphaAdjusted = 0;
float betaAdjusted = confidenceParameters[1] + (confidenceParameters[0]-alphaAdjusted)*confidenceParameters[1]/(confidenceParameters[1]+confidenceParameters[2]);
float gammaAdjusted = confidenceParameters[2] + (confidenceParameters[0]-alphaAdjusted)*confidenceParameters[2]/(confidenceParameters[1]+confidenceParameters[2]);
float[] adjustedConfidenceParameters = {alphaAdjusted, betaAdjusted, gammaAdjusted};
return adjustedConfidenceParameters;
}
/**
* Adjust the confidence of the new measurements (alpha) using a generic multiplier (between 0 and 1).
* Then it proportionally redistributes that confidence removed from alpha to beta and gamma.
* @param confidenceParameters array of: alpha, beta, gamma.
* @param alphaMultiplier generic multiplier (between 0 and 1).
* @return adjustedConfidenceParameters (alpha, beta, gamma).
*/
private float[] adjustConfidenceParametersByAlphaMultiplier(float[] confidenceParameters, float alphaMultiplier){
float alphaAdjusted = confidenceParameters[0]*alphaMultiplier;
float betaAdjusted = confidenceParameters[1] + (confidenceParameters[0]-alphaAdjusted)*confidenceParameters[1]/(confidenceParameters[1]+confidenceParameters[2]);
float gammaAdjusted = confidenceParameters[2] + (confidenceParameters[0]-alphaAdjusted)*confidenceParameters[2]/(confidenceParameters[1]+confidenceParameters[2]);
float[] adjustedConfidenceParameters = {alphaAdjusted, betaAdjusted, gammaAdjusted};
return adjustedConfidenceParameters;
}
/**
* Calculate the directions of each axis (x, y, z) of the joint coordinate system.
*/
private void calculateCoordinateSystemOrientation(){
this.estimatedDirectionX = qMult(qMult(this.estimatedOrientation, new Quaternion(0, 1, 0, 0)), qConjugate(this.estimatedOrientation)).vector;
this.estimatedDirectionY = qMult(qMult(this.estimatedOrientation, new Quaternion(0, 0, 1, 0)), qConjugate(this.estimatedOrientation)).vector;
this.estimatedDirectionZ = qMult(qMult(this.estimatedOrientation, new Quaternion(0, 0, 0, 1)), qConjugate(this.estimatedOrientation)).vector;
this.measuredDirectionX = qMult(qMult(this.measuredOrientation, new Quaternion(0, 1, 0, 0)), qConjugate(this.measuredOrientation)).vector;
this.measuredDirectionY = qMult(qMult(this.measuredOrientation, new Quaternion(0, 0, 1, 0)), qConjugate(this.measuredOrientation)).vector;
this.measuredDirectionZ = qMult(qMult(this.measuredOrientation, new Quaternion(0, 0, 0, 1)), qConjugate(this.measuredOrientation)).vector;
}
/**
* Drawing method.
* @param drawEstimatedOrientation boolean indicating if the estimated orientation should be drawn.
* @param drawMeasuredOrientation boolean indicating if the measured orientation should be drawn.
*/
public void draw(boolean drawMeasuredOrientation, boolean drawEstimatedOrientation){
this.drawPosition(this.skeleton.colorEstimated);
if(!this.isEndJoint){
this.drawOrientation(drawEstimatedOrientation, drawMeasuredOrientation); // estimated, measured
}
}
/**
* Draw a sphere on its position.
* @param color.
*/
public void drawPosition(color c){
strokeWeight(1);
sphereDetail(6);
if(this.trackingState == 2){ // normal sphere if tracked
stroke(c);
fill(c);
} else if(this.trackingState == 1){ // black contour if inferred
stroke(color(0, 0, 0, 170));
fill(c);
} else{ // black fill and controur if not tracked
stroke(color(0, 0, 0, 170));
fill(color(0,0,0,170));
}
pushMatrix();
translate(reScaleX(this.estimatedPosition.x, "joint.drawPosition"),
reScaleY(this.estimatedPosition.y, "joint.drawPosition"),
reScaleZ(this.estimatedPosition.z, "joint.drawPosition"));
sphere(3);
popMatrix();
}
/**
* Draw the joint coordinate system on screen.
* @param drawEstimated boolean indicating if the estimated orientation should be drawn.
* @param drawMeasured boolean indicating if the measured orientation should be drawn.
*/
public void drawOrientation(boolean drawEstimated, boolean drawMeasured){ // X:Red, Y:Green, Z:Blue
float size = 15;
pushMatrix();
translate(reScaleX(this.estimatedPosition.x, "joint.drawOrientation"), reScaleY(this.estimatedPosition.y, "joint.drawOrientation"), reScaleZ(this.estimatedPosition.z, "joint.drawOrientation"));
if(drawEstimated){
strokeWeight(5);
stroke(255, 0, 0, 170);
line(0, 0, 0, size*this.estimatedDirectionX.x, size*this.estimatedDirectionX.y, size*this.estimatedDirectionX.z);
stroke(0, 255, 0, 170);
line(0, 0, 0, size*this.estimatedDirectionY.x, size*this.estimatedDirectionY.y, size*this.estimatedDirectionY.z);
stroke(0, 0, 255, 170);
line(0, 0, 0, size*this.estimatedDirectionZ.x, size*this.estimatedDirectionZ.y, size*this.estimatedDirectionZ.z);
}
if(drawMeasured){
strokeWeight(2);
stroke(255, 0, 0, 85);
line(0, 0, 0, size*this.measuredDirectionX.x, size*this.measuredDirectionX.y, size*this.measuredDirectionX.z);
stroke(0, 255, 0, 85);
line(0, 0, 0, size*this.measuredDirectionY.x, size*this.measuredDirectionY.y, size*this.measuredDirectionY.z);
stroke(0, 0, 255, 85);
line(0, 0, 0, size*this.measuredDirectionZ.x, size*this.measuredDirectionZ.y, size*this.measuredDirectionZ.z);
}
popMatrix();
}
}