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);
195 // first we have to scale the last coodinates back relative to the resized
198 new Rectangle2D((lastCoordinates.getX() * (1 / originalImageFactor)),
199 (lastCoordinates.getY() * (1 / originalImageFactor)),
200 (lastCoordinates.getWidth() * (1 / originalImageFactor)),
201 (lastCoordinates.getHeight() * (1 / originalImageFactor)));
204 float startFactor = (float) (lastCoordinates.getWidth() / 100.0f);
206 // first we calculate the maximum scale factor for our 200x200 image
207 float maxScaleFactor = Math.min(imageData.getWidth() / 100f, imageData.getHeight() / 100f);
208 // maxScaleFactor = 1.0f;
210 // we simply won't recognize faces that are smaller than 40x40 px
211 float minScaleFactor = 0.5f;
213 float maxScaleDifference =
214 Math.max(Math.abs(maxScaleFactor - startFactor), Math.abs(minScaleFactor - startFactor));
216 // border for faceYes-possibility must be greater that that
217 float maxBorder = 0.999f;
219 int startPosX = (int) lastCoordinates.getX();
220 int startPosY = (int) lastCoordinates.getX();
222 for (float factorDiff = 0.0f; Math.abs(factorDiff) <= maxScaleDifference; factorDiff =
223 (factorDiff + sgn(factorDiff) * 0.1f) * -1 // we alternate between
224 // negative and positiv
228 float factor = startFactor + factorDiff;
229 if (factor > maxScaleFactor || factor < minScaleFactor)
232 // now we calculate the actualDimmension
233 int actualDimmension = (int) (100 * factor);
234 int maxX = imageData.getWidth() - actualDimmension;
235 int maxY = imageData.getHeight() - actualDimmension;
237 int maxDiffX = Math.max(Math.abs(startPosX - maxX), startPosX);
238 int maxDiffY = Math.max(Math.abs(startPosY - maxY), startPosY);
240 for (float xDiff = 0.1f; Math.abs(xDiff) <= maxDiffX; xDiff =
241 (xDiff + sgn(xDiff) * 0.5f) * -1) {
242 int xPos = Math.round((float) (startPosX + xDiff));
243 if (xPos < 0 || xPos > maxX)
247 for (float yDiff = 0.1f; Math.abs(yDiff) <= maxDiffY; yDiff =
248 (yDiff + sgn(yDiff) * 0.5f) * -1) {
249 int yPos = Math.round(startPosY + yDiff);
250 if (yPos < 0 || yPos > maxY)
253 // by now we should have a valid coordinate to process which we should
255 boolean backToYLines = false;
256 for (int iterations = 0; iterations < classifiers.size(); ++iterations) {
257 Classifier classifier = (Classifier) classifiers.get(iterations);
259 float borderline = 0.8f + (iterations / (classifiers.size() - 1)) * (maxBorder - 0.8f);
261 if (!classifier.classifyFace(imageData, factor, xPos, yPos, borderline)) {
268 // if we reach here we have a face recognized because our image went
276 Rectangle2D faceRect =
277 new Rectangle2D(xPos * originalImageFactor, yPos * originalImageFactor,
278 actualDimmension * originalImageFactor, actualDimmension * originalImageFactor);
288 // System.out.println("Time: "+(System.currentTimeMillis()-timeStart)+"ms");
293 // public Rectangle2D locateFaceRadial(BufferedImage image, Rectangle2D
294 // lastCoordinates) {
296 // int resizeTo = 600;
298 // BufferedImage smallImage = resizeImageFittingInto(image, resizeTo);
299 // float originalImageFactor = image.getWidth() / (float)
300 // smallImage.getWidth();
301 // IntegralImageData imageData = new IntegralImageData(smallImage);
303 // if (lastCoordinates == null) {
304 // // if we don't have a last coordinate we just begin in the center
305 // int smallImageMaxDimension = Math.min(smallImage.getWidth(),
306 // smallImage.getHeight());
308 // new Rectangle2D.Float((smallImage.getWidth() - smallImageMaxDimension) /
310 // (smallImage.getHeight() - smallImageMaxDimension) / 2.0f,
311 // smallImageMaxDimension,
312 // smallImageMaxDimension);
314 // // first we have to scale the last coodinates back relative to the resized
317 // new Rectangle2D.Float((float) (lastCoordinates.getX() * (1 /
318 // originalImageFactor)),
319 // (float) (lastCoordinates.getY() * (1 / originalImageFactor)),
320 // (float) (lastCoordinates.getWidth() * (1 / originalImageFactor)),
321 // (float) (lastCoordinates.getHeight() * (1 / originalImageFactor)));
324 // float startFactor = (float) (lastCoordinates.getWidth() / 100.0f);
326 // // first we calculate the maximum scale factor for our 200x200 image
327 // float maxScaleFactor = Math.min(imageData.getWidth() / 100f,
328 // imageData.getHeight() / 100f);
329 // // maxScaleFactor = 1.0f;
331 // // we simply won't recognize faces that are smaller than 40x40 px
332 // float minScaleFactor = 0.5f;
334 // float maxScaleDifference =
335 // Math.max(Math.abs(maxScaleFactor - startFactor), Math.abs(minScaleFactor -
338 // // border for faceYes-possibility must be greater that that
339 // float maxBorder = 0.999f;
341 // int startPosX = (int) lastCoordinates.getX();
342 // int startPosY = (int) lastCoordinates.getX();
344 // for (float factorDiff = 0.0f; Math.abs(factorDiff) <= maxScaleDifference;
346 // (factorDiff + sgn(factorDiff) * 0.1f) * -1 // we alternate between
347 // // negative and positiv
351 // float factor = startFactor + factorDiff;
352 // if (factor > maxScaleFactor || factor < minScaleFactor)
355 // // now we calculate the actualDimmension
356 // int actualDimmension = (int) (100 * factor);
357 // int maxX = imageData.getWidth() - actualDimmension;
358 // int maxY = imageData.getHeight() - actualDimmension;
360 // int maxDiffX = Math.max(Math.abs(startPosX - maxX), startPosX);
361 // int maxDiffY = Math.max(Math.abs(startPosY - maxY), startPosY);
363 // for (float xDiff = 0.1f; Math.abs(xDiff) <= maxDiffX; xDiff =
364 // (xDiff + sgn(xDiff) * 0.5f) * -1) {
365 // int xPos = Math.round(startPosX + xDiff);
366 // if (xPos < 0 || xPos > maxX)
369 // yLines: for (float yDiff = 0.1f; Math.abs(yDiff) <= maxDiffY; yDiff =
370 // (yDiff + sgn(yDiff) * 0.5f) * -1) {
371 // int yPos = Math.round(startPosY + yDiff);
372 // if (yPos < 0 || yPos > maxY)
375 // // by now we should have a valid coordinate to process which we should
377 // for (int iterations = 0; iterations < this.classifiers.size();
379 // Classifier classifier = this.classifiers.get(iterations);
381 // float borderline =
382 // 0.8f + (iterations / (this.classifiers.size() - 1)) * (maxBorder - 0.8f);
384 // if (!classifier.classifyFace(imageData, factor, xPos, yPos, borderline)) {
389 // // if we reach here we have a face recognized because our image went
393 // Rectangle2D faceRect =
394 // new Rectangle2D.Float(xPos * originalImageFactor, yPos *
395 // originalImageFactor,
396 // actualDimmension * originalImageFactor, actualDimmension *
397 // originalImageFactor);
408 // System.out.println("Time: "+(System.currentTimeMillis()-timeStart)+"ms");
413 // public List<Classifier> getClassifiers() {
414 // return new ArrayList<Classifier>(this.classifiers);
417 // public static void saveToXml(OutputStream out, ClassifierTree tree) throws
419 // PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
420 // writer.write(xStream.toXML(tree));
424 // public static ClassifierTree loadFromXml(InputStream in) throws IOException
426 // Reader reader = new InputStreamReader(in, "UTF-8");
427 // StringBuilder sb = new StringBuilder();
429 // char[] buffer = new char[1024];
432 // read = reader.read(buffer);
434 // sb.append(buffer, 0, read);
436 // } while (read > -1);
439 // return (ClassifierTree) xStream.fromXML(sb.toString());
442 private static int sgn(float value) {
443 return (value < 0 ? -1 : (value > 0 ? +1 : 1));