Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
180 views
in Technique[技术] by (71.8m points)

android - Pictures with Camera2 API are really dark

I'm working on Android and I'm trying to capture a picture without displaying any preview. I tried to simplify the process by making a class. It's working but all the pictures are really really dark. Here is my class :

public class Cam {
private Context context;
private CameraManager manager;
private CameraDevice camera;
private CameraCaptureSession session;
private ImageReader reader;
public static String FRONT="-1";
public static String BACK="-1";
private boolean available=true;
private String filepath;

private static final String NO_CAM = "No camera found on device!";
private static final String ERR_CONFIGURE = "Failed configuring session";
private static final String ERR_OPEN = "Can't open the camera";
private static final String CAM_DISCONNECT = "Camera disconnected";
private static final String FILE_EXIST = "File already exist";

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

public Cam(Context context) throws CameraAccessException {
    this.context = context;
    this.manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    String ids[] = manager.getCameraIdList();
    if(ids.length==2){
        BACK=ids[0];
        FRONT=ids[1];
    }
    else if(ids.length==1){
        BACK=ids[0];
    }
    else{
        available=false;
        throw new CameraAccessException(-1, NO_CAM);
    }
}

public void takePicture(String camId, String filepath) throws CameraAccessException {
    if(available){
        this.filepath=filepath;
        StreamConfigurationMap map = manager.getCameraCharacteristics(camId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea());
        reader=ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 1);
        reader.setOnImageAvailableListener(imageListener, null);
        manager.openCamera(camId, cameraStateCallback, null);
    }
    else
        throwError(NO_CAM);
}

private CameraDevice.StateCallback cameraStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {
        Cam.this.camera=camera;
        try {
            camera.createCaptureSession(Collections.singletonList(reader.getSurface()), sessionStateCallback, null);
        } catch (CameraAccessException e) {
            throwError(e.getMessage());
        }
    }

    @Override
    public void onDisconnected(CameraDevice camera) {
        throwError(CAM_DISCONNECT);
    }

    @Override
    public void onError(CameraDevice camera, int error) {
        throwError(ERR_OPEN);
    }
};

private CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(CameraCaptureSession session) {
        Cam.this.session=session;
        try {
            CaptureRequest.Builder request = camera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            request.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            request.addTarget(reader.getSurface());
            int rotation = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
            request.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
            session.capture(request.build(), captureCallback, null);
        } catch (CameraAccessException e) {
            throwError(e.getMessage());
        }
    }

    @Override
    public void onConfigureFailed(CameraCaptureSession session) {
        throwError(ERR_CONFIGURE);
    }
};

private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
        super.onCaptureFailed(session, request, failure);
        throwError(failure.toString());
    }
};

private ImageReader.OnImageAvailableListener imageListener = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        Image image = reader.acquireLatestImage();
        try {
            File file = saveImage(image);
            // Send file via a listener
            closeCamera();
        } catch (IOException e) {
            throwError(e.getMessage());
        }
        reader.close();
    }
};

private File saveImage(Image image) throws IOException {
    File file = new File(filepath);
    if (file.exists()) {
        throwError(FILE_EXIST);
        return null;
    }
    else {
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        FileOutputStream output = new FileOutputStream(file);
        output.write(bytes);
        image.close();
        output.close();
        return file;
    }
}

static class CompareSizesByArea implements Comparator<Size> {
    @Override
    public int compare(Size lhs, Size rhs) {
        return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
    }
}

private void closeCamera(){
    if(session!=null) {session.close();}
    if(reader!=null) {reader.close();}
    if(camera!=null) {camera.close();}
}

Then I call the Cam object in my Activity :

Cam cam = new Cam(MainActivity.this);
cam.takePicture(Cam.BACK, "/sdcard/pic.jpg");

A listener prevent the MainActivity when the picture is available, but I removed the code to clear a bit.

I don't know what I am doing wrong, the pictures are really dark. Maybe a flag or something... Any help will be appreciated.

EDIT: Working class : https://github.com/omaflak/Android-Camera2-Library/blob/master/ezcam/src/main/java/me/aflak/ezcam/EZCam.java

Example: https://github.com/omaflak/Android-Camera2-Library/blob/master/app/src/main/java/me/aflak/libraries/MainActivity.java

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If the only capture request you send to the camera is the one for the final picture, this is not surprising.

The camera automatic exposure, focus, and white balance routines generally need a second or two of streaming buffers before they converge to good results.

While you don't need to draw preview on screen, the simplest method here is to first run a repeating request targeting a dummy SurfaceTexture for a second or two, and then fire off the JPEG capture. You could just stream the JPEG capture, but JPEG capture is slow, so you'll need a longer time for convergence (plus it's more likely a camera implementation has a bug with repeated JPEG capture and getting good exposure, than with a typical preview).

So, create a dummy SurfaceTexture with a random texture ID argument:

private SurfaceTexture mDummyPreview = new SurfaceTexture(1);
private Surface mDummySurface = new Surface(mDummyPreview);

and include the Surface in your session configuration. Once the session is configured, create a preview request that targets the dummy preview, and after N capture results have come in, submit the capture request for the JPEG you want. You'll want to experiment with N, but probably ~30 frames is enough.

Note that you're still not dealing with:

  • Locking AF to ensure a sharp image for your JPEG
  • Running AE precapture to allow for flash metering, if you want to allow for flash use
  • Having some way for the user to know what they'll be capturing, since there's no preview, they can't aim the device at anything very well.

The AF lock and precapture trigger sequences are included in Camera2Basic sample here: https://github.com/googlesamples/android-Camera2Basic, so you can take a look at what those do.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...