Text Recognition (Metin Tanıma) App — ML Kit | Google Developers
ML Kit’in metin tanıma API’leri ile Latin tabanlı herhangi bir karakter kümesindeki metni tanıyabilir. Kredi kartları, makbuzlar ve kartvizitler gibi veri girişi görevlerini otomatikleştirmek için de kullanılabilirler.
1- Gerekli Kütüphanelerin Eklenmesi
Öncelikle build.gradle(app) içinde dependencies içine aşağıdaki kod bloğunu ekleyerek ML Kit tarafından gerekli olan özellikleri projemize eklemiş oluruz.
implementation 'com.google.android.gms:play-services-mlkit-text-recognition:17.0.0'
2- Tasarım
android:orientation=”vertical” ile tüm görsel ve içeriklerin alt alta olmasını, android:orientation=”horizontal” ile içindeki tüm nesnelerin yanyana olmasını sağlarız.
android:orientation=”vertical” olarak yaptığımızda yüksekliği “0dp” olarak veriyoruz ve layout_weight özelliğini kaplayacağı alan kadar oran veriyoruz! android:orientation=”horizontal” olarak yaptığımızda genişliği “0dp” olarak veriyoruz artık layout_weight özelliği genişlikte kaplayacağı oranı veriyoruz!
Tüm komponentlerin kolay kontrolü için hepsini bir LinearLayout içine alıyoruz. Bu şekilde LinearLayout için ayrı özellikler verebiliyoruz verdiğimiz özellikler içinde bulunan komponent için de geçerli oluyor!
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:background="@color/white"
tools:context=".MainActivity">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.3">
</ImageView>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.4"
android:layout_margin="10dp"
android:textColor="@color/black"
android:textSize="16sp">
</TextView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.15"
android:gravity="center">
<LinearLayout
android:id="@+id/detect"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/detect">
</ImageView>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.15"
android:gravity="center"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/camera"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:gravity="center">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/camera">
</ImageView>
</LinearLayout>
<LinearLayout
android:id="@+id/gallery"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:gravity="center">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/gallery">
</ImageView>
</LinearLayout>
</LinearLayout>
</LinearLayout>
3- Tasarım Fonksiyonları
Tasarım kısmında oluşturduğumuz komponentleri ilk olarak tanımlıyoruz. Tanımlanan değişkenler findViewById ile tasarım bölümümüzdeki verdiğim ID’ler ile değişkenlerimize bağlıyoruz. Arık bu değişkenler bizim komponentleri kullanmak istediğimiz tanımlar haline geliyor.
Tıklama özelliği olan detect (büyüteç görseli), kamera ve galeri butonları setOnClickListener ile tıklama özelliği kazandırıyoruz.
Kazandıkları tıklama özelliği sonucunda ne olacağını vermek için OnClick fonksiyonunu Override etmemiz gerekiyor. Switch-case yapımız içinde ID özelliğine göre tıklanan butonun ne olduğunu buluyoruz. Aşağıda olduğu gibi detect butonumuza basıldığında ilerde oluşturacağımız runTextRecognition(); fonksiyonumuz çalışmasını istiyoruz. Yine kamera butonumuza basıldığında askCameraPermissions(); , galeri butonumuza basıldığında galleryAddPic(); fonksiyonumuz çalışmasını istiyoruz. Bu fonksiyonları verdiğimiz sınıf içinde oluşturmamız ve aynı isimde olmasına dikkat etmemiz gerekiyor!
Switch içinde view.getId() yapmamız gerekiyor çünkü case içinde dikkat edecğimiz özellik ID isimleridir. Ve case içinde de break özelliğini koymayı unutmayın!
LinearLayout cameraLayout, detectLayout, galleryLayout;
ImageView imgView;
TextView textArea;@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);detectLayout = findViewById(R.id.detect);
cameraLayout = findViewById(R.id.camera);
galleryLayout = findViewById(R.id.gallery);imgView = findViewById(R.id.image);
textArea = findViewById(R.id.text);detectLayout.setOnClickListener(this);
cameraLayout.setOnClickListener(this);
galleryLayout.setOnClickListener(this);
}@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.detect:
try {
runTextRecognition();
}
catch (Exception e){
Toast.makeText(MainActivity.this, "Görsel Seçilmedi", Toast.LENGTH_SHORT).show();
} break;
case R.id.camera:
askCameraPermissions();
break;
case R.id.gallery:
galleryAddPic();
break;
}
}
4- Kamera Fonksiyonları
Kamera logosuna tıkladığımızda kameranın açılmasını bekliyoruz. Kamera açılması için Android içindeki Intent özelliğini kullanııyoruz. Intent içine verdiğimiz MediaStore.ACTION_IMAGE_CAPTURE özelliği telefonumuz içindeki kamera uygulamasının açılmasını sağlıyor. Bu Intent özelliğimizin çalışması için startActivityForResult ile Intent özelliğini başlatıyoruz.
REQUEST_IMAGE değişkeni bizim requestCode olarak adlandırdığımız değişkendir. İstenilen int değeri verilir. İşlemin başarıya ulaşması halinde verdiğimiz int değeri bize döndürülür. Döndürülen int değeri bizim verdiğimiz ile aynıysa işlemler devam edebiliriz. Bu bizim daha güvenli kod yazmamızı sağlar!
static final int REQUEST_IMAGE = 0;
private void dispatchTakePictureIntent(){
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, REQUEST_IMAGE);
}
Kamera izni bizim kullanıcıdan almamız gereken bir izindir. Ve kullanıcı bir defa izin verdiğinde artık her kamera açılışında bu izni kullanıcıdan almamamız gerekiyor. Bunun için aşağıdaki kamera izin kontrolünü if içinde yapıyoruz eğer kamera iznimiz varsa else bloğu içinde yukarı bizim kameramızı açan fonksiyonu çağırıyoruz.
static final int CAMERA_PERM = 2;private void askCameraPermissions(){
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.CAMERA
},CAMERA_PERM);
}
else {
dispatchTakePictureIntent();
}
}
Peki kullanıcı verilen izni kabul etmezse? Aşağıdaki kod bloğı bizim kullanıcının karşısına kameraya ulaşmamıza izin verip vermeyeceğini sorgulamasını yapacağımız fonksiyondur. Bunun için Android içinde onRequestPermissionsResult fonksiyonunu Override ediyoruz! If bloğunda onay verdiğinde yine kameramızı açan fonksiyonu çağırırken, onay vermediğinde kullanıcıyı Toast mesajı ile bilgilendiriyoruz!
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
dispatchTakePictureIntent();
}
else{
Toast.makeText(this, "Kamera İzni Kabul Edilmedi", Toast.LENGTH_SHORT).show();
}
}
Son olarak aşağıdaki kod bloğunu AndroidManifest.xml dosyası içinde <application…></application> üstüne yapıştırmanız gereklidir!
<uses-permission android:name=”android.permission.CAMERA”></uses-permission>
5- Galeri Fonksiyonları
Kamera da olduğu gibi Intent özelliğini kullanarak bu seferde telefonumuzun galerisini açmamız gerekiyor. Intent içine verdiğimiz Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI özelliği ile artık butona bastığımızda telefonumuzun galerisi açılacaktır. Bunu başlatmamız için startActivityForResult ile başlatmamız gerekiyor!
REQUEST_GALLERY değişkeni bizim requestCode olarak adlandırdığımız değişkendir. İstenilen int değeri verilir. İşlemin başarıya ulaşması halinde verdiğimiz int değeri bize döndürülür. Döndürülen int değeri bizim verdiğimiz ile aynıysa işlemler devam edebiliriz. Bu bizim daha güvenli kod yazmamızı sağlar!
static final int REQUEST_GALLERY = 1;private void galleryAddPic(){
Intent mediaScanIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(mediaScanIntent, REQUEST_GALLERY);
}
Galeriden alınan görseller için aşağıdaki izinleri AndroidManifest.xml dosyası içinde <application…></application> üstüne yapıştırmanız gereklidir!
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE”></uses-permission>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”></uses-permission>
6- Kamera-Galeri Özellikleri
Sıra kamera ile alınan ve galeri içinden seçilen görseli tasarım kısmında oluşturduğumuz ImageView içine eklememiz gerekiyor! Bu eklemeyi yaptığımızda artık ImageView içinden alınan görsel ile metin tespitini yapmamız gerekiyor.
Verdiğimiz requestCode değişkeni içinde REQUEST_IMAGE ise yani kullanıcı kameradan görsel aldığında fonksiyonumuz içinde tuttuğumuz data özelliğini Bitmap değişkenine dönüştürüp bunu ImageView içine ekleyebilir hale getiriyoruz. Öncesinde resultCode değişkeni RESULT_OK mi? Kontrolünü yaptığımızda daha güvenli bir şekilde data aldığımızı öğreniriz.
Galeriden alınan görseli eklememiz için REQUEST_GALLERY kontrolünü yaptıktan sonra öncelikle bir URI değişkenine ardından InputStream ve InputStream değişkeninin Bitmap değişkenine çevirmemiz gerekiyor. Bu adımlar sırasıyşa aşağıdaki kod bloğunda yapılıyor!
Peki kullanıcı galeriyi açıp hiç bir görsel seçmeden çıktığında? Bunun içinse fonksiyonumuzu try-catch içinde yazmamız gerekiyor. Herhangi bir seçmeme durumunda kullanıcıya “Görsel Galeriden Alınamadı” uyarısını Toast mesajı olarak göstereceğiz!
Bitmap galleryImage;
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case REQUEST_IMAGE:
if (resultCode == RESULT_OK){
Bitmap selectedImage = (Bitmap) data.getExtras().get("data");
imgView.setImageBitmap(selectedImage);
}
break;
case REQUEST_GALLERY:
try {
Uri imageUri = data.getData();
InputStream imageStream = getContentResolver().openInputStream(imageUri);
galleryImage = BitmapFactory.decodeStream(imageStream);
imgView.setImageBitmap(galleryImage);}catch (Exception e){
Toast.makeText(this, "Görsel Galeriden Alınamadı",Toast.LENGTH_SHORT).show();
}
break;
}
}
7- Metinden Yazı Tespiti Fonksiyonu
Bu kısımda ML Kit tarafından verilen TextRecognizer fonksiyonuna ImageView içine eklediğimiz görseli vereceğiz. Bu fonksiyon başarıya ulaştığında processTextRecognition(visionText); fonksiyonu ile artık görsel içinden nesneleri tespit etmeye başlayabiliriz!
private void runTextRecognition(){
Bitmap bitmap = ((BitmapDrawable) imgView.getDrawable()).getBitmap();
int rotationDegree = 0;InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);TextRecognizer recognizer = TextRecognition.getClient();Task<Text> result =
recognizer.process(image)
.addOnSuccessListener(new OnSuccessListener<Text>() {
@Override
public void onSuccess(Text visionText) {
processTextRecognition(visionText);
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(MainActivity.this, "Başarısız İşlem",Toast.LENGTH_SHORT).show();
}
});
}
Metinler yukarıda görüldüğü gibi bloklar halinde tespit edilir sonra satırlara ayrılır ve satırlar içindeki tüm elementler bizim sözcüklerimizi gösterir!
Eğer hiç blok yoksa demekki görsel içinde hiç metnimiz yoktur ve bunu kullanıcıya uyarı olarak verebiliriz!
Eğer bloklar varsa bunu öncelikle for döngüsü ile line değişkenleri listesine ayırmamız ardından line içinde bulunna elemntleri bir for döngüsü yardımıyla önceden oluşturduğumuz StringBuilder değişkenine append ile eklemeliyiz. Her eklemede araya bir boşluk koymayı da ihmal etmiyoruz!
Son olarak bunu tasarım kısmında eklediğimiz TextView içine setText ile eklememiz gerekiyor!
private void processTextRecognition(Text visionText) {
List<Text.TextBlock> blocks = visionText.getTextBlocks();
if (blocks.size() == 0){
Toast.makeText(MainActivity.this, "Görselde Metin Tespit Edilemedi",Toast.LENGTH_LONG).show();
}StringBuilder text = new StringBuilder();for (int i = 0; i<blocks.size();i++){
List<Text.Line> lines = blocks.get(i).getLines();
for (int j = 0; j<lines.size();j++){
List<Text.Element> elements = lines.get(j).getElements();
for (int k = 0; k<elements.size();k++){
text.append(elements.get(k).getText() + " ");
}
}
}
textArea.setText(text);
}
}