mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 63167d0040ba2c4bc6870b23555d5eea0ce4de35 Former-commit-id: dea5a4a4e13494b3490b5a69ac0a69c8aa8b6ab9 Former-commit-id: 72c754f9100b7f4722c9cf0664297d299b4e7e20af/merge-core
31 changed files with 3250 additions and 70 deletions
@ -0,0 +1 @@ |
|||
b3860ad6afc1b4eeab715c0de52156a24ec976e7 |
|||
@ -0,0 +1 @@ |
|||
64bcd395a5b08c75a48cc21db2668cfb7fe5364e |
|||
@ -0,0 +1,19 @@ |
|||
namespace ImageProcessor.Common.Extensions |
|||
{ |
|||
using System; |
|||
using System.Drawing; |
|||
|
|||
internal static class RectangleExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Compares two rectangles for equality, considering an acceptance threshold.
|
|||
/// </summary>
|
|||
public static bool IsEqual(this Rectangle objA, Rectangle objB, int threshold) |
|||
{ |
|||
return (Math.Abs(objA.X - objB.X) < threshold) && |
|||
(Math.Abs(objA.Y - objB.Y) < threshold) && |
|||
(Math.Abs(objA.Width - objB.Width) < threshold) && |
|||
(Math.Abs(objA.Height - objB.Height) < threshold); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
|||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=imaging_005Cfilters_005Cfacedetection_005Chaarcascade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
|||
@ -0,0 +1,338 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)] |
|||
public partial class opencv_storage |
|||
{ |
|||
|
|||
private opencv_storageCascade cascadeField; |
|||
|
|||
/// <remarks/>
|
|||
public opencv_storageCascade cascade |
|||
{ |
|||
get |
|||
{ |
|||
return this.cascadeField; |
|||
} |
|||
set |
|||
{ |
|||
this.cascadeField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
public partial class opencv_storageCascade |
|||
{ |
|||
|
|||
private string stageTypeField; |
|||
|
|||
private string featureTypeField; |
|||
|
|||
private byte heightField; |
|||
|
|||
private byte widthField; |
|||
|
|||
private opencv_storageCascadeStageParams stageParamsField; |
|||
|
|||
private opencv_storageCascadeFeatureParams featureParamsField; |
|||
|
|||
private byte stageNumField; |
|||
|
|||
private opencv_storageCascade_[] stagesField; |
|||
|
|||
private opencv_storageCascade_2[] featuresField; |
|||
|
|||
private string type_idField; |
|||
|
|||
/// <remarks/>
|
|||
public string stageType |
|||
{ |
|||
get |
|||
{ |
|||
return this.stageTypeField; |
|||
} |
|||
set |
|||
{ |
|||
this.stageTypeField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public string featureType |
|||
{ |
|||
get |
|||
{ |
|||
return this.featureTypeField; |
|||
} |
|||
set |
|||
{ |
|||
this.featureTypeField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public byte height |
|||
{ |
|||
get |
|||
{ |
|||
return this.heightField; |
|||
} |
|||
set |
|||
{ |
|||
this.heightField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public byte width |
|||
{ |
|||
get |
|||
{ |
|||
return this.widthField; |
|||
} |
|||
set |
|||
{ |
|||
this.widthField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public opencv_storageCascadeStageParams stageParams |
|||
{ |
|||
get |
|||
{ |
|||
return this.stageParamsField; |
|||
} |
|||
set |
|||
{ |
|||
this.stageParamsField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public opencv_storageCascadeFeatureParams featureParams |
|||
{ |
|||
get |
|||
{ |
|||
return this.featureParamsField; |
|||
} |
|||
set |
|||
{ |
|||
this.featureParamsField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public byte stageNum |
|||
{ |
|||
get |
|||
{ |
|||
return this.stageNumField; |
|||
} |
|||
set |
|||
{ |
|||
this.stageNumField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlArrayItemAttribute("_", IsNullable = false)] |
|||
public opencv_storageCascade_[] stages |
|||
{ |
|||
get |
|||
{ |
|||
return this.stagesField; |
|||
} |
|||
set |
|||
{ |
|||
this.stagesField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlArrayItemAttribute("_", IsNullable = false)] |
|||
public opencv_storageCascade_2[] features |
|||
{ |
|||
get |
|||
{ |
|||
return this.featuresField; |
|||
} |
|||
set |
|||
{ |
|||
this.featuresField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlAttributeAttribute()] |
|||
public string type_id |
|||
{ |
|||
get |
|||
{ |
|||
return this.type_idField; |
|||
} |
|||
set |
|||
{ |
|||
this.type_idField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
public partial class opencv_storageCascadeStageParams |
|||
{ |
|||
|
|||
private byte maxWeakCountField; |
|||
|
|||
/// <remarks/>
|
|||
public byte maxWeakCount |
|||
{ |
|||
get |
|||
{ |
|||
return this.maxWeakCountField; |
|||
} |
|||
set |
|||
{ |
|||
this.maxWeakCountField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
public partial class opencv_storageCascadeFeatureParams |
|||
{ |
|||
|
|||
private byte maxCatCountField; |
|||
|
|||
/// <remarks/>
|
|||
public byte maxCatCount |
|||
{ |
|||
get |
|||
{ |
|||
return this.maxCatCountField; |
|||
} |
|||
set |
|||
{ |
|||
this.maxCatCountField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
public partial class opencv_storageCascade_ |
|||
{ |
|||
|
|||
private byte maxWeakCountField; |
|||
|
|||
private double stageThresholdField; |
|||
|
|||
private opencv_storageCascade__[] weakClassifiersField; |
|||
|
|||
/// <remarks/>
|
|||
public byte maxWeakCount |
|||
{ |
|||
get |
|||
{ |
|||
return this.maxWeakCountField; |
|||
} |
|||
set |
|||
{ |
|||
this.maxWeakCountField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public double stageThreshold |
|||
{ |
|||
get |
|||
{ |
|||
return this.stageThresholdField; |
|||
} |
|||
set |
|||
{ |
|||
this.stageThresholdField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlArrayItemAttribute("_", IsNullable = false)] |
|||
public opencv_storageCascade__[] weakClassifiers |
|||
{ |
|||
get |
|||
{ |
|||
return this.weakClassifiersField; |
|||
} |
|||
set |
|||
{ |
|||
this.weakClassifiersField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
public partial class opencv_storageCascade__ |
|||
{ |
|||
|
|||
private string internalNodesField; |
|||
|
|||
private string leafValuesField; |
|||
|
|||
/// <remarks/>
|
|||
public string internalNodes |
|||
{ |
|||
get |
|||
{ |
|||
return this.internalNodesField; |
|||
} |
|||
set |
|||
{ |
|||
this.internalNodesField = value; |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
public string leafValues |
|||
{ |
|||
get |
|||
{ |
|||
return this.leafValuesField; |
|||
} |
|||
set |
|||
{ |
|||
this.leafValuesField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)] |
|||
public partial class opencv_storageCascade_2 |
|||
{ |
|||
|
|||
private string[] rectsField; |
|||
|
|||
/// <remarks/>
|
|||
[System.Xml.Serialization.XmlArrayItemAttribute("_", IsNullable = false)] |
|||
public string[] rects |
|||
{ |
|||
get |
|||
{ |
|||
return this.rectsField; |
|||
} |
|||
set |
|||
{ |
|||
this.rectsField = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System.IO; |
|||
using System.Reflection; |
|||
|
|||
using ImageProcessor.Common.Extensions; |
|||
|
|||
public static class EmbeddedHaarCascades |
|||
{ |
|||
private static HaarCascade frontFaceDefault; |
|||
|
|||
public static HaarCascade FrontFaceDefault |
|||
{ |
|||
get |
|||
{ |
|||
return frontFaceDefault ?? (frontFaceDefault = GetCascadeFromResource("haarcascade_frontalface_legacy.xml")); |
|||
} |
|||
} |
|||
|
|||
private static HaarCascade GetCascadeFromResource(string identifier) |
|||
{ |
|||
HaarCascade cascade; |
|||
var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("ImageProcessor.Imaging.Filters.ObjectDetection.Resources." + identifier); |
|||
|
|||
//using (StringReader stringReader = new StringReader(resource))
|
|||
//{
|
|||
cascade = HaarCascade.FromXml(resource); |
|||
//}
|
|||
|
|||
return cascade; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,239 @@ |
|||
// Accord Vision Library
|
|||
// The Accord.NET Framework
|
|||
// http://accord-framework.net
|
|||
//
|
|||
// Copyright © César Souza, 2009-2015
|
|||
// cesarsouza at gmail.com
|
|||
//
|
|||
// This code has been submitted as an user contribution by darko.juric2
|
|||
// GCode Issue #12 https://code.google.com/p/accord/issues/detail?id=12
|
|||
//
|
|||
// This library is free software; you can redistribute it and/or
|
|||
// modify it under the terms of the GNU Lesser General Public
|
|||
// License as published by the Free Software Foundation; either
|
|||
// version 2.1 of the License, or (at your option) any later version.
|
|||
//
|
|||
// This library is distributed in the hope that it will be useful,
|
|||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
// Lesser General Public License for more details.
|
|||
//
|
|||
// You should have received a copy of the GNU Lesser General Public
|
|||
// License along with this library; if not, write to the Free Software
|
|||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
//
|
|||
|
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
/// <summary>
|
|||
/// Group matching algorithm for detection region averaging.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// This class can be seen as a post-processing filter. Its goal is to
|
|||
/// group near or contained regions together in order to produce more
|
|||
/// robust and smooth estimates of the detected regions.
|
|||
/// </remarks>
|
|||
///
|
|||
public abstract class GroupMatching<T> |
|||
{ |
|||
|
|||
private int classCount; |
|||
private int minNeighbors; |
|||
|
|||
private int[] labels; |
|||
private int[] equals; |
|||
|
|||
private List<T> filter; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <see cref="RectangleGroupMatching"/> object.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="minimumNeighbors">
|
|||
/// The minimum number of neighbors needed to keep a detection. If a rectangle
|
|||
/// has less than this minimum number, it will be discarded as a false positive.</param>
|
|||
///
|
|||
protected GroupMatching(int minimumNeighbors = 2) |
|||
{ |
|||
this.minNeighbors = minimumNeighbors; |
|||
this.filter = new List<T>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum number of neighbors necessary to keep a detection.
|
|||
/// If a rectangle has less neighbors than this number, it will be discarded as
|
|||
/// a false positive.
|
|||
/// </summary>
|
|||
///
|
|||
public int MinimumNeighbors |
|||
{ |
|||
get { return minNeighbors; } |
|||
set |
|||
{ |
|||
if (minNeighbors < 0) |
|||
throw new ArgumentOutOfRangeException("value", "Value must be equal to or higher than zero."); |
|||
minNeighbors = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets how many classes were found in the
|
|||
/// last call to <see cref="Group(T[])"/>.
|
|||
/// </summary>
|
|||
///
|
|||
public int Classes |
|||
{ |
|||
get { return classCount; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Groups possibly near rectangles into a smaller
|
|||
/// set of distinct and averaged rectangles.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="shapes">The rectangles to group.</param>
|
|||
///
|
|||
public T[] Group(T[] shapes) |
|||
{ |
|||
// Start by classifying rectangles according to distance
|
|||
classify(shapes); // assign label for near rectangles
|
|||
|
|||
int[] neighborCount; |
|||
|
|||
// Average the rectangles contained in each labeled group
|
|||
T[] output = Average(labels, shapes, out neighborCount); |
|||
|
|||
// Check suppression
|
|||
if (minNeighbors > 0) |
|||
{ |
|||
filter.Clear(); |
|||
|
|||
// Discard weak rectangles which don't have enough neighbors
|
|||
for (int i = 0; i < output.Length; i++) |
|||
if (neighborCount[i] >= minNeighbors) filter.Add(output[i]); |
|||
|
|||
return filter.ToArray(); |
|||
} |
|||
|
|||
return output; |
|||
} |
|||
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// Detects rectangles which are near and
|
|||
/// assigns similar class labels accordingly.
|
|||
/// </summary>
|
|||
///
|
|||
private void classify(T[] shapes) |
|||
{ |
|||
equals = new int[shapes.Length]; |
|||
for (int i = 0; i < equals.Length; i++) |
|||
equals[i] = -1; |
|||
|
|||
labels = new int[shapes.Length]; |
|||
for (int i = 0; i < labels.Length; i++) |
|||
labels[i] = i; |
|||
|
|||
classCount = 0; |
|||
|
|||
// If two rectangles are near, or contained in
|
|||
// each other, merge then in a single rectangle
|
|||
for (int i = 0; i < shapes.Length - 1; i++) |
|||
{ |
|||
for (int j = i + 1; j < shapes.Length; j++) |
|||
{ |
|||
if (Near(shapes[i], shapes[j])) |
|||
merge(labels[i], labels[j]); |
|||
} |
|||
} |
|||
|
|||
// Count the number of classes and centroids
|
|||
int[] centroids = new int[shapes.Length]; |
|||
for (int i = 0; i < centroids.Length; i++) |
|||
if (equals[i] == -1) centroids[i] = classCount++; |
|||
|
|||
// Classify all rectangles with their labels
|
|||
for (int i = 0; i < shapes.Length; i++) |
|||
{ |
|||
int root = labels[i]; |
|||
while (equals[root] != -1) |
|||
root = equals[root]; |
|||
|
|||
labels[i] = centroids[root]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Merges two labels.
|
|||
/// </summary>
|
|||
///
|
|||
private void merge(int label1, int label2) |
|||
{ |
|||
int root1 = label1; |
|||
int root2 = label2; |
|||
|
|||
// Get the roots associated with the two labels
|
|||
while (equals[root1] != -1) root1 = equals[root1]; |
|||
while (equals[root2] != -1) root2 = equals[root2]; |
|||
|
|||
if (root1 == root2) // labels are already connected
|
|||
return; |
|||
|
|||
int minRoot, maxRoot; |
|||
int labelWithMinRoot, labelWithMaxRoot; |
|||
|
|||
if (root1 > root2) |
|||
{ |
|||
maxRoot = root1; |
|||
minRoot = root2; |
|||
|
|||
labelWithMaxRoot = label1; |
|||
labelWithMinRoot = label2; |
|||
} |
|||
else |
|||
{ |
|||
maxRoot = root2; |
|||
minRoot = root1; |
|||
|
|||
labelWithMaxRoot = label2; |
|||
labelWithMinRoot = label1; |
|||
} |
|||
|
|||
equals[maxRoot] = minRoot; |
|||
|
|||
for (int root = maxRoot + 1; root <= labelWithMaxRoot; root++) |
|||
{ |
|||
if (equals[root] == maxRoot) |
|||
equals[root] = minRoot; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// When overridden in a child class, should compute
|
|||
/// whether two given shapes are near. Definition of
|
|||
/// near is up to the implementation.
|
|||
/// </summary>
|
|||
///
|
|||
/// <returns>True if the two shapes are near; false otherwise.</returns>
|
|||
///
|
|||
protected abstract bool Near(T shape1, T shape2); |
|||
|
|||
/// <summary>
|
|||
/// When overridden in a child class, should compute
|
|||
/// an average of the shapes given as parameters.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="labels">The label of each shape.</param>
|
|||
/// <param name="shapes">The shapes themselves.</param>
|
|||
/// <param name="neighborCounts">Should return how many neighbors each shape had.</param>
|
|||
///
|
|||
/// <returns>The averaged shapes found in the given parameters.</returns>
|
|||
///
|
|||
protected abstract T[] Average(int[] labels, T[] shapes, out int[] neighborCounts); |
|||
} |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Xml; |
|||
using System.Xml.Serialization; |
|||
|
|||
/// <summary>
|
|||
/// Cascade of Haar-like features' weak classification stages.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// The Viola-Jones object detection framework is the first object detection framework
|
|||
/// to provide competitive object detection rates in real-time proposed in 2001 by Paul
|
|||
/// Viola and Michael Jones. Although it can be trained to detect a variety of object
|
|||
/// classes, it was motivated primarily by the problem of face detection.</para>
|
|||
///
|
|||
/// <para>
|
|||
/// The implementation of this code has used Viola and Jones' original publication, the
|
|||
/// OpenCV Library and the Marilena Project as reference. OpenCV is released under a BSD
|
|||
/// license, it is free for both academic and commercial use. Please be aware that some
|
|||
/// particular versions of the Haar object detection framework are patented by Viola and
|
|||
/// Jones and may be subject to restrictions for use in commercial applications. </para>
|
|||
///
|
|||
/// <para>
|
|||
/// References:
|
|||
/// <list type="bullet">
|
|||
/// <item><description>
|
|||
/// <a href="http://www.cs.utexas.edu/~grauman/courses/spring2007/395T/papers/viola_cvpr2001.pdf">
|
|||
/// Viola, P. and Jones, M. (2001). Rapid Object Detection using a Boosted Cascade
|
|||
/// of Simple Features.</a></description></item>
|
|||
/// <item><description>
|
|||
/// <a href="http://en.wikipedia.org/wiki/Viola-Jones_object_detection_framework">
|
|||
/// Wikipedia, The Free Encyclopedia. Viola-Jones object detection framework </a>
|
|||
/// </description></item>
|
|||
/// </list></para>
|
|||
/// </remarks>
|
|||
///
|
|||
/// <example>
|
|||
/// <para>
|
|||
/// To load an OpenCV-compatible XML definition for a Haar cascade, you can use HaarCascade's
|
|||
/// <see cref="HaarCascade.FromXml(Stream)">FromXml</see> static method. An example would be:</para>
|
|||
/// <code>
|
|||
/// String path = @"C:\Users\haarcascade-frontalface_alt2.xml";
|
|||
/// HaarCascade cascade1 = HaarCascade.FromXml(path);
|
|||
/// </code>
|
|||
///
|
|||
/// <para>
|
|||
/// After the cascade has been loaded, it is possible to create a new <see cref="HaarObjectDetector"/>
|
|||
/// using the cascade. Please see <see cref="HaarObjectDetector"/> for more details. It is also
|
|||
/// possible to generate embeddable C# definitions from a cascade, avoiding the need to load
|
|||
/// XML files on program startup. Please see <see cref="ToCode(string, string)"/> method or
|
|||
/// <see cref="HaarCascadeWriter"/> class for details.</para>
|
|||
/// </example>
|
|||
///
|
|||
[Serializable] |
|||
public class HaarCascade : ICloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the stages' base width.
|
|||
/// </summary>
|
|||
///
|
|||
public int Width { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the stages' base height.
|
|||
/// </summary>
|
|||
///
|
|||
public int Height { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the classification stages.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarCascadeStage[] Stages { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this cascade has tilted features.
|
|||
/// </summary>
|
|||
///
|
|||
/// <value>
|
|||
/// <c>true</c> if this cascade has tilted features; otherwise, <c>false</c>.
|
|||
/// </value>
|
|||
///
|
|||
public bool HasTiltedFeatures { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar Cascade.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="baseWidth">Base feature width.</param>
|
|||
/// <param name="baseHeight">Base feature height.</param>
|
|||
/// <param name="stages">Haar-like features classification stages.</param>
|
|||
///
|
|||
public HaarCascade(int baseWidth, int baseHeight, HaarCascadeStage[] stages) |
|||
{ |
|||
Width = baseWidth; |
|||
Height = baseHeight; |
|||
Stages = stages; |
|||
|
|||
// check if the classifier has tilted features
|
|||
HasTiltedFeatures = checkTiltedFeatures(stages); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar Cascade.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="baseWidth">Base feature width.</param>
|
|||
/// <param name="baseHeight">Base feature height.</param>
|
|||
///
|
|||
protected HaarCascade(int baseWidth, int baseHeight) |
|||
{ |
|||
Width = baseWidth; |
|||
Height = baseHeight; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Checks if the classifier contains tilted (rotated) features
|
|||
/// </summary>
|
|||
///
|
|||
private static bool checkTiltedFeatures(HaarCascadeStage[] stages) |
|||
{ |
|||
foreach (var stage in stages) |
|||
foreach (var tree in stage.Trees) |
|||
foreach (var node in tree) |
|||
if (node.Feature.Tilted == true) |
|||
return true; |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new object that is a copy of the current instance.
|
|||
/// </summary>
|
|||
///
|
|||
/// <returns>
|
|||
/// A new object that is a copy of this instance.
|
|||
/// </returns>
|
|||
///
|
|||
public object Clone() |
|||
{ |
|||
HaarCascadeStage[] newStages = new HaarCascadeStage[Stages.Length]; |
|||
for (int i = 0; i < newStages.Length; i++) |
|||
newStages[i] = (HaarCascadeStage)Stages[i].Clone(); |
|||
|
|||
HaarCascade r = new HaarCascade(Width, Height); |
|||
r.HasTiltedFeatures = this.HasTiltedFeatures; |
|||
r.Stages = newStages; |
|||
|
|||
return r; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Loads a HaarCascade from a OpenCV-compatible XML file.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="stream">
|
|||
/// A <see cref="Stream"/> containing the file stream
|
|||
/// for the xml definition of the classifier to be loaded.</param>
|
|||
///
|
|||
/// <returns>The HaarCascadeClassifier loaded from the file.</returns>
|
|||
///
|
|||
public static HaarCascade FromXml(Stream stream) |
|||
{ |
|||
return FromXml(new StreamReader(stream)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads a HaarCascade from a OpenCV-compatible XML file.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="path">
|
|||
/// The file path for the xml definition of the classifier to be loaded.</param>
|
|||
///
|
|||
/// <returns>The HaarCascadeClassifier loaded from the file.</returns>
|
|||
///
|
|||
public static HaarCascade FromXml(string path) |
|||
{ |
|||
return FromXml(new StreamReader(path)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads a HaarCascade from a OpenCV-compatible XML file.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="stringReader">
|
|||
/// A <see cref="StringReader"/> containing the file stream
|
|||
/// for the xml definition of the classifier to be loaded.</param>
|
|||
///
|
|||
/// <returns>The HaarCascadeClassifier loaded from the file.</returns>
|
|||
///
|
|||
public static HaarCascade FromXml(TextReader stringReader) |
|||
{ |
|||
XmlTextReader xmlReader = new XmlTextReader(stringReader); |
|||
|
|||
// Gathers the base window size
|
|||
xmlReader.ReadToFollowing("size"); |
|||
string size = xmlReader.ReadElementContentAsString(); |
|||
//xmlReader.ReadToFollowing("height");
|
|||
//int baseHeight = int.Parse(xmlReader.ReadElementContentAsString().Trim(), CultureInfo.InvariantCulture);
|
|||
|
|||
//xmlReader.ReadToFollowing("width");
|
|||
//int baseWidth = int.Parse(xmlReader.ReadElementContentAsString().Trim(), CultureInfo.InvariantCulture);
|
|||
|
|||
|
|||
// Proceeds to load the cascade stages
|
|||
xmlReader.ReadToFollowing("stages"); |
|||
XmlSerializer serializer = new XmlSerializer(typeof(HaarCascadeSerializationObject)); |
|||
var stages = (HaarCascadeSerializationObject)serializer.Deserialize(xmlReader); |
|||
|
|||
// Process base window size
|
|||
string[] s = size.Trim().Split(' '); |
|||
int baseWidth = int.Parse(s[0], CultureInfo.InvariantCulture); |
|||
int baseHeight = int.Parse(s[1], CultureInfo.InvariantCulture); |
|||
|
|||
// Create and return the new cascade
|
|||
return new HaarCascade(baseWidth, baseHeight, stages.Stages); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Saves a HaarCascade to C# code.
|
|||
/// </summary>
|
|||
///
|
|||
public void ToCode(string path, string className) |
|||
{ |
|||
ToCode(new StreamWriter(path), className); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Saves a HaarCascade to C# code.
|
|||
/// </summary>
|
|||
///
|
|||
public void ToCode(TextWriter textWriter, string className) |
|||
{ |
|||
new HaarCascadeWriter(textWriter).Write(this, className); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Xml.Serialization; |
|||
|
|||
/// <summary>
|
|||
/// Haar Cascade Serialization Root. This class is used
|
|||
/// only for XML serialization/deserialization.
|
|||
/// </summary>
|
|||
///
|
|||
[Serializable] |
|||
[XmlRoot(Namespace = "", IsNullable = false, ElementName = "stages")] |
|||
public class HaarCascadeSerializationObject |
|||
{ |
|||
/// <summary>
|
|||
/// The stages retrieved after deserialization.
|
|||
/// </summary>
|
|||
[XmlElement("_")] |
|||
public HaarCascadeStage[] Stages { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Xml.Serialization; |
|||
|
|||
/// <summary>
|
|||
/// Haar Cascade Classifier Stage.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// A Haar Cascade Classifier is composed of several stages. Each stage
|
|||
/// contains a set of classifier trees used in the decision process.
|
|||
/// </remarks>
|
|||
///
|
|||
[Serializable] |
|||
[XmlRoot("_")] |
|||
public class HaarCascadeStage : ICloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the feature trees and its respective
|
|||
/// feature tree nodes which compose this stage.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlArray("trees")] |
|||
[XmlArrayItem("_")] |
|||
[XmlArrayItem("_", NestingLevel = 1)] |
|||
public HaarFeatureNode[][] Trees { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the threshold associated with this stage,
|
|||
/// i.e. the minimum value the classifiers should output
|
|||
/// to decide if the image contains the object or not.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("stage_threshold")] |
|||
public double Threshold { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the index of the parent stage from this stage.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("parent")] |
|||
public int ParentIndex { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the index of the next stage from this stage.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("next")] |
|||
public int NextIndex { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar Cascade Stage.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarCascadeStage() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar Cascade Stage.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarCascadeStage(double threshold) |
|||
{ |
|||
this.Threshold = threshold; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar Cascade Stage.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarCascadeStage(double threshold, int parentIndex, int nextIndex) |
|||
{ |
|||
this.Threshold = threshold; |
|||
this.ParentIndex = parentIndex; |
|||
this.NextIndex = nextIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Classifies an image as having the searched object or not.
|
|||
/// </summary>
|
|||
///
|
|||
public bool Classify(FastBitmap image, int x, int y, double factor) |
|||
{ |
|||
double value = 0; |
|||
|
|||
// For each feature in the feature tree of the current stage,
|
|||
foreach (HaarFeatureNode[] tree in Trees) |
|||
{ |
|||
int current = 0; |
|||
|
|||
do |
|||
{ |
|||
// Get the feature node from the tree
|
|||
HaarFeatureNode node = tree[current]; |
|||
|
|||
// Evaluate the node's feature
|
|||
double sum = node.Feature.GetSum(image, x, y); |
|||
|
|||
// And increase the value accumulator
|
|||
if (sum < node.Threshold * factor) |
|||
{ |
|||
value += node.LeftValue; |
|||
current = node.LeftNodeIndex; |
|||
} |
|||
else |
|||
{ |
|||
value += node.RightValue; |
|||
current = node.RightNodeIndex; |
|||
} |
|||
|
|||
} while (current > 0); |
|||
|
|||
// Stop early if we have already surpassed the stage threshold value.
|
|||
//if (value > this.Threshold) return true;
|
|||
} |
|||
|
|||
// After we have evaluated the output for the
|
|||
// current stage, we will check if the value
|
|||
// is still lesser than the stage threshold.
|
|||
if (value < this.Threshold) |
|||
{ |
|||
// If it is, the stage has rejected the current
|
|||
// image and it doesn't contains our object.
|
|||
return false; |
|||
} |
|||
else |
|||
{ |
|||
// The stage has accepted the current image
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Creates a new object that is a copy of the current instance.
|
|||
/// </summary>
|
|||
///
|
|||
/// <returns>
|
|||
/// A new object that is a copy of this instance.
|
|||
/// </returns>
|
|||
///
|
|||
public object Clone() |
|||
{ |
|||
HaarFeatureNode[][] newTrees = new HaarFeatureNode[Trees.Length][]; |
|||
|
|||
for (int i = 0; i < newTrees.Length; i++) |
|||
{ |
|||
HaarFeatureNode[] tree = Trees[i]; |
|||
HaarFeatureNode[] newTree = newTrees[i] = |
|||
new HaarFeatureNode[tree.Length]; |
|||
|
|||
for (int j = 0; j < newTree.Length; j++) |
|||
newTree[j] = (HaarFeatureNode)tree[j].Clone(); |
|||
} |
|||
|
|||
HaarCascadeStage r = new HaarCascadeStage(); |
|||
r.NextIndex = NextIndex; |
|||
r.ParentIndex = ParentIndex; |
|||
r.Threshold = Threshold; |
|||
r.Trees = newTrees; |
|||
|
|||
return r; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
|
|||
/// <summary>
|
|||
/// Automatic transcriber for Haar cascades.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// This class can be used to generate code-only definitions for Haar cascades,
|
|||
/// avoiding the need for loading and parsing XML files during application startup.
|
|||
/// This class generates C# code for a class inheriting from <see cref="HaarCascade"/>
|
|||
/// which may be used to create a <see cref="HaarObjectDetector"/>.
|
|||
/// </remarks>
|
|||
///
|
|||
public class HaarCascadeWriter |
|||
{ |
|||
private TextWriter writer; |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new <see cref="HaarCascadeWriter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
///
|
|||
public HaarCascadeWriter(TextWriter stream) |
|||
{ |
|||
this.writer = stream; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the specified cascade.
|
|||
/// </summary>
|
|||
/// <param name="cascade">The cascade to write.</param>
|
|||
/// <param name="className">The name for the generated class.</param>
|
|||
///
|
|||
public void Write(HaarCascade cascade, string className) |
|||
{ |
|||
for (int i = 0; i < cascade.Stages.Length; i++) |
|||
for (int j = 0; j < cascade.Stages[i].Trees.Length; j++) |
|||
if (cascade.Stages[i].Trees[j].Length != 1) |
|||
throw new ArgumentException("Only cascades with single node trees are currently supported."); |
|||
|
|||
|
|||
writer.WriteLine("// This file has been automatically transcribed by the"); |
|||
writer.WriteLine("//"); |
|||
writer.WriteLine("// Accord Vision Library"); |
|||
writer.WriteLine("// The Accord.NET Framework"); |
|||
writer.WriteLine("// http://accord-framework.net"); |
|||
writer.WriteLine("//"); |
|||
writer.WriteLine(); |
|||
writer.WriteLine("namespace HaarCascades"); |
|||
writer.WriteLine("{"); |
|||
writer.WriteLine(" using System.CodeDom.Compiler;"); |
|||
writer.WriteLine(" using System.Collections.Generic;"); |
|||
writer.WriteLine(); |
|||
writer.WriteLine(" /// <summary>"); |
|||
writer.WriteLine(" /// Automatically generated haar-cascade definition"); |
|||
writer.WriteLine(" /// to use with the Accord.NET Framework object detectors."); |
|||
writer.WriteLine(" /// </summary>"); |
|||
writer.WriteLine(" /// "); |
|||
writer.WriteLine(" [GeneratedCode(\"Accord.NET HaarCascadeWriter\", \"2.7\")]"); |
|||
writer.WriteLine(" public class {0} : Accord.Vision.Detection.HaarCascade", className); |
|||
writer.WriteLine(" {"); |
|||
writer.WriteLine(); |
|||
writer.WriteLine(" /// <summary>"); |
|||
writer.WriteLine(" /// Automatically generated transcription"); |
|||
writer.WriteLine(" /// </summary>"); |
|||
writer.WriteLine(" public {0}()", className); |
|||
writer.WriteLine(" : base({0}, {1})", cascade.Width, cascade.Height); |
|||
writer.WriteLine(" {"); |
|||
writer.WriteLine(" List<HaarCascadeStage> stages = new List<HaarCascadeStage>();"); |
|||
writer.WriteLine(" List<HaarFeatureNode[]> nodes;"); |
|||
writer.WriteLine(" HaarCascadeStage stage;"); |
|||
writer.WriteLine(); |
|||
|
|||
if (cascade.HasTiltedFeatures) |
|||
{ |
|||
writer.WriteLine(" HasTiltedFeatures = true;"); |
|||
writer.WriteLine(); |
|||
} |
|||
|
|||
// Write cascade stages
|
|||
for (int i = 0; i < cascade.Stages.Length; i++) |
|||
writeStage(i, cascade.Stages[i]); |
|||
|
|||
writer.WriteLine(); |
|||
writer.WriteLine(" Stages = stages.ToArray();"); |
|||
writer.WriteLine(" }"); |
|||
writer.WriteLine(" }"); |
|||
writer.WriteLine("}"); |
|||
} |
|||
|
|||
private void writeStage(int i, HaarCascadeStage stage) |
|||
{ |
|||
writer.WriteLine(" #region Stage {0}", i); |
|||
writer.WriteLine(" stage = new HaarCascadeStage({0}, {1}, {2}); nodes = new List<HaarFeatureNode[]>();", |
|||
stage.Threshold.ToString("R", NumberFormatInfo.InvariantInfo), |
|||
stage.ParentIndex, stage.NextIndex); |
|||
|
|||
// Write stage trees
|
|||
for (int j = 0; j < stage.Trees.Length; j++) |
|||
writeTrees(stage, j); |
|||
|
|||
writer.WriteLine(" stage.Trees = nodes.ToArray(); stages.Add(stage);"); |
|||
writer.WriteLine(" #endregion"); |
|||
writer.WriteLine(); |
|||
} |
|||
|
|||
private void writeTrees(HaarCascadeStage stage, int j) |
|||
{ |
|||
writer.Write(" nodes.Add(new[] { "); |
|||
|
|||
// Assume trees have single node
|
|||
writeFeature(stage.Trees[j][0]); |
|||
|
|||
writer.WriteLine(" });"); |
|||
} |
|||
|
|||
private void writeFeature(HaarFeatureNode node) |
|||
{ |
|||
|
|||
writer.Write("new HaarFeatureNode({0}, {1}, {2}, ", |
|||
node.Threshold.ToString("R", NumberFormatInfo.InvariantInfo), |
|||
node.LeftValue.ToString("R", NumberFormatInfo.InvariantInfo), |
|||
node.RightValue.ToString("R", NumberFormatInfo.InvariantInfo)); |
|||
|
|||
if (node.Feature.Tilted) |
|||
writer.Write("true, "); |
|||
|
|||
// Write Haar-like rectangular features
|
|||
for (int k = 0; k < node.Feature.Rectangles.Length; k++) |
|||
{ |
|||
writeRectangle(node.Feature.Rectangles[k]); |
|||
|
|||
if (k < node.Feature.Rectangles.Length - 1) |
|||
writer.Write(", "); |
|||
} |
|||
|
|||
writer.Write(" )"); |
|||
} |
|||
|
|||
private void writeRectangle(HaarRectangle rectangle) |
|||
{ |
|||
writer.Write("new int[] {{ {0}, {1}, {2}, {3}, {4} }}", |
|||
rectangle.X.ToString(NumberFormatInfo.InvariantInfo), |
|||
rectangle.Y.ToString(NumberFormatInfo.InvariantInfo), |
|||
rectangle.Width.ToString(NumberFormatInfo.InvariantInfo), |
|||
rectangle.Height.ToString(NumberFormatInfo.InvariantInfo), |
|||
rectangle.Weight.ToString("R", NumberFormatInfo.InvariantInfo)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
// Accord Vision Library
|
|||
// The Accord.NET Framework (LGPL)
|
|||
// http://accord-framework.net
|
|||
//
|
|||
// Copyright © César Souza, 2009-2015
|
|||
// cesarsouza at gmail.com
|
|||
//
|
|||
// Copyright © Masakazu Ohtsuka, 2008
|
|||
// This work is partially based on the original Project Marilena code,
|
|||
// distributed under a 2-clause BSD License. Details are listed below.
|
|||
//
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without modification,
|
|||
// are permitted provided that the following conditions are met:
|
|||
//
|
|||
// * Redistribution's of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// * Redistribution's 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.
|
|||
//
|
|||
// 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 Intel Corporation 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.
|
|||
//
|
|||
|
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Drawing; |
|||
|
|||
/// <summary>
|
|||
/// Strong classifier based on a weaker cascade of
|
|||
/// classifiers using Haar-like rectangular features.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// The Viola-Jones object detection framework is the first object detection framework
|
|||
/// to provide competitive object detection rates in real-time proposed in 2001 by Paul
|
|||
/// Viola and Michael Jones. Although it can be trained to detect a variety of object
|
|||
/// classes, it was motivated primarily by the problem of face detection.</para>
|
|||
///
|
|||
/// <para>
|
|||
/// The implementation of this code has used Viola and Jones' original publication, the
|
|||
/// OpenCV Library and the Marilena Project as reference. OpenCV is released under a BSD
|
|||
/// license, it is free for both academic and commercial use. Please be aware that some
|
|||
/// particular versions of the Haar object detection framework are patented by Viola and
|
|||
/// Jones and may be subject to restrictions for use in commercial applications. The code
|
|||
/// has been implemented with full support for tilted Haar features.</para>
|
|||
///
|
|||
/// <para>
|
|||
/// References:
|
|||
/// <list type="bullet">
|
|||
/// <item><description>
|
|||
/// <a href="http://www.cs.utexas.edu/~grauman/courses/spring2007/395T/papers/viola_cvpr2001.pdf">
|
|||
/// Viola, P. and Jones, M. (2001). Rapid Object Detection using a Boosted Cascade
|
|||
/// of Simple Features.</a></description></item>
|
|||
/// <item><description>
|
|||
/// <a href="http://en.wikipedia.org/wiki/Viola-Jones_object_detection_framework">
|
|||
/// http://en.wikipedia.org/wiki/Viola-Jones_object_detection_framework</a>
|
|||
/// </description></item>
|
|||
/// </list>
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
///
|
|||
[Serializable] |
|||
public class HaarClassifier |
|||
{ |
|||
private HaarCascade cascade; |
|||
|
|||
private float invArea; |
|||
private float scale; |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Constructs a new classifier.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarClassifier(HaarCascade cascade) |
|||
{ |
|||
this.cascade = cascade; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new classifier.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarClassifier(int baseWidth, int baseHeight, HaarCascadeStage[] stages) |
|||
: this(new HaarCascade(baseWidth, baseHeight, stages)) { } |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Gets the cascade of weak-classifiers
|
|||
/// used by this strong classifier.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarCascade Cascade |
|||
{ |
|||
get { return cascade; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scale of the search window
|
|||
/// being currently used by the classifier.
|
|||
/// </summary>
|
|||
///
|
|||
public float Scale |
|||
{ |
|||
get { return this.scale; } |
|||
set |
|||
{ |
|||
if (this.scale == value) |
|||
return; |
|||
|
|||
this.scale = value; |
|||
this.invArea = 1f / (cascade.Width * cascade.Height * scale * scale); |
|||
|
|||
// For each stage in the cascade
|
|||
foreach (HaarCascadeStage stage in cascade.Stages) |
|||
{ |
|||
// For each tree in the cascade
|
|||
foreach (HaarFeatureNode[] tree in stage.Trees) |
|||
{ |
|||
// For each feature node in the tree
|
|||
foreach (HaarFeatureNode node in tree) |
|||
{ |
|||
// Set the scale and weight for the node feature
|
|||
node.Feature.SetScaleAndWeight(value, invArea); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Detects the presence of an object in a given window.
|
|||
/// </summary>
|
|||
public bool Compute(FastBitmap image, Rectangle rectangle) |
|||
{ |
|||
int x = rectangle.X; |
|||
int y = rectangle.Y; |
|||
int w = rectangle.Width; |
|||
int h = rectangle.Height; |
|||
|
|||
double mean = image.GetSum(x, y, w, h) * invArea; |
|||
double var = image.GetSum2(x, y, w, h) * invArea - (mean * mean); |
|||
|
|||
double sdev = (var >= 0) ? Math.Sqrt(var) : 1; |
|||
|
|||
// For each classification stage in the cascade
|
|||
foreach (HaarCascadeStage stage in cascade.Stages) |
|||
{ |
|||
// Check if the stage has rejected the image
|
|||
if (stage.Classify(image, x, y, sdev) == false) |
|||
{ |
|||
return false; // The image has been rejected.
|
|||
} |
|||
} |
|||
|
|||
// If the object has gone all stages and has not
|
|||
// been rejected, the object has been detected.
|
|||
return true; // The image has been detected.
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,223 @@ |
|||
// Accord Vision Library
|
|||
// The Accord.NET Framework (LGPL)
|
|||
// http://accord-framework.net
|
|||
//
|
|||
// Copyright © César Souza, 2009-2015
|
|||
// cesarsouza at gmail.com
|
|||
//
|
|||
// This library is free software; you can redistribute it and/or
|
|||
// modify it under the terms of the GNU Lesser General Public
|
|||
// License as published by the Free Software Foundation; either
|
|||
// version 2.1 of the License, or (at your option) any later version.
|
|||
//
|
|||
// This library is distributed in the hope that it will be useful,
|
|||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
// Lesser General Public License for more details.
|
|||
//
|
|||
// You should have received a copy of the GNU Lesser General Public
|
|||
// License along with this library; if not, write to the Free Software
|
|||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
//
|
|||
|
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Xml; |
|||
using System.Xml.Schema; |
|||
using System.Xml.Serialization; |
|||
|
|||
/// <summary>
|
|||
/// Rectangular Haar-like feature container.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// References:
|
|||
/// - http://en.wikipedia.org/wiki/Haar-like_features#Rectangular_Haar-like_features
|
|||
/// </remarks>
|
|||
[Serializable] |
|||
public sealed class HaarFeature : IXmlSerializable, ICloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets whether this feature is tilted.
|
|||
/// </summary>
|
|||
///
|
|||
public bool Tilted { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Haar rectangles for this feature.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarRectangle[] Rectangles { get; set; } |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar-like feature.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarFeature() |
|||
{ |
|||
this.Rectangles = new HaarRectangle[2]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar-like feature.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarFeature(params HaarRectangle[] rectangles) |
|||
{ |
|||
this.Rectangles = rectangles; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar-like feature.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarFeature(params int[][] rectangles) |
|||
: this(false, rectangles) { } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar-like feature.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarFeature(bool tilted, params int[][] rectangles) |
|||
{ |
|||
this.Tilted = tilted; |
|||
this.Rectangles = new HaarRectangle[rectangles.Length]; |
|||
for (int i = 0; i < rectangles.Length; i++) |
|||
this.Rectangles[i] = new HaarRectangle(rectangles[i]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the sum of the areas of the rectangular features in an integral image.
|
|||
/// </summary>
|
|||
/// <param name="image">The <see cref="FastBitmap"/> containing integral rectangle information.</param>
|
|||
/// <param name="x">The x coordinate.</param>
|
|||
/// <param name="y">The y coordinate.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="double"/> representing the sum.
|
|||
/// </returns>
|
|||
public double GetSum(FastBitmap image, int x, int y) |
|||
{ |
|||
double sum = 0.0; |
|||
|
|||
if (!Tilted) |
|||
{ |
|||
// Compute the sum for a standard feature
|
|||
foreach (HaarRectangle rect in Rectangles) |
|||
{ |
|||
sum += image.GetSum(x + rect.ScaledX, y + rect.ScaledY, |
|||
rect.ScaledWidth, rect.ScaledHeight) * rect.ScaledWeight; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Compute the sum for a rotated feature
|
|||
foreach (HaarRectangle rect in Rectangles) |
|||
{ |
|||
sum += image.GetSumT(x + rect.ScaledX, y + rect.ScaledY, |
|||
rect.ScaledWidth, rect.ScaledHeight) * rect.ScaledWeight; |
|||
} |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the scale and weight of a Haar-like rectangular feature container.
|
|||
/// </summary>
|
|||
///
|
|||
public void SetScaleAndWeight(float scale, float weight) |
|||
{ |
|||
// Manual loop unfolding
|
|||
if (Rectangles.Length == 2) |
|||
{ |
|||
HaarRectangle a = Rectangles[0]; |
|||
HaarRectangle b = Rectangles[1]; |
|||
|
|||
b.ScaleRectangle(scale); |
|||
b.ScaleWeight(weight); |
|||
|
|||
a.ScaleRectangle(scale); |
|||
a.ScaledWeight = -(b.Area * b.ScaledWeight) / a.Area; |
|||
} |
|||
else // rectangles.Length == 3
|
|||
{ |
|||
HaarRectangle a = Rectangles[0]; |
|||
HaarRectangle b = Rectangles[1]; |
|||
HaarRectangle c = Rectangles[2]; |
|||
|
|||
c.ScaleRectangle(scale); |
|||
c.ScaleWeight(weight); |
|||
|
|||
b.ScaleRectangle(scale); |
|||
b.ScaleWeight(weight); |
|||
|
|||
a.ScaleRectangle(scale); |
|||
a.ScaledWeight = -(b.Area * b.ScaledWeight |
|||
+ c.Area * c.ScaledWeight) / (a.Area); |
|||
} |
|||
} |
|||
|
|||
|
|||
#region IXmlSerializable Members
|
|||
|
|||
XmlSchema IXmlSerializable.GetSchema() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
void IXmlSerializable.ReadXml(XmlReader reader) |
|||
{ |
|||
reader.ReadStartElement("feature"); |
|||
|
|||
reader.ReadToFollowing("rects"); |
|||
reader.ReadToFollowing("_"); |
|||
|
|||
var rec = new List<HaarRectangle>(); |
|||
while (reader.Name == "_") |
|||
{ |
|||
string str = reader.ReadElementContentAsString(); |
|||
rec.Add(HaarRectangle.Parse(str)); |
|||
|
|||
while (reader.Name != "_" && reader.Name != "tilted" && |
|||
reader.NodeType != XmlNodeType.EndElement) |
|||
reader.Read(); |
|||
} |
|||
|
|||
Rectangles = rec.ToArray(); |
|||
|
|||
reader.ReadToFollowing("tilted", reader.BaseURI); |
|||
Tilted = reader.ReadElementContentAsInt() == 1; |
|||
|
|||
reader.ReadEndElement(); |
|||
} |
|||
|
|||
void IXmlSerializable.WriteXml(XmlWriter writer) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// Creates a new object that is a copy of the current instance.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// A new object that is a copy of this instance.
|
|||
/// </returns>
|
|||
public object Clone() |
|||
{ |
|||
HaarRectangle[] newRectangles = new HaarRectangle[this.Rectangles.Length]; |
|||
for (int i = 0; i < newRectangles.Length; i++) |
|||
{ |
|||
HaarRectangle rect = Rectangles[i]; |
|||
newRectangles[i] = new HaarRectangle(rect.X, rect.Y, rect.Width, rect.Height, rect.Weight); |
|||
} |
|||
|
|||
return new HaarFeature { Rectangles = newRectangles, Tilted = this.Tilted }; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,122 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Xml.Serialization; |
|||
|
|||
/// <summary>
|
|||
/// Haar Cascade Feature Tree Node.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// The Feature Node is a node belonging to a feature tree,
|
|||
/// containing information about child nodes and an associated
|
|||
/// <see cref="HaarFeature"/>.
|
|||
/// </remarks>
|
|||
///
|
|||
[Serializable] |
|||
public class HaarFeatureNode : ICloneable |
|||
{ |
|||
private int rightNodeIndex = -1; |
|||
private int leftNodeIndex = -1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the threshold for this feature.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("threshold")] |
|||
public double Threshold { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the left value for this feature.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("left_val")] |
|||
public double LeftValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the right value for this feature.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("right_val")] |
|||
public double RightValue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the left node index for this feature.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("left_node")] |
|||
public int LeftNodeIndex |
|||
{ |
|||
get { return leftNodeIndex; } |
|||
set { leftNodeIndex = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the right node index for this feature.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("right_node")] |
|||
public int RightNodeIndex |
|||
{ |
|||
get { return rightNodeIndex; } |
|||
set { rightNodeIndex = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the feature associated with this node.
|
|||
/// </summary>
|
|||
///
|
|||
[XmlElement("feature", IsNullable = false)] |
|||
public HaarFeature Feature { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new feature tree node.
|
|||
/// </summary>
|
|||
public HaarFeatureNode() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new feature tree node.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarFeatureNode(double threshold, double leftValue, double rightValue, params int[][] rectangles) |
|||
: this(threshold, leftValue, rightValue, false, rectangles) { } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new feature tree node.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarFeatureNode(double threshold, double leftValue, double rightValue, bool tilted, params int[][] rectangles) |
|||
{ |
|||
this.Feature = new HaarFeature(tilted, rectangles); |
|||
this.Threshold = threshold; |
|||
this.LeftValue = leftValue; |
|||
this.RightValue = rightValue; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Creates a new object that is a copy of the current instance.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// A new object that is a copy of this instance.
|
|||
/// </returns>
|
|||
///
|
|||
public object Clone() |
|||
{ |
|||
HaarFeatureNode r = new HaarFeatureNode(); |
|||
|
|||
r.Feature = (HaarFeature)Feature.Clone(); |
|||
r.Threshold = Threshold; |
|||
|
|||
r.RightValue = RightValue; |
|||
r.LeftValue = LeftValue; |
|||
|
|||
r.LeftNodeIndex = leftNodeIndex; |
|||
r.RightNodeIndex = rightNodeIndex; |
|||
|
|||
return r; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="HaarRectangle.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Represents a rectangle which can be at any position and scale within the original image.
|
|||
// Based on original code found in the Accord Framework at <see href="https://github.com/accord-net/framework" />
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Globalization; |
|||
|
|||
/// <summary>
|
|||
/// Represents a rectangle which can be at any position and scale within the original image.
|
|||
/// Based on original code found in the Accord Framework at <see href="https://github.com/accord-net/framework"/>
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class HaarRectangle : ICloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HaarRectangle"/> class.
|
|||
/// </summary>
|
|||
/// <param name="values">
|
|||
/// The values for this rectangle.
|
|||
/// </param>
|
|||
public HaarRectangle(int[] values) |
|||
{ |
|||
this.X = values[0]; |
|||
this.Y = values[1]; |
|||
this.Width = values[2]; |
|||
this.Height = values[3]; |
|||
this.Weight = values[4]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HaarRectangle"/> class.
|
|||
/// </summary>
|
|||
/// <param name="x">
|
|||
/// The x coordinate marking the top-left point to apply to this rectangle.
|
|||
/// </param>
|
|||
/// <param name="y">
|
|||
/// The y coordinate marking the top-left point to apply to this rectangle.
|
|||
/// </param>
|
|||
/// <param name="width">
|
|||
/// The width to apply to this rectangle.
|
|||
/// </param>
|
|||
/// <param name="height">
|
|||
/// The height to apply to this rectangle.
|
|||
/// </param>
|
|||
/// <param name="weight">
|
|||
/// The weight to apply to this rectangle.
|
|||
/// </param>
|
|||
public HaarRectangle(int x, int y, int width, int height, float weight) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.Weight = weight; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Prevents a default instance of the <see cref="HaarRectangle"/> class from being created.
|
|||
/// </summary>
|
|||
private HaarRectangle() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the x-coordinate of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int X { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the y-coordinate of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int Y { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the width of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int Width { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int Height { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the weight of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public float Weight { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaled x-coordinate of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int ScaledX { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaled y-coordinate of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int ScaledY { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaled width of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int ScaledWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaled height of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public int ScaledHeight { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaled weight of this Haar feature rectangle.
|
|||
/// </summary>
|
|||
public float ScaledWeight { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the area of this rectangle.
|
|||
/// </summary>
|
|||
public int Area |
|||
{ |
|||
get { return this.ScaledWidth * this.ScaledHeight; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="HaarRectangle"/> from a string representation.
|
|||
/// </summary>
|
|||
/// <param name="value">
|
|||
/// The value to parse.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The <see cref="HaarRectangle"/>.
|
|||
/// </returns>
|
|||
public static HaarRectangle Parse(string value) |
|||
{ |
|||
string[] values = value.Trim().Split(' '); |
|||
|
|||
int x = int.Parse(values[0], CultureInfo.InvariantCulture); |
|||
int y = int.Parse(values[1], CultureInfo.InvariantCulture); |
|||
int w = int.Parse(values[2], CultureInfo.InvariantCulture); |
|||
int h = int.Parse(values[3], CultureInfo.InvariantCulture); |
|||
float weight = float.Parse(values[4], CultureInfo.InvariantCulture); |
|||
|
|||
return new HaarRectangle(x, y, w, h, weight); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales the values of this rectangle.
|
|||
/// </summary>
|
|||
/// <param name="scale">
|
|||
/// The scale.
|
|||
/// </param>
|
|||
public void ScaleRectangle(float scale) |
|||
{ |
|||
this.ScaledX = (int)(this.X * scale); |
|||
this.ScaledY = (int)(this.Y * scale); |
|||
this.ScaledWidth = (int)(this.Width * scale); |
|||
this.ScaledHeight = (int)(this.Height * scale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales the weight of this rectangle.
|
|||
/// </summary>
|
|||
/// <param name="scale">
|
|||
/// The scale.
|
|||
/// </param>
|
|||
public void ScaleWeight(float scale) |
|||
{ |
|||
this.ScaledWeight = this.Weight * scale; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new object that is a copy of the current instance.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// A new object that is a copy of this instance.
|
|||
/// </returns>
|
|||
public object Clone() |
|||
{ |
|||
HaarRectangle r = new HaarRectangle |
|||
{ |
|||
Height = this.Height, |
|||
ScaledHeight = this.ScaledHeight, |
|||
ScaledWeight = this.ScaledWeight, |
|||
ScaledWidth = this.ScaledWidth, |
|||
ScaledX = this.ScaledX, |
|||
ScaledY = this.ScaledY, |
|||
Weight = this.Weight, |
|||
Width = this.Width, |
|||
X = this.X, |
|||
Y = this.Y |
|||
}; |
|||
|
|||
return r; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,594 @@ |
|||
// Accord Vision Library
|
|||
// The Accord.NET Framework (LGPL)
|
|||
// http://accord-framework.net
|
|||
//
|
|||
// Copyright © César Souza, 2009-2015
|
|||
// cesarsouza at gmail.com
|
|||
//
|
|||
// Copyright © Masakazu Ohtsuka, 2008
|
|||
// This work is partially based on the original Project Marilena code,
|
|||
// distributed under a 2-clause BSD License. Details are listed below.
|
|||
//
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without modification,
|
|||
// are permitted provided that the following conditions are met:
|
|||
//
|
|||
// * Redistribution's of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// * Redistribution's 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.
|
|||
//
|
|||
// 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 Intel Corporation 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.
|
|||
//
|
|||
|
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
using System.Threading.Tasks; |
|||
|
|||
using ImageProcessor.Common.Extensions; |
|||
using ImageProcessor.Imaging.Colors; |
|||
|
|||
/// <summary>
|
|||
/// Viola-Jones Object Detector based on Haar-like features.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
///
|
|||
/// <para>
|
|||
/// The Viola-Jones object detection framework is the first object detection framework
|
|||
/// to provide competitive object detection rates in real-time proposed in 2001 by Paul
|
|||
/// Viola and Michael Jones. Although it can be trained to detect a variety of object
|
|||
/// classes, it was motivated primarily by the problem of face detection.</para>
|
|||
///
|
|||
/// <para>
|
|||
/// The implementation of this code has used Viola and Jones' original publication, the
|
|||
/// OpenCV Library and the Marilena Project as reference. OpenCV is released under a BSD
|
|||
/// license, it is free for both academic and commercial use. Please be aware that some
|
|||
/// particular versions of the Haar object detection framework are patented by Viola and
|
|||
/// Jones and may be subject to restrictions for use in commercial applications. The code
|
|||
/// has been implemented with full support for tilted Haar features from the ground up.</para>
|
|||
///
|
|||
/// <para>
|
|||
/// References:
|
|||
/// <list type="bullet">
|
|||
/// <item><description>
|
|||
/// <a href="http://www.cs.utexas.edu/~grauman/courses/spring2007/395T/papers/viola_cvpr2001.pdf">
|
|||
/// Viola, P. and Jones, M. (2001). Rapid Object Detection using a Boosted Cascade
|
|||
/// of Simple Features.</a></description></item>
|
|||
/// <item><description>
|
|||
/// <a href="http://en.wikipedia.org/wiki/Viola-Jones_object_detection_framework">
|
|||
/// http://en.wikipedia.org/wiki/Viola-Jones_object_detection_framework</a>
|
|||
/// </description></item>
|
|||
/// </list>
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
///
|
|||
public class HaarObjectDetector |
|||
{ |
|||
|
|||
private List<Rectangle> detectedObjects; |
|||
private HaarClassifier classifier; |
|||
|
|||
private ObjectDetectorSearchMode searchMode = ObjectDetectorSearchMode.NoOverlap; |
|||
private ObjectDetectorScalingMode scalingMode = ObjectDetectorScalingMode.GreaterToSmaller; |
|||
|
|||
// TODO: Support ROI
|
|||
// private Rectangle searchWindow;
|
|||
|
|||
private Size minSize = new Size(15, 15); |
|||
private Size maxSize = new Size(500, 500); |
|||
private float factor = 1.2f; |
|||
private int channel = new Color32().R; |
|||
|
|||
private Rectangle[] lastObjects; |
|||
private int steadyThreshold = 2; |
|||
|
|||
private int baseWidth; |
|||
private int baseHeight; |
|||
|
|||
private int lastWidth; |
|||
private int lastHeight; |
|||
private float[] steps; |
|||
|
|||
private RectangleGroupMatching match; |
|||
|
|||
#region Constructors
|
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar object detector.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="cascade">
|
|||
/// The <see cref="HaarCascade"/> to use in the detector's classifier.
|
|||
/// For the default face cascade, please take a look on
|
|||
/// <see cref="Cascades.FaceHaarCascade"/>.
|
|||
/// </param>
|
|||
///
|
|||
public HaarObjectDetector(HaarCascade cascade) |
|||
: this(cascade, 15) { } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar object detector.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="cascade">
|
|||
/// The <see cref="HaarCascade"/> to use in the detector's classifier.
|
|||
/// For the default face cascade, please take a look on
|
|||
/// <see cref="Cascades.FaceHaarCascade"/>.</param>
|
|||
/// <param name="minSize">
|
|||
/// Minimum window size to consider when searching for
|
|||
/// objects. Default value is <c>15</c>.</param>
|
|||
///
|
|||
public HaarObjectDetector(HaarCascade cascade, int minSize) |
|||
: this(cascade, minSize, ObjectDetectorSearchMode.NoOverlap) { } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar object detector.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="cascade">
|
|||
/// The <see cref="HaarCascade"/> to use in the detector's classifier.
|
|||
/// For the default face cascade, please take a look on
|
|||
/// <see cref="Cascades.FaceHaarCascade"/>.
|
|||
/// </param>
|
|||
/// <param name="minSize">
|
|||
/// Minimum window size to consider when searching for
|
|||
/// objects. Default value is <c>15</c>.</param>
|
|||
/// <param name="searchMode">The <see cref="ObjectDetectorSearchMode"/> to use
|
|||
/// during search. Please see documentation of <see cref="ObjectDetectorSearchMode"/>
|
|||
/// for details. Default value is <see cref="ObjectDetectorSearchMode.NoOverlap"/></param>
|
|||
///
|
|||
public HaarObjectDetector(HaarCascade cascade, int minSize, ObjectDetectorSearchMode searchMode) |
|||
: this(cascade, minSize, searchMode, 1.2f) { } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar object detector.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="cascade">
|
|||
/// The <see cref="HaarCascade"/> to use in the detector's classifier.
|
|||
/// For the default face cascade, please take a look on
|
|||
/// <see cref="Cascades.FaceHaarCascade"/>.</param>
|
|||
/// <param name="minSize">
|
|||
/// Minimum window size to consider when searching for
|
|||
/// objects. Default value is <c>15</c>.</param>
|
|||
/// <param name="searchMode">
|
|||
/// The <see cref="ObjectDetectorSearchMode"/> to use
|
|||
/// during search. Please see documentation of <see cref="ObjectDetectorSearchMode"/>
|
|||
/// for details. Default value is <see cref="ObjectDetectorSearchMode.NoOverlap"/></param>
|
|||
/// <param name="scaleFactor">The re-scaling factor to use when re-scaling the window during search.</param>
|
|||
///
|
|||
public HaarObjectDetector(HaarCascade cascade, int minSize, |
|||
ObjectDetectorSearchMode searchMode, float scaleFactor) |
|||
: this(cascade, minSize, searchMode, scaleFactor, ObjectDetectorScalingMode.SmallerToGreater) { } |
|||
|
|||
/// <summary>
|
|||
/// Constructs a new Haar object detector.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="cascade">
|
|||
/// The <see cref="HaarCascade"/> to use in the detector's classifier.
|
|||
/// For the default face cascade, please take a look on
|
|||
/// <see cref="Cascades.FaceHaarCascade"/>. </param>
|
|||
/// <param name="minSize">
|
|||
/// Minimum window size to consider when searching for
|
|||
/// objects. Default value is <c>15</c>.</param>
|
|||
/// <param name="searchMode">The <see cref="ObjectDetectorSearchMode"/> to use
|
|||
/// during search. Please see documentation of <see cref="ObjectDetectorSearchMode"/>
|
|||
/// for details. Default is <see cref="ObjectDetectorSearchMode.NoOverlap"/>.</param>
|
|||
/// <param name="scaleFactor">The scaling factor to rescale the window
|
|||
/// during search. Default value is <c>1.2f</c>.</param>
|
|||
/// <param name="scalingMode">The <see cref="ObjectDetectorScalingMode"/> to use
|
|||
/// when re-scaling the search window during search. Default is
|
|||
/// <see cref="ObjectDetectorScalingMode.SmallerToGreater"/>.</param>
|
|||
///
|
|||
public HaarObjectDetector(HaarCascade cascade, int minSize, |
|||
ObjectDetectorSearchMode searchMode, float scaleFactor, |
|||
ObjectDetectorScalingMode scalingMode) |
|||
{ |
|||
this.classifier = new HaarClassifier(cascade); |
|||
this.minSize = new Size(minSize, minSize); |
|||
this.searchMode = searchMode; |
|||
this.ScalingMode = scalingMode; |
|||
this.factor = scaleFactor; |
|||
this.detectedObjects = new List<Rectangle>(); |
|||
|
|||
this.baseWidth = cascade.Width; |
|||
this.baseHeight = cascade.Height; |
|||
|
|||
this.match = new RectangleGroupMatching(0, 0.2); |
|||
} |
|||
#endregion
|
|||
|
|||
#region Properties
|
|||
/// <summary>
|
|||
/// Minimum window size to consider when searching objects.
|
|||
/// </summary>
|
|||
///
|
|||
public Size MinSize |
|||
{ |
|||
get { return minSize; } |
|||
set { minSize = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maximum window size to consider when searching objects.
|
|||
/// </summary>
|
|||
public Size MaxSize |
|||
{ |
|||
get { return maxSize; } |
|||
set { maxSize = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the color channel to use when processing color images.
|
|||
/// </summary>
|
|||
///
|
|||
public int Channel |
|||
{ |
|||
get { return channel; } |
|||
set { channel = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaling factor to rescale the window during search.
|
|||
/// </summary>
|
|||
///
|
|||
public float ScalingFactor |
|||
{ |
|||
get { return factor; } |
|||
set |
|||
{ |
|||
if (value != factor) |
|||
{ |
|||
factor = value; |
|||
steps = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the desired searching method.
|
|||
/// </summary>
|
|||
///
|
|||
public ObjectDetectorSearchMode SearchMode |
|||
{ |
|||
get { return searchMode; } |
|||
set { searchMode = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the desired scaling method.
|
|||
/// </summary>
|
|||
///
|
|||
public ObjectDetectorScalingMode ScalingMode |
|||
{ |
|||
get { return scalingMode; } |
|||
set |
|||
{ |
|||
if (value != scalingMode) |
|||
{ |
|||
scalingMode = value; |
|||
steps = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum threshold used to suppress rectangles which
|
|||
/// have not been detected sufficient number of times. This property only
|
|||
/// has effect when <see cref="SearchMode"/> is set to <see cref="ObjectDetectorSearchMode.Average"/>.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// The value of this property represents the minimum amount of detections
|
|||
/// made inside a region to report this region as an actual detection. For
|
|||
/// example, setting this property to two will discard all regions which
|
|||
/// had not achieved at least two detected rectangles within it.</para>
|
|||
///
|
|||
/// <para>
|
|||
/// Setting this property to a value higher than zero may decrease the
|
|||
/// number of false positives.</para>
|
|||
/// </remarks>
|
|||
///
|
|||
public int Suppression |
|||
{ |
|||
get { return match.MinimumNeighbors; } |
|||
set { match.MinimumNeighbors = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the detected objects bounding boxes.
|
|||
/// </summary>
|
|||
///
|
|||
public Rectangle[] DetectedObjects |
|||
{ |
|||
get { return detectedObjects.ToArray(); } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the internal Cascade Classifier used by this detector.
|
|||
/// </summary>
|
|||
///
|
|||
public HaarClassifier Classifier |
|||
{ |
|||
get { return classifier; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets how many frames the object has
|
|||
/// been detected in a steady position.
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// The number of frames the detected object
|
|||
/// has been in a steady position.</value>
|
|||
///
|
|||
public int Steady { get; private set; } |
|||
|
|||
#endregion
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// Performs object detection on the given frame.
|
|||
/// </summary>
|
|||
///
|
|||
//public Rectangle[] ProcessFrame(Bitmap frame)
|
|||
//{
|
|||
// using (FastBitmap fastBitmap = new FastBitmap(frame))
|
|||
// {
|
|||
// return ProcessFrame(fastBitmap);
|
|||
// }
|
|||
//}
|
|||
|
|||
/// <summary>
|
|||
/// Performs object detection on the given frame.
|
|||
/// </summary>
|
|||
///
|
|||
public Rectangle[] ProcessFrame(Bitmap image) |
|||
{ |
|||
int colorChannel = |
|||
image.PixelFormat == PixelFormat.Format8bppIndexed ? 0 : channel; |
|||
|
|||
Rectangle[] objects; |
|||
|
|||
// Creates an integral image representation of the frame
|
|||
using (FastBitmap fastBitmap = new FastBitmap(image, colorChannel, this.classifier.Cascade.HasTiltedFeatures)) |
|||
{ |
|||
// Creates a new list of detected objects.
|
|||
this.detectedObjects.Clear(); |
|||
|
|||
int width = fastBitmap.Width; |
|||
int height = fastBitmap.Height; |
|||
|
|||
// Update parameters only if different size
|
|||
if (steps == null || width != lastWidth || height != lastHeight) |
|||
update(width, height); |
|||
|
|||
|
|||
Rectangle window = Rectangle.Empty; |
|||
|
|||
// For each scaling step
|
|||
for (int i = 0; i < steps.Length; i++) |
|||
{ |
|||
float scaling = steps[i]; |
|||
|
|||
// Set the classifier window scale
|
|||
classifier.Scale = scaling; |
|||
|
|||
// Get the scaled window size
|
|||
window.Width = (int)(baseWidth * scaling); |
|||
window.Height = (int)(baseHeight * scaling); |
|||
|
|||
// Check if the window is lesser than the minimum size
|
|||
if (window.Width < minSize.Width || window.Height < minSize.Height) |
|||
{ |
|||
// If we are searching in greater to smaller mode,
|
|||
if (scalingMode == ObjectDetectorScalingMode.GreaterToSmaller) |
|||
{ |
|||
break; // it won't get bigger, so we should stop.
|
|||
} |
|||
else continue; // continue until it gets greater.
|
|||
} |
|||
|
|||
// Check if the window is greater than the maximum size
|
|||
else if (window.Width > maxSize.Width || window.Height > maxSize.Height) |
|||
{ |
|||
// If we are searching in greater to smaller mode,
|
|||
if (scalingMode == ObjectDetectorScalingMode.GreaterToSmaller) |
|||
{ |
|||
continue; // continue until it gets smaller.
|
|||
} |
|||
|
|||
break; // it won't get smaller, so we should stop.
|
|||
} |
|||
|
|||
// Grab some scan loop parameters
|
|||
int xStep = window.Width >> 3; |
|||
int yStep = window.Height >> 3; |
|||
|
|||
int xEnd = width - window.Width; |
|||
int yEnd = height - window.Height; |
|||
|
|||
|
|||
// Parallel mode. Scan the integral image searching
|
|||
// for objects in the window with parallelization.
|
|||
var bag = new System.Collections.Concurrent.ConcurrentBag<Rectangle>(); |
|||
|
|||
int numSteps = (int)Math.Ceiling((double)yEnd / yStep); |
|||
|
|||
// For each pixel in the window column
|
|||
var window1 = window; |
|||
Parallel.For( |
|||
0, |
|||
numSteps, |
|||
(j, options) => |
|||
{ |
|||
int y = j * yStep; |
|||
|
|||
// Create a local window reference
|
|||
Rectangle localWindow = window1; |
|||
|
|||
localWindow.Y = y; |
|||
|
|||
// For each pixel in the window row
|
|||
for (int x = 0; x < xEnd; x += xStep) |
|||
{ |
|||
if (options.ShouldExitCurrentIteration) return; |
|||
|
|||
localWindow.X = x; |
|||
|
|||
// Try to detect and object inside the window
|
|||
if (classifier.Compute(fastBitmap, localWindow)) |
|||
{ |
|||
// an object has been detected
|
|||
bag.Add(localWindow); |
|||
|
|||
if (searchMode == ObjectDetectorSearchMode.Single) |
|||
options.Stop(); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// If required, avoid adding overlapping objects at
|
|||
// the expense of extra computation. Otherwise, only
|
|||
// add objects to the detected objects collection.
|
|||
if (searchMode == ObjectDetectorSearchMode.NoOverlap) |
|||
{ |
|||
foreach (Rectangle obj in bag) |
|||
{ |
|||
if (!overlaps(obj)) |
|||
{ |
|||
detectedObjects.Add(obj); |
|||
} |
|||
} |
|||
} |
|||
else if (searchMode == ObjectDetectorSearchMode.Single) |
|||
{ |
|||
if (bag.TryPeek(out window)) |
|||
{ |
|||
detectedObjects.Add(window); |
|||
break; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
foreach (Rectangle obj in bag) |
|||
{ |
|||
detectedObjects.Add(obj); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
objects = detectedObjects.ToArray(); |
|||
|
|||
if (searchMode == ObjectDetectorSearchMode.Average) |
|||
{ |
|||
objects = match.Group(objects); |
|||
} |
|||
|
|||
checkSteadiness(objects); |
|||
lastObjects = objects; |
|||
|
|||
return objects; // Returns the array of detected objects.
|
|||
} |
|||
|
|||
private void update(int width, int height) |
|||
{ |
|||
List<float> listSteps = new List<float>(); |
|||
|
|||
// Set initial parameters according to scaling mode
|
|||
if (scalingMode == ObjectDetectorScalingMode.SmallerToGreater) |
|||
{ |
|||
float start = 1f; |
|||
float stop = Math.Min(width / (float)baseWidth, height / (float)baseHeight); |
|||
float step = factor; |
|||
|
|||
for (float f = start; f < stop; f *= step) |
|||
listSteps.Add(f); |
|||
} |
|||
else |
|||
{ |
|||
float start = Math.Min(width / (float)baseWidth, height / (float)baseHeight); |
|||
float stop = 1f; |
|||
float step = 1f / factor; |
|||
|
|||
for (float f = start; f > stop; f *= step) |
|||
listSteps.Add(f); |
|||
} |
|||
|
|||
steps = listSteps.ToArray(); |
|||
|
|||
lastWidth = width; |
|||
lastHeight = height; |
|||
} |
|||
|
|||
private void checkSteadiness(Rectangle[] rectangles) |
|||
{ |
|||
if (lastObjects == null || |
|||
rectangles == null || |
|||
rectangles.Length == 0) |
|||
{ |
|||
Steady = 0; |
|||
return; |
|||
} |
|||
|
|||
bool equals = true; |
|||
foreach (Rectangle current in rectangles) |
|||
{ |
|||
bool found = false; |
|||
foreach (Rectangle last in lastObjects) |
|||
{ |
|||
if (current.IsEqual(last, steadyThreshold)) |
|||
{ |
|||
found = true; |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
if (!found) |
|||
{ |
|||
equals = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (equals) |
|||
{ |
|||
Steady++; |
|||
} |
|||
else |
|||
{ |
|||
Steady = 0; |
|||
} |
|||
} |
|||
|
|||
private bool overlaps(Rectangle rect) |
|||
{ |
|||
foreach (Rectangle r in detectedObjects) |
|||
{ |
|||
if (rect.IntersectsWith(r)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
/// <summary>
|
|||
/// Object detector options for window scaling.
|
|||
/// </summary>
|
|||
///
|
|||
public enum ObjectDetectorScalingMode |
|||
{ |
|||
/// <summary>
|
|||
/// Will start with a big search window and
|
|||
/// gradually scale into smaller ones.
|
|||
/// </summary>
|
|||
///
|
|||
GreaterToSmaller, |
|||
|
|||
/// <summary>
|
|||
/// Will start with small search windows and
|
|||
/// gradually scale into greater ones.
|
|||
/// </summary>
|
|||
///
|
|||
SmallerToGreater, |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
/// <summary>
|
|||
/// Object detector options for the search procedure.
|
|||
/// </summary>
|
|||
///
|
|||
public enum ObjectDetectorSearchMode |
|||
{ |
|||
/// <summary>
|
|||
/// Entire image will be scanned.
|
|||
/// </summary>
|
|||
///
|
|||
Default = 0, |
|||
|
|||
/// <summary>
|
|||
/// Only a single object will be retrieved.
|
|||
/// </summary>
|
|||
///
|
|||
Single, |
|||
|
|||
/// <summary>
|
|||
/// If a object has already been detected inside an area,
|
|||
/// it will not be scanned twice for inner or overlapping
|
|||
/// objects, saving computation time.
|
|||
/// </summary>
|
|||
///
|
|||
NoOverlap, |
|||
|
|||
/// <summary>
|
|||
/// If several objects are located within one another,
|
|||
/// they will be averaged. Additionally, objects which
|
|||
/// have not been detected sufficient times may be dropped
|
|||
/// by setting <see cref="HaarObjectDetector.Suppression"/>.
|
|||
/// </summary>
|
|||
///
|
|||
Average, |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
// Accord Vision Library
|
|||
// The Accord.NET Framework
|
|||
// http://accord-framework.net
|
|||
//
|
|||
// Copyright © César Souza, 2009-2015
|
|||
// cesarsouza at gmail.com
|
|||
//
|
|||
// This code has been submitted as an user contribution by darko.juric2
|
|||
// GCode Issue #12 https://code.google.com/p/accord/issues/detail?id=12
|
|||
//
|
|||
// This library is free software; you can redistribute it and/or
|
|||
// modify it under the terms of the GNU Lesser General Public
|
|||
// License as published by the Free Software Foundation; either
|
|||
// version 2.1 of the License, or (at your option) any later version.
|
|||
//
|
|||
// This library is distributed in the hope that it will be useful,
|
|||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
// Lesser General Public License for more details.
|
|||
//
|
|||
// You should have received a copy of the GNU Lesser General Public
|
|||
// License along with this library; if not, write to the Free Software
|
|||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
//
|
|||
|
|||
namespace ImageProcessor.Imaging.Filters.ObjectDetection |
|||
{ |
|||
using System; |
|||
using System.Drawing; |
|||
|
|||
/// <summary>
|
|||
/// Group matching algorithm for detection region averaging.
|
|||
/// </summary>
|
|||
///
|
|||
/// <remarks>
|
|||
/// This class can be seen as a post-processing filter. Its goal is to
|
|||
/// group near or contained regions together in order to produce more
|
|||
/// robust and smooth estimates of the detected regions.
|
|||
/// </remarks>
|
|||
///
|
|||
public class RectangleGroupMatching : GroupMatching<Rectangle> |
|||
{ |
|||
private double threshold; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new <see cref="RectangleGroupMatching"/> object.
|
|||
/// </summary>
|
|||
///
|
|||
/// <param name="minimumNeighbors">
|
|||
/// The minimum number of neighbors needed to keep a detection. If a rectangle
|
|||
/// has less than this minimum number, it will be discarded as a false positive.</param>
|
|||
/// <param name="threshold">
|
|||
/// The minimum distance threshold to consider two rectangles as neighbors.
|
|||
/// Default is 0.2.</param>
|
|||
///
|
|||
public RectangleGroupMatching(int minimumNeighbors = 2, double threshold = 0.2) |
|||
: base(minimumNeighbors) |
|||
{ |
|||
this.threshold = threshold; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the minimum distance threshold to consider
|
|||
/// two rectangles as neighbors. Default is 0.2.
|
|||
/// </summary>
|
|||
///
|
|||
protected double Threshold |
|||
{ |
|||
get { return threshold; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if two rectangles are near.
|
|||
/// </summary>
|
|||
///
|
|||
protected override bool Near(Rectangle shape1, Rectangle shape2) |
|||
{ |
|||
if (shape1.Contains(shape2) || shape2.Contains(shape1)) |
|||
return true; |
|||
|
|||
int minHeight = Math.Min(shape1.Height, shape2.Height); |
|||
int minWidth = Math.Min(shape1.Width, shape2.Width); |
|||
double delta = 0.5 * threshold * (minHeight + minWidth); |
|||
|
|||
return Math.Abs(shape1.X - shape2.X) <= delta |
|||
&& Math.Abs(shape1.Y - shape2.Y) <= delta |
|||
&& Math.Abs(shape1.Right - shape2.Right) <= delta |
|||
&& Math.Abs(shape1.Bottom - shape2.Bottom) <= delta; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Averages rectangles which belongs to the
|
|||
/// same class (have the same class label)
|
|||
/// </summary>
|
|||
///
|
|||
protected override Rectangle[] Average(int[] labels, Rectangle[] shapes, out int[] neighborCounts) |
|||
{ |
|||
neighborCounts = new int[Classes]; |
|||
|
|||
Rectangle[] centroids = new Rectangle[Classes]; |
|||
for (int i = 0; i < shapes.Length; i++) |
|||
{ |
|||
int j = labels[i]; |
|||
|
|||
centroids[j].X += shapes[i].X; |
|||
centroids[j].Y += shapes[i].Y; |
|||
centroids[j].Width += shapes[i].Width; |
|||
centroids[j].Height += shapes[i].Height; |
|||
|
|||
neighborCounts[j]++; |
|||
} |
|||
|
|||
for (int i = 0; i < centroids.Length; i++) |
|||
{ |
|||
centroids[i] = new Rectangle( |
|||
x: (int)Math.Ceiling((float)centroids[i].X / neighborCounts[i]), |
|||
y: (int)Math.Ceiling((float)centroids[i].Y / neighborCounts[i]), |
|||
width: (int)Math.Ceiling((float)centroids[i].Width / neighborCounts[i]), |
|||
height: (int)Math.Ceiling((float)centroids[i].Height / neighborCounts[i])); |
|||
} |
|||
|
|||
return centroids; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1 @@ |
|||
b3860ad6afc1b4eeab715c0de52156a24ec976e7 |
|||
@ -0,0 +1 @@ |
|||
bc2aa3c73e580998987cc543593cff2ffb48aa31 |
|||
@ -0,0 +1 @@ |
|||
eb6792a3e6ca1e3ab14c44ba04153ab71328f027 |
|||
@ -0,0 +1,119 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="DetectObjects.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Encapsulates methods to change the DetectObjects component of the image to effect its transparency.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Processors |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
|
|||
using ImageProcessor.Common.Exceptions; |
|||
using ImageProcessor.Imaging.Filters.ObjectDetection; |
|||
using ImageProcessor.Imaging.Filters.Photo; |
|||
using ImageProcessor.Imaging.Helpers; |
|||
|
|||
/// <summary>
|
|||
/// Encapsulates methods to change the DetectObjects component of the image to effect its transparency.
|
|||
/// </summary>
|
|||
public class DetectObjects : IGraphicsProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DetectObjects"/> class.
|
|||
/// </summary>
|
|||
public DetectObjects() |
|||
{ |
|||
this.Settings = new Dictionary<string, string>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the dynamic parameter.
|
|||
/// </summary>
|
|||
public dynamic DynamicParameter |
|||
{ |
|||
get; |
|||
set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets any additional settings required by the processor.
|
|||
/// </summary>
|
|||
public Dictionary<string, string> Settings |
|||
{ |
|||
get; |
|||
set; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the image.
|
|||
/// </summary>
|
|||
/// <param name="factory">
|
|||
/// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class containing
|
|||
/// the image to process.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The processed image from the current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
|
|||
/// </returns>
|
|||
public Image ProcessImage(ImageFactory factory) |
|||
{ |
|||
Bitmap newImage = null; |
|||
Bitmap grey = null; |
|||
Image image = factory.Image; |
|||
|
|||
try |
|||
{ |
|||
HaarCascade cascade = this.DynamicParameter; |
|||
grey = new Bitmap(image.Width, image.Height); |
|||
grey.SetResolution(image.HorizontalResolution, image.VerticalResolution); |
|||
grey = MatrixFilters.GreyScale.TransformImage(image, grey); |
|||
|
|||
HaarObjectDetector detector = new HaarObjectDetector(cascade) |
|||
{ |
|||
SearchMode = ObjectDetectorSearchMode.NoOverlap, |
|||
ScalingMode = ObjectDetectorScalingMode.GreaterToSmaller, |
|||
ScalingFactor = 1.5f |
|||
}; |
|||
|
|||
// Process frame to detect objects
|
|||
Rectangle[] rectangles = detector.ProcessFrame(grey); |
|||
grey.Dispose(); |
|||
|
|||
newImage = new Bitmap(image); |
|||
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); |
|||
using (Graphics graphics = Graphics.FromImage(newImage)) |
|||
{ |
|||
using (Pen blackPen = new Pen(Color.White)) |
|||
{ |
|||
blackPen.Width = 4; |
|||
graphics.DrawRectangles(blackPen, rectangles); |
|||
} |
|||
} |
|||
|
|||
image.Dispose(); |
|||
image = newImage; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
if (grey != null) |
|||
{ |
|||
grey.Dispose(); |
|||
} |
|||
|
|||
if (newImage != null) |
|||
{ |
|||
newImage.Dispose(); |
|||
} |
|||
|
|||
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue