Learning Microsoft Cognitive Services
上QQ阅读APP看书,第一时间看更新

Grouping similar faces

If you have several images of faces, one thing you may want to do is group the faces. Typically, you will want to group faces based on similarity, which is a feature the Face API provides.

By providing the API with a list of face IDs, it will respond with one or more groups. One group consists of faces that are similar looking. Usually, this means that the faces belong to the same person. Faces that cannot find any similar counterparts are placed in a group we'll call MessyGroup.

Create a new View called FaceGroupingView.xaml. The View should have six image elements, with corresponding titles and textboxes for face IDs. It should also have a button for our group command and a textbox to output the grouping result.

In the corresponding FaceGroupingViewModel.xaml View model, you should add the BitmapImage properties for all images. You should also add the string properties for the face IDs and one for the result. There is also a need for an ICommand property.

At the start of the ViewModel, we declare some private variables, as follows:

    private FaceServiceClient _faceServiceClient;
    private List<string> _imageFiles = new List<string>();
    private List<Guid> _faceIds = new List<Guid>();

The first one is used to access the Face API. The second one contains a list of strings that in turn contain the location of our images. The last list contains the detected face IDs.

The constructor accepts a parameter of the FaceServiceClient type. It assigns it to the corresponding variable and calls the Initialize function. This creates our ICommand object and calls a function to add our images to the application.

In the function that adds images, we add hardcoded image paths to our _imageFiles list. For this example, we add six. Using a for loop, we generate each BitmapImage property. When we have an image, we want to detect faces in it:

    try {
        using (Stream fileStream = File.OpenRead(_imageFiles[i])) {
            Face[] faces = await
            _faceServiceClient.DetectAsync(fileStream);

We do not need any more data than the generated face ID, which we know is stored for 24 hours after detection:

            _faceIds.Add(faces[0].FaceId);
            CreateImageSources(image, i, faces[0].FaceId);
        }
    }

Assuming that there is only one face per image, we add that face ID to our _faceIds list. The image, face ID, and current iteration number in the loop are passed on to a new function, CreateImageSources. This function contains a switch case based on the iteration number. Based on the number, we assign the image and face ID to the corresponding image and image ID property. This is then shown in the UI.

We have a button to group the images. To group the images, we call the Face API's GroupAsync method, passing on an array of face IDs, as shown in the following code. The array of face IDs must contain at least two elements, and it cannot contain more than 1,000 elements:

    private async void GroupFaces(object obj) {
        try {
            GroupResultfaceGroups = await _faceServiceClient.GroupAsync(_faceIds.ToArray());

The response is a GroupResult type, which may contain one or more groups, as well as the messy group. We check to see whether there is a response and then we parse it, as shown in the following code:

            if (faceGroups != null)
                FaceGroupingResult = ParseGroupResult(faceGroups);
        }

Before looking at the ParseGroupResult method, add the corresponding catch clause and close-up GroupFaces function.

When parsing the results, we first create a StringBuilder class to hold our text. Then we get the groups from the result. A group is an array of face IDs of the images in that group. All groups are stored in a list, and we append the number of groups to the StringBuilder class, as shown in the following code:

private string ParseGroupResult(GroupResultfaceGroups) {
   StringBuilder result = new StringBuilder();
   List<Guid[]>groups = faceGroups.Groups;
   result.AppendFormat("There are {0} group(s)\n", groups.Count);

We loop through the list of groups. Inside this loop, we loop through each item in the group. For the sake of readability, we have a helper function to find the image name from the ID. It finds the index in our _faceIds list. This is then used in the image name, so if the index is 2, the image name would be Image 3. For this to give the intended effect, you must have placed the images in a logical order, as follows:

            result.Append("Groups:\t");

            foreach(Guid[] guid in groups)
            {
                foreach(Guid id in guid)
                {
                    result.AppendFormat("{0} - ", GetImageName(id));
                }
                result.Append("\n");
            }

The GroupResult method may also contain a MessyGroup array. This is an array of Guid variables containing the face IDs in that group. We loop through this array and append the image name, the same way we did with the regular groups, as shown in the following code:

            result.Append("Messy group:\t");

            Guid[] messyGroup = faceGroups.MessyGroup;
            foreach(Guidguid in messyGroup)
            {
                result.AppendFormat("{0} - ", GetImageName(guid));
            }

We end the function by returning the StringBuilder function's text, which will output it to the screen, as follows:

            return result.ToString();
        }

Make sure that the ViewModel instances have been created in the MainViewModel.cs file. Also, make sure that the View has been added as a TabItem property in the MainView.xaml file. Compile and test the application.

If you are using the sample images provided, you may end up with something like the following:

Grouping similar faces