Custom Vision project migration

Have you ever needed to migrate to a new Azure subscription, a new tenant or just to copy a Custom Vision project in the same account? Creating each project, copying all images manually and going through the whole tagging procedure once again sounds really cumbersome. Fortunately, Custom Vision APIs are rich enough and this procedure can be easily done programmatically. There are two provided APIs, Prediction and Training. We will use the second one, which allows us to completely manage a whole project. By using the training API you can create a new project, upload images, create tags, pin tags to image regions, train or publish your model and, finally, quickly test images within your model. All operations are listed in the training API reference.

In addition to the REST API, Custom Vision also comes with SDKs for several programming languages - C#, Java, Javascript, Go and Python. Finding something that will fit your requirements should not be a big deal. To make our life easier, Azure provides a set of code samples that showcase a variety of tools, and accompanying tutorials. Among them you will find a Python script sample which migrates the desirable project between two Cognitive Services resources. As mentioned above, there are two Custom Vision APIs and several SDKs. For the purpose of this blog, we will use the Python SDK, and more specifically the CustomVisionTrainingClient class which contains all operations related to project management, tagging and training.

Before running the script, ensure that there are no duplicate images in your project that you wish to copy, otherwise, the execution of it will fail by setting false to in_batch_successful variable from ImageCreateSummary class. Also, don't try to use the move task in the Azure portal in order to transfer manually the whole Cognitive Services resource between resource groups. This operation won't work with Custom Vision projects attached to them. The moving procedure will finish successfully, but the attached projects will be bugged and unusable.

The Python script sample is divided into three main functions:

  • migrate_project - Creates the destination project based on the given source project.
  • migrate_tags - Migrates all tags from the source project to destination project.
  • migrate_images - Migrates of all tagged and untagged images.

However, it has one limitation. It can only migrate a single project from a source to destination endpoint. Let's extend a bit the main function and make it able to re-create and migrate all the Custom Vision projects to pointed destination account. The CustomVisionTrainingClient class contains one more operation that we can use for this purpose. We will use a method called get_projects which returns a list of objects of type Project. Then we can just iterate over this list, retrieve id of each project and finally call those three main functions as we would do for a single project.

for project in src_trainer.get_projects():"Collecting information for source project:",
    destination_project = migrate_project(src_trainer, dest_trainer, (1)
    tags = migrate_tags(src_trainer, dest_trainer,, (2)
    source_images = migrate_images(src_trainer, dest_trainer,,, tags)
  1. src_trainer - The CustomVisionTrainingClient object of the source projects. Created in
  2. dest_trainer - The CustomVisionTrainingClient object of the destination projects. Created in

The client contains all the methods which correspond to the operations from the Training API.

We should also remove the arg_parser line with the project_id argument as it refers to a single project. It won't be longer necessary for us.

arg_parser.add_argument("-p", "--project", action="store", type=str, help="Source project ID", dest="project_id", default=None)

...and it's done. By doing those small changes, we are now able to migrate all projects between two Custom Vision accounts. The migration script should properly re-create all settings (like domain settings) in all the destination projects. Sadly, newly created projects are untrained and unpublished so our script needs some more customization or manual work. This should not be a big problem as training is a one-click operation in the web app. If you are a stubborn and headstrong person (like me), and you want to fully automate this procedure, you can try something like this:

for project in src_trainer.get_projects():"Collecting information for source project:",
    destination_project = migrate_project(src_trainer, dest_trainer,
    tags = migrate_tags(src_trainer, dest_trainer,,
    source_images = migrate_images(src_trainer, dest_trainer,,, tags)
    dest_trainer.train_project(, "Regular")
    while dest_trainer.get_iterations([0].status == "Training":
        if dest_trainer.get_iterations([0].status == "Completed":
  "Training completed: ",
            iteration_id = dest_trainer.get_iterations([0].id
            dest_trainer.publish_iteration(, iteration_id,, destination_resource_id) (1)
  "Training in progress: ",
  1. destination_resource_id - the id of the Cognitive Services account which Custom Vision is connected with. The value can be found in settings of the destination account. Pay attention to this value as it points the Cognitive Services resource which will be associated with each project. You might have multiple Cognitive Services resources in your subscription (e.g. for each environment). In this case you should try to assign this value differently. The variable should look like this:

This code snippet could be written better and more elaborately but let's not focus too much on details. After successful training of all projects, if needed, set your desired probability and overlap threshold values. They are not automatically updated during migration. As you can also see, we trained the project using "Regular" trainingType.

dest_trainer.train_project(, "Regular")

It's the equivalent of "Quick Training" in the web platform. If you want to perform the advanced training, you should set Advanced as trainingType. When using Advanced, do not forget to set your desired training budget in hours in order to optimize your training costs - reservedBudgetInHours parameter. By default, this value is set to 1 hour.


We successfully went through the migration process of multiple Custom Vision projects. The described approach could be modified and customized according to your needs. In this post I only wanted to give you an idea of how this thing could be done and point out the problems that you may encounter. I hope that you liked it and you will find some of this information useful.