namespace OpenCvSharp.Demo { using System; using System.Collections.Generic; using OpenCvSharp; /// /// Detected object data /// public class DetectedObject { PointsDataStabilizer marksStabilizer = null; /// /// Default constructor /// /// Data stabilizer params public DetectedObject(DataStabilizerParams stabilizerParameters) { marksStabilizer = new PointsDataStabilizer(stabilizerParameters); marksStabilizer.PerPointProcessing = false; Marks = null; Elements = new DetectedObject[0]; } /// /// Constructs object with name and region /// /// Detected objetc name /// Detected object ROI on the source image /// /// Data stabilizer params public DetectedObject(DataStabilizerParams stabilizerParameters, String name, Rect region) : this(stabilizerParameters) { Name = name; Region = region; } /// /// Constructs object with name and marks /// /// Detected object name /// Object landmarks (in the source image space) /// /// Data stabilizer params public DetectedObject(DataStabilizerParams stabilizerParameters, String name, OpenCvSharp.Point[] marks) : this(stabilizerParameters) { Name = name; marksStabilizer.Sample = marks; Marks = marksStabilizer.Sample; Region = Rect.BoundingBoxForPoints(marks); } /// /// Object name /// public String Name { get; protected set; } /// /// Object region on the source image /// public Rect Region { get; protected set; } /// /// Object key points /// public OpenCvSharp.Point[] Marks { get; protected set; } /// /// Sub-objects /// public DetectedObject[] Elements { get; set; } /// /// Applies new marks /// /// New points set /// Signals whether we should apply stabilizer /// True is new data applied, false if stabilizer rejected new data public virtual bool SetMarks(Point[] marks) { marksStabilizer.Sample = marks; Marks = marksStabilizer.Sample; return marksStabilizer.LastApplied; } } /// /// Detected face is a bit more complicated object with some extra "lazy" stuff /// public class DetectedFace : DetectedObject { /// /// Face elements /// public enum FaceElements { Jaw = 0, LeftEyebrow, RightEyebrow, NoseBridge, Nose, LeftEye, RightEye, OuterLip, InnerLip } /// /// Simple 2d integer triangle /// public struct Triangle { public Point i; public Point j; public Point k; /// /// Special constructor /// /// Vec containing triangle (like those returned by Subdiv.gettrianglesList() method) public Triangle(Vec6f vec) { i = new Point((int)(vec[0] + 0.5), (int)(vec[1] + 0.5)); j = new Point((int)(vec[2] + 0.5), (int)(vec[3] + 0.5)); k = new Point((int)(vec[4] + 0.5), (int)(vec[5] + 0.5)); } /// /// Converts triangle to points array /// /// Array of triangle points public Point[] ToArray() { return new Point[] { i, j, k }; } } /// /// Face data like convex hull, delaunay triangulation etc. /// public sealed class FaceInfo { /// /// Face shape convex hull /// public Point[] ConvexHull { get; private set; } /// /// Face shape triangulation /// public Triangle[] DelaunayTriangles { get; private set; } /// /// Constructs face info /// /// Convex hull /// Delaunay triangulation data internal FaceInfo(Point[] hull, Triangle[] triangles) { ConvexHull = hull; DelaunayTriangles = triangles; } } /// /// Face info, heavy and lazy-computed data /// public FaceInfo Info { get { if (null == faceInfo) { // it's valid to have no marks (no shape predictor used) if (null == Marks) return null; // convex hull Point[] hull = Cv2.ConvexHull(Marks); // compute triangles Rect bounds = Rect.BoundingBoxForPoints(hull); Subdiv2D subdiv = new Subdiv2D(bounds); foreach (Point pt in Marks) subdiv.Insert(pt); Vec6f[] vecs = subdiv.GetTriangleList(); List triangles = new List(); for (int i = 0; i < vecs.Length; ++i) { Triangle t = new Triangle(vecs[i]); if (bounds.Contains(t.ToArray())) triangles.Add(t); } // save faceInfo = new FaceInfo(hull, triangles.ToArray()); } return faceInfo; } } protected FaceInfo faceInfo = null; RectStabilizer faceStabilizer = null; /// /// Constructs DetectedFace object /// /// Face roi (rectangle) in the source image space /// /// Data stabilizer params public DetectedFace(DataStabilizerParams stabilizerParameters, Rect roi) : base(stabilizerParameters, "Face", roi) { faceStabilizer = new RectStabilizer(stabilizerParameters); } /// /// Sets face rect /// /// Face rect public void SetRegion(Rect roi) { faceStabilizer.Sample = roi; Region = faceStabilizer.Sample; faceInfo = null; } /// /// Creates new sub-object /// /// Face element type /// New object name /// Starting mark index /// Ending mark index /// Scale factor /// [optional] Signals whether we should apply new marks public bool DefineSubObject(FaceElements element, string name, int fromMark, int toMark, bool updateMarks = true) { int index = (int)element; Point[] subset = Marks.SubsetFromTo(fromMark, toMark); DetectedObject obj = Elements[index]; // first instance bool applied = false; if (null == obj) { applied = true; obj = new DetectedObject(faceStabilizer.Params, name, subset); Elements[index] = obj; } // updated else { if (updateMarks || null == obj.Marks || 0 == obj.Marks.Length) applied = obj.SetMarks(subset); } return applied; } /// /// Sets face landmarks /// /// New landmarks set public void SetLandmarks(Point[] points) { // set marks Marks = points; // apply subs if (null == Elements || Elements.Length < 9) Elements = new DetectedObject[9]; int keysApplied = 0; // key elements if (null != Marks) { keysApplied += DefineSubObject(FaceElements.Nose, "Nose", 30, 35) ? 1 : 0; keysApplied += DefineSubObject(FaceElements.LeftEye, "Eye", 36, 41) ? 1 : 0; keysApplied += DefineSubObject(FaceElements.RightEye, "Eye", 42, 47) ? 1 : 0; // non-key but independent DefineSubObject(FaceElements.OuterLip, "Lip", 48, 59); DefineSubObject(FaceElements.InnerLip, "Lip", 60, 67); // dependent bool updateDependants = keysApplied > 0; DefineSubObject(FaceElements.LeftEyebrow, "Eyebrow", 17, 21, updateDependants); DefineSubObject(FaceElements.RightEyebrow, "Eyebrow", 22, 26, updateDependants); DefineSubObject(FaceElements.NoseBridge, "Nose bridge", 27, 30, updateDependants); DefineSubObject(FaceElements.Jaw, "Jaw", 0, 16, updateDependants); } // re-fetch marks from sub-objects as they have separate stabilizers List fetched = new List(); foreach (DetectedObject obj in Elements) if (obj.Marks != null) fetched.AddRange(obj.Marks); Marks = fetched.ToArray(); // drop cache faceInfo = null; } } }