1. บทนำ
MediaPipe คืออะไร
โซลูชัน MediaPipe ช่วยให้คุณใช้โซลูชันแมชชีนเลิร์นนิง (ML) กับแอปได้ ซึ่งจะเป็นเฟรมเวิร์กสำหรับการกำหนดค่าไปป์ไลน์การประมวลผลที่สร้างไว้ล่วงหน้าซึ่งจะแสดงผลลัพธ์ที่ดึงดูดใจและมีประโยชน์ต่อผู้ใช้ได้ทันที คุณยังปรับแต่งโซลูชันเหล่านี้ได้ด้วย MediaPipe Model Maker เพื่ออัปเดตโมเดลเริ่มต้น
การจัดประเภทรูปภาพเป็นหนึ่งในงานด้านวิสัยทัศน์ด้วย ML หลายอย่างที่ MediaPipe Solutions มีให้บริการ MediaPipe Tasks พร้อมใช้งานสำหรับ Android, iOS, Python (รวมถึง Raspberry Pi) และเว็บ
ใน Codelab นี้ คุณจะเริ่มด้วยแอป Android ที่ช่วยให้คุณวาดตัวเลขบนหน้าจอ จากนั้นจะเพิ่มฟังก์ชันการทำงานที่จัดประเภทตัวเลขที่วาดเป็นค่าเดี่ยวตั้งแต่ 0 ถึง 9
สิ่งที่คุณจะได้เรียนรู้
- วิธีรวมงานการจัดประเภทรูปภาพในแอป Android ด้วย MediaPipe Tasks
สิ่งที่คุณต้องมี
- Android Studio เวอร์ชันที่ติดตั้งไว้ (โค้ดแล็บนี้เขียนและทดสอบด้วย Android Studio Giraffe)
- อุปกรณ์ Android หรือโปรแกรมจำลองการทำงานสำหรับเรียกใช้แอป
- ความรู้พื้นฐานเกี่ยวกับการพัฒนา Android (ไม่ใช่ "Hello World" แต่ก็ไม่ได้ห่างกันมากนัก)
2. เพิ่ม MediaPipe Tasks ลงในแอป Android
ดาวน์โหลดแอปเริ่มต้นสำหรับ Android
Codelab นี้จะเริ่มต้นด้วยตัวอย่างที่สร้างไว้ล่วงหน้าซึ่งให้คุณวาดบนหน้าจอได้ คุณดูแอปเริ่มต้นได้ในที่เก็บตัวอย่าง MediaPipe อย่างเป็นทางการที่นี่ โคลนที่เก็บหรือดาวน์โหลดไฟล์ ZIP โดยคลิกโค้ด > ดาวน์โหลด ZIP
นําเข้าแอปไปยัง Android Studio
- เปิด Android Studio
- จากหน้าจอยินดีต้อนรับสู่ Android Studio ให้เลือกเปิดที่มุมขวาบน
- ไปที่ตำแหน่งที่คุณโคลนหรือดาวน์โหลดที่เก็บ และเปิดไดเรกทอรี codelabs/digitclassifier/android/start
- ตรวจสอบว่าทุกอย่างเปิดอย่างถูกต้องโดยคลิกลูกศรเรียกใช้สีเขียว ( ) ที่ด้านขวาบนของ Android Studio
- คุณควรเห็นแอปเปิดขึ้นพร้อมหน้าจอสีดำที่วาดได้ รวมถึงปุ่มล้างเพื่อรีเซ็ตหน้าจอดังกล่าว แม้ว่าคุณจะวาดบนหน้าจอนั้นได้ แต่หน้าจอนั้นทำอะไรไม่ได้มากนัก เราจึงจะเริ่มแก้ไขปัญหานี้ทันที
รุ่น
เมื่อเรียกใช้แอปเป็นครั้งแรก คุณอาจสังเกตเห็นว่าระบบดาวน์โหลดไฟล์ชื่อ mnist.tflite และจัดเก็บไว้ในไดเรกทอรี assets ของแอป เพื่อความสะดวก เราได้นำ MNIST ซึ่งเป็นโมเดลที่รู้จักซึ่งจัดประเภทตัวเลขแล้วไปเพิ่มลงในแอปผ่านการใช้สคริปต์ download_models.gradle ในโปรเจ็กต์ หากตัดสินใจที่จะฝึกโมเดลที่กำหนดเอง เช่น โมเดลสำหรับตัวอักษรที่เขียนด้วยมือ คุณจะต้องนำไฟล์ download_models.gradle ออก ลบการอ้างอิงไฟล์ดังกล่าวในไฟล์ build.gradle ระดับแอป และเปลี่ยนชื่อโมเดลในภายหลังในโค้ด (โดยเฉพาะในไฟล์ DigitClassifierHelper.kt)
อัปเดต build.gradle
คุณต้องนำเข้าไลบรารีก่อนจึงจะเริ่มใช้ MediaPipe Tasks ได้
- เปิดไฟล์ build.gradle ในโมดูล app แล้วเลื่อนลงไปที่บล็อก dependencies
- คุณควรเห็นความคิดเห็นที่ด้านล่างของบล็อกนั้นซึ่งระบุว่า // ขั้นตอนที่ 1 การนําเข้า Dependency
- แทนที่บรรทัดนั้นด้วยการใช้งานต่อไปนี้
implementation("com.google.mediapipe:tasks-vision:latest.release")
- คลิกปุ่มซิงค์เลยที่ปรากฏในแบนเนอร์ที่ด้านบนของ Android Studio เพื่อดาวน์โหลด Dependency นี้
3. สร้างตัวช่วยแยกประเภทตัวเลขของ MediaPipe Tasks
ในขั้นตอนถัดไป คุณจะกรอกคลาสที่จะทํางานหนักสําหรับการจัดประเภทด้วยแมชชีนเลิร์นนิง เปิด DigitClassifierHelper.kt แล้วมาเริ่มกันเลย
- ค้นหาความคิดเห็นที่ด้านบนของชั้นเรียนที่ระบุว่า // ขั้นตอนที่ 2 สร้าง Listener
- แทนที่บรรทัดนั้นด้วยโค้ดต่อไปนี้ ซึ่งจะสร้าง Listener ที่จะใช้ในการส่งผลลัพธ์จากคลาส DigitClassifierHelper กลับไปยังที่ใดก็ตามที่รอรับผลลัพธ์เหล่านั้น (ในกรณีนี้จะเป็นคลาส DigitCanvasFragment แต่เราจะพูดถึงในลำดับถัดไป)
// STEP 2 Create listener
interface DigitClassifierListener {
fun onError(error: String)
fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
)
}
- นอกจากนี้ คุณจะต้องยอมรับ DigitClassifierListener เป็นพารามิเตอร์ที่ไม่บังคับสําหรับคลาสด้วย
class DigitClassifierHelper(
val context: Context,
val digitClassifierListener: DigitClassifierListener?
) {
- เลื่อนลงไปที่บรรทัดที่มีข้อความว่า // ขั้นตอนที่ 3 กำหนดตัวจัดหมวดหมู่ แล้วเพิ่มบรรทัดต่อไปนี้เพื่อสร้างตัวยึดตำแหน่งสำหรับ ImageClassifier ที่จะใช้สําหรับแอปนี้
// ขั้นตอนที่ 3 กําหนดตัวแยกประเภท
private var digitClassifier: ImageClassifier? = null
- เพิ่มฟังก์ชันต่อไปนี้ตรงที่คุณเห็นความคิดเห็น // ขั้นตอนที่ 4 ตั้งค่าตัวจัดประเภท
// STEP 4 set up classifier
private fun setupDigitClassifier() {
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
}
ในส่วนด้านบนมีการดำเนินการหลายอย่างเกิดขึ้น เรามาแยกดูทีละส่วนเพื่อให้เข้าใจสิ่งที่เกิดขึ้นกัน
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
บล็อกนี้จะกําหนดพารามิเตอร์ที่ ImageClassifier ใช้ ซึ่งรวมถึงโมเดลที่เก็บไว้ในแอป (mnist.tflite) ในส่วน BaseOptions และ RunningMode ในส่วน ImageClassifierOptions ซึ่งในกรณีนี้คือ IMAGE แต่ VIDEO และ LIVE_STREAM เป็นตัวเลือกเพิ่มเติมที่ใช้ได้ พารามิเตอร์อื่นๆ ที่มี ได้แก่ MaxResults ซึ่งจำกัดให้โมเดลแสดงผลลัพธ์สูงสุด และ ScoreThreshold ซึ่งกำหนดความน่าเชื่อถือขั้นต่ำที่โมเดลต้องมีในผลลัพธ์ก่อนที่จะแสดงผล
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
หลังจากสร้างตัวเลือกการกําหนดค่าแล้ว คุณสามารถสร้าง ImageClassifier ใหม่ได้โดยส่งบริบทและตัวเลือก หากกระบวนการเริ่มต้นใช้งานดังกล่าวเกิดข้อผิดพลาด ระบบจะแสดงข้อผิดพลาดผ่าน DigitClassifierListener
- เนื่องจากเราต้องการสร้างอินทิลีเจิล ImageClassifier ก่อนใช้งาน คุณจึงเพิ่มบล็อก init เพื่อเรียกใช้ setupDigitClassifier() ได้
init {
setupDigitClassifier()
}
- สุดท้าย ให้เลื่อนลงไปที่ความคิดเห็นที่ระบุว่า // ขั้นตอนที่ 5 สร้างฟังก์ชันการจัดประเภท แล้วเพิ่มโค้ดต่อไปนี้ ฟังก์ชันนี้จะรับ Bitmap ซึ่งในกรณีนี้คือตัวเลขที่วาด แปลงเป็นออบเจ็กต์รูปภาพ MediaPipe (MPImage) จากนั้นจัดประเภทรูปภาพนั้นโดยใช้ ImageClassifier รวมถึงบันทึกระยะเวลาในการอนุมานก่อนที่จะแสดงผลลัพธ์เหล่านั้นผ่าน DigitClassifierListener
// STEP 5 create classify function
fun classify(image: Bitmap) {
if (digitClassifier == null) {
setupDigitClassifier()
}
// Convert the input Bitmap object to an MPImage object to run inference.
// Rotating shouldn't be necessary because the text is being extracted from
// a view that should always be correctly positioned.
val mpImage = BitmapImageBuilder(image).build()
// Inference time is the difference between the system time at the start and finish of the
// process
val startTime = SystemClock.uptimeMillis()
// Run image classification using MediaPipe Image Classifier API
digitClassifier?.classify(mpImage)?.also { classificationResults ->
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
digitClassifierListener?.onResults(classificationResults, inferenceTimeMs)
}
}
เท่านี้สำหรับไฟล์ Helper ในส่วนถัดไป คุณจะกรอกข้อมูลในขั้นตอนสุดท้ายเพื่อเริ่มจัดประเภทตัวเลขที่วาด
4. เรียกใช้การอนุมานด้วย MediaPipe Tasks
คุณเริ่มส่วนนี้ได้ด้วยการเปิดคลาส DigitCanvasFragment ใน Android Studio ซึ่งเป็นที่ที่งานทั้งหมดจะเกิดขึ้น
- คุณควรเห็นความคิดเห็นที่ระบุว่า // ขั้นตอนที่ 6 ตั้งค่า Listener ที่ด้านล่างสุดของไฟล์นี้ คุณจะเพิ่มฟังก์ชัน onResults() และ onError() ที่เชื่อมโยงกับ Listener ได้ที่นี่
// STEP 6 Set up listener
override fun onError(error: String) {
activity?.runOnUiThread {
Toast.makeText(requireActivity(), error, Toast.LENGTH_SHORT).show()
fragmentDigitCanvasBinding.tvResults.text = ""
}
}
override fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
) {
activity?.runOnUiThread {
fragmentDigitCanvasBinding.tvResults.text = results
.classificationResult()
.classifications().get(0)
.categories().get(0)
.categoryName()
fragmentDigitCanvasBinding.tvInferenceTime.text = requireActivity()
.getString(R.string.inference_time, inferenceTime.toString())
}
}
onResults() มีความสําคัญอย่างยิ่งเนื่องจากจะแสดงผลลัพธ์ที่ได้รับจาก ImageClassifier เนื่องจากระบบเรียกใช้การเรียกกลับนี้จากเธรดเบื้องหลัง คุณจึงต้องเรียกใช้การอัปเดต UI ในเธรด UI ของ Android ด้วย
- เมื่อคุณเพิ่มฟังก์ชันใหม่จากอินเทอร์เฟซในขั้นตอนด้านบน คุณจะต้องเพิ่มประกาศการใช้งานที่ด้านบนของคลาสด้วย
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
- คุณควรเห็นความคิดเห็นที่ระบุว่า // STEP 7a Initialize classifier อยู่บริเวณด้านบนของชั้นเรียน คุณจะวางประกาศสำหรับ DigitClassifierHelper ไว้ที่นี่
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
- เลื่อนลงไปที่ // STEP 7b เริ่มต้นการทำงานของคลาสификатор คุณสามารถเริ่มต้นการทำงานของ digitClassifierHelper ภายในฟังก์ชัน onViewCreated()
// STEP 7b Initialize classifier
// Initialize the digit classifier helper, which does all of the
// ML work. This uses the default values for the classifier.
digitClassifierHelper = DigitClassifierHelper(
context = requireContext(), digitClassifierListener = this
)
- สำหรับขั้นตอนสุดท้าย ให้ค้นหาความคิดเห็น // STEP 8a*: classify* แล้วเพิ่มโค้ดต่อไปนี้เพื่อเรียกใช้ฟังก์ชันใหม่ที่คุณจะเพิ่มในอีกสักครู่ บล็อกโค้ดนี้จะทริกเกอร์การจัดประเภทเมื่อคุณยกนิ้วออกจากพื้นที่วาดในแอป
// STEP 8a: classify
classifyDrawing()
- สุดท้าย ให้มองหาความคิดเห็น // STEP 8b classify เพื่อเพิ่มฟังก์ชันใหม่ classifyDrawing() ซึ่งจะดึงข้อมูลบิตแมปจากผืนผ้าใบ จากนั้นส่งไปยัง DigitClassifierHelper เพื่อทำการแยกประเภทเพื่อรับผลลัพธ์ในฟังก์ชันอินเทอร์เฟซ onResults()
// STEP 8b classify
private fun classifyDrawing() {
val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
digitClassifierHelper.classify(bitmap)
}
5. ทำให้แอปใช้งานได้และทดสอบ
หลังจากทำขั้นตอนทั้งหมดแล้ว คุณควรมีแอปที่ใช้งานได้ซึ่งสามารถจัดประเภทตัวเลขที่วาดบนหน้าจอ โปรดติดตั้งใช้งานแอปใน Android Emulator หรืออุปกรณ์ Android จริงเพื่อทดสอบ
- คลิก "เรียกใช้" () ในแถบเครื่องมือของ Android Studio เพื่อเรียกใช้แอป
- วาดตัวเลขใดก็ได้ลงในผืนวาดภาพ แล้วดูว่าแอปจดจำตัวเลขนั้นได้หรือไม่ โดยควรแสดงทั้งตัวเลขที่โมเดลเชื่อว่าวาดขึ้น และระยะเวลาที่ใช้ในการคาดการณ์ตัวเลขนั้น
6. ยินดีด้วย
สำเร็จแล้ว! ในโค้ดแล็บนี้ คุณได้เรียนรู้วิธีเพิ่มการจัดประเภทรูปภาพลงในแอป Android และโดยเฉพาะวิธีจัดประเภทตัวเลขที่เขียนด้วยมือโดยใช้โมเดล MNIST
ขั้นตอนถัดไป
- เมื่อจัดประเภทตัวเลขได้แล้ว คุณอาจต้องการฝึกโมเดลของคุณเองเพื่อจัดประเภทตัวอักษรที่วาด หรือจัดประเภทสัตว์ หรือสิ่งอื่นๆ อีกมากมาย ดูเอกสารประกอบสำหรับการฝึกโมเดลการจัดประเภทรูปภาพใหม่ด้วย MediaPipe Model Maker ได้ในหน้า developers.google.com/mediapipe
- ดูข้อมูลเกี่ยวกับ MediaPipe Tasks อื่นๆ ที่มีให้บริการสำหรับ Android ซึ่งรวมถึงการตรวจจับจุดสังเกตบนใบหน้า การจดจำท่าทาง และการแยกประเภทเสียง
เราหวังว่าจะได้ดูผลงานเจ๋งๆ ที่คุณสร้างขึ้น