Deteksi dan Membaca Plat Nomor Kendaraan Secara Otomatis menggunakan Python
Sudah lama sekali rasanya sejak terakhir kali saya menulis di blog ini, terakhir kali adalah di tahun 2019. Sekarang rasaya saya pengen nulis lagi, rutin lagi seperti dulu seperti saat sebelum diberikan kerjaan freelance dan tugas kuliah yg banyak, jadi agak terbengkalai ini blog mana domainnya masih panjang kan mubadzir hehe.. Jadi mari mulai nulis lagi mumpung dah lulus kerjaan freelance juga jadi masih bisa atur waktu yg fleksibel..
Pada postingan ini saya akan membahas tugas akhir ketika saya kuliah yaitu deteksi dan membaca plat nomor kendaraan secara otomatis.
Pada awalnya proyek ini ditulis menggunakan Matlab, karena dulu ketika belajar mata kuliah computer vision dosen saya menggunakan Matlab. Matlab bagus sekali untuk digunakan penelitian, praktis, namun sayangnya kebutuhan di industri lebih memilih menggunakan Python, semua lowongan pekerjaan yg saya lihat tentang image processing atau computer vision semuanya mencantumkan Python dan saya belum lihat ada yang mencantumkan Matlab. Jadi karena itu lah saya mencoba belajar Python dan sebagai latihannya saya mencoba menulis ulang kode program tugas akhir ini ke dalam bahasa tersebut.
Tidak semua saya tulis ulang sih, beberapa langkah ada yg saya lewati atau saya ubah, yang terpenting goalnya sama, bisa deteksi dan membaca plat nomor juga belajar hal baru.
Apa saja yang diperlukan?
Ini adalah beberapa perangkat dan bahan yang saya gunakan untuk membuat proyek ini, diantaranya:
- Laptop Dell G3, dengan spesifikasi:
- Intel core i5 8300H
- Ram 16 GB
- VGA Nvidia GTX 1050 4GB
- SSD 256 GB
- Smartphone Zenfone 3 Max, dengan spesifikasi kamera:
- Resolusi 13 MP
- Aperture f/2.2, AF
- Citra test kendaraan yang diambil sendiri di parkiran kampus, dengan ketentuan:
- Diambil ketika pagi - sore
- Jarak pengambilan gambar sekitar 1 meter, plat hampir lurus.
- Resolusi 2560 x 1920 pixels
- Jumlah 60 gambar.
- Citra karakter dari 0-9 A-Z untuk training, saya lupa sumbernya dari mana karena sudah ada di laptop saya dari dulu, kalau saya ingat akan saya cantumkan sumbernya di sini.
- Setiap karakter berisi 10 citra, berukuran 28 x 28 piksel
- Untuk kode program saya membutuhkan:
- Python 3.8.6
- OpenCV 4.5.1
- NumPy 1.19.4
- Tensorflow 2.4.0
- Matplotlib
Metode yang digunakan
Secara garis besar, bagaimana cara saya mendapatkan hasil deteksi dan pembacaan plat nomor adalah sebagai berikut:
Prapengolahan, foto RGB kendaraan dimasukkan ke sistem (OpenCV akan membacanya BGR), di-resize, diubah menjadi citra grayscale, karena cahaya saat pengambilan gambar tidak selalu sama, lakukan normalisasi kondisi cahaya, lalu konversi menjadi citra BW (hitam-putih) dengan menggunakan pengambanagan Otsu.
Deteksi plat, di tugas akhir, saya menggunakan profile projection untuk melakukan deteksi plat, namun karena pengen nyoba sesatu hal yang baru di sini saya menggunakan contours, dari sini kita bisa mendapatkan area berdasarkan nilai piksel yang sama yang saling berhubungan, termasuk area plat nomornya. Untuk mendapatkan area plat nomor, saya memfilter area tersebut dengan membandingkan lebar dan aspect ratio.
Segmentasi karakter, karakter yang saya ambil di proyek ini adalah baris pertama pada bagian plat nomor, yaitu bagian yang memuat nomor unik setiap kendaraan. Untuk mendapatkan setiap karakternya, saya menggunakan cara yang sama seperti deteksi plat, yaitu dengan contours, hanya saja filter yang digunakan adalah dari tinggi dan lebar area.
Klasifikasi karakter, di tugas akhir, saya sudah mencoba menggunakan cosine similarity namun jujur saja hasilnya jelek, banyak yg misklasifikasi apalagi untuk karakter yang bentuknya mirip, seperti B dan angka 8. Oleh karena itu untuk sekarang saya pengen coba teknik yang lain, saya coba pakai model yang ada di tutorial Tensorflow ini.
Proyek ini bisa dipisah menjadi dua bagian, yaitu bagian pelatihan/training, dan bagian testing. Bagian pelatihan digunakan untuk melatih model agar bisa digunakan untuk mengklasifikasi karakter. Dan bagian testing digunakan untuk deteksi dan membaca plat nomor menggunakan model yang sudah terlatih.
Nanti akan saya bandingkan juga dengan metode yg sudah saya pakai pada tugas akhir saya.
Hasil Per Tahap
Prapengolahan
Kita ketahui bahwa cuaca atau intensitas cahaya setiap harinya sulit untuk ditebak, citra hasil pengambilan data tidak selalu memiliki level cahaya yang sama, ada yang lebih terang ada yang lebih gelap, bahkan dalam satu citra kendaraan pun terkadang terdapat bagian yang terhalang oleh bayangan sehingga apabila tidak dilakukan prapengolahan mungkin bisa mengacaukan proses selanjutnya. Proses ini digunakan untuk mempersiapkan citra agar mudah dalam mendeteksi plat.
Citra RGB diload ke sistem (OpenCV akan membacanya sebagai BGR), citra tersebut diresize karena rasanya sulit untuk mengamati hasilnya apabila ukurannya terlalu besar saat ditampilkan di layar (hampir full menutupi satu layar). Saya resize dengan mengalikan lebar dan tingginya dengan nilai 0.4 sehingga yang asalnya berukuran 1920 x 2560 sekarang menjadi 768 x 1024, lebih kecil, tidak menutupi hampir seluruh layar ketika menampilkannya.
Sekarang citra akan dilakukan normalisasi cahaya, fungsinya agar intensitas cahayanya sama, lebih mudah untuk dilanjutkan ke proses berikutnya. Normalisasi cahaya ini prosesnya dimulai dari mengubah citra RGB ke grayscale, lakukan operasi opening ke citra grayscale, lalu kurangkan citra grayscale dengan citra hasil opening, hasil pengurangan tersebut selanjutnya bisa dikonversi ke citra BW (hitam putih) dengan pengambangan Otsu.
Bisa dilihat pada gambar di bawah perbedaan citra BW hasil normalisasi dan tanpa normalisasi, bagian plat dan karakter yang dihasilkan pun lebih jelas apabila dilakukan normalisasi.
Proses Prapengolahan |
Deteksi Plat
Dari hasil pengamatan saya pada hasil prapengolahan, saya melihat bahwa bagian plat nomor membentuk garis kotak dan memiliki intensitas yang sama.
Pada skripsi saya menggunakan cara profile projection. Jadi saya bikin grafik untuk menghitung setiap piksel putih yang ada pada setiap baris, lalu lakukan pemotongan berdasarkan titik yang tertinggi. Lalu lakukan hal yang sama untuk kolom.
Kira-kira prosesnya seperti ini:
Profile Projection Plat |
Untuk sekarang karena saya ingin mencoba cara baru, saya akan memakai fungsi contours, karena jika saya lihat fungsi ini lumayan cocok juga untuk melakukan deteksi plat dari hasil citra prapengolahan yang mana kontur atau area plat bisa terlihat dengan lumayan jelas, saling berhubungan membuat suatu area.
Dari semua area kontur pada citra, untuk mendapatkan area plat nomor, saya filter berdasarkan aspect ratio dan lebarnya, jika lebarnya lebih dari atau sama dengan 200 piksel dan aspect rationya kurang dari atau sama dengan 4.
Deteksi plat nomor |
Hasilnya? Dari 60 citra 2 diantaranya tidak memuaskan.
Hasil deteksi plat nomor |
Apabila kita bandingkan hasil dari profile projection dan contours, maka profile projection akan menghasilkan pemotongan yang lebih rapih dan pas, contohnya untuk plat di bawah ini:
Segmentasi Karakter
Contours juga saya gunakan untuk segmentasi karakter. Citra hasil crop diubah ke citra BW, lalu karena hasilnya terdapat area piksel yang tidak diinginkan maka lakukan operasi opening, operasi ini akan menghilangkan area kecil dekat karakter.
Untuk mendapatkan kandidat karakternya lakukan filter terhadap kontur dengan berdasarkan lebar dan tingginya, yaitu apabila tingginya dalam rentang 40-60 piksel dan lebarnya lebih dari 10 piksel.
Apabila dilihat dari hasil di atas, kandidat karakter memiliki bagian yang bukan karakter. Untuk mendapatkan bagian yang benar sebuah karakter, kita bisa mengecek apakah bagian tersebut sebaris, berderet secara horizontal, atau memiliki letak sumbu y yang tidak jauh berbeda.
Kita bisa menghitung selisih letak sumbu y antara kandidat satu dengan yang lainnya, setiap kandidat yang selisihnya kurang dari 11 piksel maka mendapatkan 1 score/poin. Kandidat yang memiliki score yang sama dan tertinggi maka itu adalah karakter yang sesungguhnya.
Score karakter yang sama dan tertinggi |
Dari sini kita sudah memiliki bagian yang benar-benar sebuah karakter. Namun sayangnya karena kita menggunakan contours, karakter tersebut tidak berurutan dari kiri ke kanan melainkan dari atas ke bawah, oleh karena itu mari kita urutkan. Pengurutan ini penting sekali karena menentukan urutan karakter yang nantinya akan diklasifikasi oleh model, kalo urutannya ngaco meskipun klasifikasi karakternya benar ya tetap saja ngaco.
Bagaimana dengan tugas akhir saya? Di tugas akhir saya menggunakan profile projection, prosesnya sama seperti pada deteksi plat, hanya saja disini kita perlu memfilter lagi untuk mendapatkan bagian yang benar-benar karakternya.
Profile Projection Karakter |
Klasifikasi karakter
Untuk klasifikasi karakter, di sini saya menggunakan model dari tutorial Tensorflow ini, jadi kita bikin modelnya terlebih dahulu, lalu melatihnya dengan dataset karakter. Hasil dari model terlatih bisa digunakan untuk klasifikasi karakter
Pelatihan Model |
Dan hasil klasifikasinya seperti ini:
Jadi memang ada banyak hal yang mempengaruhi hasilnya, kalau dari data diatas, dataset yang lebih banyak memang sangat disarankan agar klasifikasi lebih baik. Jika memakai dataset yang sedikit maka performanya tidak jauh beda dengan memakai cosine similarity yang hanya memakai 36 dataset saja.
Kebanyakan misklasifikasi adalah karakter yang mirip seperti 1 dengan I atau B dengan 8, solusinya bisa memisahkan karakter ke dalam 3 bagian, yaitu huruf awal, angka tengah, dan huruf akhir. Selain itu juga karena hasil prapengolahan atau segmentasi yang kurang bagus.
Saya pernah melakukan percobaan pemisahan karakter menjadi 3 bagian dan memperbaiki prapengolahannya, dan memang terbukti bagus hasilnya, untuk klasifikasinya menggunakan CNN di Matlab dengan dataset berjumlah 1840, sayangnya saya hanya memiliki hasil datanya saja, kodenya sudah tidak bisa saya jalankan lagi, matlabnya berbayar euy 😢 mana lupa metodenya gimana aja dan kodenya pun tidak bersih (acak-acakan), jadi pengingat kalau eksperimen mending langsung ditulis dan sekalian nulis kode yg bersih.
Hasil Data CNN |
Kode Sumber
Bagi temen-temen yang menginginkan kode sumber beserta penjelasannya, temen-temen bisa mendownloadnya di halaman Github saya.
Terimakasih banyak yang sudah membaca.