技术标签: Android OpenCV基础 android
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,它提供了很多函数,这些函数非常高效地实现了计算机视觉算法(最基本的滤波到高级的物体检测皆有涵盖)。
OpenCV 的应用领域非常广泛,包括图像拼接、图像降噪、产品质检、人机交互、人脸识别、动作识别、动作跟踪、无人驾驶等。OpenCV 还提供了机器学习模块,你可以使用正态贝叶斯、K最近邻、支持向量机、决策树、随机森林、人工神经网络等机器学习算法。
OpenCV 使用 C/C++ 开发,同时也提供了 Python、Java、MATLAB等其他语言的接口,并且提供 Windows、Android(Android库下载)、ios等平台的接口。
OpenCV是由很多模块组成的,这些模块可以分成很多层:
我们主要关注OpenCV核心层,下图显示了 OpenCV核心层中包含的模块:
模块 | 说明 |
---|---|
core | 该模块包含 OpenCV 库的基础结构以及基本操作。 |
improc | 图像处理模块包含基本的图像转换,包括滤波以及类似的卷积操作。 |
highgui | 在 OpenCV 3.0中,分割为 imcodecs、videoio 以及 highgui 三部分。 这个模块包含可以用来显示图像或者简单的输入的用户交互函数。这可以看作是一个非常轻量级的 Windows UI 工具包。 |
video | 该模块包含读取和写视频流的函数。 |
calib3d | 这个模块包括校准单个、双目以及多个相机的算法实现。 |
feature2d | 这个模块包含用于检测、描述以及匹配特征点的算法。 |
objdectect | 这个模块包含检测特定目标,比如人脸或者行人的算法。也可以训练检测器并用来检测其他物体。 |
ml | 机器学习模块本身是一个非常完备的模块,包含大量的机器学习算法实现并且这些算法都能和 OpenCV 的数据类型自然交互。 |
flann | Flann 的意思是“快速最邻近库”。这个库包含一些你也许不会直接使用的方法,但是其他模块中的函数会调用它在数据集中进行最邻近搜索。 |
GPU | 在 OpenCV 中被分割为多个 cuda* 模块。 GPU 模块主要是函数在 CUDA GPU 上的优化实现,此外,还有一些仅用于 GPU 的功 能。其中一些函数能够返回很好的结果,但是需要足够好的计算资源,如果硬件没有GPU,则不会有什么提升。 |
photo | 这是一个相当新的模块,包含计算摄影学的一些函数工具。 |
stitching | 本模块是一个精巧的图像拼接流程实现。这是库中的新功能,但是,就像 Photo 模块一样,这个领域未来预计有很大的增长。 |
nonfree | 在 OpenCV 3.0 中,被移到 opencv_contrib/xfeatures2d。 OpenCV 包含一些受到专利保护的或者受到使用限制的(比如 SIFT 算法)算法。这些算法被隔离到它们自己的模块中,以表明你需要做一些特殊的工作,才可以在商业产品中使用它们。 |
contrib | 在 OpenCV 3.0 中,融合进了 opencv_contrib。 这个模块包含一些新的、还没有被集成进 OpenCV 库的东西。 |
legacy | 在 OpenCV 3.0 中,被取消。 这个模块包含一些老的尚未被完全取消的东西。 |
ocl | 在OpenCV 3.0 中,被取消,取而代之的是 T-API。 这是一个较新的模块,可以认为它和 GPU 模块相似,它实现了开放并行编程的 Khronos OpenCL 标准。虽然现在模块的特性比 GPU 模块少很多,但 ocl 模块的目标是提供可以运行在任何 GPU 或者是其他可以搭载 Khronos 的并行设备。这与 GPU 模 块形成了鲜明的对比,后者使用 Nividia CUDA 工具包进行开发,因此只能在 Nividia GPU 设备上工作。 |
在OpenCV github项目中可以看到Opencv核心的所有模块:
如果项目中只需要用到OpenCV中的部分模块,并且项目体积要求比较严格,可根据项目需要裁剪OpenCV,把需要的模块单独打包成so。
在集成OpenCV之前,需要了解一些NDK开发的基础知识(可以参考:Android NDK开发基础)。
要集成OpenCV,可以从OpenCV release页面直接下载官方编译产物,或者自己去OpenCV github下载并按需编译产物。本文主要介绍官方提供的Android平台OpenCV-4.5.5版本集成方式。
在下载了官方Android平台OpenCV-4.5.5产物后,里面内容如下:
如果项目中需要在java层编写OpenCV代码,则需要集成java层及native层。
在上述OpenCV java目录中,除了提供了OpenCV核心模块的java类之外,org.opencv.android包内还提供了OpenCVNativeLoader加载类、JavaCameraView、CameraGLSurfaceView相机封装等。
OpenCV java目录中提供的核心模块java类,底层实现都是通过native层实现,java层只引用了native层对象的地址,例如Opencv基础图像类Mat:
public class Mat {
/**
* Native object address
* native层对象地址
**/
public final long nativeObj;
public Mat(long addr) {
if (addr == 0)
throw new UnsupportedOperationException("Native object address is NULL");
nativeObj = addr;
}
// javadoc: Mat::Mat()
public Mat() {
nativeObj = n_Mat();
}
// C++: Mat::Mat()
private static native long n_Mat();
// javadoc: Mat::dims()
public int dims() {
return n_dims(nativeObj);
}
// C++: int Mat::dims()
private static native int n_dims(long nativeObj);
}
可以看到其对应的jni中,都是调用了opencv核心模块的c/c++方法:
#include "opencv2/core.hpp"
#define LOG_TAG "org.opencv.core.Mat"
#include "common.h"
using namespace cv;
/*
* Class: org_opencv_core_Mat
* Method: n_Mat
* Signature: (IIILjava/nio/ByteBuffer;J)J
*
* Mat::Mat(int rows, int cols, int type, void* data, size_t step)
*/
JNIEXPORT jlong JNICALL Java_org_opencv_core_Mat_n_1Mat__IIILjava_nio_ByteBuffer_2J
(JNIEnv* env, jclass, jint rows, jint cols, jint type, jobject data, jlong step);
JNIEXPORT jlong JNICALL Java_org_opencv_core_Mat_n_1Mat__IIILjava_nio_ByteBuffer_2J
(JNIEnv* env, jclass, jint rows, jint cols, jint type, jobject data, jlong step)
{
static const char method_name[] = "Mat::n_1Mat__IIILjava_nio_ByteBuffer_2J()";
try {
LOGD("%s", method_name);
return (jlong) new Mat(rows, cols, type, (void*)env->GetDirectBufferAddress(data), (size_t)step);
} catch(const std::exception &e) {
throwJavaException(env, &e, method_name);
} catch (...) {
throwJavaException(env, 0, method_name);
}
return 0;
}
要在项目中集成OpenCV,首先我们新建一个opencv_sdk模块,将官网产物中的java目录、libcxx_helper目录、native下的lib目录(架构只选择了主流的arm64-v8a、armeabi-v7a),如下所示:
至此,opencv_sdk模块集成完毕,可以在其他模块中通过java对象调用opencv的方法。
下面列举一个简单的例子:在java层通过bitmap创建一个OpenCV的Mat,并打印log。首先在build.gradle中加入依赖:
dependencies {
implementation(project(':opencv_sdk'))
}
然后就可以在app模块使用OpenCV的java类了:
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.adventure_time);
// OpenCV提供的加载libopencv_java4.so的封装类
OpenCVNativeLoader openCVNativeLoader = new OpenCVNativeLoader();
openCVNativeLoader.init();
ByteBuffer buffer = ByteBuffer.allocateDirect(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(buffer);
Mat mat = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4, buffer);
Log.d("OpenCV", "mat channels:"+mat.channels()+", cols:"+mat.cols()+", rows:"+mat.rows());
运行结果如下,说明Mat已经创建成功:
D/OpenCV: mat channels:4, cols:3000, rows:4500
如果项目中只需要在native层编写OpenCV代码,则只需要集成native层即可,可以删除上述的java目录以减小包体积。
项目中的opencv_sdk模块中只需要引入OpenCV头文件(在官网产物的OpenCV-android-sdk/sdk/native/jni/include目录下)和OpenCV动态库,如下所示:
首先新建一个sample_native模块来存放jni及native代码,模块结构如下:
在sample_native模块的build.gradle中配置cmake及opencv:
android {
defaultConfig {
ndk {
// 指定编译的abi架构
abiFilters "armeabi-v7a", "arm64-v8a"
}
externalNativeBuild {
// cmake配置
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
arguments "-DANDROID_STL=c++_shared"
arguments "-DANDROID_TOOLCHAIN=clang"
// 配置opencv头文件目录和动态库目录
arguments "-DOPENCV_HEADER_DIR=${project.rootProject.project("opencv_sdk").file("include").path}"
arguments "-DOPENCV_LIBS_DIR=${project.rootProject.project("opencv_sdk").file("native/libs").path}"
}
}
}
externalNativeBuild {
cmake {
// 构建脚本路径
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
到此就完成了cmake的基本配置,里面通过arguments配置了opencv头文件和动态库的位置,方面后面代码中引入。
在sample_native模块中,创建jni接口如下:
public class OpenCVSample {
private static boolean isLoadSuccess = false;
static {
try {
System.loadLibrary("native-lib");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public static native void createMat(Bitmap bitmap);
}
接着创建cpp目录,并创建native-lib.h头文件和native-lib.cpp文件,以及CMakeLists.txt,在CMakeLists.txt中配置opencv依赖如下:
# cmake最低版本号
cmake_minimum_required(VERSION 3.4.1)
# 导入opencv头文件
include_directories(${OPENCV_HEADER_DIR})
# add_library:把一个library添加到工程
add_library(
native-lib
SHARED
native-lib.cpp)
# 添加一个已构建的库,使用IMPORTED
add_library(opencv_java4 SHARED IMPORTED)
set_target_properties(opencv_java4
PROPERTIES IMPORTED_LOCATION
"${OPENCV_LIBS_DIR}/${ANDROID_ABI}/libopencv_java4.so")
# 首个参数是target,后面的参数是item;target必须先用add_library()创建过;
target_link_libraries(
native-lib
opencv_java4
jnigraphics
log )
然后把JNI对应的头文件native-lib.h代码完善如下:
#include <jni.h>
#include <android/log.h>
#include <android/bitmap.h>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#define TAG "NativeLibSample"
#define AndroidLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
extern "C"
JNIEXPORT void JNICALL
Java_com_bc_sample_OpenCVSample_createMat(
JNIEnv* env,
jclass thiz, jobject bitmap);
void bitmapToMat(JNIEnv *env, jobject bitmap, cv::Mat &dst);
然后,把JNI对应的native-lib.cpp文件代码完善如下,这里在c++代码中创建了Mat,然后打印出Mat的信息:
#include "native-lib.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_bc_sample_OpenCVSample_createMat(
JNIEnv* env,
jclass thiz, jobject bitmap) {
cv::Mat rgbMat;
// 把java层的bitmap转换为Mat
bitmapToMat(env, bitmap, rgbMat);
// log输出Mat的信息
AndroidLOGD("native mat channels:%d, cols:%d, rows:%d", rgbMat.channels(), rgbMat.cols, rgbMat.rows);
}
void bitmapToMat(JNIEnv *env, jobject bitmap, cv::Mat &dst) {
AndroidBitmapInfo info;
void *pixels = 0;
try {
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
CV_Assert(pixels);
dst.create(info.height, info.width, CV_8UC4);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
tmp.copyTo(dst);
} else {
cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
cvtColor(tmp, dst, CV_BGR5652RGBA);
}
AndroidBitmap_unlockPixels(env, bitmap);
return;
}catch (...) {
AndroidBitmap_unlockPixels(env, bitmap);
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
return;
}
}
最后,在代码中就可以按如下方式调用刚才创建的jni方法:
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.adventure_time);
OpenCVSample.createMat(bitmap);
运行结果如下,说明Mat已经创建成功:
D/NativeLibSample: native mat channels:4, cols:3000, rows:4500
如果在打包过程中碰到了动态库重复的问题,可以在app模块的build.gradle中添加如下配置解决:
android {
packagingOptions {
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libopencv_java4.so'
pickFirst 'lib/armeabi-v7a/libopencv_java4.so'
}
}
欢迎关注我,一起解锁更多技能:BC的掘金主页~ BC的CSDN主页~
OpenCV官网:https://opencv.org/releases/
OpenCV官方文档:https://docs.opencv.org/4.5.5/
OpenCV github:https://github.com/opencv/opencv/tree/master
OpenCV入门文档:http://c.biancheng.net/opencv/
OpenCV 入门官方文档
LearnOpenCV学习资料
裁剪OpenCV
文章浏览阅读1.8k次。点击上方“Github爱好者社区”,选择星标回复“资料”,获取小编整理的一份资料开源最前线(ID:OpenSourceTop)猿妹综合整理一年一度的双十一大促又来了,原以为今年总算不需..._github淘宝脚本
文章浏览阅读354次。using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI
文章浏览阅读1.3k次。1. 可以安装 sttyopkg update opkg install coreutils-stty2. 设定串口波特率stty -F /dev/ttyS1 raw speed 9600注意,如果在使用串口时,一定要注意 ttyS0 ttyS1 这个S是大写的,再说一下,是大写的在不清楚是,要常用TAB进行补全,这样就不会出错。_stty speed 9600 cs8 -cstopb -echo raw
文章浏览阅读3.2k次,点赞2次,收藏20次。用文氏图来理解卷积神经网络如何决定提取哪些特征:https://blog.csdn.net/kane7csdn/article/details/84890592为什么卷积能够提取图像的特征?看完此文应该能够给你一个答案:https://blog.csdn.net/charleswangzi/article/details/82733016浅析卷积神经网络为何能够进行特征提取:https://blog.csdn.net/weixin_42078618/article/details/838_卷积神经网络如何提取特征
文章浏览阅读1.4k次,点赞3次,收藏22次。【NLP】新闻文本分类----基于机器学习的文本分类机器学习模型文本表示方法one-hotBag of WordsN-gramTF-IDF基于机器学习的文本分类词袋法 + 岭回归分类器`TF-IDF + 岭回归分类器不同参数下的TF-IDF算法TF-IDF参数解读改变不同参数的F1结果改变ngram_range的参数TF-IDF算法与其他分类器模型机器学习模型机器学习是对能通过经验自动改进的计算机算法的研究。机器学习通过历史数据训练出模型对应于人类对经验进行归纳的过程,机器学习利用模型对新数据进行预测对_新闻文本分类 模型
文章浏览阅读3.9w次,点赞10次,收藏28次。http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html这篇文章写得很好,介绍了三种实现web api版本化的三种方式。我从评论里又收集到两种方式,所以一共是5种:方式一:利用URL HTTP GET:https://haveibeenpwned.com/api/v2/breacheda..._web 、app 共用一个后端,版本号如何控制
文章浏览阅读1.6k次。使用scapy 爬取的时候,少数请求链接会出现请求超时,当出现请求超时时,爬虫会自动重试三次。若超过180s且三次后仍没有得到数据,就会放弃请求出现twisted.internet.error.TimeoutError 错误。此时就出现了爬取失败的情况。错误原因:当网络无法满足在180s内下载完所有的请求数量,就会出现此类错误。解决办法:这里做个示例,具体情况具体分析1、降低同时请求的数量CONCURRENT_REQUESTS = 22、 设置合适的超时时间,DOWNLOAD_TIMEOU_twisted.internet.error.tcptimedouterror
文章浏览阅读215次。[linux]单网卡绑定多个IP配置默认网关:默认网关的文件:/etc/sysconfig/network 内容如下:其中test为主机名称NETWORKING=yesHOSTNAME=testGATEWAY=192.168.168.250或:NOZEROCONF=yesGATEWAY=10.230.17.1eth0对应的配置文件/etc/sysconfig/network-scripts/ifcfg-eth0内容如下:DEVICE=eth0BOO.._单网卡绑定两个ip
文章浏览阅读174次。WWDC23为 SwiftUI 引入了许多新功能,特别是这些修改器对金属着色器的支持。_swiftui_metal
文章浏览阅读3.1k次。一、Ubuntu18.04 Server1、查看/etc/netplan50-cloud-init.yaml文件及内容,没有文件时创建2、修改文件 ,添加 IP 地址、子网掩码、网关、DNS 服务器等配置。用192.168.1.231作为网卡enp2s0的 IP 地址,192.168.1.1作为网关地址。然后用192.168.1.1作为DNS 服务器 IP。子网掩码不清..._没有 50-cloud-init.yaml文件
文章浏览阅读2.9k次,点赞3次,收藏5次。1.在/etc/profile加入如下脚本PS1="`whoami`@`hostname`:"'[$PWD]'historyUSER_IP=`who -u am i 2>/dev/null| awk '{print $NF}'|sed -e 's/[()]//g'`if [ "$USER_IP" = "" ]thenUSER_IP=`hostname`fiif [ ! -d ..._linux 记录操作命令
文章浏览阅读5.2k次。Unit8 数组1. 理解数组2. 一位数组3. 二维数组4. 多维数组 1、认识数组软件的基本功能是处理数据,而在处理数据时,必须先进行数据持有,将数据持有之后,再对数据进行处理。我们将程序中可以临时存储数据的部分叫做容器。在java中,存储数据的容器效率最快的就是数组,也是java最基本的容器。数组,顾名思义就_uint8数组