2 * Copyright 2009 (c) Florian Frankenberger (darkblue.de)
4 * This file is part of LEA.
6 * LEA is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU Lesser General Public License as published by the Free
8 * Software Foundation, either version 3 of the License, or (at your option) any
11 * LEA is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with LEA. If not, see <http://www.gnu.org/licenses/>.
24 public class ClassifierTree {
26 private ArrayList classifiers;
28 public ClassifierTree() {
29 classifiers = new ArrayList();
32 public void addClassifier(Classifier c) {
36 // public static BufferedImage resizeImageFittingInto(BufferedImage image, int
42 // if (image.getWidth() > image.getHeight()) {
43 // factor = dimension / (float) image.getWidth();
44 // newWidth = dimension;
45 // newHeight = (int) (factor * image.getHeight());
47 // factor = dimension / (float) image.getHeight();
48 // newHeight = dimension;
49 // newWidth = (int) (factor * image.getWidth());
53 // BufferedImageOp op = new
54 // ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
55 // BufferedImage tmpImage = op.filter(image, null);
60 // BufferedImage resizedImage = new BufferedImage(newWidth, newHeight,
61 // BufferedImage.TYPE_INT_RGB);
63 // Graphics2D g2D = resizedImage.createGraphics();
64 // g2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
65 // RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
67 // g2D.drawImage(image, 0, 0, newWidth - 1, newHeight - 1, 0, 0,
68 // image.getWidth() - 1,
69 // image.getHeight() - 1, null);
71 // BufferedImageOp op = new
72 // ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
73 // BufferedImage tmpImage = op.filter(resizedImage, null);
79 // * Image should have 100x100px and should be in b/w
83 // public void learn(BufferedImage image, boolean isFace) {
84 // IntegralImageData imageData = new IntegralImageData(image);
85 // for (Classifier classifier : this.classifiers) {
86 // classifier.learn(imageData, isFace);
90 // public int getLearnedFacesYes() {
91 // return this.classifiers.get(0).getLearnedFacesYes();
94 // public int getLearnedFacesNo() {
95 // return this.classifiers.get(0).getLearnedFacesNo();
99 * Locates a face by linear iteration through all probable face positions
101 * @deprecated use locateFaceRadial instead for improved performance
103 * @return an rectangle representing the actual face position on success or
104 * null if no face could be detected
106 // public Rectangle2D locateFace(BufferedImage image) {
107 // long timeStart = System.currentTimeMillis();
109 // int resizeTo = 600;
111 // BufferedImage smallImage = resizeImageFittingInto(image, resizeTo);
112 // IntegralImageData imageData = new IntegralImageData(smallImage);
114 // float factor = image.getWidth() / (float) smallImage.getWidth();
116 // int maxIterations = 0;
118 // // first we calculate the maximum scale factor for our 200x200 image
119 // float maxScaleFactor = Math.min(imageData.getWidth() / 100f,
120 // imageData.getHeight() / 100f);
122 // // we simply won't recognize faces that are smaller than 40x40 px
123 // float minScaleFactor = 0.5f;
125 // // border for faceYes-possibility must be greater that that
126 // float maxBorder = 0.999f;
128 // for (float scale = maxScaleFactor; scale > minScaleFactor; scale -= 0.25) {
129 // int actualDimension = (int) (scale * 100);
130 // int borderX = imageData.getWidth() - actualDimension;
131 // int borderY = imageData.getHeight() - actualDimension;
132 // for (int x = 0; x <= borderX; ++x) {
133 // yLines: for (int y = 0; y <= borderY; ++y) {
135 // for (int iterations = 0; iterations < this.classifiers.size();
137 // Classifier classifier = this.classifiers.get(iterations);
139 // float borderline =
140 // 0.8f + (iterations / this.classifiers.size() - 1) * (maxBorder - 0.8f);
141 // if (iterations > maxIterations)
142 // maxIterations = iterations;
143 // if (!classifier.classifyFace(imageData, scale, x, y, borderline)) {
148 // // if we reach here we have a face recognized because our image went
152 // Rectangle2D faceRect =
153 // new Rectangle2D.Float(x * factor, y * factor, actualDimension * factor,
154 // actualDimension * factor);
156 // System.out.println("Time: " + (System.currentTimeMillis() - timeStart) +
168 * Locates a face by searching radial starting at the last known position. If
169 * lastCoordinates are null we simply start in the center of the image.
171 * TODO: This method could quite possible be tweaked so that face recognition
172 * would be much faster
175 * the image to process
176 * @param lastCoordinates
177 * the last known coordinates or null if unknown
178 * @return an rectangle representing the actual face position on success or
179 * null if no face could be detected
182 public Rectangle2D locateFaceRadial(Image smallImage, Rectangle2D lastCoordinates) {
184 IntegralImageData imageData = new IntegralImageData(smallImage);
185 float originalImageFactor = 1;
187 if (lastCoordinates == null) {
188 // if we don't have a last coordinate we just begin in the center
189 int smallImageMaxDimension = Math.min(smallImage.getWidth(), smallImage.getHeight());
191 new Rectangle2D((smallImage.getWidth() - smallImageMaxDimension) / 2.0,
192 (smallImage.getHeight() - smallImageMaxDimension) / 2.0, smallImageMaxDimension,
193 smallImageMaxDimension);
194 // System.out.println("lastCoordinates=" + lastCoordinates);
196 // first we have to scale the last coodinates back relative to the resized
199 new Rectangle2D((lastCoordinates.getX() * (1 / originalImageFactor)),
200 (lastCoordinates.getY() * (1 / originalImageFactor)),
201 (lastCoordinates.getWidth() * (1 / originalImageFactor)),
202 (lastCoordinates.getHeight() * (1 / originalImageFactor)));
205 float startFactor = (float) (lastCoordinates.getWidth() / 100.0f);
207 // first we calculate the maximum scale factor for our 200x200 image
208 float maxScaleFactor = Math.min(imageData.getWidth() / 100f, imageData.getHeight() / 100f);
209 // maxScaleFactor = 1.0f;
211 // we simply won't recognize faces that are smaller than 40x40 px
212 float minScaleFactor = 0.5f;
214 float maxScaleDifference =
215 Math.max(Math.abs(maxScaleFactor - startFactor), Math.abs(minScaleFactor - startFactor));
217 // border for faceYes-possibility must be greater that that
218 float maxBorder = 0.999f;
220 int startPosX = (int) lastCoordinates.getX();
221 int startPosY = (int) lastCoordinates.getX();
223 for (float factorDiff = 0.0f; Math.abs(factorDiff) <= maxScaleDifference; factorDiff =
224 (factorDiff + sgn(factorDiff) * 0.1f) * -1 // we alternate between
225 // negative and positiv
229 float factor = startFactor + factorDiff;
230 // System.out.println("factor=" + factor);
231 if (factor > maxScaleFactor || factor < minScaleFactor)
234 // now we calculate the actualDimmension
235 int actualDimmension = (int) (100 * factor);
236 int maxX = imageData.getWidth() - actualDimmension;
237 int maxY = imageData.getHeight() - actualDimmension;
239 int maxDiffX = Math.max(Math.abs(startPosX - maxX), startPosX);
240 int maxDiffY = Math.max(Math.abs(startPosY - maxY), startPosY);
242 for (float xDiff = 0.1f; Math.abs(xDiff) <= maxDiffX; xDiff =
243 (xDiff + sgn(xDiff) * 0.5f) * -1) {
244 int xPos = Math.round((float) (startPosX + xDiff));
245 if (xPos < 0 || xPos > maxX)
249 for (float yDiff = 0.1f; Math.abs(yDiff) <= maxDiffY; yDiff =
250 (yDiff + sgn(yDiff) * 0.5f) * -1) {
251 int yPos = Math.round(startPosY + yDiff);
252 if (yPos < 0 || yPos > maxY)
255 // by now we should have a valid coordinate to process which we should
257 boolean backToYLines = false;
258 for (int iterations = 0; iterations < classifiers.size(); ++iterations) {
259 Classifier classifier = (Classifier) classifiers.get(iterations);
261 float borderline = 0.8f + (iterations / (classifiers.size() - 1)) * (maxBorder - 0.8f);
262 if (!classifier.classifyFace(imageData, factor, xPos, yPos, borderline)) {
263 // System.out.println("continue yLines; ");
271 // if we reach here we have a face recognized because our image went
278 Rectangle2D faceRect =
279 new Rectangle2D(xPos * originalImageFactor, yPos * originalImageFactor,
280 actualDimmension * originalImageFactor, actualDimmension * originalImageFactor);
290 // System.out.println("Time: "+(System.currentTimeMillis()-timeStart)+"ms");
295 // public Rectangle2D locateFaceRadial(BufferedImage image, Rectangle2D
296 // lastCoordinates) {
298 // int resizeTo = 600;
300 // BufferedImage smallImage = resizeImageFittingInto(image, resizeTo);
301 // float originalImageFactor = image.getWidth() / (float)
302 // smallImage.getWidth();
303 // IntegralImageData imageData = new IntegralImageData(smallImage);
305 // if (lastCoordinates == null) {
306 // // if we don't have a last coordinate we just begin in the center
307 // int smallImageMaxDimension = Math.min(smallImage.getWidth(),
308 // smallImage.getHeight());
310 // new Rectangle2D.Float((smallImage.getWidth() - smallImageMaxDimension) /
312 // (smallImage.getHeight() - smallImageMaxDimension) / 2.0f,
313 // smallImageMaxDimension,
314 // smallImageMaxDimension);
316 // // first we have to scale the last coodinates back relative to the resized
319 // new Rectangle2D.Float((float) (lastCoordinates.getX() * (1 /
320 // originalImageFactor)),
321 // (float) (lastCoordinates.getY() * (1 / originalImageFactor)),
322 // (float) (lastCoordinates.getWidth() * (1 / originalImageFactor)),
323 // (float) (lastCoordinates.getHeight() * (1 / originalImageFactor)));
326 // float startFactor = (float) (lastCoordinates.getWidth() / 100.0f);
328 // // first we calculate the maximum scale factor for our 200x200 image
329 // float maxScaleFactor = Math.min(imageData.getWidth() / 100f,
330 // imageData.getHeight() / 100f);
331 // // maxScaleFactor = 1.0f;
333 // // we simply won't recognize faces that are smaller than 40x40 px
334 // float minScaleFactor = 0.5f;
336 // float maxScaleDifference =
337 // Math.max(Math.abs(maxScaleFactor - startFactor), Math.abs(minScaleFactor -
340 // // border for faceYes-possibility must be greater that that
341 // float maxBorder = 0.999f;
343 // int startPosX = (int) lastCoordinates.getX();
344 // int startPosY = (int) lastCoordinates.getX();
346 // for (float factorDiff = 0.0f; Math.abs(factorDiff) <= maxScaleDifference;
348 // (factorDiff + sgn(factorDiff) * 0.1f) * -1 // we alternate between
349 // // negative and positiv
353 // float factor = startFactor + factorDiff;
354 // if (factor > maxScaleFactor || factor < minScaleFactor)
357 // // now we calculate the actualDimmension
358 // int actualDimmension = (int) (100 * factor);
359 // int maxX = imageData.getWidth() - actualDimmension;
360 // int maxY = imageData.getHeight() - actualDimmension;
362 // int maxDiffX = Math.max(Math.abs(startPosX - maxX), startPosX);
363 // int maxDiffY = Math.max(Math.abs(startPosY - maxY), startPosY);
365 // for (float xDiff = 0.1f; Math.abs(xDiff) <= maxDiffX; xDiff =
366 // (xDiff + sgn(xDiff) * 0.5f) * -1) {
367 // int xPos = Math.round(startPosX + xDiff);
368 // if (xPos < 0 || xPos > maxX)
371 // yLines: for (float yDiff = 0.1f; Math.abs(yDiff) <= maxDiffY; yDiff =
372 // (yDiff + sgn(yDiff) * 0.5f) * -1) {
373 // int yPos = Math.round(startPosY + yDiff);
374 // if (yPos < 0 || yPos > maxY)
377 // // by now we should have a valid coordinate to process which we should
379 // for (int iterations = 0; iterations < this.classifiers.size();
381 // Classifier classifier = this.classifiers.get(iterations);
383 // float borderline =
384 // 0.8f + (iterations / (this.classifiers.size() - 1)) * (maxBorder - 0.8f);
386 // if (!classifier.classifyFace(imageData, factor, xPos, yPos, borderline)) {
391 // // if we reach here we have a face recognized because our image went
395 // Rectangle2D faceRect =
396 // new Rectangle2D.Float(xPos * originalImageFactor, yPos *
397 // originalImageFactor,
398 // actualDimmension * originalImageFactor, actualDimmension *
399 // originalImageFactor);
410 // System.out.println("Time: "+(System.currentTimeMillis()-timeStart)+"ms");
415 // public List<Classifier> getClassifiers() {
416 // return new ArrayList<Classifier>(this.classifiers);
419 // public static void saveToXml(OutputStream out, ClassifierTree tree) throws
421 // PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
422 // writer.write(xStream.toXML(tree));
426 // public static ClassifierTree loadFromXml(InputStream in) throws IOException
428 // Reader reader = new InputStreamReader(in, "UTF-8");
429 // StringBuilder sb = new StringBuilder();
431 // char[] buffer = new char[1024];
434 // read = reader.read(buffer);
436 // sb.append(buffer, 0, read);
438 // } while (read > -1);
441 // return (ClassifierTree) xStream.fromXML(sb.toString());
444 private static int sgn(float value) {
445 return (value < 0 ? -1 : (value > 0 ? +1 : 1));