Text Recognize with ML Kit
Simple OCR with ML Kit
Introduction
Pada acara Google I/O 18 ada salah satu teknologi di Machine Learning yang diperkenalkan oleh Google yaitu ML Kit. ML Kit bisa saya katakan ini merupakan Machine Learning sederhana yang ready to use oleh mobile developer tanpa perlu susah-susah setup config disana sini. Tapi, jikalau ML Kit belum bisa menangani semua case yang ada pada app sedang kita kembangkan maka, kita bisa gunakan TensorFlow Lite dimana, dengan TensorFlow Lite kita bisa masukkan model-model yang akan dipakai pada Machine Learning kita. Lalu, beberapa fitur yang bisa dilakukan oleh ML Kit adalah sebagai berikut.
- Image Labeling
Image Labeling berfungsi untuk mengetahui didalam image atau foto tersebut ada apa saja misal, fotonya selfie, lalu lokasinya dimana, dan disekitar fotonya ada binatang apa saja. - Text Recognition (OCR)
Text Recoginition atau OCR (Optical Character Recognition) berfungsi untuk membaca teks atau karakter dari sebuah gambar. - Face Detection
Face Detection berfungsi untuk bisa membaca setiap bentuk wajah manusia mulai dari bentuk mata, hidung, mulut, dan lain-lain sebagainya. - Barcode Scanning
Barcode Scanning berfungsi untuk membaca barcode yang ada. - Landmark Detection
Landmark Detection berfungsi untuk membaca dan mengetahui info dari gedung-gedung atau bangunan-bangunan terkenal. - Smart Reply
Smart Reply pada saat penulisan artikel ini masih coming soon fiturnya. Jadi, saya belum tahu fiturnya seperti apa.
Sample Project
Create Project
Untuk sample projek kali ini kita akan buat contoh program yang bisa membaca teks dari gambar baik itu dari hasil foto maupun foto yang diupload.
Langkah pertama, buat projek baru di Android Studio dan beri nama seperti berikut.
Setelah selesai buat projek baru tersebut kita lanjut atur file build.gradle(Module: app)-nya dan tambahkan dependency-dependency berikut.
/* ML Kit */
implementation 'com.google.firebase:firebase-core:16.0.4'
implementation 'com.google.firebase:firebase-ml-vision:18.0.1'
/* Camera View */
implementation 'com.otaliastudios:cameraview:1.6.0'// ... tambahkan kode apply plugin ini berada di akhir baris
apply plugin: 'com.google.gms.google-services'
Jadi, untuk menggunakan ML Kit kita harus menambahkan firebase-core
dan firebase-ml-vision
. Lalu, untuk pengambilan gambar dari kamera kita pakai library yang ready to use saja dimana, kita pakai CameraView
milik otaliastudios.
Berikutnya, ubah juga file build.gradle(Project) dan tambahkan dependency classpath 'com.google.gms:google-services:4.0.1'
Mungkin, setelah selesai sync kita akan mendapatkan pesan error berikut ya atau jika tidak dapat pesan error ini pun juga no problem ya.
Jadi, jika kita baca pesan error diatas maksudnya adalah di projek kita tidak ada file google-services.json yang mana jika kita ada pakai plugin google services maka harus ada file tersebut. Dan kebetulan kita belum buat ini jadi, don’t worry sama pesan error tersebut.
Create Project Firebase Console
Jadi, di langkah ini kita akan buat file google-services.json. Caranya adalah kita buka https://console.firebase.google.com lalu kita buat proyek baru di Firebase Console seperti berikut.
Setelah itu silakan tekan button lanjut dan tunggu hingga proyek kita selesai dibuat di Firebase Console.
Selanjutnya kita atur proyek tersebut agar bisa dipakai di Android. Pertama, kita pilih Setelan proyek.
Lalu, pilih Tambahkan Firebase ke aplikasi Android Anda seperti berikut.
Selanjutnya, kita masukkan nama package dari aplikasi android yang sudah kita buat tadi di Android Studio. Apabila kita nggak tahu nama package dari aplikasi yang sudah kita buat di Android Studio caranya, bisa kita buka file AndroidManifest.xml dan lihat pada nilai attribut applicationId seperti gambar berikut.
Lalu, jika kita sudah tahu nama package dari aplikasinya maka, langkah selanjutnya adalah masukkan nama package tersebut ke firebase console yang kita buat tadi.
Lalu, kita unduh file google-services.json dan letakkan pada projek kita di Android Studio pada direktori root modul aplikasi android.
Lalu, di tahap berikunya kita ada disuruh untuk menambahkan firebase SDK ke projek aplikasi di Android Studio namun, langkah ini sebenarnya sudah kita lakukan diawal jadi abaikan saja ya.
Lalu, akan muncul verifikasi instalasi seperti berikut dan pada tahap ini kita lewati saja langkahnya.
Atur file AndroidManifest.xml
Selanjutnya, kita tambahkan kode <meta-data>
untuk mendeklarasikan ocr milik si ML Kit kedalam file AndroidManifest.xml seperti berikut.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ysn.mlkitocr">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<meta-data
android:name="com.google.firebase.ml.vision.DEPENDENCIES"
android:value="ocr" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Buat layout activity_main.xml
Selanjutnya, kita buat layout dari file activity_main.xml seperti berikut.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.otaliastudios.cameraview.CameraView
android:id="@+id/camera_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/relative_layout_panel_overlay_camera"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scaleType="fitXY"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/relative_layout_panel_overlay_result"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<RelativeLayout
android:id="@+id/relative_layout_panel_overlay_result"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_view">
<TextView
android:id="@+id/text_view_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:padding="16dp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/relative_layout_panel_overlay_camera"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/camera_view">
<Button
android:id="@+id/button_take_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="16dp"
android:text="Take Picture" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Buat menu options
Pada contoh projek ini kita akan membuat 2 mode yaitu mode kamera dan upload dari gallery. Jadi, kita buat menu-nya dengan nama file menu_activity_main.xml dimana, didalamnya ada 2 item yaitu, Camera dan Upload Photo.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_item_camera"
android:icon="@drawable/ic_camera_black_24dp"
android:orderInCategory="1"
android:title="@string/camera" />
<item
android:id="@+id/menu_item_upload_photo"
android:icon="@drawable/ic_add_a_photo_black_24dp"
android:orderInCategory="2"
android:title="@string/upload_photo" />
</menu>
Untuk icon-nya itu saya ambil dari aset di Android Studio jadi, silakan buat sendiri ya. 😉
It’s time to ML Kit
Di langkah ini kita buat agar app kita bisa membaca teks dari gambar yang sudah kita ambil dari kamera maupun upload dari gallery. Silakan buka file MainActivity.kt dan isi dengan source code berikut.
package com.ysn.mlkitocr
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.google.firebase.ml.vision.FirebaseVision
import com.google.firebase.ml.vision.common.FirebaseVisionImage
import com.google.firebase.ml.vision.text.FirebaseVisionText
import com.otaliastudios.cameraview.Audio
import com.otaliastudios.cameraview.CameraListener
import com.otaliastudios.cameraview.CameraUtils
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val TAG = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initCameraView()
initListeners()
val permissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, 100)
} else {
ActivityCompat.requestPermissions(this, permissions, 100)
}
}
override fun onPause() {
if (camera_view.isStarted) {
camera_view.stop()
}
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_activity_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean =
when (item?.itemId) {
R.id.menu_item_camera -> {
showCameraView()
true
}
R.id.menu_item_upload_photo -> {
showGalleryView()
true
}
else -> {
/* nothing to do in here */
super.onOptionsItemSelected(item)
}
}
private fun initListeners() {
camera_view.addCameraListener(object : CameraListener() {
override fun onPictureTaken(jpeg: ByteArray?) {
camera_view.stop()
CameraUtils.decodeBitmap(jpeg) { bitmap ->
image_view.scaleType = ImageView.ScaleType.FIT_XY
image_view.setImageBitmap(bitmap)
val image = FirebaseVisionImage.fromBitmap(bitmap)
val textRecognizer = FirebaseVision.getInstance()
.onDeviceTextRecognizer
textRecognizer.processImage(image)
.addOnSuccessListener {
camera_view.visibility = View.GONE
image_view.visibility = View.VISIBLE
relative_layout_panel_overlay_camera.visibility = View.GONE
relative_layout_panel_overlay_result.visibility = View.VISIBLE
processTextRecognitionResult(it)
}
.addOnFailureListener {
showToast(it.localizedMessage)
}
super.onPictureTaken(jpeg)
}
}
})
button_take_picture.setOnClickListener {
camera_view.captureSnapshot()
}
}
private fun initCameraView() {
camera_view.audio = Audio.OFF
camera_view.playSounds = false
camera_view.cropOutput = true
}
private fun showCameraView() {
camera_view.start()
camera_view.visibility = View.VISIBLE
image_view.visibility = View.GONE
relative_layout_panel_overlay_camera.visibility = View.VISIBLE
relative_layout_panel_overlay_result.visibility = View.GONE
}
private fun showGalleryView() {
if (camera_view.isStarted) {
camera_view.stop()
}
camera_view.visibility = View.GONE
image_view.visibility = View.GONE
relative_layout_panel_overlay_camera.visibility = View.GONE
relative_layout_panel_overlay_result.visibility = View.GONE
val intentGallery = Intent()
intentGallery.type = "image/*"
intentGallery.action = Intent.ACTION_GET_CONTENT
val intentChooser = Intent.createChooser(intentGallery, "Pick Picture")
startActivityForResult(intentChooser, 100)
}
private fun processTextRecognitionResult(firebaseVisionText: FirebaseVisionText) {
text_view_result.text = firebaseVisionText.text
}
private fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT)
.show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
100 -> {
val uriSelectedImage = data?.data
val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
val cursor = contentResolver.query(uriSelectedImage!!, filePathColumn, null, null, null)
if (cursor == null || cursor.count < 1) {
return
}
cursor.moveToFirst()
val columnIndex = cursor.getColumnIndex(filePathColumn[0])
if (columnIndex < 0) {
showToast("Invalid image")
return
}
val picturePath = cursor.getString(columnIndex)
if (picturePath == null) {
showToast("Picture path not found")
return
}
cursor.close()
Log.d(TAG, "picturePath: $picturePath")
val bitmap = BitmapFactory.decodeFile(picturePath)
image_view.setImageBitmap(bitmap)
val image = FirebaseVisionImage.fromBitmap(bitmap)
val textRecognizer = FirebaseVision.getInstance()
.onDeviceTextRecognizer
textRecognizer.processImage(image)
.addOnSuccessListener {
camera_view.visibility = View.GONE
image_view.visibility = View.VISIBLE
relative_layout_panel_overlay_result.visibility = View.VISIBLE
relative_layout_panel_overlay_camera.visibility = View.GONE
image_view.scaleType = ImageView.ScaleType.CENTER_CROP
processTextRecognitionResult(it)
}
.addOnFailureListener {
showToast(it.localizedMessage)
}
}
else -> {
/* nothing to do in here */
}
}
}
}
}
Penjelasan:
- Didalam method
onCreate
kita ada minta runtime permission kamera dan baca eksternal storage. Lalu, didalamnya ada juga kita setupCameraView
agar ketika ambil dari kamera hasilnya jadi terpotong (crop) sesuai dengan layout kita buat. - Didalam method
initListeners
kita buatCameraListener
ketikaCameraView
berhasil mengambil gambar dan didalamnya ada fungsi membaca teks dimana, kita masukkan gambar kita ke objekFirebaseVisionImage
dengan sumber gambarnya daribitmap
lalu, kita deklarasikan objekFirebaseVision
dengan metodeonDeviceTextRecognizer
. Jadi, kita setFirebaseVision
agar melakukan pembacaan teksnya dengan source-nya berasal dari device. Selain dari device, kita juga bisa pakai metodecloudTextRecognizer
namun, untuk menggunakancloudTextRecognizer
kita perlu melakukan pembayaran alias tidak gratis. Selanjutnya, kita proses pembacaan teksnya dengan syntaxprocessImage
dan tambahkan callbacksuccess
danfailure
. - Didalam callback
success
kita ada panggil methodprocessTextRecognitionResult
dimana, isi dari method tersebut adalah untuk meng-set teks ketext_view_result
dan didalam callbackfailure
kita ada panggil methodshowToast
yang mana isi dari method tersebut adalah untuk menampilkanToast
dengan pesan error-nya dari callbackfailure
.
Done
Berikut adalah output dari program yang sudah kita buat.
Untuk projeknya sudah saya upload ke Github.