搭建NDK开发OpenCV环境

大家好,今天我们来正式学习Android Studio环境下的OpenCV开发。

首先看看OpenCV官网,有这么一大段介绍(令人懵逼的开头):

image

OpenCV于1999年由Intel建立,如今由Willow Garage提供支持。OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Mac OS、android、iOS等操作系统上。由一系列 C 函数和少量 C++ 类构成,所以它轻量级而且高效。还支持C#、Ch、ruby等语言进行编程,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。最新版本是3.4.1 ,2018年2月27日发布。

图像处理

利用计算机对图像进行分析处理,达到所需结果的技术,一般指的是数字图像处理,通过数码设备得到的数字图像是一个很大的二维数组,数组的元素叫像素,像素的值叫灰度值。主要的处理方法有去燥,增强,复原,分割,提取特征得到。

图像处理侧重于处理图像。

计算机视觉

研究如何使计算机可以像人一样“看”的一门科学,属于人工智能范畴,是用计算机来识别、追踪、测量等手机信息的科学。

计算机视觉侧重于模拟人的视觉。

应用领域

人机互动、物体识别、图像分割人脸识别、动作识别、运动跟踪、机器人、运动识别、机器视觉、结构分析、汽车安全驾驶、自动驾驶等等。

OpenCV模块分析

  1. core 核心功能模块。定义了被所有其他模块和基本数据结构(包括重要的多维数组Mat)使用的基本函数.包含核心功能,尤其是底层数据结构和算法函数。
基本的C语言数据结构和操作(Basic C Structures and Operations);
动态数据结构(Dynamic Structures);
数组操作相关函数(Operations on Arrays);
绘图功能(Drawing Functions);
XML和YAML语法的支持(XML/YAML Persistence);
XML和YAML语法的支持的C语言接口(XML/YAML Persistence (C API));
聚类(Clustering);
辅助功能与系统函数和宏(Utility and System Functions and Macros);
与OpenGL的互操作(OpenGL interoperability);
  1. imgproc,是Image Processing的简写。图像处理模块。
线性和非线性的图像滤波(Image Filtering);
图像的几何变换(Geometric Image Transformations);
图像的其他变换(Miscellaneous Image Transformations);
直方图(Histograms);
结构分析和形状描述(Structural Analysis and Shape Descriptors);
运动分析和目标跟踪(Motion Analysis and Object Tracking);
特征检测(Feature Detection);
目标检测(Object Detection);
  1. features2d,是2D Features Framework的简写。二维特征框架模块。
人脸识别
VR和AR
特征的检测和描述(Feature Detection and Description);
特征检测器的通用接口(Common Interfaces of Feature Detectors);
描述符提取器的通用接口(Common Interfaces of Descriptor Extractors);
描述符匹配器的通用接口(Common Interfaces of Descriptor Matchers);
通用描述符匹配器通用接口(Common Interfaces of Generic Descriptor Matchers);
关键点和匹配结果的绘制功能(Drawing Function of Keypoints and Matches);
目标分类(Object Categorization);
  1. flann,Clustering and Search in Multi-Dimensional Spaces,多维空间聚类和搜索模块。
快速近视最近邻搜索(Fast Approximate Nearest Neighbor Search);
聚类(Clustering);
  1. video,是Video Analysis的简写。视频分析模块。
运动分析和目标跟踪(Motion Analysis and Object Tracking),视频相关的,上面提到的是图片相关的。
  1. calib3d,是Camera Calibration and 3D Reconstruction的简写。这个模块主要是相机校准和三维重建相关的内容,包括基本的多视角几何算法、单个立体摄像头标定、物体姿态估计、立体相似性算法,3D信息的重建等。

静态库

image

OpenCV目录结构(3.3为例)

apk OpenCV manager针对各个架构的cpu的manager安装包,过去的OpenCV如果不用NDK方式开发,会要求手机上需要安装OpenCV manager的对应指令集的apk

samples android平台下的官方案例源码和安装包(Java调用方式)

SDK SDK文件夹,jni开发所需。

Mat

Mat是OpenCV的核心数据结构,从来表示任意N维矩阵。图像是二微矩阵的一种特殊场景,所以也可以使用Mat来表示。Mat是OpenCV中用到最多的类。定义在命名空间cv下。

Android Studio平台上搭建NDK环境(以MacOS环境为例)

1.Prefrences-->Android SDK-->SDK Tools
选择安装
cMake:外部构建工具。如果你准备只使用 ndk-build 的话,可以不使用它。
LLDB:Android Studio上面调试本地代码的工具
NDK


image.png

Android Studio平台上搭建NDK开发OpenCV环境

1. file-->New-->New Project (必须选中 Include C++ support)

image

2. 一路向下走到下面步骤,C++ Standard 需选中C++11标准。(14标准可能还不太稳定,也不能选择Toolchain Default标准,可能会出现莫名其妙的错误)

下方的两个复选框须勾选上。

image

项目创建成功后,目录结构为:

image

为了能够进行OpenCV开发,我们接下来需要对项目主工程下的build.gradle和CMakeLists.txt进行修改。

build.gradle新增内容

添加设备支持的二进制指令集
libs路径使用的是绝对路径,读取速度相对快一些,最好不使用相对路径,因为使用相对路径,需要将libs拷贝到项目中,而libs文件夹大小为四百多M


image

CMakeLists.txt修改

该文件新增:
文件创建能力;
库文件的绝对路径;
头文件的具体路径;
动态库的引入(库名 libopencv_java3 动态SHARED 引入IMPORTEDS);


image

文档尾部的target_link_libraries修改为:

image

在环境搭建好之后,如何判断我们已经能够进行OpenCV开发呢?

其实很简单,在项目的cpp目录下的native-lib.cpp文件中,引入头文件core.hpp,如果能成功引入,则说明环境搭建已经完毕:include <opencv2/core.hpp>

至此,我们项目的OpenCV环境已经搭建完成。下面我们用一个小小的示例结束本文。

将图片变为灰度图

原图:


image.png

灰度图:


image.png

废话不多说,上代码

布局activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点击处理" />

    <ImageView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

页面MainActivity.java

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private ImageView imageview;
    private Button button;
    private Bitmap bitmap;

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageview = findViewById(R.id.sample_text);
        button = findViewById(R.id.button1);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
        imageview.setImageBitmap(bitmap);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 获取图片的宽
                int w = bitmap.getWidth();
                // 获取图片的高
                int h = bitmap.getHeight();
                // 建立数组pixels,长度为w * h
                int[] pixels = new int[w * h];
                // getPixels方法获取图片像素
                bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
                // 调用jni方法获取灰度图片像素数组
                int[] resultInt = grayP(pixels, w, h);
                // 创建新的bitmap
                Bitmap resultImg = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
                // 新的bitmap存入灰度图像素
                resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
                imageview.setImageBitmap(resultImg);
            }
        });
    }

    /**
     * 将图片灰度化并获取灰度化后的图片像素
     * @param pixels int类型的数组-像素
     * @param w 图片的宽
     * @param h 图片的高
     * @return
     */
    public native int[] grayP(int[] pixels, int w, int h);
}

上述代码中,我们使用了getPixels方法(setPixels类似),那么此方法需要用到的参数都代表什么意思呢?

    public void getPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height) {
        throw new RuntimeException("Stub!");
    }
    public void setPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height) {
        throw new RuntimeException("Stub!");
    }

int[] pixels是存储图片的指定区域的所有像素的一维数组;
int offset是指定的偏移位置;
int x, int y, int width, int height是从指定的位置截取指定的宽高;
int stride 是指定在行之间跳过的像素的数目。图片是二维的,存入一个一维数组中,那么就需要这个参数来指定多少个像素换一行;

stride的特点:
在原图上截取的时候,需要读取stride个像素再去读原图的下一行
如果一行的像素个数足够,就读取stride个像素再下一行读
如果一行的像素个数不够,用0(透明)来填充到 stride个
得到的数据,依次存入pixels[]这个一维数组中

JNI native-lib.cpp

#include <jni.h>
#include <string>
#include <opencv2/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_ndk_1opencv_MainActivity_grayP(
        JNIEnv *env,
        jclass type,
        jintArray pixels_,
        jint w,
        jint h) {
    jint *pixels = env->GetIntArrayElements(pixels_, NULL);

    if (pixels == NULL) {
        return 0;
    }

    //RGBA
    Mat imgData(h, w, CV_8UC4, (unsigned char *) pixels);
    uchar *ptr = imgData.ptr(0);
    for (int i = 0; i < w * h; i++) {
        // 灰度值计算公式 R * 0.3 + G * 0.59 + B * 0.11
        uchar gray = (uchar) (ptr[4 * i + 2] * 0.299 + ptr[4 * i + 1] * 0.587 +
                              ptr[4 * i + 0] * 0.114);
        ptr[4*i+0]=gray; // b
        ptr[4*i+1]=gray;// g
        ptr[4*i+2]=gray; // r
    }

    int size = w * h;
    jintArray result = env->NewIntArray(size);
    env->SetIntArrayRegion(result, 0, size, pixels);

    env->ReleaseIntArrayElements(pixels_, pixels, 0);

    return result;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343