前言
主題:
由於一個項目需要用到相機實時預覽幀處理,而網上的資料很多均不全,所以決定自己寫一個教程;實現方案:
- 方案一: 採用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環境配置完畢,撒花