使用者期望第三方應用程式能像裝置的原生相機一樣,無論光線條件或硬體等級如何,都能拍出高畫質的相片和影片。為了在不斷擴大的 Android 生態系統中提供這種一致性,我們必須考量重要的架構。本課程將示範如何建構強大、專業的擷取管道,充分發揮裝置功能,同時在所有板型規格上保持穩定。
我們將探討 CameraX 的最新進展,說明如何透過 Google Play 服務整合動態相片和裝置端 AI 美化功能等先進功能,以及如何使用 Jetpack XR SDK 打造空間體驗。我們也會介紹 CameraX 技能,讓您選擇的程式設計代理自動實作本講座中的概念,以及更多內容!
CameraXViewfinder 會根據摺疊式裝置等不同板型規格調整相機預覽畫面。CameraX 1.7 新增手勢控制和螢幕閃光燈支援。ProjectedDeviceContext 可讓 CameraX 直接從眼鏡擷取圖片。轉錄稿
大家好。
歡迎參加本課程。
我是 Aman,旁邊是 Scott。
今天,我們很高興能與大家分享,如何運用 CameraX 和裝置端智慧功能,提升跨裝置的相機體驗。
現在就開始吧。
想想你如何使用手機。
我們經常使用手機相機捕捉精彩時刻,並與親友分享。
相機已成為智慧型手機體驗的核心。
事實上,相機品質往往是購買智慧型手機時的關鍵考量因素。
今天,我們將探討打造優質相機體驗的兩大主題。
首先,我們會探索一些 CameraX 功能,然後瞭解一些裝置端機器學習技術。
首先介紹 CameraX。
Jetpack CameraX 支援 Android 裝置的相機體驗。
除了 CameraX 之外,我們也提供 Camera1 和 Camera2,這兩者是 Android 平台架構的一部分。
那麼,您應該使用哪個程式庫?
Camera1 是 Android 上的原始相機 API。
這個 API 現已淘汰,因此新應用程式應使用 Camera2 或 CameraX。
Camera2 繼承了 Camera1。
因此,如果您需要低階相機控制功能來處理複雜用途,Camera2 是不錯的選擇。
不過,您必須自行處理裝置專屬設定。
最後,Jetpack CameraX 是以 Camera2 為基礎建構而成。
提供一致的 API,並自動處理裝置專屬設定。
這是大多數開發人員的建議選擇。
我們有非常重要的事項要提醒。
我們預計停用 Camera1,並從明年 Android 18 的 Play 政策違規處置開始。
如果您仍在使用舊版 Camera1 API,則必須遷移至 CameraX 或 Camera2。
那麼,為什麼要使用 CameraX 呢?
首先,它支援 Android 7 以上版本,因此與近 99% 的使用中 Android 裝置相容。
第二,CameraX 容易使用,並提供簡單的 API,可透過較少的程式碼,處理預覽、擷取和錄製等常見用途。
第三,它能減少裝置特有的問題 (例如預覽畫面拉伸,這在摺疊式裝置和平板電腦上相當常見),在不同裝置上提供一致的體驗。
最後,這項功能可簡化夜間模式和肖像模式等進階 OEM 功能的導入作業。
現在,我想邀請 Scott 介紹一些 CameraX 功能。
現在將時間交給 Scott。
謝謝 Aman。
大家好。
我是 Scott。
今天,我們將介紹幾項 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 內容變數,用於保存介面要求。
抱歉。
第二,在 LaunchedEffect 中,您將介面供應器設為擷取介面要求物件。
接著繫結至生命週期,然後啟動相機。
最後,每當狀態更新為有效的介面要求並觸發重組時,您會直接將要求設為 CameraXViewfinder。
你可以在這裡根據偏好,將 contentScale 設為「符合」或「裁剪」。
這樣就大功告成了。
在 CameraX 1.7 中,我們為 CameraXViewfinder 新增了幾項功能。
包括輕觸對焦或雙指撥動縮放等新手勢。
這款手機也支援螢幕閃光燈,可讓您使用螢幕調亮自拍照。
這項功能很容易實作。
CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)只要將「輕觸對焦」和「雙指撥動縮放」設為「true」即可。
如果是螢幕閃光燈,請從 onScreenFlashReady 回呼取得螢幕閃光燈例項,然後傳遞至 ImageCapture,並將閃光燈模式設為 FLASH_MODE_SCREEN。
就是這麼簡單
我們已瞭解 CameraX 如何讓相機預覽功能在所有板型規格上順暢運作。
現在,我們來進一步瞭解 Android XR 的相機拍攝功能。
XR 裝置分為三類:第一類是 XR 頭戴式裝置。
這類頭戴式裝置完全封閉,可提供沉浸式體驗,並使用相機擷取實體世界,在頭戴式裝置內顯示。
第二個是有線 XR 眼鏡。
我們今天在主題演講中,宣布推出 XREAL Aura。
正如主題演講所述,這款透視眼鏡連接到運算圓盤,可讓您查看疊加在真實世界上的虛擬內容。
第三種是音訊和顯示型眼鏡。
這款眼鏡輕巧
這類裝置可結合音訊、攝影機和螢幕,提供免持和一目瞭然的體驗。
前兩類裝置 (XR 頭戴式裝置和有線 XR 眼鏡) 也支援 Camera2 和 CameraX,就像一般手機一樣。
因此,今天我們只會著重介紹音訊和顯示型眼鏡。
在這類眼鏡上,應用程式是在 Android 手機 (主機 Android 手機) 上執行,而非眼鏡,但可同時存取手機相機和眼鏡相機。
關鍵問題是,如何知道你正在與哪部攝影機通話?
所以很簡單。
使用標準 Context 時,CameraX 會存取手機相機。
CameraX 則會透過 ProjectedDeviceContext 存取眼鏡攝影機。
因此,不同的 Context 會導致不同的目標攝影機。
請注意,相機和眼鏡權限是分開的。
手機和眼鏡的權限是分開的,因此您需要另外要求眼鏡權限。
另一個選項是使用投影活動,這項活動也會在主機 Android 裝置上執行,但 UI 會投影到眼鏡上。
這裡的規則會反轉。
如果使用標準 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 是單例模式,初始化後無法變更 Context。
因此,使用 LifecycleCameraProvider 可讓您隨時變更 Context。
接著,您需要在 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 會檢查組合,並選取裝置支援的最佳組合。
CameraX 1.6 以上版本將 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 圖片,進行專業編輯。
接下來,我們改良了夜間模式擴充功能。
這項擴充功能可在低光源環境下,不使用閃光燈也能拍出明亮清晰的相片。
我們也推出了新的夜間模式指標 API。
這項功能會監控目前環境是否昏暗,以及是否適合使用夜間模式,並顯示夜間模式指標。
CameraX 擴充功能現在也支援 Ultra HDR。
這項功能可為夜間模式帶來更寬廣的動態範圍和鮮豔色彩,目前許多近期推出的高階裝置都支援 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 年第 1 季推出動態相片功能。
這項功能推出後,只要在 ImageCapture.Builder 中設定動態相片模式,就能開始拍攝動態相片。
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)接下來,我們來看看 CameraX 如何實現高速錄影。
清晰錄製快速移動的動作,是現代相機應用程式不可或缺的功能。
特別適合用於體育賽事等節奏明快的場景。
方法是以 120 或 240 fps 的速度高速拍攝。
使用方法有兩種。
首先,以相同影格率編碼,然後建立真正的超高影格率影片。
另一個選項是以 30 fps 編碼,建立可直接播放的慢動作影片。
舉例來說,以 120 fps 錄製的一秒影片,在 30 fps 下播放需要四秒。
因此,這段時間會產生慢動作效果。
如要實作高速錄製,請使用 videoCapture 建立 HighSpeedVideoSessionConfig。
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 fps 的速度編碼,建立慢動作影片;或設為 false,建立高影格率影片。
啟用慢動作功能後,Google 相簿會自動辨識,並在縮圖上加上慢動作徽章。
接著來看看這項功能在 Google 相簿中的運作方式。
這裡的慢動作效果讓貓咪看起來更戲劇化。
我的貓從不讓我碰牠。
很抱歉。
最後,來看看 Concurrent Camera API 這項並行 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 的功能,可協助您打造專業級的相機體驗。現在,我想邀請 Aman 介紹下一場關於裝置端 AI 的課程。
感謝 Scott 為我們介紹 CameraX 功能。
接著,我們來看看如何運用裝置端機器學習技術,打造智慧型相機體驗。
裝置端機器學習技術可支援多種相機用途,主要分為兩大領域。
首先是預先拍攝。
您可以在這裡即時分析相機預覽畫面,執行物件偵測等作業,也可以強化預覽畫面本身。
第二個是拍攝後。
擷取圖片後,您可以提升圖片品質,或生成圖片說明等。
接著,我們來深入探討一些適用於相機應用程式的裝置端機器學習解決方案。
我們要介紹的第一個裝置端機器學習程式庫是 ML Kit。
ML Kit 主要用於解決視覺和自然語言領域的常見問題,例如文字辨識、臉部偵測和語言翻譯。
去年,我們推出了 ML Kit。
以下是 ML Kit Vision API 的一些範例,例如臉部網格偵測、主體區隔和即時物件偵測。
是。
去年我們推出了 GenAI API。
這些 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 網站,試用臉部偵測、物件偵測、影像分割等各種範例。
這些用途聽起來都與 ML Kit 非常相似,那麼您應該使用哪個程式庫呢?
雖然 MediaPipe 和 ML Kit 解決的問題類似,但兩者有幾項重要差異。
首先是平台支援。
MediaPipe 適用於 Android、iOS、網頁和 Python,而 ML Kit 僅適用於 Android 和 iOS。
其次,MediaPipe 提供模型自訂功能,而 ML Kit 則提供立即可用的 API。
最後,MediaPipe 是開放原始碼,ML Kit 則是 Google SDK。
以下是使用 MediaPipe Vision API 辨識手勢的範例。
這個 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)接著,從選項中建立手勢辨識器物件。
接著,取得 CameraX ImageProxy 並轉換為點陣圖,然後使用點陣圖產生 MediaPipe mpImage 物件。
最後,您需要將 MediaPipe mpImage 物件提供給 recognizeAsync 呼叫,以便即時偵測手勢。
接下來,我們將透過 Google Play 服務 API 提供裝置端圖片和影片強化功能。
在大多數高階裝置上,這個模型可提供色調對應、去模糊和超高解析度等功能。
以下是使用這個模型進行色調映射和放大後的圖片範例。
你可以清楚看到,強化後的圖片中,天空和海洋的色彩更豐富,銳利度也更高。
接著來看看圖片強化功能的程式碼。
// 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 服務 API 建立用戶端工作階段,然後定義回呼來處理產生的點陣圖,最後使用點陣圖和選項呼叫處理函式,美化圖片。
// 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。
您可以觀看媒體講座,進一步瞭解 Image and Video Enhancement API。
最後要介紹的裝置端機器學習功能是低光源增強。
在低光源環境下拍照,是智慧型手機使用者常遇到的問題。
例如使用者在昏暗環境中拍照或進行視訊通話。
低光源強化功能可即時調亮相機預覽畫面,解決這個問題。
低光源增強功能分為硬體和軟體兩種。
硬體版本會使用原始設備製造商 (OEM) 硬體,在不使用機器學習技術的情況下,強化昏暗的影格。
相較之下,軟體版本則使用裝置端機器學習模型來強化影格。
接下來,我們快速瞭解如何將 CameraX 與軟體低光源增強功能 (也稱為 Google 低光源增強功能) 整合。
Google 低光源強化功能需要兩個主要介面:首先,您需要一個用於原始相機影格的輸入介面,以及一個用於強化輸出影格的輸出目標介面。
如要與 CameraX 整合,只要將 Google 低光源強化管道包裝在 CameraEffect 中即可。
CameraX 會自動處理途徑生命週期,並順暢地為您轉送。
最後,我們將在 2026 年第 4 季推出 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)