9

我对 OpenCV 的 Android 相机示例代码感到困惑。他们创建了一个自定义类,该类实现SurfaceHolder.Callback并将以下行放入方法中surfaceChanged

mCamera.setPreviewDisplay(null);

setPreviewDisplay用于解释的 Android 文档:

此方法必须在 startPreview() 之前调用。一个例外是,如果在调用 startPreview() 之前未设置预览表面(或设置为 null),则可以使用非 null 参数调用此方法一次以设置预览表面。(这允许相机设置和表面创建并行发生,节省时间。)预览表面可能不会在预览运行时更改。

不同寻常的是,OpenCV 的代码从不setPreviewDisplay使用非空 SurfaceHolder 进行调用。它工作正常,但使用更改图像的旋转setDisplayOrientation不起作用。这条线似乎也没有做任何事情,因为没有它我会得到相同的结果。

如果我setPreviewDisplay使用提供给surfaceChanged而不是SurfaceHolder 进行调用null,则图像会旋转,但不包括图像处理的结果。稍后我也会收到一个IllegalArgumentException电话lockCanvas

这是怎么回事?

以下是他们代码中(可能)最相关的部分,稍微简化并内联了方法。这是完整版

类定义

public abstract class SampleViewBase extends SurfaceView 
    implements SurfaceHolder.Callback, Runnable {

打开相机时

mCamera.setPreviewCallbackWithBuffer(new PreviewCallback() {
    public void onPreviewFrame(byte[] data, Camera camera) {
        synchronized (SampleViewBase.this) {
            System.arraycopy(data, 0, mFrame, 0, data.length);
            SampleViewBase.this.notify(); 
        }
        camera.addCallbackBuffer(mBuffer);
    }
});

当表面发生变化

/* Now allocate the buffer */
mBuffer = new byte[size];
/* The buffer where the current frame will be copied */
mFrame = new byte [size];
mCamera.addCallbackBuffer(mBuffer);

try {
    mCamera.setPreviewDisplay(null);
} catch (IOException e) {
    Log.e(TAG, "mCamera.setPreviewDisplay/setPreviewTexture fails: " + e);
}

[...]

/* Now we can start a preview */
mCamera.startPreview();

运行方法

public void run() {
    mThreadRun = true;
    Log.i(TAG, "Starting processing thread");
    while (mThreadRun) {
        Bitmap bmp = null;

        synchronized (this) {
            try {
                this.wait();
                bmp = processFrame(mFrame);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        if (bmp != null) {
            Canvas canvas = mHolder.lockCanvas();
            if (canvas != null) {
                canvas.drawBitmap(bmp, (canvas.getWidth() - getFrameWidth()) / 2, 
                    (canvas.getHeight() - getFrameHeight()) / 2, null);
                mHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
    Log.i(TAG, "Finishing processing thread");
}
4

2 回答 2

6

我遇到了同样的问题。我没有使用 a SurfaceView.Callback,而是对他们的类进行了子类化JavaCameraView在此处查看我的实时人脸检测和绘图示例。然后在处理之前根据设备的方向旋转从相机出来的矩阵是微不足道的。链接代码的相关摘录:

@Override
    public Mat onCameraFrame(Mat inputFrame) {
        int flipFlags = 1;
        if(display.getRotation() == Surface.ROTATION_270) {
            flipFlags = -1;
            Log.i(VIEW_LOG_TAG, "Orientation is" + getRotation());
        }
        Core.flip(inputFrame, mRgba, flipFlags);
        inputFrame.release();
    Imgproc.cvtColor(mRgba, mGray, Imgproc.COLOR_RGBA2GRAY);
        if (mAbsoluteFaceSize == 0) {
            int height = mGray.rows();
            if (Math.round(height * mRelativeFaceSize) > 0) {
                mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
            }
        }
    }
于 2013-03-26T21:19:15.887 回答
3

我使用 OpenCV 本身解决了旋转问题:在找出需要使用此代码校正多少屏幕旋转后,我将旋转矩阵应用于原始相机图像(从 YUV 转换为 RGB 之后):

Point center = new Point(mFrameWidth/2, mFrameHeight/2);
Mat rotationMatrix = Imgproc.getRotationMatrix2D(center, totalRotation, 1);

[...]

Imgproc.cvtColor(mYuv, mIntermediate, Imgproc.COLOR_YUV420sp2RGBA, 4);
Imgproc.warpAffine(mIntermediate, mRgba, rotationMatrix, 
    new Size(mFrameHeight, mFrameWidth));

另一个问题是setPreviewDisplay(null)在某些手机上出现空白屏幕。我从这里得到的解决方案,并借鉴了这个错误报告和这个SO 问题,将一个隐藏的“假” SurfaceView 传递给预览显示以使其启动,但实际上在一个覆盖的自定义视图上显示输出,我称之为相机视图。因此,在调用setContentView()Activity之后onCreate(),请坚持以下代码:

if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) {
    final SurfaceView fakeView = new SurfaceView(this);
    fakeView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    fakeView.setZOrderMediaOverlay(false);

    final CameraView cameraView = (CameraView) this.findViewById(R.id.cameraview); 
    cameraView.setZOrderMediaOverlay(true); 
    cameraView.fakeView = fakeView;
}

然后,在设置预览显示时,使用以下代码:

try {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
        mCamera.setPreviewTexture(new SurfaceTexture(10));
    else
        mCamera.setPreviewDisplay(fakeView.getHolder());
} catch (IOException e) {
    Log.e(TAG, "mCamera.setPreviewDisplay fails: "+ e);
}

如果您只是为 Honeycomb 及更高版本进行开发,只需将 setPreviewDisplay(null) 替换为mCamera.setPreviewTexture(new SurfaceTexture(10));并完成即可。 setDisplayOrientation()但是,如果你这样做仍然不起作用,所以你仍然必须使用旋转矩阵解决方案。

于 2013-03-28T02:12:05.790 回答