【问题标题】:How to get Android camera preview data?如何获取Android相机预览数据?
【发布时间】:2013-07-14 23:26:07
【问题描述】:

我的相机应用在屏幕上显示相机预览,并在后台处理它。以下是相关代码,尽可能精简(例如,不显示错误处理或字段声明):

public final class CameraView extends SurfaceView implements
          SurfaceHolder.Callback, Runnable, PreviewCallback {

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 

    void openCamera() {
        // Called from parent activity after setting content view to CameraView
        mCamera = Camera.open();
        mCamera.setPreviewCallbackWithBuffer(this);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        new Thread(this).start(); 

        // Set CameraView to the optimal camera preview size

        final Camera.Parameters params = mCamera.getParameters();
        final List<Camera.Size> sizes = params.getSupportedPreviewSizes();
        final int screenWidth = ((View) getParent()).getWidth();
        int minDiff = Integer.MAX_VALUE;
        Camera.Size bestSize = null;

        if (getResources().getConfiguration().orientation 
                == Configuration.ORIENTATION_LANDSCAPE) {
            // Find the camera preview width that best matches the
            // width of the surface.
            for (Camera.Size size : sizes) {
                final int diff = Math.abs(size.width - screenWidth);
                if (diff < minDiff) {
                    minDiff = diff;
                    bestSize = size;
                }
            }
        } else {
            // Find the camera preview HEIGHT that best matches the 
            // width of the surface, since the camera preview is rotated.
            mCamera.setDisplayOrientation(90);
            for (Camera.Size size : sizes) {
                final int diff = Math.abs(size.height - screenWidth);
                if (Math.abs(size.height - screenWidth) < minDiff) {
                    minDiff = diff;
                    bestSize = size;
                }
            }
        }

        final int previewWidth = bestSize.width;
        final int previewHeight = bestSize.height;

        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.height = previewHeight;
        layoutParams.width = previewWidth;
        setLayoutParams(layoutParams);

        params.setPreviewFormat(ImageFormat.NV21);
        mCamera.setParameters(params);

        int size = previewWidth * previewHeight * 
            ImageFormat.getBitsPerPixel(params.getPreviewFormat()) / 8;
        mBuffer = new byte[size];
        mCamera.addCallbackBuffer(mBuffer);

        mCamera.setPreviewDisplay(mHolder);
        mCamera.startPreview();
    }

    public void onPreviewFrame(byte[] data, Camera camera) {
        CameraView.this.notify();
    }

    public void run() {
        mThreadRun = true;
        while (mThreadRun) {
            synchronized (this) {
                this.wait();
                processFrame(mBuffer); // convert to RGB and rotate - not shown
            }
            // Request a new frame from the camera by putting 
            // the buffer back into the queue
            mCamera.addCallbackBuffer(mBuffer);
        }

        mHolder.removeCallback(this);
        mCamera.stopPreview();
        mCamera.setPreviewCallback(null);
        mCamera.release();
        mCamera = null;
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        mThreadRun = false;
    }
}

在所有设备上,相机预览都能正常显示,并且在大多数设备(模拟器、三星 Galaxy S3 等)上,mBuffer 中存储的数据也是正确的(当然是在 NV21 到 RGB 转换和旋转之后)。但是,许多设备在 onPreviewFrame 中提供的数据不正确。我确定数据在收到后会正确转换为 RGB,因此问题似乎出在提供给mBuffer 的原始数据中。我注意到 this bug report 与 YV12(别名 YUV420p)相机预览格式有关,但我使用的是旧默认值 NV21(别名 YUV420sp),必须根据 compatibility standard 支持它(参见 7.5.3.2,第 29 页的底部)。

例如,对于这个场景(此处显示在三星 Galaxy Tab 2 上的相机预览中):

在选项卡 2 上传递给 mBuffer 的数据如下所示:

在摩托罗拉 Droid 4 上看起来像:

在所有设备上获取 Android 相机预览数据的正确方法是什么?

编辑: 对于processFrame(),我使用 OpenCV 转换为 RGB 并旋转。请参阅this answerthis answer

【问题讨论】:

  • 如何显示/处理图像?
  • @EJTH 我使用 OpenCV(方法 cvtColor 带有标志 COLOR_YUV420sp2RGBA)。
  • 所以我猜你是在不是安卓设备的设备上执行到 RGB 的转换?出于好奇,YuvImage.compressToJpeg() 是否会产生类似的扭曲图像?
  • @EJTH YuvImage.compressToJpeg() 确实有效,这一事实使我得到了正确的答案。将此放入答案中,我将奖励它。

标签: android android-camera


【解决方案1】:

唯一的问题是我没有设置预览的宽高:

params.setPreviewSize(previewWidth, previewHeight);
mCamera.setParameters(params);

这意味着我为数组分配的高度和宽度(与 previewWidth * previewHeight 成比例)往往比返回的实际数据的大小(与 默认 预览成比例宽度和预览高度)。在某些手机上,默认大小与 previewWidth 和 previewHeight 相同,因此没有问题。

【讨论】:

    【解决方案2】:

    你也可以试试这个

    public void takeSnapPhoto() {
    camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Camera.Parameters parameters = camera.getParameters();
            int format = parameters.getPreviewFormat();
            //YUV formats require more conversion
            if (format == ImageFormat.NV21 || format == ImageFormat.YUY2 || format == ImageFormat.NV16) {
                int w = parameters.getPreviewSize().width;
                int h = parameters.getPreviewSize().height;
                // Get the YuV image
                YuvImage yuv_image = new YuvImage(data, format, w, h, null);
                // Convert YuV to Jpeg
                Rect rect = new Rect(0, 0, w, h);
                ByteArrayOutputStream output_stream = new ByteArrayOutputStream();
                yuv_image.compressToJpeg(rect, 100, output_stream);
                byte[] byt = output_stream.toByteArray();
                FileOutputStream outStream = null;
                try {
                    // Write to SD Card
                    File file = createFileInSDCard(FOLDER_PATH, "Image_"+System.currentTimeMillis()+".jpg");
                    //Uri uriSavedImage = Uri.fromFile(file);
                    outStream = new FileOutputStream(file);
                    outStream.write(byt);
                    outStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                }
            }
        }
    });}
    

    【讨论】:

    • 这会拍照并将其保存到 SD 卡中,这与问题中的功能不同。
    • 你节省了我的时间!谢谢!我需要非常快地拍两张照片,这种方法适用于我。我用的时间不超过 200 毫秒。帧之间
    【解决方案3】:

    在严重依赖预览数据的 Barcode Scanner 的工作中,我觉得我已经看到了阳光下的每一个错误。我的建议只是不要调用setPreviewFormat() 并让它使用默认值。幸运的是,默认值是您想要的。似乎没有正确获得默认设置的设备少于将呼叫呼叫setPreviewFormat() 的设备。至少尝试一下,可能会也可能不会。

    【讨论】:

    • 我已经试过了,它也有同样的问题。您是否有兴趣发布代码的相关部分?通过比较这两种方法或许可以找到答案。
    • 一切都在code.google.com/p/zxing - 寻找CameraConfigurationManager。
    • 谢谢。您的代码使用setOneShotPreviewCallback 而不是setPreviewCallbackWithBuffer,所以我尝试切换,图像看起来完全一样。我越来越想知道 Android 与这些设备的摄像头集成的方式是否只是一个巨大的错误。
    • 条码扫描仪可以处理这些吗?至少这会压低它。如果是这样,即使有一些错误,驱动程序也不是没有希望的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多