|
@@ -836,40 +836,16 @@ export function solvePlanarPnPRansac(referencePoints: SpeedyMatrix, observedPoin
|
836
|
836
|
if(n < 4)
|
837
|
837
|
throw new IllegalArgumentError('solvePlanarPnP requires at least 4 points');
|
838
|
838
|
|
|
839
|
+ const K = mat3(cameraIntrinsics.read()), E = mat3x4(0);
|
839
|
840
|
const mask = new Array<number>(n);
|
840
|
841
|
mask.fill(0);
|
841
|
842
|
|
842
|
|
- const referencePointsEntries = referencePoints.read();
|
843
|
|
- const observedPointsEntries = observedPoints.read();
|
844
|
|
- const inliers = new Array<number>(n);
|
|
843
|
+ const src = referencePoints.read();
|
|
844
|
+ const dest = observedPoints.read();
|
845
|
845
|
const permutation = Utils.range(n); // vector of indices
|
846
|
846
|
|
847
|
|
- //const p = new Array<number>(2*n), q = new Array<number>(2*n);
|
848
|
|
- //let mp = Speedy.Matrix.Zeros(2, n), mq = Speedy.Matrix.Zeros(2, n);
|
849
|
847
|
const p = new Array<number>(2*4), q = new Array<number>(2*4);
|
850
|
848
|
let mp = Speedy.Matrix.Zeros(2, 4), mq = Speedy.Matrix.Zeros(2, 4);
|
851
|
|
- const reorderPoints = (permutation: number[], n: number) => {
|
852
|
|
- // resize the matrices to change the number of points
|
853
|
|
- //p.length = q.length = 2*n;
|
854
|
|
- //Utils.assert(n == 4);
|
855
|
|
-
|
856
|
|
- // reorder elements according to the permutation
|
857
|
|
- for(let j = 0; j < n; j++) {
|
858
|
|
- const k = permutation[j];
|
859
|
|
- for(let a = 0; a < 2; a++) {
|
860
|
|
- p[j*2 + a] = referencePointsEntries[k*2 + a];
|
861
|
|
- q[j*2 + a] = observedPointsEntries[k*2 + a];
|
862
|
|
- }
|
863
|
|
- }
|
864
|
|
-
|
865
|
|
- // entries are changed and matrices may be resized
|
866
|
|
- //mp = Speedy.Matrix(2, n, p);
|
867
|
|
- //mq = Speedy.Matrix(2, n, q);
|
868
|
|
-
|
869
|
|
- // matrices may NOT be resized
|
870
|
|
- mp.data.set(p);
|
871
|
|
- mq.data.set(q);
|
872
|
|
- };
|
873
|
849
|
|
874
|
850
|
let bestError = Number.POSITIVE_INFINITY;
|
875
|
851
|
let bestPose = INVALID_POSE;
|
|
@@ -879,16 +855,15 @@ export function solvePlanarPnPRansac(referencePoints: SpeedyMatrix, observedPoin
|
879
|
855
|
// Preprocessing
|
880
|
856
|
// Set p = shuffled referencePoints and q = shuffled observedPoints
|
881
|
857
|
Utils.shuffle(permutation);
|
882
|
|
- reorderPoints(permutation, 4);
|
|
858
|
+ reorderPoints4(mp, mq, p, q, src, dest, permutation);
|
883
|
859
|
|
884
|
860
|
// Generate a model with 4 points only
|
885
|
861
|
const pose = solvePlanarPnP(mp, mq, cameraIntrinsics);
|
886
|
|
- const homography = buildHomography(cameraIntrinsics, pose);
|
|
862
|
+ E.set(pose.read()); //E.set(pose.data);
|
|
863
|
+ const homography = buildHomography(K, E);
|
887
|
864
|
|
888
|
865
|
// Evaluate the model against the entire dataset
|
889
|
|
- //reorderPoints(permutation, n);
|
890
|
|
- //const error = computeReprojectionError(homography, p, q, threshold, mask);
|
891
|
|
- const error = computeReprojectionError(homography, referencePointsEntries, observedPointsEntries, threshold, mask);
|
|
866
|
+ const error = computeReprojectionError(homography, src, dest, threshold, mask);
|
892
|
867
|
|
893
|
868
|
/*
|
894
|
869
|
DEBUG && print({
|
|
@@ -901,13 +876,10 @@ export function solvePlanarPnPRansac(referencePoints: SpeedyMatrix, observedPoin
|
901
|
876
|
});
|
902
|
877
|
*/
|
903
|
878
|
|
904
|
|
- // Count and collect the inliers
|
|
879
|
+ // Count the inliers
|
905
|
880
|
let count = 0;
|
906
|
|
- for(let j = 0; j < n; j++) {
|
907
|
|
- if(mask[j] != 0)
|
908
|
|
- inliers[count++] = permutation[j];
|
909
|
|
- //inliers[count++] = j;
|
910
|
|
- }
|
|
881
|
+ for(let j = 0; j < n; j++)
|
|
882
|
+ count += mask[j];
|
911
|
883
|
|
912
|
884
|
/*
|
913
|
885
|
DEBUG && print({
|
|
@@ -923,18 +895,10 @@ export function solvePlanarPnPRansac(referencePoints: SpeedyMatrix, observedPoin
|
923
|
895
|
// Insufficient inliers? Discard the model
|
924
|
896
|
if(count < 4)
|
925
|
897
|
continue;
|
926
|
|
-
|
927
|
|
- // Generate a new model with all the inliers
|
928
|
|
- reorderPoints(inliers, count);
|
929
|
|
- const newPose = solvePlanarPnP(mp, mq, cameraIntrinsics);
|
930
|
|
- const newHomography = buildHomography(cameraIntrinsics, newPose);
|
931
|
|
-
|
932
|
|
- // Evaluate the new model against the set of inliers
|
933
|
|
- const newError = computeReprojectionError(newHomography, p, q, threshold, mask);
|
934
|
898
|
*/
|
935
|
899
|
|
936
|
|
- // If we generate a new model with all the inliers, we may end up with
|
937
|
|
- // a bad normal vector, which would adversely affect the entire pose.
|
|
900
|
+ // We don't generate a new model with all the inliers. We can expect a
|
|
901
|
+ // coarse estimate of the intrinsics K. The model will need refinement.
|
938
|
902
|
// We're just using n = 4. We'll refine the homography later.
|
939
|
903
|
const newPose = pose;
|
940
|
904
|
const newError = error;
|
|
@@ -1011,7 +975,9 @@ export function find6DoFHomography(referencePoints: SpeedyMatrix, observedPoints
|
1011
|
975
|
*/
|
1012
|
976
|
|
1013
|
977
|
// build an initial homography
|
1014
|
|
- const hom = buildHomography(cameraIntrinsics, pose);
|
|
978
|
+ const K = mat3(cameraIntrinsics.read());
|
|
979
|
+ const E = mat3x4(pose.read());
|
|
980
|
+ const hom = buildHomography(K, E);
|
1015
|
981
|
const entries = Array.from<number>(hom);
|
1016
|
982
|
const homography = Speedy.Matrix(3, 3, entries);
|
1017
|
983
|
|
|
@@ -1044,79 +1010,6 @@ export function find6DoFHomography(referencePoints: SpeedyMatrix, observedPoints
|
1044
|
1010
|
);
|
1045
|
1011
|
}
|
1046
|
1012
|
|
1047
|
|
-/*
|
1048
|
|
-// refine homography. pass only inliers!
|
1049
|
|
-export function refineHomography(homography: SpeedyMatrix, referencePoints: SpeedyMatrix, observedPoints: SpeedyMatrix): SpeedyMatrix
|
1050
|
|
-{
|
1051
|
|
- //return homography;
|
1052
|
|
- const n = referencePoints.columns;
|
1053
|
|
- const P = referencePoints.read();
|
1054
|
|
- const Q = observedPoints.read();
|
1055
|
|
- const h = mat3(0);
|
1056
|
|
- const grad = mat3(0); // or 9x1 vector
|
1057
|
|
- const delta = mat3(0);
|
1058
|
|
- const rate = 0.002;
|
1059
|
|
- const threshold = 1e-4;
|
1060
|
|
- const maxIterations = 100;
|
1061
|
|
- const thr2 = threshold * threshold;
|
1062
|
|
- let it = 0, error = 0;
|
1063
|
|
-
|
1064
|
|
- h.set(homography.read());
|
1065
|
|
-
|
1066
|
|
- // gradient descent
|
1067
|
|
- // TODO Gauss-Newton / OLS ?
|
1068
|
|
- for(it = 0; it < maxIterations; it++) {
|
1069
|
|
-
|
1070
|
|
- error = 0;
|
1071
|
|
- grad.fill(0);
|
1072
|
|
-
|
1073
|
|
- for(let j = 0; j < n; j += 2) {
|
1074
|
|
- const px = P[j], py = P[j+1], qx = Q[j], qy = Q[j+1];
|
1075
|
|
- const num1 = h[0] * px + h[3] * py + h[6];
|
1076
|
|
- const num2 = h[1] * px + h[4] * py + h[7];
|
1077
|
|
- const den = h[2] * px + h[5] * py + h[8];
|
1078
|
|
- const den2 = den * den;
|
1079
|
|
- const dx = num1 / den - qx;
|
1080
|
|
- const dy = num2 / den - qy;
|
1081
|
|
-
|
1082
|
|
- grad[0] += dx * (px / den);
|
1083
|
|
- grad[1] += dy * (px / den);
|
1084
|
|
- grad[2] += (dx * num1 + dy * num2) * px / den2;
|
1085
|
|
- grad[3] += dx * (py / den);
|
1086
|
|
- grad[4] += dy * (py / den);
|
1087
|
|
- grad[5] += (dx * num1 + dy * num2) * py / den2;
|
1088
|
|
- grad[6] += dx / den;
|
1089
|
|
- grad[7] += dy / den;
|
1090
|
|
- grad[8] += (dx * num1 + dy * num2) / den2;
|
1091
|
|
-
|
1092
|
|
- error += dx*dx + dy*dy;
|
1093
|
|
- }
|
1094
|
|
-
|
1095
|
|
- grad[2] = -grad[2];
|
1096
|
|
- grad[5] = -grad[5];
|
1097
|
|
- grad[8] = -grad[8];
|
1098
|
|
- error *= 0.5 / n;
|
1099
|
|
-
|
1100
|
|
- if(error < thr2)
|
1101
|
|
- break;
|
1102
|
|
-
|
1103
|
|
- scale(delta, grad, rate);
|
1104
|
|
- sub(h, h, delta);
|
1105
|
|
-
|
1106
|
|
- }
|
1107
|
|
-
|
1108
|
|
- DEBUG && print({
|
1109
|
|
- refineHomography: {
|
1110
|
|
- iterations: it,
|
1111
|
|
- error: Math.sqrt(error),
|
1112
|
|
- grad: Math.sqrt(grad.reduce((s,x) => s+x*x, 0)),
|
1113
|
|
- }
|
1114
|
|
- });
|
1115
|
|
-
|
1116
|
|
- return Speedy.Matrix(3, 3, Array.from(h));
|
1117
|
|
-}
|
1118
|
|
-*/
|
1119
|
|
-
|
1120
|
1013
|
|
1121
|
1014
|
|
1122
|
1015
|
|
|
@@ -1129,13 +1022,12 @@ export function refineHomography(homography: SpeedyMatrix, referencePoints: Spee
|
1129
|
1022
|
|
1130
|
1023
|
/**
|
1131
|
1024
|
* Build a homography matrix
|
1132
|
|
- * @param cameraIntrinsics 3x3 intrinsics
|
1133
|
|
- * @param cameraExtrinsics 3x4 extrinsics
|
|
1025
|
+ * @param K 3x3 intrinsics
|
|
1026
|
+ * @param E 3x4 extrinsics
|
1134
|
1027
|
* @returns 3x3 homography as a TinyMatrix
|
1135
|
1028
|
*/
|
1136
|
|
-function buildHomography(cameraIntrinsics: SpeedyMatrix, cameraExtrinsics: SpeedyMatrix): TinyMatrix
|
|
1029
|
+function buildHomography(K: TinyMatrix, E: TinyMatrix): TinyMatrix
|
1137
|
1030
|
{
|
1138
|
|
- const K = mat3(cameraIntrinsics.read());
|
1139
|
1031
|
const fx = K[0], fy = K[4], cx = K[6], cy = K[7];
|
1140
|
1032
|
const z0 = getZ0(fx);
|
1141
|
1033
|
|
|
@@ -1173,7 +1065,6 @@ function buildHomography(cameraIntrinsics: SpeedyMatrix, cameraExtrinsics: Speed
|
1173
|
1065
|
A4[15] = 1;
|
1174
|
1066
|
|
1175
|
1067
|
// compose with the pose. The result M is 3x4
|
1176
|
|
- const E = mat3x4(cameraExtrinsics.read());
|
1177
|
1068
|
mul(M, E, A4);
|
1178
|
1069
|
|
1179
|
1070
|
// remove the third column from the extrinsics matrix
|
|
@@ -1204,17 +1095,17 @@ function buildHomography(cameraIntrinsics: SpeedyMatrix, cameraExtrinsics: Speed
|
1204
|
1095
|
/**
|
1205
|
1096
|
* Given a homography matrix and n correspondences (pi, qi), compute the reprojection error
|
1206
|
1097
|
* @param homography 3x3 homography matrix
|
1207
|
|
- * @param p reference points as a 2 x n matrix
|
1208
|
|
- * @param q observed points as a 2 x n matrix
|
|
1098
|
+ * @param src reference points as a 2 x n matrix
|
|
1099
|
+ * @param dest observed points as a 2 x n matrix
|
1209
|
1100
|
* @param threshold for a point to be considered an outlier
|
1210
|
1101
|
* @param mask inliers mask (output)
|
1211
|
1102
|
* @returns a measure of the error (less is better)
|
1212
|
1103
|
*/
|
1213
|
|
-function computeReprojectionError(homography: TinyMatrix, p: number[], q: number[], threshold: number = 3, mask: number[] = []): number
|
|
1104
|
+function computeReprojectionError(homography: TinyMatrix, src: number[], dest: number[], threshold: number = 3, mask: number[] = []): number
|
1214
|
1105
|
{
|
1215
|
1106
|
const [ h11, h21, h31, h12, h22, h32, h13, h23, h33 ] = homography;
|
1216
|
1107
|
const [ ih11, ih21, ih31, ih12, ih22, ih32, ih13, ih23, ih33 ] = inverse(invH, homography);
|
1217
|
|
- const n = p.length / 2;
|
|
1108
|
+ const n = src.length / 2;
|
1218
|
1109
|
const thr2 = threshold * threshold;
|
1219
|
1110
|
let totalError = 0;
|
1220
|
1111
|
|
|
@@ -1227,12 +1118,12 @@ function computeReprojectionError(homography: TinyMatrix, p: number[], q: number
|
1227
|
1118
|
|
1228
|
1119
|
// for each point correspondence (p,q)
|
1229
|
1120
|
for(let i = 0, j = 0; i < n; i++, j += 2) {
|
1230
|
|
- const px = p[j+0];
|
1231
|
|
- const py = p[j+1];
|
|
1121
|
+ const px = src[j+0];
|
|
1122
|
+ const py = src[j+1];
|
1232
|
1123
|
const pz = 1; // homogeneous
|
1233
|
1124
|
|
1234
|
|
- const qx = q[j+0];
|
1235
|
|
- const qy = q[j+1];
|
|
1125
|
+ const qx = dest[j+0];
|
|
1126
|
+ const qy = dest[j+1];
|
1236
|
1127
|
const qz = 1;
|
1237
|
1128
|
|
1238
|
1129
|
// compute the reprojection error H*p - q
|
|
@@ -1259,8 +1150,7 @@ function computeReprojectionError(homography: TinyMatrix, p: number[], q: number
|
1259
|
1150
|
|
1260
|
1151
|
// accumulate
|
1261
|
1152
|
totalError += error2 + ierror2;
|
1262
|
|
- if(error2 < thr2 && ierror2 < thr2)
|
1263
|
|
- mask[i] = 1; // this is an inlier
|
|
1153
|
+ mask[i] = +(error2 < thr2 && ierror2 < thr2); // 1 if this is an inlier
|
1264
|
1154
|
|
1265
|
1155
|
/*
|
1266
|
1156
|
DEBUG && mask[i] && print({
|
|
@@ -1284,7 +1174,8 @@ function computeReprojectionError(homography: TinyMatrix, p: number[], q: number
|
1284
|
1174
|
}
|
1285
|
1175
|
|
1286
|
1176
|
// return the average error
|
1287
|
|
- return Math.sqrt(totalError / n);
|
|
1177
|
+ //return Math.sqrt(totalError / n);
|
|
1178
|
+ return totalError / n;
|
1288
|
1179
|
}
|
1289
|
1180
|
|
1290
|
1181
|
/**
|
|
@@ -1491,6 +1382,43 @@ function getZ0(fx: number, targetWidthInMeters: number = DEFAULT_TARGET_WIDTH_IN
|
1491
|
1382
|
return z0;
|
1492
|
1383
|
}
|
1493
|
1384
|
|
|
1385
|
+/**
|
|
1386
|
+ * Reorder n = 4 points according to a permutation
|
|
1387
|
+ * @param mp output
|
|
1388
|
+ * @param mq output
|
|
1389
|
+ * @param p temporary
|
|
1390
|
+ * @param q temporary
|
|
1391
|
+ * @param src reference points as a 2 x n matrix
|
|
1392
|
+ * @param dest observed points as a 2 x n matrix
|
|
1393
|
+ * @param permutation permutation of n indices
|
|
1394
|
+ */
|
|
1395
|
+function reorderPoints4(mp: SpeedyMatrix, mq: SpeedyMatrix, p: number[], q: number[], src: number[], dest: number[], permutation: number[])
|
|
1396
|
+{
|
|
1397
|
+ const n = 4;
|
|
1398
|
+
|
|
1399
|
+ // validate
|
|
1400
|
+ Utils.assert(
|
|
1401
|
+ permutation.length >= n &&
|
|
1402
|
+ p.length == n*2 && q.length == n*2 &&
|
|
1403
|
+ mp.rows == 2 && mp.columns == n &&
|
|
1404
|
+ mq.rows == 2 && mq.columns == n
|
|
1405
|
+ );
|
|
1406
|
+
|
|
1407
|
+ // reorder elements according to the permutation
|
|
1408
|
+ for(let j = 0; j < n; j++) {
|
|
1409
|
+ const k = permutation[j];
|
|
1410
|
+ for(let a = 0; a < 2; a++) {
|
|
1411
|
+ p[j*2 + a] = src[k*2 + a];
|
|
1412
|
+ q[j*2 + a] = dest[k*2 + a];
|
|
1413
|
+ }
|
|
1414
|
+ }
|
|
1415
|
+
|
|
1416
|
+ // matrices may NOT be resized
|
|
1417
|
+ mp.data.set(p);
|
|
1418
|
+ mq.data.set(q);
|
|
1419
|
+}
|
|
1420
|
+
|
|
1421
|
+
|
1494
|
1422
|
|
1495
|
1423
|
|
1496
|
1424
|
|