Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setPreviewDisplay and setDisplayOrientation

I'm puzzled by OpenCV's Android camera sample code. They make a custom class which implements SurfaceHolder.Callback and put the following line inside the method surfaceChanged:

mCamera.setPreviewDisplay(null);

The Android documentation for setPreviewDisplay explains:

This method must be called before startPreview(). The one exception is that if the preview surface is not set (or set to null) before startPreview() is called, then this method may be called once with a non-null parameter to set the preview surface. (This allows camera setup and surface creation to happen in parallel, saving time.) The preview surface may not otherwise change while preview is running.

Unusually, OpenCV's code never calls setPreviewDisplay with a non-null SurfaceHolder. It works fine, but changing the rotation of the image using setDisplayOrientation doesn't work. This line also doesn't appear to do anything, since I get the same results without it.

If I call setPreviewDisplay with the SurfaceHolder supplied to surfaceChanged instead of null, the image rotates but does not include the results of the image processing. I also get an IllegalArgumentException when calling lockCanvas later on.

What's going on?

Here are the (possibly) most relevant parts of their code, slightly simplified and with methods inlined. Here is the full version.

Class definition

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

When the camera is opened

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

When the surface changes

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

The run method

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");
}
like image 641
1'' Avatar asked Mar 22 '13 03:03

1''


2 Answers

I ran into this same problem. Instead of using a SurfaceView.Callback, I subclassed their class JavaCameraView. See my live face detection and drawing sample here. It was then trivial to rotate the matrix coming out of the camera according to the device's orientation, prior to processing. Relevant excerpt of linked code:

@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);
            }
        }
    }
like image 162
Charles Munger Avatar answered Nov 16 '22 21:11

Charles Munger


I solved the rotation issue using OpenCV itself: after finding out how much the screen rotation needs to be corrected using this code, I apply a rotation matrix to the raw camera image (after converting from YUV to 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));

A separate issue is that setPreviewDisplay(null) gives a blank screen on some phones. The solution, which I got from here and draws on this bugreport and this SO question, passes a hidden, "fake" SurfaceView to the preview display to get it to start, but actually displays the output on an overlaid custom view, which I call CameraView. So, after calling setContentView() in the activity's onCreate(), stick in this code:

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

Then, when setting the preview display, use this code:

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

If you are only developing for Honeycomb and above, just replace setPreviewDisplay(null) with mCamera.setPreviewTexture(new SurfaceTexture(10)); and be done with it. setDisplayOrientation() still doesn't work if you do this, though, so you'll still have to use the rotation matrix solution.

like image 3
1'' Avatar answered Nov 16 '22 20:11

1''