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: דחיפה של הקצה של למידת מכונה במכשיר
טכנולוגיה מרכזית שמאפשרת פילוח בזמן אמת באיכות גבוהה במכשירים ניידים היא LiteRT. LiteRT הוא סביבת זמן ריצה מהדור הבא של Google ל-TensorFlow Lite, עם ביצועים גבוהים. הוא מתוכנן להפיק את הביצועים הכי טובים מהחומרה הבסיסית.
הוא עושה זאת באמצעות שימוש חכם ומותאם במאיצי חומרה כמו GPU (מעבד גרפי) ו-NPU (מעבד עצבי). העברת עומס העבודה החישובי האינטנסיבי של מודל הפילוח מהמעבד לשימוש כללי למעבדים המיוחדים האלה מאפשרת ל-LiteRT להפחית באופן משמעותי את זמן ההסקה. ההאצה הזו מאפשרת להפעיל מודלים מורכבים בצורה חלקה בפיד חי של המצלמה, ומרחיבה את הגבולות של מה שאפשר להשיג באמצעות למידת מכונה ישירות בטלפון. בלי רמת הביצועים הזו, פילוח בזמן אמת יהיה איטי מדי ועם שיבושים, ולא יספק חוויית משתמש טובה.
3. להגדרה
שכפול המאגר
קודם כל, משכפלים את המאגר של LiteRT:
git clone https://github.com/google-ai-edge/LiteRT.git
LiteRT/litert/samples/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 אחרי שתהליך הייבוא מסתיים.
- בסרגל הכלים של Android Studio, בוחרים באפשרות Sync Project with Gradle Files (סנכרון הפרויקט עם קובצי Gradle).
- חשוב לא לדלג על השלב הזה – אם הוא לא יעבוד, לא תהיה משמעות לשאר המדריך.
הפעלת אפליקציית המתחילים
אחרי שיובא הפרויקט ל-Android Studio, אפשר להריץ את האפליקציה בפעם הראשונה.
מחברים את מכשיר Android למחשב באמצעות USB ולוחצים על Run (הפעלה) בסרגל הכלים של Android Studio.
האפליקציה אמורה להיפתח במכשיר. יוצג לכם פיד חי מהמצלמה, אבל עדיין לא תתבצע פילוח. כל העריכות שתבצעו בקבצים במדריך הזה יהיו בספרייה LiteRT/litert/samples/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/aiedge/examples/image_segmentation
(עכשיו אתם יודעים למה Android Studio מבצע ארגון מחדש של הספרייה הזו 😃).
תראו גם TODO
תגובות בקבצים ImageSegmentationHelper.kt
, MainViewModel.kt
ו-view/SegmentationOverlay.kt
. בשלבים הבאים תטמיעו את הפונקציונליות של פילוח התמונה על ידי מילוי התגים TODO
האלה.
4. הסבר על אפליקציית המתחילים
אפליקציית המתחילים כבר כוללת ממשק משתמש בסיסי ולוגיקה לטיפול במצלמה. סקירה כללית מהירה של הקבצים העיקריים:
-
app/src/main/java/com/google/aiedge/examples/image_segmentation/MainActivity.kt
: זו נקודת הכניסה הראשית של האפליקציה. הוא מגדיר את ממשק המשתמש באמצעות Jetpack Compose ומטפל בהרשאות הגישה למצלמה. -
app/src/main/java/com/google/aiedge/examples/image_segmentation/MainViewModel.kt
: ה-ViewModel הזה מנהל את מצב ממשק המשתמש ומתאם את תהליך פילוח התמונה. -
app/src/main/java/com/google/aiedge/examples/image_segmentation/ImageSegmentationHelper.kt
: כאן נוסיף את הלוגיקה המרכזית של פילוח התמונה. הוא יטען את המודל, יעבד את הפריימים של המצלמה ויריץ את ההסקה. -
app/src/main/java/com/google/aiedge/examples/image_segmentation/view/CameraScreen.kt
: פונקציית Composable שמציגה את התצוגה המקדימה של המצלמה ואת שכבת העל של חלוקת הגוף. -
app/src/main/assets/selfie_multiclass.tflite
: זהו מודל TensorFlow Lite לאימון מראש של פילוח תמונות, שבו נשתמש.
5. הסבר על LiteRT והוספת יחסי תלות
עכשיו נוסיף את הפונקציונליות של פילוח התמונה לאפליקציית המתחילים.
1. הוספת התלות ב-LiteRT
קודם כל, צריך להוסיף את הספרייה LiteRT לפרויקט. זהו השלב הראשון והחשוב בהפעלת למידת מכונה במכשיר באמצעות זמן הריצה המותאם של Google.
פותחים את הקובץ app/build.gradle.kts
ומוסיפים את השורה הבאה לבלוק dependencies
:
// LiteRT for on-device ML
implementation(libs.litert)
אחרי שמוסיפים את התלות, מסנכרנים את הפרויקט עם קובצי Gradle. לשם כך, לוחצים על הלחצן Sync Now (סנכרון עכשיו) שמופיע בפינה השמאלית העליונה של 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. הוא מייצג מודל שעבר קומפילציה מראש ואופטימיזציה למאיץ חומרה ספציפי (כמו המעבד או המעבד הגרפי). הקומפילציה מראש היא תכונה מרכזית של LiteRT שמובילה להסקת מסקנות מהירה ויעילה יותר. -
CompiledModel.Options
: משתמשים במחלקה הזו של Builder כדי להגדיר אתCompiledModel
. ההגדרה הכי חשובה היא ציון מאיץ החומרה שרוצים להשתמש בו להרצת המודל. -
Accelerator
: באמצעות ה-enum הזה אפשר לבחור את החומרה להסקת מסקנות. פרויקט המתחילים כבר מוגדר לטיפול באפשרויות האלה:-
Accelerator.CPU
: להרצת המודל במעבד של המכשיר. זו האפשרות הכי תואמת לכל המכשירים. -
Accelerator.GPU
: להרצת המודל ב-GPU של המכשיר. לרוב, הפעולה הזו מהירה משמעותית יותר מאשר במעבד (CPU) בדגמים מבוססי-תמונה.
-
- מאגרי קלט ופלט (
TensorBuffer
): LiteRT משתמש ב-TensorBuffer
לקלט ולפלט של המודל. כך אתם מקבלים שליטה פרטנית על הזיכרון ונמנעים מהעתקות מיותרות של נתונים. תקבלו את המאגרים האלה ישירות ממופעCompiledModel
באמצעותmodel.createInputBuffers()
ו-model.createOutputBuffers()
, ואז תכתבו את נתוני הקלט במאגרים ותקראו את התוצאות מהם. -
model.run()
: הפונקציה שמבצעת את ההסקה. מעבירים אליו את מאגרי הקלט והפלט, ו-LiteRT מטפל במשימה המורכבת של הפעלת המודל במאיץ החומרה שנבחר.
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: השיטה הזו ממפה את ה-enums של הפעולות שמוגדרות בתפריט הפעולות ל-enums של הפעולות שספציפיות למודולים המיובאים של 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
בשתי הפונקציות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
ב- (~line 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. עיבוד תמונה (Post Processing) והצגת שכבת-העל
אחרי שמריצים הסקה, מקבלים פלט גולמי מהמודל. אנחנו צריכים לעבד את הפלט הזה כדי ליצור מסכת פילוח חזותית ואז להציג אותה על המסך.
פתיחהImageSegmentationHelper.kt
- הטמעה של עיבוד הפלט: הפונקציה
processImage
ממירה את פלט הנקודה הצפה הגולמי מהמודל ל-ByteBuffer
שמייצג את מסכת הפילוח. הוא עושה את זה על ידי מציאת המחלקה עם ההסתברות הגבוהה ביותר לכל פיקסל. מחליפים אתTODO
שלו ב- (~line 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
כדי לעבד את תוצאות הפילוח מ-ImageSegmentationHelper
. ה-segmentationUiShareFlow
אוסף אתSegmentationResult
, ממיר את המסכה לBitmap
צבעונית ומספק אותה לממשק המשתמש. מחליפים אתTODO
בנכסsegmentationUiShareFlow
ב- (~line 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
כדי לוודא שהיא מיושרת בצורה נכונה עם התצוגה המקדימה של המצלמה.
- Handle Overlay Orientation (טיפול בכיוון של שכבת-העל): מאתרים את
TODO
בקובץSegmentationOverlay.kt
ומחליפים אותו בקוד הבא. הקוד הזה בודק אם המצלמה הקדמית פעילה, ואם כן, הוא מחיל היפוך אופקי על שכבת העלBitmap
לפני שהיא מצוירת עלCanvas
. (~line 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 ולוחצים על Run (הפעלה) בסרגל הכלים של 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).