public static void evaluate(String dataset) { Class type = ImageFloat32.class; DebugTldTrackerTldData generator = new DebugTldTrackerTldData(ImageType.single(type)); InterpolatePixelS interpolate = FactoryInterpolation.bilinearPixelS(type, BorderType.EXTENDED); ImageGradient gradient = FactoryDerivative.sobel(type, type); TldTracker tracker = new TldTracker(null, interpolate, gradient, type, type); generator.evaluate(dataset, tracker); }
/** @author Peter Abeles */ public class TestCirculantTracker { Random rand = new Random(234); int width = 60; int height = 80; InterpolatePixelS<ImageFloat32> interp = FactoryInterpolation.bilinearPixelS(ImageFloat32.class); @Test public void meanShift() { int w = 32; CirculantTracker<ImageFloat32> alg = new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, w, 255, interp); int peakX = 13; int peakY = 17; alg.getResponse().reshape(w, w); for (int i = 0; i < w; i++) { double b = Math.exp(-(i - peakY) * (i - peakY) / 3.0); for (int j = 0; j < w; j++) { double a = Math.exp(-(j - peakX) * (j - peakX) / 3.0); alg.getResponse().set(j, i, a * b); } } alg.subpixelPeak(peakX - 2, peakY + 1); assertEquals(2, alg.offX, 0.3); assertEquals(-1, alg.offY, 0.3); } @Test public void basicTrackingCheck() { ImageFloat32 a = new ImageFloat32(30, 35); ImageFloat32 b = new ImageFloat32(30, 35); // randomize input image and move it GImageMiscOps.fillUniform(a, rand, 0, 200); GImageMiscOps.fillUniform(b, rand, 0, 200); CirculantTracker<ImageFloat32> alg = new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 64, 255, interp); alg.initialize(a, 5, 6, 20, 25); shiftCopy(2, 4, a, b); alg.performTracking(b); double tolerance = 1; Rectangle2D_F32 r = alg.getTargetLocation(); assertEquals(5 + 2, r.x0, tolerance); assertEquals(6 + 4, r.y0, tolerance); } @Test public void computeCosineWindow() { ImageFloat64 found = new ImageFloat64(20, 25); CirculantTracker.computeCosineWindow(found); // should be between 0 and 1 for (int i = 0; i < found.data.length; i++) { assertTrue(found.data[i] >= 0 && found.data[i] <= 1); } centeredSymmetricChecks(found, false); } @Test public void computeGaussianWeights() { int w = 16; CirculantTracker<ImageFloat32> alg = new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, w, 255, interp); alg.gaussianWeight.reshape(w, w); alg.gaussianWeightDFT.reshape(w, w); alg.computeGaussianWeights(w); centeredSymmetricChecks(alg.gaussianWeight, true); } private void centeredSymmetricChecks(ImageFloat64 image, boolean offByOne) { // see comments in computeGaussianWeights int offX = offByOne ? 1 - image.width % 2 : 0; int offY = offByOne ? 1 - image.height % 2 : 0; int cx = image.width / 2; int cy = image.height / 2; int w = image.width - 1; int h = image.height - 1; // edges should be smaller than center assertTrue(image.get(cx, cy) > image.get(0, 0)); assertTrue(image.get(cx, cy) > image.get(w, h)); assertTrue(image.get(cx, cy) > image.get(w, h)); assertTrue(image.get(cx, cy) > image.get(w, 0)); // symmetry check for (int i = offY; i < cy; i++) { for (int j = offX; j < cx; j++) { double v0 = image.get(j, i); double v1 = image.get(w - j + offX, i); double v2 = image.get(j, h - i + offY); double v3 = image.get(w - j + offX, h - i + offY); assertEquals(v0, v1, 1e-4); assertEquals(v0, v2, 1e-4); assertEquals(i + " " + j, v0, v3, 1e-4); } } } /** * Check a few simple motions. It seems to be accurate to within 1 pixel. Considering alphas seems * to be the issue */ @Test public void updateTrackLocation() { ImageFloat32 a = new ImageFloat32(100, 100); ImageFloat32 b = new ImageFloat32(100, 100); // randomize input image and move it GImageMiscOps.fillUniform(a, rand, 0, 200); GImageMiscOps.fillUniform(b, rand, 0, 200); shiftCopy(0, 0, a, b); CirculantTracker<ImageFloat32> alg = new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 64, 255, interp); alg.initialize(a, 5, 6, 20, 25); alg.updateTrackLocation(b); // only pixel level precision. float tolerance = 1f; // No motion motion Rectangle2D_F32 r = alg.getTargetLocation(); assertEquals(5, r.x0, tolerance); assertEquals(6, r.y0, tolerance); // check estimated motion GImageMiscOps.fillUniform(b, rand, 0, 200); shiftCopy(-3, 2, a, b); alg.updateTrackLocation(b); r = alg.getTargetLocation(); assertEquals(5 - 3, r.x0, tolerance); assertEquals(6 + 2, r.y0, tolerance); // try out of bounds case GImageMiscOps.fillUniform(b, rand, 0, 200); shiftCopy(-6, 0, a, b); alg.updateTrackLocation(b); assertEquals(5 - 6, r.x0, tolerance); assertEquals(6, r.y0, tolerance); } @Test public void performLearning() { float interp_factor = 0.075f; ImageFloat32 a = new ImageFloat32(20, 25); ImageFloat32 b = new ImageFloat32(20, 25); ImageMiscOps.fill(a, 100); ImageMiscOps.fill(b, 200); CirculantTracker<ImageFloat32> alg = new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 64, 255, interp); alg.initialize(a, 0, 0, 20, 25); // copy its internal value ImageFloat64 templateC = new ImageFloat64(alg.template.width, alg.template.height); templateC.setTo(alg.template); // give it two images alg.performLearning(b); // make sure the images aren't full of zero assertTrue(Math.abs(ImageStatistics.sum(templateC)) > 0.1); assertTrue(Math.abs(ImageStatistics.sum(alg.template)) > 0.1); int numNotSame = 0; // the result should be an average of the two for (int i = 0; i < a.data.length; i++) { if (Math.abs(a.data[i] - alg.templateNew.data[i]) > 1e-4) numNotSame++; // should be more like the original one than the new one double expected = templateC.data[i] * (1 - interp_factor) + interp_factor * alg.templateNew.data[i]; double found = alg.template.data[i]; assertEquals(expected, found, 1e-4); } // make sure it is actually different assertTrue(numNotSame > 100); } @Test public void dense_gauss_kernel() { // try several different shifts dense_gauss_kernel(0, 0); dense_gauss_kernel(5, 0); dense_gauss_kernel(0, 5); dense_gauss_kernel(-3, -2); } public void dense_gauss_kernel(int offX, int offY) { ImageFloat64 region = new ImageFloat64(32, 32); ImageFloat64 target = new ImageFloat64(32, 32); ImageFloat64 k = new ImageFloat64(32, 32); CirculantTracker<ImageFloat32> alg = new CirculantTracker<ImageFloat32>(1f / 16, 0.2, 1e-2, 0.075, 1.0, 32, 255, interp); alg.initialize(new ImageFloat32(32, 32), 0, 0, 32, 32); // create a shape inside the image GImageMiscOps.fillRectangle(region, 200, 10, 15, 5, 7); // copy a shifted portion of the region shiftCopy(offX, offY, region, target); // process and see if the peak is where it should be alg.dense_gauss_kernel(0.2f, region, target, k); int maxX = -1, maxY = -1; double maxValue = -1; for (int y = 0; y < k.height; y++) { for (int x = 0; x < k.width; x++) { if (k.get(x, y) > maxValue) { maxValue = k.get(x, y); maxX = x; maxY = y; } } } int expectedX = k.width / 2 - offX; int expectedY = k.height / 2 - offY; assertEquals(expectedX, maxX); assertEquals(expectedY, maxY); } private void shiftCopy(int offX, int offY, ImageFloat32 src, ImageFloat32 dst) { for (int y = 0; y < src.height; y++) { for (int x = 0; x < src.width; x++) { int xx = x + offX; int yy = y + offY; if (xx >= 0 && xx < src.width && yy >= 0 && yy < src.height) { dst.set(xx, yy, src.get(x, y)); } } } } private void shiftCopy(int offX, int offY, ImageFloat64 src, ImageFloat64 dst) { for (int y = 0; y < src.height; y++) { for (int x = 0; x < src.width; x++) { int xx = x + offX; int yy = y + offY; if (xx >= 0 && xx < src.width && yy >= 0 && yy < src.height) { dst.set(xx, yy, src.get(x, y)); } } } } @Test public void imageDotProduct() { ImageFloat64 a = new ImageFloat64(width, height); ImageMiscOps.fillUniform(a, rand, 0, 10); double total = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { total += a.get(x, y) * a.get(x, y); } } double found = CirculantTracker.imageDotProduct(a); assertEquals(total, found, 1e-8); } @Test public void elementMultConjB() { InterleavedF64 a = new InterleavedF64(width, height, 2); InterleavedF64 b = new InterleavedF64(width, height, 2); InterleavedF64 c = new InterleavedF64(width, height, 2); ImageMiscOps.fillUniform(a, rand, -10, 10); ImageMiscOps.fillUniform(b, rand, -10, 10); ImageMiscOps.fillUniform(c, rand, -10, 10); CirculantTracker.elementMultConjB(a, b, c); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Complex64F aa = new Complex64F(a.getBand(x, y, 0), a.getBand(x, y, 1)); Complex64F bb = new Complex64F(b.getBand(x, y, 0), b.getBand(x, y, 1)); Complex64F cc = new Complex64F(); ComplexMath64F.conj(bb, bb); ComplexMath64F.mult(aa, bb, cc); double foundReal = c.getBand(x, y, 0); double foundImg = c.getBand(x, y, 1); assertEquals(cc.real, foundReal, 1e-4); assertEquals(cc.imaginary, foundImg, 1e-4); } } } @Test public void computeAlphas() { InterleavedF64 yf = new InterleavedF64(width, height, 2); InterleavedF64 kf = new InterleavedF64(width, height, 2); InterleavedF64 alphaf = new InterleavedF64(width, height, 2); ImageMiscOps.fillUniform(yf, rand, -10, 10); ImageMiscOps.fillUniform(kf, rand, -10, 10); ImageMiscOps.fillUniform(alphaf, rand, -10, 10); float lambda = 0.01f; CirculantTracker.computeAlphas(yf, kf, lambda, alphaf); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Complex64F a = new Complex64F(yf.getBand(x, y, 0), yf.getBand(x, y, 1)); Complex64F b = new Complex64F(kf.getBand(x, y, 0) + lambda, kf.getBand(x, y, 1)); Complex64F c = new Complex64F(); ComplexMath64F.div(a, b, c); double foundReal = alphaf.getBand(x, y, 0); double foundImg = alphaf.getBand(x, y, 1); assertEquals(c.real, foundReal, 1e-4); assertEquals(c.imaginary, foundImg, 1e-4); } } } }
/** * Create a standard image pyramid used by dense optical flow parameters. The first layer is the * size of the input image and the last layer is ≥ the minSize. The sigma for each layer is * computed using the following formula:<br> * <br> * sigmaLayer = sigma*sqrt( scale^-2 - 1 ) * * <p>If the scale is 1 then a single layer pyramid will be created. If the scale is 0 then the * scale will be determined by the maxLayers parameter. * * @param width Width of input image. * @param height Height of input image. * @param scale Scale between layers. 0 ≤ scale ≤ 1. Try 0.7 * @param sigma Adjusts the amount of blur applied to each layer. If sigma ≤ 0 then no blur is * applied. * @param minSize The minimum desired image size in the pyramid * @param maxLayers The maximum number of layers in the pyramid. * @param imageType Type of image for each layer * @param <T> Image type * @return The image pyramid. */ public static <T extends ImageSingleBand> PyramidFloat<T> standardPyramid( int width, int height, double scale, double sigma, int minSize, int maxLayers, Class<T> imageType) { if (scale > 1.0 || scale < 0) throw new IllegalArgumentException("Scale must be 0 <= scale <= 1"); int numScales; if (scale == 1 || maxLayers == 1) { numScales = 1; } else if (scale == 0) { numScales = maxLayers; double desiredReduction = minSize / (double) Math.min(width, height); scale = Math.pow(desiredReduction, 1.0 / (numScales - 1)); } else { // this is how much the input image needs to be shrunk double desiredReduction = minSize / (double) Math.min(width, height); // number the number of frames needed and round to the nearest integer numScales = (int) (Math.log(desiredReduction) / Math.log(scale) + 0.5); if (numScales > maxLayers) numScales = maxLayers; // compute a new scale factor using this number of scales scale = Math.pow(desiredReduction, 1.0 / numScales); // add one since the first scale is going to be the original image numScales++; } InterpolatePixelS<T> interp = FactoryInterpolation.bilinearPixelS(imageType, BorderType.EXTENDED); if (sigma > 0) { double layerSigma = sigma * Math.sqrt(Math.pow(scale, -2) - 1); double scaleFactors[] = new double[numScales]; double scaleSigmas[] = new double[numScales]; scaleFactors[0] = 1; scaleSigmas[0] = layerSigma; for (int i = 1; i < numScales; i++) { scaleFactors[i] = scaleFactors[i - 1] / scale; scaleSigmas[i] = layerSigma; } return new PyramidFloatGaussianScale<T>(interp, scaleFactors, scaleSigmas, imageType); } else { double scaleFactors[] = new double[numScales]; scaleFactors[0] = 1; for (int i = 1; i < numScales; i++) { scaleFactors[i] = scaleFactors[i - 1] / scale; } return new PyramidFloatScale<T>(interp, scaleFactors, imageType); } }