# Soal 3 — Menghitung Jumlah Koin untuk Setiap Ukuran dengan `regionprops` Program: `pengolahan_citra.py` · Input: `koin_100.png` (dibuat oleh `buat_gambar_koin.py` dari `koin.png`) · Output: folder `output/` Cara menjalankan: ``` pip install -r requirements.txt python buat_gambar_koin.py # membuat koin_100.png python pengolahan_citra.py # atau: python pengolahan_citra.py ``` Referensi teori: Gonzalez & Woods, *Digital Image Processing*, edisi ke-4 (Pearson, 2018). ## 1. Tujuan dan Asumsi Sebuah gambar berisi 100 koin dengan berbagai ukuran di atas latar belakang seragam, dan koin-koin tidak saling bersentuhan. Program harus menentukan jumlah koin untuk setiap ukuran. Asumsi yang dipakai: - Koin tidak bersentuhan, sesuai soal. Akibatnya satu komponen terhubung = satu koin. - Latar belakang seragam dan kontras terhadap koin. - Jumlah dan nilai ukuran koin **tidak** diketahui sebelumnya. Program mengelompokkan ukuran secara otomatis dari data diameter. ## 2. Gambar Uji Soal tidak menyertakan gambar. `koin.png` hanya berisi satu koin, jadi `buat_gambar_koin.py` menyusun gambar uji `koin_100.png` (3000 × 3000 piksel): - Koin diperkecil ke 4 ukuran dan ditempel 100 kali di posisi acak. - Jarak antar-tepi koin minimal 10 piksel, jadi tidak ada yang bersentuhan. - Latar abu-abu gelap seragam (nilai 40). - Setiap koin diputar acak dan seluruh gambar diberi noise Gaussian (σ = 3), supaya koin sejenis tidak identik piksel per piksel, seperti pada foto asli. - Seed acak tetap, jadi gambar yang sama bisa dibuat ulang. | Diameter tempel | Jumlah koin | |---|---| | 140 piksel | 30 | | 170 piksel | 27 | | 205 piksel | 25 | | 245 piksel | 18 | | **Total** | **100** | Karena jumlah per ukuran diketahui, hasil program dapat diperiksa. ![Gambar uji koin_100.png](koin_100.png) ## 3. Kode Semu ``` INPUT : citra RGB berisi koin OUTPUT: daftar (ukuran, jumlah koin, diameter rata-rata) 1. gray ← konversi citra ke grayscale 2. gray ← Gaussian blur 5×5 (gray) 3. T ← threshold Otsu dari histogram gray 4. mask ← (gray > T) 5. JIKA mayoritas piksel tepi gambar bernilai 1 MAKA mask ← NOT mask (agar koin = 1 dan latar = 0) 6. mask ← isi lubang (mask) 7. label ← labeling komponen terhubung 8-ketetanggaan (mask) 8. label ← buang komponen yang menyentuh tepi gambar (label) 9. daftar_koin ← kosong 10. UNTUK SETIAP region DALAM regionprops(label): JIKA region.area < MIN_AREA: lanjutkan (noise) d ← √(4 · region.area_convex / π) (diameter koin) tambahkan (region.label, d, region.centroid) ke daftar_koin 11. kelompok ← kosong 12. UNTUK SETIAP koin DALAM daftar_koin, terurut dari diameter terkecil: cari kelompok k dengan |d − rata2(k)| / rata2(k) ≤ TOLERANSI JIKA ada: masukkan koin ke kelompok yang paling dekat JIKA tidak: buat kelompok baru berisi koin tersebut 13. urutkan kelompok berdasarkan diameter rata-rata 14. UNTUK SETIAP kelompok: tampilkan (nomor ukuran, jumlah anggota, diameter rata-rata) 15. simpan tabel CSV, mask, dan gambar anotasi ``` ## 4. Penjelasan Setiap Langkah ### 4.1 Grayscale dan penghalusan (Bab 2, Bab 3, Bab 7) Citra warna BGR diubah ke satu kanal intensitas (`cv2.cvtColor`) dengan kombinasi linear: ``` Y = 0,299 R + 0,587 G + 0,114 B ``` Lalu citra dihaluskan dengan kernel Gaussian 5 × 5 (`cv2.GaussianBlur`). Ini filter penghalus spasial linear dari Bab 3: ``` G(s, t) = K · e^(−(s² + t²) / 2σ²) ``` Dengan argumen σ = 0, OpenCV menghitung σ dari ukuran kernel, yaitu σ = 0,3·((5 − 1)·0,5 − 1) + 0,8 = 1,1. Penghalusan meredam noise sehingga threshold tidak menghasilkan bintik-bintik di latar. ### 4.2 Threshold Otsu Citra biner diperoleh dengan: ``` mask(x, y) = 1 jika f(x, y) > T mask(x, y) = 0 jika f(x, y) ≤ T ``` Nilai T dipilih otomatis dengan metode Otsu, yaitu nilai k yang memaksimalkan varians antar-kelas: ``` σ²_B(k) = [m_G · P₁(k) − m(k)]² / [P₁(k) · (1 − P₁(k))] ``` dengan P₁(k) = proporsi piksel ≤ k, m(k) = rata-rata kumulatif sampai k, dan m_G = rata-rata global. Latar yang seragam membuat histogram bimodal (latar dan koin), sehingga Otsu cocok. Pada `koin_100.png`, T = 126. Catatan: metode Otsu dibahas di Bab 10 (Segmentasi), setelah materi UTS. Konsep fungsi thresholding sudah dibahas di Bab 3. ### 4.3 Polaritas Koin bisa lebih terang atau lebih gelap dari latar. Program memeriksa piksel di keempat tepi gambar: jika lebih dari separuhnya bernilai 1, berarti latar ikut terdeteksi sebagai objek, dan mask dibalik. Pada `koin_100.png`, 0% piksel tepi bernilai 1, jadi mask tidak dibalik. ### 4.4 Pengisian lubang (Bab 9) Relief koin (bintang, simbol $) dan pantulan cahaya bisa membuat sebagian piksel di dalam koin berada di bawah T. Akibatnya koin berlubang, atau bahkan terpecah menjadi beberapa komponen. Lubang adalah daerah latar yang dikelilingi objek. Lubang diisi dengan algoritma morfologi: ``` X_k = (X_{k−1} ⊕ B) ∩ Iᶜ, k = 1, 2, 3, ... ``` dengan I = mask, Iᶜ = komplemennya, dan B = elemen penstruktur. Iterasi berhenti saat X_k = X_{k−1}, lalu hasilnya X_k ∪ I. `binary_fill_holes` menjalankan versi otomatisnya: setiap daerah latar yang tidak terhubung ke tepi gambar dianggap lubang dan diisi. Pada `koin_100.png`, 31.941 piksel lubang terisi. ### 4.5 Labeling komponen terhubung (Bab 2 dan Bab 9) `label(mask, connectivity=2)` memberi nomor unik pada setiap komponen terhubung dengan 8-ketetanggaan: piksel p terhubung dengan 8 tetangganya, yaitu 4 horizontal/vertikal dan 4 diagonal. Secara morfologi, satu komponen yang memuat titik awal X₀ diekstraksi dengan: ``` X_k = (X_{k−1} ⊕ B) ∩ I ``` Karena koin tidak bersentuhan, setiap koin menjadi tepat satu komponen. Pada `koin_100.png` terbentuk 450 komponen: 100 koin dan 350 fragmen kecil, yaitu potongan tepi koin yang terpisah oleh bagian bayangan. ### 4.6 Membuang koin di tepi gambar (Bab 9) Koin yang terpotong tepi gambar luasnya tidak utuh, sehingga diameternya terukur lebih kecil dan bisa masuk ke kelompok ukuran yang salah. `clear_border` membuang setiap komponen yang menyentuh tepi gambar. Di buku, operasi ini disebut *border clearing* dan dilakukan dengan rekonstruksi morfologi. Pada `koin_100.png` tidak ada koin di tepi. Pada uji D (bagian 6), langkah ini membuang satu koin yang terpotong. ### 4.7 Cara `regionprops` digunakan `regionprops(label)` menghasilkan satu objek pengukuran per komponen. Properti yang dipakai: | Properti | Arti | Kegunaan di program | |---|---|---| | `area` | Jumlah piksel region | Membuang noise: region dengan area < 100 piksel diabaikan | | `area_convex` | Jumlah piksel convex hull region | Menghitung diameter koin | | `centroid` | Titik pusat region (rata-rata koordinat piksel) | Posisi lingkaran dan nomor ukuran pada gambar anotasi | | `label` | Nomor region | Identitas koin | **Filter area.** Pada `koin_100.png`, fragmen terbesar berukuran 37 piksel dan koin terkecil 13.484 piksel. Batas 100 piksel memisahkan keduanya dengan aman, jadi 350 fragmen terbuang dan tepat 100 koin tersisa. **Diameter dari convex hull.** Bayangan di tepi koin cukup gelap sehingga berada di bawah T. Mask koin menjadi lingkaran yang terpotong berbentuk sabit di satu sisi. Potongan ini terbuka ke latar, jadi tidak terisi oleh pengisian lubang. Convex hull H dari region adalah himpunan konveks terkecil yang memuat region tersebut (algoritma morfologi di Bab 9). Convex hull menutup potongan sabit, sehingga luasnya mendekati luas lingkaran utuh. Diameter dihitung sebagai diameter lingkaran yang luasnya sama dengan luas convex hull: ``` A_H = π (d/2)² → d = √(4 · A_H / π) ``` Perbandingan pada `koin_100.png`: | Diameter tempel | d dari `area` | d dari `area_convex` | |---|---|---| | 140 | 131,1 (93,6%) | 137,2 (98,0%) | | 170 | 159,4 (93,7%) | 167,3 (98,4%) | | 205 | 192,4 (93,8%) | 202,3 (98,7%) | | 245 | 230,2 (93,9%) | 242,2 (98,9%) | Catatan: deskriptor region seperti area dan centroid dibahas lebih lanjut di Bab 11. Program memakainya lewat `regionprops`, sesuai petunjuk soal. ### 4.8 Pengelompokan ukuran Diameter diurutkan dari terkecil. Setiap koin dibandingkan dengan diameter rata-rata setiap kelompok yang sudah ada. Koin masuk ke kelompok terdekat jika selisih relatifnya ≤ toleransi: ``` |d − d̄_k| / d̄_k × 100% ≤ 3% ``` Jika tidak ada kelompok yang memenuhi, koin membentuk kelompok baru. Jumlah kelompok tidak ditentukan di awal, tetapi muncul dari data. **Pemilihan toleransi.** Toleransi harus lebih besar dari variasi diameter koin sejenis, tetapi lebih kecil dari selisih antar-ukuran: - Variasi diameter koin sejenis pada `koin_100.png` paling besar **0,21%** dari rata-rata kelompoknya. - Selisih antar-ukuran yang berdekatan **19,7–22,0%**. - Pada uji C (bagian 6), ukuran berbeda sekitar 7%. Toleransi 8% menggabungkan ukuran-ukuran itu, sedangkan 3% memisahkannya dengan benar. ## 5. Hasil Output program pada `koin_100.png`: ``` Jumlah koin terdeteksi: 100 Hasil pengelompokan ukuran: Ukuran | Jumlah koin | Diameter rata-rata (piksel) 1 | 30 | 137.17 2 | 27 | 167.29 3 | 25 | 202.27 4 | 18 | 242.21 ``` | Ukuran | Diameter tempel | Jumlah sebenarnya | Jumlah dari program | |---|---|---|---| | 1 | 140 | 30 | 30 | | 2 | 170 | 27 | 27 | | 3 | 205 | 25 | 25 | | 4 | 245 | 18 | 18 | Semua jumlah tepat. File output di folder `output/`: - `jumlah_koin_per_ukuran.csv`: tabel hasil. - `mask_koin.png`: hasil segmentasi setelah pengisian lubang. - `anotasi_koin.jpg`: setiap koin dilingkari dan diberi nomor ukuran, dengan warna berbeda per ukuran. | Mask segmentasi | Anotasi | |---|---| | ![mask](output/mask_koin.png) | ![anotasi](output/anotasi_koin.jpg) | ## 6. Pengujian Tambahan Untuk menguji ketahanan program, dibuat 4 gambar sintetis tambahan, masing-masing berisi koin berbentuk lingkaran yang tidak bersentuhan dengan noise Gaussian. Setiap gambar diuji dengan versi awal program (tanpa pengisian lubang, tanpa `clear_border`, toleransi 8%) dan versi akhir. | Uji | Kondisi | Jumlah sebenarnya | Versi awal | Versi akhir | |---|---|---|---|---| | A | Koin polos, 3 ukuran | 40 / 35 / 25 | 40 / 35 / 25 | 40 / 35 / 25 | | B | Koin dengan relief gelap di dalam | 40 / 35 / 25 | 200 koin, 6 ukuran | 40 / 35 / 25 | | C | 4 ukuran, selisih sekitar 7% | 30 / 25 / 25 / 20 | 55 / 45 | 30 / 25 / 25 / 20 | | D | 3 ukuran, ditambah 1 koin terpotong tepi | 40 / 35 / 24 + 1 terpotong | 40 / 36 / 24 | 40 / 35 / 24 | - **Uji B** menunjukkan pentingnya pengisian lubang. Relief membelah setiap koin menjadi cincin luar dan bagian dalam, sehingga satu koin terhitung dua. - **Uji C** menunjukkan pentingnya toleransi yang tepat. - **Uji D** menunjukkan pentingnya `clear_border`. Pada versi akhir, program juga mencetak peringatan karena jumlah koin utuh (99) berbeda dari 100. ## 7. Batasan - **Koin bersentuhan.** Koin yang bersentuhan menjadi satu komponen dan terhitung satu koin besar. Ini sesuai asumsi soal. Untuk kasus bersentuhan diperlukan teknik pemisahan seperti watershed. - **Parameter bergantung pada gambar.** `MIN_COIN_AREA` (100 piksel) bergantung pada resolusi. Toleransi 3% bergantung pada seberapa mirip ukuran-ukuran koin dan seberapa besar variasi pengukuran. Pada foto asli, kedua nilai ini perlu dicek ulang. - **Ukuran dalam piksel.** Diameter dinyatakan dalam piksel. Untuk ukuran dalam milimeter diperlukan benda acuan berukuran diketahui di dalam gambar. - **Perspektif.** Jika foto diambil miring, koin tampak elips dan koin yang lebih jauh tampak lebih kecil. Foto sebaiknya diambil tegak lurus dari atas.