Użytkownicy oczekują, że aplikacje innych firm będą robić zdjęcia i nagrywać filmy z taką samą wysoką jakością jak natywny aparat urządzenia, niezależnie od warunków oświetleniowych i poziomu sprzętu. Zapewnienie tej spójności w rozszerzającym się ekosystemie Androida wiąże się z kluczowymi kwestiami architektonicznymi. Podczas tej sesji dowiesz się, jak tworzyć solidne, profesjonalne potoki przechwytywania, które maksymalizują możliwości urządzenia, a jednocześnie są niezawodne na wszystkich formatach.
Przedstawimy najnowsze osiągnięcia w CameraX i pokażemy, jak zintegrować najnowocześniejsze funkcje, takie jak zdjęcia ruchome i poprawki AI na urządzeniu, za pomocą usług Google Play, a także jak korzystać z funkcji przestrzennych za pomocą pakietu Jetpack XR SDK. Przyjrzymy się też naszej umiejętności CameraX, która pozwala wybranemu agentowi kodowania automatycznie wdrażać koncepcje z tej prezentacji i nie tylko.
CameraXViewfinder dostosowuje podgląd z aparatu do różnych formatów, np. urządzeń składanych. W CameraX 1.7 dodano sterowanie gestami i obsługę błysku na ekranie.ProjectedDeviceContext umożliwia CameraX robienie zdjęć bezpośrednio z okularów.Transkrypcja
Cześć!
Witamy na naszej sesji.
Nazywam się Aman, a ze mną jest Scott.
Dziś z przyjemnością opowiemy o tym, jak możesz ulepszyć działanie aparatu na różnych urządzeniach dzięki CameraX i funkcjom AI na urządzeniu.
Zaczynajmy.
Zastanów się, jak korzystasz z telefonu.
Często używamy aparatu w telefonie, aby uwieczniać chwile i dzielić się nimi z bliskimi.
Aparat stał się kluczowym elementem smartfona.
W rzeczywistości dobry aparat jest często kluczowym czynnikiem przy zakupie smartfona.
Dzisiaj omówimy 2 główne tematy związane z tworzeniem wysokiej jakości funkcji aparatu.
Najpierw omówimy niektóre funkcje CameraX, a potem poznamy techniki uczenia maszynowego na urządzeniu.
Zacznijmy od CameraX.
Jetpack CameraX obsługuje aparat na Androidzie.
Oprócz CameraX mamy też Camera1 i Camera2, które są częścią platformy Androida.
Której biblioteki należy użyć?
Camera1 to oryginalny interfejs API aparatu na Androidzie.
Został on wycofany, więc nowe aplikacje powinny używać Camera2 lub CameraX.
Camera2 zastąpiła Camera1.
Jeśli więc potrzebujesz kontroli nad aparatem na niskim poziomie w przypadku złożonych zastosowań, Camera2 jest dobrym rozwiązaniem.
Wymaga to jednak samodzielnego zarządzania konfiguracjami poszczególnych urządzeń.
I wreszcie Jetpack CameraX, który bazuje na Camera2.
Zapewnia spójny interfejs API i automatycznie obsługuje konfiguracje dostosowane do konkretnych urządzeń.
Jest to zalecany wybór dla większości deweloperów.
Mamy bardzo ważną informację.
Planujemy wyłączyć Camera1. Zaczniemy od egzekwowania zasad Google Play w przyszłym roku w Androidzie 18.
Jeśli nadal używasz starszego interfejsu Camera1 API, musisz przejść na CameraX lub Camera2.
Dlaczego warto używać CameraX?
Po pierwsze, obsługuje Androida 7 i nowsze wersje, dzięki czemu jest zgodny z prawie 99% aktywnych urządzeń z Androidem.
Po drugie, jest łatwy w użyciu i udostępnia proste interfejsy API do typowych przypadków użycia, takich jak podgląd, robienie zdjęć i nagrywanie, przy mniejszej ilości kodu.
Po trzecie, zapewnia spójne działanie na różnych urządzeniach, ograniczając problemy związane z konkretnymi urządzeniami, takie jak rozciąganie podglądu, które jest dość powszechne na urządzeniach składanych i tabletach.
Upraszcza też wdrażanie zaawansowanych funkcji OEM, takich jak tryb nocny i portretowy.
Teraz chciałbym zaprosić Scotta, który opowie o niektórych funkcjach CameraX.
Scott, oddaję Ci głos.
Dziękuję, Aman.
Cześć!
Mam na imię Scott.
Dzisiaj omówimy kilka funkcji CameraX, które pozwolą Ci stworzyć profesjonalną aplikację do obsługi aparatu.
Pierwsza z nich to adaptacyjny wizjer.
Został zaprojektowany tak, aby całkowicie wyeliminować problemy z podglądem na różnych formatach i rozmiarach ekranu.
Użytkownicy mają teraz do dyspozycji różne urządzenia, w tym urządzenia składane, telefony, laptopy i urządzenia z XR.
Dlatego tak ważne jest, aby dostosować funkcje aparatu do nowych formatów.
Podgląd z kamery jest bardzo trudny do prawidłowego wyświetlenia, zwłaszcza na składanym urządzeniu z szerokim ekranem.
Na tych urządzeniach po rozłożeniu wewnętrzny aparat jest zamontowany poziomo, a tylny – pionowo.
Jeśli więc założysz, że wszystkie czujniki aparatu są zamontowane pionowo, doprowadzi to do niedopasowania przestrzennego i zniekształcenia lub bocznego przesunięcia.
CameraXViewfinder, która została zaprojektowana tak, aby całkowicie uprościć złożoność, dzięki czemu podgląd z kamery działa bezproblemowo na wszystkich formatach.
Zobaczmy, jak używać funkcji kompozycyjnej 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
)
}Najpierw deklarujesz zmienną treści surfaceRequest, która będzie przechowywać żądanie platformy.
Przepraszam.
Po drugie, w pliku LaunchedEffect ustawiasz dostawcę platformy, aby przechwytywał obiekt żądania platformy.
Następnie powiąż z cyklem życia i uruchom aparat.
Na koniec, gdy stan zostanie zaktualizowany o prawidłowe żądanie powierzchni i wywoła rekompozycję, ustawiasz żądanie bezpośrednio na CameraXViewfinder.
Tutaj możesz ustawić contentScale, aby dopasować lub przyciąć obraz, w zależności od preferencji.
I to wszystko.
W CameraX 1.7 dodaliśmy do CameraXViewfinder kilka nowych funkcji.
Obejmuje to nowe gesty, takie jak kliknięcie, aby ustawić ostrość, lub ściągnięcie palców, aby powiększyć.
Obsługuje też błysk na ekranie, który pozwala rozjaśnić selfie za pomocą ekranu.
Wdrożenie tej funkcji jest łatwe.
CameraXViewfinder(
surfaceRequest = viewModel.surfaceRequest,
contentScale = ContentScale.Fit,
isTapToFocusEnabled = true,
isPinchToZoomEnabled = true,
onScreenFlashReady =
{
imageCapture.screenFlash = it
imageCapture.flashMode = FLASH_MODE_SCREEN
}
)Wystarczy, że włączysz opcje „Kliknij, aby ustawić ostrość” i „Ściągnij palce, aby powiększyć”.
W przypadku błysku na ekranie pobierasz instancję błysku na ekranie z wywołania zwrotnego onScreenFlashReady, a następnie przekazujesz ją do ImageCapture i ustawiasz tryb lampy błyskowej na FLASH_MODE_SCREEN.
To wszystko.
Widzieliśmy, jak CameraX umożliwia podgląd z aparatu, który działa bezproblemowo na wszystkich formatach.
Teraz idziemy o krok dalej i dodajemy możliwość robienia zdjęć aparatem na Androidzie XR.
Istnieją 3 kategorie urządzeń z XR. Pierwsza to gogle XR.
Są to całkowicie zamknięte i zapewniające efekt immersji gogle, które wykorzystują aparat do rejestrowania świata fizycznego i wyświetlania go wewnątrz gogli.
Drugi to przewodowe okulary XR.
Dzisiaj podczas prezentacji w tej kategorii ogłosiliśmy wprowadzenie XREAL Aura.
Jak widać na prezentacjach, są to przezroczyste okulary połączone z jednostką obliczeniową, które umożliwiają wyświetlanie treści wirtualnych nałożonych na świat rzeczywisty.
Trzecia kategoria to okulary z funkcjami audio i wyświetlaczem.
To są lekkie okulary.
Mogą łączyć dźwięk, aparat i wyświetlacz, aby zapewnić obsługiwane bez użycia rąk i szybkie korzystanie.
Urządzenia z pierwszych 2 kategorii, czyli gogle XR i przewodowe okulary XR, obsługują też interfejsy Camera2 i CameraX, podobnie jak zwykły telefon.
Dlatego dzisiaj skupimy się tylko na dźwięku i okularach z wyświetlaczem.
W przypadku tych okularów aplikacje działają na telefonie z Androidem, a nie na okularach, ale mają dostęp zarówno do aparatu telefonu, jak i aparatu okularów.
Kluczowe pytanie brzmi: skąd wiadomo, do której kamery się mówi?
To proste.
W przypadku standardowego kontekstu CameraX uzyskuje dostęp do aparatu telefonu.
Dzięki ProjectedDeviceContext CameraX uzyskuje dostęp do kamery w okularach.
Różne konteksty będą więc prowadzić do różnych kamer docelowych.
Pamiętaj, że uprawnienia do aparatu i okularów są oddzielne.
Uprawnienia dotyczące telefonu i okularów są oddzielne, więc musisz poprosić o uprawnienia dotyczące okularów osobno.
Inną opcją jest użycie aktywności rzutowanej, która również działa na urządzeniach z Androidem, ale interfejs jest rzutowany na okulary.
W tym przypadku reguła jest odwrotna.
Jeśli używasz standardowego kontekstu, uzyskasz dostęp do kamery w okularach.
Jeśli chcesz uzyskać dostęp do aparatu telefonu, musisz użyć HostContext.
Spójrzmy na kod i zobaczmy, jak możemy robić zdjęcia za pomocą aparatu w okularach.
// 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(...)Najpierw wywołujesz isProjectedDeviceConnected, aby sprawdzić, czy okulary są połączone.
Następnie dzwonisz do createProjectedDeviceContext, aby uzyskać projectedContext.
Po drugie, do inicjowania CameraX używasz LifecycleCameraProvider zamiast ProcessCameraProvider.
ProcessCameraProvider jest singletonem, którego kontekstu nie można zmienić po inicjalizacji.
Dzięki temu LifecycleCameraProvider pozwala na zmianę kontekstu w dowolnym momencie.
Następnie musisz wyłączyć dodatkowy strumień powtarzający się w CameraX.
Jest to konieczne, ponieważ obecnie okulary obsługują tylko 1 strumień.
Interfejs API jest obecnie wewnętrzny, ale wkrótce udostępnimy go publicznie.
To wszystko.
Od tego momentu używasz standardowego interfejsu CameraX. Wystarczy powiązać ImageCapture i możesz robić zdjęcia za pomocą okularów.
OK.
Teraz zobaczmy, jak CameraX pomaga znaleźć kombinację funkcji, które ze sobą współpracują i pozwalają najlepiej wykorzystać możliwości urządzenia.
Nowoczesne aplikacje do obsługi aparatu charakteryzują się zaawansowanymi funkcjami, a użytkownicy oczekują, że aparat będzie obsługiwać takie funkcje jak HDR, 60 kl./s i stabilizacja podglądu, i to wszystkie jednocześnie.
Wiemy jednak, że połączenie tych funkcji nie zawsze jest proste.
Sprawdzenie obsługi poszczególnych funkcji nie gwarantuje, że będą one działać razem.
Ta niepewność zmusiła deweloperów do zachowania ostrożności i ograniczenia możliwości użytkowników na urządzeniach o odpowiednich parametrach.
Aby rozwiązać ten problem, wprowadziliśmy interfejs Feature Group API, który jest oparty na platformie zapytań o kombinacje funkcji Camera2.
Można go używać na 2 sposoby: po pierwsze, użyj requiredFeatureGroup, aby sprawdzić i włączyć kombinację funkcji, gdy jest obsługiwana.
Po drugie, możesz określić priorytet funkcji za pomocą preferredFeatureGroup, a następnie CameraX wybierze dla Ciebie najlepszą obsługiwaną kombinację.
Oto przykład.
Po prawej, po lewej, przepraszam, bez włączania niczego, zwykły telefon, zwykłe nagrywanie.
Po lewej stronie włączamy jednocześnie HDR, 60 kl./s i stabilizację podglądu.
Możesz zauważyć, że jakość filmu jest znacznie lepsza, ponieważ jest on jaśniejszy, płynniejszy i nie ma drgań.
Przyjrzyjmy się kodowi.
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
SessionConfig(
useCases = listOf(preview, videoCapture),
// HDR_HLG10 > FPS_60 > Preview Stabilization.
preferredFeatureGroup =
listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
)
)W tym przypadku używamy preferredFeatureGroup, aby podać listę priorytetów funkcji, czyli HDR, 60 kl./s i stabilizację podglądu.
CameraX sprawdzi kombinację i wybierze najlepsze opcje obsługiwane przez urządzenie.
W CameraX 1.6 i nowszych wersjach dodajemy nagrywanie w 4K i stabilizację obrazu bezpośrednio do interfejsu 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)
)Połączenie rozdzielczości 4K i stabilizacji wideo jest obsługiwane przez wiele urządzeń premium.
Teraz dzięki interfejsowi Feature Group API możesz bez obaw włączyć je razem.
Dodatkowo możesz teraz stosować efekt w czasie rzeczywistym za pomocą interfejsu Feature Group API, co wcześniej nie było możliwe.
Porozmawiajmy teraz o tym, jak robić zdjęcia wysokiej jakości za pomocą CameraX.
CameraX obsługuje teraz zapisywanie zdjęć w formacie DNG RAW.
Zapewnia dostęp do nieprzetworzonego, nieskompresowanego obrazu RAW bezpośrednio z matrycy aparatu, co umożliwia profesjonalną edycję.
Ulepszyliśmy też rozszerzenie Tryb nocny.
To rozszerzenie umożliwia robienie jasnych i wyraźnych zdjęć przy słabym oświetleniu bez użycia lampy błyskowej.
Wprowadziliśmy też nowy interfejs API wskaźnika trybu nocnego.
Będzie on sprawdzać, czy w obecnym otoczeniu jest ciemno i czy można włączyć tryb nocny. Możesz go używać do wyświetlania wskaźnika trybu nocnego.
Ultra HDR jest teraz obsługiwany w rozszerzeniach CameraX.
Zapewnia to szerszy zakres dynamiki i żywe kolory w trybie nocnym. Tryb nocny z Ultra HDR jest obecnie obsługiwany na wielu najnowszych urządzeniach premium.
Tutaj możesz zobaczyć różnicę między trybem normalnym, nocnym i nocnym z Ultra HDR.
Zobaczmy, jak to zrobić.
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)Najpierw utwórz ExtensionSessionConfig dla trybu nocnego i użyj go, aby uzyskać instancję CameraInfo.
Następnie używasz CameraInfo, aby uzyskać możliwość robienia zdjęć, a potem sprawdzasz, czy obsługiwany jest tryb Ultra HDR.
Następnie ustaw format wyjściowy na Ultra HDR.
Na koniec ponownie tworzysz ExtensionSessionConfig z przypadkami użycia i powiązujesz go z cyklem życia.
Teraz możesz robić zdjęcia w trybie nocnym w formacie ultra HDR.
Wprowadzamy też obsługę zdjęć ruchomych w CameraX.
Zdjęcia ruchome rejestrują dźwięk i ruch wokół zdjęcia, dzięki czemu uwieczniają chwilę, a nie tylko nieruchomy obraz.
Ujednolicamy tę funkcję na różnych telefonach i urządzeniach z Androidem, aby ułatwić obsługę zdjęć ruchomych.
Zdjęcia ruchome będą dostępne w CameraX w I kwartale 2027 r.
Gdy funkcja będzie dostępna, po prostu ustaw tryb zdjęć ruchomych w aplikacji ImageCapture.Builder i zacznij robić zdjęcia ruchome.
val imageCapture = ImageCapture.Builder()
.setMotionPhotoMode(
ImageCapture.MOTION_PHOTO_MODE_ON,
customConfig
)
.build()
cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)Teraz przyjrzyjmy się, jak CameraX umożliwia nagrywanie filmów z dużą szybkością.
Wyraźne nagrywanie dynamicznych scen to podstawowa funkcja nowoczesnych aplikacji do obsługi aparatu.
Jest to szczególnie przydatne w przypadku dynamicznych scen, takich jak wydarzenia sportowe.
Jest to możliwe dzięki szybkiemu nagrywaniu z prędkością 120 lub 240 kl./s.
Możesz go używać na 2 sposoby.
Najpierw kodowanie z tą samą liczbą klatek, a następnie utworzenie filmu z prawdziwie wysoką liczbą klatek.
Inną opcją jest zakodowanie go przy 30 klatkach na sekundę, aby utworzyć gotowy do odtworzenia film w zwolnionym tempie.
Na przykład 1 sekunda nagrana przy 120 kl./s będzie odtwarzana przez 4 sekundy przy 30 kl./s.
Dzięki temu ten fragment filmu jest odtwarzany w zwolnionym tempie.
Aby wdrożyć szybkie nagrywanie, utwórz HighSpeedVideoSessionConfig za pomocą 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, {})Następnie ustawiasz wartość isSlowMotionEnabled na „true”, aby zakodować film z szybkością 30 kl./s i utworzyć film w zwolnionym tempie, lub na „false”, aby utworzyć film z dużą liczbą klatek na sekundę.
Gdy włączony jest tryb zwolnionego tempa, Zdjęcia Google automatycznie go rozpoznają i dodają plakietkę zwolnionego tempa do miniatury.
Zobaczmy, jak to działa w Zdjęciach Google.
W tym przypadku zwolnione tempo nadaje kotu dramatyczny efekt.
Mój kot nigdy nie chce, żebym go dotykał.
Przykro mi.
Na koniec przyjrzyjmy się ulepszeniom w interfejsie Concurrent Camera API.
Interfejs API Concurrent umożliwia strumieniowe przesyłanie obrazu z przedniego i tylnego aparatu jednocześnie.
W tym roku wprowadziliśmy pewne ulepszenia w interfejsie Concurrent Camera API.
Po powiązaniu z cyklem życia i otwarciu kamery możesz dynamicznie aktualizować kompozycję kamery.
Obejmuje to zmianę pozycji, rozmiaru i kolejności renderowania.
Możesz też dodać style, takie jak zaokrąglony róg, szerokość obramowania i kolor obramowania.
Aby to zrobić, użyj CompositionSettings do skonfigurowania stylu, np. zaokrąglenia rogów, szerokości obramowania i koloru, oraz setZOrder do dostosowania kolejności renderowania.
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))Następnie wystarczy wywołać setCompositionSettings na aktywnej kamerze równoczesnej, aby dynamicznie aktualizować kompozycję.
To wszystko.
Możesz teraz dynamicznie aktualizować kompozycję.
OK.
Omówiliśmy już wszystkie funkcje.
Mamy tu ważną informację, dlatego zdecydowanie zalecamy przejście na CameraX 1.6 lub nowszą wersję.
Dzięki temu unikniesz awarii na nadchodzących urządzeniach, które obsługują nowy format Eclipsa Video, a rozszerzenie CameraX będzie nadal działać w przyszłej aktualizacji Pixela.
To już koniec pierwszej sesji.
Omówiliśmy funkcje CameraX, które pomagają tworzyć profesjonalne funkcje aparatu. Teraz zapraszam Amana, który poprowadzi kolejną sesję na temat AI na urządzeniu.
Dziękuję, Scott, za omówienie funkcji CameraX.
Teraz przyjrzyjmy się, jak możesz wykorzystać uczenie maszynowe na urządzeniu do tworzenia inteligentnych funkcji aparatu.
Uczenie maszynowe na urządzeniu umożliwia szeroki zakres zastosowań aparatu w 2 głównych obszarach.
Pierwsza to wstępne przechwytywanie.
Możesz tu analizować podgląd z aparatu w czasie rzeczywistym, aby np. wykrywać obiekty, lub ulepszać sam podgląd.
Drugi to etap po zrobieniu zdjęcia.
Po zrobieniu zdjęcia możesz poprawić jego jakość lub wykonać inne czynności, np. wygenerować opis.
Teraz przyjrzyjmy się niektórym rozwiązaniom uczenia maszynowego na urządzeniu, które oferujemy w aplikacjach do obsługi aparatu.
Pierwszą biblioteką ML na urządzeniu, którą omówimy, jest ML Kit.
Zestaw ML Kit został stworzony przede wszystkim do rozwiązywania typowych problemów związanych z rozpoznawaniem obrazów i językiem naturalnym, takich jak rozpoznawanie tekstu, wykrywanie twarzy i tłumaczenie.
W zeszłym roku wprowadziliśmy ML Kit.
Oto kilka przykładów interfejsów ML Kit Vision API, takich jak wykrywanie siatki twarzy, segmentacja obiektów i wykrywanie obiektów w czasie rzeczywistym.
Tak.
W zeszłym roku wprowadziliśmy interfejsy API generatywnej AI.
Te interfejsy API wykorzystują możliwości Gemini Nano, aby umożliwić m.in. podsumowywanie tekstu i opisywanie obrazów.
Oto przykład opisu obrazu za pomocą interfejsu Prompt API.
Przekazujemy do modelu bitmapę obrazu z promptem tekstowym, a wynikowy opis brzmi: „Mały zielony robot Android o wyglądzie kaktusa siedzi na czarnej powierzchni”.
Wszystko to odbywa się na urządzeniu.
Całkiem nieźle, prawda?
Adobe Photoshop używa interfejsu Prompt API, aby poprawić wrażenia użytkowników w aplikacji.
Podczas edytowania komponentów użytkownicy często muszą zarządzać wieloma warstwami w aplikacji Photoshop.
Dzięki interfejsowi Prompt API aplikacja może teraz automatycznie zmieniać nazwy nieuporządkowanych warstw.
Aplikacja używa też interfejsu Prompt API do generowania podpisów i hashtagów do obrazów, co jest bardzo przydatne podczas udostępniania w mediach społecznościowych.
Przyjrzyjmy się kodowi opisu obrazu za pomocą interfejsu 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 -> {}
}Najpierw potrzebujesz obiektu klienta generativeModel, a następnie sprawdź, czy Gemini Nano jest dostępny lub czy można go pobrać na urządzenie.
Następnie wygeneruj prośbę o treść, łącząc obraz i prompt tekstowy, a na koniec prześlij tę prośbę do wywołania generateContent, aby przeprowadzić wnioskowanie w Gemini Nano i uzyskać opis obrazu.
// 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().textTak, kolejną biblioteką ML na urządzeniu jest MediaPipe.
MediaPipe oferuje szeroką gamę rozwiązań uczenia maszynowego dla Twoich aplikacji.
Możesz szybko odwiedzić witrynę MediaPipe, aby wypróbować różne przykłady, takie jak wykrywanie twarzy, wykrywanie obiektów, segmentacja obrazu i inne.
Wszystkie te przypadki użycia brzmią bardzo podobnie do ML Kit, więc której biblioteki należy użyć?
MediaPipe i ML Kit rozwiązują podobne problemy, ale istnieją między nimi pewne kluczowe różnice.
Pierwsza z nich to obsługa platform.
MediaPipe działa na Androidzie, iOS, w internecie i Pythonie, natomiast ML Kit koncentruje się tylko na Androidzie i iOS.
MediaPipe oferuje dostosowywanie modeli, a ML Kit – gotowe do użycia interfejsy API.
I wreszcie: MediaPipe jest rozwiązaniem typu open source, a ML Kit to pakiet SDK Google.
Oto przykład rozpoznawania gestów za pomocą interfejsów MediaPipe Vision API.
Ten interfejs API wykrywa gesty dłoni w czasie rzeczywistym na podstawie obrazu z kamery.
Można na przykład rozpocząć lub zatrzymać nagrywanie za pomocą prostych gestów dłoni, stojąc w pewnej odległości od telefonu.
Przyjrzyjmy się kodowi.
Najpierw musisz utworzyć obiekt GestureRecognizerOptions, podając ścieżkę modelu, tryb transmitowania na żywo i detektor wyników.
// 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)Następnie utwórz obiekt rozpoznawania gestów na podstawie opcji.
Następnie pobierz ImageProxy CameraX i przekonwertuj go na bitmapę, a potem użyj bitmapy do wygenerowania obiektu MediaPipe mpImage.
Na koniec musisz podać obiekt MediaPipe mpImage w wywołaniu recognizeAsync, aby wykrywać gesty w czasie rzeczywistym.
Następnie mamy ulepszanie obrazów i filmów na urządzeniu dostępne za pomocą interfejsu API Usług Google Play.
Ten model zapewnia funkcje takie jak mapowanie tonów, usuwanie rozmycia i superrozdzielczość na większości urządzeń premium.
Oto przykład obrazu, którego mapowanie tonów i skalowanie zostało wykonane za pomocą tego modelu.
Na ulepszonym obrazie wyraźnie widać bogatsze kolory nieba i morza oraz lepszą ostrość.
Przyjrzyjmy się kodowi ulepszania obrazu.
// 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
}Najpierw musisz sprawdzić, czy urządzenie obsługuje tę funkcję, a jeśli to konieczne, zainstalować na nim moduł ulepszeń.
Następnie utwórz obiekt EnhancementOptions, określając bitmapę i ulepszenia, które chcesz zastosować.
// 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)Następnie utwórz sesję klienta za pomocą interfejsu API usług Google Play, zdefiniuj wywołanie zwrotne do obsługi wynikowej bitmapy i wywołaj funkcję procesu z bitmapą i opcjami ulepszania obrazu.
// 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)
}Niedawno zintegrowaliśmy interfejsy API ulepszenia filmu z Jetpack Media3.
Możesz obejrzeć naszą prezentację, aby dowiedzieć się więcej o interfejsach Image Enhancement API i Video Enhancement API.
Ostatnią funkcją uczenia maszynowego na urządzeniu, którą omówimy, jest doświetlanie przy słabym oświetleniu.
Robienie zdjęć przy słabym oświetleniu to częsty problem użytkowników smartfonów.
Wyobraź sobie użytkowników robiących zdjęcia lub prowadzących rozmowy wideo w słabo oświetlonym otoczeniu.
Funkcja ulepszania obrazu przy słabym oświetleniu rozwiązuje ten problem, rozjaśniając podgląd z kamery w czasie rzeczywistym.
Dostępne są 2 warianty doświetlania przy słabym oświetleniu: sprzętowy i programowy.
Wersja sprzętowa wykorzystuje sprzęt OEM do ulepszania słabo oświetlonych klatek bez użycia uczenia maszynowego.
Wersja oprogramowania wykorzystuje natomiast model ML na urządzeniu do ulepszania klatek.
Przyjrzyjmy się szybko, jak możemy zintegrować CameraX z oprogramowaniem doświetlającym przy słabym oświetleniu, które jest też znane jako doświetlanie przy słabym oświetleniu od Google.
Google Low Light Boost wymaga 2 kluczowych powierzchni: wejściowej dla nieprzetworzonych klatek z aparatu i wyjściowej dla ulepszonych klatek.
Aby zintegrować tę funkcję z CameraX, wystarczy umieścić potok ulepszonego oświetlenia Google w CameraEffect.
CameraX automatycznie i płynnie obsługuje cykl życia i routing powierzchni.
W IV kwartale 2026 r. wprowadzimy też w CameraX efekt doświetlania przy słabym oświetleniu.
// 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)Dzięki temu możesz utworzyć obiekt Low Light Boost i dodać go do konfiguracji sesji, a następnie powiązać konfigurację sesji z cyklem życia aparatu. Będzie to działać na obsługiwanych urządzeniach.
Na tym kończymy obie sekcje.
Podsumujmy krótko, co dziś omówiliśmy.
Dzisiaj omówiliśmy, jak możesz ulepszyć działanie aparatu na różnych urządzeniach dzięki CameraX i uczeniu maszynowemu na urządzeniu.
Po pierwsze, dzięki CameraXViewfinder możesz włączyć podgląd z kamery, który dostosowuje się do różnych formatów.
Następnie dowiedzieliśmy się, że CameraX umożliwia też nagrywanie na urządzeniach z XR.
Następnie interfejs Feature Group API pomaga znaleźć optymalną kombinację funkcji sprzętowych.
Omówiliśmy też ulepszenia funkcji robienia zdjęć i dowiedzieliśmy się o nagrywaniu filmów z dużą szybkością, które umożliwia tworzenie filmów w zwolnionym tempie.
Na koniec dowiedzieliśmy się, jak dynamicznie ulepszać podgląd z jednoczesnych kamer.
Następnie przyjrzeliśmy się niektórym rozwiązaniom uczenia maszynowego na urządzeniu, takim jak ML Kit i MediaPipe, które umożliwiają tworzenie inteligentnych funkcji wizualnych.
Na koniec zobaczyliśmy ulepszanie obrazów i ulepszenie filmów na urządzeniu oraz funkcję rozjaśniania słabego oświetlenia, która rozjaśnia podgląd z aparatu w czasie rzeczywistym.
Jak wspomnieliśmy wcześniej, planujemy wyłączyć Camera1, dlatego zachęcamy do przejścia na CameraX.
Aby ułatwić tę migrację, niedawno wprowadziliśmy umiejętność CameraX.
Możesz też skorzystać z przewodnika po migracji i repozytorium z przykładami dotyczącymi aparatu.
Kliknij ten link, aby uzyskać dostęp do dodatkowych materiałów na temat funkcji, które dziś omówiliśmy.
Zawiera linki do dokumentacji, bloga i zasobów dotyczących migracji – wszystko w jednym miejscu.
Zrobię sobie przerwę i odpocznę.
OK.
Dziękuję wszystkim.
Na tym kończymy naszą prezentację.
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)