Merhabalar,


Bu yazıda Android'de Google Cloud Vision API kullanımı ile resimden yazı okutma (Text Recognition) işleminin nasıl yapılabileceğinden bahsedeceğim.


Örnek projeyi Github üzerinden paylaştım. İndirip inceleyebilirsiniz:


Örnek Proje Linki


İlk olarak Android Studio'da boş bir proje oluşturuyoruz. Sonrasında hemen build.gradle dosyamızı açarak gerekli kütüphaneleri projemize dahil ediyoruz:

// cardview
implementation 'com.android.support:cardview-v7:28.0.0'
// for floation action button
implementation 'com.android.support:design:28.0.0'
// image crop library
implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.+'
// image to text google library
implementation 'com.google.android.gms:play-services-vision:17.0.2'


activity_main.xml dosyamızı açarak basit bir tasarım yapıyoruz:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:gravity="center_horizontal"
    tools:context=".MainActivity">

    <android.support.v7.widget.CardView
        android:id="@+id/cvResult"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardBackgroundColor="#fff"
        app:cardCornerRadius="3dp"
        app:cardElevation="3dp"
        app:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="5dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Sonuç"
                android:textColor="@color/colorPrimary"
                android:textSize="20sp" />

            <EditText
                android:id="@+id/resultEt"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:autoLink="all"
                android:background="@null"
                android:hint=""
                android:padding="5dp"
                android:textColor="#000" />

        </LinearLayout>

    </android.support.v7.widget.CardView>

    <android.support.v7.widget.CardView
        android:layout_below="@id/cvResult"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardBackgroundColor="#fff"
        app:cardCornerRadius="3dp"
        app:cardElevation="3dp"
        app:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="5dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Resim Önizleme"
                android:textColor="@color/colorPrimary"
                android:textSize="20sp" />

            <ImageView
                android:id="@+id/imageIv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:maxHeight="250dp" />

        </LinearLayout>

    </android.support.v7.widget.CardView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="16dp"
        android:src="@android:drawable/ic_input_add"
        app:backgroundTint="#34495e" />

</RelativeLayout>

İşlemlerimiz şu şekilde gerçekleşecek:

  • Ekranda sağ altta bulunan FloatingActionButton'a basıldığında Kamera ve Galeri şeklinde iki seçenek karşımıza çıkacak.
  • Kameradan çekilen veya galeriden seçilen resim kırpılmak üzere Image Cropper kütüphanesinin sağladığı ekrana gönderilecek.
  • Kırpılan resimdeki yazı Vision API ile okunarak ekranda bulunan EditText'e basılacak. Böylece yazı üzerinde herhangi bir değişiklik yapılma imkanı da kullanıcıya sağlanmış olacak.


Uygulamada kamera ve galeriyi kullanmamız gerektiğinden AndroidManifest.xml dosyamızı açarak gerekli izinleri dahil ediyoruz. Ayrıca Image Cropper kütüphanesi için gerekli Activity'i de dahil etmeyi unutmuyoruz:

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
    ...
    <activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
        android:theme="@style/AppTheme"/>
</application>


Şimdi de MainActivity.java dosyamızı açarak gerekli kodlamaları yapacağız. Adım adım gidecek olursak;


Ekranda bulunan ve üzerinde işlem yapacağımız nesneleri (EditText, ImageView, FloationActionButton) tanımlıyoruz;

EditText mResultEt;
ImageView mPreviewIv;
FloatingActionButton fabImage;


Kamera ve galeri için izin isteği ile kameradan ve galeriden gelecek olan resimle alakalı olarak belirli kodlar tanımlıyoruz. Bu kodlar onRequestPermissionsResult ve onActivityResult kısımlarında bize yardımcı olacak. Ayrıca hangi izinlerin isteneceğini belirtmek adına birer dizi oluşturuyoruz. Son olarak bir de çekilen veya seçilen resmi almak için Uri tanımlıyoruz.;

private static final int CAMERA_REQUEST_CODE = 200;
private static final int STORAGE_REQUEST_CODE = 400;
private static final int IMAGE_PICK_GALLERY_CODE = 1000;
private static final int IMAGE_PICK_CAMERA_CODE = 1001;

String cameraPermission[];
String storagePermission[];

Uri image_uri;


onCreate metodumuzda gerekli ilklemeleri yapıyoruz;

mResultEt = findViewById(R.id.resultEt);
mPreviewIv = findViewById(R.id.imageIv);
fabImage = findViewById(R.id.fab);
fabImage.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        showImageImportDialog();
    }
});

cameraPermission = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
storagePermission = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};


FloatingActionButton'a tıklandığında gidecek fonksiyonu yazıyoruz;

private void showImageImportDialog() {
    String[] items = {" Kamera", " Galeri"};
    AlertDialog.Builder dialog = new AlertDialog.Builder(this);
    dialog.setTitle("Resim Seç");
    dialog.setItems(items, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            if (which == 0) {
                if (!checkCameraPermission()) {
                    requestCameraPermission();
                }
                else {
                    pickCamera();
                }
            }
            if (which == 1) {
                if (!checkStoragePermission()) {
                    requestStoragePermission();
                }
                else {
                    pickGallery();
                }
            }
        }
    });
    dialog.create().show();
}


Kamera izninin olup olmadığını kontrol eden fonksiyonu yazıyoruz;

private boolean checkCameraPermission() {
    boolean result = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == (PackageManager.PERMISSION_GRANTED);
    boolean result1 = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == (PackageManager.PERMISSION_GRANTED);
    return result && result1;
}

Eğer ki kamera izni yoksa bu izni kullanıcıdan talep etmemiz için gerekli fonksiyonu yazıyoruz;

private void requestCameraPermission() {
    ActivityCompat.requestPermissions(this, cameraPermission, CAMERA_REQUEST_CODE);
}


Galeriyi okuma izninin olup olmadığını kontrol eden fonksiyonu yazıyoruz;

private boolean checkStoragePermission() {
    boolean result = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == (PackageManager.PERMISSION_GRANTED);
    return result;
}

Eğer ki galeriden okuma izni yoksa bu izni kullanıcıdan talep etmemiz için gerekli fonksiyonu yazıyoruz;

private void requestStoragePermission() {
    ActivityCompat.requestPermissions(this, storagePermission, STORAGE_REQUEST_CODE);
}


Yukarıda belirttiğim onRequestPermissionsResult fonksiyonunu yazıyoruz. Bu fonksiyon sayesinde kullanıcıya gönderilen izin taleplerine verilen cevapları yakalamış olacağız. Buna bağlı olarak da kamerayı açtırma, galeriyi açtırma veya ekrana "İzin reddedildi" mesajı verme işlemlerini yapmış olacağız;

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
        case CAMERA_REQUEST_CODE:
            if (grantResults.length > 0) {
                boolean cameraAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                boolean writeStorageAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                if (cameraAccepted && writeStorageAccepted) {
                    pickCamera();
                }
                else {
                    Toast.makeText(this, "İzin reddedildi", Toast.LENGTH_SHORT).show();
                }
            }
            break;
        case STORAGE_REQUEST_CODE:
            if (grantResults.length > 0) {
                boolean writeStorageAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                if (writeStorageAccepted) {
                    pickGallery();
                }
                else {
                    Toast.makeText(this, "İzin reddedildi", Toast.LENGTH_SHORT).show();
                }
            }
            break;
    }
}


Kamerayı açtırma fonksiyonumuzu yazıyoruz;

private void pickCamera() {
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.TITLE, "NewPic");
    values.put(MediaStore.Images.Media.DESCRIPTION, "Image to Text");
    image_uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

    Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, image_uri);
    startActivityForResult(cameraIntent, IMAGE_PICK_CAMERA_CODE);
}


Galeriyi açtırma fonksiyonumuzu yazıyoruz;

private void pickGallery() {
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType("image/*");
    startActivityForResult(intent, IMAGE_PICK_GALLERY_CODE);
}


Ve son olarak onActivityResult fonksiyonumuzu yazıyoruz. Bu fonksiyon sayesinde eğer ki istek kodu (requestCode) kamerayı veya galeriyi açtırma ise oradan gelecek resmi (data) Image Cropper kütüphanemizin sağladığı Activity'e gönderiyoruz. Eğer ki istek kodu Image Cropper kütüphanesinden dönen kod ise kırpılmış olan resmi alıp ekrandaki ImageView nesnesinde gösteriyoruz. Sonrasında Vision API ile resimdeki yazıyı ekrandaki EditText nesnesine bastırıyoruz;

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if (resultCode == RESULT_OK) {
        if (requestCode == IMAGE_PICK_GALLERY_CODE) {
            CropImage.activity(data.getData()).setGuidelines(CropImageView.Guidelines.ON).start(this);
        }
        if (requestCode == IMAGE_PICK_CAMERA_CODE) {
            CropImage.activity(image_uri).setGuidelines(CropImageView.Guidelines.ON).start(this);
        }
        if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
            CropImage.ActivityResult result = CropImage.getActivityResult(data);
            Uri resultUri = result.getUri();
            mPreviewIv.setImageURI(resultUri);

            BitmapDrawable bitmapDrawable = (BitmapDrawable)mPreviewIv.getDrawable();
            Bitmap bitmap = bitmapDrawable.getBitmap();

            TextRecognizer recognizer = new TextRecognizer.Builder(getApplicationContext()).build();

            Frame frame = new Frame.Builder().setBitmap(bitmap).build();
            SparseArray<TextBlock> items = recognizer.detect(frame);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < items.size(); i++) {
                TextBlock myItem = items.valueAt(i);
                sb.append(myItem.getValue());
                sb.append("\n");
            }
            mResultEt.setText(sb.toString());
        }
    }
}


Hem yazıyı daha fazla uzatmamak adına, hem de zaten örnek projeyi Github'da paylaşmış olduğum için MainActivity.java dosyasının tam halini burada paylaşmayacağım.


Uygulamayı çalıştırdığımızda çıktılarımız şu şekilde olacaktır:

Resmin çekilip ImageCropper'a aktarıldığı kısım;


Resimdeki yazının okunup EditText üzerine aktarıldığı kısım;


Umarım faydalı olmuştur.


İyi çalışmalar.