——让你的OpenCV程序远离Android
关于环境的搭建,参考我前一篇博客:
第一个OpenCV4Android
程序目标:实现相机对周围环境的实时检测,要求OpenCV处理代码脱离Android代码已经JNI代码
1、Camera线程
在写C++代码时,我们会开启一个新的线程,在线程中cvqueryframe实时的得到每一帧的数据再进行处理,那么按照这个思路我们在Android使用相机也要开启一个新的线程。
思考了很久怎样在Android新线程中(非UI线程)中嵌入OpenCV打开相机的例子,其实,这一点org.opencv.android.CameraBridgeViewBase类中已经帮我们(通过继承)解决了。我们可以键入如下测试代码:
protected void onCreate(Bundle savedInstanceState) { Log.i(TAG,"called onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mOpenCvCameraView=(CameraBridgeViewBase)findViewById(R.id.HelloOpenCvView); mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE); mOpenCvCameraView.setCvCameraViewListener(this); btn=(Button)findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener(){ public void onClick(View view){ doCany=!doCany; System.out.println("OnClickListener -----"+Thread.currentThread().getId()); } }); }
public Mat onCameraFrame(CvCameraViewFrame inputFrame){ System.out.println("onCameraFrame -----"+Thread.currentThread().getId()); return inputFrame.rgba(); }
第一个println位于我们响应UI界面的OnCreatea函数,二个println位于我们响应CameraViewListener的事件响应函数中。
在LogCat中我们新建 Tag=System.out的过滤器,我们会得到下列输出:
可以看到输出的线程ID并不同,那么显然CameraBridgeViewBase中已经帮我们解决了多线程的问题,我们没有必要再开一个新的线程去处理相机的每一帧数据。直接在onCameraFrame(CvCameraViewFrameinputFrame)中加入处理每一帧图像的相关代码即可。
2、程序框架
3、程序源码
Activity_main.xml
注意android:layout_width、android:layout_height不能为任意值
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:opencv="http://schemas.android.com/apk/res-auto" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <org.opencv.android.JavaCameraView android:layout_width="320px" android:layout_height="280px" android:visibility="gone" android:id="@+id/HelloOpenCvView" opencv:show_fps="true" opencv:camera_id="any"/> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/HelloOpenCvView" android:text="Button" /> </LinearLayout>
MainActivity.java
public class MainActivity extends Activity implements CvCameraViewListener2 { private CameraBridgeViewBase mOpenCvCameraView; private boolean mIsJavaCamera = true; private boolean doCany=false; private Button btn; private static final String TAG = "OCVSample::Activity"; private ProcessImg nativeProcess; String xmlFile="heh"; //当加载OpenCV库成功后的回调函数 private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: { Log.i(TAG, "OpenCV loaded successfully"); //加载本地库 System.loadLibrary("ProcessImg"); // mOpenCvCameraView.enableView(); nativeProcess=new ProcessImg(xmlFile); } break; default: { super.onManagerConnected(status); } break; } } }; @SuppressWarnings("deprecation") @Override protected void onCreate(Bundle savedInstanceState) { Log.i(TAG,"called onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (mIsJavaCamera) mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.HelloOpenCvView); else mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.HelloOpenCvView); mOpenCvCameraView=(CameraBridgeViewBase)findViewById(R.id.HelloOpenCvView); mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE); //设置CameraView的事件监听 mOpenCvCameraView.setCvCameraViewListener(this); btn=(Button)findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener(){ public void onClick(View view){ doCany=!doCany; System.out.println("OnClickListener -----"+Thread.currentThread().getId()); } }); } public void onPause() { super.onPause(); if(mOpenCvCameraView!=null){ mOpenCvCameraView.disableView(); } } public void onResume() { super.onResume(); System.out.println("onResume -----"+Thread.currentThread().getId()); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this, mLoaderCallback); } public void onDestroy(){ super.onDestroy(); if(mOpenCvCameraView!=null){ mOpenCvCameraView.disableView(); } } @Override //当相机开始工作后触发该函数,之后会调用onCameraFrame()函数 public void onCameraViewStarted(int width, int height) { // TODO Auto-generated method stub } public void onCameraViewStopped(){ } //接收每一帧图像,并进行需要的处理 public Mat onCameraFrame(CvCameraViewFrame inputFrame){ System.out.println("onCameraFrame -----"+Thread.currentThread().getId()); if(doCany==true){ Mat CannyMat=inputFrame.gray(); inputFrame.gray().copyTo(CannyMat); nativeProcess.SignDetect(CannyMat); return CannyMat; } else { return inputFrame.rgba(); } } }
Process.java
public class ProcessImg { public ProcessImg(String xmlFile){ mNativeObj = nativeCreateObject(xmlFile); } public void SignDetect(Mat input){ nativeSignDetect(mNativeObj,input.getNativeObjAddr()); } private long mNativeObj = 0; private static native long nativeCreateObject(String cascadeName); private static native void nativeSignDetect(long thiz,long inputImage); }
ProcessImg.h自动生成,不在论述。
ProcessImg.cpp
#include "ProcessImg.h" #include <opencv2/core/core.hpp> #include <opencv/cv.hpp> #include <opencv2/highgui/highgui.hpp> #include <string> #include <vector> #include <android/log.h> #include "TrafficSign.h" using namespace std; using namespace cv; extern "C" { JNIEXPORT jlong JNICALL Java_com_pan_helloopencv_ProcessImg_nativeCreateObject (JNIEnv *jenv, jclass, jstring jFileName) { jlong result = 0; //将jstring 转换为 char* const char* xmlFileName = jenv->GetStringUTFChars(jFileName,NULL); //string xmlFile(xmlFileName); try { //创建图像处理对象(由TrafficSign.h定义) result = (jlong)new TrafficSignDetection(xmlFileName); return result; } catch(cv::Exception& e) { // LOGD("nativeCreateObject caught cv::Exception: %s", e.what()); jclass je = jenv->FindClass("org/opencv/core/CvException"); if(!je) je = jenv->FindClass("java/lang/Exception"); jenv->ThrowNew(je, e.what()); } catch (...) { // LOGD("nativeCreateObject caught unknown exception"); jclass je = jenv->FindClass("java/lang/Exception"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); return 0; } } /* * Class: com_pan_helloopencv_ProcessImg * Method: nativeSignDetect * Signature: (JJ)V */ JNIEXPORT void JNICALL Java_com_pan_helloopencv_ProcessImg_nativeSignDetect (JNIEnv *jenv, jclass, jlong thiz, jlong inputImage) { try { //处理每一帧图像 ((TrafficSignDetection*)thiz)->process(*((Mat*)inputImage)); } catch(cv::Exception& e) { //LOGD("nativeCreateObject caught cv::Exception: %s", e.what()); jclass je = jenv->FindClass("org/opencv/core/CvException"); if(!je) je = jenv->FindClass("java/lang/Exception"); jenv->ThrowNew(je, e.what()); } catch (...) { // LOGD("nativeDetect caught unknown exception"); jclass je = jenv->FindClass("java/lang/Exception"); jenv->ThrowNew(je, "Unknown exception in JNI code {highgui::VideoCapture_n_1VideoCapture__()}"); } } }
TrafficSign.h
用C++代码实现图像处理的主逻辑
#pragma once #if defined(__linux__) || defined(LINUX) || defined(__APPLE__) || defined(ANDROID) #include <opencv2/core/core.hpp> #include <vector> #include <string> class TrafficSignDetection { public: //需要的参数 struct Parameters { int minObjectSize; int maxObjectSize; double scaleFactor; int maxTrackLifetime; int minNeighbors; int minDetectionPeriod; //the minimal time between run of the big object detector (on the whole frame) in ms (1000 mean 1 sec), default=0 Parameters(); }; //含参构造函数 //TrafficSignDetection(const char* xmlFile,const Parameters& params); TrafficSignDetection(const char* xmlFile); virtual ~TrafficSignDetection(); //进行交通标志检测 virtual void process(const cv::Mat& imageGray); //停止检测 virtual void stop(); // virtual void setXmlFile(const char *fileName); private: char * FileName; }; namespace cv { ::TrafficSignDetection; } #endif
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) include E:/OpenCV-2.4.4-android-sdk/sdk/native/jni/OpenCV.mk LOCAL_SRC_FILES := ProcessImg.cpp TrafficSign.cpp LOCAL_LDLIBS += -llog -ldl LOCAL_CFLAGS += -DLIBUTILS_NATIVE=1 $(TOOL_CFLAGS) -fpermissive LOCAL_MODULE := ProcessImg include $(BUILD_SHARED_LIBRARY)
4、运行结果
点击按钮楷书处理每一帧图像:
至此,我们就实现了脱离一切JNI函数的接口文件TrafficSign.h,之后我们就可以定义TrafficSign.cpp文件,用纯C++的代码实现我们的图像算法了!
再使用Cygwin编译的时候遇到很多问题,会在后面的博文中进行讨论,因此未完待续……
技术交流,欢迎联系 [email protected]