Browse Source

Performance tweaks

customisations
alemart 6 months ago
parent
commit
0e81ac7dbd
1 changed files with 66 additions and 138 deletions
  1. 66
    138
      src/geometry/pnp.ts

+ 66
- 138
src/geometry/pnp.ts View File

836
     if(n < 4)
836
     if(n < 4)
837
         throw new IllegalArgumentError('solvePlanarPnP requires at least 4 points');
837
         throw new IllegalArgumentError('solvePlanarPnP requires at least 4 points');
838
 
838
 
839
+    const K = mat3(cameraIntrinsics.read()), E = mat3x4(0);
839
     const mask = new Array<number>(n);
840
     const mask = new Array<number>(n);
840
     mask.fill(0);
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
     const permutation = Utils.range(n); // vector of indices
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
     const p = new Array<number>(2*4), q = new Array<number>(2*4);
847
     const p = new Array<number>(2*4), q = new Array<number>(2*4);
850
     let mp = Speedy.Matrix.Zeros(2, 4), mq = Speedy.Matrix.Zeros(2, 4);
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
     let bestError = Number.POSITIVE_INFINITY;
850
     let bestError = Number.POSITIVE_INFINITY;
875
     let bestPose = INVALID_POSE;
851
     let bestPose = INVALID_POSE;
879
         // Preprocessing
855
         // Preprocessing
880
         // Set p = shuffled referencePoints and q = shuffled observedPoints
856
         // Set p = shuffled referencePoints and q = shuffled observedPoints
881
         Utils.shuffle(permutation);
857
         Utils.shuffle(permutation);
882
-        reorderPoints(permutation, 4);
858
+        reorderPoints4(mp, mq, p, q, src, dest, permutation);
883
 
859
 
884
         // Generate a model with 4 points only
860
         // Generate a model with 4 points only
885
         const pose = solvePlanarPnP(mp, mq, cameraIntrinsics);
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
         // Evaluate the model against the entire dataset
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
         DEBUG && print({
869
         DEBUG && print({
901
         });
876
         });
902
         */
877
         */
903
 
878
 
904
-        // Count and collect the inliers
879
+        // Count the inliers
905
         let count = 0;
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
         DEBUG && print({
885
         DEBUG && print({
923
         // Insufficient inliers? Discard the model
895
         // Insufficient inliers? Discard the model
924
         if(count < 4)
896
         if(count < 4)
925
             continue;
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
         // We're just using n = 4. We'll refine the homography later.
902
         // We're just using n = 4. We'll refine the homography later.
939
         const newPose = pose;
903
         const newPose = pose;
940
         const newError = error;
904
         const newError = error;
1011
     */
975
     */
1012
 
976
 
1013
     // build an initial homography
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
     const entries = Array.from<number>(hom);
981
     const entries = Array.from<number>(hom);
1016
     const homography = Speedy.Matrix(3, 3, entries);
982
     const homography = Speedy.Matrix(3, 3, entries);
1017
 
983
 
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
 
1022
 
1130
 /**
1023
 /**
1131
  * Build a homography matrix
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
  * @returns 3x3 homography as a TinyMatrix
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
     const fx = K[0], fy = K[4], cx = K[6], cy = K[7];
1031
     const fx = K[0], fy = K[4], cx = K[6], cy = K[7];
1140
     const z0 = getZ0(fx);
1032
     const z0 = getZ0(fx);
1141
 
1033
 
1173
     A4[15] = 1;
1065
     A4[15] = 1;
1174
 
1066
 
1175
     // compose with the pose. The result M is 3x4
1067
     // compose with the pose. The result M is 3x4
1176
-    const E = mat3x4(cameraExtrinsics.read());
1177
     mul(M, E, A4);
1068
     mul(M, E, A4);
1178
 
1069
 
1179
     // remove the third column from the extrinsics matrix
1070
     // remove the third column from the extrinsics matrix
1204
 /**
1095
 /**
1205
  * Given a homography matrix and n correspondences (pi, qi), compute the reprojection error
1096
  * Given a homography matrix and n correspondences (pi, qi), compute the reprojection error
1206
  * @param homography 3x3 homography matrix
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
  * @param threshold for a point to be considered an outlier
1100
  * @param threshold for a point to be considered an outlier
1210
  * @param mask inliers mask (output)
1101
  * @param mask inliers mask (output)
1211
  * @returns a measure of the error (less is better)
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
     const [ h11, h21, h31, h12, h22, h32, h13, h23, h33 ] = homography;
1106
     const [ h11, h21, h31, h12, h22, h32, h13, h23, h33 ] = homography;
1216
     const [ ih11, ih21, ih31, ih12, ih22, ih32, ih13, ih23, ih33 ] = inverse(invH, homography);
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
     const thr2 = threshold * threshold;
1109
     const thr2 = threshold * threshold;
1219
     let totalError = 0;
1110
     let totalError = 0;
1220
 
1111
 
1227
 
1118
 
1228
     // for each point correspondence (p,q)
1119
     // for each point correspondence (p,q)
1229
     for(let i = 0, j = 0; i < n; i++, j += 2) {
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
         const pz = 1; // homogeneous
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
         const qz = 1;
1127
         const qz = 1;
1237
 
1128
 
1238
         // compute the reprojection error H*p - q
1129
         // compute the reprojection error H*p - q
1259
 
1150
 
1260
         // accumulate
1151
         // accumulate
1261
         totalError += error2 + ierror2;
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
         DEBUG && mask[i] && print({
1156
         DEBUG && mask[i] && print({
1284
     }
1174
     }
1285
 
1175
 
1286
     // return the average error
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
     return z0;
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
 

Loading…
Cancel
Save