Adding people to be identified
Before we can go on to identify a person, we need to have something to identify them from. To identify a person, we need a PersonGroup
property. This is a group that contains several Persons
properties.
Creating a view
In the administration control, we will execute several operations in this regard. The UI should contain two textbox elements, two list box elements, and six buttons. The two textbox elements will allow us to input a name for the person group and a name for the person. One list box will list all person groups that we have available. The other will list all the persons in any given group.
We have buttons for each of the operations that we want to execute, which are as follows:
- Add person group
- Delete person group
- Train person group
- Add person
- Delete person
- Add person face
The View
model should have two ObservableCollection
properties: one of a PersonGroup
type and the other of a Person
type. We should also add three string
properties. One will be for our person group name, the other for our person name. The last will hold some status text. We also want a PersonGroup
property for the selected person group. Finally, we want a Person
property holding the selected person.
In our View
model, we want to add a private
variable for the FaceServiceClient
method, as shown in the following code:
private FaceServiceClient _faceServiceClient;
This should be assigned in the constructor, which should accept a parameter of a FaceServiceClient
type. It should also call an initialization function, which will initialize six ICommand
properties. These maps to the buttons, created earlier. The initialization function should call the GetPersonGroups
function to list all person groups available, as shown in the following code:
private async void GetPersonGroups() { try { PersonGroup[] personGroups = await _faceServiceClient.ListPersonGroupsAsync();
The ListPersonGroupsAsync
function does not take any parameters, and returns a PersonGroup
array if successfully executed, as shown in the following code:
if(personGroups == null || personGroups.Length == 0) { StatusText = "No person groups found."; return; } PersonGroups.Clear(); foreach (PersonGrouppersonGroup in personGroups) { PersonGroups.Add(personGroup); } }
We then check to see whether the array contains any elements. If it does, we clear out the existing PersonGroups
list. Then we loop through each item of the PersonGroup
array and add them to the PersonGroups
list.
If no person groups exist, we can add a new one by filling in a name. The name you fill in here will also be used as a person group ID. This means that it can include numbers and English lowercase letters, the "-" character (hyphen), and the "_" character (underscore). The maximum length is 64 characters. When it is filled in, we can add a person group.
Adding person groups
First, we call the DoesPersonGroupExistAsync
function, specifying PersonGroupName
as a parameter, as shown in the following code. If this is true
, then the name we have given already exists, and as such, we are not allowed to add it. Note how we call the ToLower
function on the name. This is so we are sure that the ID is in lowercase:
private async void AddPersonGroup(object obj) { try { if(await DoesPersonGroupExistAsync(PersonGroupName.ToLower())) { StatusText = $"Person group {PersonGroupName} already exist"; return; }
If the person group does not exist, we call the CreatePersonGroupAsync
function, as shown in the following code. Again, we specify the PersonGroupName
as lowercase in the first parameter. This represents the ID of the group. The second parameter indicates the name we want. We end the function by calling the GetPersonGroups
function again, so we get the newly added group in our list:
await _faceServiceClient.CreatePersonGroupAsync (PersonGroupName.ToLower(), PersonGroupName); StatusText = $"Person group {PersonGroupName} added"; GetPersonGroups(); }
The DoesPersonGroupExistAsync
function makes one API call. It tries to call the GetPersonGroupAsync
function, with the person group ID specified as a parameter. If the resultant PersonGroup
list is anything but null
, we return true
.
To delete a person group, a group must be selected as follows:
private async void DeletePersonGroup(object obj) { try { await _faceServiceClient.DeletePersonGroupAsync (SelectedPersonGroup.PersonGroupId); StatusText = $"Deleted person group {SelectedPersonGroup.Name}"; GetPersonGroups(); }
The API call to the DeletePersonGroupAsync
function requires a person group ID as a parameter. We get this from the selected person group. If no exception is caught, then the call has completed successfully, and we call the GetPersonGroups
function to update our list.
When a person group is selected from the list, we make sure that we call the GetPersons
function. This will update the list of persons, as follows:
private async void GetPersons() { if (SelectedPersonGroup == null) return; Persons.Clear(); try { Person[] persons = await _faceServiceClient.GetPersonsAsync(SelectedPersonGroup.PersonGroupId);
We make sure the selected person group is not null
. If it is not, we clear our persons
list. The API call to the GetPersonsAsync
function requires a person group ID as a parameter. A successful call will result in a Person
array.
If the resultant array contains any elements, we loop through it. Each Person
object is added to our persons
list, as shown in the following code:
if (persons == null || persons.Length == 0) { StatusText = $"No persons found in {SelectedPersonGroup.Name}."; return; } foreach (Person person in persons) { Persons.Add(person); } }
Adding new persons
If no persons exist, we can add new ones. To add a new one, a person group must be selected, and a name of the person must be filled in. With this in place, we can click on the Add button:
private async void AddPerson(object obj) { try { CreatePersonResultpersonId = await _faceServiceClient.CreatePersonAsync(SelectedPersonGroup.PersonGroupId, PersonName); StatusText = $"Added person {PersonName} got ID: {personId.PersonId.ToString()}"; GetPersons(); }
The API call to the CreatePersonAsync
function requires a person group ID as the first parameter. The next parameter is the name of the person. Optionally, we can add user data as a third parameter. In this case, it should be a string. When a new person has been created, we update the persons
list by calling the GetPersons
function again.
If we have selected a person group and a person, then we will be able to delete that person, as shown in the following code:
private async void DeletePerson(object obj) { try { await _faceServiceClient.DeletePersonAsync (SelectedPersonGroup.PersonGroupId, SelectedPerson.PersonId); StatusText = $"Deleted {SelectedPerson.Name} from {SelectedPersonGroup.Name}"; GetPersons(); }
To delete a person, we make a call to the DeletePersonAsync
function. This requires the person group ID of the person group the person lives in. It also requires the ID of the person we want to delete. If no exceptions are caught, then the call succeeded, and we call the GetPersons
function to update our person list.
Our administration control now looks similar to the following screenshot:
Associating faces with a person
Before we can identify a person, we need to associate faces with that person. With a given person group and person selected, we can add faces. To do so, we open a file dialog. When we have an image file, we can add the face to the person, as follows:
using (StreamimageFile = File.OpenRead(filePath)) { AddPersistedFaceResultaddFaceResult = await _faceServiceClient.AddPersonFaceAsync( SelectedPersonGroup.PersonGroupId, SelectedPerson.PersonId, imageFile); if (addFaceResult != null) { StatusText = $"Face added for {SelectedPerson.Name}. Remember to train the person group!"; } }
We open the image file as a Stream
. This file is passed on as the third parameter in our call to the AddPersonFaceAsync
function. Instead of a stream, we could have passed a URL to an image.
The first parameter in the call is the person group ID of the group in which the person lives. The next parameter is the person ID.
Some optional parameters to include are user data in the form of a string and a FaceRectangle
parameter for the image. The FaceRectangle
parameter is required if there is more than one face in the image.
A successful call will result in an AddPersistedFaceResult
object. This contains the persisted face ID for the person.
Each person can have a maximum of 248 faces associated with it. The more faces you can add, the more likely it is that you will receive a solid identification later. The faces that you add should from slightly different angles.
Training the model
With enough faces associated with the persons, we need to train the person group. This is a task that is required after any change to a person or person group.
We can train a person group when one has been selected, as shown in the following code:
private async void TrainPersonGroup(object obj) { try { await _faceServiceClient.TrainPersonGroupAsync( SelectedPersonGroup.PersonGroupId);
The call to the TrainPersonGroupAsync
function takes a person group ID as a parameter, as shown in the following code. It does not return anything, and it may take a while to execute:
while(true) { TrainingStatustrainingStatus = await _faceServiceClient.GetPersonGroupTrainingStatusAsync (SelectedPersonGroup.PersonGroupId);
We want to ensure that the training completed successfully. To do so, we call the GetPersonGroupTrainingStatusAsync
function inside a while
loop. This call requires a person group ID, and a successful call results in a TrainingStatus
object, as shown in the following code:
if(trainingStatus.Status != Status.Running) { StatusText = $"Person group finished with status: {trainingStatus.Status}"; break; } StatusText = "Training person group..."; await Task.Delay(1000); } }
We check the status and we show the result if it is not running. If the training is still running, we wait for one second and run the check again.
When the training has succeeded, we are ready to identify people.
Additional functionality
There are a few API calls that we have not looked at, which will be mentioned briefly in the following bullet list:
- To update a person group, call the following; this function does not return anything:
UpdatePersonGroupAsync(PERSONGROUPID, NEWNAME, USERDATA)
- To get a person's face, call the following:
GetPersonFaceAsync(PERSONGROUPID, PERSONID, PERSISTEDFACEID)
A successful call returns the persisted face ID and user-provided data.
- To delete a person's face, call the following; this call does not return anything:
DeletePersonFaceAsync(PERSONGROUPID, PERSONID, PERSISTEDFACeID)
- To update a person, call the following; this call does not return anything:
UpdatePersonAsync(PERSONGROUPID, PERSONID, NEWNAME, USERDATA)
- To update a person's face, call the following; this call does not return anything:
UpdatePersonFaceAsync(PERSONGROUID, PERSONID, PERSISTEDFACEID, USERDATA)