Bagikan melalui


HoloLens (generasi ke-1) dan Azure 302b: Visi kustom


Catatan

Tutorial Mixed Reality Academy dirancang dengan HoloLens (generasi ke-1) dan Mixed Reality Immersive Headsets dalam pikiran. Dengan demikian, kami merasa penting untuk meninggalkan tutorial ini di tempat bagi pengembang yang masih mencari panduan dalam mengembangkan untuk perangkat tersebut. Tutorial ini tidak akan diperbarui dengan toolset atau interaksi terbaru yang digunakan untuk HoloLens 2. Mereka akan dipertahankan untuk terus bekerja pada perangkat yang didukung. Akan ada serangkaian tutorial baru yang akan diposting di masa depan yang akan menunjukkan cara mengembangkan untuk HoloLens 2. Pemberitahuan ini akan diperbarui dengan tautan ke tutorial tersebut ketika diposting.


Dalam kursus ini, Anda akan mempelajari cara mengenali konten visual kustom dalam gambar yang disediakan, menggunakan kemampuan Azure Custom Vision dalam aplikasi realitas campuran.

Layanan ini akan memungkinkan Anda melatih model pembelajaran mesin menggunakan gambar objek. Anda kemudian akan menggunakan model terlatih untuk mengenali objek serupa, seperti yang disediakan oleh tangkapan kamera Microsoft HoloLens atau kamera yang terhubung ke PC Anda untuk headset imersif (VR).

hasil kursus

Azure Custom Vision adalah Microsoft Cognitive Service yang memungkinkan pengembang untuk membangun pengklasifikasi gambar kustom. Pengklasifikasi ini kemudian dapat digunakan dengan gambar baru untuk mengenali, atau mengklasifikasikan, objek dalam gambar baru tersebut. Layanan ini menyediakan portal online yang sederhana dan mudah digunakan untuk menyederhanakan proses. Untuk informasi selengkapnya, kunjungi halaman Azure Custom Vision Service.

Setelah menyelesaikan kursus ini, Anda akan memiliki aplikasi realitas campuran yang akan dapat bekerja dalam dua mode:

  • Mode Analisis: menyiapkan Custom Vision Service secara manual dengan mengunggah gambar, membuat tag, dan melatih Layanan untuk mengenali objek yang berbeda (dalam hal ini mouse dan keyboard). Anda kemudian akan membuat aplikasi HoloLens yang akan mengambil gambar menggunakan kamera, dan mencoba mengenali objek tersebut di dunia nyata.

  • Mode Pelatihan: Anda akan menerapkan kode yang akan mengaktifkan "Mode Pelatihan" di aplikasi Anda. Mode pelatihan akan memungkinkan Anda mengambil gambar menggunakan kamera HoloLens, mengunggah gambar yang diambil ke Layanan, dan melatih model visi kustom.

Kursus ini akan mengajarkan Anda cara mendapatkan hasil dari Custom Vision Service ke dalam aplikasi sampel berbasis Unity. Terserah Anda untuk menerapkan konsep ini ke aplikasi kustom yang mungkin Anda bangun.

Dukungan perangkat

Kursus HoloLens Headset imersif
MR dan Azure 302b: Visi kustom ✔️ ✔️

Catatan

Meskipun kursus ini terutama berfokus pada HoloLens, Anda juga dapat menerapkan apa yang Anda pelajari dalam kursus ini ke headset imersif (VR) Windows Mixed Reality. Karena headset imersif (VR) tidak memiliki kamera yang dapat diakses, Anda akan memerlukan kamera eksternal yang terhubung ke PC Anda. Saat mengikuti kursus, Anda akan melihat catatan tentang perubahan apa pun yang mungkin perlu Anda gunakan untuk mendukung headset imersif (VR).

Prasyarat

Catatan

Tutorial ini dirancang untuk pengembang yang memiliki pengalaman dasar dengan Unity dan C#. Perlu diketahui juga bahwa prasyarat dan instruksi tertulis dalam dokumen ini mewakili apa yang telah diuji dan diverifikasi pada saat penulisan (Juli 2018). Anda bebas menggunakan perangkat lunak terbaru, seperti yang tercantum dalam artikel instal alat , meskipun tidak boleh diasumsikan bahwa informasi dalam kursus ini akan sangat cocok dengan apa yang akan Anda temukan di perangkat lunak yang lebih baru daripada apa yang tercantum di bawah ini.

Kami merekomendasikan perangkat keras dan perangkat lunak berikut untuk kursus ini:

Sebelum memulai

  1. Untuk menghindari masalah saat membangun proyek ini, sangat disarankan agar Anda membuat proyek yang disebutkan dalam tutorial ini di folder root atau near-root (jalur folder panjang dapat menyebabkan masalah pada build-time).
  2. Siapkan dan uji HoloLens Anda. Jika Anda memerlukan dukungan untuk menyiapkan HoloLens Anda, pastikan untuk mengunjungi artikel penyiapan HoloLens.
  3. Sebaiknya lakukan Kalibrasi dan Penyetelan Sensor saat mulai mengembangkan aplikasi HoloLens baru (terkadang dapat membantu melakukan tugas tersebut untuk setiap pengguna).

Untuk bantuan tentang Kalibrasi, silakan ikuti tautan ini ke artikel Kalibrasi HoloLens.

Untuk bantuan tentang Penyetelan Sensor, silakan ikuti tautan ini ke artikel Penyetelan Sensor HoloLens.

Bab 1 - Portal Layanan Custom Vision

Untuk menggunakan Custom Vision Service di Azure, Anda harus mengonfigurasi instans Layanan agar tersedia untuk aplikasi Anda.

  1. Pertama, navigasikan ke halaman utama Custom Vision Service.

  2. Klik tombol Memulai .

    Mulai menggunakan Custom Vision Service

  3. Masuk ke Portal Layanan Custom Vision.

    Masuk ke portal

    Catatan

    Jika Anda belum memiliki akun Azure, Anda harus membuatnya. Jika Anda mengikuti tutorial ini dalam situasi ruang kelas atau lab, mintalah instruktur atau salah satu proktor untuk membantu menyiapkan akun baru Anda.

  4. Setelah Anda masuk untuk pertama kalinya, Anda akan diminta dengan panel Ketentuan Layanan . Klik pada kotak centang untuk menyetujui persyaratan. Kemudian klik saya setuju.

    Persyaratan layanan

  5. Setelah menyetujui Ketentuan, Anda akan dinavigasi ke bagian Proyek portal. Klik Proyek Baru.

    Buat proyek baru

  6. Tab akan muncul di sisi kanan, yang akan meminta Anda untuk menentukan beberapa bidang untuk proyek.

    1. Sisipkan Nama untuk proyek Anda.

    2. Sisipkan Deskripsi untuk proyek Anda (opsional).

    3. Pilih Grup Sumber Daya atau buat yang baru. Grup sumber daya menyediakan cara untuk memantau, mengontrol akses, menyediakan, dan mengelola penagihan untuk kumpulan aset Azure. Disarankan untuk menyimpan semua Layanan Azure yang terkait dengan satu proyek (misalnya seperti kursus ini) di bawah grup sumber daya umum).

    4. Atur Jenis Proyek ke Klasifikasi

    5. Atur Domain sebagai Umum.

      Mengatur domain

      Jika Anda ingin membaca selengkapnya tentang Grup Sumber Daya Azure, silakan kunjungi artikel grup sumber daya.

  7. Setelah selesai, klik Buat proyek, Anda akan diarahkan ke halaman Custom Vision Service, proyek.

Bab 2 - Melatih proyek Custom Vision Anda

Setelah berada di portal Custom Vision, tujuan utama Anda adalah melatih proyek Anda untuk mengenali objek tertentu dalam gambar. Anda memerlukan setidaknya lima (5) gambar, meskipun sepuluh (10) lebih disukai, untuk setiap objek yang ingin Anda kenali aplikasi Anda. Anda dapat menggunakan gambar yang disediakan dengan kursus ini (mouse komputer dan keyboard).

Untuk melatih proyek Custom Vision Service Anda:

  1. Klik tombol di + samping Tag.

    Tambahkan tag baru

  2. Tambahkan nama objek yang ingin Anda kenali. Klik Simpan.

    Menambahkan nama objek dan menyimpan

  3. Anda akan melihat Tag Anda telah ditambahkan (Anda mungkin perlu memuat ulang halaman Anda agar tag tersebut muncul). Klik kotak centang di samping tag baru Anda, jika belum dicentang.

    Aktifkan tag baru

  4. Klik Tambahkan Gambar di tengah halaman.

    Tambahkan gambar

  5. Klik Telusuri file lokal, dan cari, lalu pilih, gambar yang ingin Anda unggah, dengan minimal lima (5). Ingat semua gambar ini harus berisi objek yang Anda latih.

    Catatan

    Anda dapat memilih beberapa gambar pada satu waktu, untuk diunggah.

  6. Setelah Anda dapat melihat gambar di tab, pilih tag yang sesuai di kotak Tag Saya.

    Pilih tag

  7. Klik Unggah file. File akan mulai diunggah. Setelah Anda mengonfirmasi pengunggahan, klik Selesai.

    Unggah file

  8. Ulangi proses yang sama untuk membuat Tag baru bernama Keyboard dan unggah foto yang sesuai untuknya. Pastikan untuk menghapus centang Mouse setelah Anda membuat tag baru, jadi untuk menampilkan jendela Tambahkan gambar.

  9. Setelah Anda menyiapkan kedua Tag, klik Latih, dan iterasi pelatihan pertama akan mulai dibangun.

    Mengaktifkan perulangan pelatihan

  10. Setelah dibuat, Anda akan dapat melihat dua tombol yang disebut Buat default dan URL Prediksi. Klik Buat default terlebih dahulu, lalu klik URL Prediksi.

    Membuat URL default dan prediksi

    Catatan

    URL titik akhir yang disediakan dari ini, diatur ke Perulangan mana pun yang telah ditandai sebagai default. Dengan demikian, jika Nanti Anda membuat Iterasi baru dan memperbaruinya sebagai default, Anda tidak perlu mengubah kode Anda.

  11. Setelah Anda mengklik URL Prediksi, buka Notepad, dan salin dan tempel URL dan Kunci Prediksi, sehingga Anda dapat mengambilnya saat Anda membutuhkannya nanti dalam kode.

    Salin dan tempel URL dan Kunci Prediksi

  12. Klik Cog di kanan atas layar.

    Klik ikon cog untuk membuka pengaturan

  13. Salin Kunci Pelatihan dan tempelkan ke Notepad, untuk digunakan nanti.

    Salin kunci pelatihan

  14. Salin juga Id Proyek Anda, dan tempelkan juga ke file Notepad Anda, untuk digunakan nanti.

    Salin id proyek

Bab 3 - Menyiapkan proyek Unity

Berikut ini adalah pengaturan khas untuk mengembangkan dengan realitas campuran, dan dengan demikian, adalah templat yang baik untuk proyek lain.

  1. Buka Unity dan klik Baru.

    Membuat proyek Unity baru

  2. Anda sekarang perlu memberikan nama proyek Unity. Sisipkan AzureCustomVision. Pastikan templat proyek diatur ke 3D. Atur Lokasi ke tempat yang sesuai untuk Anda (ingat, lebih dekat ke direktori akar lebih baik). Lalu, klik Buat proyek.

    Mengonfigurasi pengaturan proyek

  3. Dengan Unity terbuka, ada baiknya memeriksa Editor Skrip default diatur ke Visual Studio. Buka Edit>Preferensi lalu dari jendela baru, navigasikan ke Alat Eksternal. Ubah Editor Skrip Eksternal ke Visual Studio 2017. Tutup jendela Preferensi .

    Mengonfigurasi alat eksternal

  4. Selanjutnya, buka Pengaturan Build File > dan pilih Platform Windows Universal, lalu klik tombol Beralih Platform untuk menerapkan pilihan Anda.

    Mengonfigurasi pengaturan build

  5. Saat masih dalam Pengaturan Penyusunan File > dan pastikan bahwa:

    1. Perangkat Target diatur ke HoloLens

      Untuk headset imersif, atur Perangkat Target ke Perangkat Apa Pun.

    2. Jenis Build diatur ke D3D

    3. SDK diatur ke Terbaru diinstal

    4. Versi Visual Studio diatur ke Terbaru diinstal

    5. Build and Run diatur ke Komputer Lokal

    6. Simpan adegan dan tambahkan ke build.

      1. Lakukan ini dengan memilih Tambahkan Adegan Terbuka. Jendela penyimpanan akan muncul.

        Menambahkan adegan terbuka ke daftar build

      2. Buat folder baru untuk ini, dan adegan di masa mendatang, lalu pilih tombol Folder baru, untuk membuat folder baru, beri nama Adegan.

        Membuat folder adegan baru

      3. Buka folder Adegan yang baru dibuat, lalu di bidang Nama file: teks, ketik CustomVisionScene, lalu klik Simpan.

        Beri nama file adegan baru

        Ketahuilah, Anda harus menyimpan adegan Unity Anda dalam folder Aset , karena harus dikaitkan dengan proyek Unity. Membuat folder adegan (dan folder serupa lainnya) adalah cara umum untuk menyusun proyek Unity.

    7. Pengaturan yang tersisa, di Pengaturan Build, harus dibiarkan sebagai default untuk saat ini.

      Pengaturan build default

  6. Di jendela Pengaturan Build, klik tombol Pengaturan Pemutar, ini akan membuka panel terkait di ruang tempat Pemeriksa berada.

  7. Di panel ini, beberapa pengaturan perlu diverifikasi:

    1. Di tab Pengaturan Lainnya:

      1. Versi Runtime Pembuatan Skrip harus Eksperimental (Setara.NET 4.6), yang akan memicu kebutuhan untuk memulai ulang Editor.

      2. Scripting Backend harus .NET

      3. Tingkat Kompatibilitas API harus .NET 4.6

      Mengatur compantiblity API

    2. Dalam tab Pengaturan Penerbitan, di bawah Kemampuan, periksa:

      1. InternetClient

      2. Webcam

      3. Mikrofon

      Mengonfigurasi pengaturan penerbitan

    3. Selanjutnya di bawah panel, di Pengaturan XR (ditemukan di bawah Pengaturan Penerbitan), centang Realitas Virtual yang Didukung, pastikan Windows Mixed Reality SDK ditambahkan.

    Mengonfigurasi pengaturan XR

  8. Kembali ke Pengaturan Build Proyek Unity C# tidak lagi berwarna abu-abu; centang kotak di samping ini.

  9. Tutup jendela Pengaturan Build.

  10. Simpan Adegan dan proyek Anda (FILE > SAVE SCENE / FILE > SAVE PROJECT).

Bab 4 - Mengimpor NEWtonsoft DLL di Unity

Penting

Jika Anda ingin melewati komponen Unity Siapkan kursus ini, dan lanjutkan langsung ke dalam kode, jangan ragu untuk mengunduh Paket Azure-MR-302b.unity ini, impor ke proyek Anda sebagai Paket Kustom, lalu lanjutkan dari Bab 6.

Kursus ini memerlukan penggunaan pustaka Newtonsoft , yang dapat Anda tambahkan sebagai DLL ke aset Anda. Paket yang berisi pustaka ini dapat diunduh dari Tautan ini. Untuk mengimpor pustaka Newtonsoft ke dalam proyek Anda, gunakan Paket Unity yang disertakan dengan kursus ini.

  1. Tambahkan .unitypackage ke Unity dengan menggunakan opsi menu Paket KustomPaket>ImporAset.>

  2. Dalam kotak Impor Paket Unity yang muncul, pastikan semuanya di bawah (dan termasuk) Plugin dipilih.

    Impor semua item paket

  3. Klik tombol Impor untuk menambahkan item ke proyek Anda.

  4. Buka folder Newtonsoft di bawah Plugin dalam tampilan proyek dan pilih plugin Newtonsoft.Json.

    Pilih plugin Newtonsoft

  5. Dengan plugin Newtonsoft.Json dipilih, pastikan platform apa pun tidak dicentang, lalu pastikan bahwa WSAPlayer juga tidak dicentang, lalu klik Terapkan. Ini hanya untuk mengonfirmasi bahwa file dikonfigurasi dengan benar.

    Mengonfigurasi plugin Newtonsoft

    Catatan

    Menandai plugin ini mengonfigurasinya untuk hanya digunakan di Editor Unity. Ada set yang berbeda di folder WSA yang akan digunakan setelah proyek diekspor dari Unity.

  6. Selanjutnya, Anda perlu membuka folder WSA , di dalam folder Newtonsoft . Anda akan melihat salinan file yang sama yang baru saja Anda konfigurasikan. Pilih file, lalu di pemeriksa, pastikan bahwa

    • Platform apa pun tidak dicentang
    • hanya WSAPlayer yang diperiksa
    • Proses dont diperiksa

    Mengonfigurasi pengaturan platform plugin Newtonsoft

Bab 5 - Penyiapan kamera

  1. Di Panel Hierarki, pilih Kamera Utama.

  2. Setelah dipilih, Anda akan dapat melihat semua komponen Kamera Utama di Panel Inspektur.

    1. Objek kamera harus bernama Kamera Utama (perhatikan ejaan!)

    2. Tag Kamera Utama harus diatur ke MainCamera (perhatikan ejaan!)

    3. Pastikan Posisi Transformasi diatur ke 0, 0, 0

    4. Atur Hapus Bendera ke Warna Solid (abaikan ini untuk headset imersif).

    5. Atur Warna Latar Belakang Komponen kamera ke Hitam, Alpha 0 (Kode Hex: #00000000) (abaikan ini untuk headset imersif).

    Mengonfigurasi properti komponen Kamera

Bab 6 - Buat kelas CustomVisionAnalyser.

Pada titik ini Anda siap untuk menulis beberapa kode.

Anda akan mulai dengan kelas CustomVisionAnalyser .

Catatan

Panggilan ke Custom Vision Service yang dilakukan dalam kode yang ditunjukkan di bawah ini dilakukan menggunakan Custom Vision REST API. Dengan menggunakan ini, Anda akan melihat cara mengimplementasikan dan menggunakan API ini (berguna untuk memahami cara mengimplementasikan sesuatu yang serupa sendiri). Ketahuilah, bahwa Microsoft menawarkan Custom Vision Service SDK yang juga dapat digunakan untuk melakukan panggilan ke Layanan. Untuk informasi selengkapnya, kunjungi artikel Custom Vision Service SDK .

Kelas ini bertanggung jawab untuk:

  • Memuat gambar terbaru yang diambil sebagai array byte.

  • Mengirim array byte ke instans Azure Custom Vision Service Anda untuk analisis.

  • Menerima respons sebagai string JSON.

  • Mendeserialisasi respons dan meneruskan Prediksi yang dihasilkan ke kelas SceneOrganiser, yang akan mengurus bagaimana respons harus ditampilkan.

Untuk membuat kelas ini:

  1. Klik kanan di Folder Aset yang terletak di Panel Proyek, lalu klik Buat > Folder. Panggil folder Skrip.

    Membuat folder skrip

  2. Klik dua kali pada folder yang baru saja dibuat, untuk membukanya.

  3. Klik kanan di dalam folder, lalu klik Buat>Skrip C#. Beri nama skrip CustomVisionAnalyser.

  4. Klik dua kali pada skrip CustomVisionAnalyser baru untuk membukanya dengan Visual Studio.

  5. Perbarui namespace di bagian atas file Anda agar sesuai dengan yang berikut ini:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  6. Di kelas CustomVisionAnalyser, tambahkan variabel berikut:

        /// <summary>
        /// Unique instance of this class
        /// </summary>
        public static CustomVisionAnalyser Instance;
    
        /// <summary>
        /// Insert your Prediction Key here
        /// </summary>
        private string predictionKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your prediction endpoint here
        /// </summary>
        private string predictionEndpoint = "Insert your prediction endpoint here";
    
        /// <summary>
        /// Byte array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Catatan

    Pastikan Anda memasukkan Kunci Prediksi ke dalam variabel predictionKey dan Titik Akhir Prediksi Anda ke dalam variabel predictionEndpoint. Anda menyalinnya ke Notepad sebelumnya dalam kursus.

  7. Kode untuk Awake() sekarang perlu ditambahkan untuk menginisialisasi variabel Instans:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Hapus metode Start() dan Update().

  9. Selanjutnya, tambahkan coroutine (dengan metode GetImageAsByteArray() statis di bawahnya), yang akan mendapatkan hasil analisis gambar yang diambil oleh kelas ImageCapture.

    Catatan

    Dalam coroutine AnalyseImageCapture, ada panggilan ke kelas SceneOrganiser yang belum Anda buat. Oleh karena itu, biarkan baris tersebut dikomentari untuk saat ini.

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            WWWForm webForm = new WWWForm();
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                // The response will be in JSON format, therefore it needs to be deserialized    
    
                // The following lines refers to a class that you will build in later Chapters
                // Wait until then to uncomment these lines
    
                //AnalysisObject analysisObject = new AnalysisObject();
                //analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
                //SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  10. Pastikan untuk menyimpan perubahan Anda di Visual Studio sebelum kembali ke Unity.

Bab 7 - Buat kelas CustomVisionObjects

Kelas yang akan Anda buat sekarang adalah kelas CustomVisionObjects .

Skrip ini berisi sejumlah objek yang digunakan oleh kelas lain untuk menserialisasikan dan mendeserialisasi panggilan yang dilakukan ke Custom Vision Service.

Peringatan

Penting bagi Anda untuk mencatat titik akhir yang disediakan Custom Vision Service kepada Anda, karena struktur JSON di bawah ini telah disiapkan untuk bekerja dengan Prediksi Visi Kustom v2.0. Jika Anda memiliki versi yang berbeda, Anda mungkin perlu memperbarui struktur di bawah ini.

Untuk membuat kelas ini:

  1. Klik kanan di dalam folder Skrip, lalu klik Buat>Skrip C#. Panggil skrip CustomVisionObjects.

  2. Klik dua kali pada skrip CustomVisionObjects baru untuk membukanya dengan Visual Studio.

  3. Tambahkan namespace berikut ke bagian atas file:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Hapus metode Start() dan Update() di dalam kelas CustomVisionObjects; kelas ini sekarang harus kosong.

  5. Tambahkan kelas berikut di luar kelas CustomVisionObjects. Objek ini digunakan oleh pustaka Newtonsoft untuk menserialisasikan dan mendeserialisasi data respons:

    // The objects contained in this script represent the deserialized version
    // of the objects used by this application 
    
    /// <summary>
    /// Web request object for image data
    /// </summary>
    class MultipartObject : IMultipartFormSection
    {
        public string sectionName { get; set; }
    
        public byte[] sectionData { get; set; }
    
        public string fileName { get; set; }
    
        public string contentType { get; set; }
    }
    
    /// <summary>
    /// JSON of all Tags existing within the project
    /// contains the list of Tags
    /// </summary> 
    public class Tags_RootObject
    {
        public List<TagOfProject> Tags { get; set; }
        public int TotalTaggedImages { get; set; }
        public int TotalUntaggedImages { get; set; }
    }
    
    public class TagOfProject
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int ImageCount { get; set; }
    }
    
    /// <summary>
    /// JSON of Tag to associate to an image
    /// Contains a list of hosting the tags,
    /// since multiple tags can be associated with one image
    /// </summary> 
    public class Tag_RootObject
    {
        public List<Tag> Tags { get; set; }
    }
    
    public class Tag
    {
        public string ImageId { get; set; }
        public string TagId { get; set; }
    }
    
    /// <summary>
    /// JSON of Images submitted
    /// Contains objects that host detailed information about one or more images
    /// </summary> 
    public class ImageRootObject
    {
        public bool IsBatchSuccessful { get; set; }
        public List<SubmittedImage> Images { get; set; }
    }
    
    public class SubmittedImage
    {
        public string SourceUrl { get; set; }
        public string Status { get; set; }
        public ImageObject Image { get; set; }
    }
    
    public class ImageObject
    {
        public string Id { get; set; }
        public DateTime Created { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public string ImageUri { get; set; }
        public string ThumbnailUri { get; set; }
    }
    
    /// <summary>
    /// JSON of Service Iteration
    /// </summary> 
    public class Iteration
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public bool IsDefault { get; set; }
        public string Status { get; set; }
        public string Created { get; set; }
        public string LastModified { get; set; }
        public string TrainedAt { get; set; }
        public string ProjectId { get; set; }
        public bool Exportable { get; set; }
        public string DomainId { get; set; }
    }
    
    /// <summary>
    /// Predictions received by the Service after submitting an image for analysis
    /// </summary> 
    [Serializable]
    public class AnalysisObject
    {
        public List<Prediction> Predictions { get; set; }
    }
    
    [Serializable]
    public class Prediction
    {
        public string TagName { get; set; }
        public double Probability { get; set; }
    }
    

Bab 8 - Buat kelas VoiceRecognizer

Kelas ini akan mengenali input suara dari pengguna.

Untuk membuat kelas ini:

  1. Klik kanan di dalam folder Skrip, lalu klik Buat>Skrip C#. Panggil skrip VoiceRecognizer.

  2. Klik dua kali pada skrip VoiceRecognizer baru untuk membukanya dengan Visual Studio.

  3. Tambahkan namespace berikut di atas kelas VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Kemudian tambahkan variabel berikut di dalam kelas VoiceRecognizer, di atas metode Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static VoiceRecognizer Instance;
    
        /// <summary>
        /// Recognizer class for voice recognition
        /// </summary>
        internal KeywordRecognizer keywordRecognizer;
    
        /// <summary>
        /// List of Keywords registered
        /// </summary>
        private Dictionary<string, Action> _keywords = new Dictionary<string, Action>();
    
  5. Tambahkan metode Awake() dan Start(), yang terakhir akan menyiapkan kata kunci pengguna untuk dikenali saat mengaitkan tag ke gambar:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start ()
        {
    
            Array tagsArray = Enum.GetValues(typeof(CustomVisionTrainer.Tags));
    
            foreach (object tagWord in tagsArray)
            {
                _keywords.Add(tagWord.ToString(), () =>
                {
                    // When a word is recognized, the following line will be called
                    CustomVisionTrainer.Instance.VerifyTag(tagWord.ToString());
                });
            }
    
            _keywords.Add("Discard", () =>
            {
                // When a word is recognized, the following line will be called
                // The user does not want to submit the image
                // therefore ignore and discard the process
                ImageCapture.Instance.ResetImageCapture();
                keywordRecognizer.Stop();
            });
    
            //Create the keyword recognizer 
            keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray());
    
            // Register for the OnPhraseRecognized event 
            keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
        }
    
  6. Hapus metode Update().

  7. Tambahkan handler berikut, yang disebut setiap kali input suara dikenali:

        /// <summary>
        /// Handler called when a word is recognized
        /// </summary>
        private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
        {
            Action keywordAction;
            // if the keyword recognized is in our dictionary, call that Action.
            if (_keywords.TryGetValue(args.text, out keywordAction))
            {
                keywordAction.Invoke();
            }
        }
    
  8. Pastikan untuk menyimpan perubahan Anda di Visual Studio sebelum kembali ke Unity.

Catatan

Jangan khawatir tentang kode yang mungkin tampak memiliki kesalahan, karena Anda akan segera memberikan kelas lebih lanjut, yang akan memperbaikinya.

Bab 9 - Buat kelas CustomVisionTrainer

Kelas ini akan menautkan serangkaian panggilan web untuk melatih Custom Vision Service. Setiap panggilan akan dijelaskan secara rinci tepat di atas kode.

Untuk membuat kelas ini:

  1. Klik kanan di dalam folder Skrip, lalu klik Buat>Skrip C#. Panggil skrip CustomVisionTrainer.

  2. Klik dua kali pada skrip CustomVisionTrainer baru untuk membukanya dengan Visual Studio.

  3. Tambahkan namespace berikut di atas kelas CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Kemudian tambahkan variabel berikut di dalam kelas CustomVisionTrainer, di atas metode Start().

    Catatan

    URL pelatihan yang digunakan di sini disediakan dalam dokumentasi Custom Vision Training 1.2 , dan memiliki struktur: https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Untuk informasi selengkapnya, kunjungi API referensi Custom Vision Training v1.2.

    Peringatan

    Penting bagi Anda untuk mencatat titik akhir bahwa Custom Vision Service menyediakan Anda untuk mode pelatihan, karena struktur JSON yang digunakan (dalam kelas CustomVisionObjects) telah disiapkan untuk bekerja dengan Pelatihan Visi Kustom v1.2. Jika Anda memiliki versi yang berbeda, Anda mungkin perlu memperbarui struktur Objek .

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static CustomVisionTrainer Instance;
    
        /// <summary>
        /// Custom Vision Service URL root
        /// </summary>
        private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/";
    
        /// <summary>
        /// Insert your prediction key here
        /// </summary>
        private string trainingKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your Project Id here
        /// </summary>
        private string projectId = "- Insert your Project Id here -";
    
        /// <summary>
        /// Byte array of the image to submit for analysis
        /// </summary>
        internal byte[] imageBytes;
    
        /// <summary>
        /// The Tags accepted
        /// </summary>
        internal enum Tags {Mouse, Keyboard}
    
        /// <summary>
        /// The UI displaying the training Chapters
        /// </summary>
        private TextMesh trainingUI_TextMesh;
    

    Penting

    Pastikan Anda menambahkan nilai Kunci Layanan (Kunci Pelatihan) dan nilai Id Proyek, yang Anda catat sebelumnya; ini adalah nilai yang Anda kumpulkan dari portal sebelumnya dalam kursus (Bab 2, langkah 10 dan seterusnya).

  5. Tambahkan metode Start() dan Awake() berikut. Metode tersebut dipanggil pada inisialisasi dan berisi panggilan untuk menyiapkan UI:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        private void Start()
        { 
            trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false);
        }
    
  6. Hapus metode Update(). Kelas ini tidak akan membutuhkannya.

  7. Tambahkan metode RequestTagSelection(). Metode ini adalah yang pertama dipanggil ketika gambar telah diambil dan disimpan di perangkat dan sekarang siap untuk dikirimkan ke Custom Vision Service, untuk melatihnya. Metode ini menampilkan, di antarmuka pengguna pelatihan, sekumpulan kata kunci yang dapat digunakan pengguna untuk menandai gambar yang telah diambil. Ini juga memperingatkan kelas VoiceRecognizer untuk mulai mendengarkan pengguna untuk input suara.

        internal void RequestTagSelection()
        {
            trainingUI_TextMesh.gameObject.SetActive(true);
            trainingUI_TextMesh.text = $" \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard";
    
            VoiceRecognizer.Instance.keywordRecognizer.Start();
        }
    
  8. Tambahkan metode VerifyTag(). Metode ini akan menerima input suara yang dikenali oleh kelas VoiceRecognizer dan memverifikasi validitasnya, lalu memulai proses pelatihan.

        /// <summary>
        /// Verify voice input against stored tags.
        /// If positive, it will begin the Service training process.
        /// </summary>
        internal void VerifyTag(string spokenTag)
        {
            if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString())
            {
                trainingUI_TextMesh.text = $"Tag chosen: {spokenTag}";
                VoiceRecognizer.Instance.keywordRecognizer.Stop();
                StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag));
            }
        }
    
  9. Tambahkan metode SubmitImageForTraining(). Metode ini akan memulai proses pelatihan Custom Vision Service. Langkah pertama adalah mengambil Id Tag dari Layanan yang terkait dengan input ucapan yang divalidasi dari pengguna. Id Tag kemudian akan diunggah bersama dengan gambar.

        /// <summary>
        /// Call the Custom Vision Service to submit the image.
        /// </summary>
        public IEnumerator SubmitImageForTraining(string imagePath, string tag)
        {
            yield return new WaitForSeconds(2);
            trainingUI_TextMesh.text = $"Submitting Image \nwith tag: {tag} \nto Custom Vision Service";
            string imageId = string.Empty;
            string tagId = string.Empty;
    
            // Retrieving the Tag Id relative to the voice input
            string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId);
            using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
    
                Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse);
    
                foreach (TagOfProject tOP in tagRootObject.Tags)
                {
                    if (tOP.Name == tag)
                    {
                        tagId = tOP.Id;
                    }             
                }
            }
    
            // Creating the image object to send for training
            List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>();
            MultipartObject multipartObject = new MultipartObject();
            multipartObject.contentType = "application/octet-stream";
            multipartObject.fileName = "";
            multipartObject.sectionData = GetImageAsByteArray(imagePath);
            multipartList.Add(multipartObject);
    
            string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);           
    
                //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                www.SetRequestHeader("Training-Key", trainingKey);
    
                // The upload handler will help uploading the byte array with the request
                www.uploadHandler = new UploadHandlerRaw(imageBytes);
    
                // The download handler will help receiving the analysis from Azure
                www.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse);
                imageId = m.Images[0].Image.Id;
            }
            trainingUI_TextMesh.text = "Image uploaded";
            StartCoroutine(TrainCustomVisionProject());
        }
    
  10. Tambahkan metode TrainCustomVisionProject(). Setelah gambar dikirimkan dan ditandai, metode ini akan dipanggil. Ini akan membuat Iterasi baru yang akan dilatih dengan semua gambar sebelumnya yang dikirimkan ke Layanan ditambah gambar yang baru saja diunggah. Setelah pelatihan selesai, metode ini akan memanggil metode untuk mengatur Iterasi yang baru dibuat sebagai Default, sehingga titik akhir yang Anda gunakan untuk analisis adalah iterasi terlatih terbaru.

        /// <summary>
        /// Call the Custom Vision Service to train the Service.
        /// It will generate a new Iteration in the Service
        /// </summary>
        public IEnumerator TrainCustomVisionProject()
        {
            yield return new WaitForSeconds(2);
    
            trainingUI_TextMesh.text = "Training Custom Vision Service";
    
            WWWForm webForm = new WWWForm();
    
            string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
                Debug.Log($"Training - JSON Response: {jsonResponse}");
    
                // A new iteration that has just been created and trained
                Iteration iteration = new Iteration();
                iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse);
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Custom Vision Trained";
    
                    // Since the Service has a limited number of iterations available,
                    // we need to set the last trained iteration as default
                    // and delete all the iterations you dont need anymore
                    StartCoroutine(SetDefaultIteration(iteration)); 
                }
            }
        }
    
  11. Tambahkan metode SetDefaultIteration(). Metode ini akan mengatur iterasi yang dibuat sebelumnya dan dilatih sebagai Default. Setelah selesai, metode ini harus menghapus iterasi sebelumnya yang ada di Layanan. Pada saat penulisan kursus ini, ada batas maksimum sepuluh (10) iterasi yang diizinkan untuk ada pada saat yang sama dalam Layanan.

        /// <summary>
        /// Set the newly created iteration as Default
        /// </summary>
        private IEnumerator SetDefaultIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
            trainingUI_TextMesh.text = "Setting default iteration";
    
            // Set the last trained iteration to default
            iteration.IsDefault = true;
    
            // Convert the iteration object as JSON
            string iterationAsJson = JsonConvert.SerializeObject(iteration);
            byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson);
    
            string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}", 
                                                            url, projectId, iteration.Id);
    
            using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes))
            {
                www.method = "PATCH";
                www.SetRequestHeader("Training-Key", trainingKey);
                www.SetRequestHeader("Content-Type", "application/json");
                www.downloadHandler = new DownloadHandlerBuffer();
    
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration";
                    StartCoroutine(DeletePreviousIteration(iteration));
                }
            }
        }
    
  12. Tambahkan metode DeletePreviousIteration(). Metode ini akan menemukan dan menghapus iterasi non-default sebelumnya:

        /// <summary>
        /// Delete the previous non-default iteration.
        /// </summary>
        public IEnumerator DeletePreviousIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
    
            trainingUI_TextMesh.text = "Deleting Unused \nIteration";
    
            string iterationToDeleteId = string.Empty;
    
            string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                // The iteration that has just been trained
                List<Iteration> iterationsList = new List<Iteration>();
                iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse);
    
                foreach (Iteration i in iterationsList)
                {
                    if (i.IsDefault != true)
                    {
                        Debug.Log($"Cleaning - Deleting iteration: {i.Name}, {i.Id}");
                        iterationToDeleteId = i.Id;
                        break;
                    }
                }
            }
    
            string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId);
    
            using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint))
            {
                www2.SetRequestHeader("Training-Key", trainingKey);
                www2.downloadHandler = new DownloadHandlerBuffer();
                yield return www2.SendWebRequest();
                string jsonResponse = www2.downloadHandler.text;
    
                trainingUI_TextMesh.text = "Iteration Deleted";
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "Ready for next \ncapture";
    
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "";
                ImageCapture.Instance.ResetImageCapture();
            }
        }
    
  13. Metode terakhir yang ditambahkan di kelas ini adalah metode GetImageAsByteArray(), yang digunakan pada panggilan web untuk mengonversi gambar yang diambil menjadi array byte.

        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
            BinaryReader binaryReader = new BinaryReader(fileStream);
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  14. Pastikan untuk menyimpan perubahan Anda di Visual Studio sebelum kembali ke Unity.

Bab 10 - Buat kelas SceneOrganiser

Kelas ini akan:

  • Buat objek Kursor untuk dilampirkan ke Kamera Utama.

  • Buat objek Label yang akan muncul saat Layanan mengenali objek dunia nyata.

  • Siapkan Kamera Utama dengan melampirkan komponen yang sesuai ke dalamnya.

  • Saat dalam Mode Analisis, munculkan Label pada runtime, di ruang dunia yang sesuai relatif terhadap posisi Kamera Utama, dan menampilkan data yang diterima dari Custom Vision Service.

  • Ketika dalam Mode Pelatihan, munculkan UI yang akan menampilkan berbagai tahapan proses pelatihan.

Untuk membuat kelas ini:

  1. Klik kanan di dalam folder Skrip, lalu klik Buat>Skrip C#. Beri nama skrip SceneOrganiser.

  2. Klik dua kali pada skrip SceneOrganiser baru untuk membukanya dengan Visual Studio.

  3. Anda hanya akan memerlukan satu namespace layanan, menghapus yang lain dari atas kelas SceneOrganiser :

    using UnityEngine;
    
  4. Kemudian tambahkan variabel berikut di dalam kelas SceneOrganiser, di atas metode Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SceneOrganiser Instance;
    
        /// <summary>
        /// The cursor object attached to the camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        internal GameObject label;
    
        /// <summary>
        /// Object providing the current status of the camera.
        /// </summary>
        internal TextMesh cameraStatusIndicator;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.5f;
    
  5. Hapus metode Start() dan Update().

  6. Tepat di bawah variabel, tambahkan metode Awake(), yang akan menginisialisasi kelas dan menyiapkan adegan.

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            // Use this class instance as singleton
            Instance = this;
    
            // Add the ImageCapture class to this GameObject
            gameObject.AddComponent<ImageCapture>();
    
            // Add the CustomVisionAnalyser class to this GameObject
            gameObject.AddComponent<CustomVisionAnalyser>();
    
            // Add the CustomVisionTrainer class to this GameObject
            gameObject.AddComponent<CustomVisionTrainer>();
    
            // Add the VoiceRecogniser class to this GameObject
            gameObject.AddComponent<VoiceRecognizer>();
    
            // Add the CustomVisionObjects class to this GameObject
            gameObject.AddComponent<CustomVisionObjects>();
    
            // Create the camera Cursor
            cursor = CreateCameraCursor();
    
            // Load the label prefab as reference
            label = CreateLabel();
    
            // Create the camera status indicator label, and place it above where predictions
            // and training UI will appear.
            cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true);
    
            // Set camera status indicator to loading.
            SetCameraStatus("Loading");
        }
    
  7. Sekarang tambahkan metode CreateCameraCursor() yang membuat dan memosisikan kursor Kamera Utama, dan metode CreateLabel(), yang membuat objek Label Analisis.

        /// <summary>
        /// Spawns cursor for the Main Camera
        /// </summary>
        private GameObject CreateCameraCursor()
        {
            // Create a sphere as new cursor
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    
            // Attach it to the camera
            newCursor.transform.parent = gameObject.transform;
    
            // Resize the new cursor
            newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);
    
            // Move it to the correct position
            newCursor.transform.localPosition = new Vector3(0, 0, 4);
    
            // Set the cursor color to red
            newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse"));
            newCursor.GetComponent<Renderer>().material.color = Color.green;
    
            return newCursor;
        }
    
        /// <summary>
        /// Create the analysis label object
        /// </summary>
        private GameObject CreateLabel()
        {
            // Create a sphere as new cursor
            GameObject newLabel = new GameObject();
    
            // Resize the new cursor
            newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
    
            // Creating the text of the label
            TextMesh t = newLabel.AddComponent<TextMesh>();
            t.anchor = TextAnchor.MiddleCenter;
            t.alignment = TextAlignment.Center;
            t.fontSize = 50;
            t.text = "";
    
            return newLabel;
        }
    
  8. Tambahkan metode SetCameraStatus(), yang akan menangani pesan yang ditujukan untuk jala teks yang memberikan status kamera.

        /// <summary>
        /// Set the camera status to a provided string. Will be coloured if it matches a keyword.
        /// </summary>
        /// <param name="statusText">Input string</param>
        public void SetCameraStatus(string statusText)
        {
            if (string.IsNullOrEmpty(statusText) == false)
            {
                string message = "white";
    
                switch (statusText.ToLower())
                {
                    case "loading":
                        message = "yellow";
                        break;
    
                    case "ready":
                        message = "green";
                        break;
    
                    case "uploading image":
                        message = "red";
                        break;
    
                    case "looping capture":
                        message = "yellow";
                        break;
    
                    case "analysis":
                        message = "red";
                        break;
                }
    
                cameraStatusIndicator.GetComponent<TextMesh>().text = $"Camera Status:\n<color={message}>{statusText}..</color>";
            }
        }
    
  9. Tambahkan metode PlaceAnalysisLabel() dan SetTagsToLastLabel(), yang akan menelurkan dan menampilkan data dari Custom Vision Service ke dalam adegan.

        /// <summary>
        /// Instantiate a label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
        }
    
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void SetTagsToLastLabel(AnalysisObject analysisObject)
        {
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
    
            if (analysisObject.Predictions != null)
            {
                foreach (Prediction p in analysisObject.Predictions)
                {
                    if (p.Probability > 0.02)
                    {
                        lastLabelPlacedText.text += $"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}";
                        Debug.Log($"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}");
                    }
                }
            }
        }
    
  10. Terakhir, tambahkan metode CreateTrainingUI(), yang akan menelurkan UI yang menampilkan beberapa tahap proses pelatihan ketika aplikasi berada dalam Mode Pelatihan. Metode ini juga akan dimankan untuk membuat objek status kamera.

        /// <summary>
        /// Create a 3D Text Mesh in scene, with various parameters.
        /// </summary>
        /// <param name="name">name of object</param>
        /// <param name="scale">scale of object (i.e. 0.04f)</param>
        /// <param name="yPos">height above the cursor (i.e. 0.3f</param>
        /// <param name="zPos">distance from the camera</param>
        /// <param name="setActive">whether the text mesh should be visible when it has been created</param>
        /// <returns>Returns a 3D text mesh within the scene</returns>
        internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive)
        {
            GameObject display = new GameObject(name, typeof(TextMesh));
            display.transform.parent = Camera.main.transform;
            display.transform.localPosition = new Vector3(0, yPos, zPos);
            display.SetActive(setActive);
            display.transform.localScale = new Vector3(scale, scale, scale);
            display.transform.rotation = new Quaternion();
            TextMesh textMesh = display.GetComponent<TextMesh>();
            textMesh.anchor = TextAnchor.MiddleCenter;
            textMesh.alignment = TextAlignment.Center;
            return textMesh;
        }
    
  11. Pastikan untuk menyimpan perubahan Anda di Visual Studio sebelum kembali ke Unity.

Penting

Sebelum melanjutkan, buka kelas CustomVisionAnalyser, dan dalam metode AnalyseLastImageCaptured(), batalkan komentar baris berikut:

  AnalysisObject analysisObject = new AnalysisObject();
  analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
  SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);

Bab 11 - Buat kelas ImageCapture

Kelas berikutnya yang akan Anda buat adalah kelas ImageCapture .

Kelas ini bertanggung jawab untuk:

  • Menangkap gambar menggunakan kamera HoloLens dan menyimpannya di Folder Aplikasi .

  • Menangani gerakan Ketuk dari pengguna.

  • Mempertahankan nilai Enum yang menentukan apakah aplikasi akan berjalan dalam mode Analisis atau Mode Pelatihan .

Untuk membuat kelas ini:

  1. Buka folder Skrip yang Anda buat sebelumnya.

  2. Klik kanan di dalam folder, lalu klik Buat > Skrip C#. Beri nama skrip ImageCapture.

  3. Klik dua kali pada skrip ImageCapture baru untuk membukanya dengan Visual Studio.

  4. Ganti namespace di bagian atas file dengan yang berikut ini:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Kemudian tambahkan variabel berikut di dalam kelas ImageCapture, di atas metode Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture Instance;
    
        /// <summary>
        /// Keep counts of the taps for image renaming
        /// </summary>
        private int captureCount = 0;
    
        /// <summary>
        /// Photo Capture object
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// Allows gestures recognition in HoloLens
        /// </summary>
        private GestureRecognizer recognizer;
    
        /// <summary>
        /// Loop timer
        /// </summary>
        private float secondsBetweenCaptures = 10f;
    
        /// <summary>
        /// Application main functionalities switch
        /// </summary>
        internal enum AppModes {Analysis, Training }
    
        /// <summary>
        /// Local variable for current AppMode
        /// </summary>
        internal AppModes AppMode { get; private set; }
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. Kode untuk metode Awake() dan Start() sekarang perlu ditambahkan:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
    
            // Change this flag to switch between Analysis Mode and Training Mode 
            AppMode = AppModes.Training;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
    
            SceneOrganiser.Instance.SetCameraStatus("Ready");
        }
    
  7. Terapkan handler yang akan dipanggil saat gerakan Ketuk terjadi.

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            switch (AppMode)
            {
                case AppModes.Analysis:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to looping capture.
                        SceneOrganiser.Instance.SetCameraStatus("Looping Capture");
    
                        // Begin the capture loop
                        InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures);
                    }
                    else
                    {
                        // The user tapped while the app was analyzing 
                        // therefore stop the analysis process
                        ResetImageCapture();
                    }
                    break;
    
                case AppModes.Training:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Call the image capture
                        ExecuteImageCaptureAndAnalysis();
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to uploading image.
                        SceneOrganiser.Instance.SetCameraStatus("Uploading Image");
                    }              
                    break;
            }     
        }
    

    Catatan

    Dalam mode Analisis , metode TapHandler bertindak sebagai pengalih untuk memulai atau menghentikan perulangan pengambilan foto.

    Dalam mode Pelatihan , ia akan mengambil gambar dari kamera.

    Ketika kursor berwarna hijau, itu berarti kamera tersedia untuk mengambil gambar.

    Ketika kursor berwarna merah, itu berarti kamera sibuk.

  8. Tambahkan metode yang digunakan aplikasi untuk memulai proses pengambilan gambar dan menyimpan gambar.

        /// <summary>
        /// Begin process of Image Capturing and send To Azure Custom Vision Service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            // Update camera status to analysis.
            SceneOrganiser.Instance.SetCameraStatus("Analysis");
    
            // Create a label in world space using the SceneOrganiser class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
    
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 0.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });   
        }
    
  9. Tambahkan handler yang akan dipanggil ketika foto telah diambil dan kapan siap untuk dianalisis. Hasilnya kemudian diteruskan ke CustomVisionAnalyser atau CustomVisionTrainer tergantung pada mode mana kode diatur.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
    
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the Image Analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            switch (AppMode)
            {
                case AppModes.Analysis:
                    // Call the image analysis
                    StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath));
                    break;
    
                case AppModes.Training:
                    // Call training using captured image
                    CustomVisionTrainer.Instance.RequestTagSelection();
                    break;
            }
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Update camera status to ready.
            SceneOrganiser.Instance.SetCameraStatus("Ready");
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Pastikan untuk menyimpan perubahan Anda di Visual Studio sebelum kembali ke Unity.

  11. Sekarang setelah semua skrip selesai, kembali ke Editor Unity, lalu klik dan seret kelas SceneOrganiser dari folder Skrip ke objek Kamera Utama di Panel Hierarki.

Bab 12 - Sebelum membangun

Untuk melakukan pengujian menyeluruh aplikasi Anda, Anda harus membongkarnya ke HoloLens Anda.

Sebelum melakukannya, pastikan bahwa:

  • Semua pengaturan yang disebutkan dalam Bab 2 diatur dengan benar.

  • Semua bidang di Kamera Utama, Panel Inspektur, ditetapkan dengan benar.

  • Skrip SceneOrganiser dilampirkan ke objek Kamera Utama.

  • Pastikan Anda memasukkan Kunci Prediksi ke dalam variabel predictionKey.

  • Anda telah menyisipkan Titik Akhir Prediksi Anda ke dalam variabel predictionEndpoint.

  • Anda telah memasukkan Kunci Pelatihan Anda ke dalam variabel trainingKey, dari kelas CustomVisionTrainer.

  • Anda telah menyisipkan ID Proyek Anda ke dalam variabel projectId, dari kelas CustomVisionTrainer.

Bab 13 - Bangun dan muat samping aplikasi Anda

Untuk memulai proses Build :

  1. Buka Pengaturan Penyusunan File>.

  2. Tick Unity C# Projects.

  3. Klik Bangun. Unity akan meluncurkan jendela File Explorer , tempat Anda perlu membuat lalu memilih folder untuk membangun aplikasi. Buat folder tersebut sekarang, dan beri nama Aplikasi. Kemudian dengan folder Aplikasi dipilih, klik Pilih Folder.

  4. Unity akan mulai membangun proyek Anda ke folder Aplikasi .

  5. Setelah Unity selesai membangun (mungkin perlu waktu), Unity akan membuka jendela File Explorer di lokasi build Anda (periksa bilah tugas Anda, karena mungkin tidak selalu muncul di atas jendela Anda, tetapi akan memberi tahu Anda tentang penambahan jendela baru).

Untuk menyebarkan di HoloLens:

  1. Anda akan memerlukan Alamat IP HoloLens Anda (untuk Penyebaran Jarak Jauh), dan untuk memastikan HoloLens Anda berada dalam Mode Pengembang. Untuk melakukan ini:

    1. Sementara mengenakan HoloLens Anda, buka Pengaturan.

    2. Buka Opsi Tingkat Lanjut Wi-Fi Jaringan & Internet>>

    3. Perhatikan alamat IPv4.

    4. Selanjutnya, navigasikan kembali ke Pengaturan, lalu ke Pembaruan & Keamanan>Untuk Pengembang

    5. Atur Mode Pengembang Aktif.

  2. Navigasi ke build Unity baru Anda ( folder Aplikasi ) dan buka file solusi dengan Visual Studio.

  3. Di Konfigurasi Solusi pilih Debug.

  4. Di Platform Solusi, pilih x86, Komputer Jarak Jauh. Anda akan diminta untuk memasukkan alamat IP perangkat jarak jauh (HoloLens, dalam hal ini, yang Anda catat).

    Atur alamat IP

  5. Buka menu Build dan klik Sebarkan Solusi untuk memuat samping aplikasi ke HoloLens Anda.

  6. Aplikasi Anda sekarang akan muncul dalam daftar aplikasi yang diinstal di HoloLens Anda, siap untuk diluncurkan!

Catatan

Untuk menyebarkan ke headset imersif, atur Platform Solusi ke Komputer Lokal, dan atur Konfigurasi ke Debug, dengan x86 sebagai Platform. Kemudian sebarkan ke komputer lokal, menggunakan item menu Build , memilih Sebarkan Solusi.

Untuk menggunakan aplikasi:

Untuk mengalihkan fungsionalitas aplikasi antara mode Pelatihan dan mode Prediksi , Anda perlu memperbarui variabel AppMode , yang terletak di metode Awake() yang terletak di dalam kelas ImageCapture .

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Training;

or

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Analysis;

Dalam mode Pelatihan :

  • Lihat Mouse atau Keyboard dan gunakan gerakan Ketuk.

  • Selanjutnya, teks akan muncul yang meminta Anda untuk memberikan tag.

  • Ucapkan Mouse atau Keyboard.

Dalam mode Prediksi :

  • Lihat objek dan gunakan gerakan Ketuk.

  • Teks akan muncul dengan menyediakan objek yang terdeteksi, dengan probabilitas tertinggi (ini dinormalisasi).

Bab 14 - Mengevaluasi dan meningkatkan model Custom Vision Anda

Agar Layanan Anda lebih akurat, Anda harus terus melatih model yang digunakan untuk prediksi. Ini dicapai melalui penggunaan aplikasi baru Anda, dengan mode pelatihan dan prediksi , dengan yang terakhir mengharuskan Anda untuk mengunjungi portal, yang tercakup dalam Bab ini. Bersiaplah untuk mengunjungi kembali portal Anda berkali-kali, untuk terus meningkatkan model Anda.

  1. Buka Portal Azure Custom Vision Anda lagi, dan setelah Anda berada di proyek Anda, pilih tab Prediksi (dari tengah atas halaman):

    Pilih tab prediksi

  2. Anda akan melihat semua gambar yang dikirim ke Layanan Anda saat aplikasi Anda berjalan. Jika Anda mengarahkan mouse ke atas gambar, mereka akan memberi Anda prediksi yang dibuat untuk gambar tersebut:

    Daftar gambar prediksi

  3. Pilih salah satu gambar Anda untuk membukanya. Setelah terbuka, Anda akan melihat prediksi yang dibuat untuk gambar tersebut di sebelah kanan. Jika prediksi sudah benar, dan Anda ingin menambahkan gambar ini ke model pelatihan Layanan Anda, klik kotak input Tag Saya, dan pilih tag yang ingin Anda kaitkan. Setelah selesai, klik tombol Simpan dan tutup ke kanan bawah, dan lanjutkan ke gambar berikutnya.

    Pilih gambar untuk dibuka

  4. Setelah Anda kembali ke kisi gambar, Anda akan melihat gambar yang telah Anda tambahkan tag ke (dan disimpan), akan dihapus. Jika Anda menemukan gambar yang menurut Anda tidak memiliki item yang ditandai di dalamnya, Anda dapat menghapusnya, dengan mengklik tanda centang pada gambar tersebut (dapat melakukan ini untuk beberapa gambar) lalu mengklik Hapus di sudut kanan atas halaman kisi. Pada popup berikut, Anda dapat mengklik Ya, hapus , atau Tidak, untuk mengonfirmasi penghapusan atau membatalkannya.

    Hapus gambar

  5. Ketika Anda siap untuk melanjutkan, klik tombol Latih hijau di kanan atas. Model Layanan Anda akan dilatih dengan semua gambar yang sekarang telah Anda sediakan (yang akan membuatnya lebih akurat). Setelah pelatihan selesai, pastikan untuk mengklik tombol Buat default sekali lagi, sehingga URL Prediksi Anda terus menggunakan iterasi terbaru Dari Layanan Anda.

    Memulai model layanan pelatihanPilih opsi buat default

Aplikasi Custom Vision API Anda yang sudah selesai

Selamat, Anda membangun aplikasi realitas campuran yang memanfaatkan AZURE Custom Vision API untuk mengenali objek dunia nyata, melatih model Layanan, dan menampilkan keyakinan terhadap apa yang telah dilihat.

Contoh proyek yang sudah selesai

Latihan bonus

Latihan 1

Latih Custom Vision Service Anda untuk mengenali lebih banyak objek.

Latihan 2

Sebagai cara untuk memperluas apa yang telah Anda pelajari, selesaikan latihan berikut:

Putar suara saat objek dikenali.

Latihan 3:

Gunakan API untuk melatih kembali Layanan Anda dengan gambar yang sama yang dianaalisa aplikasi Anda, jadi untuk membuat Layanan lebih akurat (lakukan prediksi dan pelatihan secara bersamaan).