使用 Camera2 API 的图片真的很暗

Posted

技术标签:

【中文标题】使用 Camera2 API 的图片真的很暗【英文标题】:Pictures with Camera2 API are really dark 【发布时间】:2015-11-02 17:37:10 【问题描述】:

我正在使用 android,我正在尝试在不显示任何预览的情况下捕获图片。我试图通过创建一个类来简化这个过程。它的工作,但所有的图片真的很黑。 这是我的课:

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();

然后我在我的 Activity 中调用 Cam 对象:

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

当图片可用时,监听器会阻止 MainActivity,但我删除了代码以清除一点。

我不知道我做错了什么,图片真的很暗。也许是一面旗帜或其他东西......任何帮助将不胜感激。

编辑: 工人阶级:https://github.com/omaflak/Android-Camera2-Library/blob/master/ezcam/src/main/java/me/aflak/ezcam/EZCam.java

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

【问题讨论】:

它们只是比预期的更暗,还是实际上是黑色的? 如果没有像太阳这样的强大光源直接进入光电传感器,图片几乎是黑色的。 例如,这是用原生应用拍摄的照片:i.imgur.com/tjvPonX.jpg?1 这是用我的应用拍摄的照片:i.imgur.com/KceBGxY.jpg?1 我已经尝试了各种方法在 Marshmallow - api23 上制作图片。看来您不能附加一个虚拟表面(我只能制作一张深色图片)。如果可能,请发布对您有用的代码。我没有棒棒糖设备。谢谢 试试这个。 ***.com/questions/28429071/… 【参考方案1】:

如果您发送给相机的唯一捕捉请求是最终图片的捕捉请求,这不足为奇。

相机自动曝光、对焦和白平衡例程通常需要一到两个流缓冲区才能收敛到良好的结果。

虽然您不需要在屏幕上绘制预览,但这里最简单的方法是首先运行针对虚拟 SurfaceTexture 的重复请求一两秒钟,然后触发 JPEG 捕获。 您可以只流式传输 JPEG 捕获,但 JPEG 捕获很慢,因此您需要更长的时间来收敛(此外,与典型预览相比,相机实现更可能存在重复 JPEG 捕获和获得良好曝光的错误) .

因此,创建一个带有随机纹理 ID 参数的虚拟 SurfaceTexture:

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

并将 Surface 包含在您的会话配置中。配置会话后,创建一个以虚拟预览为目标的预览请求,并在 N 个捕获结果进来后,提交您想要的 JPEG 的捕获请求。您可能想尝试 N,但大约 30 帧可能就足够了。

请注意,您仍然没有处理:

锁定 AF 以确保 JPEG 图像清晰 如果您想允许使用闪光灯,请运行 AE 预捕获以进行闪光测光 有一些方法让用户知道他们将要捕捉什么,因为没有预览,他们不能很好地将设备瞄准任何东西。

此处的 Camera2Basic 示例中包含 AF 锁定和预捕获触发序列:https://github.com/googlesamples/android-Camera2Basic,因此您可以查看它们的作用。

【讨论】:

感谢您的完整回复!我现在要试试这个,我会及时通知你! @omaflak 是否可以发布工作示例?谢谢 @eddy 可以发布工作示例吗?谢谢你。我正在努力完成这项工作,但到目前为止还没有运气 @eddy 你在 Android 6 上测试过代码吗?似乎如果我只附加虚拟表面,我将不会创建我的图像。似乎在 Android 6 中唯一的方法是创建一个暗图像(没有预览) 对不起,我更改了我的 SO 帐户,我没有看到您的消息。我为它做了我的图书馆,检查代码:GITHUB【参考方案2】:

或许你可以尝试开启自动曝光模式和自动白平衡:

request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);

我希望它会有所帮助:)

【讨论】:

感谢您的回复,但很遗憾没有成功:( 新尝试:也许自动白平衡有帮助?我编辑了我的帖子;)【参考方案3】:

在我的情况下,配置 FPS 对我有帮助。并且不要忘记将其发送到CaptureRequest.Builder 进行预览,也不要忘记将其发送到CaptureRequest.Builder 捕获生成器。像往常一样,FPS 10 或 15 帧足以用于照片和预览。

捕获生成器

// This is the CaptureRequest.Builder that we use to take a picture.
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
...
setupFPS(captureBuilder);

预览生成器:

// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
                ...
// set FPS rate
setupFPS(mPreviewRequestBuilder);

哪里设置FPS:

private void setupFPS(CaptureRequest.Builder builder)
        if(fpsRange != null) 
            builder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
        

并使用以下方法初始化 FPS:

CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
try 
                    Range<Integer>[] ranges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
                    if(ranges != null) 
                        for (Range<Integer> range : ranges) 
                            int upper = range.getUpper();
                            Log.i(TAG, "[FPS Range Available]:" + range);
                            if (upper >= 10) 
                                if (fpsRange == null || upper < fpsRange.getUpper()) 
                                    fpsRange = range;
                                
                            
                        
                    
                 catch (Exception e) 
                    e.printStackTrace();
                
                Log.i(TAG, "[FPS Range] is:" + fpsRange);

【讨论】:

【参考方案4】:

我知道这是旧的,但我遇到了类似的问题。与其他偶然发现这一点的人分享对我有用的东西。在这里尝试了各种答案都没有成功。

将 CONTROL_AE_MODE 设置为 CONTROL_AE_MODE_ON(正如一些人建议的那样)也没有修复它(你认为它会)。

为我解决的问题是将 CONTROL_CAPTURE_INTENT 设置为 CONTROL_CAPTURE_INTENT_VIDEO_RECORD。

request.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);

添加这条线并构建后,相机会按预期启用自动曝光并自动调整相机。

请参阅https://developer.android.com/reference/android/hardware/camera2/CameraMetadata 了解更多信息。我以此为指导来发现可用的选项。

【讨论】:

以上是关于使用 Camera2 API 的图片真的很暗的主要内容,如果未能解决你的问题,请参考以下文章

AndroidCameraHAL3-MultiCamera-CameraX

AndroidCameraHAL3-MultiCamera-CameraX

通过具有 16:9 传感器阵列的相机上的 android Camera2 API 捕获 4:3 相机图片

Android Camera API/Camera2 API 相机预览及滤镜贴纸等处理

android Camera2 API使用详解

使用 Camera2(Android 版本 21)API 录制 60fps 视频