Custom Visionにプログラムから学習データを一括登録しよう[Node.js|2019/08/02時点ではエラーで実行不可]
2019-08-06
azblob://2022/11/11/eyecatch/2019-08-06-register-learning-data-from-programs-in-custom-vision-nodejs-000.jpg

はじめに

前回は.NETでCustom Visionにプログラムから学習データを一括登録しました。
そこで、ここを参考にNode.jsでもプロジェクトの作成とプログラムから一括で学習データの登録を試みました。

データの入手

前回と同様にここから学習データなどを入手しておきます。

mkdir add_image_to_CV
cd add_image_to_CV
git clone https://github.com/Azure-Samples/cognitive-services-node-sdk-samples.git

Custom Visionでの情報の入手

右上の歯車マークを押すと、以下のようにTraining Keyなどを取得することができます。

今回必要な情報は以下です。

  • Project Id
  • Training Key
  • Training Endpoint(先頭から.microsoft.com/まで)

実行したコード

ここを参考に以下のコードを実行しました。

const util = require('util');
const fs = require("fs");
const TrainingApi = require("@azure/cognitiveservices-customvision-training");
// const visionService = require("@azure/cognitiveservices-customvision-prediction");

const setTimeoutPromise = util.promisify(setTimeout);

const trainingKey = "<training key>";
const predictionKey = "<ptrdiction key>";
const predictionResourceId = "<prediction resource id>";
const sampleDataRoot = "./cognitive-services-node-sdk-samples/Samples/customvision/images";

const endPoint = "https://japaneast.api.cognitive.microsoft.com/"

const publishIterationName = "detectModel";

const trainer = new TrainingApi.TrainingAPIClient(trainingKey, endPoint);
// const predictor = new visionService.PredictionAPIClient(predictionKey, endPoint);

process.on('unhandledRejection', console.dir);

(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");
    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`;
    console.log(forkDir)
    const forkFiles = fs.readdirSync(forkDir);
    forkFiles.forEach(file => {
        const region = new TrainingApi.TrainingAPIModels.Region();
        region.tagId = forkTag.id;
        region.left = forkImageRegions[file][0];
        region.top = forkImageRegions[file][1];
        region.width = forkImageRegions[file][2];
        region.height = forkImageRegions[file][3];

        const entry = new TrainingApi.TrainingAPIModels.ImageFileCreateEntry();
        entry.name = file;
        entry.contents = fs.readFileSync(`${forkDir}/${file}`);
        entry.regions = [region];

        const batch = new TrainingApi.TrainingAPIModels.ImageFileCreateBatch();
        batch.images = [entry];

        fileUploadPromises.push(trainer.createImagesFromFiles(sampleProject.id, batch));
    });

    const scissorsDir = `${sampleDataRoot}/Scissors`;
    const scissorsFiles = fs.readdirSync(scissorsDir);
    scissorsFiles.forEach(file => {
        const region = new TrainingApi.TrainingAPIModels.Region();
        region.tagId = scissorsTag.id;
        region.left = scissorsImageRegions[file][0];
        region.top = scissorsImageRegions[file][1];
        region.width = scissorsImageRegions[file][2];
        region.height = scissorsImageRegions[file][3];

        const entry = new TrainingApi.TrainingAPIModels.ImageFileCreateEntry();
        entry.name = file;
        entry.contents = fs.readFileSync(`${scissorsDir}/${file}`);
        entry.regions = [region];

        const batch = new TrainingApi.TrainingAPIModels.ImageFileCreateBatch();
        batch.images = [entry];

        fileUploadPromises.push(trainer.createImagesFromFiles(sampleProject.id, batch));
    });

    await Promise.all(fileUploadPromises);
})()

実行した結果は以下。
悲しいことにこのissueを見る限りSDK側の問題のようですが、まだ未対応のようです・・・。

> node .\add_train_images.js
Creating project...
Adding images...
./cognitive-services-node-sdk-samples/Samples/customvision/images/Fork
TypeError: TrainingApi.TrainingAPIModels.Region is not a constructor
    at forkFiles.forEach.file (C:\Documents\add_image_to_CV\add_train_images.js:82:24)
    at Array.forEach (<anonymous>)
    at C:\Documents\add_image_to_CV\add_train_images.js:81:15
    at process._tickCallback (internal/process/next_tick.js:68:7)

今回解決できたエラー一覧

UnhandledPromiseRejectionWarning

エラー全文は以下。

> node .\add_train_images.js
Creating project...
(node:27580) UnhandledPromiseRejectionWarning: Error: Exceeds 2 projects
    at new RestError (C:\node_modules\@azure\ms-rest-js\dist\msRest.node.js:1399:28)
    at C:\node_modules\@azure\ms-rest-js\dist\msRest.node.js:2494:37
    at process._tickCallback (internal/process/next_tick.js:68:7)
(node:27580) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3)
(node:27580) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

この場合はプログラムの18行目あたりに以下の文を追加すると、エラー文が詳しく見れるようになります。

process.on('unhandledRejection', console.dir);

BadRequestExceedProjectLimit

エラー全文は以下。

> node .\add_train_images.js
Creating project...
{ Error: Exceeds 2 projects
    at new RestError (C:\node_modules\@azure\ms-rest-js\dist\msRest.node.js:1399:28)
    at C:\node_modules\@azure\ms-rest-js\dist\msRest.node.js:2494:37
    at process._tickCallback (internal/process/next_tick.js:68:7)
  code: 'BadRequestExceedProjectLimit',
  statusCode: 400,
  request:
   WebResource {
     streamResponseBody: false,
     url:
      'https://japaneast.api.cognitive.microsoft.com/customvision/v3.0/training/projects?name=Sample%20Obj%20Detection%20Project&domainId=da2e3a8a-40a5-4171-82f4-58522f70fbc1',
     method: 'POST',
     headers: HttpHeaders { _headersMap: [Object] },
     body: undefined,
     query: undefined,
     formData: undefined,
     withCredentials: false,
     abortSignal: undefined,
     timeout: 0,
     onUploadProgress: undefined,
     onDownloadProgress: undefined,
     proxySettings: undefined,
     operationSpec:
      { httpMethod: 'POST',
        path: 'projects',
        urlParameters: [Array],
        queryParameters: [Array],
        headerParameters: [Array],
        responses: [Object],
        serializer: [Serializer] } },
  response:
   { body:
      '{"code":"BadRequestExceedProjectLimit","message":"Exceeds 2 projects"}',
     headers: HttpHeaders { _headersMap: [Object] },
     status: 400 },
  body:
   { code: 'BadRequestExceedProjectLimit',
     message: 'Exceeds 2 projects' } }

サブスクリプションの問題でこれ以上プロジェクトが作れないよというエラーなので、Custom Visionのプロジェクトページのプロジェクトにオンマウスすると出てくる、ごみ箱マークで不要なプロジェクトを削除します。

おわりに

このissueが解決したら、またチャレンジします・・・。