/*
* 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 java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.logging.Logger;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;
/**
*
* @author Samuel Audet
*/
public class ReflectanceInitializer {
public ReflectanceInitializer(CameraDevice cameraDevice, ProjectorDevice projectorDevice,
int channels, GNImageAligner.Settings alignerSettings) {
this(cameraDevice, projectorDevice, channels, alignerSettings, 51, 0.01);
}
public ReflectanceInitializer(CameraDevice cameraDevice, ProjectorDevice projectorDevice,
int channels, GNImageAligner.Settings alignerSettings, int smoothingSize, double reflectanceMin) {
this.alignerSettings = alignerSettings;
this.smoothingSize = smoothingSize;
this.reflectanceMin = reflectanceMin;
this.cameraDevice = cameraDevice;
this.projectorDevice = projectorDevice;
this.projectorImages = new IplImage[3];
for (int i = 0; i < projectorImages.length; i++) {
projectorImages[i] = IplImage.create(projectorDevice.imageWidth,
projectorDevice.imageHeight, IPL_DEPTH_32F, channels);
}
// capture "black" image (illuminated by ambient light only)
cvSetZero(projectorImages[0]);
// capture "white" image (projector illumination + ambient light)
cvSet(projectorImages[1], CvScalar.ONE);
// capture image with some texture easy to register... ugh...
CvMat H = mat3x3.get();
projectorDevice.getRectifyingHomography(cameraDevice, H);
JavaCV.fractalTriangleWave(projectorImages[2], H);
}
private static ThreadLocal<CvMat>
mat3x1 = CvMat.createThreadLocal(3, 1),
mat3x3 = CvMat.createThreadLocal(3, 3),
mat4x4 = CvMat.createThreadLocal(4, 4);
private GNImageAligner.Settings alignerSettings;
private int smoothingSize;
private double reflectanceMin;
private CameraDevice cameraDevice;
private ProjectorDevice projectorDevice;
private IplImage[] projectorImages;
public IplImage[] getProjectorImages() {
return projectorImages;
}
public IplImage initializeReflectance(IplImage[] cameraImages, IplImage reflectance,
double[] roiPts, double[] gainAmbientLight) {
int w = cameraImages[0].width();
int h = cameraImages[0].height();
int channels = cameraImages[0].nChannels();
IplImage mask = IplImage.create(w, h, IPL_DEPTH_8U, 1);
cvSetZero(mask);
cvFillConvexPoly(mask, new CvPoint(roiPts.length/2).put((byte)(16-cameraDevice.getMapsPyramidLevel()), roiPts),
4, CvScalar.WHITE, 8, 16);
// make the images very very smooth to compensate for small movements
IplImage float1 = cameraImages[0];
IplImage float2 = cameraImages[1];
cvCopy(float2, reflectance);
cvSmooth(float1, float1, CV_GAUSSIAN, smoothingSize, 0, 0, 0);
cvSmooth(float2, float2, CV_GAUSSIAN, smoothingSize, 0, 0, 0);
// remove ambient light of image1 from image2 -> image2
cvSub(float2, float1, float2, null);
// remove distortion caused by the mixing matrix of the projector light
// and recover (very very smooth) reflectance map
CvMat p = mat3x1.get();
p.put(1.0, 1.0, 1.0); // white
cvMatMul(projectorDevice.colorMixingMatrix, p, p);
CvMat invp;
if (float2.nChannels() == 4) {
invp = mat4x4.get();
invp.put(1/p.get(0), 0, 0, 0,
0, 1/p.get(1), 0, 0,
0, 0, 1/p.get(2), 0,
0, 0, 0, 1);
} else {
invp = mat3x3.get();
invp.put(1/p.get(0), 0, 0,
0, 1/p.get(1), 0,
0, 0, 1/p.get(2));
}
cvTransform(float2, float2, invp, null);
// recover (very very smooth) ambient light by removing distortions
// caused by the reflectance map
// cvDiv(image1, image3, image1, 1);
// cvDiv() doesn't support division by zero...
FloatBuffer fb1 = float1.getFloatBuffer();
FloatBuffer fb2 = float2.getFloatBuffer();
ByteBuffer mb = mask.getByteBuffer();
assert fb1.capacity() == fb2.capacity()/3;
assert fb1.capacity() == mb.capacity()/3;
int[] nPixels = new int[channels];
for (int i = 0, j = 0; j < fb1.capacity(); i++, j+=channels) {
for (int z = 0; z < channels; z++) {
float ra = fb1.get(j+z);
float r = fb2.get(j+z);
float a = r == 0 ? 0 : ra/r;
fb1.put(j+z, a);
if (mb.get(i) != 0) {
if (r > reflectanceMin) {
nPixels[z]++;
gainAmbientLight[z+1] += a;
}
}
}
}
gainAmbientLight[0] = 1.0; // assume projector gain = 1.0
for (int z = 0; z < gainAmbientLight.length-1; z++) {
gainAmbientLight[z+1] = nPixels[z] == 0 ? 0 : gainAmbientLight[z+1]/nPixels[z];
}
// System.out.println(ambientLight[0] + " " + ambientLight[1] + " " + ambientLight[2]);
// recover sharp reflectance map by using the original image2 and smooth ambient light
cvAddS(float1, cvScalar(p.get(0), p.get(1), p.get(2), 0.0), float1, null);
cvDiv(reflectance, float1, reflectance, 1.0);
cvNot(mask, mask);
// increase region a bit so that the resulting image can be
// interpolated or averaged properly within the region of interest...
cvErode(mask, mask, null, 15);
cvSet(reflectance, CvScalar.ZERO, mask);
return reflectance;
}
public CvMat initializePlaneParameters(IplImage reflectance, IplImage cameraImage,
double[] referencePoints, double[] roiPts, double[] gainAmbientLight) {
ProCamTransformer transformer = new ProCamTransformer(referencePoints, cameraDevice, projectorDevice, null);
transformer.setProjectorImage(projectorImages[2], 0, alignerSettings.pyramidLevelMax);
ProCamTransformer.Parameters parameters = transformer.createParameters();
// parameters.set(8, 0);
// parameters.set(9, 0);
// parameters.set(10, -1/cameraDevice.getSettings().nominalDistance);
final int gainAmbientLightStart = parameters.size() - gainAmbientLight.length;
final int gainAmbientLightEnd = parameters.size();
for (int i = gainAmbientLightStart; i < gainAmbientLightEnd; i++) {
parameters.set(i, gainAmbientLight[i-gainAmbientLightStart]);
}
ImageAligner aligner = new GNImageAligner(transformer, parameters,
reflectance, roiPts, cameraImage, alignerSettings);
double[] delta = new double[parameters.size()+1];
boolean converged = false;
long iterationsStartTime = System.currentTimeMillis();
int iterations = 0;
while (!converged && iterations < 100) {
converged = aligner.iterate(delta);
iterations++;
}
parameters = (ProCamTransformer.Parameters)aligner.getParameters();
// for (int i = 0; i < gainAmbientLight.length; i++) {
// gainAmbientLight[i] = parameters.get(11+i);
// }
Logger.getLogger(ReflectanceInitializer.class.getName()).info(
"iteratingTime = " + (System.currentTimeMillis()-iterationsStartTime) +
" iterations = " + iterations + " objectiveRMSE = " + (float)aligner.getRMSE());
return parameters.getN0();
}
}