Added camera calibration sample for android
authorDaniil Osokin <daniil.osokin@itseez.com>
Mon, 1 Jul 2013 20:53:18 +0000 (00:53 +0400)
committerDaniil Osokin <daniil.osokin@itseez.com>
Thu, 25 Jul 2013 21:19:32 +0000 (01:19 +0400)
13 files changed:
samples/android/camera-calibration/.classpath [new file with mode: 0644]
samples/android/camera-calibration/.project [new file with mode: 0644]
samples/android/camera-calibration/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
samples/android/camera-calibration/AndroidManifest.xml [new file with mode: 0644]
samples/android/camera-calibration/CMakeLists.txt [new file with mode: 0644]
samples/android/camera-calibration/res/drawable/icon.png [new file with mode: 0644]
samples/android/camera-calibration/res/layout/camera_calibration_surface_view.xml [new file with mode: 0644]
samples/android/camera-calibration/res/menu/calibration.xml [new file with mode: 0644]
samples/android/camera-calibration/res/values/strings.xml [new file with mode: 0644]
samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CalibrationResult.java [new file with mode: 0644]
samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java [new file with mode: 0644]
samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrator.java [new file with mode: 0644]
samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/OnCameraFrameRender.java [new file with mode: 0644]

diff --git a/samples/android/camera-calibration/.classpath b/samples/android/camera-calibration/.classpath
new file mode 100644 (file)
index 0000000..46c3d46
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+       <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>\r
+       <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>\r
+       <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>\r
+       <classpathentry kind="src" path="src"/>\r
+       <classpathentry kind="src" path="gen"/>\r
+       <classpathentry kind="output" path="bin/classes"/>\r
+</classpath>\r
diff --git a/samples/android/camera-calibration/.project b/samples/android/camera-calibration/.project
new file mode 100644 (file)
index 0000000..eae413e
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>OpenCV Sample - camera-calibration</name>\r
+       <comment></comment>\r
+       <projects>\r
+       </projects>\r
+       <buildSpec>\r
+               <buildCommand>\r
+                       <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.jdt.core.javabuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>com.android.ide.eclipse.adt.ApkBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+       </buildSpec>\r
+       <natures>\r
+               <nature>com.android.ide.eclipse.adt.AndroidNature</nature>\r
+               <nature>org.eclipse.jdt.core.javanature</nature>\r
+       </natures>\r
+</projectDescription>\r
diff --git a/samples/android/camera-calibration/.settings/org.eclipse.jdt.core.prefs b/samples/android/camera-calibration/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..48ab4c6
--- /dev/null
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6\r
+org.eclipse.jdt.core.compiler.compliance=1.6\r
+org.eclipse.jdt.core.compiler.source=1.6\r
diff --git a/samples/android/camera-calibration/AndroidManifest.xml b/samples/android/camera-calibration/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..d47576a
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"\r
+         package="org.opencv.samples.cameracalibration"\r
+         android:versionCode="1"\r
+         android:versionName="1.0" >\r
+\r
+    <application
+        android:label="@string/app_name"\r
+        android:icon="@drawable/icon"\r
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        \r
+        <activity android:name="CameraCalibrationActivity"\r
+                 android:label="@string/app_name"
+                  android:screenOrientation="landscape"
+                  android:configChanges="keyboardHidden|orientation" >\r
+            <intent-filter>\r
+                <action android:name="android.intent.action.MAIN" />\r
+                <category android:name="android.intent.category.LAUNCHER" />\r
+            </intent-filter>\r
+        </activity>\r
+    </application>
+    
+    <supports-screens android:resizeable="true"
+                      android:smallScreens="true"
+                      android:normalScreens="true"
+                      android:largeScreens="true"
+                      android:anyDensity="true" />
+
+    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="11" />
+
+    <uses-permission android:name="android.permission.CAMERA"/>
+
+    <uses-feature android:name="android.hardware.camera" android:required="false"/>
+    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
+    <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
+    <uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>\r
+\r
+</manifest>\r
diff --git a/samples/android/camera-calibration/CMakeLists.txt b/samples/android/camera-calibration/CMakeLists.txt
new file mode 100644 (file)
index 0000000..83b11b3
--- /dev/null
@@ -0,0 +1,6 @@
+set(sample example-camera-calibration)
+
+add_android_project(${sample} "${CMAKE_CURRENT_SOURCE_DIR}" LIBRARY_DEPS ${OpenCV_BINARY_DIR} SDK_TARGET 11 ${ANDROID_SDK_TARGET})
+if(TARGET ${sample})
+  add_dependencies(opencv_android_examples ${sample})
+endif()
diff --git a/samples/android/camera-calibration/res/drawable/icon.png b/samples/android/camera-calibration/res/drawable/icon.png
new file mode 100644 (file)
index 0000000..79ad948
Binary files /dev/null and b/samples/android/camera-calibration/res/drawable/icon.png differ
diff --git a/samples/android/camera-calibration/res/layout/camera_calibration_surface_view.xml b/samples/android/camera-calibration/res/layout/camera_calibration_surface_view.xml
new file mode 100644 (file)
index 0000000..0feccde
--- /dev/null
@@ -0,0 +1,12 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    xmlns:tools="http://schemas.android.com/tools"\r
+    xmlns:opencv="http://schemas.android.com/apk/res-auto"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent" >\r
+\r
+    <org.opencv.android.JavaCameraView\r
+        android:layout_width="fill_parent"\r
+        android:layout_height="fill_parent"\r
+        android:id="@+id/camera_calibration_java_surface_view" />\r
+\r
+</LinearLayout>\r
diff --git a/samples/android/camera-calibration/res/menu/calibration.xml b/samples/android/camera-calibration/res/menu/calibration.xml
new file mode 100644 (file)
index 0000000..9c90f12
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+    <group android:checkableBehavior="single">\r
+        <item android:id="@+id/calibrate"\r
+              android:title="@string/action_calibrate"\r
+              android:showAsAction="ifRoom|withText" />\r
+        <item android:id="@+id/preview_mode"\r
+              android:title="@string/preview_mode">\r
+              <menu>\r
+                  <group android:checkableBehavior="single">\r
+                      <item android:id="@+id/calibration"\r
+                            android:title="@string/calibration"\r
+                            android:checked="true" />\r
+                      <item android:id="@+id/undistortion"\r
+                            android:title="@string/undistortion" />\r
+                      <item android:id="@+id/comparison"\r
+                            android:title="@string/comparison" />\r
+                  </group>\r
+              </menu>\r
+        </item>\r
+    </group>\r
+</menu>\r
diff --git a/samples/android/camera-calibration/res/values/strings.xml b/samples/android/camera-calibration/res/values/strings.xml
new file mode 100644 (file)
index 0000000..e1ce932
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>\r
+\r
+    <string name="app_name">OCV Camera Calibration</string>\r
+    <string name="action_calibrate">Calibrate</string>
+    <string name="calibration">Calibration</string>
+    <string name="undistortion">Undistortion</string>
+    <string name="comparison">Comparison</string>
+    <string name="preview_mode">Preview mode</string>
+    <string name="calibration_successful">Successfully calibrated!\nAvg. re-projection error:</string>
+    <string name="calibration_unsuccessful">Unsuccessful calibration.\nTry again</string>
+    <string name="more_samples">Please, capture more samples</string>
+    <string name="calibrating">Calibrating...</string>
+    <string name="please_wait">Please, wait</string>
+    <string name="original">Original</string>
+    <string name="undistorted">Undistorted</string>\r
+\r
+</resources>\r
diff --git a/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CalibrationResult.java b/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CalibrationResult.java
new file mode 100644 (file)
index 0000000..4b03d59
--- /dev/null
@@ -0,0 +1,69 @@
+package org.opencv.samples.cameracalibration;
+
+import org.opencv.core.Mat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public abstract class CalibrationResult {
+    private static final String TAG = "OCVSample::CalibrationResult";
+
+    private static final int CAMERA_MATRIX_ROWS = 3;
+    private static final int CAMERA_MATRIX_COLS = 3;
+    private static final int DISTORTION_COEFFICIENTS_SIZE = 5;
+
+    public static void save(Activity activity, Mat cameraMatrix, Mat distortionCoefficients) {
+        SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPref.edit();
+
+        double[] cameraMatrixArray = new double[CAMERA_MATRIX_ROWS * CAMERA_MATRIX_COLS];
+        cameraMatrix.get(0,  0, cameraMatrixArray);
+        for (int i = 0; i < CAMERA_MATRIX_ROWS; i++) {
+            for (int j = 0; j < CAMERA_MATRIX_COLS; j++) {
+                Integer id = i * CAMERA_MATRIX_ROWS + j;
+                editor.putFloat(id.toString(), (float)cameraMatrixArray[id]);
+            }
+        }
+
+        double[] distortionCoefficientsArray = new double[DISTORTION_COEFFICIENTS_SIZE];
+        distortionCoefficients.get(0, 0, distortionCoefficientsArray);
+        int shift = CAMERA_MATRIX_ROWS * CAMERA_MATRIX_COLS;
+        for (Integer i = shift; i < DISTORTION_COEFFICIENTS_SIZE + shift; i++) {
+            editor.putFloat(i.toString(), (float)distortionCoefficientsArray[i-shift]);
+        }
+
+        editor.commit();
+        Log.i(TAG, "Saved camera matrix: " + cameraMatrix.dump());
+        Log.i(TAG, "Saved distortion coefficients: " + distortionCoefficients.dump());
+    }
+
+    public static boolean tryLoad(Activity activity, Mat cameraMatrix, Mat distortionCoefficients) {
+        SharedPreferences sharedPref = activity.getPreferences(Context.MODE_PRIVATE);
+        if (sharedPref.getFloat("0", -1) == -1) {
+            Log.i(TAG, "No previous calibration results found");
+            return false;
+        }
+
+        double[] cameraMatrixArray = new double[CAMERA_MATRIX_ROWS * CAMERA_MATRIX_COLS];
+        for (int i = 0; i < CAMERA_MATRIX_ROWS; i++) {
+            for (int j = 0; j < CAMERA_MATRIX_COLS; j++) {
+                Integer id = i * CAMERA_MATRIX_ROWS + j;
+                cameraMatrixArray[id] = sharedPref.getFloat(id.toString(), -1);
+            }
+        }
+        cameraMatrix.put(0, 0, cameraMatrixArray);
+        Log.i(TAG, "Loaded camera matrix: " + cameraMatrix.dump());
+
+        double[] distortionCoefficientsArray = new double[DISTORTION_COEFFICIENTS_SIZE];
+        int shift = CAMERA_MATRIX_ROWS * CAMERA_MATRIX_COLS;
+        for (Integer i = shift; i < DISTORTION_COEFFICIENTS_SIZE + shift; i++) {
+            distortionCoefficientsArray[i - shift] = sharedPref.getFloat(i.toString(), -1);
+        }
+        distortionCoefficients.put(0, 0, distortionCoefficientsArray);
+        Log.i(TAG, "Loaded distortion coefficients: " + distortionCoefficients.dump());
+
+        return true;
+    }
+}
diff --git a/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java b/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrationActivity.java
new file mode 100644 (file)
index 0000000..33c9bbb
--- /dev/null
@@ -0,0 +1,216 @@
+// This sample is based on "Camera calibration With OpenCV" tutorial:
+// http://docs.opencv.org/doc/tutorials/calib3d/camera_calibration/camera_calibration.html
+//
+// It uses standard OpenCV asymmetric circles grid pattern 11x4:
+// https://github.com/Itseez/opencv/blob/2.4/doc/acircles_pattern.png.
+// The results are the camera matrix and 5 distortion coefficients.
+//
+// Tap on highlighted pattern to capture pattern corners for calibration.
+// Move pattern along the whole screen and capture data.
+//
+// When you've captured necessary amount of pattern corners (usually ~20 are enough),
+// press "Calibrate" button for performing camera calibration.
+
+package org.opencv.samples.cameracalibration;
+
+import org.opencv.android.BaseLoaderCallback;
+import org.opencv.android.CameraBridgeViewBase;
+import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
+import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
+import org.opencv.android.LoaderCallbackInterface;
+import org.opencv.android.OpenCVLoader;
+import org.opencv.core.Mat;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.res.Resources;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+public class CameraCalibrationActivity extends Activity implements CvCameraViewListener2, OnTouchListener {
+    private static final String TAG = "OCVSample::Activity";
+
+    private CameraBridgeViewBase mOpenCvCameraView;
+    private CameraCalibrator mCalibrator;
+    private OnCameraFrameRender mOnCameraFrameRender;
+    private int mWidth;
+    private int mHeight;
+
+    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
+        @Override
+        public void onManagerConnected(int status) {
+            switch (status) {
+            case LoaderCallbackInterface.SUCCESS:
+            {
+                Log.i(TAG, "OpenCV loaded successfully");
+                mOpenCvCameraView.enableView();
+                mOpenCvCameraView.setOnTouchListener(CameraCalibrationActivity.this);
+            } break;
+            default:
+            {
+                super.onManagerConnected(status);
+            } break;
+            }
+        }
+    };
+
+    public CameraCalibrationActivity() {
+        Log.i(TAG, "Instantiated new " + this.getClass());
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "called onCreate");
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        setContentView(R.layout.camera_calibration_surface_view);
+
+        mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.camera_calibration_java_surface_view);
+        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
+        mOpenCvCameraView.setCvCameraViewListener(this);
+    }
+
+    @Override
+    public void onPause()
+    {
+        super.onPause();
+        if (mOpenCvCameraView != null)
+            mOpenCvCameraView.disableView();
+    }
+
+    @Override
+    public void onResume()
+    {
+        super.onResume();
+        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_2, this, mLoaderCallback);
+    }
+
+    public void onDestroy() {
+        super.onDestroy();
+        if (mOpenCvCameraView != null)
+            mOpenCvCameraView.disableView();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.calibration, menu);
+
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu (Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        menu.findItem(R.id.preview_mode).setEnabled(true);
+        if (!mCalibrator.isCalibrated())
+            menu.findItem(R.id.preview_mode).setEnabled(false);
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case R.id.calibration:
+            mOnCameraFrameRender =
+                new OnCameraFrameRender(new CalibrationFrameRender(mCalibrator));
+            item.setChecked(true);
+            return true;
+        case R.id.undistortion:
+            mOnCameraFrameRender =
+                new OnCameraFrameRender(new UndistortionFrameRender(mCalibrator));
+            item.setChecked(true);
+            return true;
+        case R.id.comparison:
+            mOnCameraFrameRender =
+                new OnCameraFrameRender(new ComparisonFrameRender(mCalibrator, mWidth, mHeight, getResources()));
+            item.setChecked(true);
+            return true;
+        case R.id.calibrate:
+            final Resources res = getResources();
+            if (mCalibrator.getCornersBufferSize() < 2) {
+                (Toast.makeText(this, res.getString(R.string.more_samples), Toast.LENGTH_SHORT)).show();
+                return true;
+            }
+
+            mOnCameraFrameRender = new OnCameraFrameRender(new PreviewFrameRender());
+            new AsyncTask<Void, Void, Void>() {
+                private ProgressDialog calibrationProgress;
+
+                @Override
+                protected void onPreExecute() {
+                    calibrationProgress = new ProgressDialog(CameraCalibrationActivity.this);
+                    calibrationProgress.setTitle(res.getString(R.string.calibrating));
+                    calibrationProgress.setMessage(res.getString(R.string.please_wait));
+                    calibrationProgress.setCancelable(false);
+                    calibrationProgress.setIndeterminate(true);
+                    calibrationProgress.show();
+                }
+
+                @Override
+                protected Void doInBackground(Void... arg0) {
+                    mCalibrator.calibrate();
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Void result) {
+                    calibrationProgress.dismiss();
+                    mCalibrator.clearCorners();
+                    mOnCameraFrameRender = new OnCameraFrameRender(new CalibrationFrameRender(mCalibrator));
+                    String resultMessage = (mCalibrator.isCalibrated()) ?
+                            res.getString(R.string.calibration_successful)  + " " + mCalibrator.getAvgReprojectionError() :
+                            res.getString(R.string.calibration_unsuccessful);
+                    (Toast.makeText(CameraCalibrationActivity.this, resultMessage, Toast.LENGTH_SHORT)).show();
+
+                    if (mCalibrator.isCalibrated()) {
+                        CalibrationResult.save(CameraCalibrationActivity.this,
+                                mCalibrator.getCameraMatrix(), mCalibrator.getDistortionCoefficients());
+                    }
+                }
+            }.execute();
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    public void onCameraViewStarted(int width, int height) {
+        if (mWidth != width || mHeight != height) {
+            mWidth = width;
+            mHeight = height;
+            mCalibrator = new CameraCalibrator(mWidth, mHeight);
+            if (CalibrationResult.tryLoad(this, mCalibrator.getCameraMatrix(), mCalibrator.getDistortionCoefficients())) {
+                mCalibrator.setCalibrated();
+            }
+
+            mOnCameraFrameRender = new OnCameraFrameRender(new CalibrationFrameRender(mCalibrator));
+        }
+    }
+
+    public void onCameraViewStopped() {
+    }
+
+    public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
+        return mOnCameraFrameRender.render(inputFrame);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        Log.d(TAG, "onTouch invoked");
+
+        mCalibrator.addCorners();
+        return false;
+    }
+}
diff --git a/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrator.java b/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/CameraCalibrator.java
new file mode 100644 (file)
index 0000000..f0cd230
--- /dev/null
@@ -0,0 +1,169 @@
+package org.opencv.samples.cameracalibration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opencv.calib3d.Calib3d;
+import org.opencv.core.Core;
+import org.opencv.core.CvType;
+import org.opencv.core.Mat;
+import org.opencv.core.MatOfDouble;
+import org.opencv.core.MatOfPoint2f;
+import org.opencv.core.MatOfPoint3f;
+import org.opencv.core.Point;
+import org.opencv.core.Scalar;
+import org.opencv.core.Size;
+
+import android.util.Log;
+
+public class CameraCalibrator {
+    private static final String TAG = "OCVSample::CameraCalibrator";
+
+    private final Size mPatternSize = new Size(4, 11);
+    private final int mCornersSize = (int)(mPatternSize.width * mPatternSize.height);
+    private boolean mPatternWasFound = false;
+    private MatOfPoint2f mCorners = new MatOfPoint2f();
+    private List<Mat> mCornersBuffer = new ArrayList<Mat>();
+    private boolean mIsCalibrated = false;
+
+    private Mat mCameraMatrix = new Mat();
+    private Mat mDistortionCoefficients = new Mat();
+    private int mFlags;
+    private double mRms;
+    private double mSquareSize = 0.0181;
+    private Size mImageSize;
+
+    public CameraCalibrator(int width, int height) {
+        mImageSize = new Size(width, height);
+        mFlags = Calib3d.CALIB_FIX_PRINCIPAL_POINT +
+                 Calib3d.CALIB_ZERO_TANGENT_DIST +
+                 Calib3d.CALIB_FIX_ASPECT_RATIO +
+                 Calib3d.CALIB_FIX_K4 +
+                 Calib3d.CALIB_FIX_K5;
+        Mat.eye(3, 3, CvType.CV_64FC1).copyTo(mCameraMatrix);
+        mCameraMatrix.put(0, 0, 1.0);
+        Mat.zeros(5, 1, CvType.CV_64FC1).copyTo(mDistortionCoefficients);
+        Log.i(TAG, "Instantiated new " + this.getClass());
+    }
+
+    public void processFrame(Mat grayFrame, Mat rgbaFrame) {
+        findPattern(grayFrame);
+        renderFrame(rgbaFrame);
+    }
+
+    public void calibrate() {
+        ArrayList<Mat> rvecs = new ArrayList<Mat>();
+        ArrayList<Mat> tvecs = new ArrayList<Mat>();
+        Mat reprojectionErrors = new Mat();
+        ArrayList<Mat> objectPoints = new ArrayList<Mat>();
+        objectPoints.add(Mat.zeros(mCornersSize, 1, CvType.CV_32FC3));
+        calcBoardCornerPositions(objectPoints.get(0));
+        for (int i = 1; i < mCornersBuffer.size(); i++) {
+            objectPoints.add(objectPoints.get(0));
+        }
+
+        Calib3d.calibrateCamera(objectPoints, mCornersBuffer, mImageSize,
+                mCameraMatrix, mDistortionCoefficients, rvecs, tvecs, mFlags);
+
+        mIsCalibrated = Core.checkRange(mCameraMatrix)
+                && Core.checkRange(mDistortionCoefficients);
+
+        mRms = computeReprojectionErrors(objectPoints, rvecs, tvecs, reprojectionErrors);
+        Log.i(TAG, String.format("Average re-projection error: %f", mRms));
+        Log.i(TAG, "Camera matrix: " + mCameraMatrix.dump());
+        Log.i(TAG, "Distortion coefficients: " + mDistortionCoefficients.dump());
+    }
+
+    public void clearCorners() {
+        mCornersBuffer.clear();
+    }
+
+    private void calcBoardCornerPositions(Mat corners) {
+        final int cn = 3;
+        float positions[] = new float[mCornersSize * cn];
+
+        for (int i = 0; i < mPatternSize.height; i++) {
+            for (int j = 0; j < mPatternSize.width * cn; j += cn) {
+                positions[(int) (i * mPatternSize.width * cn + j + 0)] =
+                        (2 * (j / cn) + i % 2) * (float) mSquareSize;
+                positions[(int) (i * mPatternSize.width * cn + j + 1)] =
+                        i * (float) mSquareSize;
+                positions[(int) (i * mPatternSize.width * cn + j + 2)] = 0;
+            }
+        }
+        corners.create(mCornersSize, 1, CvType.CV_32FC3);
+        corners.put(0, 0, positions);
+    }
+
+    private double computeReprojectionErrors(List<Mat> objectPoints,
+            List<Mat> rvecs, List<Mat> tvecs, Mat perViewErrors) {
+        MatOfPoint2f cornersProjected = new MatOfPoint2f();
+        double totalError = 0;
+        double error;
+        float viewErrors[] = new float[objectPoints.size()];
+
+        MatOfDouble distortionCoefficients = new MatOfDouble(mDistortionCoefficients);
+        int totalPoints = 0;
+        for (int i = 0; i < objectPoints.size(); i++) {
+            MatOfPoint3f points = new MatOfPoint3f(objectPoints.get(i));
+            Calib3d.projectPoints(points, rvecs.get(i), tvecs.get(i),
+                    mCameraMatrix, distortionCoefficients, cornersProjected);
+            error = Core.norm(mCornersBuffer.get(i), cornersProjected, Core.NORM_L2);
+
+            int n = objectPoints.get(i).rows();
+            viewErrors[i] = (float) Math.sqrt(error * error / n);
+            totalError  += error * error;
+            totalPoints += n;
+        }
+        perViewErrors.create(objectPoints.size(), 1, CvType.CV_32FC1);
+        perViewErrors.put(0, 0, viewErrors);
+
+        return Math.sqrt(totalError / totalPoints);
+    }
+
+    private void findPattern(Mat grayFrame) {
+        mPatternWasFound = Calib3d.findCirclesGridDefault(grayFrame, mPatternSize,
+                mCorners, Calib3d.CALIB_CB_ASYMMETRIC_GRID);
+    }
+
+    public void addCorners() {
+        if (mPatternWasFound) {
+            mCornersBuffer.add(mCorners.clone());
+        }
+    }
+
+    private void drawPoints(Mat rgbaFrame) {
+        Calib3d.drawChessboardCorners(rgbaFrame, mPatternSize, mCorners, mPatternWasFound);
+    }
+
+    private void renderFrame(Mat rgbaFrame) {
+        drawPoints(rgbaFrame);
+
+        Core.putText(rgbaFrame, "Captured: " + mCornersBuffer.size(), new Point(rgbaFrame.cols() / 3 * 2, rgbaFrame.rows() * 0.1),
+                Core.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(255, 255, 0));
+    }
+
+    public Mat getCameraMatrix() {
+        return mCameraMatrix;
+    }
+
+    public Mat getDistortionCoefficients() {
+        return mDistortionCoefficients;
+    }
+
+    public int getCornersBufferSize() {
+        return mCornersBuffer.size();
+    }
+
+    public double getAvgReprojectionError() {
+        return mRms;
+    }
+
+    public boolean isCalibrated() {
+        return mIsCalibrated;
+    }
+
+    public void setCalibrated() {
+        mIsCalibrated = true;
+    }
+}
diff --git a/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/OnCameraFrameRender.java b/samples/android/camera-calibration/src/org/opencv/samples/cameracalibration/OnCameraFrameRender.java
new file mode 100644 (file)
index 0000000..3f155c2
--- /dev/null
@@ -0,0 +1,102 @@
+package org.opencv.samples.cameracalibration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
+import org.opencv.core.Core;
+import org.opencv.core.Mat;
+import org.opencv.core.MatOfPoint;
+import org.opencv.core.Point;
+import org.opencv.core.Range;
+import org.opencv.core.Scalar;
+import org.opencv.imgproc.Imgproc;
+
+import android.content.res.Resources;
+
+abstract class FrameRender {
+    protected CameraCalibrator mCalibrator;
+
+    public abstract Mat render(CvCameraViewFrame inputFrame);
+}
+
+class PreviewFrameRender extends FrameRender {
+    @Override
+    public Mat render(CvCameraViewFrame inputFrame) {
+        return inputFrame.rgba();
+    }
+}
+
+class CalibrationFrameRender extends FrameRender {
+    public CalibrationFrameRender(CameraCalibrator calibrator) {
+        mCalibrator = calibrator;
+    }
+
+    @Override
+    public Mat render(CvCameraViewFrame inputFrame) {
+        Mat rgbaFrame = inputFrame.rgba();
+        Mat grayFrame = inputFrame.gray();
+        mCalibrator.processFrame(grayFrame, rgbaFrame);
+
+        return rgbaFrame;
+    }
+}
+
+class UndistortionFrameRender extends FrameRender {
+    public UndistortionFrameRender(CameraCalibrator calibrator) {
+        mCalibrator = calibrator;
+    }
+
+    @Override
+    public Mat render(CvCameraViewFrame inputFrame) {
+        Mat renderedFrame = new Mat(inputFrame.rgba().size(), inputFrame.rgba().type());
+        Imgproc.undistort(inputFrame.rgba(), renderedFrame,
+                mCalibrator.getCameraMatrix(), mCalibrator.getDistortionCoefficients());
+
+        return renderedFrame;
+    }
+}
+
+class ComparisonFrameRender extends FrameRender {
+    private int mWidth;
+    private int mHeight;
+    private Resources mResources;
+    public ComparisonFrameRender(CameraCalibrator calibrator, int width, int height, Resources resources) {
+        mCalibrator = calibrator;
+        mWidth = width;
+        mHeight = height;
+        mResources = resources;
+    }
+
+    @Override
+    public Mat render(CvCameraViewFrame inputFrame) {
+        Mat undistortedFrame = new Mat(inputFrame.rgba().size(), inputFrame.rgba().type());
+        Imgproc.undistort(inputFrame.rgba(), undistortedFrame,
+                mCalibrator.getCameraMatrix(), mCalibrator.getDistortionCoefficients());
+
+        Mat comparisonFrame = inputFrame.rgba();
+        undistortedFrame.colRange(new Range(0, mWidth / 2)).copyTo(comparisonFrame.colRange(new Range(mWidth / 2, mWidth)));
+        List<MatOfPoint> border = new ArrayList<MatOfPoint>();
+        final int shift = (int)(mWidth * 0.005);
+        border.add(new MatOfPoint(new Point(mWidth / 2 - shift, 0), new Point(mWidth / 2 + shift, 0),
+                new Point(mWidth / 2 + shift, mHeight), new Point(mWidth / 2 - shift, mHeight)));
+        Core.fillPoly(comparisonFrame, border, new Scalar(255, 255, 255));
+
+        Core.putText(comparisonFrame, mResources.getString(R.string.original), new Point(mWidth * 0.1, mHeight * 0.1),
+                Core.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(255, 255, 0));
+        Core.putText(comparisonFrame, mResources.getString(R.string.undistorted), new Point(mWidth * 0.6, mHeight * 0.1),
+                Core.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(255, 255, 0));
+
+        return comparisonFrame;
+    }
+}
+
+class OnCameraFrameRender {
+    private FrameRender mFrameRender;
+    public OnCameraFrameRender(FrameRender frameRender) {
+        mFrameRender = frameRender;
+    }
+    public Mat render(CvCameraViewFrame inputFrame) {
+        return mFrameRender.render(inputFrame);
+    }
+}