!7 sclass Detector extends AbstractMultiScaleObjectDetector { /** * Default step size to make when there is a hint of detection. */ public static final int DEFAULT_SMALL_STEP = 1; /** * Default step size to make when there is definitely no detection. */ public static final int DEFAULT_BIG_STEP = 2; /** * Default scale factor multiplier. */ public static final float DEFAULT_SCALE_FACTOR = 1.1f; protected StageTreeClassifier cascade; protected float scaleFactor = 1.1f; protected int smallStep = 1; protected int bigStep = 2; /** * Construct the {@link Detector} with the given parameters. * * @param cascade * the cascade or tree of stages. * @param scaleFactor * the amount to change between scales (multiplicative) * @param smallStep * the amount to step when there is a hint of detection * @param bigStep * the amount to step when there is definitely no detection */ public Detector(StageTreeClassifier cascade, float scaleFactor, int smallStep, int bigStep) { super(Math.max(cascade.width, cascade.height), 0); this.cascade = cascade; this.scaleFactor = scaleFactor; this.smallStep = smallStep; this.bigStep = bigStep; } /** * Construct the {@link Detector} with the given tree of stages and scale * factor. The default step sizes are used. * * @param cascade * the cascade or tree of stages. * @param scaleFactor * the amount to change between scales */ public Detector(StageTreeClassifier cascade, float scaleFactor) { this(cascade, scaleFactor, DEFAULT_SMALL_STEP, DEFAULT_BIG_STEP); } /** * Construct the {@link Detector} with the given tree of stages, and the * default parameters for step sizes and scale factor. * * @param cascade * the cascade or tree of stages. */ public Detector(StageTreeClassifier cascade) { this(cascade, DEFAULT_SCALE_FACTOR, DEFAULT_SMALL_STEP, DEFAULT_BIG_STEP); } /** * Perform detection at a single scale. Subclasses may override this to * customise the spatial search. The given starting and stopping coordinates * take into account any region of interest set on this detector. * * @param sat * the summed area table(s) * @param startX * the starting x-ordinate * @param stopX * the stopping x-ordinate * @param startY * the starting y-ordinate * @param stopY * the stopping y-ordinate * @param ystep * the amount to step * @param windowWidth * the window width at the current scale * @param windowHeight * the window height at the current scale * @param results * the list to store detection results in */ protected void detectAtScale(final SummedSqTiltAreaTable sat, final int startX, final int stopX, final int startY, final int stopY, final float ystep, final int windowWidth, final int windowHeight, final List results) { for (int iy = startY; iy < stopY; iy++) { final int y = Math.round(iy * ystep); for (int ix = startX, xstep = 0; ix < stopX; ix += xstep) { final int x = Math.round(ix * ystep); final int result = cascade.classify(sat, x, y); if (result > 0) { results.add(new Rectangle(x, y, windowWidth, windowHeight)); } // if there is no detection, then increase the step size xstep = (result > 0 ? smallStep : bigStep); // TODO: think about what to do if there isn't a detection, but // we're very close to having one based on the ratio of stages // passes to total stages. } } } @Override public List detect(FImage image) { final List results = new ArrayList(); final int imageWidth = image.getWidth(); final int imageHeight = image.getHeight(); final SummedSqTiltAreaTable sat = new SummedSqTiltAreaTable(image, cascade.hasTiltedFeatures); // compute the number of scales to test and the starting factor int nFactors = 0; int startFactor = 0; for (float factor = 1; factor * cascade.width < imageWidth - 10 && factor * cascade.height < imageHeight - 10; factor *= scaleFactor) { final float width = factor * cascade.width; final float height = factor * cascade.height; if (width < minSize || height < minSize) { startFactor++; } if (maxSize > 0 && (width > maxSize || height > maxSize)) { break; } nFactors++; } // run the detection at each scale float factor = (float) Math.pow(scaleFactor, startFactor); for (int scaleStep = startFactor; scaleStep < nFactors; factor *= scaleFactor, scaleStep++) { final float ystep = Math.max(2, factor); final int windowWidth = (int) (factor * cascade.width); final int windowHeight = (int) (factor * cascade.height); // determine the spatial range, taking into account any ROI. final int startX = (int) (roi == null ? 0 : Math.max(0, roi.x)); final int startY = (int) (roi == null ? 0 : Math.max(0, roi.y)); final int stopX = Math.round( (((roi == null ? imageWidth : Math.min(imageWidth, roi.x + roi.width)) - windowWidth)) / ystep); final int stopY = Math.round( (((roi == null ? imageHeight : Math.min(imageHeight, roi.y + roi.height)) - windowHeight)) / ystep); // prepare the cascade for this scale cascade.setScale(factor); detectAtScale(sat, startX, stopX, startY, stopY, ystep, windowWidth, windowHeight, results); } return results; } /** * Get the step size the detector will make if there is any hint of a * detection. This should be smaller than {@link #bigStep()}. * * @return the amount to step on any hint of detection. */ public int smallStep() { return smallStep; } /** * Get the step size the detector will make if there is definitely no * detection. This should be bigger than {@link #smallStep()}. * * @return the amount to step when there is definitely no detection. */ public int bigStep() { return bigStep; } /** * Set the step size the detector will make if there is any hint of a * detection. This should be smaller than {@link #bigStep()}. * * @param smallStep * The amount to step on any hint of detection. */ public void setSmallStep(int smallStep) { this.smallStep = smallStep; } /** * Set the step size the detector will make if there is definitely no * detection. This should be bigger than {@link #smallStep()}. * * @param bigStep * The amount to step when there is definitely no detection. */ public void bigStep(int bigStep) { this.bigStep = bigStep; } /** * Get the scale factor (the amount to change between scales * (multiplicative)). * * @return the scaleFactor */ public float getScaleFactor() { return scaleFactor; } /** * Set the scale factor (the amount to change between scales * (multiplicative)). * * @param scaleFactor * the scale factor to set */ public void setScaleFactor(float scaleFactor) { this.scaleFactor = scaleFactor; } /** * Get the classifier tree or cascade used by this detector. * * @return the classifier tree or cascade. */ public StageTreeClassifier getClassifier() { return cascade; } } sclass HaarCascadeDetector { public enum BuiltInCascade { /** * A eye detector */ eye("haarcascade_eye.xml"), /** * A eye with glasses detector */ eye_tree_eyeglasses("haarcascade_eye_tree_eyeglasses.xml"), /** * A frontal face detector */ frontalface_alt("haarcascade_frontalface_alt.xml"), /** * A frontal face detector */ frontalface_alt2("haarcascade_frontalface_alt2.xml"), /** * A frontal face detector */ frontalface_alt_tree("haarcascade_frontalface_alt_tree.xml"), /** * A frontal face detector */ frontalface_default("haarcascade_frontalface_default.xml"), /** * A fullbody detector */ fullbody("haarcascade_fullbody.xml"), /** * A left eye detector */ lefteye_2splits("haarcascade_lefteye_2splits.xml"), /** * A lower body detector */ lowerbody("haarcascade_lowerbody.xml"), /** * A detector for a pair of eyes */ mcs_eyepair_big("haarcascade_mcs_eyepair_big.xml"), /** * A detector for a pair of eyes */ mcs_eyepair_small("haarcascade_mcs_eyepair_small.xml"), /** * A left eye detector */ mcs_lefteye("haarcascade_mcs_lefteye.xml"), /** * A mouth detector */ mcs_mouth("haarcascade_mcs_mouth.xml"), /** * A nose detector */ mcs_nose("haarcascade_mcs_nose.xml"), /** * A right eye detector */ mcs_righteye("haarcascade_mcs_righteye.xml"), /** * An upper body detector */ mcs_upperbody("haarcascade_mcs_upperbody.xml"), /** * A profile face detector */ profileface("haarcascade_profileface.xml"), /** * A right eye detector */ righteye_2splits("haarcascade_righteye_2splits.xml"), /** * An upper body detector */ upperbody("haarcascade_upperbody.xml"); private String classFile; private BuiltInCascade(String classFile) { this.classFile = classFile; } /** * @return The name of the cascade resource */ public String classFile() { return classFile; } /** * Create a new detector with the this cascade. * * @return A new {@link HaarCascadeDetector} */ public HaarCascadeDetector load() { try { return new HaarCascadeDetector(classFile); } catch (final Exception e) { throw new RuntimeException(e); } } } protected Detector detector; protected DetectionFilter> groupingFilter; protected boolean histogramEqualize = false; /** * Construct with the given cascade resource. See * {@link #setCascade(String)} to understand how the cascade is loaded. * * @param cas * The cascade resource. * @see #setCascade(String) */ public HaarCascadeDetector(String cas) { try { setCascade(cas); } catch (final Exception e) { throw new RuntimeException(e); } groupingFilter = new OpenCVGrouping(); } /** * Construct with the {@link BuiltInCascade#frontalface_default} cascade. */ public HaarCascadeDetector() { this(BuiltInCascade.frontalface_default.classFile()); } /** * Construct with the {@link BuiltInCascade#frontalface_default} cascade and * the given minimum search window size. * * @param minSize * minimum search window size */ public HaarCascadeDetector(int minSize) { this(); this.detector.setMinimumDetectionSize(minSize); } /** * Construct with the given cascade resource and the given minimum search * window size. See {@link #setCascade(String)} to understand how the * cascade is loaded. * * @param cas * The cascade resource. * @param minSize * minimum search window size. * * @see #setCascade(String) */ public HaarCascadeDetector(String cas, int minSize) { this(cas); this.detector.setMinimumDetectionSize(minSize); } /** * @return The minimum detection window size */ public int getMinSize() { return this.detector.getMinimumDetectionSize(); } /** * Set the minimum detection window size * * @param size * the window size */ public void setMinSize(int size) { this.detector.setMinimumDetectionSize(size); } /** * @return The maximum detection window size */ public int getMaxSize() { return this.detector.getMaximumDetectionSize(); } /** * Set the maximum detection window size * * @param size * the window size */ public void setMaxSize(int size) { this.detector.setMaximumDetectionSize(size); } /** * @return The grouping filter */ public DetectionFilter> getGroupingFilter() { return groupingFilter; } /** * Set the filter for merging detections * * @param grouping */ public void setGroupingFilter(DetectionFilter> grouping) { this.groupingFilter = grouping; } @Override public List detectFaces(FImage image) { if (histogramEqualize) image.processInplace(new EqualisationProcessor()); final List rects = detector.detect(image); final List> filteredRects = groupingFilter.apply(rects); final List results = new ArrayList(); for (final ObjectIntPair r : filteredRects) { results.add(new DetectedFace(r.first, image.extractROI(r.first), r.second)); } return results; } /** * @see Detector#getScaleFactor() * @return The detector scale factor */ public double getScaleFactor() { return detector.getScaleFactor(); } /** * Set the cascade classifier for this detector. The cascade file is first * searched for as a java resource, and if it is not found then a it is * assumed to be a file on the filesystem. * * @param cascadeResource * The cascade to load. * @throws Exception * if there is a problem loading the cascade. */ public void setCascade(String cascadeResource) throws Exception { // try to load serialized cascade from external XML file InputStream in = null; try { in = OCVHaarLoader.class.getResourceAsStream(cascadeResource); if (in == null) { in = new FileInputStream(new File(cascadeResource)); } final StageTreeClassifier cascade = OCVHaarLoader.read(in); if (this.detector == null) this.detector = new Detector(cascade); else this.detector = new Detector(cascade, this.detector.getScaleFactor()); } catch (final Exception e) { throw e; } finally { if (in != null) { try { in.close(); } catch (final IOException e) { } } } } /** * Set the detector scale factor * * @see Detector#setScaleFactor(float) * * @param scaleFactor * the scale factor */ public void setScale(float scaleFactor) { this.detector.setScaleFactor(scaleFactor); } /** * Serialize the detector using java serialization to the given stream * * @param os * the stream * @throws IOException */ public void save(OutputStream os) throws IOException { final ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(this); } /** * Deserialize the detector from a stream. The detector must have been * written with a previous invokation of {@link #save(OutputStream)}. * * @param is * @return {@link HaarCascadeDetector} read from stream. * @throws IOException * @throws ClassNotFoundException */ public static HaarCascadeDetector read(InputStream is) throws IOException, ClassNotFoundException { final ObjectInputStream ois = new ObjectInputStream(is); return (HaarCascadeDetector) ois.readObject(); } @Override public int hashCode() { int hashCode = HashCodeUtil.SEED; hashCode = HashCodeUtil.hash(hashCode, this.detector.getMinimumDetectionSize()); hashCode = HashCodeUtil.hash(hashCode, this.detector.getScaleFactor()); hashCode = HashCodeUtil.hash(hashCode, this.detector.getClassifier().getName()); hashCode = HashCodeUtil.hash(hashCode, this.groupingFilter); hashCode = HashCodeUtil.hash(hashCode, this.histogramEqualize); return hashCode; } @Override public void readBinary(DataInput in) throws IOException { this.detector = IOUtils.read(in); this.groupingFilter = IOUtils.read(in); histogramEqualize = in.readBoolean(); } @Override public byte[] binaryHeader() { return "HAAR".getBytes(); } @Override public void writeBinary(DataOutput out) throws IOException { IOUtils.write(detector, out); IOUtils.write(groupingFilter, out); out.writeBoolean(histogramEqualize); } @Override public String toString() { return "HaarCascadeDetector[cascade=" + detector.getClassifier().getName() + "]"; } /** * @return the underlying Haar cascade. */ public StageTreeClassifier getCascade() { return detector.getClassifier(); } /** * @return the underlying {@link Detector}. */ public Detector getDetector() { return detector; } } sclass HaarCascade_FaceDetector extends F1> { new HaarCascadeDetector detector; public L get(BufferedImage img) { if (img == null) null; ret map(detector.detectFaces(ImageUtilities.createFImage(img)), func(DetectedFace f) -> RectAndConfidence { RectAndConfidence(openImajRectangleToRect(f.getBounds()), f.getConfidence()) }); } } module HCFD > DynSingleFunctionWithPrintLog { void doIt { pnl(new HaarCascade_FaceDetector().get(loadImage2(#1101409))); } }