Skip to content

Instantly share code, notes, and snippets.

@zrxq
Created July 30, 2011 13:22
Show Gist options
  • Save zrxq/1115520 to your computer and use it in GitHub Desktop.
Save zrxq/1115520 to your computer and use it in GitHub Desktop.
Face detection in C# using OpenCV with P/Invoke
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace OpenCVFaceDetector
{
class DllPaths
{
public const string KERNEL32_DLL_PATH = "kernel32.dll";
public const string OPENCV_CORE_DLL_PATH = "opencv_core230.dll";
public const string OPENCV_OBJDETECT_DLL_PATH = "opencv_objdetect230.dll";
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct IplImage
{
public int nSize;
public int ID;
public int nChannels;
public int alphaChannel;
public int depth;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string colorModel;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string channelSeq;
public int dataOrder;
public int origin;
public int align;
public int width;
public int height;
public System.IntPtr roi;
public System.IntPtr maskROI;
public System.IntPtr imageId;
public System.IntPtr tileInfo;
public int imageSize;
public System.IntPtr imageData;
public int widthStep;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.I4)]
public int[] BorderMode;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.I4)]
public int[] BorderConst;
public System.IntPtr imageDataOrigin;
}
[StructLayout(LayoutKind.Sequential)]
public struct CvSize
{
public int width;
public int height;
public CvSize(int width, int height) { this.width = width; this.height = height; }
}
[StructLayout(LayoutKind.Sequential)]
public struct CvRect
{
public int x;
public int y;
public int width;
public int height;
}
public class NativeMethods
{
[DllImport(DllPaths.KERNEL32_DLL_PATH, SetLastError = true)]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport(DllPaths.KERNEL32_DLL_PATH, SetLastError=true)]
public static extern bool FreeLibrary(IntPtr hModule);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvCreateImage", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvCreateImage(CvSize size, int depth, int channels);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvReleaseImage", CallingConvention = CallingConvention.Cdecl)]
public static extern void cvReleaseImage(ref IntPtr image);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvLoad", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvLoad([In][MarshalAs(UnmanagedType.LPStr)]string filename, IntPtr storage, IntPtr name,
IntPtr real_name);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvCreateMemStorage", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvCreateMemStorage(int block_size = 0);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvClearMemStorage", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvClearMemStorage(IntPtr memory_storage);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvReleaseMemStorage", CallingConvention = CallingConvention.Cdecl)]
public static extern void cvReleaseMemStorage(ref IntPtr storage);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvGetSeqElem", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvGetSeqElem(IntPtr sequence, int index);
[DllImport(DllPaths.OPENCV_CORE_DLL_PATH, EntryPoint = "cvSeqPopFront", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvSeqPopFront(IntPtr sequence, IntPtr dest);
[DllImport(DllPaths.OPENCV_OBJDETECT_DLL_PATH, EntryPoint = "cvReleaseHaarClassifierCascade", CallingConvention =
CallingConvention.Cdecl)]
public static extern void cvReleaseHaarClassifierCascade(ref IntPtr cascade);
[DllImport(DllPaths.OPENCV_OBJDETECT_DLL_PATH, EntryPoint = "cvHaarDetectObjects", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvHaarDetectObjects(IntPtr image, IntPtr cascade, IntPtr mem_storage,
double scale_factor, int min_neighbours, int flags,
CvSize min_size, CvSize max_size);
}
class IntelImage : IDisposable
{
IntPtr iplImagePointer;
IplImage iplImageStruct;
public IntelImage(int width, int height, bool color = true)
{
iplImagePointer = NativeMethods.cvCreateImage(new CvSize() { width = width, height = height }, 8, color ? 3 : 1);
iplImageStruct = (IplImage)Marshal.PtrToStructure(iplImagePointer, typeof(IplImage));
}
public void CopyPixels(byte[] sourcePixelBuffer, int startIndex = 0)
{
Marshal.Copy(sourcePixelBuffer, startIndex, iplImageStruct.imageData, sourcePixelBuffer.Length);
}
public int Stride { get{return iplImageStruct.widthStep;} }
public IntPtr IplImage()
{
return iplImagePointer;
}
public void Dispose()
{
NativeMethods.cvReleaseImage(ref iplImagePointer);
GC.SuppressFinalize(this);
}
~IntelImage()
{
Dispose();
}
}
class HaarClassifier : IDisposable
{
IntPtr dllHandle, haarCascade, memoryStorage;
void LoadObjDetectDll()
{
dllHandle = NativeMethods.LoadLibrary(DllPaths.OPENCV_OBJDETECT_DLL_PATH);
if (dllHandle == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
public HaarClassifier(string cascadeFilePath)
{
LoadObjDetectDll(); // this is a workaround, leave it be
haarCascade = NativeMethods.cvLoad(cascadeFilePath, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
memoryStorage = NativeMethods.cvCreateMemStorage();
}
[Flags]
public enum DetectionFlags : int
{
None = 0,
DoCannyPruning = 1,
ScaleImage = 2,
FindBiggestObject = 4,
DoRoughSearch = 8
}
public LinkedList<CvRect> DetectObjects(IntelImage sourceImage, double scaleFactor = 1.1, int minNeighbours = 3,
DetectionFlags flags = DetectionFlags.None, CvSize minSize = default(CvSize), CvSize maxSize = default(CvSize))
{
NativeMethods.cvClearMemStorage(memoryStorage);
LinkedList<CvRect> result = new LinkedList<CvRect>();
IntPtr faceSequence = NativeMethods.cvHaarDetectObjects(sourceImage.IplImage(), haarCascade, memoryStorage,
scaleFactor, minNeighbours, (int)flags, minSize, maxSize);
for (;;)
{
IntPtr faceRectPointer = NativeMethods.cvGetSeqElem(faceSequence, 0);
if (faceRectPointer == IntPtr.Zero) break;
NativeMethods.cvSeqPopFront(faceSequence, IntPtr.Zero); // TODO: merge with cvGetSeqElem
result.AddFirst((CvRect)Marshal.PtrToStructure(faceRectPointer, typeof(CvRect)));
}
return result;
}
public void Dispose()
{
NativeMethods.cvReleaseMemStorage(ref memoryStorage);
NativeMethods.cvReleaseHaarClassifierCascade(ref haarCascade);
NativeMethods.FreeLibrary(dllHandle);
GC.SuppressFinalize(this);
}
~HaarClassifier()
{
Dispose();
}
}
class Program
{
static BitmapSource CreateBitmapSourceFromFile(string filePath, PixelFormat targetPixelFormat)
{
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
return new FormatConvertedBitmap(decoder.Frames[0], targetPixelFormat, null, 0);
}
}
static IntelImage CreateIntelImageFromBitmapSource(BitmapSource bitmapSource)
{
var intelImage = new IntelImage(bitmapSource.PixelWidth, bitmapSource.PixelHeight, bitmapSource.Format != PixelFormats.Gray8);
byte[] pixelBuffer = new byte[bitmapSource.PixelHeight * intelImage.Stride];
bitmapSource.CopyPixels(pixelBuffer, intelImage.Stride, 0);
intelImage.CopyPixels(pixelBuffer);
return intelImage;
}
static IntelImage CreateIntelImageFromFile(string filePath, bool convertToGrayscale = false)
{
var bitmapSource = CreateBitmapSourceFromFile(filePath, convertToGrayscale ? PixelFormats.Gray8 : PixelFormats.Bgr24);
return CreateIntelImageFromBitmapSource(bitmapSource);
}
static void Main(string[] args)
{
var image = CreateIntelImageFromFile("myface.jpg");
var haar = new HaarClassifier("haarcascade_frontalface_alt2.xml"); // get it from opencv/data/haarcascades
var faces = haar.DetectObjects(image);
foreach(var faceRect in faces)
{
Console.WriteLine("Face detected: {0}, {1} - {2}, {3}", faceRect.x, faceRect.y,
faceRect.x + faceRect.width, faceRect.y + faceRect.height);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment