【问题标题】:setPreviewDisplay and setDisplayOrientationsetPreviewDisplay 和 setDisplayOrientation
【发布时间】:2013-03-11 20:17:41
【问题描述】:

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

mCamera.setPreviewDisplay(null);

setPreviewDisplay 的 Android 文档说明:

此方法必须在 startPreview() 之前调用。一个例外是 如果之前未设置预览表面(或设置为空) startPreview() 被调用,那么这个方法可能会被调用一次 用于设置预览表面的非空参数。 (这允许相机 设置和表面创建并行进行,节省时间。) 在预览运行时,预览表面可能不会改变。

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

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

发生了什么事?

这里是他们代码中(可能)最相关的部分,稍微简化并内联了方法。这里是the full version

类定义

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");
}

【问题讨论】:

    标签: android android-camera


    【解决方案1】:

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

    @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);
                }
            }
        }
    

    【讨论】:

    • 我没有 org.opencv.android.CameraBridgeViewBase.CvCameraViewListenerorg.opencv.android.JavaCameraView 可用作导入 - 相关的 Java 文件不存在。你是怎么配置的?
    • 你有 OpenCV SDK 作为 android 依赖项吗? docs.opencv.org/doc/tutorials/introduction/…
    • 是的,我可以使用所有其他 OpenCV 类。
    • 仅翻转屏幕以从纵向变为横向是不够的:我必须旋转图像。这就是把我带到这里的问题。
    • 转置矩阵会沿对角线翻转。要旋转矩阵,我需要使用warpAffine 和来自getRotationMatrix2d 的旋转矩阵。但是,我可以用我现有的代码做到这一点。我赞成你的回答,因为它通常很有用,但它不能解决我的具体问题。
    【解决方案2】:

    我使用 OpenCV 本身解决了旋转问题:在使用 this code 找出需要校正多少屏幕旋转后,我将旋转矩阵应用于原始相机图像(从 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) 在某些手机上会出现空白屏幕。该解决方案是我从here 获得并借鉴this bugreport 和this SO 问题,将隐藏的“假”SurfaceView 传递给预览显示以启动它,但实际上在覆盖上显示输出自定义视图,我称之为 CameraView。所以,在活动的onCreate() 中调用setContentView() 之后,坚持这段代码:

    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-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-17
    • 1970-01-01
    • 2016-02-02
    • 1970-01-01
    • 2016-07-05
    相关资源
    最近更新 更多