第一个opencv4android项目

1 篇文章 0 订阅
1 篇文章 0 订阅

相信当你找到这篇博客的时候,你已经知道opencv为何物,并且对android,c/c++编程,eclipse使用有一定基础,同时对ndk,jni为何物有一定的了解,鉴于网上对opencv4android的配置都比较陈旧,所以新人入门过程会遇到诸多的不顺,作者便是深有体会,对此,便新开一文,对较新版本的opencv配置提供一点个人见解,才疏学浅,如有疏漏,不要见怪。

简单说一下,ndk是一个编译c/c++文件为linux下可调用so库文件的一个插件,因为android底层实质上是一个linux操作系统,c/c++需要编译打包成该系统下可以识别调用的库文件才可以被java层调用,这个编译过程的开发,便是jni/ndk开发,具体的关于jni/ndk的知识,可自行查找,这里不再赘述。

opencv是一个图像处理库,可以看成是一个处理图像的方法大集合,但是这个库使用c/c++开发,当然现在也提供了javaAPI可供android直接调用,但是鉴于当下opencv java API的相对完整教程仅限于官网的文档,对于想要入门学习opencv4adnrdoi的朋友未必是一个最佳选择,而且C/C++ API相对JAVA API更为完善,所以熟悉jni/ndk开发的朋友使用c/c++ API事实上效率会更高。

好了,唠叨完背景,这里开始讲正事了,在开始接下来的环境配置之前,教程比较完善的步骤我就不再赘述了,所以你要先准备好以下几件事情:

- 完成java环境配置(本教程使用JDK8)
- 安装eclipse(本教程使用elipse LUNA)
- 完成android环境配置(本教程建议使用真机测试)
- 准备好[OpenCV-android-sdk](http://sourceforge.net/projects/opencvlibrary/files/opencv-android/)
- 完成ndk安装(建议使用r7以上版本,无需安装cygwin,本教程使用r9d)
- 完成CDT安装(这是一个在eclipse上添加native支持的一个插件)**
- 测试设备安装opencv提供的apk环境库

接下来开始配置opencv4android

1.导入opencv sdk
解压下载后的OpenCV-android-sdk文件,首先新建一个android工程并且把该文件下面的sdk文件导入(如图)
导入opencv sdk文件
这个时候你会得到这样的一个工程,这其实是一个库项目,为其他工程提供oprncv java API
这里写图片描述
好了,然后扔它在一边,第一步就算可以了

2.导入opencv范例工程
这个小范例有不少我们待会儿需要的资源,先摆出来放着吧
这里写图片描述

ok,正常来说,刚刚导入的范例工程是有错误的,因为没有配好嘛,不用管他,我们不需要运行这个工程
opencv自带的范例

3.新建一个android项目并且配置opencv开发环境
这里先创建一个叫做firstopencv的空白项目,目录结构如下
这里写图片描述

接着我们首先需要导入第1步创建的opencv库项目,很简单,步骤如下:
右键新建的项目>>属性>>android>>Library【add】选中opencv库项目,添加,ok!
这里写图片描述

如果你是仅仅想要用javaAPI开发,那么现在就可以了,是不是很简单?但是我们要使用c/c++API,所以还要再多配置一个native环境,一样非常简单

首先右键项目>>Android Tools>>Add Native Support (如果新工程没有此选项,说明cdt没有配置成功,请自行解决)
这里写图片描述

这时候你会发现项目下面多了一个jni目录,这个是添加native support时候自动生成的
这里写图片描述

有了native支持,还必须引入native库,作为开发过程中eclipse对代码的识别与提示等操作,操作如下:
右键工程>>属性>>C/C++ General>>Ptahs and Sysmbols如下:
这里写图片描述

这里说明一下,上面有个语言选项,分别有gun c,gun c++, assembly(集合),opencv提供c++API,这里作者使用cpp开发,所以选择c++,想知道其他两个的区别,读者可以自己下去尝试一下,然后add一个路径:{ndk 所在路径}\android-ndk-r9\platforms\android-anuNum\arch-arm\usr\include 确认就可以了,这时候工程目录下面多一个include文件夹,下面便是你包含进来的include的目录,这样eclipse native就支持ndk了
这里写图片描述

我们尝试着写一些代码编译着玩玩看。

4.编写第一个opencv4android程序
在MainActivity里面添加这样一个方法,什么意思就不解释了吧……
这里写图片描述

然后需要去到工程目录目录,用javah命令生成.h头文件,如下:
这里写图片描述

请务必注意这里的每个细节,-classpath是你的生成头文件的java类所在的根目录,-d是输出路径,-jni是要生成头文件的类(包名+类名)

f5刷新一下jni目录,刷出来一个.h文件,那么说明以上步骤成功了
生成.h文件

如果这个步骤不能成功,请自行搜索关于javah生成头文件的相关资料

请注意,这里的.h里引入的jni头文件是ndk里面的,如果引入失败要在ndk目录下查找,jdk下面也有一个jni,注意区分!

接下来我们开始写程序,这个时候第2步导入的范例有用了,建议读者这个时候可以大致了解一下这个范例(算法可以先不管,主要是了解项目结构和CameraBridgeViewBase这个对象,待会用到)

编写代码前,推荐一个链接,opencv如何在native层和java层传递mat数据类型

编写java层代码,MainActivity代码如下:

package com.example.firstopencv;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.JavaCameraView;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener;
import org.opencv.core.Mat;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

public class MainActivity extends Activity implements CvCameraViewListener{
    //定义log标签
    private static String TAG = "doudou";
    //摄像头对象
    private CameraBridgeViewBase mOpenCvCameraView; 
    //native 方法
    public static native void drawRect(long bitmap);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        mOpenCvCameraView = (CameraBridgeViewBase) new JavaCameraView(this, -1);
        //设置摄像头为布局样式
         setContentView(mOpenCvCameraView);
         //摄像头监听事件,利用这个监听事件调用我们的native方法,从而对每一帧在native层进行处理
         mOpenCvCameraView.setCvCameraViewListener(this);
    }

    @Override
    public void onResume()
    {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {

        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                     Log.i("doudou", "OpenCV loaded successfully");
                     System.loadLibrary("firstopencv");
                     Log.i("doudou", "libfirstopencv.so loaded successfully");
                     mOpenCvCameraView.enableView();
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };

    public void onCameraViewStarted(int width, int height) {
    }
    public void onCameraViewStopped() {
    }


    public Mat onCameraFrame(Mat inputFrame) {
        //获取mat地址
        long address = inputFrame.getNativeObjAddr();
        //使用native方法对地址指向的mat矩阵进行绘制操作
        drawRect(address);
        //根据该地址生成mat
        Mat histogram = new Mat(address);
        //返回给屏幕
        return histogram;
    }
}

编写native层代码,代码如下:

文件 com_example_firstopencv_MainActivity.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_firstopencv_MainActivity */

#ifndef _Included_com_example_firstopencv_MainActivity
#define _Included_com_example_firstopencv_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_firstopencv_MainActivity
 * Method:    drawRect
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_example_firstopencv_MainActivity_drawRect
  (JNIEnv *, jclass, jlong);

#ifdef __cplusplus
}
#endif
#endif
文件 firstopencv.cpp

#include "com_example_firstopencv_MainActivity.h"
#include "opencv2/opencv.hpp"
using namespace cv;
JNIEXPORT void JNICALL Java_com_example_firstopencv_MainActivity_drawRect
  (JNIEnv *env, jclass jclas, jlong jlon){
    Mat *bitmpaMat = (Mat*) jlon;
    rectangle(*bitmpaMat,Point(300,300),Point(600,600),Scalar(222,222,222),4);
}

ok,试着在工程目录下用nkd-build编译一下,如果不出意外,肯定是要出错的。。。额
这里写图片描述

这是因为opencv头文件目录没有加载进来,不要尝试用第3步里的方式添加路径,因为opencv作为一个第三方native库有自己的编译方式,这个规则就声明在opencv/sdk/native/OpenCV.mk里,我们需要把这个规则写在我们项目jni下面的android.mk文件里,这个mk文件可以理解成一个声明编译规则的文件,如下图(注意include语句的位置):这里写图片描述

ok,再nkd-build一下
这里写图片描述

我擦…耍我的吧,怎么错误更多了?!!!
别急,看到这个说明opencv库已经引入成功了,因为报的错误发生在库内部,我们看一下错误,找到opencv头文件里错误代码部分,发现 include失败了,没有找到algorithm头文件,什么问题呢?这是因为algorithm这个文件是一个stl文件,调用时需要声明application.mk,这好办,直接把第2步创建的范例jni下面的application.mk拷过来。

再build一下,ok可以正常生成so文件了
这里写图片描述

回到我们的firstopencv.cpp, 为什么opencv头文件引入成功还是一大堆错误啊,mat什么鬼opencv下的数据结构根本没有识别……还记得第3步吗?在第3部地方添加 {你的opencv-sdk路径}/native/jni/include 这个路径,不要照抄我的,每个人可能不一样,这个时候刷新代码,偶也,有代码提示的感觉就是爽吧

**最后还有点手尾要处理一下
-在manifest.xml文件添加摄像头权限,并调整一下布局,参考范例

跑一下程序,如果成功,大概是下面这个样子的

图片中,java层摄像头把每一帧的图片地址传递给native层,native层在图片上画一个矩形,这便是一个最简单的opencv javaAPI和nativeAPI联合使用的范例

这里写图片描述

结尾语:一直看别人的博客学习,这是我第一次写博客,我尽量站在一个opencv4android入门者的角度去剖析整个项目编写的过程,希望能给大家带来一点帮助,其中不免有很多不足的地方,希望大家能够谅解,谢谢

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值