大家好,今天我们来正式学习Android Studio环境下的OpenCV开发。
首先看看OpenCV官网,有这么一大段介绍(令人懵逼的开头):
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模块分析
- 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);
- 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);
- 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);
- flann,Clustering and Search in Multi-Dimensional Spaces,多维空间聚类和搜索模块。
快速近视最近邻搜索(Fast Approximate Nearest Neighbor Search);
聚类(Clustering);
- video,是Video Analysis的简写。视频分析模块。
运动分析和目标跟踪(Motion Analysis and Object Tracking),视频相关的,上面提到的是图片相关的。
- calib3d,是Camera Calibration and 3D Reconstruction的简写。这个模块主要是相机校准和三维重建相关的内容,包括基本的多视角几何算法、单个立体摄像头标定、物体姿态估计、立体相似性算法,3D信息的重建等。
静态库
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
Android Studio平台上搭建NDK开发OpenCV环境
1. file-->New-->New Project (必须选中 Include C++ support)
2. 一路向下走到下面步骤,C++ Standard 需选中C++11标准。(14标准可能还不太稳定,也不能选择Toolchain Default标准,可能会出现莫名其妙的错误)
下方的两个复选框须勾选上。
项目创建成功后,目录结构为:
为了能够进行OpenCV开发,我们接下来需要对项目主工程下的build.gradle和CMakeLists.txt进行修改。
build.gradle新增内容
添加设备支持的二进制指令集
libs路径使用的是绝对路径,读取速度相对快一些,最好不使用相对路径,因为使用相对路径,需要将libs拷贝到项目中,而libs文件夹大小为四百多M
CMakeLists.txt修改
该文件新增:
文件创建能力;
库文件的绝对路径;
头文件的具体路径;
动态库的引入(库名 libopencv_java3 动态SHARED 引入IMPORTEDS);
文档尾部的target_link_libraries修改为:
在环境搭建好之后,如何判断我们已经能够进行OpenCV开发呢?
其实很简单,在项目的cpp目录下的native-lib.cpp文件中,引入头文件core.hpp,如果能成功引入,则说明环境搭建已经完毕:include <opencv2/core.hpp>
至此,我们项目的OpenCV环境已经搭建完成。下面我们用一个小小的示例结束本文。
将图片变为灰度图
原图:
灰度图:
废话不多说,上代码
布局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;
}