using System.Collections; using UnityEngine; using Unity.Barracuda; using TMPro; using UnityEngine.UI; // Untuk komponen Image public class AnorganikClassifier : MonoBehaviour { [Header("Model Barracuda")] public NNModel onnxModel; private Model runtimeModel; private IWorker worker; [Header("UI Text")] public TextMeshProUGUI outputText; public TextMeshProUGUI scoreText; [Header("UI Image")] public Image displayImage; // Menampilkan foto hasil kamera [Header("Feedback Images")] public Image feedbackCorrectImage; // Image checklist public Image feedbackWrongImage; // Image silang public float feedbackDelay = 1.5f; // Delay sebelum muncul canvas (dalam detik) [Header("Canvas")] public GameObject canvasBerhasil; public GameObject canvasGagal; [Header("Audio")] [SerializeField] private AudioSource audioSource; [SerializeField] private AudioClip correctClip; [SerializeField] private AudioClip wrongClip; [SerializeField] private AudioClip awardClip; // Pastikan penamaan konsisten (gunakan huruf kecil) // Urutan kelas (sesuai training) private string[] classes = { "anorganik", "bukansampah", "organik" }; private int inputWidth = 224; private int inputHeight = 224; private int currentScore = 0; private int maxScore = 5; void Start() { // Muat model dari asset runtimeModel = ModelLoader.Load(onnxModel); // Buat worker untuk melakukan inferensi (auto memilih antara CPU/GPU) worker = WorkerFactory.CreateWorker(WorkerFactory.Type.Auto, runtimeModel); // Pastikan feedback image tersembunyi awalnya if (feedbackCorrectImage != null) feedbackCorrectImage.gameObject.SetActive(false); if (feedbackWrongImage != null) feedbackWrongImage.gameObject.SetActive(false); UpdateScoreText(); } /// /// Fungsi yang dipanggil oleh tombol untuk membuka kamera bawaan dan mengambil gambar. /// public void TakePicture() { NativeCamera.Permission permission = NativeCamera.CheckPermission(true); if (permission == NativeCamera.Permission.Denied || permission == NativeCamera.Permission.ShouldAsk) { NativeCamera.RequestPermission(true); } NativeCamera.TakePicture((path) => { if (!string.IsNullOrEmpty(path)) { // Load gambar dengan ukuran target Texture2D texture = NativeCamera.LoadImageAtPath(path, inputWidth); if (texture == null) { Debug.LogError("Gagal memuat gambar dari path: " + path); return; } // Pastikan texture bisa diakses (readable) Texture2D readableTexture = MakeTextureReadable(texture); // Koreksi orientasi gambar dari kamera (sesuaikan rotasinya jika perlu) Texture2D correctedTexture = CorrectCameraImage(readableTexture); // Tampilkan gambar di UI Image (opsional) if (displayImage != null) { Sprite newSprite = Sprite.Create(correctedTexture, new Rect(0, 0, correctedTexture.width, correctedTexture.height), new Vector2(0.5f, 0.5f)); displayImage.sprite = newSprite; } // Proses gambar yang diambil dengan inferensi RunInference(correctedTexture); } }, maxSize: inputWidth); } /// /// Melakukan inferensi pada gambar yang diberikan. /// public void RunInference(Texture2D inputTexture) { if (inputTexture == null) { Debug.LogError("Gambar tidak tersedia untuk inferensi!"); return; } // Resize texture ke ukuran input model Texture2D resized = ResizeTexture(inputTexture, inputWidth, inputHeight); // Dapatkan data piksel dan lakukan normalisasi ke rentang [-1, 1] Color32[] pixels = resized.GetPixels32(); float[] floatValues = new float[inputWidth * inputHeight * 3]; for (int i = 0; i < pixels.Length; i++) { floatValues[i * 3 + 0] = (pixels[i].r / 127.5f) - 1.0f; floatValues[i * 3 + 1] = (pixels[i].g / 127.5f) - 1.0f; floatValues[i * 3 + 2] = (pixels[i].b / 127.5f) - 1.0f; } // Buat tensor input dengan dimensi [1, inputHeight, inputWidth, 3] Tensor inputTensor = new Tensor(1, inputHeight, inputWidth, 3, floatValues); worker.Execute(inputTensor); Tensor outputTensor = worker.PeekOutput(); float[] scores = outputTensor.ToReadOnlyArray(); int predictedIndex = ArgMax(scores); float confidence = scores[predictedIndex]; string predictedClass = classes[predictedIndex]; Debug.Log($"Predicted Class: {predictedClass}\nConfidence: {confidence:F2}"); if (outputText != null) { outputText.text = $"Predicted Class: {predictedClass}\nConfidence: {confidence:F2}"; } // Logika skor dan feedback if (predictedClass == "anorganik") { currentScore++; UpdateScoreText(); if (audioSource != null && correctClip != null) { audioSource.PlayOneShot(correctClip); } // Jika sudah mencapai target (5 kali) if (currentScore >= maxScore) { StartCoroutine(ShowFeedback(true, true)); // benar dan final } else { StartCoroutine(ShowFeedback(true, false)); // benar tapi belum final } } else { if (audioSource != null && wrongClip != null) { audioSource.PlayOneShot(wrongClip); } // Jika salah, tampilkan feedback dan canvas gagal (final) StartCoroutine(ShowFeedback(false, true)); } inputTensor.Dispose(); outputTensor.Dispose(); } /// /// Coroutine untuk menampilkan image feedback (checklist atau silang) selama delay tertentu, /// kemudian jika isFinal true, akan menampilkan canvas hasil dan (jika benar) memutar awardClip. /// private IEnumerator ShowFeedback(bool isCorrect, bool isFinal) { // Tampilkan image feedback if (isCorrect) { if (feedbackCorrectImage != null) feedbackCorrectImage.gameObject.SetActive(true); } else { if (feedbackWrongImage != null) feedbackWrongImage.gameObject.SetActive(true); } // Tunggu selama feedbackDelay yield return new WaitForSeconds(feedbackDelay); // Sembunyikan image feedback if (isCorrect) { if (feedbackCorrectImage != null) feedbackCorrectImage.gameObject.SetActive(false); } else { if (feedbackWrongImage != null) feedbackWrongImage.gameObject.SetActive(false); } // Jika isFinal true, tampilkan canvas hasil dan putar awardClip (jika benar) if (isFinal) { if (isCorrect) { if (audioSource != null && awardClip != null) { audioSource.PlayOneShot(awardClip); } if (canvasBerhasil != null) canvasBerhasil.SetActive(true); } else { if (canvasGagal != null) canvasGagal.SetActive(true); } } } /// /// Mengembalikan indeks elemen dengan nilai maksimum pada array. /// private int ArgMax(float[] array) { int index = 0; float max = array[0]; for (int i = 1; i < array.Length; i++) { if (array[i] > max) { max = array[i]; index = i; } } return index; } /// /// Meresize Texture2D ke ukuran yang diinginkan. /// private Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight) { RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight); RenderTexture.active = rt; Graphics.Blit(source, rt); Texture2D newTexture = new Texture2D(newWidth, newHeight); newTexture.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0); newTexture.Apply(); RenderTexture.active = null; RenderTexture.ReleaseTemporary(rt); return newTexture; } /// /// Memperbarui tampilan skor pada UI. /// private void UpdateScoreText() { if (scoreText != null) { scoreText.text = $"{currentScore}/{maxScore}"; } } /// /// Membuat salinan texture yang dapat diakses (readable) dengan RenderTexture. /// private Texture2D MakeTextureReadable(Texture2D tex) { RenderTexture tmp = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear); Graphics.Blit(tex, tmp); RenderTexture previous = RenderTexture.active; RenderTexture.active = tmp; Texture2D readableTex = new Texture2D(tex.width, tex.height); readableTex.ReadPixels(new Rect(0, 0, tmp.width, tmp.height), 0, 0); readableTex.Apply(); RenderTexture.active = previous; RenderTexture.ReleaseTemporary(tmp); return readableTex; } /// /// Mengoreksi orientasi gambar yang diambil dari NativeCamera. /// Contoh: memutar gambar 90° searah jarum jam. /// (Jika perlu, sesuaikan rotasinya sesuai kondisi perangkat.) /// private Texture2D CorrectCameraImage(Texture2D original) { int width = original.width; int height = original.height; Texture2D rotated = new Texture2D(height, width, original.format, false); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { rotated.SetPixel(j, width - i - 1, original.GetPixel(i, j)); } } rotated.Apply(); return rotated; } private void OnDestroy() { worker?.Dispose(); } }