Auto focus added;

Old Android support added;
Android 4.1.x fixes added;
Thead sync added;
Camera errors handling added.
pull/80/head
Alexander Smorkalov 12 years ago committed by Andrey Kamaev
parent f8bcff8ff3
commit 8393fd0c9d
  1. 482
      samples/android/15-puzzle-framework/src/org/opencv/framework/OpenCvCameraBridgeViewBase.java
  2. 110
      samples/android/15-puzzle-framework/src/org/opencv/framework/OpenCvJavaCameraView.java
  3. 146
      samples/android/15-puzzle-framework/src/org/opencv/framework/OpenCvNativeCameraView.java

@ -26,295 +26,295 @@ import android.view.SurfaceView;
*/
public abstract class OpenCvCameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
private static final int MAX_UNSPECIFIED = -1;
private static final int MAX_UNSPECIFIED = -1;
protected int mFrameWidth;
protected int mFrameHeight;
protected int mFrameWidth;
protected int mFrameHeight;
protected int mMaxHeight;
protected int mMaxWidth;
protected int mMaxHeight;
protected int mMaxWidth;
private Bitmap mCacheBitmap;
private Bitmap mCacheBitmap;
public OpenCvCameraBridgeViewBase(Context context, AttributeSet attrs) {
super(context,attrs);
getHolder().addCallback(this);
mMaxWidth = MAX_UNSPECIFIED;
mMaxHeight = MAX_UNSPECIFIED;
public OpenCvCameraBridgeViewBase(Context context, AttributeSet attrs) {
super(context,attrs);
getHolder().addCallback(this);
mMaxWidth = MAX_UNSPECIFIED;
mMaxHeight = MAX_UNSPECIFIED;
}
public interface CvCameraViewListener {
/**
* This method is invoked when camera preview has started. After this method is invoked
* the frames will start to be delivered to client via the onCameraFrame() callback.
* @param width - the width of the frames that will be delivered
* @param height - the height of the frames that will be delivered
*/
public void onCameraViewStarted(int width, int height);
/**
* This method is invoked when camera preview has been stopped for some reason.
* No frames will be delivered via onCameraFrame() callback after this method is called.
*/
public void onCameraViewStopped();
/**
* This method is invoked when delivery of the frame needs to be done.
* The returned values - is a modified frame which needs to be displayed on the screen.
* TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
*/
public Mat onCameraFrame(Mat inputFrame);
}
public class FrameSize {
public int width;
public int height;
public FrameSize(int w, int h) {
width = w;
height = h;
}
}
private static final int STOPPED = 0;
private static final int STARTED = 1;
private static final String TAG = "SampleCvBase";
private CvCameraViewListener mListener;
private int mState = STOPPED;
private boolean mEnabled;
private boolean mSurfaceExist;
private Object mSyncObject = new Object();
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
synchronized(mSyncObject) {
if (!mSurfaceExist) {
mSurfaceExist = true;
checkCurrentState();
} else {
/** Surface changed. We need to stop camera and restart with new parameters */
/* Pretend that old surface has been destroyed */
mSurfaceExist = false;
checkCurrentState();
/* Now use new surface. Say we have it now */
mSurfaceExist = true;
checkCurrentState();
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
/* Do nothing. Wait until surfaceChanged delivered */
}
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized(mSyncObject) {
mSurfaceExist = false;
checkCurrentState();
}
}
}
public interface CvCameraViewListener {
/**
* This method is invoked when camera preview has started. After this method is invoked
* the frames will start to be delivered to client via the onCameraFrame() callback.
* @param width - the width of the frames that will be delivered
* @param height - the height of the frames that will be delivered
*/
public void onCameraViewStarted(int width, int height);
/**
* This method is provided for clients, so they can enable the camera connection.
* The actuall onCameraViewStarted callback will be delivered only after both this method is called and surface is available
* This method is invoked when camera preview has been stopped for some reason.
* No frames will be delivered via onCameraFrame() callback after this method is called.
*/
public void enableView() {
synchronized(mSyncObject) {
mEnabled = true;
checkCurrentState();
}
}
public void onCameraViewStopped();
/**
* This method is provided for clients, so they can disable camera connection and stop
* the delivery of frames eventhough the surfaceview itself is not destroyed and still stays on the scren
* This method is invoked when delivery of the frame needs to be done.
* The returned values - is a modified frame which needs to be displayed on the screen.
* TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
*/
public void disableView() {
synchronized(mSyncObject) {
mEnabled = false;
checkCurrentState();
}
}
public Mat onCameraFrame(Mat inputFrame);
}
public void setCvCameraViewListener(CvCameraViewListener listener) {
mListener = listener;
}
public class FrameSize {
public int width;
public int height;
/**
* This method sets the maximum size that camera frame is allowed to be. When selecting
* size - the biggest size which less or equal the size set will be selected.
* As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
* preview frame will be selected with 176x152 size.
* This method is usefull when need to restrict the size of preview frame for some reason (for example for video recording)
* @param maxWidth - the maximum width allowed for camera frame.
* @param maxHeight - the maxumum height allowed for camera frame
*/
public void setMaxFrameSize(int maxWidth, int maxHeight) {
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
public FrameSize(int w, int h) {
width = w;
height = h;
}
}
/**
* Called when mSyncObject lock is held
*/
private void checkCurrentState() {
int targetState;
if (mEnabled && mSurfaceExist) {
targetState = STARTED;
} else {
targetState = STOPPED;
}
private static final int STOPPED = 0;
private static final int STARTED = 1;
private static final String TAG = "SampleCvBase";
if (targetState != mState) {
/* The state change detected. Need to exit the current state and enter target state */
processExitState(mState);
mState = targetState;
processEnterState(mState);
}
}
private CvCameraViewListener mListener;
private int mState = STOPPED;
private void processEnterState(int state) {
switch(state) {
case STARTED:
onEnterStartedState();
if (mListener != null) {
mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
}
break;
case STOPPED:
onEnterStoppedState();
if (mListener != null) {
mListener.onCameraViewStopped();
}
break;
};
}
private boolean mEnabled;
private boolean mSurfaceExist;
private void processExitState(int state) {
switch(state) {
case STARTED:
onExitStartedState();
break;
case STOPPED:
onExitStoppedState();
break;
};
}
private void onEnterStoppedState() {
/* nothing to do */
}
private Object mSyncObject = new Object();
private void onExitStoppedState() {
/* nothing to do */
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
synchronized(mSyncObject) {
if (!mSurfaceExist) {
mSurfaceExist = true;
checkCurrentState();
} else {
/** Surface changed. We need to stop camera and restart with new parameters */
/* Pretend that old surface has been destroyed */
mSurfaceExist = false;
checkCurrentState();
/* Now use new surface. Say we have it now */
mSurfaceExist = true;
checkCurrentState();
}
}
}
private void onEnterStartedState() {
connectCamera(getWidth(), getHeight());
/* Now create cahe Bitmap */
mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
public void surfaceCreated(SurfaceHolder holder) {
/* Do nothing. Wait until surfaceChanged delivered */
}
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized(mSyncObject) {
mSurfaceExist = false;
checkCurrentState();
}
}
private void onExitStartedState() {
disconnectCamera();
if (mCacheBitmap != null) {
mCacheBitmap.recycle();
}
/**
* This method is provided for clients, so they can enable the camera connection.
* The actuall onCameraViewStarted callback will be delivered only after both this method is called and surface is available
*/
public void enableView() {
synchronized(mSyncObject) {
mEnabled = true;
checkCurrentState();
}
}
/**
* This method is provided for clients, so they can disable camera connection and stop
* the delivery of frames eventhough the surfaceview itself is not destroyed and still stays on the scren
*/
public void disableView() {
synchronized(mSyncObject) {
mEnabled = false;
checkCurrentState();
}
}
public void setCvCameraViewListener(CvCameraViewListener listener) {
mListener = listener;
}
/**
* This method sets the maximum size that camera frame is allowed to be. When selecting
* size - the biggest size which less or equal the size set will be selected.
* As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
* preview frame will be selected with 176x152 size.
* This method is usefull when need to restrict the size of preview frame for some reason (for example for video recording)
* @param maxWidth - the maximum width allowed for camera frame.
* @param maxHeight - the maxumum height allowed for camera frame
*/
public void setMaxFrameSize(int maxWidth, int maxHeight) {
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}
/**
* Called when mSyncObject lock is held
*/
private void checkCurrentState() {
int targetState;
if (mEnabled && mSurfaceExist) {
targetState = STARTED;
} else {
targetState = STOPPED;
}
if (targetState != mState) {
/* The state change detected. Need to exit the current state and enter target state */
processExitState(mState);
mState = targetState;
processEnterState(mState);
}
}
private void processEnterState(int state) {
switch(state) {
case STARTED:
onEnterStartedState();
if (mListener != null) {
mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
}
break;
case STOPPED:
onEnterStoppedState();
if (mListener != null) {
mListener.onCameraViewStopped();
}
break;
};
}
/**
* This method shall be called by the subclasses when they have valid
* object and want it to be delivered to external client (via callback) and
* then displayed on the screen.
* @param frame - the current frame to be delivered
*/
protected void deliverAndDrawFrame(Mat frame) {
Mat modified;
if (mListener != null) {
modified = mListener.onCameraFrame(frame);
} else {
modified = frame;
}
private void processExitState(int state) {
switch(state) {
case STARTED:
onExitStartedState();
break;
case STOPPED:
onExitStoppedState();
break;
};
}
if (modified != null) {
Utils.matToBitmap(modified, mCacheBitmap);
}
private void onEnterStoppedState() {
/* nothing to do */
}
if (mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawBitmap(mCacheBitmap, (canvas.getWidth() - mCacheBitmap.getWidth()) / 2, (canvas.getHeight() - mCacheBitmap.getHeight()) / 2, null);
getHolder().unlockCanvasAndPost(canvas);
}
}
}
private void onExitStoppedState() {
/* nothing to do */
}
/**
* This method is invoked shall perform concrete operation to initialize the camera.
* CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
* initialized with the size of the Camera frames that will be delivered to external processor.
* @param width - the width of this SurfaceView
* @param height - the height of this SurfaceView
*/
protected abstract void connectCamera(int width, int height);
private void onEnterStartedState() {
/**
* Disconnects and release the particular camera object beeing connected to this surface view.
* Called when syncObject lock is held
*/
protected abstract void disconnectCamera();
connectCamera(getWidth(), getHeight());
/* Now create cahe Bitmap */
mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
}
public interface ListItemAccessor {
public int getWidth(Object obj);
public int getHeight(Object obj);
};
private void onExitStartedState() {
disconnectCamera();
if (mCacheBitmap != null) {
mCacheBitmap.recycle();
}
}
/**
* This method shall be called by the subclasses when they have valid
* object and want it to be delivered to external client (via callback) and
* then displayed on the screen.
* @param frame - the current frame to be delivered
*/
protected void deliverAndDrawFrame(Mat frame) {
Mat modified;
if (mListener != null) {
modified = mListener.onCameraFrame(frame);
} else {
modified = frame;
}
/**
* This helper method can be called by subclasses to select camera preview size.
* It goes over the list of the supported preview sizes and selects the maximum one which
* fits both values set via setMaxFrameSize() and surface frame allocated for this view
* @param supportedSizes
* @param surfaceWidth
* @param surfaceHeight
* @return
*/
protected FrameSize calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
int calcWidth = 0;
int calcHeight = 0;
if (modified != null) {
Utils.matToBitmap(modified, mCacheBitmap);
}
int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
if (mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawBitmap(mCacheBitmap, (canvas.getWidth() - mCacheBitmap.getWidth()) / 2, (canvas.getHeight() - mCacheBitmap.getHeight()) / 2, null);
getHolder().unlockCanvasAndPost(canvas);
}
}
}
/**
* This method is invoked shall perform concrete operation to initialize the camera.
* CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
* initialized with the size of the Camera frames that will be delivered to external processor.
* @param width - the width of this SurfaceView
* @param height - the height of this SurfaceView
*/
protected abstract void connectCamera(int width, int height);
/**
* Disconnects and release the particular camera object beeing connected to this surface view.
* Called when syncObject lock is held
*/
protected abstract void disconnectCamera();
public interface ListItemAccessor {
public int getWidth(Object obj);
public int getHeight(Object obj);
};
/**
* This helper method can be called by subclasses to select camera preview size.
* It goes over the list of the supported preview sizes and selects the maximum one which
* fits both values set via setMaxFrameSize() and surface frame allocated for this view
* @param supportedSizes
* @param surfaceWidth
* @param surfaceHeight
* @return
*/
protected FrameSize calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
int calcWidth = 0;
int calcHeight = 0;
int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
for (Object size : supportedSizes) {
int width = accessor.getWidth(size);
int height = accessor.getHeight(size);
int width = accessor.getWidth(size);
int height = accessor.getHeight(size);
if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
if (width >= calcWidth && height >= calcHeight) {
calcWidth = (int) width;
calcHeight = (int) height;
}
if (width >= calcWidth && height >= calcHeight) {
calcWidth = (int) width;
calcHeight = (int) height;
}
}
}
return new FrameSize(calcWidth, calcHeight);
}
return new FrameSize(calcWidth, calcHeight);
}
}

@ -27,94 +27,94 @@ import org.opencv.imgproc.Imgproc;
*/
public class OpenCvJavaCameraView extends OpenCvCameraBridgeViewBase implements PreviewCallback {
private static final int MAGIC_TEXTURE_ID = 10;
private static final String TAG = "OpenCvJavaCameraView";
private static final int MAGIC_TEXTURE_ID = 10;
private static final String TAG = "OpenCvJavaCameraView";
private Mat mBaseMat;
private Mat mBaseMat;
public static class JavaCameraSizeAccessor implements ListItemAccessor {
public static class JavaCameraSizeAccessor implements ListItemAccessor {
@Override
public int getWidth(Object obj) {
Camera.Size size = (Camera.Size) obj;
return size.width;
}
@Override
public int getHeight(Object obj) {
Camera.Size size = (Camera.Size) obj;
return size.height;
}
@Override
public int getWidth(Object obj) {
Camera.Size size = (Camera.Size) obj;
return size.width;
}
@Override
public int getHeight(Object obj) {
Camera.Size size = (Camera.Size) obj;
return size.height;
}
}
private Camera mCamera;
public OpenCvJavaCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OpenCvJavaCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void connectCamera(int width, int height) {
mCamera = Camera.open(0);
@Override
protected void connectCamera(int width, int height) {
mCamera = Camera.open(0);
List<android.hardware.Camera.Size> sizes = mCamera.getParameters().getSupportedPreviewSizes();
List<android.hardware.Camera.Size> sizes = mCamera.getParameters().getSupportedPreviewSizes();
/* Select the size that fits surface considering maximum size allowed */
FrameSize frameSize = calculateCameraFrameSize(sizes, new JavaCameraSizeAccessor(), width, height);
/* Now set camera parameters */
try {
Camera.Parameters params = mCamera.getParameters();
Camera.Parameters params = mCamera.getParameters();
List<Integer> formats = params.getSupportedPictureFormats();
List<Integer> formats = params.getSupportedPictureFormats();
params.setPreviewFormat(ImageFormat.NV21);
params.setPreviewSize(frameSize.width, frameSize.height);
params.setPreviewFormat(ImageFormat.NV21);
params.setPreviewSize(frameSize.width, frameSize.height);
mCamera.setPreviewCallback(this);
mCamera.setParameters(params);
//mCamera.setPreviewTexture(new SurfaceTexture(MAGIC_TEXTURE_ID));
mCamera.setPreviewCallback(this);
mCamera.setParameters(params);
//mCamera.setPreviewTexture(new SurfaceTexture(MAGIC_TEXTURE_ID));
SurfaceTexture tex = new SurfaceTexture(MAGIC_TEXTURE_ID);
SurfaceTexture tex = new SurfaceTexture(MAGIC_TEXTURE_ID);
mCamera.setPreviewTexture(tex);
mCamera.setPreviewTexture(tex);
mFrameWidth = frameSize.width;
mFrameHeight = frameSize.height;
mFrameWidth = frameSize.width;
mFrameHeight = frameSize.height;
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
mBaseMat = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1);
mBaseMat = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1);
/* Finally we are ready to start the preview */
mCamera.startPreview();
}
mCamera.startPreview();
}
@Override
protected void disconnectCamera() {
@Override
protected void disconnectCamera() {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
}
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
}
@Override
public void onPreviewFrame(byte[] frame, Camera arg1) {
Log.i(TAG, "Preview Frame received. Need to create MAT and deliver it to clients");
@Override
public void onPreviewFrame(byte[] frame, Camera arg1) {
Log.i(TAG, "Preview Frame received. Need to create MAT and deliver it to clients");
Log.i(TAG, "Frame size is " + frame.length);
Log.i(TAG, "Frame size is " + frame.length);
mBaseMat.put(0, 0, frame);
Mat frameMat = new Mat();
Imgproc.cvtColor(mBaseMat, frameMat, Imgproc.COLOR_YUV2RGBA_NV21, 4);
deliverAndDrawFrame(frameMat);
frameMat.release();
}
mBaseMat.put(0, 0, frame);
Mat frameMat = new Mat();
Imgproc.cvtColor(mBaseMat, frameMat, Imgproc.COLOR_YUV2RGBA_NV21, 4);
deliverAndDrawFrame(frameMat);
frameMat.release();
}
}

@ -19,68 +19,68 @@ import android.util.Log;
*/
public class OpenCvNativeCameraView extends OpenCvCameraBridgeViewBase {
public static final String TAG = "OpenCvNativeCameraView";
private boolean mStopThread;
private Thread mThread;
private VideoCapture mCamera;
public static final String TAG = "OpenCvNativeCameraView";
private boolean mStopThread;
private Thread mThread;
private VideoCapture mCamera;
public OpenCvNativeCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
super(context, attrs);
}
@Override
protected void connectCamera(int width, int height) {
/* 1. We need to instantiate camera
* 2. We need to start thread which will be getting frames
*/
/* First step - initialize camera connection */
initializeCamera(getWidth(), getHeight());
/* now we can start update thread */
mThread = new Thread(new CameraWorker(getWidth(), getHeight()));
mThread.start();
}
@Override
protected void disconnectCamera() {
/* 1. We need to stop thread which updating the frames
* 2. Stop camera and release it
*/
try {
mStopThread = true;
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mThread = null;
mStopThread = false;
}
/* Now release camera */
releaseCamera();
@Override
protected void connectCamera(int width, int height) {
}
/* 1. We need to instantiate camera
* 2. We need to start thread which will be getting frames
*/
/* First step - initialize camera connection */
initializeCamera(getWidth(), getHeight());
public static class OpenCvSizeAccessor implements ListItemAccessor {
/* now we can start update thread */
mThread = new Thread(new CameraWorker(getWidth(), getHeight()));
mThread.start();
@Override
public int getWidth(Object obj) {
Size size = (Size)obj;
return (int)size.width;
}
@Override
protected void disconnectCamera() {
/* 1. We need to stop thread which updating the frames
* 2. Stop camera and release it
*/
try {
mStopThread = true;
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mThread = null;
mStopThread = false;
}
/* Now release camera */
releaseCamera();
public int getHeight(Object obj) {
Size size = (Size)obj;
return (int)size.height;
}
public static class OpenCvSizeAccessor implements ListItemAccessor {
@Override
public int getWidth(Object obj) {
Size size = (Size)obj;
return (int)size.width;
}
@Override
public int getHeight(Object obj) {
Size size = (Size)obj;
return (int)size.height;
}
}
}
private void initializeCamera(int width, int height) {
private void initializeCamera(int width, int height) {
mCamera = new VideoCapture(Highgui.CV_CAP_ANDROID);
//TODO: improve error handling
@ -101,42 +101,42 @@ public class OpenCvNativeCameraView extends OpenCvCameraBridgeViewBase {
mFrameHeight = (int)frameHeight;
Log.i(TAG, "Selected camera frame size = (" + mFrameWidth + ", " + mFrameHeight + ")");
}
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
}
}
private class CameraWorker implements Runnable {
private class CameraWorker implements Runnable {
private Mat mRgba = new Mat();
private int mWidth;
private int mHeight;
private Mat mRgba = new Mat();
private int mWidth;
private int mHeight;
CameraWorker(int w, int h) {
mWidth = w;
mHeight = h;
}
CameraWorker(int w, int h) {
mWidth = w;
mHeight = h;
}
@Override
public void run() {
Mat modified;
@Override
public void run() {
Mat modified;
do {
if (!mCamera.grab()) {
Log.e(TAG, "Camera frame grab failed");
break;
}
mCamera.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
do {
if (!mCamera.grab()) {
Log.e(TAG, "Camera frame grab failed");
break;
}
mCamera.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
deliverAndDrawFrame(mRgba);
deliverAndDrawFrame(mRgba);
} while (!mStopThread);
} while (!mStopThread);
}
}
}
}

Loading…
Cancel
Save