001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package imagingbook.common.image.matching.lucaskanade; 010 011import ij.IJ; 012import imagingbook.common.math.Matrix; 013 014 015public class LucasKanadeMatcher1D { 016 017 018 final double[] I; 019 final double[] R; 020 final int M, N; 021 final double xc; 022 final double[] gI; 023 024 int maxIterations = 100; 025 026 public void setMaxIterations(int maxIterations) { 027 this.maxIterations = maxIterations; 028 } 029 030 double tolerance = 0.00001; 031 032 public void setTolerance(double tolerance) { 033 this.tolerance = tolerance; 034 } 035 036 private int n; // number of warp parameters 037 private double qmag; // magnitude of parameter difference vector 038 private double sqrError; // squared sum of differences between I and R 039 040 private int margin = 0; // left and right margins to be ignored for matching 041 042 private int iteration; 043 044 045 public LucasKanadeMatcher1D(double[] I, double[] R) { 046 this.I = I; 047 this.R = R; 048 this.M = I.length; 049 this.N = R.length; 050 xc = 0.5 * M; 051 this.gI = makeGradient(I); 052 } 053 054 public double[] getBestMatch(double[] pInit) { 055 double[] p = pInit.clone(); 056 n = p.length; 057 iteration = 0; 058 qmag = Double.MAX_VALUE; 059 do { 060 p = iterateOnce(p); 061 } while (p != null && qmag > tolerance && iteration < maxIterations); 062 return p; 063 } 064 065 066 private double[] iterateOnce(double[] p) { 067 iteration = iteration + 1; 068 069 double[][] H = new double[n][n]; // n x n cumulated Hessian matrix, initialized to 0 070 double[] dp = new double[n]; // n-dim vector \delta_p = 0 071 sqrError = 0; 072 073 // for all positions u in R do 074 for (int u = margin; u < N - margin; u++) { 075 double x = u - xc; // position w.r.t. the center of R 076 double xT = T(p, x); // warp x -> x' 077 double g = interpolate(gI, xT + xc); // interpolated gradient in x-direction 078 double[] G = {g}; // degenerate (1D) gradient vector 079 080 // Step 4: Evaluate the Jacobian of the warp T at position x 081 double[][] J = getJacobianT(p, x); // a (1 x 2) matrix 082 083 // Step 5: compute the steepest descent image: 084 double[] sx = Matrix.multiply(G, J); // a 2-dim vector 085 086 // Step 6: Update the cumulative Hessian matrix 087 double[][] Hx = Matrix.outerProduct(sx, sx); 088 H = Matrix.add(H, Hx); 089 090 // Step 7: Calculate the error 091 double d = R[u] - interpolate(I, xT + xc); 092 sqrError = sqrError + d * d; 093 094 095 // Step 8: update the column vector dp: 096 dp = Matrix.add(dp, Matrix.multiply(d, sx)); 097 } 098 099 // IJ.log(String.format(" iteration=%d s=%.4f t=%.4f sqrError=%.3f", iteration, p[0]+1, p[1], sqrError)); 100 // Step 9: calculate the optimal parameter change: 101 102 double[] qopt = Matrix.solve(H, dp); 103 if (qopt == null) { 104 // IJ.log("singular Hessian!"); 105 return null; 106 } 107 108 qmag = Matrix.normL2squared(qopt); 109 110 // Step 10: Update and return the warp parameters p + qopt: 111 return Matrix.add(p, qopt); 112 } 113 114 private double[] makeGradient(double[] I) { 115 int M = I.length; // I is assumed 0 outside 116 double[] G = new double[M]; 117 G[0] = 0.5 * I[1]; 118 for (int i = 1; i < M - 1; i++) { 119 G[i] = 0.5 * (I[i+1] - I[i-1]); 120 } 121 G[M-1] = 0.5 * -I[M-2]; 122 return G; 123 } 124 125 private double T(double[] p, double u) { 126 final double s = p[0] + 1; 127 final double t = p[1]; 128 return s * u + t; 129 } 130 131 private double[][] getJacobianT(double[] p, double x) { 132// final double s = p[0]; 133// final double t = p[1]; 134 return new double[][] {{x, 1}}; // degenerate case: (1 x n) matrix 135 } 136 137 private double interpolate(double[] A, double x) { 138 int u0 = (int) Math.floor(x); 139 int u1 = u0 + 1; 140 double d = x - u0; 141 double a0 = (u0 >= 0 && u0 < A.length) ? A[u0] : 0; 142 double a1 = (u1 >= 0 && u1 < A.length) ? A[u1] : 0; 143 return (1 - d) * a0 + d * a1; 144 } 145 146 public double getRmsError() { 147 return Math.sqrt(sqrError); 148 } 149 150}