In the previous post, we went over how to use the Custom Vision Training and Prediction SDKs to programmatically predict image URLs and image files. In this post, we’re going to use those same SDKs to show how to programmatically upload more training images to the service and train a new model with those new images.

Similar to the previous post, we will need the same three NuGet packages.

And similar to the other post, we’ll get instances of the TrainingApi and get a reference to the herbs project.

var keys = GetApiKeys();

var trainingApi = new TrainingApi { ApiKey = keys.TrainingKey };

var projects = trainingApi.GetProjects();
var herbProject = projects.FirstOrDefault(p => p.Name == "Herbs");

Now that we have all this setup, we can start uploading more training images and train a new version of our model.

You can find the code for this post is up on GitHub.

Upload New Training Images

If you remember back in the first post of this series when we uploaded images we had to give each of them a tag. Programmatically, it’s the same process. To start an upload, we’ll ask the user to give the file path of the image, but we’ll also ask what tag the image should be attributed to.

Console.WriteLine("Input path to image to train model with:");
var imagePath = Console.ReadLine();

Console.WriteLine("What tag would you give this image? Rosemary, cilantro, or basil?");
var imageTag = Console.ReadLine();

Now that we have the inputs from the user, let’s upload our image to the custom vision service. Before we do that, however, we should make sure the tag we got from the user matches what we already have stored. To do that, the input tag from the user would need to be capitalized since that’s how we have our tags in the custom vision service. Then we can call the GetTags method from the TrainingApi class and filter those based on the capitalized tag.

var capitilizedTag = char.ToUpper(imageTag.First()) + imageTag.Substring(1).ToLower();

var tags = trainingApi.GetTags(herbProject.Id);

var matchedTag = tags.Tags.FirstOrDefault(t => t.Name == capitilizedTag);

With the matched tag, let’s now get the image file ready for upload. This does involve a few more steps than we did with predicting on an image file. The one similarity is that we can use the File.OpenRead method again to get the FileStream object.

var imageFile = File.OpenRead(imagePath);

The FileStream can’t be used by itself in this case. We must copy it to a MemoryStream object.

var memoryStream = new MemoryStream();
imageFile.CopyTo(memoryStream);

Now with the image data inside the memoryStream object, we can use some classes on the Training SDK: ImageFileCreateEntry and ImageFileCreateBatch.

var fileCreateEntry = new ImageFileCreateEntry(imageFile.Name, memoryStream.ToArray());
var fileCreateBatch = new ImageFileCreateBatch { Images = new List<ImageFileCreateEntry> { fileCreateEntry }, TagIds = new List<Guid> { matchedTag.Id } };

The ImageFileCreateEntry is what we need to create to house the image name and data. Then we use that to help create the ImageFileCreateBatch. The ImageFileCreateBatch has the Images property where we can create a collection, a List in our case, of the fileCreateEntry object we created just above. There’s also a TagIds property where we can use the matchedTag that we created to fill in as a List of Guid objects and just use the tag ID.

With all of that created we can use the CreateImagesFromFiles method off of the trainingApi object to send the images to the custom vision service.

var result = trainingApi.CreateImagesFromFiles(herbProject.Id, fileCreateBatch);

This returns a response from the custom vision service and depending on how big the image is and how many we sent to the service, we would get a status from the result to check it. We only sent one image so we can get a reference to it.

var resultImage = result.Images.FirstOrDefault();

A nice thing the custom vision service does is that it will tell us if any extra images we upload to it for training are duplicates of images that it already has. We can check this status programmatically with the OKDuplicate status.

switch(resultImage.Status)
{
    case "OKDuplicate":
        Console.WriteLine("Image is already used for training. Please use another to train with");
        Console.ReadLine();
        break;
    default:
        break;
}

With the image uploaded to the service, let’s train a new model.

Training a New Model

To train a new model, just simply call the TrainProject method on the trainingApi object. Each time you train the project you get a new iteration, so when we called the TrainProject method that gave us a reference to the next iteration of the model.

var iteration = trainingApi.TrainProject(herbProject.Id);

However, it may take a while to train the project depending on how many extra images that were sent to the service. Because of that, we need to check the iteration status.

while (iteration.Status != "Completed")
{
    System.Threading.Thread.Sleep(1000);

    iteration = trainingApi.GetIteration(herbProject.Id, iteration.Id);
}

Once training is eventually complete there’s one more step we need to take – tell the custom vision service to set this new iteration of the model as the default one.

iteration.IsDefault = true;
trainingApi.UpdateIteration(herbProject.Id, iteration.Id, iteration);

After that, we’re free to make predictions like in the previous post on our updated model.


In this post, we went over how to programmatically add new training images to the custom vision service to classify images. Then, we showed how to train a new iteration of the model that was trained on the new images to improve the model’s accuracy when making new predictions. We’ll continue with the Custom Vision SDK by showing how to use the Python version of the SDK and use that to create a brand new classification project.