Je travaille sur une application Android qui traite l'image d'entrée de la caméra et l'affiche à l'utilisateur. C'est assez simple, j'enregistre un PreviewCallback
sur la caméra avec le setPreviewCallbackWithBuffer
. C'est facile et fonctionne bien avec l'ancienne API de la caméra
public void onPreviewFrame(byte[] data, Camera cam) {
// custom image data processing
}
J'essaie de porter mon application pour profiter de la nouvelle API Camera2 et je ne sais pas exactement comment je dois le faire. J'ai suivi les échantillons Camera2Video en L Preview qui permettent d'enregistrer une vidéo. Cependant, il n'y a pas de transfert direct de données d'image dans l'échantillon, donc je ne comprends pas exactement où dois-je obtenir les données de pixel d'image et comment les traiter.
Quelqu'un pourrait-il m'aider ou suggérer comment obtenir les fonctionnalités de PreviewCallback
dans Android L, ou comment il est possible de traiter les données d'aperçu de la caméra avant de les afficher sur l'écran? (il n'y a pas de rappel de prévisualisation sur l'objet caméra)
Merci!
Depuis le Camera2
L'API est très différente de l'API Camera
actuelle, il pourrait être utile de parcourir la documentation.
Un bon point de départ est camera2basic
Exemple. Il montre comment utiliser Camera2
API et configurez ImageReader
pour obtenir des images JPEG et vous inscrire ImageReader.OnImageAvailableListener
pour recevoir ces images
Pour recevoir des images d'aperçu, vous devez ajouter la surface de votre ImageReader
à setRepeatingRequest
's CaptureRequest.Builder
.
Vous devez également définir le format de ImageReader
sur YUV_420_888
, qui vous donnera 30fps à 8MP (La documentation garantit 30fps à 8MP pour Nexus 5).
Combiner quelques réponses en une réponse plus digeste car la réponse de @ VP, bien que techniquement claire, est difficile à comprendre si c'est la première fois que vous passez d'une caméra à une caméra2:
En utilisant https://github.com/googlesamples/Android-Camera2Basic comme point de départ, modifiez ce qui suit:
Dans createCameraPreviewSession()
initiez un nouveau Surface
à partir de mImageReader
Surface mImageSurface = mImageReader.getSurface();
Ajoutez cette nouvelle surface comme cible de sortie de votre variable CaptureRequest.Builder
. À l'aide de l'exemple Camera2Basic, la variable sera mPreviewRequestBuilder
mPreviewRequestBuilder.addTarget(mImageSurface);
Voici l'extrait avec les nouvelles lignes (voir mes commentaires @AngeloS):
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
//@AngeloS - Our new output surface for preview frame data
Surface mImageSurface = mImageReader.getSurface();
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//@AngeloS - Add the new target to our CaptureRequest.Builder
mPreviewRequestBuilder.addTarget(mImageSurface);
mPreviewRequestBuilder.addTarget(surface);
...
Ensuite, dans setUpCameraOutputs()
, changez le format de ImageFormat.JPEG
En ImageFormat.YUV_420_888
Lorsque vous lancez votre ImageReader
. (PS, je recommande également de supprimer la taille de votre aperçu pour un fonctionnement plus fluide - une fonctionnalité intéressante de Camera2)
mImageReader = ImageReader.newInstance(largest.getWidth() / 16, largest.getHeight() / 16, ImageFormat.YUV_420_888, 2);
Enfin, dans votre méthode onImageAvailable()
de ImageReader.OnImageAvailableListener
, Assurez-vous d'utiliser la suggestion de @ Kamala car l'aperçu s'arrêtera après quelques images si vous ne le fermez pas
@Override
public void onImageAvailable(ImageReader reader) {
Log.d(TAG, "I'm an image frame!");
Image image = reader.acquireNextImage();
...
if (image != null)
image.close();
}
Dans la classe ImageReader.OnImageAvailableListener, fermez l'image après la lecture comme indiqué ci-dessous (cela libérera le tampon pour la prochaine capture). Vous devrez gérer l'exception à la fermeture
Image image = imageReader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
image.close();
J'avais besoin de la même chose, j'ai donc utilisé leur exemple et ajouté un appel à une nouvelle fonction lorsque la caméra est en état d'aperçu.
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback()
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
if (buttonPressed){
savePreviewShot();
}
break;
}
La savePreviewShot()
est simplement une version recyclée de la captureStillPicture()
originale adaptée pour utiliser le modèle d'aperçu.
private void savePreviewShot(){
try {
final Activity activity = getActivity();
if (null == activity || null == mCameraDevice) {
return;
}
// This is the CaptureRequest.Builder that we use to take a picture.
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureBuilder.addTarget(mImageReader.getSurface());
// Orientation
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
CameraCaptureSession.CaptureCallback CaptureCallback
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss:SSS");
Date resultdate = new Date(System.currentTimeMillis());
String mFileName = sdf.format(resultdate);
mFile = new File(getActivity().getExternalFilesDir(null), "pic "+mFileName+" preview.jpg");
Log.i("Saved file", ""+mFile.toString());
unlockFocus();
}
};
mCaptureSession.stopRepeating();
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (Exception e) {
e.printStackTrace();
}
};
Il est préférable d'initier ImageReader
avec le tampon d'image max est 2
Puis d'utiliser reader.acquireLatestImage()
à l'intérieur de onImageAvailable()
.
Parce que acquireLatestImage()
va acquérir la dernière image de la file d'attente d'ImageReader, en supprimant l'ancienne. Il est recommandé d'utiliser cette fonction sur acquireNextImage()
pour la plupart des cas d'utilisation, car elle convient mieux au traitement en temps réel. Notez que le tampon d'image max doit être d'au moins 2
.
Et n'oubliez pas de close()
votre image après traitement.