Triển khai phân loại hình ảnh trên thiết bị Android

1. Giới thiệu

Trong thời đại công nghệ phát triển mạnh mẽ, trí tuệ nhân tạo (AI) và học máy (ML) đã trở thành những công cụ không thể thiếu trong nhiều lĩnh vực. Trên các thiết bị di động, việc ứng dụng AI để phân loại hình ảnh đang ngày càng phổ biến, mang lại nhiều tiện ích và trải nghiệm người dùng tốt hơn.

Bài viết này sẽ hướng dẫn bạn cách triển khai một ứng dụng Android có khả năng phân loại hình ảnh, sử dụng mô hình học sâu. Chúng ta sẽ tìm hiểu cách tích hợp mô hình đã huấn luyện mobilenet vào ứng dụng, đồng thời tối ưu hiệu suất cho thiết bị di động.

Ứng dụng sẽ tải model đã được huấn luyện, lần lượt duyệt qua các ảnh trong thư mục trong thiết bị sau đó xác định đối tượng chính xuất hiện trong ảnh và hiển thị ảnh và thông tin đối tượng đó lên màn hình.

aicandy.vn

2. Cấu trúc dự án

Sử dụng phần mềm lập trình Android studio, dùng ngôn ngữ Java (phiên bản ngôn ngữ Kotin xem tại đây), các tệp và thư mục chính được tổ chức như sau:

root@aicandy:/aicandy/projects/AIcandy_Android_ImageClassification_smdkrohy/app/src//main# tree
.
├── AndroidManifest.xml
├── assets
│       ├── image_test
│       │       ├── ...
│       └── mobilenet-v2.pt
├── java
│       └── com
│           └── aicandy
│               └── imageclassification
│                   └── mobilenet
│                       ├── Classifier.java
│                       ├── Constants.java
│                       ├── MainActivity.java
│                       ├── Result.java
│                       └── Utils.java
└── res
    ├── layout
    │       ├── activity_main.xml
    │       ├── activity_result.xml
    │       ├── content_main.xml
    │       └── content_result.xml
    └── ...
        └── ...

Trong đó:

  • Thư mục image_test chứa các cảnh cần kiểm tra.
  • Tệp mobilenet-v2.pt là model đã được huấn luyện
  • Tệp Classifier.java chứa mã nguồn để xử lý hình ảnh
  • Tệp Constants.java chứa thông tin về tất cả các đối tượng được dự đoán trong mô hình.
  • Tệp MainActivity.java chứa chương trình chính của ứng dụng.
  • Thư mục layout chứa các tệp cấu hình giao diện hiển thị.

3. Cấu hình AndroidManifest

Tệp AndroidManifest là nơi cấu hình thông tin quan trọng về ứng dụng, bao gồm các activity (màn hình), biểu tượng, tên ứng dụng, và các tính năng hỗ trợ khác.

Một số cấu hình chính trong tệp:

android:allowBackup=”true”

Cho phép hệ thống sao lưu và phục hồi dữ liệu ứng dụng khi cần (như khi cài lại app).

android:icon=”@mipmap/ic_launcher”

Chỉ định biểu tượng chính của ứng dụng, thường được hiển thị trên màn hình chính.

android:label=”@string/app_name”

Đặt tên của ứng dụng, được định nghĩa trong tệp string resources (res/values/strings.xml).

android:theme=”@style/AppTheme”

Chỉ định theme (giao diện) chung cho ứng dụng, được định nghĩa trong tệp styles (res/values/styles.xml).

android:name=”.Result”

Đây là tên class của activity, là màn hình được điều hướng đến khi cần hiển thị kết quả (nằm trong gói chính của ứng dụng).

android:name=”.MainActivity”

Đây là tên class của MainActivity, là màn hình khởi động chính của ứng dụng.

<action android:name=”android.intent.action.MAIN” />

Định nghĩa đây là “main entry point”, tức là điểm vào chính của ứng dụng.

<category android:name=”android.intent.category.LAUNCHER” />

Chỉ định activity này xuất hiện trong danh sách ứng dụng (launcher) của thiết bị, là nơi người dùng có thể khởi động ứng dụng từ màn hình chính.

<activity
    android:name=".MainActivity"
    android:label="@string/title_activity_main"
    android:theme="@style/AppTheme.NoActionBar">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

4. Giao diện hiển thị

Ứng dụng có giao diện với các thành phần chính để hiển thị thông tin trạng thái chương trình, hiển thị hình ảnh và hiển thị kết quả.

aicandy.vn

Tạo TextView để hiển thị trạng thái chương trình, các trạng thái như “Đang xử lý ảnh”, “Đang tải model”, “Kết quả” …

<TextView
        android:id="@+id/statusText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:visibility="gone"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/progressBar" />

Tạo ImageView để hiển thị hình ảnh trên màn hình

<ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:src="@drawable/ic_launcher_background"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:id="@+id/image"/>

Và cuối cùng, chúng ta tạo TextView để hiển thị tên của đối tượng có trong ảnh mà mô hình đã phát hiện được.

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World"
        android:id="@+id/label"
        android:textSize="16pt"
        app:layout_constraintStart_toStartOf="@id/image"
        app:layout_constraintEnd_toEndOf="@id/image"
        app:layout_constraintTop_toBottomOf="@id/image"

5. Model và tiền xử lý

Chú ý rằng các đoạn code trong bài là các key chính, code đầy đủ cần xem phần source code ở mục 7.

Bước 1: Tải model

Để tải model đã huấn luyện, sử dụng phương thức Module trong pytorch và load model với tham số đầu vào là đường dẫn của model.

model = Module.load(modelPath);

Cũng cần chú ý là để sử dụng được thư viện pytorch, chúng ta cần khai báo thư viện pytorch trong build.gradle

dependencies { 
      implementation 'org.pytorch:pytorch_android:2.1.0'
      ...
}

Bước 2: Chuẩn hóa hình ảnh
Model huấn luyện đã sử dụng bộ tham số chuẩn hóa (mean và std), do đó trong quá trình đánh giá (kiểm tra/dự đoán) chúng ta cũng cần chuẩn hóa tương tự. Việc chuẩn hóa có tác dụng:

  • Đưa các giá trị pixel về một phạm vi dễ quản lý: Hình ảnh gốc có các giá trị pixel từ 0 đến 255. Chuẩn hóa làm cho các giá trị pixel nằm trong khoảng [-1, 1] hoặc gần với nó, điều này giúp các mô hình học sâu dễ dàng tối ưu hơn.
  • Tăng tốc độ hội tụ của mô hình: Khi dữ liệu đầu vào có cùng phân phối, các thuật toán tối ưu như gradient descent sẽ hoạt động hiệu quả hơn, dẫn đến việc mô hình huấn luyện nhanh hơn.
  • Giảm sự phụ thuộc vào các biến đổi về ánh sáng: Nhờ chuẩn hóa, mô hình ít bị ảnh hưởng bởi sự thay đổi về độ sáng, màu sắc trong dữ liệu ảnh thực tế, giúp mô hình tổng quát tốt hơn khi triển khai trên các hình ảnh khác nhau.
float[] mean = {0.485f, 0.456f, 0.406f};
float[] std = {0.229f, 0.224f, 0.225f};
bitmap = Bitmap.createScaledBitmap(bitmap,size,size,false);
return TensorImageUtils.bitmapToFloat32Tensor(bitmap,this.mean,this.std);

Bước 3: Tạo dự đoán

Ở bước này, chúng ta tạo hàm xử lý dữ liệu ảnh bitmap đầu vào để tra ra kết quả. Việc đầu tiên là chuyển dữ liệu đầu vào sang dạng tensor để phù hợp với tính toán trong pytorch. Sau đó đưa tensor đầu vào qua model và lấy điểm số ở đầu ra.

Tensor tensor = preprocess(bitmap,224);
IValue inputs = IValue.from(tensor);
Tensor outputs = model.forward(inputs).toTensor();
float[] scores = outputs.getDataAsFloatArray();

6. Chương trình chính

Trong chương trình này sẽ thực hiện các công việc như duyệt và lấy tên các file ảnh trong thư mục, chuyển đổi sang bitmap (là cấu trúc dữ liệu được sử dụng phổ biến để lưu trữ và quản lý các hình ảnh. Nó đại diện cho một ma trận các pixel với mỗi pixel chứa thông tin về màu sắc. Bitmap cho phép bạn dễ dàng truy xuất giá trị pixel để thực hiện chuyển ảnh sang dạng tensor), dự đoán đối tượng từ ảnh, hiển thị ảnh và hiển thị tên đối tượng lên màn hình.

Bước 1: Duyệt ảnh

Để duyệt ảnh được lưu trong thư mục “asset” cần sử dụng AssetManager

List imageFiles = new ArrayList<>();
AssetManager assetManager = this.getAssets();
String[] files = assetManager.list(folderPath);

Bước 2: Tải ảnh và chuyển đổi sang bitmap

inputStream = getAssets().open(fileName);

getAssets() là một phương thức của Context (hoặc lớp con của nó, như Activity) để truy cập thư mục assets của ứng dụng.

BitmapFactory.Options options = new BitmapFactory.Options();

Tạo một đối tượng BitmapFactory.Options để thiết lập các tùy chọn cấu hình khi giải mã tệp hình ảnh. BitmapFactory là một lớp tiện ích dùng để chuyển đổi các tệp hình ảnh (như PNG, JPEG) thành đối tượng Bitmap có thể được hiển thị trong các giao diện Android.

options.inPreferredConfig = Bitmap.Config.ARGB_8888;

Thiết lập cấu hình cho Bitmap. Cấu hình này quyết định cách mà pixel trong hình ảnh sẽ được lưu trữ. ARGB_8888 cung cấp chất lượng hình ảnh tốt với độ chi tiết màu sắc cao (32-bit), nhưng sẽ chiếm nhiều bộ nhớ hơn so với các cấu hình khác.

inputStream = getAssets().open(fileName);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeStream(inputStream, null, options);

Bước 3: Dự đoán

String prediction = classifier.predict(originalBitmap);
Log.d(TAG, "AIcandy.vn - detected: " + prediction);

Bước 4: Hiển thị ảnh và thông tin đối tượng lên màn hình
mainHandler.post(new Runnable()

mainHandler là một đối tượng của lớp Handler, thường được sử dụng để gửi và xử lý các đối tượng Runnable. Phương thức post() giúp đăng một công việc (Runnable) lên hàng đợi, để nó được thực thi trên thread mà Handler liên kết (ở đây thường là main thread).

Intent resultView = new Intent(MainActivity.this, Result.class);

Tạo một đối tượng Intent để chuyển từ MainActivity sang Result activity. Intent giúp truyền dữ liệu và bắt đầu một activity mới.

resultView.putExtra(“image_path”, imagePath);

Sử dụng putExtra() để đính kèm thêm dữ liệu vào Intent dưới dạng cặp khóa-giá trị (key-value).

mainHandler.post(new Runnable() {
            @Override
            public void run() {
                statusText.setVisibility(View.GONE);
                Intent resultView = new Intent(MainActivity.this, Result.class);
                resultView.putExtra("image_path", imagePath);
                resultView.putExtra("pred", prediction);
                startActivity(resultView);
            }
        });

7. Demo và source code

Video demo có tại đây 

Source code phiên bản sử dụng ngôn ngữ Java có tại đây

Source code phiên bản sử dụng ngôn ngữ Kotin có tại đây

 

Chúc bạn thành công trong hành trình khám phá và ứng dụng trí tuệ nhân tạo vào học tập và công việc. Đừng quên truy cập thường xuyên để cập nhật thêm kiến thức mới tại AIcandy