Bermain Dengan OpenCV di Android (Java) (bag. 1): Rekognisi Lingkaran

Reading Time: 9 minutes

Selamat datang di UNY Developer Network. Pada postingan kali ini, kita akan bermain-main dengan OpenCV di Android. Wow! OpenCV? Apakah itu OpenCV? OpenCV (Open Source Computer Vision Library), adalah sebuah library open source yang dikembangkan oleh intel yang fokus untuk menyederhanakan programing terkait citra digital (Binus, 2017). Ya, sederhananya Library ini memudahkan kita untuk bermain-main dengan computer vision tanpa harus memikirkan dalam-dalam metode-metode lain pendukungnya. Nah, kali ini kita akan mencoba sesuatu yang berbeda. Pada umumnya, openCV dijalankan di lingkungan C++ atau Python. Namun, sekarang kita akan menjalankannnya di lingkungan Android alias Java Hahahaha…. Bagaimana cara kita melakukannya? Simak postingan ini sampai akhir.

Baik, sebelum kita memulai, ada baiknya kita akan berkenalan dengan lingkungan project kita kali ini. Bisa dibilang cukup rumit ya! Hal ini dikarenakan memang OpenCV itu dibangun untuk secara default dijalankan dilingkungan OpenCV / Python. Namun, sekarang kita akan menjalankannya di lingkungan Java secara spesifik di lingkungan Android. Untuk bentuk akhir dari project kita bagaimana sih nantinya? Mari kita tonton video berikut ini:

Cukup sederhana ya? Yap. Anda juga bisa melihat pada video, terdapat satu button untuk capture gambar namun belum berfungsi. Ya, ya, ya… sabar yaa.. Karena ini baru bagian 1. Bagian capture gambar akan disampaikan di bagian lain.

Baik, bagaimana cara kita membangun aplikasi Android dengan openCV ini? Mari kita ikuti langkah-langkah berikut ini.

1. Unduh SDK OpenCV yang paling terbaru

Versi terbaru dari OpenCV saat ini adalah versi 4.6.0

Untuk itu, klik pada tombol Android untuk memulai proses pengunduhan. Anda akan dibawa ke laman sourceforge untuk melakukan proses pengunduhan, ikuti saja dan pastikan tidak ada aplikasi pihak ketika yang mengganggu proses unduhan.

Ukuran file SDKnya sekitar 250MB. Jadi Anda harus bersabar ketika proses mengunduh. Setelah proses unduhan selesai, Anda akan mendapatkan sebuah file .zip seperti tangkapan layar di bawah ini.

Lakukan ekstraksi file, dan Anda akan memperoleh beberapa direktori seperti tangkapan layar di bawah ini.

Direktori sdk tersebut akan kita include kan ke dalam project Android kita nantinya.

2. Membuat Project Android Baru

Langkah selanjutnya adalah membuat project Android baru. Buatlah sebuah project Android baru dengan: Bahasa Pemrograman Java dan Layout Empty. Ketika saya menulis artikel ini, Saya memberikan nama project Androidnya: OpenCV-Project. Untuk minimum APInya 30 dan targetnya 32.

3. Import Module: OpenCV

Setelah project selesai dibuat, langkah ketiga adalah melakukan import module. Untuk itu, Klik File > New > Import Module

Ketika muncul jendela di bawah klik pada icon folder

Cari lokasi hasil ekstraksi SDK OpenCV kita dan pilih folder SDK. Lanjutkan dengan klik OK

Pada Module Name, berikan nama sesuai selera, namun saya sarankan dengan menggunakan format OpenCVLibrary<Versi>. Klik Finish untuk memulai proses import Module

Tunggu sampai proses import selesai. Proses akan memakan waktu kurang lebih tiga sampai dengan lima menit.

Jika Anda menemukan error, maka lakukan hal berikut:

Buka file gradle SDK OpenCV.

Berikan comment pada dependency yang membuat proses import error. Contohnya pada project ini adalah, karena kita menggunakan Java, maka dependency Kotlin tidak akan diincludekan. Hal tersebut akan membuat proses import error. Maka untuk memperbaikinya hanya tinggal memberikan tanda comment pada bagian dependency:

apply plugin: 'kotlin-android'

Lakukan sync gradle lagi dan proses import module akan berjalan dengan mulus.

Langkah selanjutnya, adalah mengaktifkan dependency modul OpenCV yang sudah kita import. Untuk melakukannya klik File > Project Structure.

Anda akan menemukan jendela seperti tangkapan layar di bawah ini. Klik pada tombol + pada kotak Declared Dependency untuk menambahkan modul.

Pilih Module Dependency.

Pilih Modul OpenCV yang sudah kita import tadi dan klik OK.

Proses menambahkan dan mengaktifkan module OpenCV di Project Android telah selesai. Mari kita lanjutkan ke langkah selanjutnya.

4. Salin Library Native OpenCV ke Project Android

Pada Project Android Studio, klik File > New > Folder > JNI Folder.

Check pada Change Folder Location dan rename folder menjadi jniLibs.

Buka SDK OpenCV hasil ekstrasi, dan navigasikan ke: sdk/native/libs. Salin seluruh isi yang ada di dalam direktori libs ke dalam direktori jniLibs yang sudah kita buat kita sebelumnya

Kembali ke Android Studio, klik Build > Clean Project dan setelah selesai lanjutkan dengan Build > Rebuild Project

Library dan module OpenCV sudah siap digunakan untuk project Android kita.

5. Membuat Resource (Icon Drawable)

Buatlah satu icon drawable dengan nama ic_action_capture yang akan kita gunakan sebagai tombol capture gambar. Saya anggap di sini Anda sudah mampu semua membuat icon drawable di Android Studio ya!

6. Menambahkan Strings

Kita juga akan menambahkan resource strings untuk mendukung project kita terutama pada menu title dan caption pada button.

<resources>
<string name="app_name">OpenCV-Project</string>
<string name="app_menu">Choose Mode:</string>
<string name="btn_circle_camera">Circle Recognition</string>
</resources>

7. Mengonfigurasi Manifest

Kita akan mengonfigurasi manifest kita sehingga siap untuk mendapatkan permission untuk kamera dan storage (karena nantinya kita akan mengcapture hasil kamera dan menyimpannya ke dalam galeri). Tambahkan tiga permission berikut ini:

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

8. Buat Activity Baru: CircleDetection.java (activity_circle_detection.xml)

Buatlah activity baru dengan nama CircleDetection.java dan dengan empty layout yang bernama activity_circle_detection.xml yang berfungsi untuk main feature dari project kita yakni rekognisi lingkaran. Berikut adalah layout dari activity CircleDetection.java (activity_circle_detection.xml)

<?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"
    tools:context=".CircleDetection">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:weightSum="10">


        <org.opencv.android.JavaCameraView
            android:id="@+id/opencv_camera"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <LinearLayout
            android:id="@+id/capture_button"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="9"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/teal_700"
                android:padding="20dp"
                android:src="@drawable/ic_action_capture"/>

        </LinearLayout>

    </LinearLayout>
</RelativeLayout>

Dan berikut ini adalah logika pemrograman dari CircleDetection.java

package com.unydevelopernetwork.opencv_project;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.LinearLayout;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.JavaCameraView;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

public class CircleDetection extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2{

    private static final int CAMERA_PERMISSION_CODE = 100;
    private static final int STORAGE_PERMISSION_CODE = 101;
    private JavaCameraView cameraView;
    private LinearLayout captureButton;

    public static final String TAG = "src";

    static {
        if (!OpenCVLoader.initDebug()) {
            Log.wtf(TAG, "OpenCV Library failed to load!");
        }
    }


    private final BaseLoaderCallback loaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            if (status == SUCCESS) {
                Log.i(TAG, "OpenCV Library loaded successfully");
                cameraView.enableView();
            } else {
                super.onManagerConnected(status);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_detection);
        initLevel0();
        initLevel1();
        initLevel2();
    }

    private void initLevel0(){
        cameraView = findViewById(R.id.opencv_camera);
        captureButton = findViewById(R.id.capture_button);
    }

    private void initLevel1(){
        getCameraPermission();
        getStoragePermission();
    }

    private void initLevel2(){
        cameraView.setCameraPermissionGranted();
        cameraView.setCvCameraViewListener(this);
    }

    private void getCameraPermission(){
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED){
            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE);
        }
    }

    private void getStoragePermission(){
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED){
            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, loaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            loaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (cameraView != null)
            cameraView.disableView();
    }

    @Override
    public void onCameraViewStarted(int width, int height) {

    }

    @Override
    public void onCameraViewStopped() {

    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        Mat input = inputFrame.gray();
        Mat circles = new Mat();
        Imgproc.blur(input, input, new Size(7, 7), new Point(2, 2));
        Imgproc.HoughCircles(input, circles, Imgproc.CV_HOUGH_GRADIENT, 2, 100, 100, 90, 0, 1000);

        Log.i(TAG, "size: " + circles.cols() + ", " + circles.rows());

        if (circles.cols() > 0) {
            for (int x=0; x < Math.min(circles.cols(), 1); x++ ) {
                double[] circleVec = circles.get(0, x);

                if (circleVec == null) {
                    break;
                }

                Point center = new Point((int) circleVec[0], (int) circleVec[1]);
                int radius = (int) circleVec[2];

                Imgproc.circle(input, center, 3, new Scalar(0, 255, 0), 10);
                Imgproc.circle(input, center, radius, new Scalar(0, 255, 0), 5);
            }
        }

        circles.release();
        input.release();
        return inputFrame.rgba();
    }

    @Override
    public void onBackPressed() {
        finish();
    }

}

9. MainActivity.java dan Layout activity_main.xml

Selanjutnya kita buat layout untuk MainActivity (activity_main.xml). Berikut adalah file xml nya.

<?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:layout_margin="20dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/app_menu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/app_menu"
        android:textStyle="bold"
        android:textSize="20sp"/>

    <Button
        android:id="@+id/btn_circle_camera"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="@string/btn_circle_camera"
        android:layout_below="@+id/app_menu"/>
</RelativeLayout>

Dan berikut ini adalah logika pemrogramannya (MainActivity.java)

package com.unydevelopernetwork.opencv_project;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Button circleDetection;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initLevel0();
        initLevel1();
    }

    private void initLevel0(){
        circleDetection = findViewById(R.id.btn_circle_camera);
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initLevel1(){
        circleDetection.setOnTouchListener((view, motionEvent) -> {
            goToCircleCamera();
            return false;
        });
    }

    private void goToCircleCamera(){
        Intent goCircleCamera = new Intent(MainActivity.this, CircleDetection.class);
        goCircleCamera.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivity(goCircleCamera);
    }

}

10. Konfigurasi Manifest

Terakhir kita akan mengonfigurasikan kembali file manifest kita untuk menghandle behavior dari masing-masing activity yang sudah kita buat.

<application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.OpenCVProject"
        tools:targetApi="31">
        <activity
            android:name=".CircleDetection"
            android:screenOrientation="landscape"
            android:parentActivityName=".MainActivity"
            android:exported="false">
            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

Sehingga versi final dari AndroidManifest.xml adalah sebagai 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">

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.OpenCVProject"
        tools:targetApi="31">
        <activity
            android:name=".CircleDetection"
            android:screenOrientation="landscape"
            android:parentActivityName=".MainActivity"
            android:exported="false">
            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

11. Lakukan Analyze APK

Sebelum kita melakukan testing pada project yang sudah dibuat, ada baiknya kita melakukan analyze APK. Klik pada Build > Analyze APK

Selanjutnya, Anda akan melihat jendela seperti tangkapan layar di bawah ini. Pilih app-debug.apk

Anda kemudian akan melihat jendela seperti tangkapan layar di bawah ini. Navigasi ke: lib > <jenis sytem>. Jika Anda melakukan proses import modul dan library OpenCV dengan benar, maka Anda akan menemukan dua buah file: libopencv_java<version>.so dan libc++_shared.so. Jika kedua file ini sudah ada di dalam hasil analyze APK ini, Anda siap untuk melakukan test aplikasi.

12. Test Project!

Langkah terakhir adalah melakukan test project. Pada test kali ini saya menggunakan device langsung yakni Xiaomi Redmi Note 11 Pro (2201117TY) Varian 4/128. Berikut adalah hasil ujinya.

Tampilan Menu Utama
Aplikasi mendeteksi adanya lingkaran dengan memberikan border hitam pada object lingkaran yang tertangkap.
Aplikasi mendeteksi adanya lingkaran dengan memberikan border hitam pada object lingkaran yang tertangkap

Bagaimana? cukup mengasyikkan bukan? Saya tidak akan bilang project ini mudah. Asyik iya, namun mudah tidak. Terutama saat melakukan import modul dan library OpenCV ke dalam project Android. Karena metode yang tersedia dari OpenCV itu sendiri bisa dibilang cukup tua dan tidak update dengan perkembangan IDE serta metode development aplikasi di Android. Jadi ya, saya bilang project ini tidak cukup mudah untuk pemula. Namun, tenang saja! Jika Anda tetap ingin mencoba project ini, dan ternyata menemukan kendala dalam prosesnya, silakan tinggalkan pertanyaan di kolom komentar. Saya akan menjawab pertanyaan Anda.

Baiklah, apakah project ini berhenti sampai di sini? hmmm.. tidak ya! Kita masih ada beberapa bentuk lain yang perlu kita rekognisi dengan menggunakan OpenCV & Android. Ingat, tombol capture kita juga belum aktif, jadi tentu saja masih ada kelanjutan dari postingan kali ini. Untuk Project ini dapat Anda unduh melalui tautan berikut ini: https://github.com/milstrike/OpenCV-Project

Namun, ada baiknya untuk kali ini kita akhiri dulu ya. Demikianlah postingan saya tentang Bermain Dengan OpenCV di Android (Java) (bag. 1): Rekognisi Lingkaran. Semoga bermanfaat bagi Anda para pembaca. Apabila ada pertanyaan mengenai postingan ini, Anda dapat meninggalkannya di kolom komentar. Dan, Apabila Anda menemukan artikel ini berguna, Anda dapat membagikannya. Anda juga dapat mencuplik beberapa bagian dari artikel ini, namun jangan lupa untuk sertakan URL nya. Terima kasih.

^_^


ARTIKEL TERKAIT

Advertisements

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *