1. บทนำ
MediaPipe คืออะไร
MediaPipe Solutions ให้คุณนำโซลูชันแมชชีนเลิร์นนิง (ML) ไปใช้กับแอป โดยมีเฟรมเวิร์กสำหรับกำหนดค่าไปป์ไลน์การประมวลผลที่สร้างไว้ล่วงหน้าซึ่งจะให้ผลลัพธ์ที่เป็นประโยชน์ น่าสนใจ และมีประโยชน์แก่ผู้ใช้ได้ทันที คุณยังสามารถปรับแต่งโซลูชันเหล่านี้ได้ด้วย MediaPipe Model Maker เพื่ออัปเดตโมเดลเริ่มต้น
การจัดประเภทอิมเมจเป็นหนึ่งในงานด้านวิสัยทัศน์ ML มากมายที่ MediaPipe Solutions MediaPipe Tasks ใช้ได้กับ Android, iOS, Python (รวมถึง Raspberry Pi!) และเว็บ
ใน Codelab นี้ คุณจะเริ่มต้นด้วยแอป Android ที่ให้คุณวาดตัวเลขบนหน้าจอได้ จากนั้นจึงเพิ่มฟังก์ชันที่จำแนกตัวเลขที่วาดเหล่านั้นเป็นค่าเดียวตั้งแต่ 0 ถึง 9
สิ่งที่คุณจะได้เรียนรู้
- วิธีรวมงานการจัดประเภทรูปภาพในแอป Android ด้วย MediaPipe Tasks
สิ่งที่ต้องมี
- Android Studio เวอร์ชันติดตั้ง (Codelab นี้เขียนและทดสอบด้วย Android Studio Giraffe)
- อุปกรณ์ Android หรือโปรแกรมจำลองสำหรับเรียกใช้แอป
- ความรู้เบื้องต้นเกี่ยวกับการพัฒนาแอป Android (ไม่ใช่คำว่า "สวัสดีโลก" นะ แต่ก็ไม่ได้ไกลเกินไปนะ)
2. เพิ่มงาน MediaPipe ลงในแอป Android
ดาวน์โหลดแอปเริ่มต้นสำหรับ Android
Codelab นี้จะเริ่มต้นด้วยตัวอย่างที่ทำไว้ล่วงหน้าเพื่อให้คุณวาดบนหน้าจอได้ คุณสามารถค้นหาแอปเริ่มต้นในที่เก็บ MediaPipe Samples อย่างเป็นทางการได้ที่นี่ โคลนที่เก็บหรือดาวน์โหลดไฟล์ 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 ที่อยู่ในโมดูลแอป จากนั้นเลื่อนลงไปที่บล็อกdependencies
- คุณควรเห็นความคิดเห็นที่ด้านล่างของบล็อกนั้นๆ ที่ระบุว่า // ขั้นตอนที่ 1 การนำเข้าการขึ้นต่อกัน
- แทนที่บรรทัดนี้ด้วยการติดตั้งต่อไปนี้
implementation("com.google.mediapipe:tasks-vision:latest.release")
- คลิกปุ่ม Sync Now ที่ปรากฏในแบนเนอร์ที่ด้านบนของ 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 สร้างฟังก์ชันการแยกประเภท แล้วเพิ่มโค้ดต่อไปนี้ ฟังก์ชันนี้จะยอมรับบิตแมป ซึ่งในกรณีนี้จะเป็นตัวเลขที่วาด แปลงเป็นออบเจ็กต์รูปภาพ 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)
}
}
และทั้งหมดนี้คือไฟล์ตัวช่วย ในส่วนถัดไป คุณจะต้องกรอกข้อมูลในขั้นตอนสุดท้ายเพื่อเริ่มแยกประเภทตัวเลขที่คุณวาด
4. เรียกใช้การอนุมานด้วยงาน MediaPipe
คุณสามารถเริ่มส่วนนี้ได้โดยเปิดคลาส 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 เนื่องจากมีการทริกเกอร์ Callback นี้จากเทรดในเบื้องหลัง คุณจึงต้องเรียกใช้การอัปเดต UI ในเทรด UI ของ Android ด้วย
- ขณะเพิ่มฟังก์ชันใหม่จากอินเทอร์เฟซในขั้นตอนข้างต้น คุณจะต้องเพิ่มการประกาศการใช้งานที่ด้านบนของชั้นเรียนด้วย
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
- ที่ส่วนบนสุดของชั้นเรียน คุณจะเห็นความคิดเห็นที่ระบุว่า // ขั้นตอน 7a เริ่มต้นตัวแยกประเภท คุณจะต้องวางการประกาศสำหรับ DigitClassifierHelper ที่นี่
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
- เลื่อนลงไปที่ // STEP 7b เริ่มต้นตัวแยกประเภท คุณสามารถเริ่มต้น digitalClassifierHelper ภายในฟังก์ชัน 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
)
- สำหรับขั้นตอนสุดท้าย ให้ค้นหาความคิดเห็น // ขั้นตอน 8a*: izing* และเพิ่มโค้ดต่อไปนี้เพื่อเรียกฟังก์ชันใหม่ที่คุณจะเพิ่มในอีกสักครู่ โค้ดบล็อกนี้จะเรียกใช้การแยกประเภทเมื่อคุณยกนิ้วออกจากพื้นที่วาดภาพในแอป
// STEP 8a: classify
classifyDrawing()
- สุดท้าย ให้มองหาความคิดเห็น // STEP 8b classify เพื่อเพิ่มฟังก์ชันอ้างอิงจากclassDrawing() วิธีนี้จะดึงบิตแมปออกจาก Canvas แล้วส่งไปยัง DigitClassifierHelper เพื่อทำการแยกประเภทเพื่อรับผลลัพธ์ในฟังก์ชันอินเทอร์เฟซ onResults()
// STEP 8b classify
private fun classifyDrawing() {
val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
digitClassifierHelper.classify(bitmap)
}
5. ติดตั้งใช้งานและทดสอบแอป
หลังจากนั้น คุณต้องมีแอปที่ใช้งานได้ซึ่งจำแนกตัวเลขที่วาดบนหน้าจอได้ โปรดติดตั้งใช้งานแอปกับโปรแกรมจำลอง Android หรืออุปกรณ์ Android จริงเพื่อทดสอบ
- คลิกเรียกใช้ ( ) ในแถบเครื่องมือ Android Studio เพื่อเรียกใช้แอป
- วาดตัวเลขใดๆ ลงบนแพดสำหรับวาดภาพแล้วดูว่าแอปรู้จักตัวเลขดังกล่าวหรือไม่ โมเดลควรแสดงตัวเลขที่โมเดลเชื่อว่าวาด รวมถึงระยะเวลาที่ใช้ในการคาดการณ์ตัวเลขนั้น
6. ยินดีด้วย
สำเร็จแล้ว! ใน Codelab นี้ คุณได้เรียนรู้วิธีเพิ่มการจัดประเภทรูปภาพลงในแอป Android และโดยเฉพาะอย่างยิ่งวิธีจำแนกตัวเลขที่วาดด้วยมือโดยใช้โมเดล MNIST
ขั้นตอนถัดไป
- ตอนนี้คุณสามารถแยกประเภทตัวเลขได้แล้ว คุณอาจต้องการฝึกโมเดลของคุณเองให้แยกประเภทตัวอักษรที่วาด หรือแยกประเภทสัตว์ หรือสิ่งอื่นๆ จำนวนมาก คุณสามารถดูเอกสารสำหรับการฝึกโมเดลการจัดประเภทรูปภาพใหม่ด้วย MediaPipe Model Maker ในหน้า developers.google.com/mediapipe
- เรียนรู้เกี่ยวกับ งาน MediaPipe อื่นๆ ที่พร้อมใช้งานสำหรับ Android ซึ่งรวมถึงการตรวจจับจุดสังเกตใบหน้า การจดจำท่าทางสัมผัส และการจัดประเภทเสียง
เราจะตั้งตารอสิ่งดีๆ ทั้งหมดจากคุณ