המשתמשים מצפים שאפליקציות של צד שלישי יצלמו תמונות וסרטונים באותה רמת דיוק כמו המצלמה המקורית של המכשיר, בלי קשר לתנאי התאורה או לרמת החומרה. כדי לספק את העקביות הזו בסביבה העסקית המתרחבת של Android, צריך להביא בחשבון שיקולים חשובים שקשורים לארכיטקטורה. בסשן הזה נדגים איך לבנות צינורות עיבוד נתונים חזקים ומקצועיים לצילום, שממקסמים את יכולות המכשיר תוך שמירה על אמינות בכל הגורמים המשפיעים.
נבחן את הפיתוחים האחרונים ב-CameraX, נראה איך לשלב יכולות מתקדמות כמו תמונות עם תנועה ושיפורים באמצעות AI במכשיר דרך Google Play Services, וגם חוויות מרחביות באמצעות Jetpack XR SDK. נבחן גם את מיומנות CameraX, שמאפשרת לסוכן הקידוד שבחרתם להטמיע באופן אוטומטי את המושגים שמוצגים בהרצאה הזו, ועוד.
CameraXViewfinder מתאים את התצוגות המקדימות של המצלמה לגורמי צורה שונים, כמו מכשירים מתקפלים. ב-CameraX 1.7, נוספו פקדי תנועה ותמיכה בהבהוב מסך.ProjectedDeviceContext מאפשרת ל-CameraX לצלם תמונות ישירות מהמשקפיים.תמליל
שלום לכולם,
ברוכים הבאים לפגישה שלנו.
אני אמיר, ואיתי נמצא עופר.
היום אנחנו ממש מתרגשים לדבר על איך אפשר לשפר את חוויית השימוש במצלמה במכשירים שונים באמצעות CameraX ובינה מלאכותית במכשיר.
אז בואו נתחיל.
חשבו על האופן שבו אתם משתמשים בטלפונים שלכם.
אנחנו משתמשים במצלמת הטלפון לעיתים קרובות כדי לתעד רגעים ולשתף אותם עם האנשים שאנחנו אוהבים.
המצלמה הפכה לחלק מרכזי בחוויית השימוש בסמארטפון.
למעשה, מצלמה טובה היא לעיתים קרובות גורם מרכזי ברכישת סמארטפונים.
היום נדבר על שני נושאים רחבים שקשורים ליצירת חוויות פרימיום במצלמה.
קודם נבחן כמה תכונות של CameraX, ואז נלמד על כמה טכניקות של למידת מכונה במכשיר.
נתחיל עם CameraX.
Jetpack CameraX מפעיל את חוויית המצלמה ב-Android.
חוץ מ-CameraX, יש גם את Camera1 ו-Camera2, שהן חלק מה-framework של פלטפורמת Android.
אז באיזו ספרייה כדאי להשתמש?
Camera1 היה ה-API המקורי של המצלמה ב-Android.
הוא הוצא משימוש, ולכן באפליקציות חדשות צריך להשתמש ב-Camera2 או ב-CameraX.
Camera2 החליפה את Camera1.
לכן, אם אתם צריכים שליטה במצלמה ברמה נמוכה לתרחישים מורכבים, Camera2 היא אפשרות טובה.
עם זאת, תצטרכו לטפל בעצמכם בהגדרות הספציפיות למכשיר.
ולסיום, Jetpack CameraX מבוסס על Camera2.
הוא מספק API עקבי ומטפל באופן אוטומטי בהגדרות ספציפיות למכשיר.
זו האפשרות המומלצת לרוב המפתחים.
יש לנו הודעה חשובה מאוד.
אנחנו מתכננים להשבית את Camera1, ונתחיל באכיפת המדיניות של Play בשנה הבאה ב-Android 18.
אם אתם עדיין משתמשים ב-API הקודם, Camera1, אתם צריכים לעבור ל-CameraX או ל-Camera2.
אז למה כדאי להשתמש ב-CameraX?
קודם כל, היא תומכת ב-Android מגרסה 7 ואילך, ולכן היא תואמת לכמעט 99% ממכשירי Android הפעילים.
שנית, קל להשתמש בה והיא מספקת ממשקי API פשוטים לתרחישים נפוצים כמו תצוגה מקדימה, צילום והקלטה עם פחות קוד.
שלישית, היא מספקת חוויית שימוש עקבית במכשירים שונים על ידי צמצום בעיות ספציפיות למכשיר, כמו מתיחה של התצוגה המקדימה, שהיא די נפוצה במכשירים מתקפלים ובטאבלטים.
ולסיום, הוא מפשט את ההטמעה של יכולות OEM מתקדמות כמו מצב לילה ודיוקן.
עכשיו אני רוצה להזמין את סקוט לדבר על כמה מהתכונות של CameraX.
אליך, סקוט.
תודה, אמיר.
שלום לכולם,
קוראים לי סקוט.
היום נסקור כמה תכונות של CameraX שיאפשרו לכם ליצור חוויית שימוש במצלמה ברמה מקצועית.
התכונה הראשונה היא "תצוגת צילום דינמית".
הוא נועד להקל על התצוגה המקדימה של הכינוי בגורמי צורה ובגדלי מסך שונים.
המשתמשים משתמשים עכשיו במגוון מכשירים, כולל מכשירים מתקפלים, טלפונים, מחשבים ניידים ומכשירי XR.
לכן, חשוב יותר מתמיד ליצור חוויית שימוש במצלמה שמתאימה לגורמי הצורה החדשים.
בנוסף, קשה מאוד להגדיר נכון את התצוגה המקדימה של המצלמה, במיוחד במכשיר מתקפל עם מסך רחב.
במכשירים האלה, כשהם פתוחים, המצלמה הפנימית מותקנת בצורה אופקית והמצלמה האחורית מותקנת בצורה אנכית.
לכן, אם מניחים שכל חיישני המצלמה מותקנים בצורה אנכית, זה יוביל לחוסר התאמה מרחבית ולעיוות או לתמונה על הצד.
ו-CameraXViewfinder, הוא נועד לטפל במורכבות בשבילכם, כדי להבטיח שהתצוגה המקדימה של המצלמה תפעל בצורה חלקה בכל הגורמים המשפיעים.
עכשיו נראה איך להשתמש ברכיב ה-Composable של CameraXViewfinder.
var surfaceRequest by remember {
mutableStateOf<SurfaceRequest?>(null)
}
LaunchedEffect(...) {
preview.setSurfaceProvider { surfaceRequest = it }
cameraProvider.bindToLifecycle(..., preview)
}
surfaceRequest?.let { request ->
CameraXViewfinder(
surfaceRequest = request,
contentScale = ContentScale.Fit
)
}קודם כל, מגדירים את משתנה התוכן surfaceRequest שיכיל את הבקשה למשטח.
סליחה.
שנית, ב-LaunchedEffect, מגדירים את ספק המשטח כך שיקלוט אובייקט של בקשת משטח.
ואז אתם מקשרים למחזור החיים ומפעילים את המצלמה.
ולסיום, בכל פעם שהמצב מתעדכן עם בקשת פני שטח תקינה ומופעלת הרכבה מחדש, אתם מגדירים את הבקשה ישירות ל-CameraXViewfinder.
וכאן אפשר להגדיר את contentScale להתאמה או לחיתוך, בהתאם להעדפה שלך.
זהו, סיימתם.
בגרסה 1.7 של CameraX, הוספנו כמה תכונות חדשות ל-CameraXViewfinder.
זה כולל תנועות חדשות כמו לחיצה כדי להתמקד או תנועת צביטה כדי לשנות את מרחק התצוגה.
היא גם תומכת בתכונה "הבהוב מסך", שמאפשרת להשתמש במסך כדי להבהיר את תמונת הסלפי.
קל להטמיע את התכונה הזו.
CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)פשוט מפעילים את האפשרויות "הקשה למיקוד" ו"צביטה לשינוי מרחק התצוגה".
כדי להשתמש בהבהוב המסך, צריך לקבל את המופע של הבהוב המסך מהקריאה החוזרת (callback) של onScreenFlashReady, להעביר אותו ל-ImageCapture ולהגדיר את מצב הפלאש ל-FLASH_MODE_SCREEN.
וזהו.
ראינו איך CameraX מאפשרת תצוגה מקדימה של המצלמה שפועלת בצורה חלקה בכל הגורמים המשפיעים על החוויה.
עכשיו נתקדם עוד שלב ונצלם תמונה ב-Android XR.
יש שלוש קטגוריות של מכשירי XR: הראשונה היא משקפי XR.
אלה משקפיים סגורים לחלוטין שמאפשרים חוויה סוחפת. הם משתמשים במצלמה כדי לצלם את העולם הפיזי ולהציג אותו בתוך המשקפיים.
ושנית, משקפי XR עם חיבור חוטי.
היום הכרזנו על XREAL Aura בנאום המרכזי בקטגוריה הזו.
כמו שאתם יכולים לראות בהרצאות, אלה משקפיים שקופים שמחוברים למחשב ומאפשרים לכם לראות את התוכן הווירטואלי שמוצג על גבי העולם האמיתי.
והשלישית, משקפיים עם אודיו ותצוגה פנימית.
אלה משקפיים קלים.
הם יכולים לשלב אודיו, מצלמה ומסך כדי לאפשר חוויית שימוש ללא מגע וקבלת מידע במבט חטוף.
שתי הקטגוריות הראשונות, משקפי VR עם Android XR ומשקפי AR עם Android XR וחיבור חוטי, תומכות גם ב-Camera2 וב-CameraX, בדיוק כמו טלפון רגיל.
לכן היום נתמקד רק באודיו ובמשקפיים עם תצוגה פנימית.
במשקפיים האלה, האפליקציות פועלות בטלפון Android המארח ולא במשקפיים, אבל יש גישה גם למצלמה של הטלפון וגם למצלמה של המשקפיים.
השאלה המרכזית היא איך יודעים לאיזו מצלמה מדברים?
זה פשוט.
עם Context רגיל, CameraX ניגש למצלמת הטלפון.
ובאמצעות ProjectedDeviceContext, CameraX ניגש למצלמת המשקפיים.
לכן, הקשרים שונים יובילו למצלמות יעד שונות.
חשוב לזכור שההרשאות למצלמה ולמשקפיים נפרדות.
ההרשאות לטלפון ולמשקפיים נפרדות, לכן צריך לבקש הרשאה למשקפיים בנפרד.
אפשרות נוספת היא להשתמש ב-Projected Activity, שגם פועלת במכשירי Android המארחים, אבל ממשק המשתמש מוקרן על המשקפיים.
וכאן הכלל מתהפך.
אם אתם משתמשים ב-Context הרגיל, תהיה לכם גישה למצלמה של המשקפיים.
אם רוצים לגשת למצלמת הטלפון, צריך להשתמש ב-HostContext.
עכשיו נסתכל על הקוד ונראה איך אפשר לצלם באמצעות המצלמה של המשקפיים.
// In a coroutine
val imageCapture = ImageCapture.Builder().build()
ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
.collectLatest { isConnected ->
if (isConnected) {
val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setRepeatingStreamForced(false)
.build()
val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
}
}
// Shutter button clicked
imageCapture.takePicture(...)קודם כל, אתם מפעילים את isProjectedDeviceConnected כדי לבדוק אם המשקפיים מחוברים או לא.
ואז מתקשרים אל createProjectedDeviceContext כדי לקבל projectedContext.
שנית, כאן משתמשים ב-LifecycleCameraProvider כדי להפעיל את CameraX, במקום ב-ProcessCameraProvider.
אז ProcessCameraProvider הוא סינגלטון שאי אפשר לשנות את ההקשר שלו אחרי האתחול.
לכן, השימוש ב-LifecycleCameraProvider מאפשר לשנות את ההקשר בכל שלב.
בשלב הבא, צריך להשבית את השידור החוזר הנוסף ב-CameraX.
הסיבה לכך היא שהמשקפיים של היום תומכים רק בסטרימינג יחיד.
ה-API כרגע פנימי, אבל בקרוב נהפוך אותו לציבורי.
וזהו.
מכאן זה CameraX רגיל, פשוט מקשרים את ImageCapture, ועכשיו מצלמים דרך המשקפיים.
אוקיי.
עכשיו נראה איך CameraX עוזרת לכם למצוא את שילוב התכונות שפועלות יחד ומפיקות את המקסימום מהמכשיר.
אפליקציות מצלמה מודרניות מוגדרות על ידי תכונות מתקדמות, והמשתמשים מצפים שהמצלמה תתמוך בתכונות כמו HDR, 60 FPS וייצוב תצוגה מקדימה, והכול בו-זמנית.
אבל כמפתחים, אנחנו יודעים שלא תמיד קל לשלב את התכונות האלה.
גם אם כל תכונה נתמכת בנפרד, זה לא מבטיח שהן יעבדו ביחד.
חוסר הוודאות הזה מאלץ את המפתחים להיות שמרנים ולהגביל את מה שהמשתמשים יכולים לחוות במכשירים בעלי יכולת.
כדי לפתור את הבעיה, השקנו את Feature Group API, שמופעל על ידי מסגרת השאילתות של שילובי התכונות ב-Camera2.
יש שתי דרכים להשתמש בה: קודם כל, אפשר להשתמש ב-requiredFeatureGroup כדי לבדוק ולהפעיל שילוב של תכונות כשהוא נתמך.
שנית, אתם יכולים לציין את סדר העדיפויות של התכונות באמצעות preferredFeatureGroup, ואז CameraX תבחר את השילוב הנתמך הטוב ביותר בשבילכם.
והנה הדוגמה.
בצד ימין, בצד שמאל, סליחה, בלי להפעיל שום דבר, רק טלפון רגיל, הקלטה רגילה.
בצד שמאל, אנחנו מפעילים את HDR, 60 FPS וייצוב התצוגה המקדימה, כולם ביחד.
כמו שאתם רואים, האיכות השתפרה משמעותית – הסרטון בהיר יותר, חלק יותר וללא רעידות.
בואו נסתכל על הקוד.
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)לכן, כאן אנחנו משתמשים ב-preferredFeatureGroup כדי לספק את רשימת התכונות לפי סדר העדיפות, שהיא HDR, ואז 60 FPS, ואז ייצוב התצוגה המקדימה.
CameraX יבדוק את השילוב ויבחר את השילובים הטובים ביותר שנתמכים במכשיר.
בגרסה 1.6 ומעלה של CameraX, אנחנו מוסיפים את התכונות "צילום באיכות 4K" ו"ייצוב וידאו" ישירות ל-Feature Group API.
// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)שילוב של 4K וייצוב וידאו נתמך בהרבה מכשירי פרימיום.
עכשיו, באמצעות Feature Group API, אתם יכולים להפעיל אותם יחד בראש שקט.
בנוסף, עכשיו אתם יכולים להחיל את האפקט בזמן אמת באמצעות Feature Group API, מה שלא היה אפשרי בעבר.
עכשיו נדבר על איך אפשר לצלם תמונות באיכות גבוהה ב-CameraX.
עכשיו אפשר לצלם ב-DNG RAW ב-CameraX.
היא מאפשרת לכם לגשת לתמונת RAW לא מעובדת ולא דחוסה ישירות מחיישן המצלמה, כדי שתוכלו לערוך אותה בצורה מקצועית.
בנוסף, שיפרנו את התוסף "מצב לילה".
התוסף הזה מאפשר לצלם תמונה בהירה וברורה בתאורה חלשה בלי להשתמש בפלאש.
הוספנו גם API חדש של אינדיקטור מצב הלילה.
הוא ינטר מתי הסביבה הנוכחית חשוכה ומתאימה למצב לילה, ותוכלו להשתמש בו כדי להציג את האינדיקטור של מצב הלילה.
בנוסף, עכשיו יש תמיכה ב-Ultra HDR בתוספים של CameraX.
התכונה הזו מוסיפה טווח דינמי רחב יותר וצבעים מלאי חיים למצב הלילה. נכון לעכשיו, מצב הלילה עם Ultra HDR נתמך בהרבה מכשירי פרימיום מהזמן האחרון.
כאן אפשר לראות את ההבדל בין מצב רגיל, מצב לילה ומצב לילה עם Ultra HDR.
בואו נראה איך מטמיעים את זה.
val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))
val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
val imageCapture = ImageCapture.Builder().apply {
if (imageCapabilities.supportedOutputFormats
.contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
}
}.build()
val extensionSessionConfig = ExtensionSessionConfig(
ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
)
cameraProvider.bindToLifecycle(..., extensionSessionConfig)קודם יוצרים ExtensionSessionConfig למצב לילה, ומשתמשים בו כדי לקבל את המכונה של CameraInfo.
בשלב הבא, משתמשים ב-CameraInfo כדי לקבל את יכולת צילום התמונות, ואז בודקים אם יש תמיכה ב-Ultra HDR.
ואז מגדירים את פורמט הפלט ל-Ultra HDR.
ולבסוף, יוצרים שוב את ה-ExtensionSessionConfig עם תרחישי השימוש, ומקשרים למחזור החיים.
עכשיו אתם יכולים לצלם תמונות במצב לילה ב-Ultra HDR.
אנחנו גם מוסיפים ל-CameraX תמיכה בתמונות עם תנועה.
תמונות עם תנועה מתעדות את הצלילים והתנועה שסביב התמונה, וכך משמרות את הרגע ולא רק תמונה סטטית.
אנחנו יוצרים סטנדרט אחיד לכל הטלפונים ומכשירי Android, כדי שיהיה קל יותר לתמוך בתמונות עם תנועה.
התכונה "תמונות עם תנועה" תהיה זמינה ב-CameraX ברבעון הראשון של 2027.
כשהתכונה תהיה זמינה, פשוט מגדירים את מצב "תמונות עם תנועה" ב-ImageCapture.Builder, ומתחילים לצלם תמונות עם תנועה.
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)עכשיו נראה איך CameraX מאפשרת צילום וידאו במהירות גבוהה.
אחת התכונות החשובות באפליקציות מצלמה מודרניות היא היכולת לצלם פעולה מהירה בצורה ברורה.
היא שימושית במיוחד בתרחישים מהירים, כמו ספורט.
כדי לעשות את זה, המצלמה מצלמת במהירות גבוהה של 120 או 240 פריימים לשנייה.
יש שתי דרכים להשתמש בו.
קודם צריך לקודד באותו קצב פריימים, ואז ליצור סרטון עם קצב פריימים גבוה באמת.
האפשרות השנייה היא לקודד אותו בקצב של 30 פריימים לשנייה כדי ליצור סרטון בהילוך איטי שמוכן להפעלה.
לדוגמה, שנייה אחת שצולמה בקצב של 120 פריימים לשנייה תופעל במשך ארבע שניות בקצב של 30 פריימים לשנייה.
כך, פרק הזמן הזה יוצר אפקט של הילוך איטי.
כדי להטמיע הקלטה במהירות גבוהה, יוצרים HighSpeedVideoSessionConfig באמצעות videoCapture.
val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.withOutput(recorder)
val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
HighSpeedVideoSessionConfig(videoCapture, preview)).first()
val sessionConfig = HighSpeedVideoSessionConfig(
videoCapture,
preview,
frameRateRange = frameRateRange,
isSlowMotionEnabled = true
)
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, sessionConfig)
// Start recording slow motion videos.
val recording = recorder.prepareRecording(context, outputOption)
.start(executor, {})ואז מגדירים את isSlowMotionEnabled כ-true כדי לקודד אותו ב-30 פריימים לשנייה וליצור סרטון בהילוך איטי, או מגדירים אותו כ-false כדי ליצור סרטון בקצב פריימים גבוה.
כשההילוך האיטי מופעל, אפליקציית Google Photos מזהה אותו אוטומטית ומוסיפה תג של הילוך איטי לתמונה הממוזערת.
עכשיו נראה איך זה עובד ב-Google Photos.
כאן ההילוך האיטי נותן אפקט דרמטי לחתול.
החתול שלי אף פעם לא רוצה שאגע בו.
אז סליחה.
ולסיום, נראה כמה שיפורים ב-API שלנו לפעולות בו-זמניות, Concurrent Camera API.
API בו-זמני מאפשר לאפליקציה להעביר סטרימינג מהמצלמה הקדמית והאחורית בו-זמנית.
השנה הוספנו כמה שיפורים ל-Concurrent Camera API.
אפשר לעדכן באופן דינמי את קומפוזיציית המצלמה אחרי הקישור למחזור החיים ופתיחת המצלמה.
זה כולל שינוי של המיקום, הגודל וסדר העיבוד.
אפשר גם להוסיף סגנונות כמו פינה מעוגלת, רוחב גבול וצבע גבול.
כדי לעשות את זה, פשוט משתמשים ב-CompositionSettings כדי להגדיר את הסגנון, כמו פינות מעוגלות, רוחב הגבול והצבע, ומשתמשים ב-setZOrder כדי לשנות את סדר הרינדור.
val concurrentCamera =
cameraProvider.bindToLifecycle(list(config1, config2))
// CameraX 1.7 features: rounded corner, border width & color
val pipSettings = CompositionSettings.Builder()
.setOffset(0.2f, 0.2f)
.setScale(0.35f, 0.35f)
.setRoundedCornerRatio(0.15f)
.setBorderWidthRatio(0.02f)
.setBorderColor(Color.WHITE)
.setZOrder(1)
.build()
// CameraX 1.7 features: dynamical composition update
concurrentCamera.setCompositionSettings(
listOf(mainSettings, pipSettings))בשלב הבא, פשוט מפעילים את setCompositionSettings במצלמה הפעילה כדי לעדכן את הקומפוזיציה באופן דינמי.
וזהו.
עכשיו אפשר לעדכן את הקומפוזיציה באופן דינמי.
אוקיי.
עכשיו, אחרי שעברנו על כל התכונות,
יש לנו הערה חשובה בנושא הזה, ולכן אנחנו ממליצים מאוד לשדרג לגרסה CameraX 1.6 ומעלה.
כך אפשר למנוע קריסה במכשירים עתידיים שתומכים בפורמט החדש, Eclipsa Video, וגם לוודא שתוסף CameraX ימשיך לפעול בעדכון העתידי של התכונה ב-Pixel.
בזה אנחנו מסיימים את המפגש הראשון שלנו.
דיברנו על התכונות של CameraX שעוזרות לכם ליצור חוויית צילום ברמה מקצועית, ועכשיו אני רוצה להזמין את אמאן להציג את הסשן הבא שלנו בנושא AI במכשיר.
תודה, סקוט, על ההסבר על התכונות של CameraX.
עכשיו נראה איך אפשר להשתמש בלמידת מכונה במכשיר כדי ליצור חוויות חכמות במצלמה.
למידת מכונה במכשיר מאפשרת מגוון רחב של תרחישים לדוגמה של שימוש במצלמה בשני תחומים עיקריים.
השלב הראשון הוא לפני הצילום.
כאן אתם יכולים לנתח את התצוגה המקדימה של המצלמה בזמן אמת כדי לבצע פעולות כמו זיהוי אובייקטים, או לשפר את התצוגה המקדימה עצמה.
השלב השני הוא אחרי הצילום.
אחרי שצילמתם את התמונה, אתם יכולים לשפר את האיכות שלה או לבצע פעולות כמו יצירת תיאור לתמונה.
עכשיו נתעמק בכמה מהפתרונות של למידת מכונה במכשיר שיש לנו לאפליקציות המצלמה שלכם.
ספריית ה-ML הראשונה במכשיר שנדבר עליה היא ML Kit.
ערכת ML Kit נוצרה בעיקר כדי לפתור בעיות נפוצות בתחומים של ראייה ממוחשבת ועיבוד שפה טבעית (NLP), כמו זיהוי טקסט, זיהוי פנים ותרגום שפות.
בשנה שעברה השקנו את ML Kit.
הנה כמה דוגמאות לממשקי Vision API של ML Kit, למשל זיהוי רשת פנים, פילוח נושאים וזיהוי אובייקטים בזמן אמת.
כן.
בשנה שעברה השקנו את ממשקי ה-API של GenAI.
ממשקי ה-API האלה מנצלים את היכולות של Gemini Nano כדי לאפשר סיכום טקסט, תיאור תמונות ועוד הרבה יותר.
הנה דוגמה לתיאור תמונה באמצעות Prompt API.
אנחנו מעבירים למודל את מפת הסיביות של התמונה עם הפרומפט הטקסטואלי, והתיאור שמתקבל הוא: רובוט Android קטן וירוק בעיצוב דמוי קקטוס יושב על משטח שחור.
וכל זה קורה במכשיר.
מגניב, נכון?
ב-Adobe Photoshop משתמשים ב-Prompt API כדי לשפר את חוויית המשתמש באפליקציה.
כשעורכים נכסים, לעיתים קרובות צריך לנהל כמה שכבות באפליקציית Photoshop.
בעזרת Prompt API, האפליקציה יכולה עכשיו לשנות באופן אוטומטי את השמות של השכבות הלא מאורגנות.
האפליקציה גם משתמשת ב-Prompt API כדי ליצור כיתובים והאשטאגים לתמונות, וזה מאוד שימושי לשיתוף ברשתות החברתיות.
בואו נסתכל על הקוד לתיאור תמונה באמצעות Prompt API.
// 1. Initialize the on-device GenerativeModel client
val generativeModel = Generation.getClient()
// 2. Check whether Gemini Nano is available on the device
val status = generativeModel.checkStatus()
when (status) {
FeatureStatus.UNAVAILABLE -> {}
FeatureStatus.DOWNLOADABLE -> {}
FeatureStatus.DOWNLOADING -> {}
FeatureStatus.AVAILABLE -> {}
}קודם כל, צריך אובייקט לקוח של generativeModel, ואז לבדוק אם Gemini Nano זמין או ניתן להורדה במכשיר.
אחר כך, יוצרים בקשת תוכן על ידי שילוב של התמונה והפרומפט הטקסטואלי, ולבסוף מספקים את הבקשה הזו לקריאה ל-generateContent כדי להריץ את ההיקש ב-Gemini Nano ולקבל את תיאור התמונה.
// 3. Build a multimodal request with the image and text prompt
val request = generateContentRequest(
ImagePart(bitmap),
TextPart("Describe this image in detail.")
) {}
// 4. Run inference on-device and get the description
val response = generativeModel.generateContent(request)
val description = response.candidates.first().textכן, ספריית ה-ML הבאה במכשיר היא MediaPipe.
MediaPipe מציעה מגוון רחב של פתרונות למידת מכונה לאפליקציות שלכם.
אתם יכולים להיכנס במהירות לאתר MediaPipe כדי לנסות דוגמאות שונות כמו זיהוי פנים, זיהוי אובייקטים, חלוקת תמונות למקטעים ועוד.
כל התרחישים האלה לדוגמה נשמעים דומים מאוד ל-ML Kit, אז באיזו ספרייה כדאי להשתמש?
למרות ש-MediaPipe ו-ML Kit פותרים בעיות דומות, יש כמה הבדלים עיקריים.
הראשון הוא תמיכה בפלטפורמות.
MediaPipe פועל ב-Android, ב-iOS, באינטרנט וב-Python, ואילו ML Kit מתמקד רק ב-Android וב-iOS.
בנוסף, MediaPipe מאפשר להתאים אישית את המודלים, ואילו ML Kit מספק ממשקי API מוכנים לשימוש.
ולבסוף, MediaPipe הוא קוד פתוח, אבל ML Kit היא ערכת SDK של Google.
דוגמה לזיהוי תנועות באמצעות ממשקי ה-API של MediaPipe Vision.
ה-API הזה מזהה את תנועות הידיים בזמן אמת מהקלט של המצלמה.
לדוגמה, אפשר להשתמש בתנועות ידיים פשוטות כדי להתחיל או להפסיק את הצילום במצלמה כשעומדים במרחק מהטלפון.
בואו נסתכל על הקוד.
לכן, קודם צריך ליצור את האובייקט GestureRecognizerOptions, ולספק את נתיב המודל, מצב הסטרימינג בשידור חי ומאזין לתוצאות.
// 1. Build GestureRecognizerOptions
val options = GestureRecognizerOptions.builder().setBaseOptions(
BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
.setRunningMode(RunningMode.LIVE_STREAM)
.setResultListener { result, image -> /* Handle detected gesture */ }
.build()
// 2. Create the GestureRecognizer
val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)
// 3. Convert CameraX ImageProxy to MediaPipe MPImage
val bitmap = rotateBitmap(imageProxy.toBitmap(),
imageProxy.imageInfo.rotationDegrees)
val mpImage = BitmapImageBuilder(bitmap).build()
// 4. Run gesture recognition on the image
gestureRecognizer.recognizeAsync(mpImage, frameTime)לאחר מכן, יוצרים אובייקט לזיהוי תנועות מתוך האפשרויות.
בשלב הבא, מקבלים את ה-ImageProxy של CameraX, ממירים אותו למפת סיביות (bitmap) ומשתמשים במפת הסיביות כדי ליצור אובייקט mpImage של MediaPipe.
לבסוף, צריך לספק את האובייקט mpImage של MediaPipe לקריאה ל-recognizeAsync כדי לזהות תנועות בזמן אמת.
התכונה הבאה היא שיפור תמונות וסרטונים במכשיר, שזמינה דרך ה-API של Google Play Services.
המודל הזה מספק יכולות כמו מיפוי גוונים, הסרת טשטוש ושדרוג הרזולוציה ברוב מכשירי הפרימיום.
הנה דוגמה לתמונה שעברה מיפוי גוונים והגדלה באמצעות המודל הזה.
בתמונה המשופרת אפשר לראות בבירור את הצבעים העשירים יותר בשמיים ובים, ואת החדות המשופרת.
בואו נסתכל על הקוד של שיפור התמונות.
// Step 1: Verify device support & install module
val enhancementClient = Enhancement.getClient(context)
suspend fun prepare(): Boolean {
if (!enhancementClient.isDeviceSupported().await()) return false
if (!enhancementClient.isModuleInstalled().await()) {
check(enhancementClient.installModule().await()) {
"Enhancement module install failed"
}
}
return true
}קודם כל, צריך לבדוק אם המכשיר תומך בזה, ואם צריך, להתקין את מודול השיפור במכשיר.
אחר כך יוצרים את האובייקט EnhancementOptions על ידי ציון מפת הסיביות והשיפורים שרוצים לבחור.
// Step 2: Select Enhancement options & create session
val options = EnhancementOptions(
width = bitmap.width,
height = bitmap.height,
enhancementMode = EnhancementMode.BITMAP,
isTonemappingEnabled = true,
isDeblurAndDenoisePhotoEnabled = true,
isDeblurAndDenoiseVideoEnabled = false,
isUpscalePhotoEnabled = false,
isUpscaleVideoEnabled = false
)
val enhancementSession = enhancementClient
.createSessionAsync(options, enhancementExecutor)בשלב הבא, יוצרים סשן לקוח באמצעות Google Play Services API, מגדירים קריאה חוזרת (callback) כדי לטפל במפת הסיביות שנוצרה, ולבסוף קוראים לפונקציית התהליך עם מפת הסיביות והאפשרויות לשיפור התמונה.
// Step 3: Process Enhancement
val enhancedBitmap = suspendCancellableCoroutine { continuation ->
val enhancementCallback = object : EnhancementCallback {
override fun onBitmapProcessed(result: Bitmap) {
if (continuation.isActive) continuation.resume(result)
}
override fun onError(statusCode: Int) {
if (continuation.isActive) {
continuation.resumeWithException(
Exception("Bitmap processing failed: $statusCode")
)
}
}
override fun onSurfaceProcessed(timestamp: Long) {}
}
enhancementSession.process(bitmap, options, enhancementCallback)
}לאחרונה שילבנו את ממשקי ה-API לשיפור סרטונים ב-Jetpack Media3.
אתם יכולים לצפות בשיחה שלנו בנושא מדיה כדי לקבל מידע נוסף על ממשקי ה-API לשיפור תמונות וסרטונים.
התכונה האחרונה של למידת מכונה במכשיר שנדבר עליה היא "הגברת התאורה".
צילום בתאורה חלשה הוא בעיה נפוצה אצל משתמשי סמארטפונים.
למשל, משתמשים שמצלמים תמונות או מבצעים שיחות וידאו בסביבה עם תאורה עמומה.
התכונה "הגברת התאורה" פותרת את הבעיה הזו על ידי הבהרת התצוגה המקדימה של המצלמה בזמן אמת.
יש שתי גרסאות של התכונה "שיפור איכות התאורה": חומרה ותוכנה.
גרסת החומרה משתמשת בחומרה של ה-OEM כדי לשפר את הפריימים עם התאורה החלשה בלי להשתמש בלמידת מכונה.
לעומת זאת, גרסת התוכנה משתמשת במודל למידת מכונה במכשיר כדי לשפר את הפריימים.
בואו נראה איך אפשר לשלב את CameraX עם "שיפור תוכנה לתאורה חלשה", שנקרא גם "שיפור תאורה חלשה של Google".
כדי להשתמש ב-Google Low Light Boost, צריך שני משטחים עיקריים: משטח קלט לפריימים הגולמיים של המצלמה ומשטח יעד לפלט של הפריימים המשופרים.
כדי לשלב את התכונה עם CameraX, פשוט עוטפים את צינור עיבוד הנתונים של Google Low Light Boost בתוך CameraEffect.
CameraX מטפל באופן אוטומטי במחזור החיים של המשטח ובניתוב בצורה חלקה.
בנוסף, ברבעון הרביעי של 2026 נשיק את האפקט "שיפור בתאורה חלשה" ב-CameraX.
// Integrates SW Low Light Boost
val lowLightBoostEffect =
LowLightBoostEffect(
CameraEffect.PREVIEW or
CameraEffect.VIDEO_CAPTURE or
CameraEffect.IMAGE_CAPTURE
)
val sessionConfig = SessionConfig(
useCases = listOf(preview, imageCapture),
effects = lowLightBoostEffect
)
cameraProvider.bindToLifecycle(.. sessionConfig)כך תוכלו ליצור אובייקט של Low Light Boost ולהוסיף אותו להגדרות הסשן, ואז לקשר את הגדרות הסשן למחזור החיים של המצלמה. זה פשוט עובד במכשירים נתמכים.
זהו, סיימנו את שני החלקים.
בואו נסכם בקצרה את כל מה שלמדנו היום.
היום דיברנו על איך אפשר לשפר את חוויית השימוש במצלמה במכשירים שונים באמצעות CameraX ולמידת מכונה במכשיר.
ראשית, באמצעות CameraXViewfinder, אתם יכולים להפעיל את התצוגות המקדימות של המצלמה כדי להתאים אותן לגורמי צורה שונים.
בשלב הבא, ראינו ש-CameraX מאפשרת לצלם גם במכשירי XR.
לאחר מכן, Feature Group API עוזר לכם למצוא את השילוב האופטימלי של תכונות החומרה.
בדקנו גם שדרוגים לצילום תמונות ולמדנו על צילום סרטונים במהירות גבוהה לסרטונים בהילוך איטי.
ולבסוף, למדנו איך לשפר את התצוגה המקדימה של המצלמה בו-זמנית באופן דינמי.
אחר כך בדקנו כמה מהפתרונות של למידת מכונה במכשיר, כמו ML Kit ו-MediaPipe, כדי ליצור חוויות חזותיות חכמות.
ולבסוף, ראינו את התכונות "שיפור תמונות וסרטונים" ו"הגברת התאורה" במכשיר, שמאפשרות להבהיר את התצוגה המקדימה של המצלמה בזמן אמת.
כמו שציינו קודם, אנחנו מתכננים להשבית את Camera1, ולכן אנחנו ממליצים מאוד לעבור ל-CameraX.
כדי לתמוך בהעברה הזו, השקנו לאחרונה את המיומנות CameraX.
יש לנו גם מדריך להעברת נתונים (מיגרציה) ומאגר של דוגמאות למצלמות שיכולים לעזור לכם.
בקישור הזה אפשר למצוא מקורות מידע נוספים על התכונות שדיברנו עליהן היום.
הוא כולל קישור למסמכי תיעוד, לבלוג ולמקורות מידע בנושא העברה, והכול במקום אחד.
אני אעצור לרגע.
אוקיי.
תודה לכולם.
הגענו לסוף השיחה שלנו.
var surfaceRequest by remember {
mutableStateOf<SurfaceRequest?>(null)
}
LaunchedEffect(...) {
preview.setSurfaceProvider { surfaceRequest = it }
cameraProvider.bindToLifecycle(..., preview)
}
surfaceRequest?.let { request ->
CameraXViewfinder(
surfaceRequest = request,
contentScale = ContentScale.Fit
)
}CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)// In a coroutine
val imageCapture = ImageCapture.Builder().build()
ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
.collectLatest { isConnected ->
if (isConnected) {
val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
.setRepeatingStreamForced(false)
.build()
val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
}
}
// Shutter button clicked
imageCapture.takePicture(...)cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
useCases = listOf(preview, videoCapture),
requiredFeatureGroup =
setOf(UHD_RECORDING, VIDEO_STABILIZATION),
effects = listOf(overlayEffect)
)val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))
val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
val imageCapture = ImageCapture.Builder().apply {
if (imageCapabilities.supportedOutputFormats
.contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
}
}.build()
val extensionSessionConfig = ExtensionSessionConfig(
ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
)
cameraProvider.bindToLifecycle(..., extensionSessionConfig)val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.withOutput(recorder)
val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
HighSpeedVideoSessionConfig(videoCapture, preview)).first()
val sessionConfig = HighSpeedVideoSessionConfig(
videoCapture,
preview,
frameRateRange = frameRateRange,
isSlowMotionEnabled = true
)
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, sessionConfig)
// Start recording slow motion videos.
val recording = recorder.prepareRecording(context, outputOption)
.start(executor, {})val concurrentCamera =
cameraProvider.bindToLifecycle(list(config1, config2))
// CameraX 1.7 features: rounded corner, border width & color
val pipSettings = CompositionSettings.Builder()
.setOffset(0.2f, 0.2f)
.setScale(0.35f, 0.35f)
.setRoundedCornerRatio(0.15f)
.setBorderWidthRatio(0.02f)
.setBorderColor(Color.WHITE)
.setZOrder(1)
.build()
// CameraX 1.7 features: dynamical composition update
concurrentCamera.setCompositionSettings(
listOf(mainSettings, pipSettings))// 1. Initialize the on-device GenerativeModel client
val generativeModel = Generation.getClient()
// 2. Check whether Gemini Nano is available on the device
val status = generativeModel.checkStatus()
when (status) {
FeatureStatus.UNAVAILABLE -> {}
FeatureStatus.DOWNLOADABLE -> {}
FeatureStatus.DOWNLOADING -> {}
FeatureStatus.AVAILABLE -> {}
}// 3. Build a multimodal request with the image and text prompt
val request = generateContentRequest(
ImagePart(bitmap),
TextPart("Describe this image in detail.")
) {}
// 4. Run inference on-device and get the description
val response = generativeModel.generateContent(request)
val description = response.candidates.first().text// 1. Build GestureRecognizerOptions
val options = GestureRecognizerOptions.builder().setBaseOptions(
BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
.setRunningMode(RunningMode.LIVE_STREAM)
.setResultListener { result, image -> /* Handle detected gesture */ }
.build()
// 2. Create the GestureRecognizer
val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)
// 3. Convert CameraX ImageProxy to MediaPipe MPImage
val bitmap = rotateBitmap(imageProxy.toBitmap(),
imageProxy.imageInfo.rotationDegrees)
val mpImage = BitmapImageBuilder(bitmap).build()
// 4. Run gesture recognition on the image
gestureRecognizer.recognizeAsync(mpImage, frameTime)// Step 1: Verify device support & install module
val enhancementClient = Enhancement.getClient(context)
suspend fun prepare(): Boolean {
if (!enhancementClient.isDeviceSupported().await()) return false
if (!enhancementClient.isModuleInstalled().await()) {
check(enhancementClient.installModule().await()) {
"Enhancement module install failed"
}
}
return true
}// Step 2: Select Enhancement options & create session
val options = EnhancementOptions(
width = bitmap.width,
height = bitmap.height,
enhancementMode = EnhancementMode.BITMAP,
isTonemappingEnabled = true,
isDeblurAndDenoisePhotoEnabled = true,
isDeblurAndDenoiseVideoEnabled = false,
isUpscalePhotoEnabled = false,
isUpscaleVideoEnabled = false
)
val enhancementSession = enhancementClient
.createSessionAsync(options, enhancementExecutor)// Step 3: Process Enhancement
val enhancedBitmap = suspendCancellableCoroutine { continuation ->
val enhancementCallback = object : EnhancementCallback {
override fun onBitmapProcessed(result: Bitmap) {
if (continuation.isActive) continuation.resume(result)
}
override fun onError(statusCode: Int) {
if (continuation.isActive) {
continuation.resumeWithException(
Exception("Bitmap processing failed: $statusCode")
)
}
}
override fun onSurfaceProcessed(timestamp: Long) {}
}
enhancementSession.process(bitmap, options, enhancementCallback)
}// Integrates SW Low Light Boost
val lowLightBoostEffect =
LowLightBoostEffect(
CameraEffect.PREVIEW or
CameraEffect.VIDEO_CAPTURE or
CameraEffect.IMAGE_CAPTURE
)
val sessionConfig = SessionConfig(
useCases = listOf(preview, imageCapture),
effects = lowLightBoostEffect
)
cameraProvider.bindToLifecycle(.. sessionConfig)