/*
* Copyright (C) 2009-2012 Samuel Audet
*
* Licensed either under the Apache License, Version 2.0, or (at your option)
* under the terms of the GNU General Public License as published by
* the Free Software Foundation (subject to the "Classpath" exception),
* either version 2, or any later version (collectively, the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.gnu.org/licenses/
* http://www.gnu.org/software/classpath/license.html
*
* or as provided in the LICENSE.txt file that accompanied this code.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bytedeco.javacv;
import static org.bytedeco.javacpp.opencv_calib3d.*;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;
import static org.bytedeco.javacv.cvkernels.*;
/**
*
* @author Samuel Audet
*/
public class ProjectiveTransformer implements ImageTransformer {
public ProjectiveTransformer() {
this(null, null, null, null, null, new double[0], null);
}
public ProjectiveTransformer(double[] referencePoints) {
this(null, null, null, null, null, referencePoints, null);
}
public ProjectiveTransformer(ProjectiveDevice d1, ProjectiveDevice d2, CvMat n,
double[] referencePoints1, double[] referencePoints2) {
// assuming d1 has identity values, use d2's stuff directly
this(d1.cameraMatrix, d2.cameraMatrix, d2.R, d2.T, n, referencePoints1, referencePoints2);
}
public ProjectiveTransformer(CvMat K1, CvMat K2, CvMat R, CvMat t, CvMat n,
double[] referencePoints1, double[] referencePoints2) {
this.K1 = K1 == null ? null : K1.clone();
this.K2 = K2 == null ? null : K2.clone();
this.invK1 = K1 == null ? null : K1.clone();
this.invK2 = K2 == null ? null : K2.clone();
if (K1 != null) {
cvInvert(K1, invK1);
}
if (K2 != null) {
cvInvert(K2, invK2);
}
this.R = R == null ? null : R.clone();
this.t = t == null ? null : t.clone();
this.n = n == null ? null : n.clone();
this.referencePoints1 = referencePoints1 == null ? null : referencePoints1.clone();
this.referencePoints2 = referencePoints2 == null ? null : referencePoints2.clone();
}
protected static ThreadLocal<CvMat>
H3x3 = CvMat.createThreadLocal(3, 3),
pts4x1 = CvMat.createThreadLocal(4, 1, CV_64F, 2);
protected CvMat K1 = null, K2 = null, invK1 = null, invK2 = null, R = null, t = null, n = null;
protected double[] referencePoints1 = null, referencePoints2 = null;
protected CvScalar fillColor = cvScalar(0.0, 0.0, 0.0, 1.0);
protected KernelData kernelData = null;
protected CvMat[] H = null;
public CvScalar getFillColor() {
return fillColor;
}
public void setFillColor(CvScalar fillColor) {
this.fillColor = fillColor;
}
public double[] getReferencePoints1() {
return referencePoints1;
}
public double[] getReferencePoints2() {
return referencePoints2;
}
public CvMat getK1() {
return K1;
}
public CvMat getK2() {
return K2;
}
public CvMat getInvK1() {
return invK1;
}
public CvMat getInvK2() {
return invK2;
}
public CvMat getR() {
return R;
}
public CvMat getT() {
return t;
}
public CvMat getN() {
return n;
}
protected void prepareHomography(CvMat H, int pyramidLevel, Parameters p, boolean inverse) {
if (K2 != null && invK1 != null && R != null && t != null && p.fakeIdentity) {
// no identity available for plane parameter...
// fakeIdentity needs to be implemented..
cvSetIdentity(H);
return;
}
if (inverse) {
H.put(p.getH());
} else {
cvInvert(p.getH(), H);
}
// adjust the scale of the transformation based on the pyramid level
if (pyramidLevel > 0) {
int scale = 1<<pyramidLevel;
H.put(2, H.get(2)/scale);
H.put(5, H.get(5)/scale);
H.put(6, H.get(6)*scale);
H.put(7, H.get(7)*scale);
}
}
public void transform(IplImage srcImage, IplImage dstImage, CvRect roi, int pyramidLevel,
ImageTransformer.Parameters parameters, boolean inverse) {
Parameters p = ((Parameters)parameters);
if (K2 != null && invK1 != null && R != null && t != null && p.fakeIdentity) {
// no identity available for plane parameter...
// fakeIdentity needs to be implemented..
if (srcImage != dstImage) {
cvCopy(srcImage, dstImage);
}
return;
}
CvMat H = H3x3.get();
prepareHomography(H, pyramidLevel, p, true);
// use ROI not as a sub-image, but as the region we want to fill in
// the destination image... so here we compensate for the translation
// caused by the ROI inside cvWarpPerspective()
if (roi != null && (roi.x() != 0 || roi.y() != 0)) {
int x = roi.x(), y = roi.y();
if (inverse) {
H.put(2, H.get(0)*x + H.get(1)*y + H.get(2));
H.put(5, H.get(3)*x + H.get(4)*y + H.get(5));
H.put(8, H.get(6)*x + H.get(7)*y + H.get(8));
} else {
H.put(0, H.get(0) - x*H.get(6));
H.put(1, H.get(1) - x*H.get(7));
H.put(2, H.get(2) - x*H.get(8));
H.put(3, H.get(3) - y*H.get(6));
H.put(4, H.get(4) - y*H.get(7));
H.put(5, H.get(5) - y*H.get(8));
}
}
dstImage.origin(srcImage.origin()); // cvWarpPerspective doesn't use it..
if (roi == null) {
cvResetImageROI(dstImage);
} else {
cvSetImageROI(dstImage, roi);
}
cvWarpPerspective(srcImage, dstImage, H, CV_INTER_LINEAR |
CV_WARP_FILL_OUTLIERS | (inverse ? CV_WARP_INVERSE_MAP : 0),
getFillColor());
}
public void transform(CvMat srcPts, CvMat dstPts, ImageTransformer.Parameters parameters, boolean inverse) {
Parameters p = ((Parameters)parameters);
CvMat H;
if (inverse) {
H = H3x3.get();
cvInvert(p.getH(), H);
} else {
H = p.getH();
}
cvPerspectiveTransform(srcPts, dstPts, H);
}
public void transform(Data[] data, CvRect roi, ImageTransformer.Parameters[] parameters, boolean[] inverses) {
assert data.length == parameters.length;
if (kernelData == null || kernelData.capacity() < data.length) {
kernelData = new KernelData(data.length);
}
if (H == null || H.length < data.length) {
H = new CvMat[data.length];
for (int i = 0; i < H.length; i++) {
H[i] = CvMat.create(3, 3);
}
}
for (int i = 0; i < data.length; i++) {
kernelData.position(i);
kernelData.srcImg(data[i].srcImg);
kernelData.srcImg2(null);
kernelData.subImg(data[i].subImg);
kernelData.srcDotImg(data[i].srcDotImg);
kernelData.mask(data[i].mask);
kernelData.zeroThreshold(data[i].zeroThreshold);
kernelData.outlierThreshold(data[i].outlierThreshold);
prepareHomography(H[i], data[i].pyramidLevel, (Parameters)parameters[i],
inverses == null ? false : inverses[i]);
kernelData.H1(H[i]);
kernelData.H2(null);
kernelData.X (null);
kernelData.transImg(data[i].transImg);
kernelData.dstImg(data[i].dstImg);
kernelData.dstDstDot(data[i].dstDstDot);
}
long fullCapacity = kernelData.capacity();
kernelData.capacity(data.length);
multiWarpColorTransform(kernelData, roi, getFillColor());
kernelData.capacity(fullCapacity);
for (int i = 0; i < data.length; i++) {
kernelData.position(i);
data[i].dstCount = kernelData.dstCount();
data[i].dstCountZero = kernelData.dstCountZero();
data[i].dstCountOutlier = kernelData.dstCountOutlier();
data[i].srcDstDot = kernelData.srcDstDot();
}
}
public Parameters createParameters() {
return new Parameters();
}
public class Parameters implements ImageTransformer.Parameters {
protected Parameters() {
reset(false);
}
protected double[] projectiveParameters = null;
private CvMat H = CvMat.create(3, 3), n2 = null, R2 = null, t2 = null;
private double constraintError = 0;
private boolean updateNeeded = true;
protected boolean fakeIdentity = false;
public boolean isUpdateNeeded() {
return updateNeeded;
}
public void setUpdateNeeded(boolean updateNeeded) {
this.updateNeeded = updateNeeded;
}
public int size() {
return projectiveParameters.length;
}
public double[] get() {
double[] p = new double[size()];
for (int i = 0; i < p.length; i++) {
p[i] = get(i);
}
return p;
}
public double get(int i) {
return projectiveParameters[i];
}
public void set(double ... p) {
for (int i = 0; i < p.length; i++) {
set(i, p[i]);
}
}
public void set(int i, double p) {
if (projectiveParameters[i] != p) {
projectiveParameters[i] = p;
setUpdateNeeded(true);
}
}
public void set(ImageTransformer.Parameters p) {
set(p.get());
fakeIdentity = ((Parameters)p).fakeIdentity;
}
public void reset(boolean asIdentity) {
setUpdateNeeded(true);
if (referencePoints1 != null && (referencePoints1.length == 0 || referencePoints1.length == 8)) {
if (referencePoints1.length == 0) {
// if (K2 != null && invK1 != null && n == null) {
// projectiveParameters = new double[] { 1, 0, 0, 0, 1, 0, 0, 0 /*, 1*/, 0, 0, 0};
// } else {
projectiveParameters = new double[] { 1, 0, 0, 0, 1, 0, 0, 0 /*, 1*/ };
// }
} else {
// if (K2 != null && invK1 != null && n == null) {
// projectiveParameters = Arrays.copyOf(referencePoints, 11);
// } else {
projectiveParameters = referencePoints1.clone();
// }
}
} else if (K2 != null && invK1 != null) {
if (R != null && t != null) {
// no identity available for this one, so...
//projectiveParameters = new double[] { 0, 0, 0 };
projectiveParameters = new double[] {
referencePoints1[0], referencePoints1[2], referencePoints1[4] };
} else if (n != null) {
projectiveParameters = new double[] { 0, 0, 0, 0, 0, 0 };
} else {
projectiveParameters = new double[] { 0, 0, 0, 0, 0, 0, 0, 0, 0};
}
}
}
// public boolean addDelta(int i) {
// return addDelta(i, 1);
// }
//
// public boolean addDelta(int i, double scale) {
// if (referencePoints != null && i < 8) {
// // add one pixel..
// projectiveParameters[i] += scale;
// } else if (K2 != null && invK1 != null) {
// if (i < 3 || i >= 8) {
// projectiveParameters[i] += scale;
// } else {
// // translation vector
//
// // assuming a reference plane at [0, 0, 1],
// // this is about 1% of image resolution?
// projectiveParameters[i] += 0.01 * scale;
// }
// }
// setUpdateNeeded(true);
// return false;
// }
public double getConstraintError() {
update();
return constraintError;
}
public void set(CvMat setH, boolean inverse) {
if (projectiveParameters.length == 8 && referencePoints1 != null) {
if (inverse) {
cvInvert(setH, H);
} else if (setH != H) {
cvCopy(setH, H);
}
if (referencePoints1.length == 0) {
// direct homography parameterization
for (int i = 0; i < 8; i++) {
projectiveParameters[i] = H.get(i)/H.get(8);
}
} else {
// 4 point parametrization
CvMat pts = pts4x1.get().put(referencePoints1);
cvPerspectiveTransform(pts, pts, H);
pts.get(projectiveParameters);
}
setUpdateNeeded(true);
} else {
throw new UnsupportedOperationException("Set homography operation not supported.");
}
}
public void compose(ImageTransformer.Parameters p1, boolean inverse1,
ImageTransformer.Parameters p2, boolean inverse2) {
Parameters pp1 = (Parameters)p1, pp2 = (Parameters)p2;
if (K2 != null && invK1 != null && R != null && t != null && pp1.fakeIdentity) {
// no identity available for plane parameter...
// fakeIdentity needs to be implemented..
return;
}
compose(pp1.getH(), inverse1, pp2.getH(), inverse2);
}
public void compose(CvMat H1, boolean inverse1, CvMat H2, boolean inverse2) {
if (inverse1 && inverse2) {
cvMatMul(H2, H1, H);
cvInvert(H, H);
} else if (inverse1) {
cvInvert(H1, H);
cvMatMul(H, H2, H);
} else if (inverse2) {
cvInvert(H2, H);
cvMatMul(H1, H, H);
} else {
cvMatMul(H1, H2, H);
}
set(H, false);
}
public CvMat getH() {
update();
return H;
}
public CvMat getN() {
update();
return n2;
}
public CvMat getR() {
update();
return R2;
}
public CvMat getT() {
update();
return t2;
}
protected void update() {
if (!isUpdateNeeded()) {
return;
}
if (referencePoints1 != null && (referencePoints1.length == 0 || referencePoints1.length == 8)) {
if (referencePoints1.length == 0) {
// direct homography parameterization
H.put(0, projectiveParameters, 0, 8);
H.put(8, 1);
} else {
// 4 point parameterization
JavaCV.getPerspectiveTransform(referencePoints1, projectiveParameters, H);
}
// if (K1 != null && invK2 != null && n != null) {
// CvMat Hprime = CvMat.take(3, 3);
// cvCopy(H, Hprime);
// if (R2 == null) {
// R2 = CvMat.create(3, 3);
// }
// if (t2 == null) {
// t2 = CvMat.create(3, 1);
// }
//
// cvMatMul(invK2, Hprime, Hprime);
// cvMatMul(Hprime, K1, Hprime);
//
// // get 3D rotation and translation, with given n
// constraintError = JavaCV.HnToRt(Hprime, n, R2, t2);
// //System.out.println(constraintError);
// Hprime.pool();
// }
} else if (K2 != null && invK1 != null) {
if (R != null && t != null) {
// 3D plane motion, with given R and t
// if (n2 == null) {
// n2 = CvMat.create(3, 1);
// }
double[] src = referencePoints2;
double[] dst = { projectiveParameters[0], referencePoints1[1],
projectiveParameters[1], referencePoints1[3],
projectiveParameters[2], referencePoints1[5] };
if (R2 == null) {
R2 = CvMat.create(3, 3);
}
if (t2 == null) {
t2 = CvMat.create(3, 1);
}
cvTranspose(R, R2);
cvGEMM(R2, t, -1, null, 0, t2, 0);
JavaCV.getPerspectiveTransform(src, dst, invK2, K1, R2, t2, H);
//cvConvertScale(H, H, 1/H.get(8), 0);
//System.out.println(H);
// n2.put(projectiveParameters);
// // H = R-t*n^T
// cvGEMM(t, n2, -1, R, 1, H, CV_GEMM_B_T);
} else {
// 3D rotation and translation, with given n
if (n != null) {
n2 = n; // take n from transformer
} else {
if (n2 == null) {
n2 = CvMat.create(3, 1);
}
n2.put(0, projectiveParameters, 8, 3); // take n from parameters
}
// put rotation angle and translation in matrices
if (R2 == null) {
R2 = CvMat.create(3, 3);
}
if (t2 == null) {
t2 = CvMat.create(3, 1);
}
t2.put(0, projectiveParameters, 0, 3);
cvRodrigues2(t2, R2, null);
t2.put(0, projectiveParameters, 3, 3);
// H = R-tn^T
cvGEMM(t2, n2, -1, R2, 1, H, CV_GEMM_B_T);
}
// // H = K2 * H * K1^-1
// cvMatMul(K2, H, H);
// cvMatMul(H, invK1, H);
}
setUpdateNeeded(false);
}
// public void project() {
// CvMat t2 = getT(), n2 = getN(), R2 = getR();
// cvSetIdentity(H);
// cvGEMM(t2, n2, -1, H, 1, H, CV_GEMM_B_T);
// cvMatMul(R2, H, H);
// cvMatMul(K2, H, H);
// cvMatMul(H, invK1, H);
//
// CvMat pts = CvMat.take(4, 1, CV_64F, 2);
// pts.put(referencePoints);
// cvPerspectiveTransform(pts, pts, H);
// pts.get(projectiveParameters);
// pts.pool();
// }
public boolean preoptimize() {
return false;
}
public double[] getSubspace() {
return null;
}
public void setSubspace(double ... p) {
}
@Override public Parameters clone() {
Parameters p = new Parameters();
p.set(this);
return p;
}
@Override public String toString() {
String s = "[";
double[] p = get();
for (int i = 0; i < p.length; i++) {
s += (float)p[i];
if (i < p.length-1) {
s+= ", ";
}
}
s += "]";
return s;
}
}
}