מגדירים כל תרחיש לדוגמה של CameraX כדי לשלוט בהיבטים שונים של הפעולות בתרחיש לדוגמה.
לדוגמה, בתרחיש לדוגמה של צילום תמונה, אפשר להגדיר יחס גובה-רוחב של היעד ומצב פלאש. בדוגמה הבאה מוצג קוד לדוגמה:
Kotlin
val imageCapture = ImageCapture.Builder() .setFlashMode(...) .setTargetAspectRatio(...) .build()
Java
ImageCapture imageCapture = new ImageCapture.Builder() .setFlashMode(...) .setTargetAspectRatio(...) .build();
בנוסף לאפשרויות ההגדרה, יש תרחישים לדוגמה שחושפים ממשקי API כדי לשנות את ההגדרות באופן דינמי אחרי שיוצרים את התרחיש לדוגמה. מידע על הגדרות שספציפיות לתרחישי שימוש מסוימים זמין במאמרים הטמעה של תצוגה מקדימה, ניתוח תמונות ולכידת תמונות.
CameraXConfig
כדי לפשט את התהליך, ל-CameraX יש הגדרות ברירת מחדל כמו מנהלי ביצוע פנימיים ומנהלי handler שמתאימים לרוב תרחישי השימוש. עם זאת, אם לאפליקציה שלכם יש דרישות מיוחדות או שאתם מעדיפים להתאים אישית את ההגדרות האלה, CameraXConfig
הוא הממשק המתאים לכך.
באמצעות CameraXConfig
, אפליקציה יכולה:
- אופטימיזציה של זמן האחזור בהפעלה באמצעות
setAvailableCameraLimiter()
. - מספקים את המבצע של האפליקציה ל-CameraX באמצעות
setCameraExecutor()
. - מחליפים את handler ברירת המחדל של המתזמן ב-
setSchedulerHandler()
. - משנים את רמת הרישום ביומן באמצעות
setMinimumLoggingLevel()
.
מודל השימוש
בשלבים הבאים מוסבר איך להשתמש ב-CameraXConfig
:
- יוצרים אובייקט
CameraXConfig
עם ההגדרות המותאמות אישית. - מטמיעים את הממשק
CameraXConfig.Provider
ב-Application
ומחזירים את האובייקטCameraXConfig
ב-getCameraXConfig()
. - מוסיפים את הכיתה
Application
לקובץAndroidManifest.xml
, כמו שמתואר כאן.
לדוגמה, קטע הקוד הבא מגביל את הרישום ביומן של CameraX להודעות שגיאה בלבד:
Kotlin
class CameraApplication : Application(), CameraXConfig.Provider { override fun getCameraXConfig(): CameraXConfig { return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) .setMinimumLoggingLevel(Log.ERROR).build() } }
שומרים עותק מקומי של אובייקט CameraXConfig
אם האפליקציה צריכה לדעת את ההגדרה של CameraX אחרי שמגדירים אותה.
מגביל המצלמה
במהלך ההפעלה הראשונה של
ProcessCameraProvider.getInstance()
,
CameraX מבצעת ספירה ושולחת שאילתות לגבי המאפיינים של המצלמות שזמינות במכשיר. מכיוון ש-CameraX צריכה לתקשר עם רכיבי חומרה, התהליך הזה יכול לקחת זמן רב לכל מצלמה, במיוחד במכשירים ברמה נמוכה. אם האפליקציה שלכם משתמשת רק במצלמות ספציפיות במכשיר, כמו המצלמה הקדמית שמוגדרת כברירת מחדל, אתם יכולים להגדיר את CameraX כך שתתעלם ממצלמות אחרות. כך תוכלו לקצר את זמן ההשהיה של ההפעלה של המצלמות שבהן האפליקציה משתמשת.
אם המסנן CameraSelector
שמועבר אל CameraXConfig.Builder.setAvailableCamerasLimiter()
מסנן מצלמה, CameraX מתנהגת כאילו המצלמה הזו לא קיימת. לדוגמה, הקוד הבא מגביל את השימוש באפליקציה למצלמה האחורית שמוגדרת כברירת מחדל במכשיר:
Kotlin
class MainApplication : Application(), CameraXConfig.Provider { override fun getCameraXConfig(): CameraXConfig { return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig()) .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) .build() } }
שרשורים
הרבה ממשקי API של פלטפורמות שעליהם מבוסס CameraX דורשים חסימה של תקשורת בין תהליכים (IPC) עם חומרה, ולפעמים לוקח מאות אלפיות השנייה עד שהם מגיבים. לכן, CameraX קורא לממשקי ה-API האלה רק מ-threads ברקע, כדי שה-thread הראשי לא ייחסם וממשק המשתמש יישאר חלק. CameraX מנהלת באופן פנימי את השרשורים האלה ברקע, כך שההתנהגות הזו נראית שקופה. עם זאת, יש אפליקציות שנדרש בהן בקרה קפדנית על השרשורים. CameraXConfig
מאפשר לאפליקציה להגדיר את השרשורים ברקע שנעשה בהם שימוש באמצעות CameraXConfig.Builder.setCameraExecutor()
ו-CameraXConfig.Builder.setSchedulerHandler()
.
Camera Executor
ה-executor של המצלמה משמש לכל הקריאות הפנימיות ל-API של פלטפורמת המצלמה, וגם לקריאות חוזרות מממשקי ה-API האלה. CameraX מקצה ומנהל Executor
פנימי כדי לבצע את המשימות האלה.
עם זאת, אם האפליקציה שלכם דורשת שליטה מחמירה יותר בשרשורים, אתם צריכים להשתמש ב-CameraXConfig.Builder.setCameraExecutor()
.
Scheduler Handler
ה-handler של מתזמן המשימות משמש לתזמון משימות פנימיות במרווחי זמן קבועים, כמו ניסיון חוזר לפתוח את המצלמה כשהיא לא זמינה. ה-handler הזה לא מריץ עבודות, והוא רק שולח אותן למבצע המצלמה. הוא משמש גם לפלטפורמות API מדור קודם שדורשות Handler
לקריאות חוזרות. במקרים כאלה, הקריאות החוזרות עדיין נשלחות ישירות רק למבצע של המצלמה. CameraX מקצה ומנהל HandlerThread
פנימי כדי לבצע את המשימות האלה, אבל אפשר לבטל את ההקצאה באמצעות CameraXConfig.Builder.setSchedulerHandler()
.
רישום
רישום ביומן של CameraX מאפשר לאפליקציות לסנן הודעות ב-logcat, כי מומלץ להימנע מהודעות מפורטות בקוד הייצור. CameraX תומך בארבע רמות של רישום ביומן, מהמפורט ביותר ועד החמור ביותר:
-
Log.DEBUG
(ברירת מחדל) Log.INFO
Log.WARN
Log.ERROR
במסמכי התיעוד של יומן Android מופיעים תיאורים מפורטים של רמות היומן האלה. כדי להגדיר את רמת הרישום המתאימה לאפליקציה, צריך להשתמש ב-CameraXConfig.Builder.setMinimumLoggingLevel(int)
.
בחירה אוטומטית
CameraX מספקת באופן אוטומטי פונקציונליות שספציפית למכשיר שבו האפליקציה פועלת. לדוגמה, אם לא מציינים רזולוציה או אם הרזולוציה שצוינה לא נתמכת, CameraX קובעת באופן אוטומטי את הרזולוציה הטובה ביותר לשימוש. הספרייה מטפלת בכל זה, כך שלא צריך לכתוב קוד ספציפי למכשיר.
המטרה של CameraX היא לאתחל סשן מצלמה בהצלחה. המשמעות היא ש-CameraX מתפשרת על הרזולוציה ויחסי הגובה-רוחב בהתאם ליכולות המכשיר. הפריצה יכולה לקרות בגלל:
- המכשיר לא תומך ברזולוציה המבוקשת.
- יש בעיות תאימות במכשיר, למשל מכשירים מדור קודם שנדרש להם רזולוציה מסוימת כדי לפעול בצורה תקינה.
- במכשירים מסוימים, פורמטים מסוימים זמינים רק ביחסי גובה-רוחב מסוימים.
- במכשיר מוגדרת העדפה לקידוד JPEG או וידאו בשיטת 'המספר הקרוב ביותר שמתחלק ב-16'. מידע נוסף זמין במאמר
SCALER_STREAM_CONFIGURATION_MAP
.
למרות ש-CameraX יוצרת את הסשן ומנהלת אותו, תמיד צריך לבדוק את גדלי התמונות שמוחזרים בפלט של תרחיש השימוש בקוד, ולבצע התאמות בהתאם.
סיבוב
כברירת מחדל, סיבוב המצלמה מוגדר כך שיתאים לסיבוב של התצוגה שמוגדרת כברירת מחדל במהלך יצירת תרחיש השימוש. במקרה ברירת המחדל הזה, CameraX יוצרת פלט כדי שהאפליקציה תתאים למה שאתם מצפים לראות בתצוגה המקדימה. כדי לתמוך במכשירים עם כמה מסכים, אפשר לשנות את הרוטציה לערך מותאם אישית על ידי העברת הכיוון הנוכחי של המסך כשמגדירים אובייקטים של תרחישי שימוש, או באופן דינמי אחרי שהם נוצרו.
האפליקציה יכולה להגדיר את סיבוב היעד באמצעות הגדרות התצורה. לאחר מכן, אפשר לעדכן את הגדרות הרוטציה באמצעות ה-methods מממשקי ה-API של תרחישי השימוש (למשל ImageAnalysis.setTargetRotation()
), גם בזמן שמחזור החיים נמצא במצב פעיל. אפשר להשתמש בזה כשהאפליקציה נעולה למצב אנכי – ולכן לא מתבצעת הגדרה מחדש בסיבוב – אבל התמונה או ניתוח מקרה השימוש צריכים להיות מודעים לסיבוב הנוכחי של המכשיר. לדוגמה, יכול להיות שיהיה צורך בהתאמה לסיבוב כדי שהפנים יהיו בכיוון הנכון לזיהוי פנים, או כדי שהתמונות יהיו בכיוון לרוחב או לאורך.
יכול להיות שנתונים של תמונות שצולמו יישמרו בלי מידע על סיבוב. נתוני Exif כוללים מידע על סיבוב, כדי שאפליקציות גלריה יוכלו להציג את התמונה בכיוון הנכון אחרי השמירה.
כדי להציג נתוני תצוגה מקדימה בכיוון הנכון, אפשר להשתמש בפלט המטא-נתונים מ-Preview.PreviewOutput()
כדי ליצור טרנספורמציות.
בדוגמת הקוד הבאה אפשר לראות איך מגדירים את הסיבוב באירוע של שינוי כיוון:
Kotlin
override fun onCreate() { val imageCapture = ImageCapture.Builder().build() val orientationEventListener = object : OrientationEventListener(this as Context) { override fun onOrientationChanged(orientation : Int) { // Monitors orientation values to determine the target rotation value val rotation : Int = when (orientation) { in 45..134 -> Surface.ROTATION_270 in 135..224 -> Surface.ROTATION_180 in 225..314 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageCapture.targetRotation = rotation } } orientationEventListener.enable() }
Java
@Override public void onCreate() { ImageCapture imageCapture = new ImageCapture.Builder().build(); OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) { @Override public void onOrientationChanged(int orientation) { int rotation; // Monitors orientation values to determine the target rotation value if (orientation >= 45 && orientation < 135) { rotation = Surface.ROTATION_270; } else if (orientation >= 135 && orientation < 225) { rotation = Surface.ROTATION_180; } else if (orientation >= 225 && orientation < 315) { rotation = Surface.ROTATION_90; } else { rotation = Surface.ROTATION_0; } imageCapture.setTargetRotation(rotation); } }; orientationEventListener.enable(); }
בהתאם לסיבוב שמוגדר, כל תרחיש שימוש מסובב את נתוני התמונה ישירות או מספק מטא-נתונים של סיבוב לצרכנים של נתוני התמונה שלא סובבו.
- תצוגה מקדימה: המערכת מספקת פלט של מטא-נתונים כדי לדעת מהי רזולוציית היעד של הסיבוב באמצעות
Preview.getTargetRotation()
. - ImageAnalysis: פלט המטא-נתונים מסופק כדי שקואורדינטות מאגר התמונות יהיו ידועות ביחס לקואורדינטות התצוגה.
- ImageCapture: המטא-נתונים של Exif של התמונה, המאגר או גם המאגר וגם המטא-נתונים משתנים כדי לציין את הגדרת הסיבוב. הערך שמשתנה תלוי בהטמעה של HAL.
חיתוך מלבני
כברירת מחדל, מלבן החיתוך הוא מלבן המאגר המלא. אפשר להתאים אותו אישית באמצעות ViewPort
ו-UseCaseGroup
. על ידי קיבוץ של תרחישי שימוש והגדרת אזור התצוגה, CameraX מבטיח שכל המלבנים לחיתוך של כל תרחישי השימוש בקבוצה יצביעו על אותו אזור בחיישן המצלמה.
קטע הקוד הבא מראה איך להשתמש בשתי המחלקות האלה:
Kotlin
val viewPort = ViewPort.Builder(Rational(width, height), display.rotation).build() val useCaseGroup = UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageAnalysis) .addUseCase(imageCapture) .setViewPort(viewPort) .build() cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)
Java
ViewPort viewPort = new ViewPort.Builder( new Rational(width, height), getDisplay().getRotation()).build(); UseCaseGroup useCaseGroup = new UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageAnalysis) .addUseCase(imageCapture) .setViewPort(viewPort) .build(); cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);
הערך ViewPort
מגדיר את המלבן של מאגר הנתונים הזמני שגלוי למשתמשי הקצה. לאחר מכן, CameraX מחשב את מלבן החיתוך הגדול ביותר האפשרי על סמך המאפיינים של אזור התצוגה והתרחישים לדוגמה שמצורפים. בדרך כלל, כדי להשיג אפקט WYSIWYG, אפשר להגדיר את אזור התצוגה על סמך תרחיש השימוש בתצוגה המקדימה. דרך פשוטה לקבל את אזור התצוגה היא באמצעות PreviewView
.
בקטע הקוד הבא מוצג אופן ההשגה של האובייקט ViewPort
:
Kotlin
val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort
Java
ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();
בדוגמה הקודמת, מה שהאפליקציה מקבלת מ-ImageAnalysis
ומ-ImageCapture
זהה למה שמשתמש הקצה רואה ב-PreviewView
, בהנחה שסוג הסקאלה של PreviewView
מוגדר כברירת המחדל, FILL_CENTER
. אחרי שמחילים את מלבן החיתוך והסיבוב על מאגר הפלט, התמונה מכל תרחישי השימוש זהה, אבל יכול להיות שהרזולוציה שלה שונה. מידע נוסף על אופן השימוש בפרטי הטרנספורמציה זמין במאמר בנושא טרנספורמציה של פלט.
בחירת מצלמה
CameraX בוחרת באופן אוטומטי את מכשיר המצלמה המתאים ביותר לדרישות של האפליקציה ולתרחישי השימוש שלה. אם אתם רוצים להשתמש במכשיר אחר מזה שנבחר עבורכם, יש כמה אפשרויות:
- שליחת בקשה למצלמה הקדמית שמוגדרת כברירת מחדל באמצעות
CameraSelector.DEFAULT_FRONT_CAMERA
. - מבקשים את המצלמה האחורית שמוגדרת כברירת מחדל באמצעות
CameraSelector.DEFAULT_BACK_CAMERA
. - מסננים את רשימת המכשירים הזמינים לפי
CameraCharacteristics
עםCameraSelector.Builder.addCameraFilter()
.
בדוגמת הקוד הבאה אפשר לראות איך ליצור CameraSelector
כדי להשפיע על בחירת המכשיר:
Kotlin
fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? { val cam2Infos = provider.availableCameraInfos.map { Camera2CameraInfo.from(it) }.sortedByDescending { // HARDWARE_LEVEL is Int type, with the order of: // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) } return when { cam2Infos.isNotEmpty() -> { CameraSelector.Builder() .addCameraFilter { it.filter { camInfo -> // cam2Infos[0] is either EXTERNAL or best built-in camera val thisCamId = Camera2CameraInfo.from(camInfo).cameraId thisCamId == cam2Infos[0].cameraId } }.build() } else -> null } } // create a CameraSelector for the USB camera (or highest level internal camera) val selector = selectExternalOrBestCamera(processCameraProvider) processCameraProvider.bindToLifecycle(this, selector, preview, analysis)
בחירה של כמה מצלמות בו-זמנית
החל מ-CameraX 1.3, אפשר גם לבחור כמה מצלמות בו-זמנית. לדוגמה, אפשר לקשר בין המצלמה הקדמית למצלמה האחורית כדי לצלם תמונות או להקליט סרטונים משתי נקודות המבט בו-זמנית.
כשמשתמשים בתכונה 'מצלמות בו-זמניות', המכשיר יכול להפעיל שתי מצלמות עם עדשות שפונות לכיוונים שונים בו-זמנית, או להפעיל שתי מצלמות אחוריות בו-זמנית. בלוק הקוד הבא מראה איך להגדיר שתי מצלמות כשמבצעים קריאה ל-bindToLifecycle
, ואיך לקבל את שני אובייקטי המצלמה מהאובייקט ConcurrentCamera
שמוחזר.
Kotlin
// Build ConcurrentCameraConfig val primary = ConcurrentCamera.SingleCameraConfig( primaryCameraSelector, useCaseGroup, lifecycleOwner ) val secondary = ConcurrentCamera.SingleCameraConfig( secondaryCameraSelector, useCaseGroup, lifecycleOwner ) val concurrentCamera = cameraProvider.bindToLifecycle( listOf(primary, secondary) ) val primaryCamera = concurrentCamera.cameras[0] val secondaryCamera = concurrentCamera.cameras[1]
Java
// Build ConcurrentCameraConfig SingleCameraConfig primary = new SingleCameraConfig( primaryCameraSelector, useCaseGroup, lifecycleOwner ); SingleCameraConfig secondary = new SingleCameraConfig( primaryCameraSelector, useCaseGroup, lifecycleOwner ); ConcurrentCamera concurrentCamera = mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary)); Camera primaryCamera = concurrentCamera.getCameras().get(0); Camera secondaryCamera = concurrentCamera.getCameras().get(1);
רזולוציית המצלמה
אתם יכולים לבחור שהספרייה CameraX תגדיר את רזולוציית התמונה על סמך שילוב של יכולות המכשיר, רמת החומרה הנתמכת במכשיר, תרחיש השימוש ויחס הגובה-רוחב שצוין. לחלופין, אפשר להגדיר רזולוציית יעד ספציפית או יחס רוחב-גובה ספציפי בתרחישי שימוש שתומכים בהגדרה הזו.
רזולוציה אוטומטית
CameraX יכולה לקבוע באופן אוטומטי את הגדרות הרזולוציה הטובות ביותר על סמך תרחישי השימוש שצוינו ב-cameraProcessProvider.bindToLifecycle()
. בכל הזדמנות, כדאי לציין את כל תרחישי השימוש שצריך להפעיל במקביל בסשן יחיד בקריאה יחידה של bindToLifecycle()
. CameraX קובעת את הרזולוציות על סמך קבוצת תרחישי השימוש שמוגבלים על ידי רמת החומרה הנתמכת במכשיר, ועל ידי התחשבות בשונות ספציפית למכשיר (במקרים שבהם המכשיר עומד בדרישות של הגדרות הסטרימינג הזמינות או חורג מהן).
המטרה היא לאפשר לאפליקציה לפעול במגוון רחב של מכשירים, תוך צמצום של נתיבי קוד ספציפיים למכשיר.
יחס הגובה-רוחב שמוגדר כברירת מחדל לתרחישי שימוש של צילום תמונות וניתוח תמונות הוא 4:3.
ליחסי גובה-רוחב יש הגדרה שמאפשרת לאפליקציה לציין את יחס הגובה-רוחב הרצוי על סמך עיצוב ממשק המשתמש. הפלט של CameraX נוצר בהתאם ליחסי הגובה-רוחב המבוקשים, באופן הכי מדויק שאפשר בהתאם לתמיכה של המכשיר. אם אין רזולוציה של התאמה מדויקת, המערכת בוחרת את הרזולוציה שממלאת את רוב התנאים. לכן, האפליקציה קובעת איך המצלמה תופיע באפליקציה, ו-CameraX קובעת את הגדרות הרזולוציה הטובות ביותר של המצלמה כדי לעמוד בדרישות האלה במכשירים שונים.
לדוגמה, אפליקציה יכולה לבצע כל אחת מהפעולות הבאות:
- הגדרת רזולוציית יעד של 4:3 או 16:9 לתרחיש שימוש
- מציינים רזולוציה מותאמת אישית, ו-CameraX מנסה למצוא את הרזולוציה הכי קרובה
- הגדרת יחס גובה-רוחב לחיתוך של
ImageCapture
CameraX בוחר באופן אוטומטי את רזולוציות המשטח הפנימיות של Camera2. בטבלה הבאה מוצגות הרזולוציות:
תרחיש לדוגמה | רזולוציה של משטחים פנימיים | רזולוציית נתוני הפלט |
---|---|---|
תצוגה מקדימה | יחס גובה-רוחב: הרזולוציה שהכי מתאימה ליעד בהגדרה. | רזולוציית מסך פנימית. המטא-נתונים מסופקים כדי לאפשר חיתוך, שינוי גודל וסיבוב של התצוגה בהתאם ליחס הגובה-רוחב של היעד. |
רזולוציית ברירת מחדל: הרזולוציה הגבוהה ביותר בתצוגה המקדימה, או הרזולוציה המועדפת הגבוהה ביותר במכשיר שתואמת ליחס הגובה-רוחב של התצוגה המקדימה. | ||
רזולוציה מקסימלית: גודל התצוגה המקדימה, שמתייחס לגודל הטוב ביותר שמתאים לרזולוציית המסך של המכשיר, או ל-1080p (1920x1080), הקטן מביניהם. | ||
ניתוח תמונות | יחס גובה-רוחב: הרזולוציה שהכי מתאימה ליעד בהגדרה. | רזולוציית מסך פנימית. |
רזולוציית ברירת מחדל: הגדרת ברירת המחדל של רזולוציית היעד היא 640x480. שינוי הרזולוציה של היעד ויחס הגובה-רוחב התואם יוביל לרזולוציה הנתמכת הטובה ביותר. | ||
רזולוציה מקסימלית: רזולוציית הפלט המקסימלית של מכשיר המצלמה בפורמט YUV_420_888, שמאוחזרת מ-StreamConfigurationMap.getOutputSizes() .
רזולוציית היעד מוגדרת כ-640x480 כברירת מחדל, לכן אם רוצים רזולוציה גדולה יותר מ-640x480, צריך להשתמש בתגים
setTargetResolution()
ו-
setTargetAspectRatio()
כדי לקבל את הרזולוציה הכי קרובה מבין הרזולוציות הנתמכות.
|
||
צילום תמונה | יחס גובה-רוחב: יחס הגובה-רוחב שהכי מתאים להגדרה. | רזולוציית מסך פנימית. |
רזולוציית ברירת מחדל: הרזולוציה הגבוהה ביותר שזמינה, או הרזולוציה הגבוהה ביותר שהמכשיר מעדיף שתואמת ליחס הגובה-רוחב של ImageCapture. | ||
רזולוציה מקסימלית: רזולוציית הפלט המקסימלית של מכשיר המצלמה בפורמט JPEG. אפשר לאחזר את הנתונים באמצעות StreamConfigurationMap.getOutputSizes() .
|
ציון רזולוציה
אפשר להגדיר רזולוציות ספציפיות כשיוצרים תרחישים לדוגמה באמצעות השיטה setTargetResolution(Size resolution)
, כמו בדוגמת הקוד הבאה:
Kotlin
val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(1280, 720)) .build()
Java
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() .setTargetResolution(new Size(1280, 720)) .build();
אי אפשר להגדיר גם יחס רוחב-גובה של היעד וגם רזולוציית יעד באותו תרחיש שימוש. אם עושים זאת, מתקבלת שגיאה IllegalArgumentException
כשיוצרים את אובייקט ההגדרה.
מבטאים את הרזולוציה Size
במערכת הצירים אחרי סיבוב המידות הנתמכות בסיבוב היעד. לדוגמה, מכשיר עם אוריינטציה טבעית לאורך בסיבוב טבעי של הטירגוט שמבקש תמונה לאורך יכול לציין 480x640, ואותו מכשיר, שמסובב ב-90 מעלות ומטרגט אוריינטציה לרוחב, יכול לציין 640x480.
רזולוציית היעד מנסה לקבוע גבול מינימלי לרזולוציית התמונה. הרזולוציה בפועל של התמונה היא הרזולוציה הקרובה ביותר שזמינה בגודל שלא קטן מהרזולוציה הרצויה, כפי שנקבע על ידי הטמעת המצלמה.
עם זאת, אם אין רזולוציה ששווה לרזולוציית היעד או גדולה ממנה, המערכת בוחרת ברזולוציה הקרובה ביותר שקטנה מרזולוציית היעד. לרזולוציות עם אותו יחס גובה-רוחב כמו Size
שצוין ניתנת עדיפות גבוהה יותר מאשר לרזולוציות עם יחסי גובה-רוחב שונים.
CameraX בוחר את הרזולוציה המתאימה ביותר על סמך הבקשות. אם הצורך העיקרי הוא להתאים את יחס הגובה-רוחב, מציינים רק setTargetAspectRatio
, ו-CameraX קובע רזולוציה ספציפית שמתאימה למכשיר.
אם הצורך העיקרי של האפליקציה הוא לציין רזולוציה כדי לייעל את עיבוד התמונות (למשל, תמונה קטנה או בינונית על סמך יכולת העיבוד של המכשיר), צריך להשתמש בהרשאה setTargetResolution(Size resolution)
.
אם האפליקציה שלכם דורשת רזולוציה מדויקת, תוכלו לעיין בטבלה שבמאמר createCaptureSession()
כדי לראות אילו רזולוציות מקסימליות נתמכות בכל רמת חומרה. כדי לבדוק אילו רזולוציות ספציפיות נתמכות במכשיר הנוכחי, אפשר לעיין במאמר StreamConfigurationMap.getOutputSizes(int)
.
אם האפליקציה פועלת ב-Android מגרסה 10 ואילך, אפשר להשתמש ב-isSessionConfigurationSupported()
כדי לאמת SessionConfiguration
ספציפי.
שליטה בפלט המצלמה
בנוסף לאפשרות להגדיר את פלט המצלמה לפי הצורך לכל תרחיש שימוש, CameraX גם מטמיע את הממשקים הבאים כדי לתמוך בפעולות מצלמה שמשותפות לכל תרחישי השימוש הקשורים:
-
CameraControl
מאפשר לכם להגדיר תכונות נפוצות של המצלמה. -
CameraInfo
מאפשר לכם לשלוח שאילתות לגבי המצבים של תכונות המצלמה הנפוצות האלה.
אלה תכונות המצלמה הנתמכות ב-CameraControl:
- שינוי מרחק התצוגה
- פנס
- מיקוד ומדידה (הקשה למיקוד)
- פיצוי חשיפה
קבלת מופעים של CameraControl ו-CameraInfo
מאחזרים מופעים של CameraControl
ושל CameraInfo
באמצעות האובייקט Camera
שמוחזר על ידי ProcessCameraProvider.bindToLifecycle()
.
בדוגמה הבאה מוצג קוד:
Kotlin
val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // For performing operations that affect all outputs. val cameraControl = camera.cameraControl // For querying information and states. val cameraInfo = camera.cameraInfo
Java
Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // For performing operations that affect all outputs. CameraControl cameraControl = camera.getCameraControl() // For querying information and states. CameraInfo cameraInfo = camera.getCameraInfo()
לדוגמה, אפשר לשלוח פעולות של זום ושל CameraControl
אחרי שקוראים ל-bindToLifecycle()
. אחרי שמפסיקים או משמידים את הפעילות שמשמשת לקישור מופע המצלמה, CameraControl
לא יכול יותר לבצע פעולות ומחזיר ListenableFuture
שנכשלה.
שינוי מרחק התצוגה
ב-CameraControl יש שתי שיטות לשינוי רמת הזום:
setZoomRatio()
מגדיר את הזום לפי יחס הזום.היחס צריך להיות בטווח שבין
CameraInfo.getZoomState().getValue().getMinZoomRatio()
ל-CameraInfo.getZoomState().getValue().getMaxZoomRatio()
. אחרת, הפונקציה מחזירהListenableFuture
עם הערך 'נכשל'.
setLinearZoom()
מגדיר את הזום הנוכחי עם ערך זום לינארי בטווח שבין 0 ל-1.0.היתרון של שינוי מרחק מתצוגה לינארי הוא ששדה הראייה (FOV) משתנה בהתאם לשינויים במרחק מתצוגה. לכן היא אידיאלית לשימוש בתצוגה
Slider
.
CameraInfo.getZoomState()
מחזירה LiveData של מצב הזום הנוכחי. הערך משתנה כשהמצלמה מאותחלת או אם מגדירים את רמת הזום באמצעות setZoomRatio()
או setLinearZoom()
. הפעלת כל אחת מהשיטות מגדירה את הערכים שמאחורי ZoomState.getZoomRatio()
ו-ZoomState.getLinearZoom()
.
האפשרות הזו שימושית אם רוצים להציג טקסט של יחס הזום לצד פס הזזה.
פשוט מסתכלים על ZoomState
LiveData
כדי לעדכן את שניהם בלי להמיר.
הערך ListenableFuture
שמוחזר על ידי שני ממשקי ה-API מאפשר לאפליקציות לקבל הודעה כשבקשה חוזרת עם ערך הזום שצוין מסתיימת. בנוסף, אם מגדירים ערך חדש של שינוי גודל התצוגה בזמן שהפעולה הקודמת עדיין מתבצעת, הפעולה הקודמת של שינוי גודל התצוגה נכשלת מיד.ListenableFuture
פנס
CameraControl.enableTorch(boolean)
מפעיל או משבית את הפנס.
אפשר להשתמש ב-CameraInfo.getTorchState()
כדי לשאול מה המצב הנוכחי של הפנס. אפשר לבדוק את הערך שמוחזר על ידי CameraInfo.hasFlashUnit()
כדי לקבוע אם יש פנס. אם לא, קריאה ל-calling
CameraControl.enableTorch(boolean)
גורמת להשלמה מיידית של ListenableFuture
עם תוצאה של כשל, ומגדירה את מצב הפנס ל-TorchState.OFF
.
כשהפנס מופעל, הוא נשאר דולק בזמן צילום תמונות וסרטונים, בלי קשר להגדרה של flashMode. flashMode
ב-ImageCapture
פועל רק כשהפנס מושבת.
מיקוד ומדידה
CameraControl.startFocusAndMetering()
מפעיל פוקוס אוטומטי ומדידת חשיפה על ידי הגדרת אזורי מדידה של AF/AE/AWB
על סמך FocusMeteringAction שצוין. היא משמשת לעיתים קרובות להטמעה של התכונה 'הקשה למיקוד' בהרבה אפליקציות מצלמה.
MeteringPoint
כדי להתחיל, יוצרים MeteringPoint
באמצעות MeteringPointFactory.createPoint(float x, float y, float
size)
.
MeteringPoint
מייצג נקודה אחת במצלמה Surface
. הוא מאוחסן בצורה מנורמלת כדי שאפשר יהיה להמיר אותו בקלות לקואורדינטות של חיישן לצורך הגדרת אזורי AF/AE/AWB.
הגודל של MeteringPoint
נע בין 0 ל-1, והגודל שמוגדר כברירת מחדל הוא 0.15f. כשקוראים ל-MeteringPointFactory.createPoint(float x, float y, float
size)
, CameraX יוצר אזור מלבני שממורכז ב-(x, y)
עבור size
שסופק.
בדוגמה הבאה מוצג קוד ליצירת MeteringPoint
:
Kotlin
// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview. previewView.setOnTouchListener((view, motionEvent) -> { val meteringPoint = previewView.meteringPointFactory .createPoint(motionEvent.x, motionEvent.y) … } // Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for // preview. Please note that if the preview is scaled or cropped in the View, // it’s the application's responsibility to transform the coordinates properly // so that the width and height of this factory represents the full Preview FOV. // And the (x,y) passed to create MeteringPoint might need to be adjusted with // the offsets. val meteringPointFactory = DisplayOrientedMeteringPointFactory( surfaceView.display, camera.cameraInfo, surfaceView.width, surfaceView.height ) // Use SurfaceOrientedMeteringPointFactory if the point is specified in // ImageAnalysis ImageProxy. val meteringPointFactory = SurfaceOrientedMeteringPointFactory( imageWidth, imageHeight, imageAnalysis)
startFocusAndMetering ו-FocusMeteringAction
כדי להפעיל את startFocusAndMetering()
, האפליקציות צריכות ליצור FocusMeteringAction
, שמורכב מאחד או יותר מ-MeteringPoints
עם שילובים אופציונליים של מצבי מדידה מ-FLAG_AF
, FLAG_AE
ו-FLAG_AWB
. הקוד הבא מדגים את השימוש הזה:
Kotlin
val meteringPoint1 = meteringPointFactory.createPoint(x1, x1) val meteringPoint2 = meteringPointFactory.createPoint(x2, y2) val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB // Optionally add meteringPoint2 for AF/AE. .addPoint(meteringPoint2, FLAG_AF | FLAG_AE) // The action is canceled in 3 seconds (if not set, default is 5s). .setAutoCancelDuration(3, TimeUnit.SECONDS) .build() val result = cameraControl.startFocusAndMetering(action) // Adds listener to the ListenableFuture if you need to know the focusMetering result. result.addListener({ // result.get().isFocusSuccessful returns if the auto focus is successful or not. }, ContextCompat.getMainExecutor(this)
כפי שמוצג בקוד שלמעלה, הפונקציה startFocusAndMetering()
מקבלת FocusMeteringAction
שכולל MeteringPoint
אחד לאזורי מדידה של AF/AE/AWB ועוד MeteringPoint ל-AF ול-AE בלבד.
באופן פנימי, CameraX ממירה את זה ל-Camera2
MeteringRectangles
ומגדירה את הפרמטרים התואמים של
CONTROL_AF_REGIONS
/
CONTROL_AE_REGIONS
/
CONTROL_AWB_REGIONS
לבקשת הצילום.
מכיוון שלא כל מכשיר תומך ב-AF/AE/AWB ובאזורים מרובים, CameraX מבצע את FocusMeteringAction
על בסיס הפתרון האפשרי הטוב ביותר. CameraX משתמש במספר המקסימלי של MeteringPoints שנתמכים, בסדר שבו הנקודות נוספו. המערכת מתעלמת מכל הערכים של MeteringPoints שנוספו אחרי שהגעתם למספר המקסימלי. לדוגמה, אם מספקים FocusMeteringAction
עם 3 MeteringPoints בפלטפורמה שתומכת רק ב-2, רק 2 ה-MeteringPoints הראשונים ישמשו. המצלמה מתעלמת מהערך הסופי של MeteringPoint
.
פיצוי חשיפה
פיצוי החשיפה שימושי כשצריך לכוונן את ערכי החשיפה (EV) באפליקציות מעבר לתוצאת הפלט של החשיפה האוטומטית (AE). ערכי פיצוי החשיפה משולבים באופן הבא כדי לקבוע את החשיפה הנדרשת לתנאי התמונה הנוכחיים:
Exposure = ExposureCompensationIndex * ExposureCompensationStep
CameraX מספקת את הפונקציה Camera.CameraControl.setExposureCompensationIndex()
להגדרת פיצוי החשיפה כערך אינדקס.
ערכים חיוביים של האינדקס מבהירים את התמונה, וערכים שליליים מכהים אותה. אפליקציות יכולות לשלוח שאילתה לגבי הטווח הנתמך באמצעות CameraInfo.ExposureState.exposureCompensationRange()
, כפי שמתואר בקטע הבא. אם הערך נתמך, הפעולה ListenableFuture
שמוחזרת מסתיימת כשהערך מופעל בהצלחה בבקשת הלכידה. אם האינדקס שצוין נמצא מחוץ לטווח הנתמך, הפעולה setExposureCompensationIndex()
גורמת לסיום מיידי של הפעולה ListenableFuture
שמוחזרת עם תוצאה של כשל.
CameraX שומרת רק את בקשת setExposureCompensationIndex()
האחרונה שלא טופלה, וקריאה לפונקציה כמה פעמים לפני שהבקשה הקודמת מבוצעת גורמת לביטול שלה.
בקטע הקוד הבא מוגדר אינדקס של פיצוי חשיפה ונרשמת פונקציית קריאה חוזרת (callback) לביצוע כשבקשת שינוי החשיפה הושלמה:
Kotlin
camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex) .addListener({ // Get the current exposure compensation index, it might be // different from the asked value in case this request was // canceled by a newer setting request. val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex … }, mainExecutor)
Camera.CameraInfo.getExposureState()
מאחזר אתExposureState
הנוכחי, כולל:- התמיכה באמצעי הבקרה של פיצוי החשיפה.
- מדד פיצוי החשיפה הנוכחי.
- טווח האינדקס של פיצוי החשיפה.
- שלב פיצוי החשיפה שמשמש לחישוב ערך פיצוי החשיפה.
לדוגמה, הקוד הבא מאתחל את ההגדרות של חשיפה SeekBar
עם הערכים הנוכחיים של ExposureState
:
Kotlin
val exposureState = camera.cameraInfo.exposureState binding.seekBar.apply { isEnabled = exposureState.isExposureCompensationSupported max = exposureState.exposureCompensationRange.upper min = exposureState.exposureCompensationRange.lower progress = exposureState.exposureCompensationIndex }
מקורות מידע נוספים
כדי לקבל מידע נוסף על CameraX, אפשר לעיין במקורות המידע הנוספים הבאים.
Codelab
קוד לדוגמה
קהילת המפתחים
קבוצת דיון בנושא Android CameraX