يتوقّع المستخدمون أن تلتقط التطبيقات الخارجية الصور والفيديوهات بالدقة العالية نفسها التي تلتقطها الكاميرا الأصلية في أجهزتهم، بغض النظر عن ظروف الإضاءة أو فئة الأجهزة. يتطلب تقديم هذا الاتساق عبر منظومة Android المتكاملة المتوسعة مراعاة اعتبارات معمارية رئيسية. توضّح هذه الجلسة كيفية إنشاء مسارات التقاط قوية واحترافية تستفيد إلى أقصى حد من إمكانات الجهاز مع الحفاظ على موثوقيتها على جميع أشكال الأجهزة.
سنستكشف أحدث التطوّرات في CameraX، وسنوضّح لك كيفية دمج الإمكانات المتطوّرة، مثل "الصور الحيّة" والتحسينات المستندة إلى الذكاء الاصطناعي على الجهاز من خلال "خدمات Google Play"، بالإضافة إلى التجارب المكانية باستخدام حزمة تطوير البرامج (SDK) لـ Jetpack XR. سنلقي نظرة أيضًا على مهارة CameraX التي تتيح لوكيل الترميز الذي تختاره تنفيذ المفاهيم الواردة في هذه الجلسة تلقائيًا، والمزيد.
CameraXViewfinder معاينات الكاميرا على مختلف أشكال الأجهزة، مثل الأجهزة القابلة للطي. في الإصدار 1.7 من CameraX، تمت إضافة عناصر التحكّم بالإيماءات وميزة "وميض الشاشة".ProjectedDeviceContext لـ CameraX بالتقاط الصور مباشرةً من النظارات.النص
مرحبًا،
مرحبًا بكم في جلستنا.
اسمي "أمان"، ومعي "سكوت".
يسرّنا اليوم التحدّث عن كيفية تحسين تجارب الكاميرا على جميع الأجهزة باستخدام CameraX والذكاء الاصطناعي على الجهاز فقط.
إذًا، هيا بنا نبدأ.
فكِّروا في كيفية استخدام هواتفكم.
غالبًا ما نستخدم كاميرا الهاتف لالتقاط اللحظات ومشاركتها مع أحبائنا.
أصبحت الكاميرا جزءًا أساسيًا من تجربة استخدام الهاتف الذكي.
في الواقع، غالبًا ما تكون الكاميرا الجيدة عاملاً رئيسيًا عند شراء الهواتف الذكية.
سنتناول اليوم موضوعَين رئيسيَّين لإنشاء تجارب كاميرا متميّزة.
أولاً، سنستكشف بعض ميزات CameraX، ثم سنتعرّف على بعض تقنيات تعلُّم الآلة على الجهاز.
لنبدأ باستخدام CameraX.
توفّر Jetpack CameraX تجربة الكاميرا على Android.
بالإضافة إلى CameraX، لدينا أيضًا Camera1 وCamera2، وهما جزء من إطار عمل نظام Android الأساسي.
إذًا، أي مكتبة يجب استخدامها؟
كانت Camera1 هي واجهة برمجة التطبيقات الأصلية للكاميرا على Android.
وقد تم إيقافها نهائيًا، لذا يجب أن تستخدم التطبيقات الجديدة إما Camera2 أو CameraX.
حلّت Camera2 محل Camera1.
لذلك، إذا كنت بحاجة إلى التحكّم في الكاميرا على مستوى منخفض لحالات الاستخدام المعقّدة، فإنّ Camera2 هو خيار جيد.
ومع ذلك، يتطلّب ذلك التعامل مع الإعدادات الخاصة بالجهاز بنفسك.
وأخيرًا، تعتمد Jetpack CameraX على Camera2.
وتوفّر واجهة برمجة تطبيقات متّسقة وتتعامل مع الإعدادات الخاصة بالجهاز تلقائيًا.
وهو الخيار المقترَح لمعظم المطوّرين.
لدينا ملاحظة مهمة جدًا يجب توضيحها.
نخطّط لإيقاف Camera1، وسنبدأ بتطبيق سياسات Play في العام المقبل في Android 18.
إذا كنت لا تزال تستخدم واجهة برمجة التطبيقات القديمة Camera1، عليك الانتقال إلى CameraX أو Camera2.
إذًا، لماذا يجب استخدام CameraX؟
أولاً، تتوافق هذه الميزة مع الإصدار 7 من نظام التشغيل Android والإصدارات الأحدث، ما يجعلها متوافقة مع %99 تقريبًا من أجهزة Android النشطة.
ثانيًا، إنّها سهلة الاستخدام وتوفّر واجهات برمجة تطبيقات بسيطة لحالات الاستخدام الشائعة، مثل المعاينة والالتقاط والتسجيل باستخدام رمز أقل.
ثالثًا، توفّر هذه الميزة تجربة متّسقة على جميع الأجهزة من خلال تقليل المشاكل المتعلّقة بالأجهزة، مثل تمديد المعاينة، وهو أمر شائع جدًا على الأجهزة القابلة للطي والأجهزة اللوحية.
وأخيرًا، يسهّل تنفيذ ميزات المصنّع الأصلي للجهاز المتقدّمة، مثل "الوضع الليلي" و"بورتريه".
والآن، أودّ دعوة "سكوت" للتحدّث عن بعض ميزات CameraX.
حان دورك الآن، "سامي".
شكرًا جزيلاً لك يا "أمان".
مرحبًا،
اسمي "سكوت".
واليوم، سنتناول العديد من ميزات CameraX لتمكينك من إنشاء تجربة كاميرا احترافية.
أولاً، ميزة "عدسة الكاميرا التكيُّفية".
تم تصميم هذه الميزة لتسهيل معاينة الاسم المعرِّف على مختلف أشكال الأجهزة وأحجام الشاشات.
يستخدم المستخدمون الآن مجموعة متنوعة من الأجهزة، بما في ذلك الأجهزة القابلة للطي والهواتف وأجهزة الكمبيوتر المحمولة وأجهزة XR.
لذا، من المهم أكثر من أي وقت مضى إنشاء تجربة كاميرا تتوافق مع أشكال الأجهزة الجديدة.
ومن المعروف أنّ معاينة الكاميرا صعبة للغاية، خاصةً على الأجهزة القابلة للطي ذات الشاشة العريضة.
على هذه الأجهزة، عند فتحها، يتم تثبيت الكاميرا الداخلية أفقيًا، بينما يتم تثبيت الكاميرا الخلفية عموديًا.
لذلك، إذا افترضت أنّ جميع أجهزة استشعار الكاميرا مثبّتة بشكل عمودي، سيؤدي ذلك إلى عدم تطابق في المساحة، ما يسبّب تشويهًا أو انحرافًا في الصورة.
وCameraXViewfinder، تم تصميمه للتعامل مع التعقيدات بالكامل نيابةً عنك، ما يضمن عمل معاينة الكاميرا بسلاسة على جميع أشكال الأجهزة.
لنتعرّف على كيفية استخدام الدالة المركّبة 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 للاحتفاظ بطلب Surface.
نعتذر لحدوث ذلك.
ثانيًا، في 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
}
)ما عليك سوى تفعيل ميزتَي "النقر للتركيز" و"التكبير/التصغير بإصبعَين".
وبالنسبة إلى وميض الشاشة، يمكنك الحصول على مثيل وميض الشاشة من دالة الاستدعاء onScreenFlashReady ثم تمريره إلى ImageCapture وضبط وضع الفلاش على FLASH_MODE_SCREEN.
هذا كل ما في الأمر
لقد رأينا كيف تتيح واجهة برمجة التطبيقات CameraX معاينة الكاميرا التي تعمل بسلاسة على جميع أشكال الأجهزة.
لننتقل الآن إلى خطوة أخرى من خلال التقاط صورة بالكاميرا على Android XR.
هناك ثلاث فئات من أجهزة XR: أولاً، سماعات الرأس بنظام الواقع الممتد.
وهي سمّاعات رأس مغلقة بالكامل وغامرة تستخدم الكاميرا لالتقاط العالم المادي وعرضه داخل سمّاعات الرأس.
والثانية هي النظارات السلكية بنظام الواقع الممتد.
وقد أعلنّا عن XREAL Aura في الكلمة الرئيسية ضمن هذه الفئة اليوم.
كما ترون في الكلمات الرئيسية، هذه هي النظارات الشفافة المتصلة بوحدة المعالجة والتي تتيح لكم عرض المحتوى الافتراضي فوق العالم الحقيقي.
والفئة الثالثة هي النظارات الصوتية والنظارات بشاشة عرض.
هذه هي النظارات الخفيفة الوزن.
ويمكنها الجمع بين الصوت والكاميرا والشاشة لتوفير تجارب بدون لمس الجهاز وبنظرة سريعة.
وتتيح الفئتان الأوليان، وهما سماعة رأس بنظام الواقع الممتد ونظارات بنظام الواقع الممتد السلكية، استخدام Camera2 وCameraX، تمامًا مثل الهاتف العادي.
لذا، سنركّز اليوم على النظارات الصوتية والنظارات بشاشة عرض فقط.
في هذه النظارات، يتم تشغيل التطبيقات على هاتف Android، وهو هاتف 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.
هذا الإجراء ضروري لأنّ النظارات الحالية تتيح بثًا واحدًا فقط.
وواجهة برمجة التطبيقات داخلية حاليًا، ولكننا سنطرحها للجميع قريبًا.
هذا كل ما في الأمر
من هنا، يمكنك استخدام واجهة برمجة التطبيقات CameraX العادية، وربط ImageCapture، والتقاط الصور من خلال النظارات.
حسنًا
بعد ذلك، لنتعرّف على كيفية مساعدة CameraX في العثور على مجموعة الميزات التي تعمل معًا وتحقيق أفضل النتائج من الجهاز.
تتميز تطبيقات الكاميرا الحديثة بميزات فعّالة، ويتوقع المستخدم أن تتيح الكاميرا ميزات مثل النطاق العالي الديناميكية (HDR) و60 لقطة في الثانية وتثبيت المعاينة، وكل ذلك في الوقت نفسه.
ولكن كمطوّر، نعلم أنّ الجمع بين هذه الميزات معًا ليس أمرًا سهلاً دائمًا.
التحقّق من توفّر ميزة دعم فردية لا يضمن أنّها ستعمل معًا.
وقد أجبرت حالة عدم اليقين هذه المطوّرين على توخي الحذر والحدّ من تجربة المستخدم على الأجهزة القادرة على تشغيل هذه الميزات.
لحلّ هذه المشكلة، قدّمنا واجهة برمجة التطبيقات Feature Group API التي تستند إلى إطار عمل طلب البحث عن مجموعات ميزات Camera2.
وهناك طريقتان لاستخدامها: أولاً، استخدِم requiredFeatureGroup للتحقّق من مجموعة الميزات وتفعيلها عندما تكون متاحة.
ثانيًا، يمكنك تحديد أولوية الميزات حسب preferredFeatureGroup، ثم تختار CameraX أفضل مجموعة متوافقة لك.
وإليك المثال.
على اليمين، على اليسار، عذرًا، مع عدم تفعيل أي شيء، مجرد هاتف عادي، وتسجيل عادي.
على اليمين، نفعّل ميزات HDR و60 لقطة في الثانية وتثبيت المعاينة معًا.
ويمكنكم ملاحظة تحسّن الجودة بشكل كبير من خلال الفيديو الأكثر سطوعًا وسلاسةً والخالي من الاهتزاز.
لنلقِ نظرة على الرمز.
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)لذلك، نستخدم هنا preferredFeatureGroup لتوفير قائمة أولويات الميزات، وهي النطاق العالي الديناميكية، ثم 60 لقطة في الثانية، ثم ميزة "تثبيت المعاينة".
وستتحقّق واجهة 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.
تتيح مكتبة CameraX الآن التقاط صور بتنسيق DNG RAW.
تتيح لك هذه الميزة الوصول إلى صورة RAW غير المعالَجة وغير المضغوطة مباشرةً من أداة استشعار الكاميرا لإجراء تعديلات احترافية.
بعد ذلك، أجرينا تحسينات على إضافة "الوضع الليلي".
تتيح هذه الإضافة التقاط صور ساطعة وواضحة في الإضاءة المنخفضة بدون استخدام الفلاش.
وقدّمنا واجهة برمجة تطبيقات جديدة لمؤشر الوضع الليلي.
ستراقب الواجهة البيئة الحالية عندما تكون مظلمة ومناسبة لـ "الوضع الليلي"، ويمكنك استخدامها لعرض مؤشر "الوضع الليلي".
وأصبحت ميزة Ultra HDR متاحة الآن في إضافات CameraX.
يوفّر ذلك نطاقًا ديناميكيًا أوسع وألوانًا نابضة بالحياة في "الوضع الليلي"، ويتوفّر "الوضع الليلي" مع ميزة "دقة 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 للحصول على إمكانية التقاط الصور، ثم الاستعلام عما إذا كانت ميزة "دقة HDR فائقة" متاحة.
بعد ذلك، يمكنك ضبط تنسيق الإخراج على Ultra HDR.
وأخيرًا، يمكنك إنشاء ExtensionSessionConfig مرة أخرى باستخدام حالات الاستخدام والربط بمراحل النشاط.
أنت الآن جاهز لالتقاط صور بدقة 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 على "صحيح" لترميزه بمعدّل 30 لقطة في الثانية لإنشاء فيديو بالحركة البطيئة، أو ضبطه على "خطأ" لإنشاء فيديو بمعدّل لقطات عالي السرعة.
وعند تفعيل ميزة "الحركة البطيئة"، يتعرّف عليها تطبيق "صور Google" تلقائيًا ويضيف شارة "الحركة البطيئة" إلى الصورة المصغّرة.
لنتعرّف على طريقة عملها في "صور Google".
هنا، يضفي التصوير البطيء تأثيرًا دراميًا على القط.
لا تسمح لي قطتي بلمسها أبدًا.
لذا، أعتذر.
أخيرًا، لنتعرّف على بعض التحسينات في واجهة Concurrent Camera 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 على الكاميرا المتزامنة النشطة لتعديل التركيب ديناميكيًا.
هذا كل ما في الأمر
يمكنك الانتقال إلى تعديل التركيبة ديناميكيًا.
حسنًا
لقد استعرضنا الآن جميع الميزات.
لدينا ملاحظة مهمة هنا، لذا ننصحك بشدة بالترقية إلى الإصدار 1.6 من CameraX وجميع الإصدارات الأحدث.
يمكن أن يؤدي ذلك إلى تجنُّب تعطُّل الأجهزة القادمة التي تتيح التنسيق الجديد، وهو Eclipsa Video، وضمان استمرار عمل إضافة CameraX في تحديث Pixel المستقبلي للميزة.
بهذا نختتم جلستنا الأولى.
لقد تناولنا ميزات CameraX التي تساعدك في إنشاء تجربة كاميرا احترافية، والآن أودّ دعوة "أمان" لتغطية جلستنا التالية حول الذكاء الاصطناعي على الجهاز.
شكرًا لك يا "سكوت" على شرح ميزات CameraX.
لنلقِ نظرة الآن على كيفية الاستفادة من تعلُّم الآلة على الجهاز فقط لإنشاء تجارب كاميرا ذكية.
يتيح تعلُّم الآلة على الجهاز فقط مجموعة واسعة من حالات استخدام الكاميرا في مجالَين رئيسيَين.
الأول هو ما قبل الالتقاط.
هنا، يمكنك تحليل معاينة الكاميرا في الوقت الفعلي لتنفيذ إجراءات مثل رصد العناصر أو يمكنك تحسين المعاينة نفسها.
والثاني هو ما بعد الالتقاط.
بعد التقاط الصورة، يمكنك تحسين جودتها أو تنفيذ إجراءات مثل إنشاء وصف لها.
والآن، لنتعرّف على بعض حلول تعلُّم الآلة على الجهاز التي نوفّرها لتطبيقات الكاميرا.
أول مكتبة لتعلُّم الآلة على الجهاز فقط سنتحدث عنها هي "حزمة تعلّم الآلة".
تم إنشاء "حزمة تعلّم الآلة" في الأساس لحلّ المشاكل الشائعة في مجالات الرؤية واللغة الطبيعية، مثل التعرّف على النص والوجوه وترجمة اللغة.
في العام الماضي، طرحنا حزمة تعلّم الآلة.
في ما يلي بعض الأمثلة على واجهات حزمة تعلّم الآلة Vision 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نعم، مكتبة تعلُّم الآلة التالية على الجهاز هي MediaPipe.
تقدّم MediaPipe مجموعة واسعة من حلول تعلُّم الآلة لتطبيقاتك.
يمكنك زيارة موقع MediaPipe الإلكتروني بسرعة لتجربة نماذج مختلفة، مثل التعرّف على الوجوه ورصد العناصر وتقسيم الصور وغير ذلك الكثير.
جميع حالات الاستخدام هذه تشبه إلى حد كبير حزمة تعلّم الآلة، فما هي المكتبة التي يجب استخدامها؟
على الرغم من أنّ MediaPipe و"حزمة تعلّم الآلة" يحلّان مشاكل مشابهة، هناك بعض الاختلافات الرئيسية بينهما.
أولاً، دعم المنصات.
تعمل MediaPipe على Android وiOS والويب وPython، بينما تركّز "حزمة تعلّم الآلة" على Android وiOS فقط.
بعد ذلك، يتيح MediaPipe تخصيص النماذج، بينما توفّر "حزمة تعلّم الآلة" واجهات برمجة تطبيقات جاهزة للاستخدام.
وأخيرًا، في حين أنّ MediaPipe مفتوح المصدر، فإنّ "حزمة تعلّم الآلة" هي حزمة تطوير برامج (SDK) من Google.
في ما يلي مثال على التعرّف على الإيماءات باستخدام واجهات برمجة التطبيقات 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 وحوِّله إلى صورة نقطية، واستخدِم الصورة النقطية لإنشاء عنصر mpImage في MediaPipe.
أخيرًا، عليك تقديم عنصر MediaPipe mpImage إلى طلب recognizeAsync لرصد الإيماءات في الوقت الفعلي.
بعد ذلك، لدينا ميزة "تحسين الصور والفيديوهات على الجهاز" المتاحة من خلال واجهة برمجة التطبيقات لـ "خدمات Google Play".
يوفّر هذا النموذج إمكانات مثل تعيين الدرجات اللونية وإزالة التمويه ودقة سوبر على معظم الأجهزة المميّزة.
في ما يلي مثال على صورة تم ربط درجات ألوانها وتحسين دقتها باستخدام هذا النموذج.
يمكنك رؤية الألوان الأكثر ثراءً في السماء والبحر بوضوح وتحسين الحدة في الصورة المحسّنة.
لنلقِ نظرة على الرمز البرمجي لميزة "تحسين الصور".
// 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"، ثم حدِّد دالة رد اتصال لمعالجة الصورة النقطية الناتجة، وأخيرًا استدعِ دالة المعالجة باستخدام الصورة النقطية والخيارات لتحسين صورتك.
// 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)
}لقد دمجنا مؤخرًا واجهات برمجة تطبيقات تحسين الفيديو في Jetpack Media3.
يمكنك الاطّلاع على جلستنا حول الوسائط لمعرفة المزيد عن واجهتَي Image Enhancement API وVideo Enhancement API.
آخر ميزة سنتناولها من ميزات تعلُّم الآلة على الجهاز فقط هي "تحسين الإضاءة المنخفضة".
يواجه مستخدمو الهواتف الذكية مشكلة شائعة عند التقاط الصور في الإضاءة المنخفضة.
تخيَّل مستخدمين يلتقطون صورًا أو يجرون مكالمات فيديو في بيئة ذات إضاءة خافتة.
تحلّ ميزة "تحسين الإضاءة المنخفضة" هذه المشكلة من خلال زيادة سطوع معاينة الكاميرا في الوقت الفعلي.
هناك خياران لميزة "تحسين الإضاءة المنخفضة": الأجهزة والبرامج.
يستخدم إصدار الأجهزة أجهزة المصنّع الأصلي للجهاز لتحسين اللقطات ذات الإضاءة الخافتة بدون استخدام تعلُّم الآلة.
في المقابل، يستخدم إصدار البرنامج نموذج تعلُّم الآلة على الجهاز لتحسين الإطارات.
لنلقِ نظرة سريعة على كيفية دمج CameraX مع ميزة "تحسين الإضاءة المنخفضة" في البرنامج، والمعروفة أيضًا باسم "تحسين الإضاءة المنخفضة" من Google.
تتطلّب ميزة "تحسين الإضاءة المنخفضة" من Google مساحتَي عرض رئيسيتَين: أولاً، تحتاج إلى مساحة عرض لإدخال إطارات الكاميرا الأولية ومساحة عرض مستهدَفة لإخراج الإطارات المحسّنة.
لدمج الميزة مع CameraX، ما عليك سوى تضمين مسار معالجة ميزة "تحسين الإضاءة المنخفضة" من Google داخل CameraEffect.
تتولى CameraX تلقائيًا إدارة مراحل نشاط السطح وتوجيهه بسلاسة نيابةً عنك.
أخيرًا، سنطلق تأثير "تحسين الإضاءة المنخفضة" في CameraX في الربع الرابع من عام 2026.
// 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 في العثور على أفضل مجموعة من ميزات الأجهزة.
واستكشفنا أيضًا الترقيات التي تم إجراؤها على ميزة "التقاط الصور" وتعرّفنا على ميزة "تسجيل الفيديو بسرعة عالية" لإنشاء فيديوهات بالتصوير البطيء.
أخيرًا، تعرّفنا على كيفية تحسين معاينة ميزة "الكاميرا المتزامنة" ديناميكيًا.
بعد ذلك، تناولنا بعض حلول تعلُّم الآلة على الجهاز، مثل "حزمة تعلّم الآلة" و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)