前言
主题:
由于一个项目需要用到相机实时预览帧处理,而网上的资料很多均不全,所以决定自己写一个教程;实现方案:
- 方案一: 采用Android Camera SurfaceView + JNI 实现;
- 方案二: 采用OpenCV Camera框架 + JNI实现;
- 方案三:采用Android Camera GLSurfaceView + OpenGL实现
- 工具:
Android Studio 3.3.2 、NDK R16
OpenCV 环境搭建
构建项目
- 注意:
由于需要使用JNI来实现帧处理,所以创建工程时选择支持Native C++的工程
填写自己的项目名和包名,API等级可以默认可以自己选,但是应当注意,API >= 23时需要请求动态运行权限;
选择需要采用的C++标准,根据习惯选择即可
此时会得到一个默认的ManActivity.java文件,运行次文件可以验证支持JNI的项目工程是否搭建成功
运行测试一下,获得下面界面表示成功:
构建OpenCV环境
第0步,改变JNI函数的调用
由于帧处理需要大量的C++函数,所以个人建议将C++的Native函数放在一个Java类文件里声明,
Step 1:新建一个class,命名为NDKInterface.java
Step 2:修改native-lib.cpp文件
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_cn_angry_rabbit_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
/************* update ************/
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_cn_angry_rabbit_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
由两个差别可以看出,修改的各部分代表含义
Java_<Package_Name><Ndk_Interface_Class_Name><Native_Function_Name>
Step 3: 修改NDKInterface.java
public class NDKInterface {
//load the native library
static {
System.loadLibrary("native-lib");
}
// claim the native c++ function
public native String stringFromJNI();
}
Step 4 : 修改MainActivity调用这个Native函数
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(new NDKInterface().stringFromJNI());
}
}
Step 5 : 测试出现上文相同手机界面即可
第一步,下载OpenCV for Android
Opencv for Android下载页面
我选择的时3.4.1版本
第1步,导入OPenCV Module
File --> New --> Import Module
在文件夹中找到你所需要的OpenCV Java Module,一般相对路径为 OpenCV-android-sdk/sdk/java
接下来默认Next --> Finish 即可。
第2步,修改OPenCV的配置
现在sync是无法通过的需要修改两个地方:
第一:openCVLibrary/build.gradle
修改成和当前工程相同的
compileSdkVersion
minSdkVersion
targetSdkVersion
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
第二:openCV/AndroidManifest.xml
删除倒数第二行,修改成下面样式
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.opencv"
android:versionCode="3410"
android:versionName="3.4.1">
</manifest>
再次同步即可通过
第3步,导入OPenCV的依赖
File --> Project Structure 中添加
第4步,修改app/build.gradle配置
Step 1 :在dependencies中添加一句
implementation fileTree(dir: "$buildDir/native-libs", include: 'native-libs.jar')
更改为类似与下面
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation fileTree(dir: "$buildDir/native-libs", include: 'native-libs.jar')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(':openCVLibrary341')
}
Step 2 :在文末添加
task nativeLibsToJar(type: Jar, description: 'creat a jar archive of the native libs') {
destinationDir file("$buildDir/native-libs")
baseName 'native-libs'
from fileTree(dir: 'libs', include: '**/*.so')
into 'lib/'
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn(nativeLibsToJar)
}
恭喜,到此Java环境的opencv已经可以使用,测试一下:
修改MAinActivity内容
public class MainActivity extends AppCompatActivity {
private static final String TAG = "OpenCV";
static {
if (OpenCVLoader.initDebug()) {
Log.i(TAG, "Loaded successfully...");
} else {
Log.i(TAG, "Can not to loaded...");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
在Logcat中可以看见
第5步:修改CMakeList.txt
#这里修改成你的android-opencv jni文件夹路径
set(OpenCV_DIR /home/hirah/OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED)
if (OpenCV_FOUND)
include_directories(${OpenCV_INCLUDE_DIRS})
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")
else (OpenCV_FOUND)
message(FATAL_ERROR "OpenCV library not found")
endif (OpenCV_FOUND)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a")
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
#添加需要的OpenCV库
target_link_libraries(
native-lib
jnigraphics
${OpenCV_LIBS}
${log-lib})
第6步 : 测试
恭喜你万里长征已经还差最后一步 测试
step 1: 在res/drawable中添加一个图片
Step 2: 修改activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bt_toCanny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="11dp"
android:layout_marginTop="29dp"
android:text="Canny"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:contentDescription="@string/todo"
android:scaleType="fitCenter"
android:src="@drawable/test"/>
<!-- 上面的test为你的图片名-->
</RelativeLayout>
Step 3: 修改MAinActivity.java
public class MainActivity extends AppCompatActivity {
private static int cannyID = 0;
private static final String TAG = "OpenCV";
// OpenCV库静态加载并初始化
static {
if (OpenCVLoader.initDebug()) {
Log.i(TAG, "Load successfully...");
} else {
Log.i(TAG, "Fail to load...");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btCanny = findViewById(R.id.bt_toCanny);
btCanny.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cannyID = (cannyID + 1) % 2;
imageToCanny();
}
});
}
private void imageToCanny() {
ImageView imageView = findViewById(R.id.imageView);
Bitmap image = BitmapFactory.decodeResource(this.getResources(), R.drawable.test);
if (cannyID == 1) {
new NDKInterface().getEdge(image);
}
imageView.setImageBitmap(image);
}
}
Step 3: 修改native-lib.cpp
#include <jni.h>
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
extern "C" {
// Canny edge detect
JNIEXPORT void JNICALL
Java_cn_angry_opencvcamera_JniInterface_getEdge(JNIEnv *env, jobject, jobject bitmap) {
AndroidBitmapInfo info;
void *pixels;
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);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
Mat temp(info.height, info.width, CV_8UC4, pixels);
Mat gray;
cvtColor(temp, gray, COLOR_RGBA2GRAY);
Canny(gray, gray, 100, 185);
cvtColor(gray, temp, COLOR_GRAY2RGBA);
} else {
Mat temp(info.height, info.width, CV_8UC2, pixels);
Mat gray;
cvtColor(temp, gray, COLOR_RGB2GRAY);
Canny(gray, gray, 100, 185);
cvtColor(gray, temp, COLOR_GRAY2RGB);
}
AndroidBitmap_unlockPixels(env, bitmap);
}
}
Step 4: 修改NDKInterface.java
public class JniInterface {
static {
System.loadLibrary("native-lib");
}
/**
* This is a method for image processing.
* You can see detail in native-lib.cpp
* @param:
* bitmap for processing
* @return:
* void
*/
public native void getEdge(Object bitmap);
}
到此OpenCV-Android的Java环境和Jni环境配置完毕,撒花