499 lines
12 KiB
Java
499 lines
12 KiB
Java
package com.theveganrobot.cvcamera;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.util.LinkedList;
|
|
import java.util.Scanner;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.app.ProgressDialog;
|
|
import android.content.DialogInterface;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.os.Bundle;
|
|
import android.os.Environment;
|
|
import android.util.Log;
|
|
import android.view.Gravity;
|
|
import android.view.KeyEvent;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.Window;
|
|
import android.view.WindowManager;
|
|
import android.view.ViewGroup.LayoutParams;
|
|
import android.widget.AnalogClock;
|
|
import android.widget.Button;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ImageButton;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.Toast;
|
|
|
|
import com.opencv.camera.NativePreviewer;
|
|
import com.opencv.camera.NativeProcessor;
|
|
import com.opencv.camera.NativeProcessor.PoolCallback;
|
|
import com.opencv.jni.image_pool;
|
|
import com.opencv.opengl.GL2CameraViewer;
|
|
import com.theveganrobot.cvcamera.jni.Processor;
|
|
import com.theveganrobot.cvcamera.jni.cvcamera;
|
|
|
|
public class CVCamera extends Activity {
|
|
|
|
static final int DIALOG_CALIBRATING = 0;
|
|
static final int DIALOG_CALIBRATION_FILE = 1;
|
|
private static final int DIALOG_OPENING_TUTORIAL = 2;
|
|
private static final int DIALOG_TUTORIAL_FAST = 3;
|
|
private static final int DIALOG_TUTORIAL_SURF = 4;
|
|
private static final int DIALOG_TUTORIAL_STAR = 5;
|
|
private static final int DIALOG_TUTORIAL_CHESS = 6;
|
|
private boolean captureChess;
|
|
|
|
ProgressDialog makeCalibDialog() {
|
|
ProgressDialog progressDialog;
|
|
progressDialog = new ProgressDialog(this);
|
|
progressDialog.setMessage("Callibrating. Please wait...");
|
|
progressDialog.setCancelable(false);
|
|
|
|
return progressDialog;
|
|
}
|
|
|
|
void toasts(int id) {
|
|
switch (id) {
|
|
case DIALOG_OPENING_TUTORIAL:
|
|
Toast.makeText(this, "Try clicking the menu for CV options.",
|
|
Toast.LENGTH_LONG).show();
|
|
break;
|
|
case DIALOG_TUTORIAL_FAST:
|
|
Toast.makeText(this, "Detecting and Displaying FAST features",
|
|
Toast.LENGTH_LONG).show();
|
|
break;
|
|
case DIALOG_TUTORIAL_SURF:
|
|
Toast.makeText(this, "Detecting and Displaying SURF features",
|
|
Toast.LENGTH_LONG).show();
|
|
break;
|
|
case DIALOG_TUTORIAL_STAR:
|
|
Toast.makeText(this, "Detecting and Displaying STAR features",
|
|
Toast.LENGTH_LONG).show();
|
|
break;
|
|
case DIALOG_TUTORIAL_CHESS:
|
|
Toast
|
|
.makeText(
|
|
this,
|
|
"Calibration Mode, Point at a chessboard pattern and press the camera button, space,"
|
|
+ "or the DPAD to capture.",
|
|
Toast.LENGTH_LONG).show();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
protected Dialog onCreateDialog(int id) {
|
|
Dialog dialog;
|
|
switch (id) {
|
|
case DIALOG_CALIBRATING:
|
|
dialog = makeCalibDialog();
|
|
break;
|
|
|
|
case DIALOG_CALIBRATION_FILE:
|
|
dialog = makeCalibFileAlert();
|
|
break;
|
|
default:
|
|
dialog = null;
|
|
}
|
|
return dialog;
|
|
}
|
|
|
|
private Dialog makeCalibFileAlert() {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setMessage(calib_text).setTitle(
|
|
"camera.yml at " + calib_file_loc).setCancelable(false)
|
|
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
|
|
}
|
|
});
|
|
AlertDialog alert = builder.create();
|
|
return alert;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see android.app.Activity#onKeyUp(int, android.view.KeyEvent)
|
|
*/
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
|
|
switch (keyCode) {
|
|
case KeyEvent.KEYCODE_CAMERA:
|
|
case KeyEvent.KEYCODE_SPACE:
|
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
|
captureChess = true;
|
|
return true;
|
|
|
|
default:
|
|
return super.onKeyUp(keyCode, event);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see android.app.Activity#onKeyLongPress(int, android.view.KeyEvent)
|
|
*/
|
|
@Override
|
|
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
|
|
|
return super.onKeyLongPress(keyCode, event);
|
|
}
|
|
|
|
/**
|
|
* Avoid that the screen get's turned off by the system.
|
|
*/
|
|
public void disableScreenTurnOff() {
|
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
|
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
}
|
|
|
|
/**
|
|
* Set's the orientation to landscape, as this is needed by AndAR.
|
|
*/
|
|
public void setOrientation() {
|
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
}
|
|
|
|
/**
|
|
* Maximize the application.
|
|
*/
|
|
public void setFullscreen() {
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
}
|
|
|
|
public void setNoTitle() {
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
menu.add("FAST");
|
|
menu.add("STAR");
|
|
menu.add("SURF");
|
|
menu.add("Chess");
|
|
return true;
|
|
}
|
|
|
|
private NativePreviewer mPreview;
|
|
private GL2CameraViewer glview;
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
LinkedList<PoolCallback> defaultcallbackstack = new LinkedList<PoolCallback>();
|
|
defaultcallbackstack.addFirst(glview.getDrawCallback());
|
|
if (item.getTitle().equals("FAST")) {
|
|
|
|
defaultcallbackstack.addFirst(new FastProcessor());
|
|
toasts(DIALOG_TUTORIAL_FAST);
|
|
}
|
|
|
|
else if (item.getTitle().equals("Chess")) {
|
|
|
|
defaultcallbackstack.addFirst(new CalibrationProcessor());
|
|
toasts(DIALOG_TUTORIAL_CHESS);
|
|
|
|
}
|
|
|
|
else if (item.getTitle().equals("STAR")) {
|
|
|
|
defaultcallbackstack.addFirst(new STARProcessor());
|
|
toasts(DIALOG_TUTORIAL_STAR);
|
|
|
|
}
|
|
|
|
else if (item.getTitle().equals("SURF")) {
|
|
|
|
defaultcallbackstack.addFirst(new SURFProcessor());
|
|
toasts(DIALOG_TUTORIAL_SURF);
|
|
|
|
}
|
|
|
|
mPreview.addCallbackStack(defaultcallbackstack);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onOptionsMenuClosed(Menu menu) {
|
|
// TODO Auto-generated method stub
|
|
super.onOptionsMenuClosed(menu);
|
|
}
|
|
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
setFullscreen();
|
|
disableScreenTurnOff();
|
|
|
|
FrameLayout frame = new FrameLayout(this);
|
|
|
|
// Create our Preview view and set it as the content of our activity.
|
|
mPreview = new NativePreviewer(getApplication(), 640, 480);
|
|
|
|
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
|
|
LayoutParams.WRAP_CONTENT);
|
|
params.height = getWindowManager().getDefaultDisplay().getHeight();
|
|
params.width = (int) (params.height * 4.0 / 2.88);
|
|
|
|
LinearLayout vidlay = new LinearLayout(getApplication());
|
|
|
|
vidlay.setGravity(Gravity.CENTER);
|
|
vidlay.addView(mPreview, params);
|
|
frame.addView(vidlay);
|
|
|
|
// make the glview overlay ontop of video preview
|
|
mPreview.setZOrderMediaOverlay(false);
|
|
|
|
glview = new GL2CameraViewer(getApplication(), false, 0, 0);
|
|
glview.setZOrderMediaOverlay(true);
|
|
glview.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
|
|
LayoutParams.FILL_PARENT));
|
|
|
|
ImageButton capture_button = new ImageButton(getApplicationContext());
|
|
capture_button.setImageDrawable(getResources().getDrawable(android.R.drawable.ic_menu_camera));
|
|
capture_button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
|
|
LayoutParams.WRAP_CONTENT));
|
|
capture_button.setOnClickListener(new View.OnClickListener() {
|
|
|
|
|
|
@Override
|
|
public void onClick(View v) {
|
|
captureChess = true;
|
|
|
|
}
|
|
});
|
|
|
|
LinearLayout buttons = new LinearLayout(getApplicationContext());
|
|
buttons.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
|
|
LayoutParams.WRAP_CONTENT));
|
|
|
|
buttons.addView(capture_button);
|
|
|
|
Button focus_button = new Button(getApplicationContext());
|
|
focus_button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
|
|
LayoutParams.WRAP_CONTENT));
|
|
focus_button.setText("Focus");
|
|
focus_button.setOnClickListener(new View.OnClickListener() {
|
|
|
|
@Override
|
|
public void onClick(View v) {
|
|
mPreview.postautofocus(100);
|
|
}
|
|
});
|
|
buttons.addView(focus_button);
|
|
|
|
frame.addView(glview);
|
|
|
|
frame.addView(buttons);
|
|
setContentView(frame);
|
|
toasts(DIALOG_OPENING_TUTORIAL);
|
|
|
|
}
|
|
|
|
@Override
|
|
public boolean onTrackballEvent(MotionEvent event) {
|
|
if(event.getAction() == MotionEvent.ACTION_UP){
|
|
captureChess = true;
|
|
return true;
|
|
}
|
|
return super.onTrackballEvent(event);
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
|
|
|
|
//clears the callback stack
|
|
mPreview.onPause();
|
|
|
|
glview.onPause();
|
|
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
glview.onResume();
|
|
|
|
//add an initiall callback stack to the preview on resume...
|
|
//this one will just draw the frames to opengl
|
|
LinkedList<NativeProcessor.PoolCallback> cbstack = new LinkedList<PoolCallback>();
|
|
cbstack.add(glview.getDrawCallback());
|
|
mPreview.addCallbackStack(cbstack);
|
|
mPreview.onResume();
|
|
|
|
}
|
|
|
|
// final processor so taht these processor callbacks can access it
|
|
final Processor processor = new Processor();
|
|
|
|
class FastProcessor implements NativeProcessor.PoolCallback {
|
|
|
|
@Override
|
|
public void process(int idx, image_pool pool, long timestamp,
|
|
NativeProcessor nativeProcessor) {
|
|
processor.detectAndDrawFeatures(idx, pool, cvcamera.DETECT_FAST);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class STARProcessor implements NativeProcessor.PoolCallback {
|
|
|
|
@Override
|
|
public void process(int idx, image_pool pool, long timestamp,
|
|
NativeProcessor nativeProcessor) {
|
|
processor.detectAndDrawFeatures(idx, pool, cvcamera.DETECT_STAR);
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class SURFProcessor implements NativeProcessor.PoolCallback {
|
|
|
|
@Override
|
|
public void process(int idx, image_pool pool, long timestamp,
|
|
NativeProcessor nativeProcessor) {
|
|
processor.detectAndDrawFeatures(idx, pool, cvcamera.DETECT_SURF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
String calib_text = null;
|
|
String calib_file_loc = null;
|
|
|
|
class CalibrationProcessor implements NativeProcessor.PoolCallback {
|
|
|
|
boolean calibrated = false;
|
|
|
|
@Override
|
|
public void process(int idx, image_pool pool, long timestamp,
|
|
NativeProcessor nativeProcessor) {
|
|
|
|
if (calibrated) {
|
|
processor.drawText(idx, pool, "Calibrated successfully");
|
|
return;
|
|
}
|
|
if (processor.getNumberDetectedChessboards() == 10) {
|
|
|
|
File opencvdir = new File(Environment
|
|
.getExternalStorageDirectory(), "opencv");
|
|
if (!opencvdir.exists()) {
|
|
opencvdir.mkdir();
|
|
}
|
|
File calibfile = new File(opencvdir, "camera.yml");
|
|
|
|
calib_file_loc = calibfile.getAbsolutePath();
|
|
processor.calibrate(calibfile.getAbsolutePath());
|
|
Log.i("chessboard", "calibrated");
|
|
calibrated = true;
|
|
processor.resetChess();
|
|
runOnUiThread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
removeDialog(DIALOG_CALIBRATING);
|
|
|
|
}
|
|
});
|
|
|
|
try {
|
|
|
|
StringBuilder text = new StringBuilder();
|
|
String NL = System.getProperty("line.separator");
|
|
Scanner scanner = new Scanner(calibfile);
|
|
|
|
try {
|
|
while (scanner.hasNextLine()) {
|
|
text.append(scanner.nextLine() + NL);
|
|
}
|
|
} finally {
|
|
scanner.close();
|
|
}
|
|
|
|
calib_text = text.toString();
|
|
|
|
runOnUiThread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
showDialog(DIALOG_CALIBRATION_FILE);
|
|
|
|
}
|
|
});
|
|
|
|
} catch (FileNotFoundException e) {
|
|
// TODO Auto-generated catch block
|
|
e.printStackTrace();
|
|
}
|
|
|
|
} else if (captureChess
|
|
&& processor.detectAndDrawChessboard(idx, pool)) {
|
|
|
|
runOnUiThread(new Runnable() {
|
|
|
|
String numchess = String.valueOf(processor
|
|
.getNumberDetectedChessboards());
|
|
|
|
@Override
|
|
public void run() {
|
|
Toast.makeText(CVCamera.this,
|
|
"Detected " + numchess + " of 10 chessboards",
|
|
Toast.LENGTH_SHORT).show();
|
|
|
|
}
|
|
});
|
|
Log.i("cvcamera",
|
|
"detected a chessboard, n chess boards found: "
|
|
+ String.valueOf(processor
|
|
.getNumberDetectedChessboards()));
|
|
|
|
}
|
|
|
|
captureChess = false;
|
|
|
|
if (processor.getNumberDetectedChessboards() == 10) {
|
|
runOnUiThread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
showDialog(DIALOG_CALIBRATING);
|
|
|
|
}
|
|
});
|
|
|
|
processor.drawText(idx, pool, "Calibrating, please wait.");
|
|
}
|
|
if (processor.getNumberDetectedChessboards() < 10) {
|
|
|
|
processor.drawText(idx, pool, "found "
|
|
+ processor.getNumberDetectedChessboards()
|
|
+ "/10 chessboards");
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|