/** * * @author greg (at) myrobotlab.org * * This file is part of MyRobotLab (http://myrobotlab.org). * * MyRobotLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version (subject to the "Classpath" exception * as provided in the LICENSE.txt file that accompanied this code). * * MyRobotLab is distributed in the hope that it will be useful or fun, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * All libraries in thirdParty bundle are subject to their own license * requirements - please refer to http://myrobotlab.org/libraries for * details. * * Enjoy ! * * */ package org.myrobotlab.opencv; /* import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2HSV; import static org.bytedeco.javacpp.opencv_imgproc.CV_HAAR_DO_CANNY_PRUNING; import static org.bytedeco.javacpp.opencv_imgproc.cvHaarDetectObjects; import static org.bytedeco.javacpp.opencv_core.CV_RGB; import static org.bytedeco.javacpp.opencv_core.cvClearMemStorage; import static org.bytedeco.javacpp.opencv_core.cvCreateMemStorage; import static org.bytedeco.javacpp.opencv_core.cvDrawLine; import static org.bytedeco.javacpp.opencv_core.cvGetSeqElem; import static org.bytedeco.javacpp.opencv_core.cvLoad; import static org.bytedeco.javacpp.opencv_core.cvRectangle; import static org.bytedeco.javacpp.opencv_core.cvSize; import org.bytedeco.javacpp.opencv_imgproc.CvHaarClassifierCascade; import org.bytedeco.javacpp.opencv_core.CvMemStorage; import org.bytedeco.javacpp.opencv_core.CvPoint; import org.bytedeco.javacpp.opencv_core.CvRect; import org.bytedeco.javacpp.opencv_core.CvSeq; import org.bytedeco.javacpp.opencv_core.IplImage; */ import static org.bytedeco.javacpp.helper.opencv_objdetect.cvHaarDetectObjects; import static org.bytedeco.javacpp.opencv_core.cvClearMemStorage; import static org.bytedeco.javacpp.opencv_core.cvCreateMemStorage; import static org.bytedeco.javacpp.opencv_core.cvGetSeqElem; import static org.bytedeco.javacpp.opencv_core.cvLoad; import static org.bytedeco.javacpp.opencv_core.cvPoint; import static org.bytedeco.javacpp.opencv_imgproc.cvDrawRect; import static org.bytedeco.javacpp.opencv_objdetect.CV_HAAR_DO_CANNY_PRUNING; import static org.bytedeco.javacpp.opencv_objdetect.CV_HAAR_FIND_BIGGEST_OBJECT; import java.util.ArrayList; import org.bytedeco.javacpp.Loader; import org.bytedeco.javacpp.opencv_core.CvMemStorage; import org.bytedeco.javacpp.opencv_core.CvRect; import org.bytedeco.javacpp.opencv_core.CvScalar; import org.bytedeco.javacpp.opencv_core.CvSeq; import org.bytedeco.javacpp.opencv_core.IplImage; import org.bytedeco.javacpp.opencv_objdetect; import org.bytedeco.javacpp.opencv_objdetect.CvHaarClassifierCascade; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.data.Rectangle; import org.slf4j.Logger; public class OpenCVFilterFaceDetect extends OpenCVFilter { private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(OpenCVFilterFaceDetect.class.getCanonicalName()); CvMemStorage storage = null; public CvHaarClassifierCascade cascade = null; // TODO - was static public String cascadeDir = "haarcascades"; public String cascadeFile = "haarcascade_frontalface_alt2.xml"; // public String cascadePath = "haarcascades/haarcascade_mcs_lefteye.xml"; // public String cascadePath = // "haarcascades/haarcascade_mcs_eyepair_big.xml"; int i; // public int stablizedFrameCount = 10; public int minFaceFrames = 10; public int minEmptyFrames = 10; public int firstFaceFrame = 0; public int firstEmptyFrame = 0; public int faceCnt = 0; public int lastFaceCnt = 0; public static final String STATE_LOST_TRACKING = "STATE_LOST_TRACKING"; public static final String STATE_LOSING_TRACKING = "STATE_LOSING_TRACKING"; public static final String STATE_DETECTING_FACE = "STATE_DETECTING_FACE"; public static final String STATE_DETECTED_FACE = "STATE_DETECTED_FACE"; private String state = STATE_LOST_TRACKING; int x0, y0, x1, y1; public OpenCVFilterFaceDetect() { super(); } public OpenCVFilterFaceDetect(String name) { super(name); } @Override public IplImage display(IplImage image, OpenCVData data) { if (data != null) { ArrayList<Rectangle> bb = data.getBoundingBoxArray(); if (bb != null) { for (int i = 0; i < bb.size(); ++i) { Rectangle rect = bb.get(i); if (useFloatValues) { x0 = (int) (rect.x * width); y0 = (int) (rect.y * height); x1 = x0 + (int) (rect.width * width); y1 = y0 + (int) (rect.height * height); cvDrawRect(image, cvPoint(x0, y0), cvPoint(x1, y1), CvScalar.RED, 1, 1, 0); } else { x0 = (int) rect.x; y0 = (int) rect.y; x1 = x0 + (int) rect.width; y1 = y0 + (int) rect.height; cvDrawRect(image, cvPoint(x0, y0), cvPoint(x1, y1), CvScalar.RED, 1, 1, 0); } } return image; } } return image; } @Override public void imageChanged(IplImage image) { // Allocate the memory storage TODO make this globalData if (storage == null) { storage = cvCreateMemStorage(0); } if (cascade == null) { // Preload the opencv_objdetect module to work around a known bug. Loader.load(opencv_objdetect.class); cascade = new CvHaarClassifierCascade(cvLoad(String.format("%s/%s", cascadeDir, cascadeFile))); // cascade = new // CvHaarClassifierCascade(cvLoad("haarcascades/haarcascade_eye.xml")); if (cascade == null) { log.error("Could not load classifier cascade"); } } } @Override public IplImage process(IplImage image, OpenCVData data) { // Clear the memory storage which was used before cvClearMemStorage(storage); // Find whether the cascade is loaded, to find the faces. If yes, then: if (cascade != null) { // CV_HAAR_DO_CANNY_PRUNING - causes flat regions (no lines) to be // skipped // CV_HAAR_SCALE_IMAGE // CV_HAAR_FIND_BIGGEST_OBJECT - tells the detector to return the // biggest - hence # of objects will be 1 or none // CV_HAAR_DO_ROUGH_SEARCH // faces = cvHaarDetectObjects(grayImage, classifier, storage, 1.1, // 3, CV_HAAR_DO_ROUGH_SEARCH | CV_HAAR_FIND_BIGGEST_OBJECT); // faces = cvHaarDetectObjects(grayImage, classifier_eyes, storage, // 1.1, 3, CV_HAAR_DO_CANNY_PRUNING); CvSeq faces = cvHaarDetectObjects(image, cascade, storage, 1.1, 1, CV_HAAR_DO_CANNY_PRUNING | CV_HAAR_FIND_BIGGEST_OBJECT); if (faces != null) { ArrayList<Rectangle> bb = new ArrayList<Rectangle>(); faceCnt = faces.total(); // Loop the number of faces found. for (i = 0; i < faces.total(); i++) { CvRect r = new CvRect(cvGetSeqElem(faces, i)); Rectangle rect; if (useFloatValues) { rect = new Rectangle((float) r.x() / width, (float) r.y() / height, (float) r.width() / width, (float) r.height() / height); } else { rect = new Rectangle(r.x(), r.y(), r.width(), r.height()); } bb.add(rect); try { // close resource r.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } data.put(bb); } } else { cascade = new CvHaarClassifierCascade(cvLoad(String.format("%s/%s", cascadeDir, cascadeFile))); } // WOOHOO LOOK AT THAT A STRING SWITCH !!! // 16 years later ! :D // converted from compiler into 2 stage hash switch :) cool ! switch (state) { case STATE_LOST_TRACKING: if (faceCnt > 0) { firstFaceFrame = frameIndex; state = STATE_DETECTING_FACE; broadcastFilterState(); } break; case STATE_DETECTING_FACE: if (faceCnt > 0 && frameIndex - firstFaceFrame > minFaceFrames) { state = STATE_DETECTED_FACE; // broadcastFilterState(); } else if (faceCnt == 0) { firstFaceFrame = frameIndex; } break; case STATE_DETECTED_FACE: if (faceCnt == 0) { state = STATE_LOSING_TRACKING; firstFaceFrame = frameIndex; broadcastFilterState(); } break; case STATE_LOSING_TRACKING: if (faceCnt == 0 && frameIndex - firstEmptyFrame > minEmptyFrames) { state = STATE_LOST_TRACKING; // broadcastFilterState(); } else if (faceCnt > 0) { firstEmptyFrame = frameIndex; } break; default: log.error("invalid state"); break; } // face detection events if (faceCnt > 0 && frameIndex - firstFaceFrame > minFaceFrames) { } else { } lastFaceCnt = faceCnt; return image; } }