/*
* 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.util.Arrays;
import org.bytedeco.javacpp.IntPointer;
import static org.bytedeco.javacpp.ARToolKitPlus.*;
import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_imgproc.*;
/**
*
* @author Samuel Audet
*/
public class MarkerDetector {
public MarkerDetector(Settings settings) {
setSettings(settings);
}
public MarkerDetector() {
this(new Settings());
}
// the k's will depend strongly on the ratio between ambient light
// (including minimum projector intensity) and the intensity level
// used for the projector markers... this is because we use binary
// thresholding while we actually have three levels..
public static class Settings extends BaseChildSettings {
int thresholdWindowMin = 5;
int thresholdWindowMax = 63;
double thresholdVarMultiplier = 1.0;
double thresholdKBlackMarkers = 0.6;
double thresholdKWhiteMarkers = 1.0;
int subPixelWindow = 11;
public int getThresholdWindowMin() {
return thresholdWindowMin;
}
public void setThresholdWindowMin(int thresholdWindowMin) {
this.thresholdWindowMin = thresholdWindowMin;
}
public int getThresholdWindowMax() {
return thresholdWindowMax;
}
public void setThresholdWindowMax(int thresholdWindowMax) {
this.thresholdWindowMax = thresholdWindowMax;
}
public double getThresholdVarMultiplier() {
return thresholdVarMultiplier;
}
public void setThresholdVarMultiplier(double thresholdVarMultiplier) {
this.thresholdVarMultiplier = thresholdVarMultiplier;
}
public double getThresholdKBlackMarkers() {
return thresholdKBlackMarkers;
}
public void setThresholdKBlackMarkers(double thresholdKBlackMarkers) {
this.thresholdKBlackMarkers = thresholdKBlackMarkers;
}
public double getThresholdKWhiteMarkers() {
return thresholdKWhiteMarkers;
}
public void setThresholdKWhiteMarkers(double thresholdKWhiteMarkers) {
this.thresholdKWhiteMarkers = thresholdKWhiteMarkers;
}
public int getSubPixelWindow() {
return subPixelWindow;
}
public void setSubPixelWindow(int subPixelWindow) {
this.subPixelWindow = subPixelWindow;
}
}
private Settings settings;
public Settings getSettings() {
return settings;
}
public void setSettings(Settings settings) {
this.settings = settings;
this.subPixelSize = cvSize(settings.subPixelWindow/2, settings.subPixelWindow/2);
this.subPixelZeroZone = cvSize(-1,-1);
this.subPixelTermCriteria = cvTermCriteria(CV_TERMCRIT_EPS, 100, 0.001);
}
private MultiTracker tracker = null;
private IntPointer markerNum = new IntPointer(1);
private int width = 0, height = 0, depth = 0, channels = 0;
private IplImage tempImage, tempImage2, sumImage, sqSumImage, thresholdedImage;
private CvMat points = CvMat.create(1, 4, CV_32F, 2);
private CvPoint2D32f corners = new CvPoint2D32f(4);
private CvMemStorage memory = CvMemStorage.create();
private CvSize subPixelSize = null, subPixelZeroZone = null;
private CvTermCriteria subPixelTermCriteria = null;
private CvFont font = cvFont(1, 1);
private CvSize textSize = new CvSize();
public IplImage getThresholdedImage() {
return thresholdedImage;
}
private void init(IplImage image) {
if (tracker != null && image.width() == width && image.height() == height &&
image.depth() == depth && image.nChannels() == channels) {
return;
}
width = image.width();
height = image.height();
depth = image.depth();
channels = image.nChannels();
if (depth != IPL_DEPTH_8U || channels > 1) {
tempImage = IplImage.create(width, height, IPL_DEPTH_8U, 1);
}
if (depth != IPL_DEPTH_8U && channels > 1) {
tempImage2 = IplImage.create(width, height, IPL_DEPTH_8U, 3);
}
sumImage = IplImage.create(width+1, height+1, IPL_DEPTH_64F, 1);
sqSumImage = IplImage.create(width+1, height+1, IPL_DEPTH_64F, 1);
thresholdedImage = IplImage.create(width, height, IPL_DEPTH_8U, 1);
tracker = new MultiTracker(thresholdedImage.widthStep(), thresholdedImage.height());
// if (depth != IPL_DEPTH_8U) {
// throw new Exception("Unsupported format: IplImage must have depth == IPL_DEPTH_8U.");
// }
int pixfmt = PIXEL_FORMAT_LUM;
// switch (nChannels) {
// case 4: pixfmt = PIXEL_FORMAT_BGRA; break;
// case 3: pixfmt = PIXEL_FORMAT_BGR; break;
// case 1: pixfmt = PIXEL_FORMAT_LUM; break;
// default:
// throw new Exception("Unsupported format: No support for IplImage with " + channels + " channels.");
// }
tracker.setPixelFormat(pixfmt);
// if(!tracker.init("data/LogitechPro4000.dat",
// "data/markerboard_480-499.cfg", 1.0f, 1000.0f, null)) {
// throw new Exception("ERROR: init() failed.");
// }
tracker.setBorderWidth(0.125f);
// tracker.setThreshold(128);
// tracker.activateAutoThreshold(true);
// tracker.setNumAutoThresholdRetries(10);
tracker.setUndistortionMode(UNDIST_NONE);
// tracker.setPoseEstimator(POSE_ESTIMATOR_RPP);
tracker.setMarkerMode(MARKER_ID_BCH);
tracker.setImageProcessingMode(IMAGE_FULL_RES);
}
public Marker[] detect(IplImage image, boolean whiteMarkers) {
init(image);
if (depth != IPL_DEPTH_8U && channels > 1) {
cvConvertScale(image, tempImage2, 255/image.highValue(), 0);
cvCvtColor(tempImage2, tempImage, channels > 3 ? CV_RGBA2GRAY : CV_BGR2GRAY);
image = tempImage;
} else if (depth != IPL_DEPTH_8U) {
cvConvertScale(image, tempImage, 255/image.highValue(), 0);
image = tempImage;
} else if (channels > 1) {
cvCvtColor(image, tempImage, channels > 3 ? CV_RGBA2GRAY : CV_BGR2GRAY);
image = tempImage;
}
//long time1 = System.currentTimeMillis();
JavaCV.adaptiveThreshold(image, sumImage, sqSumImage, thresholdedImage, whiteMarkers,
settings.thresholdWindowMax, settings.thresholdWindowMin, settings.thresholdVarMultiplier,
whiteMarkers ? settings.thresholdKWhiteMarkers : settings.thresholdKBlackMarkers);
//CanvasFrame.global.showImage(thresholded, 0.5);
//CanvasFrame.global.waitKey();
//long time2 = System.currentTimeMillis();
int n = 0;
ARMarkerInfo markers = new ARMarkerInfo(null);
tracker.arDetectMarkerLite(thresholdedImage.imageData(), 128 /*tracker.getThreshold()*/, markers, markerNum);
//long time3 = System.currentTimeMillis();
Marker[] markers2 = new Marker[markerNum.get(0)];
for (int i = 0; i < markers2.length && !markers.isNull(); i++) {
markers.position(i);
int id = markers.id();
if (id < 0) {
// no detected ID...
continue;
}
int dir = markers.dir();
float confidence = markers.cf();
float[] vertex = new float[8];
markers.vertex().get(vertex);
int w = settings.subPixelWindow/2+1;
if (vertex[0]-w < 0 || vertex[0]+w >= width || vertex[1]-w < 0 || vertex[1]+w >= height ||
vertex[2]-w < 0 || vertex[2]+w >= width || vertex[3]-w < 0 || vertex[3]+w >= height ||
vertex[4]-w < 0 || vertex[4]+w >= width || vertex[5]-w < 0 || vertex[5]+w >= height ||
vertex[6]-w < 0 || vertex[6]+w >= width || vertex[7]-w < 0 || vertex[7]+w >= height) {
// too tight for cvFindCornerSubPix...
continue;
}
points.getFloatBuffer().put(vertex);
CvBox2D box = cvMinAreaRect2(points, memory);
float bw = box.size().width();
float bh = box.size().height();
cvClearMemStorage(memory);
if (bw <= 0 || bh <= 0 || bw/bh < 0.1 || bw/bh > 10) {
// marker is too "flat" to have been IDed correctly...
continue;
}
for (int j = 0; j < 4; j++) {
corners.position(j).put(vertex[2*j], vertex[2*j+1]);
}
if (false) {
// move the search window a bit (max 1/4 of the window) toward the center...
// this allows us to cram more markers closer to one another
double cx = 0, cy = 0;
for (int j = 0; j < 4; j++) {
corners.position(j);
cx += corners.x();
cy += corners.y();
}
cx /= 4;
cy /= 4;
for (int j = 0; j < 4; j++) {
corners.position(j);
float x = corners.x();
float y = corners.y();
double dx = cx - x;
double dy = cy - y;
corners.x(x + (float)Math.signum(dx)*(settings.subPixelWindow/4));
corners.y(y + (float)Math.signum(dy)*(settings.subPixelWindow/4));
}
}
cvFindCornerSubPix(image, corners.position(0), 4, subPixelSize, subPixelZeroZone, subPixelTermCriteria);
double[] d = { corners.position((4-dir)%4).x(), corners.position((4-dir)%4).y(),
corners.position((5-dir)%4).x(), corners.position((5-dir)%4).y(),
corners.position((6-dir)%4).x(), corners.position((6-dir)%4).y(),
corners.position((7-dir)%4).x(), corners.position((7-dir)%4).y() };
markers2[n++] = new Marker(id, d, confidence);
}
//long time4 = System.currentTimeMillis();
//System.out.println("thresholdTime = " + (time2-time1) + " detectTime = " + (time3-time2) + " subPixTime = " + (time4-time3));
//cvCvtColor(thresholdedImage, image, CV_GRAY2BGR);
//cvCopy(thresholdedImage, image, null);
return Arrays.copyOf(markers2, n);
}
public void draw(IplImage image, Marker[] markers) {
for (Marker m : markers) {
int cx = 0, cy = 0;
int[] pts = new int[8];
for (int i = 0; i < 4; i++) {
int x = (int)Math.round(m.corners[i*2 ] * (1<<16));
int y = (int)Math.round(m.corners[i*2+1] * (1<<16));
pts[2*i ] = x;
pts[2*i + 1] = y;
cx += x;
cy += y;
// draw little colored squares in corners to confirm that the corners
// are returned in the right order...
// CvPoint pt2a = cvPoint(pts[i].x+200000, pts[i].y+200000);
// cvRectangle(image, pts, pt2a,
// i == 0? CV_RGB(maxIntensity, 0, 0) :
// i == 1? CV_RGB(0, maxIntensity, 0) :
// i == 2? CV_RGB(0, 0, maxIntensity) :
// CV_RGB(maxIntensity, maxIntensity, maxIntensity),
// CV_FILLED, CV_AA, 16);
}
cx /= 4;
cy /= 4;
cvPolyLine(image, pts, new int[] { pts.length/2 }, 1, 1, CV_RGB(0, 0, image.highValue()), 1, CV_AA, 16);
String text = Integer.toString(m.id);
int[] baseline = new int[1];
cvGetTextSize(text, font, textSize, baseline);
int[] pt1 = { cx - (textSize.width() *3/2 << 16)/2,
cy + (textSize.height()*3/2 << 16)/2 };
int[] pt2 = { cx + (textSize.width() *3/2 << 16)/2,
cy - (textSize.height()*3/2 << 16)/2 };
cvRectangle(image, pt1, pt2, CV_RGB(0, image.highValue(), 0), CV_FILLED, CV_AA, 16);
int[] pt = { (int)Math.round((double)cx/(1<<16) - textSize.width()/2),
(int)Math.round((double)cy/(1<<16) + textSize.height()/2) + 1 };
cvPutText(image, text, pt, font, CvScalar.BLACK);
}
}
}