Bagikan melalui


HoloLens (generasi ke-1) dan Azure 310: Deteksi objek

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 dan posisi spasialnya dalam gambar yang disediakan, menggunakan kemampuan "Deteksi Objek" 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 dan memperkirakan lokasinya di dunia nyata, seperti yang disediakan oleh tangkapan kamera Microsoft HoloLens atau kamera yang terhubung ke PC untuk headset imersif (VR).

hasil kursus

Azure Custom Vision, Deteksi Objek adalah Layanan Microsoft yang memungkinkan pengembang untuk membangun pengklasifikasi gambar kustom. Pengklasifikasi ini kemudian dapat digunakan dengan gambar baru untuk mendeteksi objek dalam gambar baru tersebut, dengan menyediakan Batas Kotak dalam gambar itu sendiri. Layanan ini menyediakan portal online yang sederhana dan mudah digunakan untuk menyederhanakan proses ini. Untuk informasi selengkapnya, kunjungi tautan berikut:

Setelah menyelesaikan kursus ini, Anda akan memiliki aplikasi realitas campuran yang akan dapat melakukan hal berikut:

  1. Pengguna akan dapat menatap objek, yang telah mereka latih menggunakan Azure Custom Vision Service, Deteksi Objek.
  2. Pengguna akan menggunakan gerakan Ketuk untuk mengambil gambar dari apa yang mereka lihat.
  3. Aplikasi ini akan mengirim gambar ke Azure Custom Vision Service.
  4. Akan ada balasan dari Layanan yang akan menampilkan hasil pengenalan sebagai teks ruang dunia. Ini akan dicapai dengan menggunakan Pelacakan Spasial Microsoft HoloLens, sebagai cara untuk memahami posisi dunia objek yang dikenali, dan kemudian menggunakan Tag yang terkait dengan apa yang terdeteksi dalam gambar, untuk menyediakan teks label.

Kursus ini juga akan mencakup pengunggahan gambar secara manual, membuat tag, dan melatih Layanan untuk mengenali objek yang berbeda (dalam contoh yang disediakan, cangkir) dengan mengatur Kotak Batas dalam gambar yang Anda kirimkan.

Penting

Setelah pembuatan dan penggunaan aplikasi, pengembang harus menavigasi kembali ke Azure Custom Vision Service, dan mengidentifikasi prediksi yang dibuat oleh Layanan, dan menentukan apakah mereka benar atau tidak (melalui penandaan apa pun yang terlewatkan Layanan, dan menyesuaikan Kotak Pembatas). Layanan kemudian dapat dilatih kembali, yang akan meningkatkan kemungkinannya mengenali objek dunia nyata.

Kursus ini akan mengajari Anda cara mendapatkan hasil dari Azure Custom Vision Service, Object Detection, 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 310: Deteksi objek ✔️

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 ini, kunjungi 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 Custom Vision

Untuk menggunakan Azure Custom Vision Service, Anda harus mengonfigurasi instansnya agar tersedia untuk aplikasi Anda.

  1. Navigasi ke halaman utama Custom Vision Service.

  2. Klik Memulai.

    Cuplikan layar yang menyoroti tombol Memulai.

  3. Masuk ke Portal Visi Kustom.

    Cuplikan layar yang memperlihatkan tombol Masuk.

  4. 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.

  5. Setelah Anda masuk untuk pertama kalinya, Anda akan diminta dengan panel Ketentuan Layanan . Klik kotak centang untuk menyetujui persyaratan. Lalu klik Saya setuju.

    Cuplikan layar yang memperlihatkan panel Ketentuan Layanan.

  6. Setelah menyetujui persyaratan, Anda sekarang berada di bagian Proyek Saya. Klik Proyek Baru.

    Cuplikan layar yang memperlihatkan tempat untuk memilih Proyek Baru.

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

    1. Menyisipkan nama untuk proyek Anda

    2. Menyisipkan 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).

      Cuplikan layar yang memperlihatkan tempat menambahkan detail untuk proyek baru.

    4. Atur Jenis Proyek sebagai Deteksi Objek (pratinjau).

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

Bab 2 - Melatih proyek Custom Vision Anda

Setelah berada di Portal Visi Kustom, tujuan utama Anda adalah melatih proyek Anda untuk mengenali objek tertentu dalam gambar.

Anda memerlukan setidaknya lima belas (15) gambar untuk setiap objek yang ingin Anda kenali aplikasi Anda. Anda dapat menggunakan gambar yang disediakan dengan kursus ini (serangkaian cangkir).

Untuk melatih proyek Custom Vision Anda:

  1. Klik tombol di + samping Tag.

    Cuplikan layar yang memperlihatkan tombol + di samping Tag.

  2. Tambahkan nama untuk tag yang akan digunakan untuk mengaitkan gambar Anda. Dalam contoh ini kita menggunakan gambar cangkir untuk pengenalan, jadi telah menamai tag untuk ini, Cup. Klik Simpan setelah selesai.

    Cuplikan layar yang memperlihatkan tempat menambahkan nama untuk tag.

  3. Anda akan melihat Tag Anda telah ditambahkan (Anda mungkin perlu memuat ulang halaman Anda agar tag tersebut muncul).

    Cuplikan layar yang memperlihatkan tempat tag Anda ditambahkan.

  4. Klik Tambahkan gambar di tengah halaman.

    Cuplikan layar yang memperlihatkan tempat untuk menambahkan gambar.

  5. Klik Telusuri file lokal, dan telusuri ke gambar yang ingin Anda unggah untuk satu objek, dengan minimal lima belas (15).

    Tip

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

    Cuplikan layar yang memperlihatkan gambar yang dapat Anda unggah.

  6. Tekan Unggah file setelah Anda memilih semua gambar yang ingin Anda latih proyeknya. File akan mulai diunggah. Setelah Anda mengonfirmasi pengunggahan, klik Selesai.

    Cuplikan layar yang memperlihatkan kemajuan gambar yang diunggah.

  7. Pada titik ini gambar Anda diunggah, tetapi tidak ditandai.

    Cuplikan layar yang memperlihatkan gambar yang tidak diberi tag.

  8. Untuk menandai gambar Anda, gunakan mouse Anda. Saat Anda mengarahkan mouse ke atas gambar Anda, sorotan pilihan akan membantu Anda dengan menggambar pilihan secara otomatis di sekitar objek Anda. Jika tidak akurat, Anda dapat menggambar sendiri. Ini dicapai dengan menahan klik kiri pada mouse, dan menyeret wilayah pilihan untuk mencakup objek Anda.

    Cuplikan layar yang memperlihatkan cara menandai gambar.

  9. Mengikuti pilihan objek Anda dalam gambar, permintaan kecil akan meminta Anda untuk Menambahkan Tag Wilayah. Pilih tag yang dibuat sebelumnya ('Cup', dalam contoh di atas), atau jika Anda menambahkan lebih banyak tag, ketikkan tag tersebut dan klik tombol + (plus).

    Cuplikan layar yang memperlihatkan tag yang Anda tambahkan ke gambar.

  10. Untuk menandai gambar berikutnya, Anda dapat mengklik panah di sebelah kanan bilah, atau menutup bilah tag (dengan mengklik X di sudut kanan atas bilah) lalu klik gambar berikutnya. Setelah Anda menyiapkan gambar berikutnya, ulangi prosedur yang sama. Lakukan ini untuk semua gambar yang telah Anda unggah, hingga semuanya ditandai.

    Catatan

    Anda dapat memilih beberapa objek dalam gambar yang sama, seperti gambar di bawah ini:

    Cuplikan layar yang memperlihatkan beberapa objek dalam gambar.

  11. Setelah Anda menandai semuanya, klik tombol yang diberi tag, di sebelah kiri layar, untuk mengungkapkan gambar yang ditandai.

    Cuplikan layar yang menyoroti tombol Ditandai.

  12. Anda sekarang siap untuk melatih Layanan Anda. Klik tombol Latih , dan iterasi pelatihan pertama akan dimulai.

    Cuplikan layar yang menyoroti tombol Latih.

    Cuplikan layar yang memperlihatkan iterasi pelatihan pertama.

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

    Cuplikan layar yang menyoroti tombol Buat default.

    Catatan

    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.

  14. Setelah Anda mengklik URL Prediksi, buka Notepad, dan salin dan tempel URL (juga disebut Titik Akhir Prediksi Anda) dan Kunci Prediksi Layanan, sehingga Anda dapat mengambilnya saat Anda membutuhkannya nanti dalam kode.

    Cuplikan layar yang memperlihatkan titik akhir prediksi dan kunci predisi.

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.

    Cuplikan layar yang menyoroti tombol Baru.

  2. Anda sekarang perlu memberikan nama proyek Unity. Sisipkan CustomVisionObjDetection. Pastikan jenis proyek diatur ke 3D, dan atur Lokasi ke tempat yang sesuai untuk Anda (ingat, lebih dekat ke direktori root lebih baik). Lalu, klik Buat proyek.

    Cuplikan layar yang memperlihatkan detail proyek dan tempat memilih Buat 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. Tutup jendela Preferensi .

    Cuplikan layar yang memperlihatkan tempat mengubah Editor Skrip Eksternal ke Visual Studio.

  4. Selanjutnya, buka Pengaturan Build File > dan alihkan Platform ke Platform Windows Universal, lalu klik tombol Beralih Platform.

    Cuplikan layar yang menyoroti tombol Beralih Platform.

  5. Di jendela Pengaturan Build yang sama, pastikan berikut ini diatur:

    1. Perangkat Target diatur ke HoloLens

    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. Pengaturan yang tersisa, di Pengaturan Build, harus dibiarkan sebagai default untuk saat ini.

      Cuplikan layar yang memperlihatkan opsi konfigurasi Pengaturan Build.

  6. Di jendela Pengaturan Build yang sama, 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. Backend Scripting harus .NET.

      3. Tingkat Kompatibilitas API harus .NET 4.6.

        Cuplikan layar yang memperlihatkan opsi Tingkat Kompatibilitas API diatur ke .NET 4.6.

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

      1. InternetClient

      2. Webcam

      3. Persepsi Spasial

        Cuplikan layar yang memperlihatkan bagian atas opsi konfigurasi Kemampuan.Cuplikan layar yang memperlihatkan bagian bawah opsi konfigurasi Kemampuan.

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

      Cuplikan layar yang menunjukkan bahwa Windows Mixed Reality SDK ditambahkan.

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

  9. Tutup jendela Pengaturan Build.

  10. Di Editor, klik Edit>Grafik Pengaturan>Proyek.

    Cuplikan layar yang memperlihatkan opsi menu Grafik dipilih.

  11. Di Panel Pemeriksa, Pengaturan Grafis akan terbuka. Gulir ke bawah hingga Anda melihat array yang disebut Always Include Shaders. Tambahkan slot dengan meningkatkan variabel Ukuran satu per satu (dalam contoh ini, itu adalah 8 sehingga kami membuatnya 9). Slot baru akan muncul, di posisi terakhir array, seperti yang ditunjukkan di bawah ini:

    Cuplikan layar yang menyoroti array Always Included Shaders.

  12. Di slot, klik lingkaran target kecil di samping slot untuk membuka daftar shader. Cari Shader Warisan/Shader Transparan/Difus dan klik dua kali.

    Cuplikan layar yang menyoroti Shader Warisan/Shader Transparan/Difus.

Bab 4 - Mengimpor paket Unity CustomVisionObjDetection

Untuk kursus ini Anda disediakan dengan Paket Aset Unity yang disebut Azure-MR-310.unitypackage.

[TIP] Objek apa pun yang didukung oleh Unity, termasuk seluruh adegan, dapat dikemas ke dalam file .unitypackage , dan diekspor / diimpor di proyek lain. Ini adalah cara paling aman, dan paling efisien untuk memindahkan aset di antara proyek Unity yang berbeda.

Anda dapat menemukan paket Azure-MR-310 yang perlu Anda unduh di sini.

  1. Dengan dasbor Unity di depan Anda, klik Aset di menu di bagian atas layar, lalu klik Impor Paket Kustom Paket > Kustom.

    Cuplikan layar yang menyoroti opsi menu Paket Kustom.

  2. Gunakan pemilih file untuk memilih paket azure-MR-310.unitypackage dan klik Buka. Daftar komponen untuk aset ini akan ditampilkan kepada Anda. Konfirmasi impor dengan mengklik tombol Impor .

    Cuplikan layar yang memperlihatkan daftar komponen aset yang ingin Anda impor.

  3. Setelah selesai mengimpor, Anda akan melihat bahwa folder dari paket sekarang telah ditambahkan ke folder Aset Anda. Struktur folder semacam ini khas untuk proyek Unity.

    Cuplikan layar yang memperlihatkan konten folder Aset.

    1. Folder Materi berisi materi yang digunakan oleh Kursor Tatap.

    2. Folder Plugin berisi DLL Newtonsoft yang digunakan oleh kode untuk mendeserialisasi respons web Layanan. Dua (2) versi berbeda yang terkandung dalam folder, dan sub-folder, diperlukan untuk memungkinkan pustaka digunakan dan dibangun oleh Editor Unity dan build UWP.

    3. Folder Prefabs berisi prefab yang terkandung dalam adegan. Yaitu:

      1. GazeCursor, kursor yang digunakan dalam aplikasi. Akan bekerja sama dengan prefab SpatialMapping untuk dapat ditempatkan di tempat kejadian di atas objek fisik.
      2. Label, yang merupakan objek UI yang digunakan untuk menampilkan tag objek dalam adegan jika diperlukan.
      3. SpatialMapping, yang merupakan objek yang memungkinkan aplikasi untuk menggunakan buat peta virtual, menggunakan pelacakan spasial Microsoft HoloLens.
    4. Folder Adegan yang saat ini berisi adegan bawaan untuk kursus ini.

  4. Buka folder Adegan, di Panel Proyek, dan klik dua kali pada ObjDetectionScene, untuk memuat adegan yang akan Anda gunakan untuk kursus ini.

    Cuplikan layar yang memperlihatkan ObjDetectionScene di folder Adegan.

    Catatan

    Tidak ada kode yang disertakan, Anda akan menulis kode dengan mengikuti kursus ini.

Bab 5 - 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 dibuat 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 SDK yang juga dapat digunakan untuk melakukan panggilan ke Layanan. Untuk informasi selengkapnya, kunjungi artikel Custom Vision 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.

    Cuplikan layar yang memperlihatkan cara membuat folder Skrip.

  2. Klik dua kali pada folder yang baru 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. Pastikan Anda memiliki namespace berikut yang dirujuk di bagian atas file:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  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>
        /// Bite array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Catatan

    Pastikan Anda memasukkan Kunci Prediksi Layanan ke dalam variabel predictionKey dan Prediction-Endpoint Anda ke dalam variabel predictionEndpoint. Anda menyalinnya ke Notepad sebelumnya, di Bab 2, Langkah 14.

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

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. 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)
        {
            Debug.Log("Analyzing...");
    
            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;
    
                Debug.Log("response: " + jsonResponse);
    
                // Create a texture. Texture size does not matter, since
                // LoadImage will replace with the incoming image size.
                //Texture2D tex = new Texture2D(1, 1);
                //tex.LoadImage(imageBytes);
                //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
                // The response will be in JSON format, therefore it needs to be deserialized
                //AnalysisRootObject analysisRootObject = new AnalysisRootObject();
                //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
                //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
            }
        }
    
        /// <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);
        }
    
  9. Hapus metode Start() dan Update(), karena tidak akan digunakan.

  10. Pastikan untuk menyimpan perubahan Anda di Visual Studio, sebelum kembali ke Unity.

Penting

Seperti disebutkan sebelumnya, jangan khawatir tentang kode yang mungkin tampak memiliki kesalahan, karena Anda akan segera memberikan kelas lebih lanjut, yang akan memperbaikinya.

Bab 6 - 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.

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. Pastikan Anda memiliki namespace berikut yang dirujuk di 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.

    Peringatan

    Penting bagi Anda untuk mengikuti instruksi berikutnya dengan hati-hati. Jika Anda meletakkan deklarasi kelas baru di dalam kelas CustomVisionObjects , Anda akan mendapatkan kesalahan kompilasi dalam bab 10, yang menyatakan bahwa AnalysisRootObject dan BoundingBox tidak ditemukan.

  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
    /// Includes Bounding Box
    /// </summary>
    public class AnalysisRootObject
    {
        public string id { get; set; }
        public string project { get; set; }
        public string iteration { get; set; }
        public DateTime created { get; set; }
        public List<Prediction> predictions { get; set; }
    }
    
    public class BoundingBox
    {
        public double left { get; set; }
        public double top { get; set; }
        public double width { get; set; }
        public double height { get; set; }
    }
    
    public class Prediction
    {
        public double probability { get; set; }
        public string tagId { get; set; }
        public string tagName { get; set; }
        public BoundingBox boundingBox { get; set; }
    }
    
  6. Pastikan untuk menyimpan perubahan Anda di Visual Studio, sebelum kembali ke Unity.

Bab 7 - Buat kelas SpatialMapping

Kelas ini akan mengatur Spatial Mapping Collider di adegan sehingga dapat mendeteksi tabrakan antara objek virtual dan objek nyata.

Untuk membuat kelas ini:

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

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

  3. Pastikan Anda memiliki namespace berikut yang dirujuk di atas kelas SpatialMapping :

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

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SpatialMapping Instance;
    
        /// <summary>
        /// Used by the GazeCursor as a property with the Raycast call
        /// </summary>
        internal static int PhysicsRaycastMask;
    
        /// <summary>
        /// The layer to use for spatial mapping collisions
        /// </summary>
        internal int physicsLayer = 31;
    
        /// <summary>
        /// Creates environment colliders to work with physics
        /// </summary>
        private SpatialMappingCollider spatialMappingCollider;
    
  5. Tambahkan Awake() dan Start():

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Initialize and configure the collider
            spatialMappingCollider = gameObject.GetComponent<SpatialMappingCollider>();
            spatialMappingCollider.surfaceParent = this.gameObject;
            spatialMappingCollider.freezeUpdates = false;
            spatialMappingCollider.layer = physicsLayer;
    
            // define the mask
            PhysicsRaycastMask = 1 << physicsLayer;
    
            // set the object as active one
            gameObject.SetActive(true);
        }
    
  6. Hapus metode Update().

  7. Pastikan untuk menyimpan perubahan Anda di Visual Studio, sebelum kembali ke Unity.

Bab 8 - Buat kelas GazeCursor

Kelas ini bertanggung jawab untuk menyiapkan kursor di lokasi yang benar di ruang nyata, dengan menggunakan SpatialMappingCollider, yang dibuat di bab sebelumnya.

Untuk membuat kelas ini:

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

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

  3. Pastikan Anda memiliki namespace berikut yang dirujuk di atas kelas GazeCursor :

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

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Perbarui metode Start() dengan kode berikut:

        /// <summary>
        /// Runs at initialization right after the Awake method
        /// </summary>
        void Start()
        {
            // Grab the mesh renderer that is on the same object as this script.
            meshRenderer = gameObject.GetComponent<MeshRenderer>();
    
            // Set the cursor reference
            SceneOrganiser.Instance.cursor = gameObject;
            gameObject.GetComponent<Renderer>().material.color = Color.green;
    
            // If you wish to change the size of the cursor you can do so here
            gameObject.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
        }
    
  6. Perbarui metode Update() dengan kode berikut:

        /// <summary>
        /// Update is called once per frame
        /// </summary>
        void Update()
        {
            // Do a raycast into the world based on the user's head position and orientation.
            Vector3 headPosition = Camera.main.transform.position;
            Vector3 gazeDirection = Camera.main.transform.forward;
    
            RaycastHit gazeHitInfo;
            if (Physics.Raycast(headPosition, gazeDirection, out gazeHitInfo, 30.0f, SpatialMapping.PhysicsRaycastMask))
            {
                // If the raycast hit a hologram, display the cursor mesh.
                meshRenderer.enabled = true;
                // Move the cursor to the point where the raycast hit.
                transform.position = gazeHitInfo.point;
                // Rotate the cursor to hug the surface of the hologram.
                transform.rotation = Quaternion.FromToRotation(Vector3.up, gazeHitInfo.normal);
            }
            else
            {
                // If the raycast did not hit a hologram, hide the cursor mesh.
                meshRenderer.enabled = false;
            }
        }
    

    Catatan

    Jangan khawatir tentang kesalahan untuk kelas SceneOrganiser yang tidak ditemukan, Anda akan membuatnya di bab berikutnya.

  7. Pastikan untuk menyimpan perubahan Anda di Visual Studio, sebelum kembali ke Unity.

Bab 9 - Buat kelas SceneOrganiser

Kelas ini akan:

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

  • Ketika objek terdeteksi, objek akan bertanggung jawab untuk menghitung posisinya di dunia nyata dan menempatkan Label Tag di dekatnya dengan Nama Tag yang sesuai.

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. Pastikan Anda memiliki namespace berikut yang dirujuk di atas kelas SceneOrganiser :

    using System.Collections.Generic;
    using System.Linq;
    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 Main Camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        public GameObject label;
    
        /// <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.8f;
    
        /// <summary>
        /// The quad object hosting the imposed image captured
        /// </summary>
        private GameObject quad;
    
        /// <summary>
        /// Renderer of the quad object
        /// </summary>
        internal Renderer quadRenderer;
    
  5. Hapus metode Start() dan Update().

  6. 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 CustomVisionObjects class to this Gameobject
            gameObject.AddComponent<CustomVisionObjects>();
        }
    
  7. Tambahkan metode PlaceAnalysisLabel(), yang akan Membuat Instans label di adegan (yang pada titik ini tidak terlihat oleh pengguna). Ini juga menempatkan quad (juga tidak terlihat) di mana gambar ditempatkan, dan tumpang tindih dengan dunia nyata. Ini penting karena koordinat kotak yang diambil dari Layanan setelah analisis ditelusuri kembali ke quad ini untuk menentukan perkiraan lokasi objek di dunia nyata.

        /// <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>();
            lastLabelPlacedText.text = "";
            lastLabelPlaced.transform.localScale = new Vector3(0.005f,0.005f,0.005f);
    
            // Create a GameObject to which the texture can be applied
            quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
            quadRenderer = quad.GetComponent<Renderer>() as Renderer;
            Material m = new Material(Shader.Find("Legacy Shaders/Transparent/Diffuse"));
            quadRenderer.material = m;
    
            // Here you can set the transparency of the quad. Useful for debugging
            float transparency = 0f;
            quadRenderer.material.color = new Color(1, 1, 1, transparency);
    
            // Set the position and scale of the quad depending on user position
            quad.transform.parent = transform;
            quad.transform.rotation = transform.rotation;
    
            // The quad is positioned slightly forward in font of the user
            quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f);
    
            // The quad scale as been set with the following value following experimentation,  
            // to allow the image on the quad to be as precisely imposed to the real world as possible
            quad.transform.localScale = new Vector3(3f, 1.65f, 1f);
            quad.transform.parent = null;
        }
    
  8. Tambahkan metode FinaliseLabel(). Ini bertanggung jawab untuk:

    • Mengatur teks Label dengan Tag Prediksi dengan keyakinan tertinggi.
    • Memanggil perhitungan Kotak Pembatas pada objek quad, diposisikan sebelumnya, dan menempatkan label di adegan.
    • Menyesuaikan kedalaman label dengan menggunakan Raycast menuju Bounding Box, yang seharusnya bertabrakan dengan objek di dunia nyata.
    • Mengatur ulang proses pengambilan untuk memungkinkan pengguna mengambil gambar lain.
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void FinaliseLabel(AnalysisRootObject analysisObject)
        {
            if (analysisObject.predictions != null)
            {
                lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
                // Sort the predictions to locate the highest one
                List<Prediction> sortedPredictions = new List<Prediction>();
                sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList();
                Prediction bestPrediction = new Prediction();
                bestPrediction = sortedPredictions[sortedPredictions.Count - 1];
    
                if (bestPrediction.probability > probabilityThreshold)
                {
                    quadRenderer = quad.GetComponent<Renderer>() as Renderer;
                    Bounds quadBounds = quadRenderer.bounds;
    
                    // Position the label as close as possible to the Bounding Box of the prediction 
                    // At this point it will not consider depth
                    lastLabelPlaced.transform.parent = quad.transform;
                    lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox);
    
                    // Set the tag text
                    lastLabelPlacedText.text = bestPrediction.tagName;
    
                    // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service.
                    // At that point it will reposition the label where the ray HL sensor collides with the object,
                    // (using the HL spatial tracking)
                    Debug.Log("Repositioning Label");
                    Vector3 headPosition = Camera.main.transform.position;
                    RaycastHit objHitInfo;
                    Vector3 objDirection = lastLabelPlaced.position;
                    if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f,   SpatialMapping.PhysicsRaycastMask))
                    {
                        lastLabelPlaced.position = objHitInfo.point;
                    }
                }
            }
            // Reset the color of the cursor
            cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the analysis process
            ImageCapture.Instance.ResetImageCapture();        
        }
    
  9. Tambahkan metode CalculateBoundingBoxPosition(), yang menghosting sejumlah perhitungan yang diperlukan untuk menerjemahkan koordinat Kotak Pembatas yang diambil dari Layanan dan membuatnya kembali secara proporsional pada quad.

        /// <summary>
        /// This method hosts a series of calculations to determine the position 
        /// of the Bounding Box on the quad created in the real world
        /// by using the Bounding Box received back alongside the Best Prediction
        /// </summary>
        public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox)
        {
            Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}");
    
            double centerFromLeft = boundingBox.left + (boundingBox.width / 2);
            double centerFromTop = boundingBox.top + (boundingBox.height / 2);
            Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}");
    
            double quadWidth = b.size.normalized.x;
            double quadHeight = b.size.normalized.y;
            Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}");
    
            double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2);
            double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2);
    
            return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0);
        }
    
  10. 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:

    // Create a texture. Texture size does not matter, since 
    // LoadImage will replace with the incoming image size.
    Texture2D tex = new Texture2D(1, 1);
    tex.LoadImage(imageBytes);
    SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
    // The response will be in JSON format, therefore it needs to be deserialized
    AnalysisRootObject analysisRootObject = new AnalysisRootObject();
    analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
    SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
    

Catatan

Jangan khawatir tentang pesan kelas ImageCapture 'tidak dapat ditemukan', Anda akan membuatnya di bab berikutnya.

Bab 10 - 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.

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>
        /// 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;
        }
    
        /// <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 Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Terapkan handler yang akan dipanggil saat gerakan Ketuk terjadi:

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            if (!captureIsActive)
            {
                captureIsActive = true;
    
                // Set the cursor color to red
                SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                // Begin the capture loop
                Invoke("ExecuteImageCaptureAndAnalysis", 0);
            }
        }
    

    Penting

    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()
        {
            // Create a label in world space using the ResultsLabel 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(true, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 1.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 untuk analisis.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            try
            {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
            }
            catch (Exception e)
            {
                Debug.LogFormat("Exception capturing photo to disk: {0}", e.Message);
            }
        }
    
        /// <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;
    
            // Call the image analysis
            StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); 
        }
    
        /// <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;
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Pastikan untuk menyimpan perubahan Anda di Visual Studio, sebelum kembali ke Unity.

Bab 11 - Menyiapkan skrip di adegan

Sekarang setelah Anda menulis semua kode yang diperlukan untuk proyek ini, adalah waktunya untuk menyiapkan skrip di adegan, dan pada prefab, agar mereka berperilaku dengan benar.

  1. Dalam Editor Unity, di Panel Hierarki, pilih Kamera Utama.

  2. Di Panel Pemeriksa, dengan Kamera Utama dipilih, klik Tambahkan Komponen, lalu cari skrip SceneOrganiser dan klik dua kali, untuk menambahkannya.

    Cuplikan layar yang memperlihatkan skrip SceneOrganizer.

  3. Di Panel Proyek, buka folder Prefabs, seret prefab Label ke area input target referensi kosong Label, di skrip SceneOrganiser yang baru saja Anda tambahkan ke Kamera Utama, seperti yang ditunjukkan pada gambar di bawah ini:

    Cuplikan layar yang memperlihatkan skrip yang Anda tambahkan ke Kamera Utama.

  4. Di Panel Hierarki, pilih Anak GazeCursor dari Kamera Utama.

  5. Di Panel Inspektur, dengan GazeCursor dipilih, klik Tambahkan Komponen, lalu cari skrip GazeCursor dan klik dua kali, untuk menambahkannya.

    Cuplikan layar yang memperlihatkan tempat Anda menambahkan skrip GazeCursor.

  6. Sekali lagi, di Panel Hierarki, pilih anak SpatialMapping dari Kamera Utama.

  7. Di Panel Pemeriksa, dengan SpatialMapping dipilih, klik Tambahkan Komponen, lalu cari skrip SpatialMapping dan klik dua kali, untuk menambahkannya.

    Cuplikan layar yang memperlihatkan tempat Anda menambahkan skrip SpatialMapping.

Skrip yang tersisa yang belum Anda tetapkan akan ditambahkan oleh kode dalam skrip SceneOrganiser , selama runtime.

Bab 12 - Sebelum membangun

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

Sebelum melakukannya, pastikan bahwa:

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

  • Skrip SceneOrganiser dilampirkan ke objek Kamera Utama.

  • Skrip GazeCursor dilampirkan ke objek GazeCursor .

  • Skrip SpatialMapping dilampirkan ke objek SpatialMapping .

  • Dalam Bab 5, Langkah 6:

    • Pastikan Anda memasukkan Kunci Prediksi Layanan ke dalam variabel predictionKey.
    • Anda telah menyisipkan Titik Akhir Prediksi anda ke dalam kelas predictionEndpoint .

Bab 13 - Bangun solusi UWP dan muat sisi aplikasi Anda

Anda sekarang siap untuk membangun aplikasi Anda sebagai Solusi UWP yang akan dapat Anda sebarkan ke Microsoft HoloLens. Untuk memulai proses build:

  1. Buka Pengaturan Penyusunan File>.

  2. Tick Unity C# Projects.

  3. Klik Tambahkan Adegan Terbuka. Ini akan menambahkan adegan yang saat ini terbuka ke build.

    Cuplikan layar yang menyoroti tombol Tambahkan Adegan Terbuka.

  4. 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.

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

  6. 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).

  7. Untuk menyebarkan ke Microsoft HoloLens, Anda akan memerlukan Alamat IP perangkat tersebut (untuk Penyebaran Jarak Jauh), dan untuk memastikan bahwa perangkat juga memiliki set 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.

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

  9. Di Konfigurasi Solusi pilih Debug.

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

    Cuplikan layar yang memperlihatkan tempat menyisipkan alamat IP.

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

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

Untuk menggunakan aplikasi:

  • Lihat objek, yang telah Anda latih dengan Azure Custom Vision Service, Deteksi Objek, dan gunakan gerakan Ketuk.
  • Jika objek berhasil terdeteksi, Teks Label spasi dunia akan muncul dengan nama tag.

Penting

Setiap kali Anda mengambil foto dan mengirimkannya ke Layanan, Anda dapat kembali ke halaman Layanan dan melatih kembali Layanan dengan gambar yang baru diambil. Pada awalnya, Anda mungkin juga harus memperbaiki Kotak Pembatas agar lebih akurat dan melatih kembali Layanan.

Catatan

Teks Label yang ditempatkan mungkin tidak muncul di dekat objek ketika sensor Microsoft HoloLens dan/atau SpatialTrackingComponent di Unity gagal menempatkan tabrakan yang sesuai, relatif terhadap objek dunia nyata. Cobalah untuk menggunakan aplikasi pada permukaan yang berbeda jika demikian.

Aplikasi Custom Vision, Object Detection Anda

Selamat, Anda membangun aplikasi realitas campuran yang memanfaatkan Azure Custom Vision, OBJECT Detection API, yang dapat mengenali objek dari gambar, lalu memberikan perkiraan posisi untuk objek tersebut dalam ruang 3D.

Cuplikan layar yang memperlihatkan aplikasi realitas campuran yang memanfaatkan Azure Custom Vision, OBJECT Detection API.

Latihan bonus

Latihan 1

Menambahkan ke Label Teks, gunakan kubus semi transparan untuk membungkus objek nyata dalam Kotak Pembatas 3D.

Latihan 2

Latih Custom Vision Service Anda untuk mengenali lebih banyak objek.

Latihan 3:

Putar suara saat objek dikenali.

Latihan 4

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).