Android上实现MobileSSD 实时摄像头检测 - NCNN
NCNN
ncnn 是騰訊提供的移動端框架 非常時候在手機玩在研究了一段時間的 ncnn后 準(zhǔn)備做一個安卓的demo 以便更好的熟悉和使用 NCNN先上APP和效果
app地址 :https://pan.baidu.com/s/1MX8GnM3xR4u0dcyw7iVSRg
第二張圖沒框準(zhǔn)是因為人在動 ,計算耗時還是比較長 大約需要1.幾秒
安卓程序編寫過程
程序本身并不復(fù)雜 但是搞安卓的過程中發(fā)現(xiàn) 攝像頭獲取下視頻真的是累的要去死程序框架
啟動過程
---------- ----------------- -------------|Main窗口| -->|Camera Fragment| 獲取圖像 ----> |Surface顯示| <------------ | ----------------- | ------------- || | || -------------- | -------------- |-->|JNI 加載模型| --->|ncnn推理計算|----------------- --------------數(shù)據(jù)流程
JAVA ----------- ------------------ -------------|CAMERAYUV| -->|YUV 轉(zhuǎn)RGB Bitmap| --> |Surface顯示|----------- | ------------------ -------------| | JNI | -------------- ----------- | ->|YUV Y 轉(zhuǎn) RGB| -->|Mobilenet| -->|-------------- -----------編寫過程
攝像頭權(quán)限問題
android 7以后的程序需要獲取攝像頭的需要主動申請權(quán)限 一開始沒弄各種不行
后參考使用camera2 官方demo解決
private void requestCameraPermission() {if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);} else {requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {if (requestCode == REQUEST_CAMERA_PERMISSION) {if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {ErrorDialog.newInstance(getString(R.string.request_permission)).show(getChildFragmentManager(), FRAGMENT_DIALOG);}} else {super.onRequestPermissionsResult(requestCode, permissions, grantResults);}}引入ncnn庫的問題
其實官方說明已經(jīng)很詳細(xì)了(~ ~! 并沒有找到啊~~)
實際引入需要對cmakefile進行一些調(diào)整
首先參照AS的向?qū)Ыdk工程
然后將github上release中的 ncnn庫文件 和lib拷貝到 cpp目錄下
這里我使用的是 ncnn-android-lib 目錄 如圖
導(dǎo)入后需要對路徑進行配置 并鏈接 openmp 否則會出現(xiàn)錯誤
修改CMakeLists.txt
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.4.1)FIND_PACKAGE( OpenMP REQUIRED) if(OPENMP_FOUND)message("OPENMP FOUND")set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") endif()# Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK.include_directories(./ncnn-android-lib/include)add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).mobilenetssd.cpp)# Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build.find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log)# Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.native-lib${CMAKE_SOURCE_DIR}/ncnn-android-lib/${ANDROID_ABI}/libncnn.a# Links the target library to the log library# included in the NDK.${log-lib})然后就可以正常使用 NCNN了
jni調(diào)用的參數(shù)傳遞
為了提高效率 我并沒有對圖像數(shù)組進行拷貝
而是使用了 臨界區(qū)映射的方法 GetPrimitiveArrayCritical 這樣避免了拷貝但是出現(xiàn)了一個巨坑 各種百度不到
extern "C" JNIEXPORT jdoubleArray JNICALL Java_com_chenty_testncnn_CameraNcnnFragment_detectyonly(JNIEnv *env, jobject thiz, jbyteArray frame, jint src_width,jint src_height, jdoubleArray detect) {char *argb_frame = (char*)env->GetPrimitiveArrayCritical(frame, NULL);int size = env->GetArrayLength(frame);int objectcnt = 0;int i;//shift argb to rgbachar *rgba = (char *)malloc(size);memcpy(rgba, argb_frame + 1, size - 1);env->ReleasePrimitiveArrayCritical(frame, argb_frame, JNI_ABORT);struct ai_object_t *obj = cpp_mobilenet_aidetect((const unsigned char *)rgba, src_width, src_height, &objectcnt);free(rgba);double *detect_out = (double *)env->GetPrimitiveArrayCritical(detect, NULL);if(objectcnt <= 0)objectcnt = 1;for(i = 0 ; i < objectcnt ; i++){detect_out[i*6 + 0] = obj->label;detect_out[i*6 + 1] = obj->prob;detect_out[i*6 + 2] = obj->x;detect_out[i*6 + 3] = obj->y;detect_out[i*6 + 4] = obj->xe;detect_out[i*6 + 5] = obj->ye;obj++;}env->ReleasePrimitiveArrayCritical(detect, detect_out, JNI_ABORT);return detect; }問題是 本身函數(shù)設(shè)計是 輸入圖像數(shù)組 長 寬 輸出 判定結(jié)果
然后在里面死活new 不了double數(shù)組
后來想想應(yīng)該就是這個臨界區(qū)不能new對象的原因
最后喪心病狂 直接java new一個數(shù)組傳進來算了。這就是目前的實現(xiàn)了
YUV數(shù)據(jù)的獲取
安卓默認(rèn)獲取攝像頭數(shù)據(jù) 是直接寫入到SURFACE的 效率很高 無法直接獲取
目前有2個方法獲取這個數(shù)據(jù)
這里使用第二種方法 這個魔改demo的地方實在是太多了 大家自己看吧
基本上就是建立session 獲取YUV 其中的坑很多 但是都可以百度解決還行吧。
數(shù)據(jù)進入計算都完成了
現(xiàn)在已經(jīng)可以在Log中看到SSD出來的目標(biāo)了,很好,然后是數(shù)據(jù)繪制數(shù)據(jù)繪制
我們截斷了數(shù)據(jù)的獲取過程,當(dāng)需要顯示的時候又很糾結(jié)了。
首先發(fā)現(xiàn)安卓 后攝像頭默認(rèn)是橫著的 WTF。。。 繪制圖像需要旋轉(zhuǎn)90度。。。
直接鎖定應(yīng)用只能橫屏顯示(機智~~)
繪制需要使用Surface 并且縮放,so Ctrl+C Ctrl+V了一堆代碼 實現(xiàn)了。。
顯示和計算分離
經(jīng)過上面的過程,已經(jīng)可以顯示出大體的效果了,但是每次計算耗時需要1秒,圖像像幻燈片一樣 這這么行。
于是做了一個簡單的自鎖和計算顯示分離
至此 功能部分已經(jīng)完整OK了
mDetect_isbusy = false;}}).start();}if(mDetect_result != null) {mSurfaceView.Draw(yuv, width, height, mDetect_result, 90);} 至此 功能部分已經(jīng)完整OK了Code:https://github.com/chentyjpm/MobileNetSSD_Demo_AndoridNCNN總結(jié)
以上是生活随笔為你收集整理的Android上实现MobileSSD 实时摄像头检测 - NCNN的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二项式分布(Binomial Distr
- 下一篇: JavaWeb用户信息管理系统-修改用户