Browse Source

Initial commit.

Former-commit-id: 63167d0040ba2c4bc6870b23555d5eea0ce4de35
Former-commit-id: dea5a4a4e13494b3490b5a69ac0a69c8aa8b6ab9
Former-commit-id: 72c754f9100b7f4722c9cf0664297d299b4e7e20
af/merge-core
James South 11 years ago
parent
commit
fc3aa36ef2
  1. 142
      src/ImageProcessor.Playground/Program.cs
  2. 1
      src/ImageProcessor.Playground/images/input/haarcascade_frontalface_alt.xml.REMOVED.git-id
  3. 1
      src/ImageProcessor.Playground/images/input/test2.png.REMOVED.git-id
  4. 34
      src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs
  5. 19
      src/ImageProcessor/Common/Extensions/RectangleExtensions.cs
  6. 12
      src/ImageProcessor/ImageFactory.cs
  7. 23
      src/ImageProcessor/ImageProcessor.csproj
  8. 2
      src/ImageProcessor/ImageProcessor.csproj.DotSettings
  9. 255
      src/ImageProcessor/Imaging/FastBitmap.cs
  10. 6
      src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs
  11. 338
      src/ImageProcessor/Imaging/Filters/ObjectDetection/Class1.cs
  12. 33
      src/ImageProcessor/Imaging/Filters/ObjectDetection/EmbeddedHaarCascades.cs
  13. 239
      src/ImageProcessor/Imaging/Filters/ObjectDetection/GroupMatching.cs
  14. 243
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascade.cs
  15. 21
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascadeSerializationObject.cs
  16. 168
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascadeStage.cs
  17. 154
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascadeWriter.cs
  18. 175
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarClassifier.cs
  19. 223
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarFeature.cs
  20. 122
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarFeatureNode.cs
  21. 203
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarRectangle.cs
  22. 594
      src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarObjectDetector.cs
  23. 23
      src/ImageProcessor/Imaging/Filters/ObjectDetection/ObjectDetectorScalingMode.cs
  24. 38
      src/ImageProcessor/Imaging/Filters/ObjectDetection/ObjectDetectorSearchMode.cs
  25. 126
      src/ImageProcessor/Imaging/Filters/ObjectDetection/RectangleGroupMatching.cs
  26. 1
      src/ImageProcessor/Imaging/Filters/ObjectDetection/Resources/haarcascade_frontalface_alt.xml.REMOVED.git-id
  27. 1
      src/ImageProcessor/Imaging/Filters/ObjectDetection/Resources/haarcascade_frontalface_default.xml.REMOVED.git-id
  28. 1
      src/ImageProcessor/Imaging/Filters/ObjectDetection/Resources/haarcascade_frontalface_legacy.xml.REMOVED.git-id
  29. 1
      src/ImageProcessor/Processors/Alpha.cs
  30. 119
      src/ImageProcessor/Processors/DetectObjects.cs
  31. 2
      src/ImageProcessor/Settings.StyleCop

142
src/ImageProcessor.Playground/Program.cs

@ -21,6 +21,7 @@ namespace ImageProcessor.PlayGround
using ImageProcessor.Configuration;
using ImageProcessor.Imaging;
using ImageProcessor.Imaging.Filters.EdgeDetection;
using ImageProcessor.Imaging.Filters.ObjectDetection;
using ImageProcessor.Imaging.Filters.Photo;
using ImageProcessor.Imaging.Formats;
using ImageProcessor.Processors;
@ -60,89 +61,92 @@ namespace ImageProcessor.PlayGround
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "gamma_dalai_lama_gray.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Arc-de-Triomphe-France.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Martin-Schoeller-Jack-Nicholson-Portrait.jpeg"));
////FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "crop-base-300x200.jpg"));
FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "test2.png"));
//////FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "crop-base-300x200.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png"));
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png", ".jpg", ".jpeg");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png");
foreach (FileInfo fileInfo in files)
{
if (fileInfo.Name == "test5.jpg")
{
continue;
}
//foreach (FileInfo fileInfo in files)
//{
// if (fileInfo.Name == "test5.jpg")
// {
// continue;
// }
byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName);
Console.WriteLine("Processing: " + fileInfo.Name);
byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName);
Console.WriteLine("Processing: " + fileInfo.Name);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
// ImageProcessor
using (MemoryStream inStream = new MemoryStream(photoBytes))
// ImageProcessor
using (MemoryStream inStream = new MemoryStream(photoBytes))
{
using (ImageFactory imageFactory = new ImageFactory(true, false))
{
using (ImageFactory imageFactory = new ImageFactory(true, false))
{
Size size = new Size(500, 0);
//CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage);
//ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
//ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size)
//{
// ConvolutionType = ConvolutionType.Sobel
//};
// Load, resize, set the format and quality and save an image.
imageFactory.Load(inStream)
//.Overlay(new ImageLayer
// {
// Image = overlay,
// Opacity = 50
// })
//.Alpha(50)
//.BackgroundColor(Color.White)
//.Resize(new Size((int)(size.Width * 1.1), 0))
//.ContentAwareResize(layer)
//.Constrain(size)
//.Mask(mask)
//.Format(new PngFormat())
//.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128)
//.Resize(size)
//.Resize(new ResizeLayer(size, ResizeMode.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new Laplacian3X3EdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.GaussianBlur(new GaussianLayer(10, 11))
//.EntropyCrop()
.Halftone()
//.RotateBounded(150, false)
//.Crop(cropLayer)
//.Rotate(140)
//.Filter(MatrixFilters.Invert)
//.Contrast(50)
//.Filter(MatrixFilters.Comic)
//.Flip()
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)
//.GaussianSharpen(10)
//.Format(new PngFormat() { IsIndexed = true })
//.Format(new PngFormat() )
.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name)));
//.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png")));
stopwatch.Stop();
}
Size size = new Size(500, 0);
//CropLayer cropLayer = new CropLayer(20, 20, 20, 20, ImageProcessor.Imaging.CropMode.Percentage);
//ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
//ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size)
//{
// ConvolutionType = ConvolutionType.Sobel
//};
// Load, resize, set the format and quality and save an image.
imageFactory.Load(inStream)
.DetectObjects(EmbeddedHaarCascades.FrontFaceDefault)
//.Overlay(new ImageLayer
// {
// Image = overlay,
// Opacity = 50
// })
//.Alpha(50)
//.BackgroundColor(Color.White)
//.Resize(new Size((int)(size.Width * 1.1), 0))
//.ContentAwareResize(layer)
//.Constrain(size)
//.Mask(mask)
//.Format(new PngFormat())
//.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128)
//.Resize(size)
//.Resize(new ResizeLayer(size, ResizeMode.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new Laplacian3X3EdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.GaussianBlur(new GaussianLayer(10, 11))
//.EntropyCrop()
//.Halftone()
//.RotateBounded(150, false)
//.Crop(cropLayer)
//.Rotate(140)
//.Filter(MatrixFilters.Invert)
//.Contrast(50)
//.Filter(MatrixFilters.Comic)
//.Flip()
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)
//.GaussianSharpen(10)
//.Format(new PngFormat() { IsIndexed = true })
//.Format(new PngFormat() )
.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name)));
//.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png")));
stopwatch.Stop();
}
}
long peakWorkingSet64 = Process.GetCurrentProcess().PeakWorkingSet64;
float mB = peakWorkingSet64 / (float)1024 / 1024;
long peakWorkingSet64 = Process.GetCurrentProcess().PeakWorkingSet64;
float mB = peakWorkingSet64 / (float)1024 / 1024;
Console.WriteLine(@"Completed {0} in {1:s\.fff} secs {2}Peak memory usage was {3} bytes or {4} Mb.", fileInfo.Name, stopwatch.Elapsed, Environment.NewLine, peakWorkingSet64.ToString("#,#"), mB);
Console.WriteLine(@"Completed {0} in {1:s\.fff} secs {2}Peak memory usage was {3} bytes or {4} Mb.", fileInfo.Name, stopwatch.Elapsed, Environment.NewLine, peakWorkingSet64.ToString("#,#"), mB);
//Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms");
}
//Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms");
//}
Console.ReadLine();
}

1
src/ImageProcessor.Playground/images/input/haarcascade_frontalface_alt.xml.REMOVED.git-id

@ -0,0 +1 @@
b3860ad6afc1b4eeab715c0de52156a24ec976e7

1
src/ImageProcessor.Playground/images/input/test2.png.REMOVED.git-id

@ -0,0 +1 @@
64bcd395a5b08c75a48cc21db2668cfb7fe5364e

34
src/ImageProcessor/Common/Extensions/AssemblyExtensions.cs

@ -15,6 +15,7 @@ namespace ImageProcessor.Common.Extensions
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
/// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Reflection.Assembly"/> class.
@ -48,6 +49,39 @@ namespace ImageProcessor.Common.Extensions
}
}
/// <summary>
/// Converts an assembly resource into a string.
/// </summary>
/// <param name="assembly">
/// The <see cref="System.Reflection.Assembly"/> to load the strings from.
/// </param>
/// <param name="resource">
/// The resource.
/// </param>
/// <param name="encoding">
/// The character encoding to return the resource in.
/// </param>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public static string GetResourceAsString(this Assembly assembly, string resource, Encoding encoding = null)
{
encoding = encoding ?? Encoding.UTF8;
using (MemoryStream ms = new MemoryStream())
{
using (Stream manifestResourceStream = assembly.GetManifestResourceStream(resource))
{
if (manifestResourceStream != null)
{
manifestResourceStream.CopyTo(ms);
}
}
return encoding.GetString(ms.GetBuffer()).Replace('\0', ' ').Trim();
}
}
/// <summary>
/// Returns the <see cref="FileInfo"/> identifying the file used to load the assembly
/// </summary>

19
src/ImageProcessor/Common/Extensions/RectangleExtensions.cs

@ -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);
}
}
}

12
src/ImageProcessor/ImageFactory.cs

@ -21,6 +21,7 @@ namespace ImageProcessor
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging;
using ImageProcessor.Imaging.Filters.EdgeDetection;
using ImageProcessor.Imaging.Filters.ObjectDetection;
using ImageProcessor.Imaging.Filters.Photo;
using ImageProcessor.Imaging.Formats;
using ImageProcessor.Imaging.Helpers;
@ -497,6 +498,17 @@ namespace ImageProcessor
return this;
}
public ImageFactory DetectObjects(HaarCascade cascade, bool drawRectangles = true, Color color = default(Color))
{
if (this.ShouldProcess)
{
DetectObjects detectObjects = new DetectObjects { DynamicParameter = cascade };
this.CurrentImageFormat.ApplyProcessor(detectObjects.ProcessImage, this);
}
return this;
}
/// <summary>
/// Crops an image to the area of greatest entropy.
/// </summary>

23
src/ImageProcessor/ImageProcessor.csproj

@ -126,6 +126,7 @@
<ItemGroup>
<Compile Include="Common\Extensions\AssemblyExtensions.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Extensions\RectangleExtensions.cs" />
<Compile Include="Common\Helpers\WriteLock.cs" />
<Compile Include="Common\Helpers\UpgradeableReadLock.cs" />
<Compile Include="Common\Helpers\TypeFinder.cs" />
@ -161,6 +162,21 @@
<Compile Include="Imaging\FastBitmap.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Imaging\Filters\ObjectDetection\Class1.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\EmbeddedHaarCascades.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\GroupMatching.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarCascade.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarCascadeSerializationObject.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarCascadeStage.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarCascadeWriter.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarClassifier.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarFeature.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarFeatureNode.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarCascade\HaarRectangle.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\HaarObjectDetector.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\ObjectDetectorScalingMode.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\ObjectDetectorSearchMode.cs" />
<Compile Include="Imaging\Filters\ObjectDetection\RectangleGroupMatching.cs" />
<Compile Include="Imaging\Formats\GifDecoder.cs" />
<Compile Include="Common\Extensions\IntegerExtensions.cs" />
<Compile Include="Common\Exceptions\ImageFormatException.cs" />
@ -220,6 +236,7 @@
<Compile Include="Imaging\Resizer.cs" />
<Compile Include="Imaging\RoundedCornerLayer.cs" />
<Compile Include="Imaging\TextLayer.cs" />
<Compile Include="Processors\DetectObjects.cs" />
<Compile Include="Processors\Gamma.cs" />
<Compile Include="Processors\Alpha.cs" />
<Compile Include="Processors\EntropyCrop.cs" />
@ -257,6 +274,12 @@
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Imaging\Filters\ObjectDetection\Resources\haarcascade_frontalface_default.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Imaging\Filters\ObjectDetection\Resources\haarcascade_frontalface_legacy.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</Project>

2
src/ImageProcessor/ImageProcessor.csproj.DotSettings

@ -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>

255
src/ImageProcessor/Imaging/FastBitmap.cs

@ -13,6 +13,7 @@ namespace ImageProcessor.Imaging
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using ImageProcessor.Imaging.Colors;
@ -46,6 +47,34 @@ namespace ImageProcessor.Imaging
/// </summary>
private int color32Size;
/// <summary>
/// The color channel - blue, green, red, alpha.
/// </summary>
private int channel;
/// <summary>
/// Whether to compute tilted integral rectangles.
/// </summary>
private bool computeTilted;
private long[,] nSumImage; // normal integral image
private long[,] sSumImage; // squared integral image
private long[,] tSumImage; // tilted integral image
private long* nSum; // normal integral image
private long* sSum; // squared integral image
private long* tSum; // tilted integral image
private GCHandle nSumHandle;
private GCHandle sSumHandle;
private GCHandle tSumHandle;
private int nWidth;
private int nHeight;
private int tWidth;
private int tHeight;
/// <summary>
/// The bitmap data.
/// </summary>
@ -74,11 +103,51 @@ namespace ImageProcessor.Imaging
/// </summary>
/// <param name="bitmap">The input bitmap.</param>
public FastBitmap(Image bitmap)
: this(bitmap, 2, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FastBitmap"/> class.
/// </summary>
/// <param name="bitmap">The input bitmap.</param>
/// <param name="integralColorChannel">
/// The integral color channel. Blue, Green, Red, or Alpha.
/// </param>
/// <param name="computeTilted">
/// Whether to compute tilted integral rectangles.
/// </param>
public FastBitmap(Image bitmap, int integralColorChannel, bool computeTilted)
{
this.bitmap = (Bitmap)bitmap;
this.width = this.bitmap.Width;
this.height = this.bitmap.Height;
this.channel = integralColorChannel;
this.computeTilted = computeTilted;
this.nWidth = this.width + 1;
this.nHeight = this.height + 1;
this.tWidth = this.width + 2;
this.tHeight = this.height + 2;
this.nSumImage = new long[this.nHeight, this.nWidth];
this.nSumHandle = GCHandle.Alloc(this.nSumImage, GCHandleType.Pinned);
this.nSum = (long*)this.nSumHandle.AddrOfPinnedObject().ToPointer();
this.sSumImage = new long[this.nHeight, this.nWidth];
this.sSumHandle = GCHandle.Alloc(this.sSumImage, GCHandleType.Pinned);
this.sSum = (long*)this.sSumHandle.AddrOfPinnedObject().ToPointer();
if (this.computeTilted)
{
this.tSumImage = new long[this.tHeight, this.tWidth];
this.tSumHandle = GCHandle.Alloc(this.tSumImage, GCHandleType.Pinned);
this.tSum = (long*)this.tSumHandle.AddrOfPinnedObject().ToPointer();
}
this.LockBitmap();
this.CalculateIntegrals();
}
/// <summary>
@ -202,6 +271,69 @@ namespace ImageProcessor.Imaging
data->A = color.A;
}
/// <summary>
/// Gets the sum of the pixels in a rectangle of the Integral image.
/// </summary>
/// <param name="x">The horizontal position of the rectangle <c>x</c>.</param>
/// <param name="y">The vertical position of the rectangle <c>y</c>.</param>
/// <param name="rectangleWidth">The rectangle's width <c>w</c>.</param>
/// <param name="rectangleHeight">The rectangle's height <c>h</c>.</param>
/// <returns>
/// The sum of all pixels contained in the rectangle, computed
/// as I[y, x] + I[y + h, x + w] - I[y + h, x] - I[y, x + w].
/// </returns>
public long GetSum(int x, int y, int rectangleWidth, int rectangleHeight)
{
int a = (this.nWidth * y) + x;
int b = (this.nWidth * (y + rectangleHeight)) + (x + rectangleWidth);
int c = (this.nWidth * (y + rectangleHeight)) + x;
int d = (this.nWidth * y) + (x + rectangleWidth);
return this.nSum[a] + this.nSum[b] - this.nSum[c] - this.nSum[d];
}
/// <summary>
/// Gets the sum of the squared pixels in a rectangle of the Integral image.
/// </summary>
/// <param name="x">The horizontal position of the rectangle <c>x</c>.</param>
/// <param name="y">The vertical position of the rectangle <c>y</c>.</param>
/// <param name="rectangleWidth">The rectangle's width <c>w</c>.</param>
/// <param name="rectangleHeight">The rectangle's height <c>h</c>.</param>
/// <returns>
/// The sum of all pixels contained in the rectangle, computed
/// as I²[y, x] + I²[y + h, x + w] - I²[y + h, x] - I²[y, x + w].
/// </returns>
public long GetSum2(int x, int y, int rectangleWidth, int rectangleHeight)
{
int a = (this.nWidth * y) + x;
int b = (this.nWidth * (y + rectangleHeight)) + (x + rectangleWidth);
int c = (this.nWidth * (y + rectangleHeight)) + x;
int d = (this.nWidth * y) + (x + rectangleWidth);
return this.sSum[a] + this.sSum[b] - this.sSum[c] - this.sSum[d];
}
/// <summary>
/// Gets the sum of the pixels in a tilted rectangle of the Integral image.
/// </summary>
/// <param name="x">The horizontal position of the rectangle <c>x</c>.</param>
/// <param name="y">The vertical position of the rectangle <c>y</c>.</param>
/// <param name="rectangleWidth">The rectangle's width <c>w</c>.</param>
/// <param name="rectangleHeight">The rectangle's height <c>h</c>.</param>
/// <returns>
/// The sum of all pixels contained in the rectangle, computed
/// as T[y + w, x + w + 1] + T[y + h, x - h + 1] - T[y, x + 1] - T[y + w + h, x + w - h + 1].
/// </returns>
public long GetSumT(int x, int y, int rectangleWidth, int rectangleHeight)
{
int a = (this.tWidth * (y + rectangleWidth)) + (x + rectangleWidth + 1);
int b = (this.tWidth * (y + rectangleHeight)) + (x - rectangleHeight + 1);
int c = (this.tWidth * y) + (x + 1);
int d = (this.tWidth * (y + rectangleWidth + rectangleHeight)) + (x + rectangleWidth - rectangleHeight + 1);
return this.tSum[a] + this.tSum[b] - this.tSum[c] - this.tSum[d];
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
@ -266,6 +398,24 @@ namespace ImageProcessor.Imaging
// Call the appropriate methods to clean up
// unmanaged resources here.
if (this.nSumHandle.IsAllocated)
{
this.nSumHandle.Free();
this.nSum = null;
}
if (this.sSumHandle.IsAllocated)
{
this.sSumHandle.Free();
this.sSum = null;
}
if (this.tSumHandle.IsAllocated)
{
this.tSumHandle.Free();
this.tSum = null;
}
// Note disposing is done.
this.isDisposed = true;
}
@ -294,6 +444,111 @@ namespace ImageProcessor.Imaging
this.pixelBase = (byte*)this.bitmapData.Scan0.ToPointer();
}
/// <summary>
/// Computes all possible rectangular areas in the image.
/// </summary>
private void CalculateIntegrals()
{
// Calculate integral and integral squared values.
int stride = this.bitmapData.Stride;
int offset = stride - this.bytesInARow;
byte* srcStart = this.pixelBase + this.channel;
// Do the job
byte* src = srcStart;
// For each line
// TODO. Make this parallel
for (int y = 1; y <= this.height; y++)
{
int yy = this.nWidth * y;
int y1 = this.nWidth * (y - 1);
// For each pixel
for (int x = 1; x <= this.width; x++, src += this.color32Size)
{
int pixel = *src;
int pixelSquared = pixel * pixel;
int r = yy + x;
int a = yy + (x - 1);
int b = y1 + x;
int c = y1 + (x - 1);
this.nSum[r] = pixel + this.nSum[a] + this.nSum[b] - this.nSum[c];
this.sSum[r] = pixelSquared + this.sSum[a] + this.sSum[b] - this.sSum[c];
}
src += offset;
}
if (this.computeTilted)
{
src = srcStart;
// Left-to-right, top-to-bottom pass
for (int y = 1; y <= this.height; y++, src += offset)
{
int yy = this.tWidth * y;
int y1 = this.tWidth * (y - 1);
for (int x = 2; x < this.width + 2; x++, src += this.color32Size)
{
int a = y1 + (x - 1);
int b = yy + (x - 1);
int c = y1 + (x - 2);
int r = yy + x;
this.tSum[r] = *src + this.tSum[a] + this.tSum[b] - this.tSum[c];
}
}
{
int yy = this.tWidth * this.height;
int y1 = this.tWidth * (this.height + 1);
for (int x = 2; x < this.width + 2; x++, src += this.color32Size)
{
int a = yy + (x - 1);
int c = yy + (x - 2);
int b = y1 + (x - 1);
int r = y1 + x;
this.tSum[r] = this.tSum[a] + this.tSum[b] - this.tSum[c];
}
}
// Right-to-left, bottom-to-top pass
for (int y = this.height; y >= 0; y--)
{
int yy = this.tWidth * y;
int y1 = this.tWidth * (y + 1);
for (int x = this.width + 1; x >= 1; x--)
{
int r = yy + x;
int b = y1 + (x - 1);
this.tSum[r] += this.tSum[b];
}
}
for (int y = this.height + 1; y >= 0; y--)
{
int yy = this.tWidth * y;
for (int x = this.width + 1; x >= 2; x--)
{
int r = yy + x;
int b = yy + (x - 2);
this.tSum[r] -= this.tSum[b];
}
}
}
}
/// <summary>
/// Unlocks the bitmap from system memory.
/// </summary>

6
src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs

@ -1,9 +1,13 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BinaryThreshold.cs" company="James South">
// Copyright (c) James South.
// // Licensed under the Apache License, Version 2.0.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Performs binary threshold filtering against a given greyscale image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.Binarization
{
using System.Drawing;

338
src/ImageProcessor/Imaging/Filters/ObjectDetection/Class1.cs

@ -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;
}
}
}
}

33
src/ImageProcessor/Imaging/Filters/ObjectDetection/EmbeddedHaarCascades.cs

@ -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;
}
}
}

239
src/ImageProcessor/Imaging/Filters/ObjectDetection/GroupMatching.cs

@ -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);
}
}

243
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascade.cs

@ -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);
}
}
}

21
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascadeSerializationObject.cs

@ -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; }
}
}

168
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascadeStage.cs

@ -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;
}
}
}

154
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarCascadeWriter.cs

@ -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));
}
}
}

175
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarClassifier.cs

@ -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.
}
}
}

223
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarFeature.cs

@ -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 };
}
}
}

122
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarFeatureNode.cs

@ -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;
}
}
}

203
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarCascade/HaarRectangle.cs

@ -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;
}
}
}

594
src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarObjectDetector.cs

@ -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;
}
}
}

23
src/ImageProcessor/Imaging/Filters/ObjectDetection/ObjectDetectorScalingMode.cs

@ -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,
}
}

38
src/ImageProcessor/Imaging/Filters/ObjectDetection/ObjectDetectorSearchMode.cs

@ -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,
}
}

126
src/ImageProcessor/Imaging/Filters/ObjectDetection/RectangleGroupMatching.cs

@ -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;
}
}
}

1
src/ImageProcessor/Imaging/Filters/ObjectDetection/Resources/haarcascade_frontalface_alt.xml.REMOVED.git-id

@ -0,0 +1 @@
b3860ad6afc1b4eeab715c0de52156a24ec976e7

1
src/ImageProcessor/Imaging/Filters/ObjectDetection/Resources/haarcascade_frontalface_default.xml.REMOVED.git-id

@ -0,0 +1 @@
bc2aa3c73e580998987cc543593cff2ffb48aa31

1
src/ImageProcessor/Imaging/Filters/ObjectDetection/Resources/haarcascade_frontalface_legacy.xml.REMOVED.git-id

@ -0,0 +1 @@
eb6792a3e6ca1e3ab14c44ba04153ab71328f027

1
src/ImageProcessor/Processors/Alpha.cs

@ -68,6 +68,7 @@ namespace ImageProcessor.Processors
int percentage = this.DynamicParameter;
newImage = new Bitmap(image);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
newImage = Adjustments.Alpha(newImage, percentage);
image.Dispose();

119
src/ImageProcessor/Processors/DetectObjects.cs

@ -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;
}
}
}

2
src/ImageProcessor/Settings.StyleCop

@ -1,11 +1,13 @@
<StyleCopSettings Version="105">
<GlobalSettings>
<CollectionProperty Name="RecognizedWords">
<Value>Accord</Value>
<Value>bitstream</Value>
<Value>dd</Value>
<Value>ddd</Value>
<Value>dllimport</Value>
<Value>gps</Value>
<Value>Haar</Value>
<Value>Kayyali</Value>
<Value>Laplacian</Value>
<Value>mmmm</Value>

Loading…
Cancel
Save