1. ก่อนเริ่มต้น
การพิมพ์โค้ดเป็นวิธีที่ยอดเยี่ยมในการสร้างความจำของกล้ามเนื้อและเพิ่มความเข้าใจในเนื้อหา แม้ว่าการคัดลอกและวางจะช่วยประหยัดเวลาได้ แต่การลงทุนในแนวทางปฏิบัตินี้จะช่วยเพิ่มประสิทธิภาพและทักษะการเขียนโค้ดในระยะยาว
ใน Codelab นี้ คุณจะได้เรียนรู้วิธีสร้างแอปพลิเคชัน Android ที่ทำการแบ่งกลุ่มรูปภาพแบบเรียลไทม์ในฟีดกล้องสดโดยใช้รันไทม์ใหม่ของ Google สำหรับ TensorFlow Lite ซึ่งก็คือ LiteRT คุณจะใช้แอปพลิเคชัน Android เริ่มต้นและเพิ่มความสามารถในการแบ่งกลุ่มรูปภาพลงในแอป นอกจากนี้ เราจะดูขั้นตอนการประมวลผลเบื้องต้น การอนุมาน และการประมวลผลภายหลังด้วย คุณจะได้รับสิ่งต่อไปนี้
- สร้างแอป Android ที่แบ่งกลุ่มรูปภาพแบบเรียลไทม์
- ผสานรวมโมเดลการแบ่งกลุ่มรูปภาพ LiteRT ที่ฝึกไว้ล่วงหน้า
- ประมวลผลรูปภาพอินพุตล่วงหน้าสำหรับโมเดล
- ใช้รันไทม์ LiteRT เพื่อการเร่งความเร็ว CPU และ GPU
- ทำความเข้าใจวิธีประมวลผลเอาต์พุตของโมเดลเพื่อแสดงมาสก์การแบ่งกลุ่ม
- ดูวิธีปรับกล้องหน้า
สุดท้ายแล้ว คุณจะสร้างผลงานที่คล้ายกับรูปภาพด้านล่าง

ข้อกำหนดเบื้องต้น
Codelab นี้ออกแบบมาสำหรับนักพัฒนาแอปบนอุปกรณ์เคลื่อนที่ที่มีประสบการณ์ซึ่งต้องการได้รับประสบการณ์ด้านแมชชีนเลิร์นนิง คุณควรคุ้นเคยกับสิ่งต่อไปนี้
- การพัฒนาแอป Android โดยใช้ Kotlin และ Android Studio
- แนวคิดพื้นฐานของการประมวลผลรูปภาพ
สิ่งที่คุณจะได้เรียนรู้
- วิธีผสานรวมและใช้รันไทม์ LiteRT ในแอปพลิเคชัน Android
- วิธีทำการแบ่งกลุ่มรูปภาพโดยใช้โมเดล LiteRT ที่ฝึกมาแล้วล่วงหน้า
- วิธีประมวลผลล่วงหน้ารูปภาพอินพุตสำหรับโมเดล
- วิธีเรียกใช้การอนุมานสำหรับโมเดล
- วิธีประมวลผลเอาต์พุตของโมเดลการแบ่งกลุ่มเพื่อแสดงผลลัพธ์เป็นภาพ
- วิธีใช้ CameraX เพื่อประมวลผลฟีดกล้องแบบเรียลไทม์
สิ่งที่คุณต้องมี
- Android Studio เวอร์ชันล่าสุด (ทดสอบใน v2025.1.1)
- อุปกรณ์ Android จริง โดยได้รับการทดสอบบนอุปกรณ์ Galaxy และ Pixel
- โค้ดตัวอย่าง (จาก GitHub)
- ความรู้พื้นฐานเกี่ยวกับการพัฒนาแอป Android ใน Kotlin
2. การแบ่งกลุ่มรูปภาพ
การแบ่งกลุ่มรูปภาพเป็นงานด้านคอมพิวเตอร์วิทัศน์ที่เกี่ยวข้องกับการแบ่งรูปภาพออกเป็นหลายๆ ส่วนหรือภูมิภาค การแบ่งกลุ่มรูปภาพจะกำหนดคลาสหรือป้ายกำกับที่เฉพาะเจาะจงให้กับทุกพิกเซลในรูปภาพ ซึ่งต่างจากการตรวจหาออบเจ็กต์ที่จะวาดกรอบล้อมรอบออบเจ็กต์ ซึ่งจะช่วยให้คุณเข้าใจเนื้อหาของรูปภาพได้ละเอียดและเฉพาะเจาะจงมากขึ้น ทำให้ทราบรูปร่างและขอบเขตที่แน่นอนของแต่ละออบเจ็กต์
เช่น แทนที่จะรู้แค่ว่ามี "คน" อยู่ในกรอบ คุณจะรู้ได้ว่าพิกเซลใดเป็นของบุคคลนั้น บทแนะนำนี้แสดงวิธีทำการแบ่งกลุ่มรูปภาพแบบเรียลไทม์ในอุปกรณ์ Android โดยใช้โมเดลแมชชีนเลิร์นนิงที่ฝึกไว้ล่วงหน้า

LiteRT: การผลักดันขีดจำกัดของ ML ในอุปกรณ์
เทคโนโลยีสำคัญที่ช่วยให้การแบ่งกลุ่มแบบเรียลไทม์ที่มีความเที่ยงตรงสูงในอุปกรณ์เคลื่อนที่คือ LiteRT LiteRT เป็นรันไทม์ประสิทธิภาพสูงรุ่นถัดไปของ Google สำหรับ TensorFlow Lite ซึ่งได้รับการออกแบบมาเพื่อให้ได้ประสิทธิภาพที่ดีที่สุดจากฮาร์ดแวร์พื้นฐาน
โดยทำได้ผ่านการใช้ตัวเร่งฮาร์ดแวร์อย่างชาญฉลาดและได้รับการเพิ่มประสิทธิภาพ เช่น GPU (หน่วยประมวลผลกราฟิก) และ NPU (หน่วยประมวลผลนิวรัล) การลดภาระงานด้านการคำนวณที่เข้มข้นของโมเดลการแบ่งกลุ่มจาก CPU แบบอเนกประสงค์ไปยังโปรเซสเซอร์เฉพาะทางเหล่านี้ช่วยให้ LiteRT ลดเวลาการอนุมานได้อย่างมาก การเร่งความเร็วนี้ช่วยให้เราสามารถเรียกใช้โมเดลที่ซับซ้อนได้อย่างราบรื่นในฟีดกล้องสด ซึ่งเป็นการขยายขีดจำกัดของสิ่งที่เราทำได้ด้วยแมชชีนเลิร์นนิงโดยตรงบนโทรศัพท์ หากไม่มีประสิทธิภาพในระดับนี้ การแบ่งกลุ่มแบบเรียลไทม์จะช้าและไม่ราบรื่นจนเกินไป ซึ่งจะส่งผลต่อประสบการณ์ของผู้ใช้
3. ตั้งค่า
โคลนที่เก็บ
ก่อนอื่น ให้โคลนที่เก็บสำหรับ LiteRT:
git clone https://github.com/google-ai-edge/litert-samples.git
litert-samples/compiled_model_api/image_segmentation คือไดเรกทอรีที่มีแหล่งข้อมูลทั้งหมดที่คุณต้องการ สำหรับ Codelab นี้ คุณจะต้องใช้โปรเจ็กต์ kotlin_cpu_gpu/android_starter เท่านั้น คุณอาจต้องตรวจสอบโปรเจ็กต์ที่เสร็จสมบูรณ์แล้วหากติดขัด kotlin_cpu_gpu/android
หมายเหตุเกี่ยวกับเส้นทางของไฟล์
บทแนะนำนี้จะระบุเส้นทางของไฟล์ในรูปแบบ Linux/macOS หากใช้ Windows คุณจะต้องปรับเส้นทางตามนั้น
นอกจากนี้ คุณควรทราบความแตกต่างระหว่างมุมมองโปรเจ็กต์ Android Studio กับมุมมองระบบไฟล์มาตรฐานด้วย มุมมองโปรเจ็กต์ของ Android Studio คือการแสดงไฟล์ของโปรเจ็กต์อย่างเป็นระบบ ซึ่งจัดระเบียบไว้สำหรับการพัฒนา Android เส้นทางไฟล์ในบทแนะนำนี้หมายถึงเส้นทางระบบไฟล์ ไม่ใช่เส้นทางในมุมมองโปรเจ็กต์ Android Studio
นำเข้าแอปเริ่มต้น
มาเริ่มกันด้วยการนำเข้าแอปเริ่มต้นไปยัง Android Studio
- เปิด Android Studio แล้วเลือกเปิด

- ไปที่ไดเรกทอรี
kotlin_cpu_gpu/android_starterแล้วเปิด

คุณควรซิงค์โปรเจ็กต์กับไฟล์ Gradle เมื่อกระบวนการนำเข้าเสร็จสิ้น เพื่อให้มั่นใจว่าแอปของคุณมีทรัพยากร Dependency ทั้งหมด
- เลือกซิงค์โปรเจ็กต์กับไฟล์ Gradle จากแถบเครื่องมือ Android Studio

- โปรดอย่าข้ามขั้นตอนนี้ หากขั้นตอนนี้ไม่ได้ผล บทแนะนำที่เหลือจะไม่มีประโยชน์
เรียกใช้แอปเริ่มต้น
ตอนนี้คุณได้นำเข้าโปรเจ็กต์ไปยัง Android Studio แล้ว และพร้อมที่จะเรียกใช้แอปเป็นครั้งแรก
เชื่อมต่ออุปกรณ์ Android ผ่าน USB กับคอมพิวเตอร์ แล้วคลิกเรียกใช้ในแถบเครื่องมือของ Android Studio

แอปควรเปิดขึ้นในอุปกรณ์ คุณจะเห็นฟีดกล้องสด แต่จะยังไม่มีการแบ่งส่วน การแก้ไขไฟล์ทั้งหมดที่คุณจะทำในบทแนะนำนี้จะอยู่ในไดเรกทอรี litert-samples/compiled_model_api/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/ai/edge/examples/image_segmentation (ตอนนี้คุณคงทราบแล้วว่าเหตุใด Android Studio จึงปรับโครงสร้างนี้ใหม่ 😃)

นอกจากนี้ คุณจะเห็นความคิดเห็น TODO ในไฟล์ ImageSegmentationHelper.kt, MainViewModel.kt และ view/SegmentationOverlay.kt ด้วย ในขั้นตอนต่อไปนี้ คุณจะใช้ฟังก์ชันการทำงานของการแบ่งกลุ่มรูปภาพโดยการกรอกข้อมูลใน TODOs เหล่านี้
4. ทำความเข้าใจแอปเริ่มต้น
แอปเริ่มต้นมีตรรกะ UI และการจัดการกล้องพื้นฐานอยู่แล้ว ภาพรวมโดยสรุปของไฟล์สำคัญมีดังนี้
app/src/main/java/com/google/ai/edge/examples/image_segmentation/MainActivity.kt: นี่คือจุดแรกเข้าหลักของแอปพลิเคชัน โดยจะตั้งค่า UI โดยใช้ Jetpack Compose และจัดการสิทธิ์เข้าถึงกล้องapp/src/main/java/com/google/ai/edge/examples/image_segmentation/MainViewModel.kt: ViewModel นี้จัดการสถานะ UI และประสานกระบวนการแบ่งกลุ่มรูปภาพapp/src/main/java/com/google/ai/edge/examples/image_segmentation/ImageSegmentationHelper.kt: ส่วนนี้คือที่ที่เราจะเพิ่มตรรกะหลักสำหรับการแบ่งกลุ่มรูปภาพ โดยจะจัดการการโหลดโมเดล ประมวลผลเฟรมกล้อง และเรียกใช้การอนุมานapp/src/main/java/com/google/ai/edge/examples/image_segmentation/view/CameraScreen.kt: ฟังก์ชันที่ประกอบกันได้นี้จะแสดงตัวอย่างกล้องและการซ้อนทับการแบ่งกลุ่มapp/download_model.gradle: สคริปต์นี้จะดาวน์โหลดselfie_multiclass.tfliteนี่คือโมเดลการแบ่งกลุ่มรูปภาพ TensorFlow Lite ที่ฝึกไว้ล่วงหน้าซึ่งเราจะใช้
5. ทำความเข้าใจ LiteRT และการเพิ่มการอ้างอิง
ตอนนี้มาเพิ่มฟังก์ชันการทำงานของการแบ่งกลุ่มรูปภาพลงในแอปเริ่มต้นกัน
1. เพิ่มทรัพยากร Dependency ของ LiteRT
ก่อนอื่น คุณต้องเพิ่มไลบรารี LiteRT ลงในโปรเจ็กต์ นี่คือก้าวแรกที่สำคัญในการเปิดใช้แมชชีนเลิร์นนิงในอุปกรณ์ด้วยรันไทม์ที่เพิ่มประสิทธิภาพของ Google
เปิดไฟล์ app/build.gradle.kts แล้วเพิ่มบรรทัดต่อไปนี้ลงในบล็อก dependencies
// LiteRT for on-device ML
implementation(libs.litert)
หลังจากเพิ่มทรัพยากร Dependency แล้ว ให้ซิงค์โปรเจ็กต์กับไฟล์ Gradle โดยคลิกปุ่มซิงค์เลยที่ปรากฏที่มุมขวาบนของ Android Studio

2. ทำความเข้าใจ API หลักของ LiteRT
เปิดImageSegmentationHelper.kt
ก่อนที่จะเขียนโค้ดการติดตั้งใช้งาน คุณควรทำความเข้าใจคอมโพเนนต์หลักของ LiteRT API ที่คุณจะใช้ ตรวจสอบว่าคุณกำลังนำเข้าจากแพ็กเกจ com.google.ai.edge.litert และเพิ่มการนำเข้าต่อไปนี้ที่ด้านบนของ ImageSegmentationHelper.kt
import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
CompiledModel: นี่คือคลาสส่วนกลางสำหรับการโต้ตอบกับโมเดล TFLite ซึ่งแสดงถึงโมเดลที่คอมไพล์ล่วงหน้าและเพิ่มประสิทธิภาพสำหรับตัวเร่งฮาร์ดแวร์ที่เฉพาะเจาะจง (เช่น CPU หรือ GPU) การคอมไพล์ล่วงหน้านี้เป็นฟีเจอร์หลักของ LiteRT ที่ช่วยให้การอนุมานเร็วขึ้นและมีประสิทธิภาพมากขึ้นCompiledModel.Options: คุณใช้คลาสเครื่องมือสร้างนี้เพื่อกำหนดค่าCompiledModelการตั้งค่าที่สำคัญที่สุดคือการระบุตัวเร่งฮาร์ดแวร์ที่คุณต้องการใช้ในการเรียกใช้โมเดลAccelerator: การแจงนับนี้ช่วยให้คุณเลือกฮาร์ดแวร์สำหรับการอนุมานได้ โปรเจ็กต์เริ่มต้นได้รับการกำหนดค่าให้จัดการตัวเลือกต่อไปนี้แล้วAccelerator.CPU: สำหรับการเรียกใช้โมเดลใน CPU ของอุปกรณ์ ตัวเลือกนี้เป็นตัวเลือกที่เข้ากันได้กับทุกอุปกรณ์Accelerator.GPU: สำหรับการเรียกใช้โมเดลใน GPU ของอุปกรณ์ ซึ่งมักจะเร็วกว่า CPU มากสำหรับโมเดลที่อิงตามรูปภาพ
- บัฟเฟอร์อินพุตและเอาต์พุต (
TensorBuffer): LiteRT ใช้TensorBufferสำหรับอินพุตและเอาต์พุตของโมเดล ซึ่งช่วยให้คุณควบคุมหน่วยความจำได้อย่างละเอียดและหลีกเลี่ยงการคัดลอกข้อมูลที่ไม่จำเป็น คุณจะได้รับบัฟเฟอร์เหล่านี้โดยตรงจากอินสแตนซ์CompiledModelโดยใช้model.createInputBuffers()และmodel.createOutputBuffers()จากนั้นเขียนข้อมูลอินพุตลงในบัฟเฟอร์และอ่านผลลัพธ์จากบัฟเฟอร์ model.run(): นี่คือฟังก์ชันที่ดำเนินการอนุมาน คุณส่งบัฟเฟอร์อินพุตและเอาต์พุตไปยัง LiteRT และ LiteRT จะจัดการงานที่ซับซ้อนในการเรียกใช้โมเดลในฮาร์ดแวร์ Accelerator ที่เลือก
6. การติดตั้งใช้งาน ImageSegmentationHelper เริ่มต้นให้เสร็จสมบูรณ์
ตอนนี้ก็ถึงเวลาเขียนโค้ดแล้ว คุณจะติดตั้งใช้งาน ImageSegmentationHelper.kt ในขั้นต้นให้เสร็จสมบูรณ์ ซึ่งเกี่ยวข้องกับการตั้งค่าSegmenterคลาสส่วนตัวเพื่อเก็บโมเดล LiteRT และการใช้ฟังก์ชัน cleanup() เพื่อเผยแพร่โมเดลอย่างเหมาะสม
- ทําให้คลาส
Segmenterและฟังก์ชันcleanup()เสร็จสมบูรณ์: ในไฟล์ImageSegmentationHelper.ktคุณจะเห็นโครงสร้างของคลาสส่วนตัวชื่อSegmenterและฟังก์ชันชื่อcleanup()ก่อนอื่น ให้ทําSegmenterคลาสให้เสร็จสมบูรณ์โดยกําหนดตัวสร้างเพื่อเก็บโมเดล สร้างพร็อพเพอร์ตี้สําหรับบัฟเฟอร์อินพุต/เอาต์พุต และเพิ่มเมธอดclose()เพื่อเผยแพร่โมเดล จากนั้นใช้ฟังก์ชันcleanup()เพื่อเรียกใช้เมธอดclose()ใหม่นี้ แทนที่คลาสSegmenterและฟังก์ชันcleanup()ที่มีอยู่ด้วยรายการต่อไปนี้ (~บรรทัดที่ 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() } } - กำหนดเมธอด toAccelerator: เมธอดนี้จะแมปการแจงนับตัวเร่งที่กำหนดจากเมนูตัวเร่งไปยังการแจงนับตัวเร่งที่เฉพาะเจาะจงสำหรับโมดูล LiteRT ที่นำเข้า (~บรรทัดที่ 225)
fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator { return when (acceleratorEnum) { AcceleratorEnum.CPU -> Accelerator.CPU AcceleratorEnum.GPU -> Accelerator.GPU } } - เริ่มต้น
CompiledModel: ตอนนี้ให้ค้นหาฟังก์ชันinitSegmenterนี่คือส่วนที่คุณจะสร้างอินสแตนซ์CompiledModelและใช้เพื่อสร้างอินสแตนซ์ของคลาสSegmenterที่กำหนดไว้ในตอนนี้ โค้ดนี้จะตั้งค่าโมเดลด้วยตัวเร่งที่ระบุ (CPU หรือ GPU) และเตรียมโมเดลสำหรับการอนุมาน แทนที่TODOในinitSegmenterด้วยการติดตั้งใช้งานต่อไปนี้ (Cmd/Ctrl+f ‘initSegmenter` หรือ ~บรรทัดที่ 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. เริ่มการแบ่งกลุ่มและการประมวลผลล่วงหน้า
ตอนนี้เรามีโมเดลแล้ว เราจึงต้องทริกเกอร์กระบวนการแบ่งกลุ่มและเตรียมข้อมูลอินพุตสำหรับโมเดล
การแบ่งกลุ่มทริกเกอร์
กระบวนการแบ่งกลุ่มจะเริ่มต้นใน MainViewModel.kt ซึ่งรับเฟรมจากกล้อง
เปิดMainViewModel.kt
- ทริกเกอร์การแบ่งกลุ่มจากเฟรมกล้อง: ฟังก์ชัน
segmentในMainViewModelเป็นจุดเริ่มต้นของงานการแบ่งกลุ่ม ระบบจะเรียกใช้ฟังก์ชันเหล่านี้ทุกครั้งที่มีรูปภาพใหม่จากกล้องหรือเลือกจากแกลเลอรี จากนั้นฟังก์ชันเหล่านี้จะเรียกใช้เมธอดsegmentในImageSegmentationHelperแทนที่TODOในทั้ง 2 ฟังก์ชันsegmentด้วยข้อความต่อไปนี้ (บรรทัดที่ ~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) } }
ประมวลผลรูปภาพล่วงหน้า
ตอนนี้เรากลับไปที่ ImageSegmentationHelper.kt เพื่อจัดการการประมวลผลรูปภาพล่วงหน้ากัน
เปิดImageSegmentationHelper.kt
- ใช้ฟังก์ชัน Public
segment: ฟังก์ชันนี้ทำหน้าที่เป็น Wrapper ที่เรียกใช้ฟังก์ชันsegmentแบบส่วนตัวภายในคลาสSegmenterแทนที่TODOด้วย (~บรรทัดที่ 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) } - ใช้การประมวลผลล่วงหน้า: ฟังก์ชัน
segmentส่วนตัวภายในคลาสSegmenterคือที่ที่เราจะทำการเปลี่ยนรูปแบบที่จำเป็นในรูปภาพอินพุตเพื่อเตรียมรูปภาพสำหรับโมเดล ซึ่งรวมถึงการปรับขนาด หมุน และปรับรูปภาพให้เป็นปกติ จากนั้นฟังก์ชันนี้จะเรียกใช้ฟังก์ชันsegmentส่วนตัวอีกฟังก์ชันหนึ่งเพื่อทำการอนุมาน แทนที่TODOในฟังก์ชันsegment(bitmap: Bitmap, ...)ด้วย (~บรรทัดที่ 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. การอนุมานหลักด้วย LiteRT
เมื่อประมวลผลข้อมูลอินพุตล่วงหน้าแล้ว ตอนนี้เราสามารถเรียกใช้การอนุมานหลักโดยใช้ LiteRT ได้
เปิดImageSegmentationHelper.kt
- การใช้การดำเนินการโมเดล: ฟังก์ชัน
segment(inputFloatArray: FloatArray)ส่วนตัวคือที่ที่เราโต้ตอบกับเมธอด LiteRTrun()โดยตรง เราเขียนข้อมูลที่ประมวลผลล่วงหน้าไปยังบัฟเฟอร์อินพุต เรียกใช้โมเดล และอ่านผลลัพธ์จากบัฟเฟอร์เอาต์พุต แทนที่TODOในฟังก์ชันนี้ด้วย (~บรรทัดที่ 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. การประมวลผลภายหลังและการแสดงภาพซ้อน
หลังจากเรียกใช้การอนุมาน เราจะได้รับเอาต์พุตดิบจากโมเดล เราต้องประมวลผลเอาต์พุตนี้เพื่อสร้างมาสก์การแบ่งกลุ่มภาพ แล้วจึงแสดงบนหน้าจอ
เปิดImageSegmentationHelper.kt
- ใช้การประมวลผลเอาต์พุต: ฟังก์ชัน
processImageจะแปลงเอาต์พุตจุดลอยตัวดิบจากโมเดลเป็นByteBufferที่แสดงถึงมาสก์การแบ่งกลุ่ม โดยจะพิจารณาจากคลาสที่มีโอกาสสูงสุดสำหรับแต่ละพิกเซล แทนที่TODOด้วย (~บรรทัดที่ 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
เปิดMainViewModel.kt
- รวบรวมและประมวลผลผลลัพธ์การแบ่งกลุ่ม: ตอนนี้เรากลับไปที่
MainViewModelเพื่อประมวลผลผลลัพธ์การแบ่งกลุ่มจากImageSegmentationHelpersegmentationUiShareFlowจะรวบรวมSegmentationResultแปลงมาสก์เป็นBitmapที่มีสีสัน และแสดงใน UI แทนที่TODOในพร็อพเพอร์ตี้segmentationUiShareFlowด้วย (~บรรทัดที่ 63) อย่าแทนที่โค้ดที่มีอยู่แล้ว เพียงแค่กรอกข้อมูลในเนื้อหา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) } }
เปิดview/SegmentationOverlay.kt
ส่วนสุดท้ายคือการวางภาพซ้อนทับการแบ่งกลุ่มอย่างถูกต้องเมื่อผู้ใช้พลิกไปใช้กล้องหน้า ฟีดกล้องจะพลิกโดยอัตโนมัติสำหรับกล้องหน้า ดังนั้นเราจึงต้องพลิกแนวนอนภาพซ้อนทับ Bitmap ในลักษณะเดียวกันเพื่อให้ภาพซ้อนทับสอดคล้องกับการแสดงตัวอย่างกล้องอย่างถูกต้อง
- จัดการการวางแนวภาพซ้อนทับ: ค้นหา
TODOในไฟล์SegmentationOverlay.ktแล้วแทนที่ด้วยโค้ดต่อไปนี้ โค้ดนี้จะตรวจสอบว่ากล้องหน้าทำงานอยู่หรือไม่ หากทำงานอยู่ ก็จะพลิกภาพซ้อนทับในแนวนอนBitmapก่อนที่จะวาดบนCanvas(~บรรทัดที่ 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. เรียกใช้และใช้แอปสุดท้าย
ตอนนี้คุณได้ทำการเปลี่ยนแปลงโค้ดที่จำเป็นทั้งหมดแล้ว ได้เวลาเรียกใช้แอปและดูผลงานของคุณแล้ว
- เรียกใช้แอป: เชื่อมต่ออุปกรณ์ Android แล้วคลิกเรียกใช้ในแถบเครื่องมือของ Android Studio

- ทดสอบฟีเจอร์: เมื่อเปิดแอปแล้ว คุณควรเห็นฟีดกล้องสดพร้อมการซ้อนทับการแบ่งส่วนที่มีสีสัน
- สลับกล้อง: แตะไอคอนพลิกกล้องที่ด้านบนเพื่อสลับระหว่างกล้องหน้าและกล้องหลัง สังเกตว่าการซ้อนทับปรับทิศทางของตัวเองอย่างถูกต้องได้อย่างไร
- เปลี่ยนตัวเร่ง: แตะปุ่ม "CPU" หรือ "GPU" ที่ด้านล่างเพื่อเปลี่ยนตัวเร่งฮาร์ดแวร์ สังเกตการเปลี่ยนแปลงในเวลาอนุมานที่แสดงที่ด้านล่างของหน้าจอ GPU ควรเร็วกว่าเดิมอย่างมาก
- ใช้รูปภาพในแกลเลอรี: แตะแท็บ "แกลเลอรี" ที่ด้านบนเพื่อเลือกรูปภาพจากแกลเลอรีรูปภาพของอุปกรณ์ แอปจะทำการแบ่งกลุ่มในรูปภาพนิ่งที่เลือก

ตอนนี้คุณมีแอปการแบ่งกลุ่มรูปภาพแบบเรียลไทม์ที่ทำงานได้อย่างเต็มรูปแบบซึ่งขับเคลื่อนโดย LiteRT แล้ว
11. ขั้นสูง (ไม่บังคับ): การใช้ NPU
ที่เก็บนี้ยังมีแอปเวอร์ชันที่ได้รับการเพิ่มประสิทธิภาพสำหรับหน่วยประมวลผลนิวรัล (NPU) ด้วย เวอร์ชัน NPU สามารถเพิ่มประสิทธิภาพได้อย่างมากในอุปกรณ์ที่มี NPU ที่เข้ากันได้
หากต้องการลองใช้เวอร์ชัน NPU ให้เปิดkotlin_npu/androidโปรเจ็กต์ใน Android Studio โค้ดมีความคล้ายคลึงกับเวอร์ชัน CPU/GPU มาก และได้รับการกำหนดค่าให้ใช้ตัวแทน NPU
ดูตัวอย่างการใช้งานตัวแทน NPU ได้ในแอปตัวอย่าง
12. ยินดีด้วย
คุณสร้างแอป Android ที่ทำการแบ่งกลุ่มรูปภาพแบบเรียลไทม์โดยใช้ LiteRT ได้สำเร็จแล้ว คุณได้เรียนรู้วิธีต่อไปนี้
- ผสานรวมรันไทม์ LiteRT เข้ากับแอป Android
- โหลดและเรียกใช้โมเดลการแบ่งกลุ่มรูปภาพ TFLite
- ประมวลผลอินพุตของโมเดลล่วงหน้า
- ประมวลผลเอาต์พุตของโมเดลเพื่อสร้างมาสก์การแบ่งกลุ่ม
- ใช้ CameraX สำหรับแอปกล้องแบบเรียลไทม์
ขั้นตอนถัดไป
- ลองใช้โมเดลการแบ่งกลุ่มรูปภาพอื่น
- ทดสอบกับตัวแทน LiteRT ต่างๆ (CPU, GPU, NPU)