1. Trước khi bắt đầu
Việc nhập mã là một cách hiệu quả để tạo trí nhớ cơ bắp và hiểu sâu hơn về tài liệu. Mặc dù việc sao chép và dán có thể giúp bạn tiết kiệm thời gian, nhưng việc đầu tư vào phương pháp này có thể giúp bạn đạt được hiệu quả cao hơn và kỹ năng lập trình tốt hơn về lâu dài.
Trong lớp học lập trình này, bạn sẽ tìm hiểu cách tạo một ứng dụng Android thực hiện phân đoạn hình ảnh theo thời gian thực trên nguồn cấp dữ liệu trực tiếp từ camera bằng thời gian chạy mới của Google cho TensorFlow Lite, LiteRT. Bạn sẽ dùng một ứng dụng Android khởi đầu và thêm các chức năng phân đoạn hình ảnh vào ứng dụng đó. Chúng ta cũng sẽ xem xét các bước tiền xử lý, suy luận và hậu xử lý. Bạn sẽ:
- Tạo một ứng dụng Android phân đoạn hình ảnh theo thời gian thực.
- Tích hợp mô hình phân đoạn hình ảnh LiteRT được huấn luyện trước.
- Xử lý trước hình ảnh đầu vào cho mô hình.
- Sử dụng thời gian chạy LiteRT để tăng tốc CPU và GPU.
- Tìm hiểu cách xử lý đầu ra của mô hình để hiển thị mặt nạ phân đoạn.
- Tìm hiểu cách điều chỉnh cho camera trước.
Cuối cùng, bạn sẽ tạo ra một thứ tương tự như hình ảnh bên dưới:
Điều kiện tiên quyết
Lớp học lập trình này dành cho những nhà phát triển di động có kinh nghiệm muốn có thêm kinh nghiệm về Học máy. Bạn cần thông thạo:
- Phát triển Android bằng Kotlin và Android Studio
- Các khái niệm cơ bản về xử lý hình ảnh
Kiến thức bạn sẽ học được
- Cách tích hợp và sử dụng thời gian chạy LiteRT trong một ứng dụng Android.
- Cách phân đoạn hình ảnh bằng mô hình LiteRT được huấn luyện trước.
- Cách tiền xử lý hình ảnh đầu vào cho mô hình.
- Cách chạy suy luận cho mô hình.
- Cách xử lý đầu ra của một mô hình phân đoạn để trực quan hoá kết quả.
- Cách sử dụng CameraX để xử lý nguồn cấp dữ liệu camera theo thời gian thực.
Bạn cần có
- Một phiên bản Android Studio gần đây (đã thử nghiệm trên phiên bản 2025.1.1).
- Một thiết bị Android thực. Tính năng này được kiểm thử tốt nhất trên các thiết bị Galaxy và Pixel.
- Mã mẫu (từ GitHub).
- Có kiến thức cơ bản về phát triển Android bằng Kotlin.
2. Phân đoạn hình ảnh
Phân đoạn hình ảnh là một nhiệm vụ thị giác máy tính liên quan đến việc phân vùng một hình ảnh thành nhiều phân đoạn hoặc khu vực. Không giống như tính năng phát hiện đối tượng (vẽ một hộp giới hạn xung quanh một đối tượng), tính năng phân đoạn hình ảnh sẽ chỉ định một lớp hoặc nhãn cụ thể cho từng pixel trong hình ảnh. Điều này giúp bạn hiểu rõ hơn và chi tiết hơn về nội dung của hình ảnh, cho phép bạn biết hình dạng và ranh giới chính xác của từng đối tượng.
Ví dụ: thay vì chỉ biết có một "người" trong một khung hình, bạn có thể biết chính xác những pixel nào thuộc về người đó. Hướng dẫn này minh hoạ cách thực hiện phân đoạn hình ảnh theo thời gian thực trên thiết bị Android bằng mô hình học máy được huấn luyện trước.
LiteRT: Đẩy mạnh việc học máy trên thiết bị
LiteRT là một công nghệ quan trọng giúp phân đoạn theo thời gian thực với độ trung thực cao trên thiết bị di động. Là thời gian chạy hiệu suất cao thế hệ tiếp theo của Google cho TensorFlow Lite, LiteRT được thiết kế để đạt được hiệu suất tốt nhất tuyệt đối từ phần cứng cơ bản.
Việc này được thực hiện thông qua việc sử dụng các trình tăng tốc phần cứng một cách thông minh và tối ưu, chẳng hạn như GPU (Đơn vị xử lý đồ hoạ) và NPU (Đơn vị xử lý thần kinh). Bằng cách chuyển khối lượng công việc tính toán chuyên sâu của mô hình phân đoạn từ CPU đa năng sang các bộ xử lý chuyên dụng này, LiteRT giúp giảm đáng kể thời gian suy luận. Nhờ khả năng tăng tốc này, các mô hình phức tạp có thể chạy mượt mà trên nguồn cấp dữ liệu trực tiếp của camera, mở rộng phạm vi những gì chúng ta có thể đạt được bằng học máy ngay trên điện thoại. Nếu không đạt được mức hiệu suất này, tính năng phân đoạn theo thời gian thực sẽ quá chậm và giật cục, khiến người dùng có trải nghiệm không tốt.
3. Bắt đầu thiết lập
Sao chép kho lưu trữ
Trước tiên, hãy sao chép kho lưu trữ cho LiteRT:
git clone https://github.com/google-ai-edge/LiteRT.git
LiteRT/litert/samples/image_segmentation
là thư mục chứa tất cả tài nguyên bạn sẽ cần. Trong lớp học lập trình này, bạn sẽ chỉ cần dự án kotlin_cpu_gpu/android_starter
. Bạn nên xem lại dự án đã hoàn thành nếu gặp khó khăn: kotlin_cpu_gpu/android
Lưu ý về đường dẫn tệp
Hướng dẫn này chỉ định đường dẫn tệp ở định dạng Linux/macOS. Nếu đang dùng Windows, bạn cần điều chỉnh các đường dẫn cho phù hợp.
Bạn cũng cần lưu ý sự khác biệt giữa chế độ xem dự án của Android Studio và chế độ xem hệ thống tệp tiêu chuẩn. Chế độ xem dự án của Android Studio là một bản trình bày có cấu trúc về các tệp của dự án, được sắp xếp để phát triển Android. Đường dẫn tệp trong hướng dẫn này đề cập đến đường dẫn hệ thống tệp, chứ không phải đường dẫn trong khung hiển thị dự án của Android Studio.
Nhập ứng dụng khởi đầu
Hãy bắt đầu bằng cách nhập ứng dụng khởi đầu vào Android Studio.
- Mở Android Studio rồi chọn Open (Mở).
- Chuyển đến thư mục
kotlin_cpu_gpu/android_starter
rồi mở thư mục này.
Để đảm bảo rằng ứng dụng của bạn có tất cả các phần phụ thuộc, bạn nên đồng bộ hoá dự án với các tệp gradle khi quá trình nhập hoàn tất.
- Chọn Đồng bộ hoá dự án với tệp Gradle trên thanh công cụ của Android Studio.
- Vui lòng không bỏ qua bước này – nếu bước này không hiệu quả, thì phần còn lại của hướng dẫn sẽ không có ý nghĩa.
Chạy ứng dụng khởi đầu
Giờ đây, bạn đã nhập dự án vào Android Studio và sẵn sàng chạy ứng dụng lần đầu tiên.
Kết nối thiết bị Android với máy tính qua USB rồi nhấp vào Chạy trong thanh công cụ của Android Studio.
Ứng dụng sẽ chạy trên thiết bị của bạn. Bạn sẽ thấy một nguồn cấp dữ liệu trực tiếp từ camera, nhưng chưa có phân đoạn nào diễn ra. Tất cả các nội dung chỉnh sửa tệp mà bạn sẽ thực hiện trong hướng dẫn này đều nằm trong thư mục LiteRT/litert/samples/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/aiedge/examples/image_segmentation
(giờ bạn đã biết lý do Android Studio tái cấu trúc thư mục này 😃).
Bạn cũng sẽ thấy các bình luận TODO
trong các tệp ImageSegmentationHelper.kt
, MainViewModel.kt
và view/SegmentationOverlay.kt
. Trong các bước sau, bạn sẽ triển khai chức năng phân đoạn hình ảnh bằng cách điền vào các TODO
này.
4. Tìm hiểu về ứng dụng khởi đầu
Ứng dụng khởi đầu đã có giao diện người dùng cơ bản và logic xử lý camera. Sau đây là thông tin tổng quan nhanh về các tệp chính:
app/src/main/java/com/google/aiedge/examples/image_segmentation/MainActivity.kt
: Đây là điểm truy cập chính của ứng dụng. Ứng dụng này thiết lập giao diện người dùng bằng Jetpack Compose và xử lý các quyền truy cập camera.app/src/main/java/com/google/aiedge/examples/image_segmentation/MainViewModel.kt
: ViewModel này quản lý trạng thái giao diện người dùng và điều phối quy trình phân đoạn hình ảnh.app/src/main/java/com/google/aiedge/examples/image_segmentation/ImageSegmentationHelper.kt
: Đây là nơi chúng ta sẽ thêm logic cốt lõi cho việc phân đoạn hình ảnh. Thao tác này sẽ xử lý việc tải mô hình, xử lý khung hình camera và chạy quy trình suy luận.app/src/main/java/com/google/aiedge/examples/image_segmentation/view/CameraScreen.kt
: Hàm có khả năng kết hợp này hiển thị bản xem trước camera và lớp phủ phân đoạn.app/src/main/assets/selfie_multiclass.tflite
: Đây là mô hình phân đoạn hình ảnh TensorFlow Lite được huấn luyện trước mà chúng ta sẽ sử dụng.
5. Tìm hiểu về LiteRT và cách thêm phần phụ thuộc
Bây giờ, hãy thêm chức năng phân đoạn hình ảnh vào ứng dụng khởi động.
1. Thêm phần phụ thuộc LiteRT
Trước tiên, bạn phải thêm thư viện LiteRT vào dự án của mình. Đây là bước đầu tiên quan trọng để bật tính năng học máy trên thiết bị bằng thời gian chạy được tối ưu hoá của Google.
Mở tệp app/build.gradle.kts
rồi thêm dòng sau vào khối dependencies
:
// LiteRT for on-device ML
implementation(libs.litert)
Sau khi thêm phần phụ thuộc, hãy đồng bộ hoá dự án với các tệp Gradle bằng cách nhấp vào nút Sync Now (Đồng bộ hoá ngay) xuất hiện ở góc trên cùng bên phải của Android Studio.
2. Tìm hiểu về các API LiteRT chính
Mở ImageSegmentationHelper.kt
Trước khi viết mã triển khai, bạn cần hiểu rõ các thành phần cốt lõi của API LiteRT mà bạn sẽ sử dụng. Đảm bảo bạn đang nhập từ gói com.google.ai.edge.litert
, hãy thêm các lệnh nhập sau vào đầu ImageSegmentationHelper.kt
:
import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
CompiledModel
: Đây là lớp trung tâm để tương tác với mô hình TFLite. Đây là một mô hình đã được biên dịch trước và tối ưu hoá cho một trình tăng tốc phần cứng cụ thể (chẳng hạn như CPU hoặc GPU). Quá trình biên dịch trước này là một tính năng chính của LiteRT, giúp suy luận nhanh hơn và hiệu quả hơn.CompiledModel.Options
: Bạn dùng lớp trình tạo này để định cấu hìnhCompiledModel
. Chế độ cài đặt quan trọng nhất là chỉ định bộ tăng tốc phần cứng mà bạn muốn dùng để chạy mô hình.Accelerator
: Enum này cho phép bạn chọn phần cứng để suy luận. Dự án khởi đầu đã được định cấu hình để xử lý các lựa chọn sau:Accelerator.CPU
: Để chạy mô hình trên CPU của thiết bị. Đây là lựa chọn tương thích nhất với mọi thiết bị.Accelerator.GPU
: Để chạy mô hình trên GPU của thiết bị. Điều này thường nhanh hơn đáng kể so với CPU đối với các mô hình dựa trên hình ảnh.
- Vùng đệm đầu vào và đầu ra (
TensorBuffer
): LiteRT sử dụngTensorBuffer
cho đầu vào và đầu ra của mô hình. Điều này giúp bạn kiểm soát bộ nhớ một cách chi tiết và tránh sao chép dữ liệu không cần thiết. Bạn sẽ nhận được các vùng đệm này trực tiếp từ thực thểCompiledModel
bằng cách sử dụngmodel.createInputBuffers()
vàmodel.createOutputBuffers()
, sau đó ghi dữ liệu đầu vào vào các vùng đệm này và đọc kết quả từ các vùng đệm này. model.run()
: Đây là hàm thực thi suy luận. Bạn truyền các vùng đệm đầu vào và đầu ra vào đó, còn LiteRT sẽ xử lý nhiệm vụ phức tạp là chạy mô hình trên bộ tăng tốc phần cứng đã chọn.
6. Hoàn tất việc triển khai ImageSegmentationHelper ban đầu
Bây giờ là lúc viết một số mã. Bạn sẽ hoàn tất việc triển khai ban đầu của ImageSegmentationHelper.kt
. Việc này liên quan đến việc thiết lập lớp riêng tư Segmenter
để giữ mô hình LiteRT và triển khai hàm cleanup()
để phát hành đúng cách.
- Hoàn tất lớp
Segmenter
và hàmcleanup()
: Trong tệpImageSegmentationHelper.kt
, bạn sẽ thấy một cấu trúc cho lớp riêng tư có tên làSegmenter
và một hàm có tên làcleanup()
. Trước tiên, hãy hoàn tất lớpSegmenter
bằng cách xác định hàm khởi tạo của lớp để giữ mô hình, tạo các thuộc tính cho vùng đệm đầu vào/đầu ra và thêm phương thứcclose()
để phát hành mô hình. Sau đó, hãy triển khai hàmcleanup()
để gọi phương thứcclose()
mới này.Thay thế lớpSegmenter
và hàmcleanup()
hiện có bằng nội dung sau: (~dòng 83)private class Segmenter( // Add this argument private val model: CompiledModel, private val coloredLabels: List<ColoredLabel>, ) { // Add these private vals private val inputBuffers: = model.createInputBuffers() private val outputBuffers: = model.createOutputBuffers() fun cleanup() { // cleanup buffers inputBuffers.forEach { it.close() } outputBuffers.forEach { it.close() } // cleanup model model.close() } }
- Xác định phương thức toAccelerator: Phương thức này liên kết các enum của phím tắt đã xác định từ trình đơn phím tắt với các enum của phím tắt dành riêng cho các mô-đun LiteRT đã nhập (~dòng 225):
fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator { return when (acceleratorEnum) { AcceleratorEnum.CPU -> Accelerator.CPU AcceleratorEnum.GPU -> Accelerator.GPU } }
- Khởi động
CompiledModel
: Bây giờ, hãy tìm hàminitSegmenter
. Đây là nơi bạn sẽ tạo thực thểCompiledModel
và dùng thực thể này để tạo bản sao lớpSegmenter
mà bạn vừa xác định. Đoạn mã này thiết lập mô hình bằng trình tăng tốc được chỉ định (CPU hoặc GPU) và chuẩn bị mô hình cho quá trình suy luận. Thay thếTODO
tronginitSegmenter
bằng cách triển khai sau đây (Cmd/Ctrl+f "initSegmenter" hoặc ~dòng 62):cleanup() try { withContext(singleThreadDispatcher) { val model = CompiledModel.create( context.assets, "selfie_multiclass.tflite", CompiledModel.Options(toAccelerator(acceleratorEnum)), null, ) segmenter = Segmenter(model, coloredLabels) Log.d(TAG, "Created an image segmenter") } } catch (e: Exception) { Log.i(TAG, "Create LiteRT from selfie_multiclass is failed: ${e.message}") _error.emit(e) }
7. Bắt đầu phân đoạn và tiền xử lý
Giờ đây, khi đã có mô hình, chúng ta cần kích hoạt quy trình phân đoạn và chuẩn bị dữ liệu đầu vào cho mô hình.
Phân đoạn dựa trên điều kiện kích hoạt
Quá trình phân đoạn bắt đầu trong MainViewModel.kt
, nhận các khung hình từ camera.
Mở MainViewModel.kt
- Kích hoạt tính năng phân đoạn từ khung hình camera: Các hàm
segment
trongMainViewModel
là điểm truy cập cho tác vụ phân đoạn của chúng ta. Các hàm này được gọi bất cứ khi nào có hình ảnh mới từ máy ảnh hoặc được chọn trong thư viện. Sau đó, các hàm này sẽ gọi phương thứcsegment
trongImageSegmentationHelper
của chúng ta. Thay thếTODO
trong cả hai hàmsegment
bằng đoạn mã sau (dòng ~107):// For ImageProxy (from CameraX) fun segment(imageProxy: ImageProxy) { segmentJob = viewModelScope.launch { imageSegmentationHelper.segment(imageProxy.toBitmap(), imageProxy.imageInfo.rotationDegrees) imageProxy.close() } } // For Bitmaps (from gallery) fun segment(bitmap: Bitmap, rotationDegrees: Int) { segmentJob = viewModelScope.launch { val argbBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) imageSegmentationHelper.segment(argbBitmap, rotationDegrees) } }
Xử lý trước hình ảnh
Bây giờ, hãy quay lại ImageSegmentationHelper.kt
để xử lý trước hình ảnh.
Mở ImageSegmentationHelper.kt
- Triển khai hàm công khai
segment
: Hàm này đóng vai trò là một trình bao bọc gọi hàm riêng tưsegment
trong lớpSegmenter
. Thay thếTODO
bằng (~dòng 95):try { withContext(singleThreadDispatcher) { segmenter?.segment(bitmap, rotationDegrees)?.let { if (isActive) _segmentation.emit(it) } } } catch (e: Exception) { Log.i(TAG, "Image segment error occurred: ${e.message}") _error.emit(e) }
- Triển khai quy trình xử lý trước: Hàm
segment
riêng tư bên trong lớpSegmenter
là nơi chúng ta sẽ thực hiện các phép biến đổi cần thiết trên hình ảnh đầu vào để chuẩn bị cho mô hình. Điều này bao gồm việc điều chỉnh tỷ lệ, xoay và chuẩn hoá hình ảnh. Sau đó, hàm này sẽ gọi một hàmsegment
riêng tư khác để thực hiện suy luận. Thay thếTODO
trong hàmsegment(bitmap: Bitmap, ...)
bằng (~dòng 121):val totalStartTime = SystemClock.uptimeMillis() val rotation = -rotationDegrees / 90 val (h, w) = Pair(256, 256) // Preprocessing val preprocessStartTime = SystemClock.uptimeMillis() var image = bitmap.scale(w, h, true) image = rot90Clockwise(image, rotation) val inputFloatArray = normalize(image, 127.5f, 127.5f) Log.d(TAG, "Preprocessing time: ${SystemClock.uptimeMillis() - preprocessStartTime} ms") // Inference val inferenceStartTime = SystemClock.uptimeMillis() val segmentResult = segment(inputFloatArray) Log.d(TAG, "Inference time: ${SystemClock.uptimeMillis() - inferenceStartTime} ms") Log.d(TAG, "Total segmentation time: ${SystemClock.uptimeMillis() - totalStartTime} ms") return SegmentationResult(segmentResult, SystemClock.uptimeMillis() - inferenceStartTime)
8. Suy luận chính bằng LiteRT
Sau khi xử lý trước dữ liệu đầu vào, giờ đây, chúng ta có thể chạy suy luận cốt lõi bằng LiteRT.
Mở ImageSegmentationHelper.kt
- Triển khai quá trình thực thi mô hình: Hàm
segment(inputFloatArray: FloatArray)
riêng tư là nơi chúng ta tương tác trực tiếp với phương thứcrun()
LiteRT. Chúng tôi ghi dữ liệu đã xử lý trước vào vùng đệm đầu vào, chạy mô hình và đọc kết quả từ vùng đệm đầu ra. Thay thếTODO
trong hàm này bằng (~dòng 188):val (h, w, c) = Triple(256, 256, 6) // MODEL EXECUTION PHASE val modelExecStartTime = SystemClock.uptimeMillis() // Write input data - measure time val bufferWriteStartTime = SystemClock.uptimeMillis() inputBuffers[0].writeFloat(inputFloatArray) val bufferWriteTime = SystemClock.uptimeMillis() - bufferWriteStartTime Log.d(TAG, "Buffer write time: $bufferWriteTime ms") // Optional tensor inspection logTensorStats("Input tensor", inputFloatArray) // Run model inference - measure time val modelRunStartTime = SystemClock.uptimeMillis() model.run(inputBuffers, outputBuffers) val modelRunTime = SystemClock.uptimeMillis() - modelRunStartTime Log.d(TAG, "Model.run() time: $modelRunTime ms") // Read output data - measure time val bufferReadStartTime = SystemClock.uptimeMillis() val outputFloatArray = outputBuffers[0].readFloat() val outputBuffer = FloatBuffer.wrap(outputFloatArray) val bufferReadTime = SystemClock.uptimeMillis() - bufferReadStartTime Log.d(TAG, "Buffer read time: $bufferReadTime ms") val modelExecTime = SystemClock.uptimeMillis() - modelExecStartTime Log.d(TAG, "Total model execution time: $modelExecTime ms") // Optional tensor inspection logTensorStats("Output tensor", outputFloatArray) // POSTPROCESSING PHASE val postprocessStartTime = SystemClock.uptimeMillis() // Process mask from model output val inferenceData = InferenceData(width = w, height = h, channels = c, buffer = outputBuffer) val mask = processImage(inferenceData) val postprocessTime = SystemClock.uptimeMillis() - postprocessStartTime Log.d(TAG, "Postprocessing time (mask creation): $postprocessTime ms") return Segmentation( listOf(Mask(mask, inferenceData.width, inferenceData.height)), coloredLabels, )
9. Hậu xử lý và hiển thị lớp phủ
Sau khi chạy suy luận, chúng ta sẽ nhận được đầu ra thô từ mô hình. Chúng ta cần xử lý đầu ra này để tạo một mặt nạ phân đoạn trực quan, sau đó hiển thị mặt nạ đó trên màn hình.
Mở ImageSegmentationHelper.kt
- Triển khai quy trình xử lý đầu ra: Hàm
processImage
chuyển đổi đầu ra thô có dấu phẩy động từ mô hình thànhByteBuffer
biểu thị mặt nạ phân đoạn. Việc này được thực hiện bằng cách tìm lớp có xác suất cao nhất cho mỗi pixel. Thay thếTODO
của nó bằng (~dòng 238):val mask = ByteBuffer.allocateDirect(inferenceData.width * inferenceData.height) for (i in 0 until inferenceData.height) { for (j in 0 until inferenceData.width) { val offset = inferenceData.channels * (i * inferenceData.width + j) var maxIndex = 0 var maxValue = inferenceData.buffer.get(offset) for (index in 1 until inferenceData.channels) { if (inferenceData.buffer.get(offset + index) > maxValue) { maxValue = inferenceData.buffer.get(offset + index) maxIndex = index } } mask.put(i * inferenceData.width + j, maxIndex.toByte()) } } return mask
Mở MainViewModel.kt
- Thu thập và xử lý kết quả phân đoạn: Giờ đây, chúng ta quay lại
MainViewModel
để xử lý kết quả phân đoạn từImageSegmentationHelper
.segmentationUiShareFlow
thu thậpSegmentationResult
, chuyển đổi mặt nạ thànhBitmap
đầy màu sắc và cung cấp cho giao diện người dùng. Thay thếTODO
trong thuộc tínhsegmentationUiShareFlow
bằng (~dòng 63) – đừng thay thế mã đã có, chỉ cần điền nội dung:viewModelScope.launch { imageSegmentationHelper.segmentation .filter { it.segmentation.masks.isNotEmpty() } .map { val segmentation = it.segmentation val mask = segmentation.masks[0] val maskArray = mask.data val width = mask.width val height = mask.height val pixelSize = width * height val pixels = IntArray(pixelSize) val colorLabels = segmentation.coloredLabels.mapIndexed { index, coloredLabel -> ColorLabel(index, coloredLabel.label, coloredLabel.argb) } // Set color for pixels for (i in 0 until pixelSize) { val colorLabel = colorLabels[maskArray[i].toInt()] val color = colorLabel.getColor() pixels[i] = color } // Get image info val overlayInfo = OverlayInfo(pixels = pixels, width = width, height = height) val inferenceTime = it.inferenceTime Pair(overlayInfo, inferenceTime) } .collect { flow.emit(it) } }
Mở view/SegmentationOverlay.kt
Phần cuối cùng là định hướng chính xác lớp phủ phân đoạn khi người dùng chuyển sang camera trước. Nguồn cấp dữ liệu của camera được phản chiếu tự nhiên cho camera trước, vì vậy, chúng ta cần áp dụng cùng một thao tác lật ngang cho lớp phủ Bitmap
để đảm bảo lớp phủ này căn chỉnh chính xác với bản xem trước của camera.
- Xử lý hướng lớp phủ: Tìm
TODO
trong tệpSegmentationOverlay.kt
rồi thay thế bằng mã sau. Mã này kiểm tra xem camera trước có đang hoạt động hay không. Nếu có, mã này sẽ áp dụng thao tác lật ngang cho lớp phủBitmap
trước khi lớp phủ này được vẽ trênCanvas
. (~dòng 42):val orientedBitmap = if (lensFacing == CameraSelector.LENS_FACING_FRONT) { // Create a matrix for horizontal flipping val matrix = Matrix().apply { preScale(-1f, 1f) } Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, false).also { image.recycle() } } else { image }
10. Chạy và sử dụng ứng dụng cuối cùng
Giờ đây, bạn đã hoàn tất mọi thay đổi cần thiết về mã. Đã đến lúc chạy ứng dụng và xem thành quả của bạn!
- Chạy ứng dụng: Kết nối thiết bị Android rồi nhấp vào Chạy trong thanh công cụ của Android Studio.
- Kiểm thử các tính năng: Sau khi ứng dụng khởi chạy, bạn sẽ thấy nguồn cấp dữ liệu trực tiếp từ camera có lớp phủ phân đoạn nhiều màu.
- Chuyển đổi camera: Nhấn vào biểu tượng lật camera ở trên cùng để chuyển đổi giữa camera trước và sau. Hãy lưu ý cách lớp phủ tự định hướng chính xác.
- Thay đổi trình tăng tốc: Nhấn vào nút "CPU" hoặc "GPU" ở dưới cùng để chuyển đổi trình tăng tốc phần cứng. Quan sát sự thay đổi về Thời gian suy luận xuất hiện ở cuối màn hình. GPU sẽ nhanh hơn đáng kể.
- Sử dụng hình ảnh trong thư viện: Nhấn vào thẻ "Thư viện" ở trên cùng để chọn một hình ảnh trong thư viện ảnh trên thiết bị của bạn. Ứng dụng sẽ chạy tính năng phân đoạn trên hình ảnh tĩnh đã chọn.
Giờ đây, bạn đã có một ứng dụng phân đoạn hình ảnh theo thời gian thực, có đầy đủ chức năng và được hỗ trợ bởi LiteRT!
11. Nâng cao (Không bắt buộc): Sử dụng NPU
Kho lưu trữ này cũng chứa một phiên bản ứng dụng được tối ưu hoá cho Đơn vị xử lý thần kinh (NPU). Phiên bản NPU có thể giúp tăng hiệu suất đáng kể trên những thiết bị có NPU tương thích.
Để dùng thử phiên bản NPU, hãy mở dự án kotlin_npu/android
trong Android Studio. Mã này rất giống với phiên bản CPU/GPU và được định cấu hình để sử dụng uỷ quyền NPU.
Để sử dụng uỷ quyền NPU, bạn cần đăng ký tham gia Chương trình tiếp cận sớm.
12. Xin chúc mừng!
Bạn đã tạo thành công một ứng dụng Android thực hiện phân đoạn hình ảnh theo thời gian thực bằng LiteRT. Bạn đã tìm hiểu cách:
- Tích hợp thời gian chạy LiteRT vào một ứng dụng Android.
- Tải và chạy mô hình phân đoạn hình ảnh TFLite.
- Xử lý trước dữ liệu đầu vào của mô hình.
- Xử lý đầu ra của mô hình để tạo mặt nạ phân đoạn.
- Sử dụng CameraX cho ứng dụng máy ảnh theo thời gian thực.
Các bước tiếp theo
- Hãy thử một mô hình phân đoạn hình ảnh khác.
- Thử nghiệm với nhiều đại biểu LiteRT (CPU, GPU, NPU).