/*
* Copyright (C) 2009,2010,2011 Samuel Audet
*
* This file is part of JavaCV.
*
* JavaCV is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version (subject to the "Classpath" exception
* as provided in the LICENSE.txt file that accompanied this code).
*
* JavaCV is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JavaCV. If not, see <http://www.gnu.org/licenses/>.
*/
package com.googlecode.javacv;
import java.util.Arrays;
import com.googlecode.javacv.ImageTransformer.Data;
import com.googlecode.javacv.ImageTransformer.Parameters;
import com.googlecode.javacv.Parallel.Looper;
import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;
/**
*
* @author Samuel Audet
*/
public class GNImageAligner implements ImageAligner {
public GNImageAligner(ImageTransformer transformer, Parameters initialParameters,
IplImage template0, double[] roiPts, IplImage target0) {
this(transformer, initialParameters, template0, roiPts, target0, new Settings());
}
public GNImageAligner(ImageTransformer transformer, Parameters initialParameters,
IplImage template0, double[] roiPts, IplImage target0, Settings settings) {
setSettings(settings);
int n = initialParameters.size();
this.template = new IplImage[settings.pyramidLevels];
this.target = new IplImage[settings.pyramidLevels];
this.transformed = new IplImage[settings.pyramidLevels];
this.residual = new IplImage[settings.pyramidLevels];
this.roiMask = new IplImage[settings.pyramidLevels];
int w = template0.width();
int h = template0.height();
int c = template0.nChannels();
int o = template0.origin();
for (int i = 0; i < settings.pyramidLevels; i++) {
if (i == 0 && template0.depth() == IPL_DEPTH_32F) {
template[0] = template0;
} else {
template[i] = IplImage.create(w, h, IPL_DEPTH_32F, c, o);
}
if (i == 0 && target0.depth() == IPL_DEPTH_32F) {
target[0] = target0;
} else {
target[i] = IplImage.create(w, h, IPL_DEPTH_32F, c, o);
}
transformed[i] = IplImage.create(w, h, IPL_DEPTH_32F, c, o);
residual [i] = IplImage.create(w, h, IPL_DEPTH_32F, c, o);
roiMask [i] = IplImage.create(w, h, IPL_DEPTH_8U, 1, o);
w /= 2;
h /= 2;
}
this.srcRoiPts = CvMat.create(4, 1, CV_64F, 2);
this.dstRoiPts = CvMat.create(4, 1, CV_64F, 2);
this.dstRoiPtsArray = new CvPoint(4);
this.roi = new CvRect();
this.subroi = new CvRect[settings.numThreads];
for (int i = 0; i < subroi.length; i++) {
this.subroi[i] = new CvRect();
}
this.transformer = transformer;
this.hessianGradientTransformerData = new Data[settings.numThreads][n];
for (int i = 0; i < hessianGradientTransformerData.length; i++) {
for (int j = 0; j < hessianGradientTransformerData[i].length; j++) {
hessianGradientTransformerData[i][j] = new Data(template[pyramidLevel],
transformed[pyramidLevel], residual[pyramidLevel], roiMask[pyramidLevel],
0, 0, pyramidLevel, null, null, n);
}
}
this.residualTransformerData = new Data[settings.numThreads][1];
for (int i = 0; i < residualTransformerData.length; i++) {
residualTransformerData[i][0] = new Data(template[pyramidLevel],
target[pyramidLevel], null, roiMask[pyramidLevel],
0, 0, pyramidLevel, transformed[pyramidLevel], residual[pyramidLevel], 1);
}
this.parameters = initialParameters.clone();
this.parametersArray = new Parameters[] { parameters };
this.tempParameters = new Parameters[n];
for (int i = 0; i < tempParameters.length; i++) {
this.tempParameters[i] = initialParameters.clone();
}
subspaceParameters = parameters.getSubspace();
if (subspaceParameters != null) {
tempSubspaceParameters = new double[settings.numThreads][];
for (int i = 0; i < tempSubspaceParameters.length; i++) {
tempSubspaceParameters[i] = subspaceParameters.clone();
}
// for (double d : subspaceParameters) {
// System.out.print(d + " ");
// }
// System.out.println();
}
setConstrained(settings.constrained);
setTemplateImage(template0, roiPts);
setTargetImage(target0);
}
public static class Settings extends ImageAligner.Settings implements Cloneable {
public Settings() { }
public Settings(Settings s) {
super(s);
stepScale = s.stepScale;
lineSearch = s.lineSearch;
deltaMin = s.deltaMin;
deltaMax = s.deltaMax;
displacementMax = s.displacementMax;
subspaceAlpha = s.subspaceAlpha;
numThreads = s.numThreads;
}
double stepScale = 0.1;
double[] lineSearch = {1.0, 0.25};
double deltaMin = 10;
double deltaMax = 300;
double displacementMax = 0.15;
double subspaceAlpha = 0.1;
int numThreads = Parallel.numCores;
public double getStepScale() {
return stepScale;
}
public void setStepScale(double stepScale) {
this.stepScale = stepScale;
}
public double[] getLineSearch() {
return lineSearch;
}
public void setLineSearch(double[] lineSearch) {
this.lineSearch = lineSearch;
}
public double getDeltaMin() {
return deltaMin;
}
public void setDeltaMin(double deltaMin) {
this.deltaMin = deltaMin;
}
public double getDeltaMax() {
return deltaMax;
}
public void setDeltaMax(double deltaMax) {
this.deltaMax = deltaMax;
}
public double getDisplacementMax() {
return displacementMax;
}
public void setDisplacementMax(double displacementMax) {
this.displacementMax = displacementMax;
}
public double getSubspaceAlpha() {
return subspaceAlpha;
}
public void setSubspaceAlpha(double subspaceAlpha) {
this.subspaceAlpha = subspaceAlpha;
}
public int getNumThreads() {
return numThreads;
}
public void setNumThreads(int numThreads) {
this.numThreads = numThreads;
}
@Override public Settings clone() {
return new Settings(this);
}
}
private Settings settings;
public Settings getSettings() {
return settings;
}
public void setSettings(ImageAligner.Settings settings) {
this.settings = (Settings)settings;
}
private IplImage[] template, target, transformed, residual, roiMask;
private CvMat srcRoiPts, dstRoiPts;
private CvPoint dstRoiPtsArray;
private CvRect roi, subroi[];
private ImageTransformer transformer;
private Data[][] hessianGradientTransformerData, residualTransformerData;
private Parameters parameters, parametersArray[], tempParameters[], priorParameters;
private CvMat hessian, regularizedHessian, gradient, update, prior;
private double[] constraintGrad, subspaceResidual, subspaceJacobian[], updateScale;
private boolean[] subspaceCorrelated;
private int pyramidLevel;
private double RMSE;
private boolean residualUpdateNeeded = true;
private int lastLinePosition = 0;
private int trials = 0;
// private double prevOutlierRatio = 0;
public double[] subspaceParameters, tempSubspaceParameters[];
public IplImage getTemplateImage() {
return template[pyramidLevel];
}
public void setTemplateImage(IplImage template0, double[] roiPts) {
if (roiPts == null) {
this.srcRoiPts.put(0.0, 0.0, template0.width(), 0.0,
template0.width(), template0.height(), 0.0, template0.height());
} else {
this.srcRoiPts.put(roiPts);
}
if (template0.depth() == IPL_DEPTH_32F) {
template[0] = template0;
} else {
cvConvertScale(template0, template[0], 1.0/template0.highValue(), 0);
}
for (int i = 1; i < template.length; i++) {
cvPyrDown(template[i-1], template[i], CV_GAUSSIAN_5x5);
}
setPyramidLevel(template.length-1);
}
public IplImage getTargetImage() {
return target[pyramidLevel];
}
public void setTargetImage(IplImage target0) {
if (target0.depth() == IPL_DEPTH_32F) {
target[0] = target0;
}
if (settings.displacementMax > 0) {
setPyramidLevel(0);
doRoi(settings.displacementMax);
int align = 1<<target.length;
subroi[0].x(Math.max(0, (int)Math.floor((double)roi.x()/align)*align));
subroi[0].y(Math.max(0, (int)Math.floor((double)roi.y()/align)*align));
subroi[0].width (Math.min(target0.width(), (int)Math.ceil((double)roi.width() /align)*align));
subroi[0].height(Math.min(target0.height(), (int)Math.ceil((double)roi.height()/align)*align));
cvSetImageROI(target0, subroi[0]);
cvSetImageROI(target[0], subroi[0]);
} else {
cvResetImageROI(target0);
cvResetImageROI(target[0]);
}
if (target0.depth() != IPL_DEPTH_32F) {
cvConvertScale(target0, target[0], 1.0/target0.highValue(), 0);
cvResetImageROI(target0);
}
for (int i = 1; i < target.length; i++) {
IplROI ir = target[i-1].roi();
if (ir != null) {
subroi[0].x(ir.xOffset()/2); subroi[0].width (ir.width() /2);
subroi[0].y(ir.yOffset()/2); subroi[0].height(ir.height()/2);
cvSetImageROI(target[i], subroi[0]);
} else {
cvResetImageROI(target[i]);
}
cvPyrDown(target[i-1], target[i], CV_GAUSSIAN_5x5);
}
setPyramidLevel(target.length-1);
}
public int getPyramidLevel() {
return pyramidLevel;
}
public void setPyramidLevel(int pyramidLevel) {
this.pyramidLevel = pyramidLevel;
residualUpdateNeeded = true;
trials = 0;
}
public boolean isConstrained() {
return settings.constrained;
}
public void setConstrained(boolean constrained) {
if (settings.constrained == constrained && hessian != null &&
regularizedHessian != null && gradient != null && update != null) {
return;
}
settings.constrained = constrained;
int n = parameters.size();
int m = constrained ? n+1 : n;
if (subspaceParameters != null && settings.subspaceAlpha != 0.0) {
m += subspaceParameters.length;
}
hessian = CvMat.create(m, m);
regularizedHessian = CvMat.create(m, m);
gradient = CvMat.create(m, 1);
update = CvMat.create(m, 1);
updateScale = new double[m];
prior = CvMat.create(n, 1);
constraintGrad = new double[n];
subspaceResidual = new double[n];
subspaceJacobian = new double[m][n];
subspaceCorrelated = new boolean[n];
}
public Parameters getParameters() {
return parameters;
}
public void setParameters(Parameters parameters) {
this.parameters.set(parameters);
subspaceParameters = parameters.getSubspace();
if (subspaceParameters != null && settings.subspaceAlpha != 0.0) {
for (int i = 0; i < tempSubspaceParameters.length; i++) {
tempSubspaceParameters[i] = subspaceParameters.clone();
}
}
residualUpdateNeeded = true;
}
public Parameters getPriorParameters() {
return priorParameters;
}
public void setPriorParameters(Parameters priorParameters) {
this.priorParameters.set(priorParameters);
}
public double[] getTransformedRoiPts() {
return dstRoiPts.get();
}
public IplImage getTransformedImage() {
if (residualUpdateNeeded) {
doRoi();
doResidual();
}
return transformed[pyramidLevel];
}
public IplImage getResidualImage() {
if (residualUpdateNeeded) {
doRoi();
doResidual();
}
return residual[pyramidLevel];
}
public IplImage getRoiMaskImage() {
return roiMask[pyramidLevel];
}
public double getRMSE() {
if (residualUpdateNeeded) {
doRoi();
doResidual();
}
return RMSE;
}
public int getPixelCount() {
int dstCount = 0;
for (Data[] data : residualTransformerData) {
dstCount += data[0].dstCount;
}
return dstCount;
}
public CvRect getRoi() {
if (residualUpdateNeeded) {
doRoi();
}
return roi;
}
public int getLastLinePosition() {
return lastLinePosition;
}
public boolean iterate(double[] delta) {
boolean converged = false;
final int n = parameters.size();
final double prevRMSE = getRMSE();
final double[] prevParameters = parameters.get();
final double[] prevSubspaceParameters = subspaceParameters == null ? null : subspaceParameters.clone();
if (trials == 0 && parameters.preoptimize()) {
setParameters(parameters);
doResidual();
}
final double[] resetParameters = parameters.get();
final double[] resetSubspaceParameters = subspaceParameters == null ? null : subspaceParameters.clone();
doHessianGradient(updateScale);
// add Tikhonov regularization
int rows = hessian.rows(), cols = hessian.cols();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
double h = hessian.get(i, j);
double g = 0;
if (settings.gammaTgamma != null && i < settings.gammaTgamma.rows() && j < settings.gammaTgamma.cols()) {
g = settings.gammaTgamma.get(i, j);
}
double a = 0;
if (i == j && i < n) {
a = settings.tikhonovAlpha * settings.tikhonovAlpha;
}
regularizedHessian.put(i, j, h + g + a);
}
}
lastLinePosition = 0;
// solve for optimal parameter update
cvSolve(regularizedHessian, gradient, update, CV_SVD);
for (int i = 0; i < n; i++) {
parameters.set(i, parameters.get(i) + settings.lineSearch[0]*update.get(i)*updateScale[i]);
}
for (int i = n; i < update.length(); i++) {
subspaceParameters[i-n] += settings.lineSearch[0]*update.get(i)*updateScale[i];
}
residualUpdateNeeded = true;
for (int j = 1; j < settings.lineSearch.length && getRMSE() > prevRMSE; j++) {
RMSE = prevRMSE;
parameters.set(resetParameters);
if (subspaceParameters != null) {
System.arraycopy(resetSubspaceParameters, 0, subspaceParameters, 0, subspaceParameters.length);
}
lastLinePosition = j;
for (int i = 0; i < n; i++) {
parameters.set(i, parameters.get(i) + settings.lineSearch[j]*update.get(i)*updateScale[i]);
}
for (int i = n; i < update.length(); i++) {
subspaceParameters[i-n] += settings.lineSearch[j]*update.get(i)*updateScale[i];
}
residualUpdateNeeded = true;
}
double deltaNorm = 0;
if (delta != null) {
for (int i = 0; i < delta.length && i < updateScale.length; i++) {
delta[i] = settings.lineSearch[lastLinePosition]*update.get(i)*updateScale[i];
}
deltaNorm = JavaCV.norm(Arrays.copyOf(delta, n));
}
boolean invalid = getRMSE() > prevRMSE || deltaNorm > settings.deltaMax ||
Double.isNaN(RMSE) || Double.isInfinite(RMSE);
if (invalid) {
RMSE = prevRMSE;
parameters.set(prevParameters);
if (subspaceParameters != null) {
System.arraycopy(prevSubspaceParameters, 0, subspaceParameters, 0, subspaceParameters.length);
}
residualUpdateNeeded = true;
}
if (invalid && deltaNorm > settings.deltaMin && ++trials < 2) {
return false;
} else if (invalid || deltaNorm < settings.deltaMin) {
trials = 0;
if (pyramidLevel > 0) {
setPyramidLevel(pyramidLevel-1);
} else {
converged = true;
}
} else {
trials = 0;
}
//long residualTime = System.currentTimeMillis();
//accTime += (residualTime-start);
//System.out.println("gradientHessianTime = "+ (gradientHessianTime-start) +
// " solveTime = " + (solveTime-gradientHessianTime) +
// " residualTime = " + (residualTime-solveTime) +
// " totalTime = " + (residualTime-start) +
// " accTime = " + accTime);
return converged;
}
private void doHessianGradient(final double[] scale) {
final int n = parameters.size();
final double constraintError = parameters.getConstraintError();
final double stepScale = settings.stepScale;
cvSetZero(gradient);
cvSetZero(hessian);
Parallel.loop(0, n, settings.numThreads, new Looper() {
public void loop(int from, int to, int looperID) {
// for (int i = 0; i < n; i++) {
for (int i = from; i < to; i++) {
tempParameters[i].set(parameters);
tempParameters[i].set(i, tempParameters[i].get(i) + /*(1<<pyramidLevel)**/stepScale);
scale[i] = tempParameters[i].get(i) - parameters.get(i);
constraintGrad[i] = tempParameters[i].getConstraintError() - constraintError;
}}});
// final double adjustedRMSE = (1-prevOutlierRatio)*RMSE;
Parallel.loop(0, hessianGradientTransformerData.length, settings.numThreads, new Looper() {
public void loop(int from, int to, int looperID) {
for (int i = 0; i < n; i++) {
Data d = hessianGradientTransformerData[looperID][i];
d.srcImg = template [pyramidLevel];
d.subImg = transformed[pyramidLevel];
d.srcDotImg = residual [pyramidLevel];
d.transImg = d.dstImg = null;
d.mask = roiMask[pyramidLevel];
d.zeroThreshold = /*adjustedRMSE**/settings.zeroThresholds [Math.min(settings.zeroThresholds .length-1, pyramidLevel)];
d.outlierThreshold = /*adjustedRMSE**/settings.outlierThresholds[Math.min(settings.outlierThresholds.length-1, pyramidLevel)];
d.pyramidLevel = pyramidLevel;
}
int y1 = roi.y() + looperID *roi.height()/hessianGradientTransformerData.length;
int y2 = roi.y() + (looperID+1)*roi.height()/hessianGradientTransformerData.length;
subroi[looperID].x (roi.x());
subroi[looperID].y (y1);
subroi[looperID].width (roi.width());
subroi[looperID].height(y2-y1);
transformer.transform(hessianGradientTransformerData[looperID], subroi[looperID], tempParameters, null);
}});
double dstCount = 0;
double dstCountZero = 0;
double dstCountOutlier = 0;
for (Data[] data : hessianGradientTransformerData) {
dstCount += data[0].dstCount;
dstCountZero += data[0].dstCountZero;
dstCountOutlier += data[0].dstCountOutlier;
for (int i = 0; i < n; i++) {
Data d = (Data)data[i];
gradient.put(i, gradient.get(i) - d.srcDstDot);
for (int j = 0; j < n; j++) {
hessian.put(i, j, hessian.get(i, j) + d.dstDstDot[j]);
}
}
}
// prevOutlierRatio = dstCountOutlier/dstCount;
//System.out.println(dstCountZero/dstCount + " " + dstCountOutlier/dstCount);
// if we have a gamma of an alpha, compute the prior for regularization, but
// if prioParameters == null, our prior is zero motion, so no need to compute it
if ((settings.gammaTgamma != null || settings.tikhonovAlpha != 0) &&
prior != null && priorParameters != null) {
for (int i = 0; i < n; i++) {
prior.put(i, parameters.get(i) - priorParameters.get(i));
}
cvMatMul(hessian, prior, prior);
// compute gradient
for (int i = 0; i < n; i++) {
gradient.put(i, gradient.get(i) + prior.get(i));
}
}
//System.out.println(prior);
if (settings.constrained) {
// to get a well-conditionned matrix, compute what
// looks like an appropriate scale for the constraint
double constraintGradSum = 0;
for (double d : constraintGrad) {
constraintGradSum += d;
}
scale[n] = n*constraintGradSum;
for (int i = 0; i < n; i++) {
double c = constraintGrad[i]*scale[n];
hessian.put(i, n, c);
hessian.put(n, i, c);
}
gradient.put(n, -constraintError*scale[n]);
}
if (subspaceParameters != null && subspaceParameters.length > 0 &&
settings.subspaceAlpha != 0.0) {
final int m = subspaceParameters.length;
// double[][] subspaceHessian = new double[n+m][n+m];
// double[] subspaceGradient = new double[n+m];
Arrays.fill(subspaceCorrelated, false);
tempParameters[0].set(parameters);
tempParameters[0].setSubspace(subspaceParameters);
Parallel.loop(0, n+m, settings.numThreads, new Looper() {
public void loop(int from, int to, int looperID) {
// int looperID = 0;
// for (int i = 0; i < n+m; i++) {
for (int i = from; i < to; i++) {
if (i < n) {
Arrays.fill(subspaceJacobian[i], 0);
subspaceJacobian[i][i] = scale[i];
} else {
System.arraycopy(subspaceParameters, 0, tempSubspaceParameters[looperID], 0, m);
tempSubspaceParameters[looperID][i-n] += stepScale;
tempParameters[i-n+1].set(parameters);
tempParameters[i-n+1].setSubspace(tempSubspaceParameters[looperID]);
scale[i] = tempSubspaceParameters[looperID][i-n] - subspaceParameters[i-n];
for (int j = 0; j < n; j++) {
subspaceJacobian[i][j] = tempParameters[0].get(j) - tempParameters[i-n+1].get(j);
subspaceCorrelated[j] |= subspaceJacobian[i][j] != 0; // this may not work in parallel...
}
}
}}});
int subspaceCorrelatedCount = 0;
// double subspaceRMSE = 0;
for (int i = 0; i < n; i++) {
subspaceResidual[i] = parameters.get(i) - tempParameters[0].get(i);
// subspaceRMSE += subspaceResidual[i]*subspaceResidual[i];
if (subspaceCorrelated[i]) {
subspaceCorrelatedCount++;
}
}
// subspaceRMSE = Math.sqrt(subspaceRMSE/n);
//System.out.println((float)RMSE + " " + (float)subspaceRMSE);
final double K = settings.subspaceAlpha*settings.subspaceAlpha * RMSE*RMSE/
subspaceCorrelatedCount;//(subspaceRMSE*subspaceRMSE);
Parallel.loop(0, n+m, settings.numThreads, new Looper() {
public void loop(int from, int to, int looperID) {
// int looperID = 0;
// for (int i = 0; i < n+m; i++) {
for (int i = from; i < to; i++) {
if (i < n && !subspaceCorrelated[i]) {
continue;
}
for (int j = i; j < n+m; j++) {
if (j < n && !subspaceCorrelated[j]) {
continue;
}
double h = 0;
for (int k = 0; k < n; k++) {
h += subspaceJacobian[i][k]*subspaceJacobian[j][k];
}
// subspaceHessian[i][j] = h;
h = hessian.get(i, j) + K*h;
hessian.put(i, j, h);
hessian.put(j, i, h);
}
double g = 0;
for (int k = 0; k < n; k++) {
g -= subspaceJacobian[i][k]*subspaceResidual[k];
}
// subspaceGradient[i] = g;
g = gradient.get(i) + K*g;
gradient.put(i, g);
}}});
}
}
private void doRoi() {
doRoi(0);
}
private void doRoi(double extraPadding) {
transformer.transform(srcRoiPts, dstRoiPts, parameters, false);
double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE,
minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE;
for (int i = 0; i < dstRoiPts.length(); i++) {
double x = dstRoiPts.get(2*i )/(1<<pyramidLevel);
double y = dstRoiPts.get(2*i+1)/(1<<pyramidLevel);
dstRoiPts.put(2*i , x);
dstRoiPts.put(2*i+1, y);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
cvSetZero(roiMask[pyramidLevel]);
dstRoiPtsArray.fill((byte)16, dstRoiPts.get());
cvFillConvexPoly(roiMask[pyramidLevel], dstRoiPtsArray, 4, CvScalar.WHITE, 8, 16);
// add +3 all around because cvWarpPerspective() needs it apparently
minX = Math.max(0, minX-3-(maxX-minX)*extraPadding);
minY = Math.max(0, minY-3-(maxY-minY)*extraPadding);
maxX = Math.min(roiMask[pyramidLevel].width(), maxX+3+(maxX-minX)*extraPadding);
maxY = Math.min(roiMask[pyramidLevel].height(), maxY+3+(maxY-minY)*extraPadding);
// there seems to be something funny with memory alignment and
// ROIs, so let's align our ROI to a 16 byte boundary just in case..
roi.x(Math.max(0, (int)Math.floor(minX/16)*16));
roi.y(Math.max(0, (int)Math.floor(minY)));
roi.width (Math.min(roiMask[pyramidLevel].width(), (int)Math.ceil(maxX/16)*16) - roi.x());
roi.height(Math.min(roiMask[pyramidLevel].height(), (int)Math.ceil(maxY)) - roi.y());
// roi.x = 0;
// roi.y = 0;
// roi.width = roiMask[pyramidLevel].width;
// roi.height = roiMask[pyramidLevel].height;
//System.out.println(roi);
}
private void doResidual() {
parameters.getConstraintError();
// cvSetZero(transformed[pyramidLevel]);
// cvSetZero(residual [pyramidLevel]);
Parallel.loop(0, residualTransformerData.length, settings.numThreads, new Looper() {
public void loop(int from, int to, int looperID) {
Data d = residualTransformerData[looperID][0];
d.srcImg = template [pyramidLevel];
d.subImg = target [pyramidLevel];
d.srcDotImg = null;
d.transImg = transformed[pyramidLevel];
d.dstImg = residual [pyramidLevel];
d.mask = roiMask[pyramidLevel];
d.zeroThreshold = 0;
d.outlierThreshold = 0;
d.pyramidLevel = pyramidLevel;
int y1 = roi.y() + looperID *roi.height()/residualTransformerData.length;
int y2 = roi.y() + (looperID+1)*roi.height()/residualTransformerData.length;
subroi[looperID].x (roi.x());
subroi[looperID].y (y1);
subroi[looperID].width (roi.width());
subroi[looperID].height(y2-y1);
transformer.transform(residualTransformerData[looperID], subroi[looperID], parametersArray, null);
}});
double dstDstDot = 0, dstCount = 0;
for (Data[] data : residualTransformerData) {
dstDstDot += data[0].dstDstDot[0];
dstCount += data[0].dstCount;
}
if (dstCount < parameters.size()) {
RMSE = Double.NaN;
} else {
RMSE = Math.sqrt(dstDstDot/dstCount);
// RMSE = dstDstDot/dstCount;
}
// if (Double.isNaN(RMSE)) {
//System.out.println("dstCount " + dstCount + " RMSE " + RMSE + " " + pyramidLevel);
// }
residualUpdateNeeded = false;
}
}