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