/// <summary> /// Split the given face on the given plane. Remove the original face /// and add as many new faces as required for the split. /// </summary> /// <param name="faceIndex">The index of the face to split.</param> /// <param name="plane">The plane to split the face on. The face will not be split /// if it is not intersected by this plane.</param> /// <param name="onPlaneDistance">If a given edge of the face has a vertex that is within /// this distance of the plane, the edge will not be split.</param> /// <returns>Returns if the edge was actually split.</returns> public bool SplitFace(int faceIndex, Plane plane, double onPlaneDistance = .001) { var newVertices = new List <Vector3Float>(); var newFaces = new List <Face>(); if (Faces[faceIndex].Split(this.Vertices, plane, newFaces, newVertices, onPlaneDistance)) { var vertexCount = Vertices.Count; // remove the face index Faces.RemoveAt(faceIndex); // add the new vertices Vertices.AddRange(newVertices); // add the new faces (have to make the vertex indices to the new vertices foreach (var newFace in newFaces) { Face faceNewIndices = newFace; faceNewIndices.v0 += vertexCount; faceNewIndices.v1 += vertexCount; faceNewIndices.v2 += vertexCount; Faces.Add(faceNewIndices); } CleanAndMerge(); return(true); } return(false); }
/// <inheritdoc /> public override List <Chain <TNode> > GetChains(IGraph <TNode> graph) { Initialize(graph); if (Faces.Count != 0) { // Get faces and remove the largest one Faces.RemoveAt(Faces.MaxBy(x => x.Count)); } var decomposition = new PartialDecomposition(Faces); decomposition = GetFirstComponent(decomposition); while (decomposition.GetAllCoveredVertices().Count != Graph.VerticesCount) { decomposition = ExtendDecomposition(decomposition); } var chains = decomposition.GetFinalDecomposition(); logger.WriteLine("Final decomposition:"); foreach (var chain in chains) { logger.WriteLine($"[{string.Join(",", chain.Nodes)}]"); } return(chains); }
/// <inheritdoc /> public override List <Chain <TNode> > GetChains(IGraph <TNode> graph) { Initialize(graph); if (Faces.Count != 0) { // Get faces and remove the largest one Faces.RemoveAt(Faces.MaxBy(x => x.Count)); } var chains = new List <Chain <TNode> >(); if (Faces.Count != 0) { chains.Add(new Chain <TNode>(GetFirstCycle(Faces), chains.Count)); } // Process cycles while (Faces.Count != 0) { var chain = GetNextCycle(); var isFromCycle = chain != null; if (chain == null) { chain = GetNextPath(); } // Must not happen. There must always be a cycle or a path while we have at least one face available. if (chain == null) { throw new InvalidOperationException(); } chains.Add(new Chain <TNode>(chain, chains.Count) { IsFromFace = isFromCycle, }); } // Add remaining nodes while (Graph.Vertices.Any(x => !IsCovered(x))) { var chain = GetNextPath(); // Must not happen. There must always be a path while there are vertices that are not covered. (The graph must be connected) if (chain == null) { throw new InvalidOperationException(); } chains.Add(new Chain <TNode>(chain, chains.Count) { IsFromFace = false, }); } return(chains); }
private void _removeFace(int id) { try { int index = -1; for (int i = 0; i < Faces.Count; i++) { if (Faces[i].Id == id) { index = i; break; } } // Index found if (index != -1) { Face v = Faces[index]; // Raise event first RaiseFaceLost(v.Id, v.Gender, v.Age, v.DwellTime); m_startTimeMap.Remove(v.Id); // Remove index from map and Face from list Faces.RemoveAt(index); FaceCount = Faces.Count; Console.WriteLine("Remove faces " + id + " at index " + index); } } catch (Exception ex) { Console.WriteLine("REMOVE VIEWER ERROR: " + ex.Message); ActivityLog += "REMOVE VIEWER ERROR:" + ex.Message + "\n"; } }
private void _updateFacesList(JArray ja) { ObservableCollection <Face> newColl = new ObservableCollection <Face>(); // Main face computation int mainFaceID = -1; float maxHeadSize = -1; // Copy new faces in existing list if (ja != null) { foreach (var v in ja) { // Parse basic info int id = int.Parse(v["id"].ToString()); string gender = v["gender"].ToString(); string age = v["age"].ToString(); var ageInteger = Int16.Parse(age); string ageRange = ageInteger <= 16 ? "child" : ageInteger <= 30 ? "young adult" : ageInteger <= 45 ? "middle-aged adult" : "old-aged adult"; var x = float.Parse(v["location"]["x"].ToString(), CultureInfo.InvariantCulture); // TODO: normalize? (or on C++ side?) var y = float.Parse(v["location"]["y"].ToString(), CultureInfo.InvariantCulture); var width = float.Parse(v["location"]["width"].ToString(), CultureInfo.InvariantCulture); var height = float.Parse(v["location"]["height"].ToString(), CultureInfo.InvariantCulture); // Intuiface coordinates system: move X&Y coords to represent the center of the head, not the top left corner x += width / 2; y += height / 2; // TODO: estimate distance in cm based on head size and camera focal / calibration step ?? var faceSize = (int)(width * height * 100 * 100); // Current value: % of head area over total image area var mainEmotion = v["mainEmotion"]["emotion"].ToString(); var mainEmotionConfidence = float.Parse(v["mainEmotion"]["confidence"].ToString(), CultureInfo.InvariantCulture); // Parse additional emotions EmotionConfidence emotionConfidence = new EmotionConfidence(); emotionConfidence.Angry = float.Parse(v["emotions"]["anger"].ToString(), CultureInfo.InvariantCulture); emotionConfidence.Happy = float.Parse(v["emotions"]["happy"].ToString(), CultureInfo.InvariantCulture); emotionConfidence.Neutral = float.Parse(v["emotions"]["neutral"].ToString(), CultureInfo.InvariantCulture); emotionConfidence.Sad = float.Parse(v["emotions"]["sad"].ToString(), CultureInfo.InvariantCulture); emotionConfidence.Surprised = float.Parse(v["emotions"]["surprise"].ToString(), CultureInfo.InvariantCulture); // Parse head pose estimation HeadPoseEstimation headPoseEstimation = new HeadPoseEstimation(); headPoseEstimation.Pitch = float.Parse(v["headpose"]["pitch"].ToString(), CultureInfo.InvariantCulture); headPoseEstimation.Yaw = float.Parse(v["headpose"]["yaw"].ToString(), CultureInfo.InvariantCulture); headPoseEstimation.Roll = float.Parse(v["headpose"]["roll"].ToString(), CultureInfo.InvariantCulture); // If face size filtering is active, check is head is bigger than threshold if (faceSize < m_dMinimumFaceSize) { // Don't add face to list continue; } // TODO: use real "distance" and take the min one. if (faceSize > maxHeadSize) { maxHeadSize = faceSize; mainFaceID = id; } newColl.Add(new Face() { Id = id, X = x, Y = y, Width = width, Height = height, FaceSize = faceSize, Gender = gender, Age = age, AgeRange = ageRange, MainEmotion = mainEmotion, MainEmotionConfidence = mainEmotionConfidence, EmotionConfidence = emotionConfidence, HeadPoseEstimation = headPoseEstimation }); } } // Order by ID newColl = new ObservableCollection <Face>(newColl.OrderBy(i => i.Id)); // Compare old (current) list and new list var removed = Faces.Except(newColl); var added = newColl.Except(Faces); if (removed.Count() > 0) { foreach (var face in removed) { // Raise face lost event Console.WriteLine("Face lost: " + face); RaiseFaceLost(face.Id, face.Gender, face.Age, face.DwellTime); m_startTimeMap.Remove(face.Id); } } if (added.Count() > 0) { foreach (var face in added) { // Raise face added event Console.WriteLine("Face added: " + face); RaiseFaceDetected(face.Id, face.Gender, face.Age); m_startTimeMap.Add(face.Id, DateTime.Now); } } // Update elements in current list with new list int newCount = newColl.Count; int oldCount = Faces.Count; if (newCount != oldCount) { // Raise face count changed event Console.WriteLine("Face count changed: " + newCount); RaiseFaceCountChanged(newCount); } // Adjust number of faces in Faces // New users to add if (newCount > oldCount) { for (int i = 0; i < newCount - oldCount; i++) { Faces.Add(new Face()); } } // Users to remove else if (newCount < oldCount) { for (int i = newCount; i < oldCount; i++) { Faces.RemoveAt(newCount); } } // Copy new coll properties in existing faces list. int index = 0; foreach (var item in newColl) { try { Faces[index].Id = item.Id; Faces[index].X = item.X; Faces[index].Y = item.Y; Faces[index].Width = item.Width; Faces[index].Height = item.Height; Faces[index].Gender = item.Gender; Faces[index].Age = item.Age; Faces[index].AgeRange = item.AgeRange; Faces[index].DwellTime = (DateTime.Now - m_startTimeMap[item.Id]).TotalSeconds; Faces[index].FaceSize = item.FaceSize; Faces[index].MainEmotion = item.MainEmotion; Faces[index].MainEmotionConfidence = item.MainEmotionConfidence; // Additional emotions Faces[index].EmotionConfidence = item.EmotionConfidence; // Head pose estimation Faces[index].HeadPoseEstimation = item.HeadPoseEstimation; } catch (Exception ex) { ActivityLog += ex.Message + "\n"; throw; } index++; } // Update face count FaceCount = Faces.Count(); // Copy main face info if (FaceCount > 0) { var item = Faces.FirstOrDefault(i => i.Id == mainFaceID); if (item != null) { MainFace.Id = item.Id; MainFace.X = item.X; MainFace.Y = item.Y; MainFace.Width = item.Width; MainFace.Height = item.Height; MainFace.FaceSize = item.FaceSize; MainFace.Gender = item.Gender; MainFace.Age = item.Age; MainFace.AgeRange = item.AgeRange; MainFace.DwellTime = (DateTime.Now - m_startTimeMap[item.Id]).TotalSeconds; MainFace.MainEmotion = item.MainEmotion; MainFace.MainEmotionConfidence = item.MainEmotionConfidence; // Additional emotions MainFace.EmotionConfidence = item.EmotionConfidence; // Head pose estimation MainFace.HeadPoseEstimation = item.HeadPoseEstimation; // Mark the face detected IsMainFaceDetected = true; } else { IsMainFaceDetected = false; } } else { IsMainFaceDetected = false; } }
/// <summary> /// Tries to find a face that neighbours with covered vertices in the smallest depth. /// Chooses the smallest one if there are multiple in the same depth. /// </summary> /// <returns></returns> private List <TNode> GetNextCycle() { var bestFaceIndex = -1; var bestFaceSize = int.MaxValue; var bestFaceDepth = int.MaxValue; for (var i = 0; i < Faces.Count; i++) { var face = Faces[i]; // Check nodes in the face and also all neighbouring nodes var nodesToCheck = face.Concat(face.SelectMany(x => Graph.GetNeighbours(x))).Where(IsCovered).ToList(); // If nodesToCheck is empty, it does not neighbour with any covered node and therefore we skip it if (nodesToCheck.Count == 0) { continue; } var minDepth = nodesToCheck.Min(GetDepth); var size = face.Count(x => !IsCovered(x)); if (minDepth < bestFaceDepth || (minDepth == bestFaceDepth && size < bestFaceSize)) { bestFaceIndex = i; bestFaceSize = size; bestFaceDepth = minDepth; } } // Return null if no face was found. That means that we must now consider paths and then come back to cycles. if (bestFaceIndex == -1) { return(null); } var nextFace = Faces[bestFaceIndex].Where(x => !IsCovered(x)).ToList(); Faces.RemoveAt(bestFaceIndex); // This must not happen as all faces with all nodes covered must be already removed if (nextFace.Count == 0) { throw new InvalidOperationException(); } var chain = new List <TNode>(); var counter = ChainsCounter; // Find a vertex that neighbours with a covered node var firstVertexIndex = -1; for (var i = 0; i < nextFace.Count; i++) { var vertex = nextFace[i]; if (Graph.GetNeighbours(vertex).Any(IsCovered)) { firstVertexIndex = i; break; } } // This must not happen as we considered only faces that neighbour with already covered vertices if (firstVertexIndex == -1) { throw new InvalidOperationException(); } // Use the first vertex SetDepth(nextFace[firstVertexIndex], counter++); chain.Add(nextFace[firstVertexIndex]); nextFace.RemoveAt(firstVertexIndex); // Add vertices starting with the ones that neighbour with nodes on the highest depths while (nextFace.Count != 0) { var nextVertexIndex = nextFace.MinBy(SmallestCoveredNeighbourDepth); var nextVertex = nextFace[nextVertexIndex]; SetDepth(nextVertex, counter++); chain.Add(nextVertex); nextFace.Remove(nextVertex); } // Fix depths foreach (var node in chain) { SetDepth(node, ChainsCounter); } ChainsCounter++; RemoveCoveredNodes(Faces); return(chain); }