/* Class to recognize a single-stroke glyph from a library of glyphs. - Ken Perlin Algorithm: Uniformly transform to fill middle of unit square. Reparameterize X coordinates by stroke length. Reparameterize Y coordinates by stroke length. Resample both X and Y into N samples (eg: N = 20). Do least squares comparison against library which is in same form. Choose candidate with lowest sum of X error and Y error. Speedup: Initially subsample with small N (eg: N = 8) to rapidly eliminate all but smallest K library candidates (eg: K = 8). Then final K candidates compete using larger N (eg: N = 32). */ import java.util.*; public class Recognizer extends Vector { int N = 10; double d[]; public Recognizer() { this(10); } public Recognizer(int N) { this.N = N; d = new double[2*N]; } public double[] add(int xy[], int n, Object obj) { double objData[] = new double[2*N]; normalize(xy, n/2, objData); addElement(objData); addElement(obj); return objData; } public Object recognize(int xy[], int n) { normalize(xy, n/2, d); double errMin = 10000, err, t; int iMin = -1; for (int i = 0 ; i < size() ; i += 2) { err = 0; double data[] = (double[])elementAt(i); for (int j = 0 ; j < d.length ; j++) { t = d[j] - data[j]; err += t * t; } if (err < errMin) { errMin = err; iMin = i; } } return iMin == -1 ? null : elementAt(iMin+1); } public double[] getPattern(Object obj) { for (int i = 0 ; i < size() ; i += 2) if ( ((String)obj).equals( (String)elementAt(i+1) ) ) return (double[])elementAt(i); return null; } public static void normalize(int xy[], int n, double data[]) { int N = data.length / 2; // COPY INPUT DATA AND COMPUTE LENGTH-BASED PARAMETER T double X[] = new double[n]; double Y[] = new double[n]; double T[] = new double[n]; for (int i = 0 ; i < n ; i++) { X[i] = xy[2*i]; Y[i] = xy[2*i+1]; if (i > 0) { double dx = X[i] - X[i-1]; double dy = Y[i] - Y[i-1]; T[i] = T[i-1] + Math.sqrt(dx * dx + dy * dy); } } for (int i = 0 ; i < n ; i++) T[i] = T[i] / T[n-1]; // NORMALIZE TO FIT INTO A UNIT SQUARE double xlo = 10000, xhi = -10000; double ylo = 10000, yhi = -10000; for (int i = 0 ; i < X.length ; i++) { xlo = Math.min(xlo, X[i]); xhi = Math.max(xhi, X[i]); ylo = Math.min(ylo, Y[i]); yhi = Math.max(yhi, Y[i]); } if (xhi - xlo > yhi - ylo) fitIntoSquare(X,Y, xlo, xhi, ylo, yhi); // WIDE STROKE: UNIT WIDTH else fitIntoSquare(Y,X, ylo, yhi, xlo, xhi); // TALL STROKE: UNIT HEIGHT // RESAMPLE X AND Y TO N SAMPLES for (int i = 0 ; i < N ; i++) { double t = (i-.001) / (N-1); data[i ] = sample(X, T, t); data[i+N] = sample(Y, T, t); } } static void fitIntoSquare(double U[], double V[], double ulo, double uhi, double vlo, double vhi) { double du = uhi - ulo; double dv = vhi - vlo; double v0 = .5 * (1 - dv / du); for (int i = 0 ; i < U.length ; i++) { U[i] = (U[i] - ulo) / du; V[i] = (V[i] - vlo) / du + v0; } } static double sample(double U[], double T[], double t) { for (int i = 1 ; i < U.length ; i++) if (T[i-1] <= t && T[i] > t) return U[i-1] + (t - T[i-1]) / (T[i] - T[i-1]) * (U[i] - U[i-1]); return U[0]; } }