빠른 시작: Custom Vision 클라이언트 라이브러리를 사용하여 개체 검색 프로젝트 만들기
.NET용 Custom Vision 클라이언트 라이브러리를 시작합니다. 이러한 단계에 따라 패키지를 설치하고 개체 검색 모델을 빌드하기 위한 예제 코드를 사용해 보세요. 프로젝트를 만들고, 태그를 추가하고, 샘플 이미지에서 프로젝트를 학습하고, 프로젝트의 예측 엔드포인트 URL을 사용하여 프로그래밍 방식으로 테스트합니다. 자체 이미지 인식 앱을 빌드하기 위한 템플릿으로 이 예제를 사용합니다.
참고 항목
코드를 작성하지 않고 개체 감지 모델을 빌드하고 학습하려면 브라우저 기반 지침을 대신 참조하세요.
참조 설명서 | 라이브러리 소스 코드 (학습) (예측) | 패키지(NuGet) (학습) (예측) | 샘플
필수 조건
- Azure 구독 - 체험 구독 만들기
- Visual Studio IDE 또는 현재 버전의 .NET Core.
- Azure 구독이 있으면 Azure portal에서 Custom Vision 리소스를 생성하여 교육 및 예측 리소스를 생성합니다.
- 평가판 가격 책정 계층(
F0
)을 통해 서비스를 사용해보고, 나중에 프로덕션용 유료 계층으로 업그레이드할 수 있습니다.
- 평가판 가격 책정 계층(
환경 변수 만들기
이 예제에서는 애플리케이션을 실행하는 로컬 컴퓨터의 환경 변수에 자격 증명을 작성합니다.
Azure Portal로 이동합니다. 필수 조건 섹션에서 생성한 Custom Vision 리소스가 성공적으로 배포된 경우 다음 단계 아래에서 리소스로 이동 버튼을 선택합니다. 리소스 관리 아래 리소스의 키 및 엔드포인트 페이지에서 키와 엔드포인트를 찾을 수 있습니다. API 엔드포인트와 함께 학습 및 예측 리소스에 대한 키를 가져와야 합니다.
Azure Portal에 있는 예측 리소스의 속성 탭에서 리소스 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로 바꾸세요.
Important
API 키를 사용하는 경우 Azure Key Vault와 같은 다른 위치에 안전하게 저장하세요. API 키를 코드에 직접 포함하지 말고, 공개적으로 게시하지 마세요.
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 패키지 관리를 선택하여 클라이언트 라이브러리를 설치합니다. 열리는 패키지 관리자에서 찾아보기를 선택하고, 시험판 포함을 선택하고, Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training
및 Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction
을 검색합니다. 최신 버전을 선택한 다음, 설치를 선택합니다.
팁
한 번에 전체 빠른 시작 코드 파일을 보시겠습니까? GitHub에서 찾을 수 있으며 이 빠른 시작의 코드 예제를 포함합니다.
프로젝트 디렉터리에서 program.cs 파일을 열고 using
지시문을 추가합니다.
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;
}
새 Custom Vision 프로젝트 만들기
다음 메서드는 개체 검색 프로젝트를 만듭니다. 만든 프로젝트는 Custom Vision 웹 사이트에 표시됩니다. 프로젝트를 만들 때 다른 옵션을 지정하려면 CreateProject 메서드를 참조하세요(탐지기 빌드 웹 포털 가이드에 설명되어 있음).
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 } }
};
참고 항목
자체 프로젝트의 대해 지역의 좌표를 표시하는 클릭 및 끌기 유틸리티가 없는 경우 Custom Vision 웹 사이트에서 웹 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 Portal에 있는 리소스의 속성 탭에서 리소스 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/에 있음)에 태그가 적절하게 지정되는지, 검색 지역이 올바른지 확인할 수 있습니다. 이제 아무 키를 눌러 애플리케이션을 종료할 수 있습니다.
리소스 정리
고유한 개체 검색 프로젝트를 구현하려면(또는 이미지 분류 프로젝트를 시도하려면) 이 예제에서 포크/가위 검색 프로젝트를 삭제하는 것이 좋습니다. 체험 구독은 두 개의 Custom Vision 프로젝트를 허용합니다.
Custom Vision 웹 사이트에서 프로젝트로 이동하여 [내 새 프로젝트] 아래에서 휴지통을 선택합니다.
다음 단계
이제 코드에서 개체 감지 프로세스의 모든 단계를 완료했습니다. 이 샘플은 학습을 한 번만 반복하지만, 정확도를 높이기 위해 모델을 여러 차례 학습하고 테스트해야 하는 경우가 많습니다. 다음 가이드에서는 이미지 분류를 다루지만, 원칙은 개체 검색과 비슷합니다.
- Custom Vision이란?
- 이 샘플의 소스 코드는 GitHub에서 확인할 수 있습니다.
- SDK 참조 설명서
이 가이드는 Go용 Custom Vision 클라이언트 라이브러리를 사용하여 개체 감지 모델을 빌드하는 데 유용한 지침과 샘플 코드를 제공합니다. 프로젝트를 만들고, 태그를 추가하고, 프로젝트를 학습시키고, 프로젝트의 예측 엔드포인트 URL을 사용하여 프로그래밍 방식으로 테스트합니다. 자체 이미지 인식 앱을 빌드하기 위한 템플릿으로 이 예제를 사용합니다.
참고 항목
코드를 작성하지 않고 개체 감지 모델을 빌드하고 학습하려면 브라우저 기반 지침을 대신 참조하세요.
Go용 Custom Vision 클라이언트 라이브러리를 사용하여 다음을 수행합니다.
- 새 Custom Vision 프로젝트 만들기
- 프로젝트에 태그 추가
- 이미지 업로드 및 태그 지정
- 프로젝트 학습
- 현재 반복 게시
- 예측 엔드포인트 테스트
필수 조건
- Azure 구독 - 체험 구독 만들기
- Go 1.8+
- Azure 구독이 있으면 Azure portal에서 Custom Vision 리소스를 생성하여 교육 및 예측 리소스를 생성합니다.
- 평가판 가격 책정 계층(
F0
)을 통해 서비스를 사용해보고, 나중에 프로덕션용 유료 계층으로 업그레이드할 수 있습니다.
- 평가판 가격 책정 계층(
환경 변수 만들기
이 예제에서는 애플리케이션을 실행하는 로컬 컴퓨터의 환경 변수에 자격 증명을 작성합니다.
Azure Portal로 이동합니다. 필수 조건 섹션에서 생성한 Custom Vision 리소스가 성공적으로 배포된 경우 다음 단계 아래에서 리소스로 이동 버튼을 선택합니다. 리소스 관리 아래 리소스의 키 및 엔드포인트 페이지에서 키와 엔드포인트를 찾을 수 있습니다. API 엔드포인트와 함께 학습 및 예측 리소스에 대한 키를 가져와야 합니다.
Azure Portal에 있는 예측 리소스의 속성 탭에서 리소스 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로 바꾸세요.
Important
API 키를 사용하는 경우 Azure Key Vault와 같은 다른 위치에 안전하게 저장하세요. API 키를 코드에 직접 포함하지 말고, 공개적으로 게시하지 마세요.
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
환경 변수가 추가되면 콘솔 창을 포함하여 환경 변수를 읽는 실행 중인 프로그램을 다시 시작해야 할 수 있습니다.
설정
Custom Vision 클라이언트 라이브러리 설치
Go용 Custom Vision을 사용하여 이미지 분석 앱을 작성하려면 Custom Vision 서비스 클라이언트 라이브러리가 필요합니다. 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 샘플 저장소의 이미지를 사용합니다. 이 리포지토리를 개발 환경에 복제 또는 다운로드합니다. 이후 단계에 대한 폴더 위치를 기억하세요.
Custom Vision 프로젝트 만들기
원하는 프로젝트 디렉터리에 sample.go라는 새 파일을 만들고, 원하는 코드 편집기에서 이 파일을 엽니다.
새 Custom Vision Service 프로젝트를 만드는 다음 코드를 스크립트에 추가합니다.
프로젝트를 만들 때 다른 옵션을 지정하려면 CreateProject 메서드를 참조하세요(탐지기 빌드 웹 포털 가이드에 설명되어 있음).
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에서 웹 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: ®iontest[0],
Top: ®iontest[1],
Width: ®iontest[2],
Height: ®iontest[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:®ion[0],
Top:®ion[1],
Width:®ion[2],
Height:®ion[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에 있음)에 태그가 적절하게 지정되는지, 검색 지역이 올바른지 확인할 수 있습니다.
리소스 정리
고유한 개체 검색 프로젝트를 구현하려면(또는 이미지 분류 프로젝트를 시도하려면) 이 예제에서 포크/가위 검색 프로젝트를 삭제하는 것이 좋습니다. 체험 구독은 두 개의 Custom Vision 프로젝트를 허용합니다.
Custom Vision 웹 사이트에서 프로젝트로 이동하여 [내 새 프로젝트] 아래에서 휴지통을 선택합니다.
다음 단계
이제 코드에서 개체 감지 프로세스의 모든 단계를 완료했습니다. 이 샘플은 학습을 한 번만 반복하지만, 정확도를 높이기 위해 모델을 여러 차례 학습하고 테스트해야 하는 경우가 많습니다. 다음 가이드에서는 이미지 분류를 다루지만, 원칙은 개체 검색과 비슷합니다.
Java용 Custom Vision 클라이언트 라이브러리를 사용하여 개체 검색 모델을 빌드합니다. 이러한 단계에 따라 패키지를 설치하고 기본 작업을 위한 예제 코드를 사용해 봅니다. 자체 이미지 인식 앱을 빌드하기 위한 템플릿으로 이 예제를 사용합니다.
참고 항목
코드를 작성하지 않고 개체 감지 모델을 빌드하고 학습하려면 브라우저 기반 지침을 대신 참조하세요.
Java용 Custom Vision 클라이언트 라이브러리를 사용하여 다음을 수행합니다.
- 새 Custom Vision 프로젝트 만들기
- 프로젝트에 태그 추가
- 이미지 업로드 및 태그 지정
- 프로젝트 학습
- 현재 반복 게시
- 예측 엔드포인트 테스트
참조 설명서 | 라이브러리 소스 코드 (학습) (예측) | 아티팩트(Maven) (학습) (예측) | 샘플
필수 조건
- Azure 구독 - 체험 구독 만들기
- JDK(Java Development Kit)의 현재 버전
- Gradle 빌드 도구 또는 다른 종속성 관리자
- Azure 구독이 있으면 Azure portal에서 Custom Vision 리소스를 생성하여 교육 및 예측 리소스를 생성합니다.
- 평가판 가격 책정 계층(
F0
)을 통해 서비스를 사용해보고, 나중에 프로덕션용 유료 계층으로 업그레이드할 수 있습니다.
- 평가판 가격 책정 계층(
환경 변수 만들기
이 예제에서는 애플리케이션을 실행하는 로컬 컴퓨터의 환경 변수에 자격 증명을 작성합니다.
Azure Portal로 이동합니다. 필수 조건 섹션에서 생성한 Custom Vision 리소스가 성공적으로 배포된 경우 다음 단계 아래에서 리소스로 이동 버튼을 선택합니다. 리소스 관리 아래 리소스의 키 및 엔드포인트 페이지에서 키와 엔드포인트를 찾을 수 있습니다. API 엔드포인트와 함께 학습 및 예측 리소스에 대한 키를 가져와야 합니다.
Azure Portal에 있는 예측 리소스의 속성 탭에서 리소스 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로 바꾸세요.
Important
API 키를 사용하는 경우 Azure Key Vault와 같은 다른 위치에 안전하게 저장하세요. API 키를 코드에 직접 포함하지 말고, 공개적으로 게시하지 마세요.
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
명령을 실행합니다. 이 명령은 build.gradle.kts를 포함하여 런타임에 애플리케이션을 만들고 구성하는 데 사용되는 Gradle용 필수 빌드 파일을 만듭니다.
gradle init --type basic
DSL을 선택하라는 메시지가 표시되면 Kotlin을 선택합니다.
클라이언트 라이브러리 설치
build.gradle.kts를 찾고, 원하는 IDE 또는 텍스트 편집기에서 엽니다. 그런 다음, 다음 빌드 구성을 복사합니다. 이 구성은 프로젝트를 Java 애플리케이션(진입점이 CustomVisionQuickstart 클래스임)으로 정의합니다. Custom Vision 라이브러리를 가져옵니다.
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);
개체 모델
다음 클래스 및 인터페이스는 Custom Vision Java 클라이언트 라이브러리의 몇 가지 주요 기능을 처리합니다.
이름 | 설명 |
---|---|
CustomVisionTrainingClient | 이 클래스는 모델의 생성, 학습 및 게시를 처리합니다. |
CustomVisionPredictionClient | 이 클래스는 개체 검색 예측에 대한 모델의 쿼리를 처리합니다. |
ImagePrediction | 이 클래스는 단일 이미지에서 단일 개체 예측을 정의합니다. 여기에는 개체 ID 및 이름, 개체의 경계 상자 위치 및 신뢰도 점수에 대한 속성이 포함됩니다. |
코드 예제
이러한 코드 조각에서는 Java용 Custom Vision 클라이언트 라이브러리를 사용하여 다음 작업을 수행하는 방법을 보여줍니다.
클라이언트 인증
main 메서드에서 엔드포인트와 키를 사용하여 학습 및 예측 클라이언트를 인스턴스화합니다.
// Authenticate
CustomVisionTrainingClient trainClient = CustomVisionTrainingManager
.authenticate(trainingEndpoint, trainingApiKey)
.withEndpoint(trainingEndpoint);
CustomVisionPredictionClient predictor = CustomVisionPredictionManager
.authenticate(predictionEndpoint, predictionApiKey)
.withEndpoint(predictionEndpoint);
새 Custom Vision 프로젝트 만들기
다음 메서드는 개체 검색 프로젝트를 만듭니다. 생성된 프로젝트는 앞에서 방문한 Custom Vision 웹 사이트에 표시됩니다. 프로젝트를 만들 때 다른 옵션을 지정하려면 CreateProject 메서드 오버로드를 참조하세요(탐지기 빌드 웹 포털 가이드에 설명되어 있음).
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();
}
이미지 업로드 및 태그 지정
먼저 이 프로젝트에 대한 샘플 이미지를 다운로드합니다. 샘플 이미지 폴더의 내용을 로컬 디바이스에 저장합니다.
참고 항목
학습을 완료하려면 보다 광범위한 이미지 세트가 필요한가요? Microsoft Garage 프로젝트인 Trove를 사용하면 학습 목적으로 이미지 세트를 수집하고 구매할 수 있습니다. 이미지를 수집한 후에는 해당 이미지를 다운로드한 다음, 일반적인 방법으로 Custom Vision 프로젝트로 가져올 수 있습니다. 자세한 내용은 Trove 페이지를 참조하세요.
개체 검색 프로젝트의 이미지에 태그를 지정할 때 정규화된 좌표를 사용하여 태그가 지정된 각 개체의 지역을 지정해야 합니다. 다음 코드는 각 샘플 이미지를 태그가 지정된 지역과 연결합니다.
참고 항목
영역의 좌표를 표시하는 클릭 및 끌기 유틸리티를 사용하지 않는 경우 Customvision.ai에서 웹 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 });
다음 코드 블록은 프로젝트에 이미지를 추가합니다. 다운로드한 fork 및 scissors 폴더의 위치를 가리키도록 GetImage
호출의 변수를 변경해야 합니다.
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 Portal에 있는 리소스의 속성 탭에서 리소스 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 서비스 구독을 정리하고 제거하려면 리소스 또는 리소스 그룹을 삭제할 수 있습니다. 리소스 그룹을 삭제하면 해당 리소스 그룹에 연결된 다른 모든 리소스가 함께 삭제됩니다.
고유한 개체 검색 프로젝트를 구현하려면(또는 이미지 분류 프로젝트를 시도하려면) 이 예제에서 포크/가위 검색 프로젝트를 삭제하는 것이 좋습니다. 체험 구독은 두 개의 Custom Vision 프로젝트를 허용합니다.
Custom Vision 웹 사이트에서 프로젝트로 이동하여 [내 새 프로젝트] 아래에서 휴지통을 선택합니다.
다음 단계
이제 코드에서 개체 감지 프로세스의 모든 단계를 완료했습니다. 이 샘플은 학습을 한 번만 반복하지만, 정확도를 높이기 위해 모델을 여러 차례 학습하고 테스트해야 하는 경우가 많습니다. 다음 가이드에서는 이미지 분류를 다루지만, 원칙은 개체 검색과 비슷합니다.
- Custom Vision이란?
- 이 샘플의 소스 코드는 GitHub에서 확인할 수 있습니다.
이 가이드는 Node.js용 Custom Vision 클라이언트 라이브러리를 사용하여 개체 감지 모델을 빌드하는 데 유용한 지침과 샘플 코드를 제공합니다. 프로젝트를 만들고, 태그를 추가하고, 프로젝트를 학습시키고, 프로젝트의 예측 엔드포인트 URL을 사용하여 프로그래밍 방식으로 테스트합니다. 자체 이미지 인식 앱을 빌드하기 위한 템플릿으로 이 예제를 사용합니다.
참고 항목
코드를 작성하지 않고 개체 감지 모델을 빌드하고 학습하려면 브라우저 기반 지침을 대신 참조하세요.
.NET용 Custom Vision 클라이언트 라이브러리를 사용하여 다음을 수행합니다.
- 새 Custom Vision 프로젝트 만들기
- 프로젝트에 태그 추가
- 이미지 업로드 및 태그 지정
- 프로젝트 학습
- 현재 반복 게시
- 예측 엔드포인트 테스트
참조 설명서(학습)(예측) | 패키지(npm)(학습)(예측) | 샘플
필수 조건
- Azure 구독 - 체험 구독 만들기
- 현재 버전의 Node.js
- Azure 구독이 있으면 Azure portal에서 Custom Vision 리소스를 생성하여 교육 및 예측 리소스를 생성합니다.
- 평가판 가격 책정 계층(
F0
)을 통해 서비스를 사용해보고, 나중에 프로덕션용 유료 계층으로 업그레이드할 수 있습니다.
- 평가판 가격 책정 계층(
환경 변수 만들기
이 예제에서는 애플리케이션을 실행하는 로컬 컴퓨터의 환경 변수에 자격 증명을 작성합니다.
Azure Portal로 이동합니다. 필수 조건 섹션에서 생성한 Custom Vision 리소스가 성공적으로 배포된 경우 다음 단계 아래에서 리소스로 이동 버튼을 선택합니다. 리소스 관리 아래 리소스의 키 및 엔드포인트 페이지에서 키와 엔드포인트를 찾을 수 있습니다. API 엔드포인트와 함께 학습 및 예측 리소스에 대한 키를 가져와야 합니다.
Azure Portal에 있는 예측 리소스의 속성 탭에서 리소스 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로 바꾸세요.
Important
API 키를 사용하는 경우 Azure Key Vault와 같은 다른 위치에 안전하게 저장하세요. API 키를 코드에 직접 포함하지 말고, 공개적으로 게시하지 마세요.
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
package.json
파일을 사용하여 노드 애플리케이션을 만들려면 npm init
명령을 실행합니다.
npm init
클라이언트 라이브러리 설치
Node.js용 Custom Vision을 사용하여 이미지 분석 앱을 작성하려면 Custom Vision 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용 Custom Vision 클라이언트 라이브러리를 사용하여 다음 작업을 수행하는 방법을 보여줍니다.
클라이언트 인증
클라이언트 개체를 엔드포인트와 키로 인스턴스화합니다. 키를 사용하여 ApiKeyCredentials 개체를 만들고 엔드포인트와 함께 사용하여 TrainingAPIClient 및 PredictionAPIClient 개체를 만듭니다.
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);
새 Custom Vision 프로젝트 만들기
모든 Custom Vision 함수 호출을 포함하도록 새 함수를 시작합니다. 다음 코드를 추가하여 새 Custom Vision 서비스 프로젝트를 만듭니다.
(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);
Important
Azure AI 서비스 Python SDK 샘플 리포지토리를 다운로드한 위치에 따라 이미지(sampleDataRoot
) 경로를 변경해야 합니다.
참고 항목
영역의 좌표를 표시하는 클릭 및 끌기 유틸리티를 사용하지 않는 경우 Customvision.ai에서 웹 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}`);
});
그런 다음, Custom Vision 함수를 닫고 호출합니다.
})()
애플리케이션 실행
quickstart 파일의 node
명령을 사용하여 애플리케이션을 실행합니다.
node index.js
애플리케이션의 출력이 콘솔에 표시됩니다. 그런 다음 테스트 이미지(<sampleDataRoot>/Test/에 있음)에 적절하게 태그가 지정되고 검색 영역이 올바른지 확인할 수 있습니다. Custom Vision 웹 사이트로 돌아가서 새로 만든 프로젝트의 현재 상태를 살펴볼 수도 있습니다.
리소스 정리
고유한 개체 검색 프로젝트를 구현하려면(또는 이미지 분류 프로젝트를 시도하려면) 이 예제에서 포크/가위 검색 프로젝트를 삭제하는 것이 좋습니다. 체험 구독은 두 개의 Custom Vision 프로젝트를 허용합니다.
Custom Vision 웹 사이트에서 프로젝트로 이동하여 [내 새 프로젝트] 아래에서 휴지통을 선택합니다.
다음 단계
이제 코드에서 개체 감지 프로세스의 모든 단계를 완료했습니다. 이 샘플은 학습을 한 번만 반복하지만, 정확도를 높이기 위해 모델을 여러 차례 학습하고 테스트해야 하는 경우가 많습니다. 다음 가이드에서는 이미지 분류를 다루지만, 원칙은 개체 검색과 비슷합니다.
- Custom Vision이란?
- 이 샘플의 소스 코드는 GitHub에서 확인할 수 있습니다.
- SDK 참조 설명서(학습)
- SDK 참조 설명서(예측)
Python용 Custom Vision 클라이언트 라이브러리를 시작합니다. 이러한 단계에 따라 패키지를 설치하고 개체 검색 모델을 빌드하기 위한 예제 코드를 사용해 보세요. 프로젝트를 만들고, 태그를 추가하고, 프로젝트를 학습시키고, 프로젝트의 예측 엔드포인트 URL을 사용하여 프로그래밍 방식으로 테스트합니다. 자체 이미지 인식 앱을 빌드하기 위한 템플릿으로 이 예제를 사용합니다.
참고 항목
코드를 작성하지 않고 개체 감지 모델을 빌드하고 학습하려면 브라우저 기반 지침을 대신 참조하세요.
Python용 Custom Vision 클라이언트 라이브러리를 사용하여 다음을 수행합니다.
- 새 Custom Vision 프로젝트 만들기
- 프로젝트에 태그 추가
- 이미지 업로드 및 태그 지정
- 프로젝트 학습
- 현재 반복 게시
- 예측 엔드포인트 테스트
참조 설명서 | 라이브러리 소스 코드 | 패키지(PyPI) | 샘플
필수 조건
- Azure 구독 - 체험 구독 만들기
- Python 3.x
- Python 설치에 pip가 포함되어야 합니다. 명령줄에서
pip --version
을 실행하여 pip가 설치되어 있는지 확인할 수 있습니다. 최신 버전의 Python을 설치하여 pip를 받으세요.
- Python 설치에 pip가 포함되어야 합니다. 명령줄에서
- Azure 구독이 있으면 Azure Portal에서 Custom Vision 리소스를 생성하여 훈련 및 예측 리소스를 생성합니다.
- 평가판 가격 책정 계층(
F0
)을 통해 서비스를 사용해보고, 나중에 프로덕션용 유료 계층으로 업그레이드할 수 있습니다.
- 평가판 가격 책정 계층(
환경 변수 만들기
이 예제에서는 애플리케이션을 실행하는 로컬 컴퓨터의 환경 변수에 자격 증명을 작성합니다.
Azure Portal로 이동합니다. 필수 조건 섹션에서 생성한 Custom Vision 리소스가 성공적으로 배포된 경우 다음 단계 아래에서 리소스로 이동 버튼을 선택합니다. 리소스 관리 아래 리소스의 키 및 엔드포인트 페이지에서 키와 엔드포인트를 찾을 수 있습니다. API 엔드포인트와 함께 학습 및 예측 리소스에 대한 키를 가져와야 합니다.
Azure Portal에 있는 예측 리소스의 속성 탭에서 리소스 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로 바꾸세요.
Important
API 키를 사용하는 경우 Azure Key Vault와 같은 다른 위치에 안전하게 저장하세요. API 키를 코드에 직접 포함하지 말고, 공개적으로 게시하지 마세요.
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용 Custom Vision을 사용하여 이미지 분석 앱을 작성하려면 Custom Vision 클라이언트 라이브러리가 필요합니다. 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용 Custom Vision 클라이언트 라이브러리를 사용하여 다음 작업을 수행하는 방법을 보여줍니다.
클라이언트 인증
엔드포인트와 키를 사용하여 학습 및 예측 클라이언트를 인스턴스화합니다. 키를 사용하여 ApiKeyServiceClientCredentials 개체를 만들고 엔드포인트와 함께 사용하여 CustomVisionTrainingClient 및 CustomVisionPredictionClient 개체를 만듭니다.
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)
새 Custom Vision 프로젝트 만들기
새 Custom Vision Service 프로젝트를 만드는 다음 코드를 스크립트에 추가합니다.
프로젝트를 만들 때 다른 옵션을 지정하려면 create_project 메서드를 참조하세요(탐지기 빌드 웹 포털 가이드에 설명되어 있음).
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에서 웹 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에 있음)에 태그가 적절하게 지정되는지, 검색 지역이 올바른지 확인할 수 있습니다. Custom Vision 웹 사이트로 돌아가서 새로 만든 프로젝트의 현재 상태를 살펴볼 수도 있습니다.
리소스 정리
고유한 개체 검색 프로젝트를 구현하려면(또는 이미지 분류 프로젝트를 시도하려면) 이 예제에서 포크/가위 검색 프로젝트를 삭제하는 것이 좋습니다. 체험 구독은 두 개의 Custom Vision 프로젝트를 허용합니다.
Custom Vision 웹 사이트에서 프로젝트로 이동하여 [내 새 프로젝트] 아래에서 휴지통을 선택합니다.
다음 단계
이제 코드에서 개체 감지 프로세스의 모든 단계를 완료했습니다. 이 샘플은 학습을 한 번만 반복하지만, 정확도를 높이기 위해 모델을 여러 차례 학습하고 테스트해야 하는 경우가 많습니다. 다음 가이드에서는 이미지 분류를 다루지만, 원칙은 개체 검색과 비슷합니다.
- Custom Vision이란?
- 이 샘플의 소스 코드는 GitHub에서 확인할 수 있습니다.
- SDK 참조 설명서