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

Finding similar faces

Using the Face API, you can find faces similar to a provided face. The API allows for two search modes. Match person mode is the default mode. This will match faces to the same person, according to an internal same-person threshold. The other is match face mode, which will ignore the same-person threshold. This returns matches that are similar, but the similarity may be low.

In the example code provided, we have three buttons in our UI: one for generating a face list, another for adding faces to the list, and, finally, one to find similar faces. We need a textbox to specify a name for the face list. For convenience, we add a list box, outputting the persisted face IDs from the face list. We also add an image element to show the image we are checking, and a textbox outputting the result.

In the corresponding ViewModel, we need to add a BitmapImage property for the image element. We need two string properties: one for our face-list name and one for the API call result. To get data to our list box, we need an ObservableCollection property containing Guids. The buttons need to be hooked up to individual ICommand properties.

We declare two private variables at the start of the ViewModel, as shown in the following code. The first one is a bool variable to indicate whether or not the face list already exists. The other is used to access the Face API:

    private bool _faceListExists = false;
    private FaceServiceClient _faceServiceClient;

The constructor should accept the FaceServiceClient parameter, which it assigns to the preceding variable. It will then call an Initialize function, as follows:

    private async void Initialize()
    {
        FaceListName = "Chapter2";

        CreateFaceListCommand = new DelegateCommand(CreateFaceListAsync, CanCreateFaceList);
        FindSimilarFaceCommand = new DelegateCommand(FindSimilarFace);
        AddExampleFacesToListCommand = new DelegateCommand(AddExampleFacesToList, CanAddExampleFaces);

First, we initialize the FaceListName property to Chapter2. Next, we create the command objects, specifying actions and predicates.

We finish the Initialize function by calling two functions, as shown in the following code. One checks whether the face list exists, while the second updates the list of face IDs:

        await DoesFaceListExistAsync();
        UpdateFaceGuidsAsync();
    }

To check whether a given face list exists, we first need to get a list of all face lists. We do this by calling the ListFaceListsAsync method, which will return a FaceListMetadata array. We make sure that the result has data before we loop through the array, as shown in the following code:

    private async Task DoesFaceListExistAsync()
    {
        FaceListMetadata[] faceLists = await _faceServiceClient.ListFaceListsAsync();

Each FaceListMetadata array, from the resultant array, contains a face-list ID, a name of the face list, and user-provided data. For this example, we are just interested in the name. If the face-list name that we have specified is the name of any face list returned, we set the _faceListExists parameter to true, as shown in the following code:

    foreach (FaceListMetadatafaceList in faceLists) {
        if (faceList.Name.Equals(FaceListName)) {
            _faceListExists = true;
            break;
        }
    }

If the face list exists, we can update the list of face IDs.

To get the faces in a face list, we need to get the face list first. This is done with a call to the Face API's function, the GetFaceListAsync method. This requires the face-list ID to be passed as a parameter. The face-list ID needs to be in lowercase or digits, and can contain a maximum of 64 characters. For the sake of simplicity, we use the face-list name as the face ID, as follows:

    private async void UpdateFaceGuidsAsync() {
        if (!_faceListExists) return;

        try { 
            FaceListfaceList = await _faceServiceClient.GetFaceListAsync(FaceListName.ToLower());

The result of this API call is a FaceList object, containing the face-list ID and face-list name. It also contains user-provided data and an array of persisted faces.

We check whether we have any data and then get the array of persisted faces. Looping through this array, we are able to get the PersistedFaceId parameter (as a guid variable) and user-provided data of each item. The persisted face ID is added to the FaceIds ObservableCollection, as shown in the following code:

        if (faceList == null) return;

        PersonFace[] faces = faceList.PersistedFaces;

        foreach (PersonFace face in faces) {
            FaceIds.Add(face.PersistedFaceId);
        }

Finish the function by adding the corresponding catch clause.

If the face list does not exist and we have specified a face-list name, then we can create a new face list, as follows:

    private async void CreateFaceListAsync(object obj) {
        try {
            if (!_faceListExists) {
                await _faceServiceClient.CreateFaceListAsync (
FaceListName.ToLower(), FaceListName, string.Empty);
                await DoesFaceListExistAsync();
            }
        }

First, we check to see that the face list does not exist. Using the _faceServiceClient parameter, you are required to pass on a face-list ID, a face-list name, and user data. As seen previously, the face-list ID needs to be lowercase characters or digits.

Using the REST API, the user parameter is optional, and as such, you would not have to provide it.

After we have created a face list, we want to ensure that it exists. We do this by a call to the previously created DoesFaceListExistAsync function. Add the catch clause to finish the function.

If the named face list exists, we can add faces to this list. Add the AddExampleFacesToList function. It should accept object as a parameter. I will leave the details of adding the images up to you. In the provided example, we get a list of images from a given directory and loop through it.

With the file path of a given image, we open the image as a Stream. To optimize it for our similarity operation, we find the FaceRectangle parameter in an image. As there should be only one face per image in the face list, we select the first element in the Face array, as follows:

    using (StreamfileStream = File.OpenRead(image))
    {
        Face[] faces = await _faceServiceClient.DetectAsync(fileStream);
        FaceRectanglefaceRectangle = faces[0].FaceRectangle;

Adding the face to the face list is as simple as calling the AddFaceToFaceListAsync function. We need to specify the face-list ID and the image. The image may come from a Stream (as in our case) or a URL. Optionally, we can add user data and the face rectangle of the image, as follows:

AddPersistedFaceResult addFacesResult = await _faceServiceClient.AddFaceToFaceListAsync(FaceListName.ToLower(), fileStream, null, faceRectangle);
UpdateFaceGuidsAsync();

The result of the API call is an AddPersistedFaceResult variable. This contains the persisted face ID, which is different from a face ID in the DetectAsync call. A face added to a face list will not expire until it is deleted.

We finish the function by calling the UpdateFaceGuidsAsync method.

Finally, we create our FindSimilarFace function, also accepting object as a parameter. To be able to search for similar faces, we need a face ID (the Guid variable) from the DetectAsync method. This can be called with a local image or from a URL. The example code opens a file browser and allows the user to browse for an image.

With the face ID, we can search for similar faces, as shown in the following code:

    try {
        SimilarPersistedFace[] similarFaces = await _faceServiceClient.FindSimilarAsync (findFaceGuid, FaceListName.ToLower(), 3);

We call the FindSimilarAsync function. The first parameter is the face ID of the face we specified. The next parameter is the face-list ID, and the final parameter is the number of candidate faces returned. The default for this is 20, so it is often best to specify a number.

Instead of using a face list to find similar faces, you can use an array of the Guid variable. That array should contain face IDs retrieved from the DetectAsync method.

At the time of writing, the NuGet API package only supports match person mode. If you are using the REST API directly, you can specify the mode as a parameter.

Depending on the mode selected, the result will contain either the face ID or the persisted face ID of similar faces. It will also contain the confidence of the similarity of the given face.

To delete a face from the face list, call the following function in the Face API:

    DeleteFaceFromFaceListAsync(FACELISTID, PERSISTEDFACEID)

To delete a face list, call the following function in the Face API:

    DeleteFaceListAsync(FACELISTID)

To update a face list, call the following function in the Face API:

    UpdateFaceListAsync(FACELISTID, FACELISTNAME, USERDATA)