你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

快速入门:使用自定义视觉客户端库创建对象检测项目

适用于 .NET 的自定义视觉客户端库入门。 请按照以下步骤安装包并试用用于生成对象检测模型的示例代码。 你将创建一个项目,添加标签,针对示例图像训练该项目,并使用该项目的预测终结点 URL 以编程方式对其进行测试。 使用此示例作为模板来构建你自己的图像识别应用。

注意

若要在不编写代码的情况下构建和训练对象检测模型,请改为参阅基于浏览器的指南

参考文档 | 库源代码(训练)(预测)| 包 (NuGet)(训练)(预测) | 示例

先决条件

创建环境变量

在此示例中,你将凭据写入运行应用程序的本地计算机上的环境变量。

转到 Azure 门户。 如果在“先决条件”部分中创建的自定义视觉资源已成功部署,请选择“后续步骤”下的“转到资源”按钮。 在资源的“密钥和终结点”页的“资源管理”下可找到密钥和终结点。 你需要获取训练资源和预测资源这两者的密钥,以及 API 终结点。

可以在 Azure 门户中预测资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID。

提示

还可以使用 https://www.customvision.ai 来获取这些值。 登录后,请选择右上角的“设置”图标。 在“设置”页上,可以查看所有密钥、资源 ID 和终结点。

若要设置环境变量,请打开控制台窗口,按照操作系统和开发环境的说明进行操作。

  • 若要设置 VISION_TRAINING KEY 环境变量,请将 <your-training-key> 替换为训练资源的其中一个密钥。
  • 若要设置 VISION_TRAINING_ENDPOINT 环境变量,请将 <your-training-endpoint> 替换为训练资源的终结点。
  • 若要设置 VISION_PREDICTION_KEY 环境变量,请将 <your-prediction-key> 替换为预测资源的其中一个密钥。
  • 若要设置 VISION_PREDICTION_ENDPOINT 环境变量,请将 <your-prediction-endpoint> 替换为预测资源的终结点。
  • 若要设置 VISION_PREDICTION_RESOURCE_ID 环境变量,请将 <your-resource-id> 替换为预测资源的资源 ID。

重要

我们建议使用 Azure 资源的托管标识进行 Microsoft Entra ID 身份验证,以避免将凭据随云中运行的应用程序一起存储。

请谨慎使用 API 密钥。 请不要直接在代码中包含 API 密钥,并且切勿公开发布该密钥。 如果使用 API 密钥,请将其安全地存储在 Azure Key Vault 中,定期轮换密钥,并使用基于角色的访问控制和网络访问限制来限制对 Azure Key Vault 的访问。 若要详细了解如何在应用中安全地使用 API 密钥,请参阅 API 密钥与 Azure Key Vault

有关 Azure AI 服务安全性的详细信息,请参阅对 Azure AI 服务的请求进行身份验证

setx VISION_TRAINING_KEY <your-training-key>
setx VISION_TRAINING_ENDPOINT <your-training-endpoint>
setx VISION_PREDICTION_KEY <your-prediction-key>
setx VISION_PREDICTION_ENDPOINT <your-prediction-endpoint>
setx VISION_PREDICTION_RESOURCE_ID <your-resource-id>

添加环境变量后,可能需要重启任何正在运行的、读取环境变量的程序(包括控制台窗口)。

设置

新建 C# 应用程序

使用 Visual Studio 创建新的 .NET Core 应用程序。

安装客户端库

创建新项目后,右键单击“解决方案资源管理器”中的项目解决方案,然后选择“管理 NuGet 包”,以安装客户端库 。 在打开的包管理器中,选择“浏览”,选中“包括预发行版”并搜索 。 选择最新版本,然后选择“安装”。

提示

想要立即查看整个快速入门代码文件? 可以在 GitHub 上找到它,其中包含此快速入门中的代码示例。

从项目目录中,打开 Program.cs 文件,并添加以下 指令:

using Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction;
using Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training;
using Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

在应用程序的“Main”方法中,创建从环境变量中检索资源的密钥和终结点的变量。 你还将声明一些基本对象以供稍后使用。

    string trainingEndpoint = Environment.GetEnvironmentVariable("VISION_TRAINING_ENDPOINT");

    string trainingKey = Environment.GetEnvironmentVariable("VISION_TRAINING_KEY");
    string predictionEndpoint = Environment.GetEnvironmentVariable("VISION_PREDICTION_ENDPOINT");
    string predictionKey = Environment.GetEnvironmentVariable("VISION_PREDICTION_KEY");

    private static Iteration iteration;
    private static string publishedModelName = "CustomODModel";

在应用程序的“Main”方法中,添加对本快速入门中使用的方法的调用。 稍后将实现这些操作。

CustomVisionTrainingClient trainingApi = AuthenticateTraining(trainingEndpoint, trainingKey);
CustomVisionPredictionClient predictionApi = AuthenticatePrediction(predictionEndpoint, predictionKey);

Project project = CreateProject(trainingApi);
AddTags(trainingApi, project);
UploadImages(trainingApi, project);
TrainProject(trainingApi, project);
PublishIteration(trainingApi, project);
TestIteration(predictionApi, project);

验证客户端

在新方法中,使用终结点和密钥来实例化训练和预测客户端。

private CustomVisionTrainingClient AuthenticateTraining(string endpoint, string trainingKey, string predictionKey)
{
    // Create the Api, passing in the training key
    CustomVisionTrainingClient trainingApi = new CustomVisionTrainingClient(new Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training.ApiKeyServiceClientCredentials(trainingKey))
    {
        Endpoint = endpoint
    };
    return trainingApi;
}
private CustomVisionPredictionClient AuthenticatePrediction(string endpoint, string predictionKey)
{
    // Create a prediction endpoint, passing in the obtained prediction key
    CustomVisionPredictionClient predictionApi = new CustomVisionPredictionClient(new Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction.ApiKeyServiceClientCredentials(predictionKey))
    {
        Endpoint = endpoint
    };
    return predictionApi;
}

创建新的自定义视觉项目

下一个方法将创建对象检测项目。 创建的项目将显示在自定义视觉网站上。 请查看 CreateProject 方法,以在创建项目时指定其他选项(在生成检测器 Web 门户指南中进行了说明)。

private Project CreateProject(CustomVisionTrainingClient trainingApi)
{
    // Find the object detection ___domain
    var domains = trainingApi.GetDomains();
    var objDetectionDomain = domains.FirstOrDefault(d => d.Type == "ObjectDetection");

    // Create a new project
    Console.WriteLine("Creating new project:");
    project = trainingApi.CreateProject("My New Project", null, objDetectionDomain.Id);

    return project;
}

将标记添加到项目中

此方法定义要针对其训练模型的标签。

private void AddTags(CustomVisionTrainingClient trainingApi, Project project)
{
    // Make two tags in the new project
    var forkTag = trainingApi.CreateTag(project.Id, "fork");
    var scissorsTag = trainingApi.CreateTag(project.Id, "scissors");
}

上传和标记图像

首先,下载此项目的示例图像。 将示例图像文件夹的内容保存到本地设备。

在对象检测项目中标记图像时,需要使用标准化坐标指定每个标记对象的区域。 以下代码将每个示例图像与其标记的区域相关联。

private void UploadImages(CustomVisionTrainingClient trainingApi, Project project)
{
    Dictionary<string, double[]> fileToRegionMap = new Dictionary<string, double[]>()
    {
        // FileName, Left, Top, Width, Height
        {"scissors_1", new double[] { 0.4007353, 0.194068655, 0.259803921, 0.6617647 } },
        {"scissors_2", new double[] { 0.426470578, 0.185898721, 0.172794119, 0.5539216 } },
        {"scissors_3", new double[] { 0.289215684, 0.259428144, 0.403186262, 0.421568632 } },
        {"scissors_4", new double[] { 0.343137264, 0.105833367, 0.332107842, 0.8055556 } },
        {"scissors_5", new double[] { 0.3125, 0.09766343, 0.435049027, 0.71405226 } },
        {"scissors_6", new double[] { 0.379901975, 0.24308826, 0.32107842, 0.5718954 } },
        {"scissors_7", new double[] { 0.341911763, 0.20714055, 0.3137255, 0.6356209 } },
        {"scissors_8", new double[] { 0.231617644, 0.08459154, 0.504901946, 0.8480392 } },
        {"scissors_9", new double[] { 0.170343131, 0.332957536, 0.767156839, 0.403594762 } },
        {"scissors_10", new double[] { 0.204656869, 0.120539248, 0.5245098, 0.743464053 } },
        {"scissors_11", new double[] { 0.05514706, 0.159754932, 0.799019635, 0.730392158 } },
        {"scissors_12", new double[] { 0.265931368, 0.169558853, 0.5061275, 0.606209159 } },
        {"scissors_13", new double[] { 0.241421565, 0.184264734, 0.448529422, 0.6830065 } },
        {"scissors_14", new double[] { 0.05759804, 0.05027781, 0.75, 0.882352948 } },
        {"scissors_15", new double[] { 0.191176474, 0.169558853, 0.6936275, 0.6748366 } },
        {"scissors_16", new double[] { 0.1004902, 0.279036, 0.6911765, 0.477124184 } },
        {"scissors_17", new double[] { 0.2720588, 0.131977156, 0.4987745, 0.6911765 } },
        {"scissors_18", new double[] { 0.180147052, 0.112369314, 0.6262255, 0.6666667 } },
        {"scissors_19", new double[] { 0.333333343, 0.0274019931, 0.443627447, 0.852941155 } },
        {"scissors_20", new double[] { 0.158088237, 0.04047389, 0.6691176, 0.843137264 } },
        {"fork_1", new double[] { 0.145833328, 0.3509314, 0.5894608, 0.238562092 } },
        {"fork_2", new double[] { 0.294117659, 0.216944471, 0.534313738, 0.5980392 } },
        {"fork_3", new double[] { 0.09191177, 0.0682516545, 0.757352948, 0.6143791 } },
        {"fork_4", new double[] { 0.254901975, 0.185898721, 0.5232843, 0.594771266 } },
        {"fork_5", new double[] { 0.2365196, 0.128709182, 0.5845588, 0.71405226 } },
        {"fork_6", new double[] { 0.115196079, 0.133611143, 0.676470637, 0.6993464 } },
        {"fork_7", new double[] { 0.164215669, 0.31008172, 0.767156839, 0.410130739 } },
        {"fork_8", new double[] { 0.118872553, 0.318251669, 0.817401946, 0.225490168 } },
        {"fork_9", new double[] { 0.18259804, 0.2136765, 0.6335784, 0.643790841 } },
        {"fork_10", new double[] { 0.05269608, 0.282303959, 0.8088235, 0.452614367 } },
        {"fork_11", new double[] { 0.05759804, 0.0894935, 0.9007353, 0.3251634 } },
        {"fork_12", new double[] { 0.3345588, 0.07315363, 0.375, 0.9150327 } },
        {"fork_13", new double[] { 0.269607842, 0.194068655, 0.4093137, 0.6732026 } },
        {"fork_14", new double[] { 0.143382356, 0.218578458, 0.7977941, 0.295751631 } },
        {"fork_15", new double[] { 0.19240196, 0.0633497, 0.5710784, 0.8398692 } },
        {"fork_16", new double[] { 0.140931368, 0.480016381, 0.6838235, 0.240196079 } },
        {"fork_17", new double[] { 0.305147052, 0.2512582, 0.4791667, 0.5408496 } },
        {"fork_18", new double[] { 0.234068632, 0.445702642, 0.6127451, 0.344771236 } },
        {"fork_19", new double[] { 0.219362751, 0.141781077, 0.5919118, 0.6683006 } },
        {"fork_20", new double[] { 0.180147052, 0.239820287, 0.6887255, 0.235294119 } }
    };

注意

对于你自己的项目,如果没有用于标记区域坐标的单击并拖动实用工具,则可以使用自定义视觉网站的 Web UI。 在此示例中,已提供坐标。

然后,使用此关联映射上传每个示例图像及其区域坐标。 最多可以在单个批次中上传 64 个图像。 可能需要更改 imagePath 值以指向正确的文件夹位置。

    // Add all images for fork
    var imagePath = Path.Combine("Images", "fork");
    var imageFileEntries = new List<ImageFileCreateEntry>();
    foreach (var fileName in Directory.EnumerateFiles(imagePath))
    {
        var region = fileToRegionMap[Path.GetFileNameWithoutExtension(fileName)];
        imageFileEntries.Add(new ImageFileCreateEntry(fileName, File.ReadAllBytes(fileName), null, new List<Region>(new Region[] { new Region(forkTag.Id, region[0], region[1], region[2], region[3]) })));
    }
    trainingApi.CreateImagesFromFiles(project.Id, new ImageFileCreateBatch(imageFileEntries));

    // Add all images for scissors
    imagePath = Path.Combine("Images", "scissors");
    imageFileEntries = new List<ImageFileCreateEntry>();
    foreach (var fileName in Directory.EnumerateFiles(imagePath))
    {
        var region = fileToRegionMap[Path.GetFileNameWithoutExtension(fileName)];
        imageFileEntries.Add(new ImageFileCreateEntry(fileName, File.ReadAllBytes(fileName), null, new List<Region>(new Region[] { new Region(scissorsTag.Id, region[0], region[1], region[2], region[3]) })));
    }
    trainingApi.CreateImagesFromFiles(project.Id, new ImageFileCreateBatch(imageFileEntries));
}

至此,你已上传了所有示例图像,并使用关联的像素矩形标记了每个示例图像(叉子剪刀)。

定型项目

此方法将在项目中创建第一个训练迭代。 它将查询服务,直到训练完成。

private void TrainProject(CustomVisionTrainingClient trainingApi, Project project)
{

    // Now there are images with tags start training the project
    Console.WriteLine("\tTraining");
    iteration = trainingApi.TrainProject(project.Id);

    // The returned iteration will be in progress, and can be queried periodically to see when it has completed
    while (iteration.Status == "Training")
    {
        Thread.Sleep(1000);

        // Re-query the iteration to get its updated status
        iteration = trainingApi.GetIteration(project.Id, iteration.Id);
    }
}

提示

使用选定标记进行训练

可以选择只对应用的标记的子集进行训练。 如果你还没有应用足够多的特定标记,但是你确实有足够多的其他标记,则可能需要这样做。 在 TrainProject 调用中,使用 trainingParameters 参数。 构造一个 TrainingParameters,并将其 SelectedTags 属性设置为要使用的标记的 ID 列表。 模型将训练成只识别该列表中的标记。

发布当前迭代

此方法使模型的当前迭代可用于查询。 可以将模型名称用作发送预测请求的引用。 需要为 predictionResourceId 输入自己的值。 可以在 Azure 门户中资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID 。

private void PublishIteration(CustomVisionTrainingClient trainingApi, Project project)
{

    // The iteration is now trained. Publish it to the prediction end point.
    var predictionResourceId = Environment.GetEnvironmentVariable("VISION_PREDICTION_RESOURCE_ID");
    trainingApi.PublishIteration(project.Id, iteration.Id, publishedModelName, predictionResourceId);
    Console.WriteLine("Done!\n");
}

测试预测终结点

此方法用于加载测试图像、查询模型终结点,以及将预测数据输出到控制台。

private void TestIteration(CustomVisionPredictionClient predictionApi, Project project)
{

    // Make a prediction against the new project
    Console.WriteLine("Making a prediction:");
    var imageFile = Path.Combine("Images", "test", "test_image.jpg");
    using (var stream = File.OpenRead(imageFile))
    {
        var result = predictionApi.DetectImage(project.Id, publishedModelName, stream);

        // Loop over each prediction and write out the results
        foreach (var c in result.Predictions)
        {
            Console.WriteLine($"\t{c.TagName}: {c.Probability:P1} [ {c.BoundingBox.Left}, {c.BoundingBox.Top}, {c.BoundingBox.Width}, {c.BoundingBox.Height} ]");
        }
    }
    Console.ReadKey();
}

运行应用程序

单击 IDE 窗口顶部的“调试”按钮,运行应用程序。

应用程序运行时,会打开一个控制台窗口并写入以下输出:

Creating new project:
        Training
Done!

Making a prediction:
        fork: 98.2% [ 0.111609578, 0.184719115, 0.6607002, 0.6637112 ]
        scissors: 1.2% [ 0.112389535, 0.119195729, 0.658031344, 0.7023591 ]

然后,可以验证测试图像(在 Images/Test/ 中找到)是否已正确标记,并验证检测区域是否正确。 此时,可以按任意键退出应用程序。

清理资源

如果你希望实现自己的物体检测项目(或改为尝试图像分类项目),可能希望从此示例中删除叉子/剪刀检测项目。 免费订阅允许创建两个自定义视觉项目。

自定义视觉网站上,导航到“项目”,然后在“我的新项目”下选择垃圾桶。

标有“我的新项目”的面板的屏幕截图,其中包含垃圾桶图标。

后续步骤

现在,你已在代码中完成了对象检测过程的每一步。 此示例执行单次训练迭代,但通常需要多次训练和测试模型,以使其更准确。 以下指南涉及图像分类,但其原理与对象检测类似。

本指南提供说明和示例代码,以帮助你开始使用适用于 Go 的自定义视觉客户端库来构建对象检测模型。 你将创建一个项目,添加标记,训练该项目,并使用该项目的预测终结点 URL 以编程方式对其进行测试。 使用此示例作为模板来构建你自己的图像识别应用。

注意

若要在不编写代码的情况下构建和训练对象检测模型,请改为参阅基于浏览器的指南

参考文档(训练)(预测)

先决条件

创建环境变量

在此示例中,你将凭据写入运行应用程序的本地计算机上的环境变量。

转到 Azure 门户。 如果在“先决条件”部分中创建的自定义视觉资源已成功部署,请选择“后续步骤”下的“转到资源”按钮。 在资源的“密钥和终结点”页的“资源管理”下可找到密钥和终结点。 你需要获取训练资源和预测资源这两者的密钥,以及 API 终结点。

可以在 Azure 门户中预测资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID。

提示

还可以使用 https://www.customvision.ai 来获取这些值。 登录后,请选择右上角的“设置”图标。 在“设置”页上,可以查看所有密钥、资源 ID 和终结点。

若要设置环境变量,请打开控制台窗口,按照操作系统和开发环境的说明进行操作。

  • 若要设置 VISION_TRAINING KEY 环境变量,请将 <your-training-key> 替换为训练资源的其中一个密钥。
  • 若要设置 VISION_TRAINING_ENDPOINT 环境变量,请将 <your-training-endpoint> 替换为训练资源的终结点。
  • 若要设置 VISION_PREDICTION_KEY 环境变量,请将 <your-prediction-key> 替换为预测资源的其中一个密钥。
  • 若要设置 VISION_PREDICTION_ENDPOINT 环境变量,请将 <your-prediction-endpoint> 替换为预测资源的终结点。
  • 若要设置 VISION_PREDICTION_RESOURCE_ID 环境变量,请将 <your-resource-id> 替换为预测资源的资源 ID。

重要

我们建议使用 Azure 资源的托管标识进行 Microsoft Entra ID 身份验证,以避免将凭据随云中运行的应用程序一起存储。

请谨慎使用 API 密钥。 请不要直接在代码中包含 API 密钥,并且切勿公开发布该密钥。 如果使用 API 密钥,请将其安全地存储在 Azure Key Vault 中,定期轮换密钥,并使用基于角色的访问控制和网络访问限制来限制对 Azure Key Vault 的访问。 若要详细了解如何在应用中安全地使用 API 密钥,请参阅 API 密钥与 Azure Key Vault

有关 Azure AI 服务安全性的详细信息,请参阅对 Azure AI 服务的请求进行身份验证

setx VISION_TRAINING_KEY <your-training-key>
setx VISION_TRAINING_ENDPOINT <your-training-endpoint>
setx VISION_PREDICTION_KEY <your-prediction-key>
setx VISION_PREDICTION_ENDPOINT <your-prediction-endpoint>
setx VISION_PREDICTION_RESOURCE_ID <your-resource-id>

添加环境变量后,可能需要重启任何正在运行的、读取环境变量的程序(包括控制台窗口)。

设置

安装自定义视觉客户端库

若要使用适用于 Go 的自定义视觉来编写图像分析应用,需要自定义视觉服务客户端库。 在 PowerShell 中运行以下命令:

go get -u github.com/Azure/azure-sdk-for-go/...

或者,如果使用 dep,则在存储库中运行:

dep ensure -add github.com/Azure/azure-sdk-for-go

获取示例图像

此示例使用 GitHub 上 Azure AI 服务 Python SDK 示例存储库中的图像。 将此存储库克隆或下载到开发环境。 请记住它的文件夹位置,以便后面的步骤使用。

创建自定义视觉项目

在首选的项目目录中创建名为 sample.go 的新文件,并在首选的代码编辑器中将其打开。

将以下代码添加到脚本中以创建新的自定义视觉服务项目。

请查看 CreateProject 方法,以在创建项目时指定其他选项(在生成检测器 Web 门户指南中进行了说明)。

import(
    "context"
    "bytes"
    "fmt"
    "io/ioutil"
    "path"
    "log"
    "time"
    "github.com/Azure/azure-sdk-for-go/services/cognitiveservices/v3.0/customvision/training"
    "github.com/Azure/azure-sdk-for-go/services/cognitiveservices/v3.0/customvision/prediction"
)

// retrieve environment variables:
var (
    training_key string = os.Getenv("VISION_TRAINING_KEY")
    prediction_key string = os.Getenv("VISION_PREDICTION_KEY")
    prediction_resource_id = os.Getenv("VISION_PREDICTION_RESOURCE_ID")
    endpoint string = os.Getenv("VISION_ENDPOINT")
   
    project_name string = "Go Sample OD Project"
    iteration_publish_name = "detectModel"
    sampleDataDirectory = "<path to sample images>"
)

func main() {
    fmt.Println("Creating project...")

    ctx = context.Background()

    trainer := training.New(training_key, endpoint)

    var objectDetectDomain training.Domain
    domains, _ := trainer.GetDomains(ctx)

    for _, ___domain := range *domains.Value {
        fmt.Println(___domain, ___domain.Type)
        if ___domain.Type == "ObjectDetection" && *___domain.Name == "General" {
            objectDetectDomain = ___domain
            break
        }
    }
    fmt.Println("Creating project...")
    project, _ := trainer.CreateProject(ctx, project_name, "", objectDetectDomain.ID, "")

在项目中创建标记

若要在项目中创建分类标记,请将以下代码添加到 sample.go 末尾:

# Make two tags in the new project
forkTag, _ := trainer.CreateTag(ctx, *project.ID, "fork", "A fork", string(training.Regular))
scissorsTag, _ := trainer.CreateTag(ctx, *project.ID, "scissors", "Pair of scissors", string(training.Regular))

上传和标记图像

在对象检测项目中标记图像时,需要使用标准化坐标指定每个标记对象的区域。

注意

如果没有用于标记区域坐标的单击并拖动实用工具,则可以使用 Customvision.ai 的 Web UI。 在此示例中,已提供坐标。

若要将图像、标记和区域添加到项目,请在创建标记后插入以下代码。 请注意,在本教程中,区域已进行内联硬编码。 区域在标准化坐标中指定边界框,坐标按以下顺序给定:左部、顶部、宽度、高度。

forkImageRegions := map[string][4]float64{
    "fork_1.jpg": [4]float64{ 0.145833328, 0.3509314, 0.5894608, 0.238562092 },
    "fork_2.jpg": [4]float64{ 0.294117659, 0.216944471, 0.534313738, 0.5980392 },
    "fork_3.jpg": [4]float64{ 0.09191177, 0.0682516545, 0.757352948, 0.6143791 },
    "fork_4.jpg": [4]float64{ 0.254901975, 0.185898721, 0.5232843, 0.594771266 },
    "fork_5.jpg": [4]float64{ 0.2365196, 0.128709182, 0.5845588, 0.71405226 },
    "fork_6.jpg": [4]float64{ 0.115196079, 0.133611143, 0.676470637, 0.6993464 },
    "fork_7.jpg": [4]float64{ 0.164215669, 0.31008172, 0.767156839, 0.410130739 },
    "fork_8.jpg": [4]float64{ 0.118872553, 0.318251669, 0.817401946, 0.225490168 },
    "fork_9.jpg": [4]float64{ 0.18259804, 0.2136765, 0.6335784, 0.643790841 },
    "fork_10.jpg": [4]float64{ 0.05269608, 0.282303959, 0.8088235, 0.452614367 },
    "fork_11.jpg": [4]float64{ 0.05759804, 0.0894935, 0.9007353, 0.3251634 },
    "fork_12.jpg": [4]float64{ 0.3345588, 0.07315363, 0.375, 0.9150327 },
    "fork_13.jpg": [4]float64{ 0.269607842, 0.194068655, 0.4093137, 0.6732026 },
    "fork_14.jpg": [4]float64{ 0.143382356, 0.218578458, 0.7977941, 0.295751631 },
    "fork_15.jpg": [4]float64{ 0.19240196, 0.0633497, 0.5710784, 0.8398692 },
    "fork_16.jpg": [4]float64{ 0.140931368, 0.480016381, 0.6838235, 0.240196079 },
    "fork_17.jpg": [4]float64{ 0.305147052, 0.2512582, 0.4791667, 0.5408496 },
    "fork_18.jpg": [4]float64{ 0.234068632, 0.445702642, 0.6127451, 0.344771236 },
    "fork_19.jpg": [4]float64{ 0.219362751, 0.141781077, 0.5919118, 0.6683006 },
    "fork_20.jpg": [4]float64{ 0.180147052, 0.239820287, 0.6887255, 0.235294119 },
}

scissorsImageRegions := map[string][4]float64{
    "scissors_1.jpg": [4]float64{ 0.4007353, 0.194068655, 0.259803921, 0.6617647 },
    "scissors_2.jpg": [4]float64{ 0.426470578, 0.185898721, 0.172794119, 0.5539216 },
    "scissors_3.jpg": [4]float64{ 0.289215684, 0.259428144, 0.403186262, 0.421568632 },
    "scissors_4.jpg": [4]float64{ 0.343137264, 0.105833367, 0.332107842, 0.8055556 },
    "scissors_5.jpg": [4]float64{ 0.3125, 0.09766343, 0.435049027, 0.71405226 },
    "scissors_6.jpg": [4]float64{ 0.379901975, 0.24308826, 0.32107842, 0.5718954 },
    "scissors_7.jpg": [4]float64{ 0.341911763, 0.20714055, 0.3137255, 0.6356209 },
    "scissors_8.jpg": [4]float64{ 0.231617644, 0.08459154, 0.504901946, 0.8480392 },
    "scissors_9.jpg": [4]float64{ 0.170343131, 0.332957536, 0.767156839, 0.403594762 },
    "scissors_10.jpg": [4]float64{ 0.204656869, 0.120539248, 0.5245098, 0.743464053 },
    "scissors_11.jpg": [4]float64{ 0.05514706, 0.159754932, 0.799019635, 0.730392158 },
    "scissors_12.jpg": [4]float64{ 0.265931368, 0.169558853, 0.5061275, 0.606209159 },
    "scissors_13.jpg": [4]float64{ 0.241421565, 0.184264734, 0.448529422, 0.6830065 },
    "scissors_14.jpg": [4]float64{ 0.05759804, 0.05027781, 0.75, 0.882352948 },
    "scissors_15.jpg": [4]float64{ 0.191176474, 0.169558853, 0.6936275, 0.6748366 },
    "scissors_16.jpg": [4]float64{ 0.1004902, 0.279036, 0.6911765, 0.477124184 },
    "scissors_17.jpg": [4]float64{ 0.2720588, 0.131977156, 0.4987745, 0.6911765 },
    "scissors_18.jpg": [4]float64{ 0.180147052, 0.112369314, 0.6262255, 0.6666667 },
    "scissors_19.jpg": [4]float64{ 0.333333343, 0.0274019931, 0.443627447, 0.852941155 },
    "scissors_20.jpg": [4]float64{ 0.158088237, 0.04047389, 0.6691176, 0.843137264 },
}

然后,使用此关联映射上传每个样本图像及其区域坐标(最多可以在单个批次中上传 64 个图像)。 添加以下代码。

注意

需根据此前下载 Azure AI 服务 Go SDK 示例项目的位置更改图像的路径。

// Go through the data table above and create the images
fmt.Println("Adding images...")
var fork_images []training.ImageFileCreateEntry
for file, region := range forkImageRegions {
    imageFile, _ := ioutil.ReadFile(path.Join(sampleDataDirectory, "fork", file))

    regiontest := forkImageRegions[file]
    imageRegion := training.Region{
        TagID:  forkTag.ID,
        Left:   &regiontest[0],
        Top:    &regiontest[1],
        Width:  &regiontest[2],
        Height: &regiontest[3],
    }
    var fileName string = file

    fork_images = append(fork_images, training.ImageFileCreateEntry{
        Name:     &fileName,
        Contents: &imageFile,
        Regions:  &[]training.Region{imageRegion}
    })
}
    
fork_batch, _ := trainer.CreateImagesFromFiles(ctx, *project.ID, training.ImageFileCreateBatch{ 
    Images: &fork_images,
})

if (!*fork_batch.IsBatchSuccessful) {
    fmt.Println("Batch upload failed.")
}

var scissor_images []training.ImageFileCreateEntry
for file, region := range scissorsImageRegions {
    imageFile, _ := ioutil.ReadFile(path.Join(sampleDataDirectory, "scissors", file))

    imageRegion := training.Region { 
        TagID:scissorsTag.ID,
        Left:&region[0],
        Top:&region[1],
        Width:&region[2],
        Height:&region[3],
    }

    scissor_images = append(scissor_images, training.ImageFileCreateEntry {
        Name: &file,
        Contents: &imageFile,
        Regions: &[]training.Region{ imageRegion },
    })
}
    
scissor_batch, _ := trainer.CreateImagesFromFiles(ctx, *project.ID, training.ImageFileCreateBatch{ 
    Images: &scissor_images,
})
    
if (!*scissor_batch.IsBatchSuccessful) {
    fmt.Println("Batch upload failed.")
}     

训练并发布项目

此代码创建预测模型的第一个迭代,然后将该迭代发布到预测终结点。 为发布的迭代起的名称可用于发送预测请求。 在发布迭代之前,迭代在预测终结点中不可用。

iteration, _ := trainer.TrainProject(ctx, *project.ID)
fmt.Println("Training status:", *iteration.Status)
for {
    if *iteration.Status != "Training" {
        break
    }
    time.Sleep(5 * time.Second)
    iteration, _ = trainer.GetIteration(ctx, *project.ID, *iteration.ID)
    fmt.Println("Training status:", *iteration.Status)
}

trainer.PublishIteration(ctx, *project.ID, *iteration.ID, iteration_publish_name, prediction_resource_id))

使用预测终结点

若要将图像发送到预测终结点并检索预测,请将以下代码添加到文件末尾:

    fmt.Println("Predicting...")
    predictor := prediction.New(prediction_key, endpoint)

    testImageData, _ := ioutil.ReadFile(path.Join(sampleDataDirectory, "Test", "test_od_image.jpg"))
    results, _ := predictor.DetectImage(ctx, *project.ID, iteration_publish_name, ioutil.NopCloser(bytes.NewReader(testImageData)), "")

    for _, prediction := range *results.Predictions    {
        boundingBox := *prediction.BoundingBox

        fmt.Printf("\t%s: %.2f%% (%.2f, %.2f, %.2f, %.2f)", 
            *prediction.TagName,
            *prediction.Probability * 100,
            *boundingBox.Left,
            *boundingBox.Top,
            *boundingBox.Width,
            *boundingBox.Height)
        fmt.Println("")
    }
}

运行应用程序

运行 sample.go

go run sample.go

应用程序的输出应显示在控制台中。 然后,可以验证测试图像(在 samples/vision/images/Test 中找到)是否已正确标记,并验证检测区域是否正确。

清理资源

如果你希望实现自己的物体检测项目(或改为尝试图像分类项目),可能希望从此示例中删除叉子/剪刀检测项目。 免费订阅允许创建两个自定义视觉项目。

自定义视觉网站上,导航到“项目”,然后在“我的新项目”下选择垃圾桶。

标有“我的新项目”的面板的屏幕截图,其中包含垃圾桶图标。

后续步骤

现在,你已在代码中完成了对象检测过程的每一步。 此示例执行单次训练迭代,但通常需要多次训练和测试模型,以使其更准确。 以下指南涉及图像分类,但其原理与对象检测类似。

开始使用适用于 Java 的自定义视觉客户端库来构建对象检测模型。 请按照以下步骤安装程序包并试用基本任务的示例代码。 使用此示例作为模板来构建你自己的图像识别应用。

注意

若要在不编写代码的情况下构建和训练对象检测模型,请改为参阅基于浏览器的指南

参考文档 | 库源代码(训练)(预测)| 项目 (Maven)(训练)(预测) | 示例

先决条件

创建环境变量

在此示例中,你将凭据写入运行应用程序的本地计算机上的环境变量。

转到 Azure 门户。 如果在“先决条件”部分中创建的自定义视觉资源已成功部署,请选择“后续步骤”下的“转到资源”按钮。 在资源的“密钥和终结点”页的“资源管理”下可找到密钥和终结点。 你需要获取训练资源和预测资源这两者的密钥,以及 API 终结点。

可以在 Azure 门户中预测资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID。

提示

还可以使用 https://www.customvision.ai 来获取这些值。 登录后,请选择右上角的“设置”图标。 在“设置”页上,可以查看所有密钥、资源 ID 和终结点。

若要设置环境变量,请打开控制台窗口,按照操作系统和开发环境的说明进行操作。

  • 若要设置 VISION_TRAINING KEY 环境变量,请将 <your-training-key> 替换为训练资源的其中一个密钥。
  • 若要设置 VISION_TRAINING_ENDPOINT 环境变量,请将 <your-training-endpoint> 替换为训练资源的终结点。
  • 若要设置 VISION_PREDICTION_KEY 环境变量,请将 <your-prediction-key> 替换为预测资源的其中一个密钥。
  • 若要设置 VISION_PREDICTION_ENDPOINT 环境变量,请将 <your-prediction-endpoint> 替换为预测资源的终结点。
  • 若要设置 VISION_PREDICTION_RESOURCE_ID 环境变量,请将 <your-resource-id> 替换为预测资源的资源 ID。

重要

我们建议使用 Azure 资源的托管标识进行 Microsoft Entra ID 身份验证,以避免将凭据随云中运行的应用程序一起存储。

请谨慎使用 API 密钥。 请不要直接在代码中包含 API 密钥,并且切勿公开发布该密钥。 如果使用 API 密钥,请将其安全地存储在 Azure Key Vault 中,定期轮换密钥,并使用基于角色的访问控制和网络访问限制来限制对 Azure Key Vault 的访问。 若要详细了解如何在应用中安全地使用 API 密钥,请参阅 API 密钥与 Azure Key Vault

有关 Azure AI 服务安全性的详细信息,请参阅对 Azure AI 服务的请求进行身份验证

setx VISION_TRAINING_KEY <your-training-key>
setx VISION_TRAINING_ENDPOINT <your-training-endpoint>
setx VISION_PREDICTION_KEY <your-prediction-key>
setx VISION_PREDICTION_ENDPOINT <your-prediction-endpoint>
setx VISION_PREDICTION_RESOURCE_ID <your-resource-id>

添加环境变量后,可能需要重启任何正在运行的、读取环境变量的程序(包括控制台窗口)。

设置

创建新的 Gradle 项目

在控制台窗口(例如 cmd、PowerShell 或 Bash)中,为应用创建一个新目录并导航到该目录。

mkdir myapp && cd myapp

从工作目录运行 gradle init 命令。 此命令将创建 Gradle 的基本生成文件,包括 build.gradle.kts,在运行时将使用该文件创建并配置应用程序。

gradle init --type basic

当提示你选择一个 DSL 时,选择 Kotlin

安装客户端库

找到 build.gradle.kts,并使用喜好的 IDE 或文本编辑器将其打开。 然后在该文件中复制以下生成配置。 此配置将项目定义为一个 Java 应用程序,其入口点为 CustomVisionQuickstart 类。 它将导入自定义视觉库。

plugins {
    java
    application
}
application { 
    mainClassName = "CustomVisionQuickstart"
}
repositories {
    mavenCentral()
}
dependencies {
    compile(group = "com.azure", name = "azure-cognitiveservices-customvision-training", version = "1.1.0-preview.2")
    compile(group = "com.azure", name = "azure-cognitiveservices-customvision-prediction", version = "1.1.0-preview.2")
}

创建 Java 文件

从工作目录运行以下命令,以创建项目源文件夹:

mkdir -p src/main/java

导航到新文件夹,并创建名为 CustomVisionQuickstart.java 的文件。 在喜好的编辑器或 IDE 中打开该文件并添加以下 import 语句:

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

import com.google.common.io.ByteStreams;

import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.Classifier;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.Domain;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.DomainType;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.ImageFileCreateBatch;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.ImageFileCreateEntry;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.Iteration;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.Project;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.Region;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.TrainProjectOptionalParameter;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.CustomVisionTrainingClient;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.Trainings;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.CustomVisionTrainingManager;
import com.microsoft.azure.cognitiveservices.vision.customvision.prediction.models.ImagePrediction;
import com.microsoft.azure.cognitiveservices.vision.customvision.prediction.models.Prediction;
import com.microsoft.azure.cognitiveservices.vision.customvision.prediction.CustomVisionPredictionClient;
import com.microsoft.azure.cognitiveservices.vision.customvision.prediction.CustomVisionPredictionManager;
import com.microsoft.azure.cognitiveservices.vision.customvision.training.models.Tag;

提示

想要立即查看整个快速入门代码文件? 可以在 GitHub 上找到它,其中包含此快速入门中的代码示例。

在应用程序的 CustomVisionQuickstart 类中,创建从环境变量中检索资源的密钥和终结点的变量。

// retrieve environment variables
final static String trainingApiKey = System.getenv("VISION_TRAINING_KEY");
final static String trainingEndpoint = System.getenv("VISION_TRAINING_ENDPOINT");
final static String predictionApiKey = System.getenv("VISION_PREDICTION_KEY");
final static String predictionEndpoint = System.getenv("VISION_PREDICTION_ENDPOINT");
final static String predictionResourceId = System.getenv("VISION_PREDICTION_RESOURCE_ID");

在应用程序的 main 方法中,添加对本快速入门中使用的方法的调用。 稍后将对这些调用进行定义。

Project projectOD = createProjectOD(trainClient);
addTagsOD(trainClient, projectOD);
uploadImagesOD(trainClient, projectOD);
trainProjectOD(trainClient, projectOD);
publishIterationOD(trainClient, project);
testProjectOD(predictor, projectOD);

对象模型

以下类和接口用于处理自定义视觉 Java 客户端库的某些主要功能。

名称 说明
CustomVisionTrainingClient 此类处理模型的创建、训练和发布。
CustomVisionPredictionClient 此类处理用于对象检测预测的模型查询。
ImagePrediction 此类定义单一图像上的单一对象预测。 其中包括对象 ID 和名称的属性、对象的边界框位置以及可信度分数。

代码示例

这些代码片段演示如何使用适用于 Java 的自定义视觉客户端库执行以下任务:

验证客户端

在 main 方法中,使用终结点和密钥来实例化训练和预测客户端。

// Authenticate
CustomVisionTrainingClient trainClient = CustomVisionTrainingManager
        .authenticate(trainingEndpoint, trainingApiKey)
        .withEndpoint(trainingEndpoint);
CustomVisionPredictionClient predictor = CustomVisionPredictionManager
        .authenticate(predictionEndpoint, predictionApiKey)
        .withEndpoint(predictionEndpoint);

创建新的自定义视觉项目

下一个方法将创建对象检测项目。 创建的项目将显示在以前访问过的自定义视觉网站上。 请查看 CreateProject 方法重载,以在创建项目时指定其他选项(在生成检测器 Web 门户指南中进行了说明)。

public static Project createProjectOD(CustomVisionTrainingClient trainClient) {
    Trainings trainer = trainClient.trainings();

    // find the object detection ___domain to set the project type
    Domain objectDetectionDomain = null;
    List<Domain> domains = trainer.getDomains();
    for (final Domain ___domain : domains) {
        if (___domain.type() == DomainType.OBJECT_DETECTION) {
            objectDetectionDomain = ___domain;
            break;
        }
    }

    if (objectDetectionDomain == null) {
        System.out.println("Unexpected result; no objects were detected.");
    }

    System.out.println("Creating project...");
    // create an object detection project
    Project project = trainer.createProject().withName("Sample Java OD Project")
            .withDescription("Sample OD Project").withDomainId(objectDetectionDomain.id())
            .withClassificationType(Classifier.MULTILABEL.toString()).execute();

    return project;
}

将标记添加到你的项目

此方法定义要针对其训练模型的标签。

public static void addTagsOD(CustomVisionTrainingClient trainClient, Project project) {
    Trainings trainer = trainClient.trainings();
    // create fork tag
    Tag forkTag = trainer.createTag().withProjectId(project.id()).withName("fork").execute();

    // create scissors tag
    Tag scissorsTag = trainer.createTag().withProjectId(project.id()).withName("scissor").execute();
}

上传和标记图像

首先,下载此项目的示例图像。 将示例图像文件夹的内容保存到本地设备。

注意

是否需要一组范围更广的图像来完成训练? Trove 是一个 Microsoft Garage 项目,可用于收集和购买图像集以便进行训练。 收集图像后,可以通过一般方式下载映像,然后将其导入到自定义视觉项目。 若要了解详细信息,请访问 Trove 页面

在对象检测项目中标记图像时,需要使用标准化坐标指定每个标记对象的区域。 以下代码将每个示例图像与其标记的区域相关联。

注意

如果没有用于标记区域坐标的单击并拖动实用工具,则可以使用 Customvision.ai 的 Web UI。 在此示例中,已提供坐标。

public static void uploadImagesOD(CustomVisionTrainingClient trainClient, Project project) {
    // Mapping of filenames to their respective regions in the image. The
    // coordinates are specified
    // as left, top, width, height in normalized coordinates. I.e. (left is left in
    // pixels / width in pixels)

    // This is a hardcoded mapping of the files we'll upload along with the bounding
    // box of the object in the
    // image. The boudning box is specified as left, top, width, height in
    // normalized coordinates.
    // Normalized Left = Left / Width (in Pixels)
    // Normalized Top = Top / Height (in Pixels)
    // Normalized Bounding Box Width = (Right - Left) / Width (in Pixels)
    // Normalized Bounding Box Height = (Bottom - Top) / Height (in Pixels)
    HashMap<String, double[]> regionMap = new HashMap<String, double[]>();
    regionMap.put("scissors_1.jpg", new double[] { 0.4007353, 0.194068655, 0.259803921, 0.6617647 });
    regionMap.put("scissors_2.jpg", new double[] { 0.426470578, 0.185898721, 0.172794119, 0.5539216 });
    regionMap.put("scissors_3.jpg", new double[] { 0.289215684, 0.259428144, 0.403186262, 0.421568632 });
    regionMap.put("scissors_4.jpg", new double[] { 0.343137264, 0.105833367, 0.332107842, 0.8055556 });
    regionMap.put("scissors_5.jpg", new double[] { 0.3125, 0.09766343, 0.435049027, 0.71405226 });
    regionMap.put("scissors_6.jpg", new double[] { 0.379901975, 0.24308826, 0.32107842, 0.5718954 });
    regionMap.put("scissors_7.jpg", new double[] { 0.341911763, 0.20714055, 0.3137255, 0.6356209 });
    regionMap.put("scissors_8.jpg", new double[] { 0.231617644, 0.08459154, 0.504901946, 0.8480392 });
    regionMap.put("scissors_9.jpg", new double[] { 0.170343131, 0.332957536, 0.767156839, 0.403594762 });
    regionMap.put("scissors_10.jpg", new double[] { 0.204656869, 0.120539248, 0.5245098, 0.743464053 });
    regionMap.put("scissors_11.jpg", new double[] { 0.05514706, 0.159754932, 0.799019635, 0.730392158 });
    regionMap.put("scissors_12.jpg", new double[] { 0.265931368, 0.169558853, 0.5061275, 0.606209159 });
    regionMap.put("scissors_13.jpg", new double[] { 0.241421565, 0.184264734, 0.448529422, 0.6830065 });
    regionMap.put("scissors_14.jpg", new double[] { 0.05759804, 0.05027781, 0.75, 0.882352948 });
    regionMap.put("scissors_15.jpg", new double[] { 0.191176474, 0.169558853, 0.6936275, 0.6748366 });
    regionMap.put("scissors_16.jpg", new double[] { 0.1004902, 0.279036, 0.6911765, 0.477124184 });
    regionMap.put("scissors_17.jpg", new double[] { 0.2720588, 0.131977156, 0.4987745, 0.6911765 });
    regionMap.put("scissors_18.jpg", new double[] { 0.180147052, 0.112369314, 0.6262255, 0.6666667 });
    regionMap.put("scissors_19.jpg", new double[] { 0.333333343, 0.0274019931, 0.443627447, 0.852941155 });
    regionMap.put("scissors_20.jpg", new double[] { 0.158088237, 0.04047389, 0.6691176, 0.843137264 });
    regionMap.put("fork_1.jpg", new double[] { 0.145833328, 0.3509314, 0.5894608, 0.238562092 });
    regionMap.put("fork_2.jpg", new double[] { 0.294117659, 0.216944471, 0.534313738, 0.5980392 });
    regionMap.put("fork_3.jpg", new double[] { 0.09191177, 0.0682516545, 0.757352948, 0.6143791 });
    regionMap.put("fork_4.jpg", new double[] { 0.254901975, 0.185898721, 0.5232843, 0.594771266 });
    regionMap.put("fork_5.jpg", new double[] { 0.2365196, 0.128709182, 0.5845588, 0.71405226 });
    regionMap.put("fork_6.jpg", new double[] { 0.115196079, 0.133611143, 0.676470637, 0.6993464 });
    regionMap.put("fork_7.jpg", new double[] { 0.164215669, 0.31008172, 0.767156839, 0.410130739 });
    regionMap.put("fork_8.jpg", new double[] { 0.118872553, 0.318251669, 0.817401946, 0.225490168 });
    regionMap.put("fork_9.jpg", new double[] { 0.18259804, 0.2136765, 0.6335784, 0.643790841 });
    regionMap.put("fork_10.jpg", new double[] { 0.05269608, 0.282303959, 0.8088235, 0.452614367 });
    regionMap.put("fork_11.jpg", new double[] { 0.05759804, 0.0894935, 0.9007353, 0.3251634 });
    regionMap.put("fork_12.jpg", new double[] { 0.3345588, 0.07315363, 0.375, 0.9150327 });
    regionMap.put("fork_13.jpg", new double[] { 0.269607842, 0.194068655, 0.4093137, 0.6732026 });
    regionMap.put("fork_14.jpg", new double[] { 0.143382356, 0.218578458, 0.7977941, 0.295751631 });
    regionMap.put("fork_15.jpg", new double[] { 0.19240196, 0.0633497, 0.5710784, 0.8398692 });
    regionMap.put("fork_16.jpg", new double[] { 0.140931368, 0.480016381, 0.6838235, 0.240196079 });
    regionMap.put("fork_17.jpg", new double[] { 0.305147052, 0.2512582, 0.4791667, 0.5408496 });
    regionMap.put("fork_18.jpg", new double[] { 0.234068632, 0.445702642, 0.6127451, 0.344771236 });
    regionMap.put("fork_19.jpg", new double[] { 0.219362751, 0.141781077, 0.5919118, 0.6683006 });
    regionMap.put("fork_20.jpg", new double[] { 0.180147052, 0.239820287, 0.6887255, 0.235294119 });

下一个代码块可将图像添加到项目。 需要更改 GetImage 调用的参数以指向你下载的“fork”和“scissors”文件夹的位置 。

    Trainings trainer = trainClient.trainings();

    System.out.println("Adding images...");
    for (int i = 1; i <= 20; i++) {
        String fileName = "fork_" + i + ".jpg";
        byte[] contents = GetImage("/fork", fileName);
        AddImageToProject(trainer, project, fileName, contents, forkTag.id(), regionMap.get(fileName));
    }

    for (int i = 1; i <= 20; i++) {
        String fileName = "scissors_" + i + ".jpg";
        byte[] contents = GetImage("/scissors", fileName);
        AddImageToProject(trainer, project, fileName, contents, scissorsTag.id(), regionMap.get(fileName));
    }
}

上一代码片段使用两个帮助程序函数,以资源流的形式检索图像并将其上传到服务(最多可以在单个批次中上传 64 个图像)。 定义这些方法。

private static void AddImageToProject(Trainings trainer, Project project, String fileName, byte[] contents,
        UUID tag, double[] regionValues) {
    System.out.println("Adding image: " + fileName);
    ImageFileCreateEntry file = new ImageFileCreateEntry().withName(fileName).withContents(contents);

    ImageFileCreateBatch batch = new ImageFileCreateBatch().withImages(Collections.singletonList(file));

    // If Optional region is specified, tack it on and place the tag there,
    // otherwise
    // add it to the batch.
    if (regionValues != null) {
        Region region = new Region().withTagId(tag).withLeft(regionValues[0]).withTop(regionValues[1])
                .withWidth(regionValues[2]).withHeight(regionValues[3]);
        file = file.withRegions(Collections.singletonList(region));
    } else {
        batch = batch.withTagIds(Collections.singletonList(tag));
    }

    trainer.createImagesFromFiles(project.id(), batch);
}

private static byte[] GetImage(String folder, String fileName) {
    try {
        return ByteStreams.toByteArray(CustomVisionSamples.class.getResourceAsStream(folder + "/" + fileName));
    } catch (Exception e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
    }
    return null;
}

定型项目

此方法将在项目中创建第一个训练迭代。 它将查询服务,直到训练完成。

public static String trainProjectOD(CustomVisionTrainingClient trainClient, Project project) {
    Trainings trainer = trainClient.trainings();
    System.out.println("Training...");
    Iteration iteration = trainer.trainProject(project.id(), new TrainProjectOptionalParameter());

    while (iteration.status().equals("Training")) {
        System.out.println("Training Status: " + iteration.status());
        Thread.sleep(5000);
        iteration = trainer.getIteration(project.id(), iteration.id());
    }
    System.out.println("Training Status: " + iteration.status());
}

发布当前迭代

此方法使模型的当前迭代可用于查询。 可以将模型名称用作发送预测请求的引用。 需要为 predictionResourceId 输入自己的值。 可以在 Azure 门户中资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID 。

public static String publishIterationOD(CustomVisionTrainingClient trainClient, Project project) {
    Trainings trainer = trainClient.trainings();

    // The iteration is now trained. Publish it to the prediction endpoint.
    String publishedModelName = "myModel";
    String predictionID = "<your-prediction-resource-ID>";
    trainer.publishIteration(project.id(), iteration.id(), publishedModelName, predictionID);
    return publishedModelName;
}

测试预测终结点

此方法用于加载测试图像、查询模型终结点,以及将预测数据输出到控制台。

public static void testProjectOD(CustomVisionPredictionClient predictor, Project project) {

    // load test image
    byte[] testImage = GetImage("/ObjectTest", "test_image.jpg");

    // predict
    ImagePrediction results = predictor.predictions().detectImage().withProjectId(project.id())
            .withPublishedName(publishedModelName).withImageData(testImage).execute();

    for (Prediction prediction : results.predictions()) {
        System.out.println(String.format("\t%s: %.2f%% at: %.2f, %.2f, %.2f, %.2f", prediction.tagName(),
                prediction.probability() * 100.0f, prediction.boundingBox().left(), prediction.boundingBox().top(),
                prediction.boundingBox().width(), prediction.boundingBox().height()));
    }
}

运行应用程序

可使用以下命令生成应用:

gradle build

使用 gradle run 命令运行应用程序:

gradle run

清理资源

如果想要清理并移除 Azure AI 服务订阅,可以删除资源或资源组。 删除资源组同时也会删除与之相关联的任何其他资源。

如果你希望实现自己的物体检测项目(或改为尝试图像分类项目),可能希望从此示例中删除叉子/剪刀检测项目。 免费订阅允许创建两个自定义视觉项目。

自定义视觉网站上,导航到“项目”,然后在“我的新项目”下选择垃圾桶。

标有“我的新项目”的面板的屏幕截图,其中包含垃圾桶图标。

后续步骤

现在,你已在代码中完成了对象检测过程的每一步。 此示例执行单次训练迭代,但通常需要多次训练和测试模型,以使其更准确。 以下指南涉及图像分类,但其原理与对象检测类似。

  • 什么是自定义视觉?
  • 可以在 GitHub 上找到此示例的源代码

本指南提供说明和示例代码,以帮助你开始使用适用于 Node.js 的自定义视觉客户端库来构建对象检测模型。 你将创建一个项目,添加标记,训练该项目,并使用该项目的预测终结点 URL 以编程方式对其进行测试。 使用此示例作为模板来构建你自己的图像识别应用。

注意

若要在不编写代码的情况下构建和训练对象检测模型,请改为参阅基于浏览器的指南

参考文档(训练)(预测) | 包 (npm) (训练)(预测) | 示例

先决条件

  • Azure 订阅 - 免费创建订阅
  • 最新版本的 Node.js
  • 拥有 Azure 订阅后,请在 Azure 门户中创建自定义视觉资源,以创建训练和预测资源。
    • 可以使用免费定价层 (F0) 试用该服务,然后再升级到付费层进行生产。

创建环境变量

在此示例中,你将凭据写入运行应用程序的本地计算机上的环境变量。

转到 Azure 门户。 如果在“先决条件”部分中创建的自定义视觉资源已成功部署,请选择“后续步骤”下的“转到资源”按钮。 在资源的“密钥和终结点”页的“资源管理”下可找到密钥和终结点。 你需要获取训练资源和预测资源这两者的密钥,以及 API 终结点。

可以在 Azure 门户中预测资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID。

提示

还可以使用 https://www.customvision.ai 来获取这些值。 登录后,请选择右上角的“设置”图标。 在“设置”页上,可以查看所有密钥、资源 ID 和终结点。

若要设置环境变量,请打开控制台窗口,按照操作系统和开发环境的说明进行操作。

  • 若要设置 VISION_TRAINING KEY 环境变量,请将 <your-training-key> 替换为训练资源的其中一个密钥。
  • 若要设置 VISION_TRAINING_ENDPOINT 环境变量,请将 <your-training-endpoint> 替换为训练资源的终结点。
  • 若要设置 VISION_PREDICTION_KEY 环境变量,请将 <your-prediction-key> 替换为预测资源的其中一个密钥。
  • 若要设置 VISION_PREDICTION_ENDPOINT 环境变量,请将 <your-prediction-endpoint> 替换为预测资源的终结点。
  • 若要设置 VISION_PREDICTION_RESOURCE_ID 环境变量,请将 <your-resource-id> 替换为预测资源的资源 ID。

重要

我们建议使用 Azure 资源的托管标识进行 Microsoft Entra ID 身份验证,以避免将凭据随云中运行的应用程序一起存储。

请谨慎使用 API 密钥。 请不要直接在代码中包含 API 密钥,并且切勿公开发布该密钥。 如果使用 API 密钥,请将其安全地存储在 Azure Key Vault 中,定期轮换密钥,并使用基于角色的访问控制和网络访问限制来限制对 Azure Key Vault 的访问。 若要详细了解如何在应用中安全地使用 API 密钥,请参阅 API 密钥与 Azure Key Vault

有关 Azure AI 服务安全性的详细信息,请参阅对 Azure AI 服务的请求进行身份验证

setx VISION_TRAINING_KEY <your-training-key>
setx VISION_TRAINING_ENDPOINT <your-training-endpoint>
setx VISION_PREDICTION_KEY <your-prediction-key>
setx VISION_PREDICTION_ENDPOINT <your-prediction-endpoint>
setx VISION_PREDICTION_RESOURCE_ID <your-resource-id>

添加环境变量后,可能需要重启任何正在运行的、读取环境变量的程序(包括控制台窗口)。

设置

创建新的 Node.js 应用程序

在控制台窗口(例如 cmd、PowerShell 或 Bash)中,为应用创建一个新目录并导航到该目录。

mkdir myapp && cd myapp

运行 npm init 命令以使用 package.json 文件创建一个 node 应用程序。

npm init

安装客户端库

若要使用适用于 Node.js 的自定义视觉编写图像分析应用,需要自定义视觉 npm 包。 若要安装它们,请在 PowerShell 中运行以下命令:

npm install @azure/cognitiveservices-customvision-training
npm install @azure/cognitiveservices-customvision-prediction

应用的 package.json 文件将使用依赖项进行更新。

创建一个名为 index.js 的文件,并导入以下库:

const util = require('util');
const fs = require('fs');
const TrainingApi = require("@azure/cognitiveservices-customvision-training");
const PredictionApi = require("@azure/cognitiveservices-customvision-prediction");
const msRest = require("@azure/ms-rest-js");

提示

想要立即查看整个快速入门代码文件? 可以在 GitHub 上找到它,其中包含此快速入门中的代码示例。

为资源的 Azure 终结点和密钥创建变量。

// retrieve environment variables
const trainingKey = process.env["VISION_TRAINING_KEY"];
const trainingEndpoint = process.env["VISION_TRAINING_ENDPOINT"];

const predictionKey = process.env["VISION_PREDICTION_KEY"];
const predictionResourceId = process.env["VISION_PREDICTION_RESOURCE_ID"];
const predictionEndpoint = process.env["VISION_PREDICTION_ENDPOINT"];

此外,为项目名称添加字段,并为异步调用添加超时参数。

const publishIterationName = "detectModel";
const setTimeoutPromise = util.promisify(setTimeout);

对象模型

名称 说明
TrainingAPIClient 此类处理模型的创建、训练和发布。
PredictionAPIClient 此类处理用于对象检测预测的模型查询。
预测 此接口定义对单一图像的单一预测。 它包括对象 ID 和名称的属性,以及可信度分数。

代码示例

这些代码片段演示如何使用适用于 JavaScript 的自定义视觉客户端库执行以下任务:

验证客户端

使用终结点和密钥实例化客户端对象。 使用你的密钥创建 ApiKeyCredentials 对象,并将其用于终结点,以创建 TrainingAPIClientPredictionAPIClient 对象。

const credentials = new msRest.ApiKeyCredentials({ inHeader: { "Training-key": trainingKey } });
const trainer = new TrainingApi.TrainingAPIClient(credentials, trainingEndpoint);
const predictor_credentials = new msRest.ApiKeyCredentials({ inHeader: { "Prediction-key": predictionKey } });
const predictor = new PredictionApi.PredictionAPIClient(predictor_credentials, predictionEndpoint);

添加帮助程序函数

添加以下函数以帮助进行多个异步调用。 稍后将使用它。

const credentials = new msRest.ApiKeyCredentials({ inHeader: { "Training-key": trainingKey } });
const trainer = new TrainingApi.TrainingAPIClient(credentials, trainingEndpoint);
const predictor_credentials = new msRest.ApiKeyCredentials({ inHeader: { "Prediction-key": predictionKey } });
const predictor = new PredictionApi.PredictionAPIClient(predictor_credentials, predictionEndpoint);

创建新的自定义视觉项目

编写新函数,使其包含所有自定义视觉函数调用。 添加以下代码以创建新的自定义视觉服务项目。

(async () => {
    console.log("Creating project...");
    const domains = await trainer.getDomains()
    const objDetectDomain = domains.find(___domain => ___domain.type === "ObjectDetection");
    const sampleProject = await trainer.createProject("Sample Obj Detection Project", { domainId: objDetectDomain.id });

将标记添加到项目中

若要在项目中创建分类标记,请将以下代码添加到你的函数:

const forkTag = await trainer.createTag(sampleProject.id, "Fork");
const scissorsTag = await trainer.createTag(sampleProject.id, "Scissors");

上传和标记图像

首先,下载此项目的示例图像。 将示例图像文件夹的内容保存到本地设备。

要将示例图像添加到项目,请在创建标记后插入以下代码。 此代码会上传具有相应标记的每个图像。 在对象检测项目中标记图像时,需要使用标准化坐标指定每个标记对象的区域。 在本教程中,区域使用代码进行内联硬编码。 区域在标准化坐标中指定边界框,坐标按以下顺序给定:左部、顶部、宽度、高度。 最多可以在单个批次中上传 64 个图像。

const sampleDataRoot = "Images";

const forkImageRegions = {
    "fork_1.jpg": [0.145833328, 0.3509314, 0.5894608, 0.238562092],
    "fork_2.jpg": [0.294117659, 0.216944471, 0.534313738, 0.5980392],
    "fork_3.jpg": [0.09191177, 0.0682516545, 0.757352948, 0.6143791],
    "fork_4.jpg": [0.254901975, 0.185898721, 0.5232843, 0.594771266],
    "fork_5.jpg": [0.2365196, 0.128709182, 0.5845588, 0.71405226],
    "fork_6.jpg": [0.115196079, 0.133611143, 0.676470637, 0.6993464],
    "fork_7.jpg": [0.164215669, 0.31008172, 0.767156839, 0.410130739],
    "fork_8.jpg": [0.118872553, 0.318251669, 0.817401946, 0.225490168],
    "fork_9.jpg": [0.18259804, 0.2136765, 0.6335784, 0.643790841],
    "fork_10.jpg": [0.05269608, 0.282303959, 0.8088235, 0.452614367],
    "fork_11.jpg": [0.05759804, 0.0894935, 0.9007353, 0.3251634],
    "fork_12.jpg": [0.3345588, 0.07315363, 0.375, 0.9150327],
    "fork_13.jpg": [0.269607842, 0.194068655, 0.4093137, 0.6732026],
    "fork_14.jpg": [0.143382356, 0.218578458, 0.7977941, 0.295751631],
    "fork_15.jpg": [0.19240196, 0.0633497, 0.5710784, 0.8398692],
    "fork_16.jpg": [0.140931368, 0.480016381, 0.6838235, 0.240196079],
    "fork_17.jpg": [0.305147052, 0.2512582, 0.4791667, 0.5408496],
    "fork_18.jpg": [0.234068632, 0.445702642, 0.6127451, 0.344771236],
    "fork_19.jpg": [0.219362751, 0.141781077, 0.5919118, 0.6683006],
    "fork_20.jpg": [0.180147052, 0.239820287, 0.6887255, 0.235294119]
};

const scissorsImageRegions = {
    "scissors_1.jpg": [0.4007353, 0.194068655, 0.259803921, 0.6617647],
    "scissors_2.jpg": [0.426470578, 0.185898721, 0.172794119, 0.5539216],
    "scissors_3.jpg": [0.289215684, 0.259428144, 0.403186262, 0.421568632],
    "scissors_4.jpg": [0.343137264, 0.105833367, 0.332107842, 0.8055556],
    "scissors_5.jpg": [0.3125, 0.09766343, 0.435049027, 0.71405226],
    "scissors_6.jpg": [0.379901975, 0.24308826, 0.32107842, 0.5718954],
    "scissors_7.jpg": [0.341911763, 0.20714055, 0.3137255, 0.6356209],
    "scissors_8.jpg": [0.231617644, 0.08459154, 0.504901946, 0.8480392],
    "scissors_9.jpg": [0.170343131, 0.332957536, 0.767156839, 0.403594762],
    "scissors_10.jpg": [0.204656869, 0.120539248, 0.5245098, 0.743464053],
    "scissors_11.jpg": [0.05514706, 0.159754932, 0.799019635, 0.730392158],
    "scissors_12.jpg": [0.265931368, 0.169558853, 0.5061275, 0.606209159],
    "scissors_13.jpg": [0.241421565, 0.184264734, 0.448529422, 0.6830065],
    "scissors_14.jpg": [0.05759804, 0.05027781, 0.75, 0.882352948],
    "scissors_15.jpg": [0.191176474, 0.169558853, 0.6936275, 0.6748366],
    "scissors_16.jpg": [0.1004902, 0.279036, 0.6911765, 0.477124184],
    "scissors_17.jpg": [0.2720588, 0.131977156, 0.4987745, 0.6911765],
    "scissors_18.jpg": [0.180147052, 0.112369314, 0.6262255, 0.6666667],
    "scissors_19.jpg": [0.333333343, 0.0274019931, 0.443627447, 0.852941155],
    "scissors_20.jpg": [0.158088237, 0.04047389, 0.6691176, 0.843137264]
};

console.log("Adding images...");
let fileUploadPromises = [];

const forkDir = `${sampleDataRoot}/fork`;
const forkFiles = fs.readdirSync(forkDir);

await asyncForEach(forkFiles, async (file) => {
    const region = { tagId: forkTag.id, left: forkImageRegions[file][0], top: forkImageRegions[file][1], width: forkImageRegions[file][2], height: forkImageRegions[file][3] };
    const entry = { name: file, contents: fs.readFileSync(`${forkDir}/${file}`), regions: [region] };
    const batch = { images: [entry] };
    // Wait one second to accommodate rate limit.
    await setTimeoutPromise(1000, null);
    fileUploadPromises.push(trainer.createImagesFromFiles(sampleProject.id, batch));
});

const scissorsDir = `${sampleDataRoot}/scissors`;
const scissorsFiles = fs.readdirSync(scissorsDir);

await asyncForEach(scissorsFiles, async (file) => {
    const region = { tagId: scissorsTag.id, left: scissorsImageRegions[file][0], top: scissorsImageRegions[file][1], width: scissorsImageRegions[file][2], height: scissorsImageRegions[file][3] };
    const entry = { name: file, contents: fs.readFileSync(`${scissorsDir}/${file}`), regions: [region] };
    const batch = { images: [entry] };
    // Wait one second to accommodate rate limit.
    await setTimeoutPromise(1000, null);
    fileUploadPromises.push(trainer.createImagesFromFiles(sampleProject.id, batch));
});

await Promise.all(fileUploadPromises);

重要

需根据将 Azure AI 服务 Python SDK 示例存储库下载到的位置更改图像 (sampleDataRoot) 的路径。

注意

如果没有用于标记区域坐标的单击并拖动实用工具,则可以使用 Customvision.ai 的 Web UI。 在此示例中,已提供坐标。

定型项目

此代码用于创建预测模型的第一次迭代。

console.log("Training...");
let trainingIteration = await trainer.trainProject(sampleProject.id);

// Wait for training to complete
console.log("Training started...");
while (trainingIteration.status == "Training") {
    console.log("Training status: " + trainingIteration.status);
    // wait for ten seconds
    await setTimeoutPromise(10000, null);
    trainingIteration = await trainer.getIteration(sampleProject.id, trainingIteration.id)
}
console.log("Training status: " + trainingIteration.status);

发布当前迭代

此代码用于将训练好的迭代发布到预测终结点。 为发布的迭代起的名称可用于发送预测请求。 在发布迭代之前,迭代在预测终结点中不可用。

// Publish the iteration to the end point
await trainer.publishIteration(sampleProject.id, trainingIteration.id, publishIterationName, predictionResourceId);    

测试预测终结点

若要将图像发送到预测终结点并检索预测,请将以下代码添加到你的函数。

const testFile = fs.readFileSync(`${sampleDataRoot}/test/test_image.jpg`);
const results = await predictor.detectImage(sampleProject.id, publishIterationName, testFile)

// Show results
console.log("Results:");
results.predictions.forEach(predictedResult => {
    console.log(`\t ${predictedResult.tagName}: ${(predictedResult.probability * 100.0).toFixed(2)}% ${predictedResult.boundingBox.left},${predictedResult.boundingBox.top},${predictedResult.boundingBox.width},${predictedResult.boundingBox.height}`);
});

然后,关闭你的自定义视觉函数并调用它。

})()

运行应用程序

在快速入门文件中使用 node 命令运行应用程序。

node index.js

应用程序的输出应显示在控制台中。 然后,可以验证是否已正确标记(在 <sampleDataRoot>/Test/ 中找到的)测试图像,并验证检测区域是否正确。 也可返回到自定义视觉网站,查看新创建项目的当前状态。

清理资源

如果你希望实现自己的物体检测项目(或改为尝试图像分类项目),可能希望从此示例中删除叉子/剪刀检测项目。 免费订阅允许创建两个自定义视觉项目。

自定义视觉网站上,导航到“项目”,然后在“我的新项目”下选择垃圾桶。

标有“我的新项目”的面板的屏幕截图,其中包含垃圾桶图标。

后续步骤

现在,你已在代码中完成了对象检测过程的每一步。 此示例执行单次训练迭代,但通常需要多次训练和测试模型,以使其更准确。 以下指南涉及图像分类,但其原理与对象检测类似。

适用于 Python 的自定义视觉客户端库入门。 请按照以下步骤安装包并试用用于生成对象检测模型的示例代码。 你将创建一个项目,添加标记,训练该项目,并使用该项目的预测终结点 URL 以编程方式对其进行测试。 使用此示例作为模板来构建你自己的图像识别应用。

注意

若要在不编写代码的情况下构建和训练对象检测模型,请改为参阅基于浏览器的指南

参考文档 | 库源代码 | 包 (PyPI) | 示例

先决条件

  • Azure 订阅 - 免费创建订阅
  • Python 3.x
    • 你的 Python 安装应包含 pip。 可以通过在命令行上运行 pip --version 来检查是否安装了 pip。 通过安装最新版本的 Python 获取 pip。
  • 拥有 Azure 订阅后,请在 Azure 门户中创建自定义视觉资源,以创建训练和预测资源。
    • 可以使用免费定价层 (F0) 试用该服务,然后再升级到付费层进行生产。

创建环境变量

在此示例中,你将凭据写入运行应用程序的本地计算机上的环境变量。

转到 Azure 门户。 如果在“先决条件”部分中创建的自定义视觉资源已成功部署,请选择“后续步骤”下的“转到资源”按钮。 在资源的“密钥和终结点”页的“资源管理”下可找到密钥和终结点。 你需要获取训练资源和预测资源这两者的密钥,以及 API 终结点。

可以在 Azure 门户中预测资源的“属性”选项卡上找到列为“资源 ID”的预测资源 ID。

提示

还可以使用 https://www.customvision.ai 来获取这些值。 登录后,请选择右上角的“设置”图标。 在“设置”页上,可以查看所有密钥、资源 ID 和终结点。

若要设置环境变量,请打开控制台窗口,按照操作系统和开发环境的说明进行操作。

  • 若要设置 VISION_TRAINING KEY 环境变量,请将 <your-training-key> 替换为训练资源的其中一个密钥。
  • 若要设置 VISION_TRAINING_ENDPOINT 环境变量,请将 <your-training-endpoint> 替换为训练资源的终结点。
  • 若要设置 VISION_PREDICTION_KEY 环境变量,请将 <your-prediction-key> 替换为预测资源的其中一个密钥。
  • 若要设置 VISION_PREDICTION_ENDPOINT 环境变量,请将 <your-prediction-endpoint> 替换为预测资源的终结点。
  • 若要设置 VISION_PREDICTION_RESOURCE_ID 环境变量,请将 <your-resource-id> 替换为预测资源的资源 ID。

重要

我们建议使用 Azure 资源的托管标识进行 Microsoft Entra ID 身份验证,以避免将凭据随云中运行的应用程序一起存储。

请谨慎使用 API 密钥。 请不要直接在代码中包含 API 密钥,并且切勿公开发布该密钥。 如果使用 API 密钥,请将其安全地存储在 Azure Key Vault 中,定期轮换密钥,并使用基于角色的访问控制和网络访问限制来限制对 Azure Key Vault 的访问。 若要详细了解如何在应用中安全地使用 API 密钥,请参阅 API 密钥与 Azure Key Vault

有关 Azure AI 服务安全性的详细信息,请参阅对 Azure AI 服务的请求进行身份验证

setx VISION_TRAINING_KEY <your-training-key>
setx VISION_TRAINING_ENDPOINT <your-training-endpoint>
setx VISION_PREDICTION_KEY <your-prediction-key>
setx VISION_PREDICTION_ENDPOINT <your-prediction-endpoint>
setx VISION_PREDICTION_RESOURCE_ID <your-resource-id>

添加环境变量后,可能需要重启任何正在运行的、读取环境变量的程序(包括控制台窗口)。

设置

安装客户端库

若要使用适用于 Python 的自定义视觉来编写图像分析应用,需要自定义视觉客户端库。 安装 Python 后,在 PowerShell 或控制台窗口中运行以下命令:

pip install azure-cognitiveservices-vision-customvision

创建新的 Python 应用程序

创建新的 Python 文件并导入以下库。

from azure.cognitiveservices.vision.customvision.training import CustomVisionTrainingClient
from azure.cognitiveservices.vision.customvision.prediction import CustomVisionPredictionClient
from azure.cognitiveservices.vision.customvision.training.models import ImageFileCreateBatch, ImageFileCreateEntry, Region
from msrest.authentication import ApiKeyCredentials
import os, time, uuid

提示

想要立即查看整个快速入门代码文件? 可以在 GitHub 上找到它,其中包含此快速入门中的代码示例。

为资源的 Azure 终结点和密钥创建变量。

# Replace with valid values
ENDPOINT = os.environ["VISION_TRAINING_ENDPOINT"]
training_key = os.environ["VISION_TRAINING_KEY"]
prediction_key = os.environ["VISION_PREDICTION_KEY"]
prediction_resource_id = os.environ["VISION_PREDICTION_RESOURCE_ID"]

对象模型

名称 说明
CustomVisionTrainingClient 此类处理模型的创建、训练和发布。
CustomVisionPredictionClient 此类处理用于对象检测预测的模型查询。
ImagePrediction 此类定义单一图像上的单一对象预测。 其中包括对象 ID 和名称的属性、对象的边界框位置以及可信度分数。

代码示例

这些代码片段演示如何使用适用于 Python 的自定义视觉客户端库执行以下任务:

验证客户端

使用终结点和密钥来实例化训练和预测客户端。 使用密钥创建 ApiKeyServiceClientCredentials 对象,并将它们与终结点一起使用以创建 CustomVisionTrainingClientCustomVisionPredictionClient 对象。

credentials = ApiKeyCredentials(in_headers={"Training-key": training_key})
trainer = CustomVisionTrainingClient(ENDPOINT, credentials)
prediction_credentials = ApiKeyCredentials(in_headers={"Prediction-key": prediction_key})
predictor = CustomVisionPredictionClient(ENDPOINT, prediction_credentials)

创建新的自定义视觉项目

将以下代码添加到脚本中以创建新的自定义视觉服务项目。

请查看 create_project 方法,以在创建项目时指定其他选项(在生成检测器 Web 门户指南中进行了说明)。

publish_iteration_name = "detectModel"

# Find the object detection ___domain
obj_detection_domain = next(___domain for ___domain in trainer.get_domains() if ___domain.type == "ObjectDetection" and ___domain.name == "General")

# Create a new project
print ("Creating project...")
# Use uuid to avoid project name collisions.
project = trainer.create_project(str(uuid.uuid4()), domain_id=obj_detection_domain.id)

将标记添加到项目中

若要在项目中创建对象标记,请添加以下代码:

# Make two tags in the new project
fork_tag = trainer.create_tag(project.id, "fork")
scissors_tag = trainer.create_tag(project.id, "scissors")

上传和标记图像

首先,下载此项目的示例图像。 将示例图像文件夹的内容保存到本地设备。

在对象检测项目中标记图像时,需要使用标准化坐标指定每个标记对象的区域。 以下代码将每个示例图像与其标记的区域相关联。 区域在标准化坐标中指定边界框,坐标按以下顺序给定:左部、顶部、宽度、高度。

fork_image_regions = {
    "fork_1": [ 0.145833328, 0.3509314, 0.5894608, 0.238562092 ],
    "fork_2": [ 0.294117659, 0.216944471, 0.534313738, 0.5980392 ],
    "fork_3": [ 0.09191177, 0.0682516545, 0.757352948, 0.6143791 ],
    "fork_4": [ 0.254901975, 0.185898721, 0.5232843, 0.594771266 ],
    "fork_5": [ 0.2365196, 0.128709182, 0.5845588, 0.71405226 ],
    "fork_6": [ 0.115196079, 0.133611143, 0.676470637, 0.6993464 ],
    "fork_7": [ 0.164215669, 0.31008172, 0.767156839, 0.410130739 ],
    "fork_8": [ 0.118872553, 0.318251669, 0.817401946, 0.225490168 ],
    "fork_9": [ 0.18259804, 0.2136765, 0.6335784, 0.643790841 ],
    "fork_10": [ 0.05269608, 0.282303959, 0.8088235, 0.452614367 ],
    "fork_11": [ 0.05759804, 0.0894935, 0.9007353, 0.3251634 ],
    "fork_12": [ 0.3345588, 0.07315363, 0.375, 0.9150327 ],
    "fork_13": [ 0.269607842, 0.194068655, 0.4093137, 0.6732026 ],
    "fork_14": [ 0.143382356, 0.218578458, 0.7977941, 0.295751631 ],
    "fork_15": [ 0.19240196, 0.0633497, 0.5710784, 0.8398692 ],
    "fork_16": [ 0.140931368, 0.480016381, 0.6838235, 0.240196079 ],
    "fork_17": [ 0.305147052, 0.2512582, 0.4791667, 0.5408496 ],
    "fork_18": [ 0.234068632, 0.445702642, 0.6127451, 0.344771236 ],
    "fork_19": [ 0.219362751, 0.141781077, 0.5919118, 0.6683006 ],
    "fork_20": [ 0.180147052, 0.239820287, 0.6887255, 0.235294119 ]
}

scissors_image_regions = {
    "scissors_1": [ 0.4007353, 0.194068655, 0.259803921, 0.6617647 ],
    "scissors_2": [ 0.426470578, 0.185898721, 0.172794119, 0.5539216 ],
    "scissors_3": [ 0.289215684, 0.259428144, 0.403186262, 0.421568632 ],
    "scissors_4": [ 0.343137264, 0.105833367, 0.332107842, 0.8055556 ],
    "scissors_5": [ 0.3125, 0.09766343, 0.435049027, 0.71405226 ],
    "scissors_6": [ 0.379901975, 0.24308826, 0.32107842, 0.5718954 ],
    "scissors_7": [ 0.341911763, 0.20714055, 0.3137255, 0.6356209 ],
    "scissors_8": [ 0.231617644, 0.08459154, 0.504901946, 0.8480392 ],
    "scissors_9": [ 0.170343131, 0.332957536, 0.767156839, 0.403594762 ],
    "scissors_10": [ 0.204656869, 0.120539248, 0.5245098, 0.743464053 ],
    "scissors_11": [ 0.05514706, 0.159754932, 0.799019635, 0.730392158 ],
    "scissors_12": [ 0.265931368, 0.169558853, 0.5061275, 0.606209159 ],
    "scissors_13": [ 0.241421565, 0.184264734, 0.448529422, 0.6830065 ],
    "scissors_14": [ 0.05759804, 0.05027781, 0.75, 0.882352948 ],
    "scissors_15": [ 0.191176474, 0.169558853, 0.6936275, 0.6748366 ],
    "scissors_16": [ 0.1004902, 0.279036, 0.6911765, 0.477124184 ],
    "scissors_17": [ 0.2720588, 0.131977156, 0.4987745, 0.6911765 ],
    "scissors_18": [ 0.180147052, 0.112369314, 0.6262255, 0.6666667 ],
    "scissors_19": [ 0.333333343, 0.0274019931, 0.443627447, 0.852941155 ],
    "scissors_20": [ 0.158088237, 0.04047389, 0.6691176, 0.843137264 ]
}

注意

如果没有用于标记区域坐标的单击并拖动实用工具,则可以使用 Customvision.ai 的 Web UI。 在此示例中,已提供坐标。

然后,使用此关联映射上传每个样本图像及其区域坐标(最多可以在单个批次中上传 64 个图像)。 添加以下代码。

base_image_location = os.path.join (os.path.dirname(__file__), "Images")

# Go through the data table above and create the images
print ("Adding images...")
tagged_images_with_regions = []

for file_name in fork_image_regions.keys():
    x,y,w,h = fork_image_regions[file_name]
    regions = [ Region(tag_id=fork_tag.id, left=x,top=y,width=w,height=h) ]

    with open(os.path.join (base_image_location, "fork", file_name + ".jpg"), mode="rb") as image_contents:
        tagged_images_with_regions.append(ImageFileCreateEntry(name=file_name, contents=image_contents.read(), regions=regions))

for file_name in scissors_image_regions.keys():
    x,y,w,h = scissors_image_regions[file_name]
    regions = [ Region(tag_id=scissors_tag.id, left=x,top=y,width=w,height=h) ]

    with open(os.path.join (base_image_location, "scissors", file_name + ".jpg"), mode="rb") as image_contents:
        tagged_images_with_regions.append(ImageFileCreateEntry(name=file_name, contents=image_contents.read(), regions=regions))

upload_result = trainer.create_images_from_files(project.id, ImageFileCreateBatch(images=tagged_images_with_regions))
if not upload_result.is_batch_successful:
    print("Image batch upload failed.")
    for image in upload_result.images:
        print("Image status: ", image.status)
    exit(-1)

注意

需根据先前将 Azure AI 服务 Python SDK 示例存储库下载到的位置更改图像的路径。

定型项目

此代码用于创建预测模型的第一次迭代。

print ("Training...")
iteration = trainer.train_project(project.id)
while (iteration.status != "Completed"):
    iteration = trainer.get_iteration(project.id, iteration.id)
    print ("Training status: " + iteration.status)
    time.sleep(1)

提示

使用选定标记进行训练

可以选择只对应用的标记的子集进行训练。 如果你还没有应用足够多的特定标记,但是你确实有足够多的其他标记,则可能需要这样做。 在 train_project 调用中,将可选参数 selected_tags 设置为要使用的标记的 ID 字符串列表。 该模型会训练成只识别该列表中的标记。

发布当前迭代

在发布迭代之前,迭代在预测终结点中不可用。 以下代码使模型的当前迭代可用于查询。

# The iteration is now trained. Publish it to the project endpoint
trainer.publish_iteration(project.id, iteration.id, publish_iteration_name, prediction_resource_id)
print ("Done!")

测试预测终结点

若要将图像发送到预测终结点并检索预测,请将以下代码添加到文件末尾:

# Now there is a trained endpoint that can be used to make a prediction

# Open the sample image and get back the prediction results.
with open(os.path.join (base_image_location, "test", "test_image.jpg"), mode="rb") as test_data:
    results = predictor.detect_image(project.id, publish_iteration_name, test_data)

# Display the results.    
for prediction in results.predictions:
    print("\t" + prediction.tag_name + ": {0:.2f}% bbox.left = {1:.2f}, bbox.top = {2:.2f}, bbox.width = {3:.2f}, bbox.height = {4:.2f}".format(prediction.probability * 100, prediction.bounding_box.left, prediction.bounding_box.top, prediction.bounding_box.width, prediction.bounding_box.height))

运行应用程序

运行 CustomVisionQuickstart.py。

python CustomVisionQuickstart.py

应用程序的输出应显示在控制台中。 然后,可以验证测试图像(在 <base_image_location>/images/Test 中找到的)是否已正确标记,并验证检测区域是否正确。 也可返回到自定义视觉网站,查看新创建项目的当前状态。

清理资源

如果你希望实现自己的物体检测项目(或改为尝试图像分类项目),可能希望从此示例中删除叉子/剪刀检测项目。 免费订阅允许创建两个自定义视觉项目。

自定义视觉网站上,导航到“项目”,然后在“我的新项目”下选择垃圾桶。

标有“我的新项目”的面板的屏幕截图,其中包含垃圾桶图标。

后续步骤

现在,你已在代码中完成了对象检测过程的每一步。 此示例执行单次训练迭代,但通常需要多次训练和测试模型,以使其更准确。 以下指南涉及图像分类,但其原理与对象检测类似。