All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openimaj.image.objectdetection.haar.OCVHaarLoader Maven / Gradle / Ivy

/**
 * Copyright (c) 2011, The University of Southampton and the individual contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * 	Redistributions of source code must retain the above copyright notice,
 * 	this list of conditions and the following disclaimer.
 *
 *   *	Redistributions in binary form must reproduce the above copyright notice,
 * 	this list of conditions and the following disclaimer in the documentation
 * 	and/or other materials provided with the distribution.
 *
 *   *	Neither the name of the University of Southampton nor the names of its
 * 	contributors may be used to endorse or promote products derived from this
 * 	software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.openimaj.image.objectdetection.haar;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

/**
 * Support for reading OpenCV Haar Cascade XML files. Currently only supports
 * the old-style format.
 * 
 * @author Jonathon Hare ([email protected])
 */
public class OCVHaarLoader {
	private static final float ICV_STAGE_THRESHOLD_BIAS = 0.0001f;

	private static final String NEXT_NODE = "next";
	private static final String PARENT_NODE = "parent";
	private static final String STAGE_THRESHOLD_NODE = "stage_threshold";
	private static final String ANONYMOUS_NODE = "_";
	private static final String RIGHT_NODE_NODE = "right_node";
	private static final String RIGHT_VAL_NODE = "right_val";
	private static final String LEFT_NODE_NODE = "left_node";
	private static final String LEFT_VAL_NODE = "left_val";
	private static final String THRESHOLD_NODE = "threshold";
	private static final String TILTED_NODE = "tilted";
	private static final String RECTS_NODE = "rects";
	private static final String FEATURE_NODE = "feature";
	private static final String TREES_NODE = "trees";
	private static final String STAGES_NODE = "stages";
	private static final String SIZE_NODE = "size";
	private static final String OCV_STORAGE_NODE = "opencv_storage";

	static class TreeNode {
		HaarFeature feature;
		float threshold;
		float left_val;
		float right_val;
		int left_node = -1;
		int right_node = -1;
	}

	static class StageNode {
		private int parent = -1;
		private int next = -1;
		private float threshold;
		private List> trees = new ArrayList>();
	}

	static class OCVHaarClassifierNode {
		int width;
		int height;
		String name;
		boolean hasTiltedFeatures = false;
		List stages = new ArrayList();
	}

	/**
	 * Read using an XML Pull Parser. This requires the exact format of the xml
	 * is consistent (i.e. element order is consistent). Checks are made at each
	 * node to ensure that we're reading the correct data.
	 * 
	 * @param in
	 *            the InputStream to consume
	 * @return the parsed cascade
	 * @throws IOException
	 */
	static OCVHaarClassifierNode readXPP(InputStream in) throws IOException {
		try {
			final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
			final XmlPullParser reader = factory.newPullParser();

			reader.setInput(in, null);

			reader.nextTag(); // opencv_storage
			checkNode(reader, OCV_STORAGE_NODE);

			reader.nextTag(); // haarcascade_{type}
			if (!"opencv-haar-classifier".equals(reader.getAttributeValue(null, "type_id"))) {
				throw new IOException("Unsupported format: " + reader.getAttributeValue(null, "type_id"));
			}

			final OCVHaarClassifierNode root = new OCVHaarClassifierNode();
			root.name = reader.getName();

			reader.nextTag(); // 
			checkNode(reader, SIZE_NODE);

			final String sizeStr = reader.nextText();
			final String[] widthHeight = sizeStr.trim().split(" ");
			if (widthHeight.length != 2) {
				throw new IOException("expecting 'w h' for size element, got: " + sizeStr);
			}

			root.width = Integer.parseInt(widthHeight[0]);
			root.height = Integer.parseInt(widthHeight[1]);

			reader.nextTag(); // 
			checkNode(reader, STAGES_NODE);

			// parse stage tags
			while (reader.nextTag() == XmlPullParser.START_TAG) { // <_>
				checkNode(reader, ANONYMOUS_NODE);

				final StageNode currentStage = new StageNode();
				root.stages.add(currentStage);

				reader.nextTag(); // 
				checkNode(reader, TREES_NODE);

				while (reader.nextTag() == XmlPullParser.START_TAG) { // <_>
					checkNode(reader, ANONYMOUS_NODE);

					final List currentTree = new ArrayList();
					currentStage.trees.add(currentTree);

					while (reader.nextTag() == XmlPullParser.START_TAG) { // <_>
						checkNode(reader, ANONYMOUS_NODE);

						final List regions = new ArrayList(3);

						reader.nextTag(); // 
						checkNode(reader, FEATURE_NODE);

						reader.nextTag(); // 
						checkNode(reader, RECTS_NODE);

						while (reader.nextTag() == XmlPullParser.START_TAG) { // <_>
							checkNode(reader, ANONYMOUS_NODE);
							regions.add(WeightedRectangle.parse(reader.nextText()));
						}

						reader.nextTag(); // 
						checkNode(reader, TILTED_NODE);
						final boolean tilted = "1".equals(reader.nextText());

						if (tilted)
							root.hasTiltedFeatures = true;

						reader.nextTag(); // 
						checkNode(reader, FEATURE_NODE);

						final HaarFeature currentFeature = HaarFeature.create(regions, tilted);

						reader.nextTag(); // 
						checkNode(reader, THRESHOLD_NODE);
						final float threshold = (float) Double.parseDouble(reader.nextText());

						final TreeNode treeNode = new TreeNode();
						treeNode.threshold = threshold;
						treeNode.feature = currentFeature;

						reader.nextTag(); //  || 
						checkNode(reader, LEFT_VAL_NODE, LEFT_NODE_NODE);
						final String leftText = reader.nextText();
						if ("left_val".equals(reader.getName())) {
							treeNode.left_val = Float.parseFloat(leftText);
						} else {
							// find leftIndexed classifier
							treeNode.left_node = Integer.parseInt(leftText);
						}
						reader.nextTag(); //  || 
						checkNode(reader, RIGHT_VAL_NODE, RIGHT_NODE_NODE);
						final String rightText = reader.nextText();
						if ("right_val".equals(reader.getName())) {
							treeNode.right_val = Float.parseFloat(rightText);
						} else {
							// find right indexed classifier (put off the lookup
							// until later)
							treeNode.right_node = Integer.parseInt(rightText);
						}

						reader.nextTag(); // 
						checkNode(reader, ANONYMOUS_NODE);
						currentTree.add(treeNode);
					}
				}

				reader.nextTag(); // 
				checkNode(reader, STAGE_THRESHOLD_NODE);
				currentStage.threshold = Float.parseFloat(reader.nextText()) - ICV_STAGE_THRESHOLD_BIAS;

				reader.nextTag(); // 
				checkNode(reader, PARENT_NODE);
				currentStage.parent = Integer.parseInt(reader.nextText());

				reader.nextTag(); // 
				checkNode(reader, NEXT_NODE);
				currentStage.next = Integer.parseInt(reader.nextText());

				reader.nextTag(); // 
				checkNode(reader, ANONYMOUS_NODE);
			}

			return root;
		} catch (final XmlPullParserException ex) {
			throw new IOException(ex);
		}
	}

	/**
	 * Read the cascade from an OpenCV xml serialisation. Currently this only
	 * supports the old-style cascade xml.
	 * 
	 * @param is
	 *            the stream to read from
	 * @return the cascade object
	 * @throws IOException
	 */
	public static StageTreeClassifier read(InputStream is) throws IOException {
		final OCVHaarClassifierNode root = readXPP(is);

		return buildCascade(root);
	}

	private static StageTreeClassifier buildCascade(OCVHaarClassifierNode root) throws IOException {
		return new StageTreeClassifier(root.width, root.height, root.name, root.hasTiltedFeatures,
				buildStages(root.stages));
	}

	private static Stage buildStages(List stageNodes) throws IOException {
		final Stage[] stages = new Stage[stageNodes.size()];
		for (int i = 0; i < stages.length; i++) {
			final StageNode node = stageNodes.get(i);

			stages[i] = new Stage(node.threshold, buildClassifiers(node.trees), null, null);
		}

		Stage root = null;
		boolean isCascade = true;
		for (int i = 0; i < stages.length; i++) {
			final StageNode node = stageNodes.get(i);

			if (node.parent == -1 && node.next == -1) {
				if (root == null) {
					root = stages[i];
				} else {
					throw new IOException("Inconsistent cascade/tree: multiple roots found");
				}
			}

			if (node.parent != -1) {
				// if it's a tree, multiple nodes might have the same parent,
				// but the first one we see should set the successStage
				if (stages[node.parent].successStage == null) {
					stages[node.parent].successStage = stages[i];
				}
			}

			if (node.next != -1) {
				isCascade = false; // it's a tree
				stages[i].failureStage = stages[node.next];
			}
		}

		if (!isCascade) {
			optimiseTree(root);
		}

		return root;
	}

	/**
	 * Any failure along a success branch after a node that has a failure node
	 * should result in that failure nodes branch being executed. In order to
	 * simplify the iteration through the tree, we link all failure nodes of
	 * children of the success branch to the appropriate failure node. For
	 * example,
	 * 
	 * 
	 * 	3 -> 4 -> 5 -> 7 -> 9
	 *            |
	 *            | (failure) 
	 *           \ /
	 *            6 -> 8 -> 10
	 * 
* * becomes: * *
	 * 	3 -> 4 -> 5 -> 7 -> 9
	 *            |    |     |
	 *            +----/-----/
	 *            |
	 *           \ /
	 *            6 -> 8 -> 10
	 * 
* * Note: implementation based on Matt Nathan's Java port of the OpenCV Haar * code. * * @param root * the root of the tree */ private static void optimiseTree(Stage root) { final Deque stack = new ArrayDeque(); stack.push(root); Stage failureStage = null; while (!stack.isEmpty()) { final Stage stage = stack.pop(); if (stage.failureStage == null) { // child of failure branch stage.failureStage = failureStage; if (stage.successStage != null) { stack.push(stage.successStage); } } else if (stage.failureStage != failureStage) { // new failure branch stack.push(stage); failureStage = stage.failureStage; if (stage.successStage != null) { stack.push(stage.successStage); } } else { // old failure branch stack.push(stage.failureStage); failureStage = null; } } } private static Classifier[] buildClassifiers(final List> trees) { final Classifier[] classifiers = new Classifier[trees.size()]; for (int i = 0; i < classifiers.length; i++) { classifiers[i] = buildClassifier(trees.get(i)); } return classifiers; } private static Classifier buildClassifier(final List tree) { return buildClassifier(tree, tree.get(0)); } private static Classifier buildClassifier(final List tree, TreeNode current) { final HaarFeatureClassifier fc = new HaarFeatureClassifier(current.feature, current.threshold, null, null); if (current.left_node == -1) { fc.left = new ValueClassifier(current.left_val); } else { fc.left = buildClassifier(tree, tree.get(current.left_node)); } if (current.right_node == -1) { fc.right = new ValueClassifier(current.right_val); } else { fc.right = buildClassifier(tree, tree.get(current.right_node)); } return fc; } private static void checkNode(XmlPullParser reader, String... expected) throws IOException { for (final String e : expected) if (e.equals(reader.getName())) return; throw new IOException("Unexpected tag: " + reader.getName() + " (expected: " + Arrays.toString(expected) + ")"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy