源碼:https://github.com/huangshuyuan/OpenCvDemo-Master/
一、導入程序
1. 確定SdkVersion
在導入程序之前阻问,我們需要先確定待會的OpenCv工程中的一些和SdkVersion有關的配置,最好的辦法就是先用AS建一個HelloWorld握童,也可以順便熟悉一下Android Studio的開發(fā)流程罪佳。在工程中打開build.gradle痢畜,可以得到我們需要的信息:
這個隨意,和開發(fā)的APP的兼容性有關`
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "org.opencv.samples.facedetect"
minSdkVersion 14
targetSdkVersion 26
ndk {
moduleName "detection_based_tracker"
abiFilters "armeabi-v7a"
}
}
2.導入samples\face-detection
第一步:
第二步:
找到opencv下載的demo
打開肺稀,直接下一步就行第股,我這邊已經(jīng)導入過了
此時,我們在AS左側(cè)的Project窗口的打開如下的配置文件:
openCVSamplefacedetection->src->build.gradle
修改如下4個配置信息:
compileSdkVersion 26
buildToolsVersion 26.0.2
minSdkVersion 14
targetSdkVersion 26
在修改完成之后话原,需要重新進行”Gradle project sync”夕吻,點擊Tools->Android->Sync Project with Gradle Files:
原因在于,工程openCVSamplefacedetection依賴于庫工程openCVLibrary340繁仁,而庫工程openCVLibrary340的build.gradle配置也需要修改涉馅。這里不再贅述,找到openCVLibrary340下的build.gradle進行修改即可黄虱。
修改完成后再次進行”Gradle project sync”稚矿,這一次”Gradle Sync”沒有報錯,隨后AS會進行”Gradle build”悬钳,也順利完成盐捷。
3. jni的配置
怎樣解決上面出現(xiàn)的問題?
首先默勾,我們需要先配置NDK的路徑碉渡,點擊File->Project Structure,在如下的界面上配置NDK的路徑母剥。
然后滞诺,在左側(cè)的項目窗口中選中openCVSamplefacedetection,右鍵點擊”Link C++ Project with Gradle”环疼,在彈出的窗口中按照下圖選擇习霹,點擊”O(jiān)K”。
ok炫隶,即可淋叶,會在gradle里面生成以下代碼
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
接下來,在左右的項目窗口中的“External Build Files”下伪阶,選擇Android.mk煞檩,修改OpenCV.mk的路徑:
include ../../sdk/native/jni/OpenCV.mk
修改后的路徑為处嫌,當然你需要根據(jù)自己電腦上的OpenCV4Android路徑進行配置:
include D:\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
在第12行的ndk部分加入以下的聲明:
abiFilters "armeabi-v7a"
最終得到:
ndk {
moduleName "detection_based_tracker"
abiFilters "armeabi-v7a"
}
至此,整個jni部分的配置就全部完成斟湃,這時再點擊”構建“熏迹,可以發(fā)現(xiàn)已經(jīng)可以生成成功。
二凝赛、剔除OpenCV Manager依賴
在上一小節(jié)中注暗,我們已經(jīng)可以成功地配置fd工程,并且編譯生成都成功墓猎,此時我們可以將apk部署到手機上進行運行捆昏。但是等等,你還想被OpenCV Manager所困擾嗎毙沾?說實話屡立,每次我想要找到適合自己手機的OpenCV Manager,都要上網(wǎng)查一大堆資料搀军,費時又費勁。
那么接下來我就基于face-detection工程勇皇,給大家分享一個去掉OpenCV Manager依賴的方法罩句。
1、把OpenCV sdk for Android文件下F:\OpenCV-android-sdk\sdk\native下的libs文件夾拷貝到你的安卓項目下敛摘,即自己的項目\src\main下面门烂,并且將libs改名為 jniLibs(需要)
2、將OpenCV-android-sdk\samples\image-manipulations\res\layout下的xml文件拷貝到自己的項目\src\main\res下面(不需要也行)
3兄淫、將OpenCV-android-sdk\samples\image-manipulations\src\org\opencv\samples\imagemanipulations下的java文件拷到自己的項目\src\main\java\你 屯远,MainActivity所在的包名,即和MainActivity同級目錄(我改的是demo捕虽,沒加)
4慨丐、在項目清單文件中為剛才導入的java文件進行配置,加上相應的權限(看demo)
gradle:
sourceSets.main {
jniLibs.srcDir 'src/main/libs' //set .so files directory to libs
jni.srcDirs = [] //disable automatic ndk-build call
}
三泄私、常見問題
1房揭、openCV默認是橫屏,改成豎屏不全屏晌端,豎屏無法識別人臉
2捅暴、openCV Demo 里面沒有獲取圖片的地方
3、OpenCV Android 打開前置后置攝像頭
1咧纠、攝像頭豎屏全屏的設置
在CameraBridgeViewBase.java 文件修改 deliverAndDrawFrame()函數(shù)中蓬痒,修改以下部分
if (canvas != null) {
canvas.rotate(90,0,0);
float scale = canvas.getWidth() / (float)mCacheBitmap.getHeight();
float scale2 = canvas.getHeight() / (float)mCacheBitmap.getWidth();
if(scale2 > scale){
scale = scale2;
}
if (scale != 0) {
canvas.scale(scale, scale,0,0);
}
canvas.drawBitmap(mCacheBitmap, 0, -mCacheBitmap.getHeight(), null);
// canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
Log.d(TAG, "mStretch value: " + mScale);
/* if (mScale != 0) {
canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
(int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
(int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
(int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
} else {
canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
(canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
(canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
(canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
}*/
因為opencv要在橫屏時才能得到較好的結果,那么我可以先把豎屏時得到的圖像順時針旋轉(zhuǎn)90度漆羔,這樣就和橫屏時一樣了梧奢,然后我在把得到識別綠框的圖像逆時針旋轉(zhuǎn)90度狱掂,再輸出這樣就能做到豎屏時實現(xiàn)人臉檢測了。
所以修改FaActivity中的onCameraViewStarted和onCameraFrame()函數(shù)修改如下
Mat Matlin, gMatlin;
int absoluteFaceSize;
public void onCameraViewStarted(int width, int height) {
// mGray = new Mat();
// mRgba = new Mat();
mRgba = new Mat(width, height, CvType.CV_8UC4);
mGray = new Mat(height, width, CvType.CV_8UC4);
Matlin = new Mat(width, height, CvType.CV_8UC4);
gMatlin = new Mat(width, height, CvType.CV_8UC4);
absoluteFaceSize = (int) (height * 0.2);
}
int faceSerialCount = 0;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame InputFrame) {
mGray = InputFrame.gray();
mRgba = InputFrame.rgba();
int rotation = mOpenCvCameraView.getDisplay().getRotation();
//使前置的圖像也是正的
Core.flip(mRgba, mRgba, 1);
Core.flip(mGray, mGray, 1);
//MatOfRect faces = new MatOfRect();
if (rotation == Surface.ROTATION_0) {
MatOfRect faces = new MatOfRect();
Core.rotate(mGray, gMatlin, Core.ROTATE_90_CLOCKWISE);
Core.rotate(mRgba, Matlin, Core.ROTATE_90_CLOCKWISE);
if (mJavaDetector != null) {
mJavaDetector.detectMultiScale(gMatlin, faces, 1.1, 2, 2, new Size(absoluteFaceSize, absoluteFaceSize), new Size());
}
Rect[] faceArray = faces.toArray();
for (int i = 0; i < faceArray.length; i++)
Imgproc.rectangle(Matlin, faceArray[i].tl(), faceArray[i].br(), new Scalar(0, 255, 0, 255), 2);
Core.rotate(Matlin, mRgba, Core.ROTATE_90_COUNTERCLOCKWISE);
} else {
MatOfRect faces = new MatOfRect();
if (mJavaDetector != null)
mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2, // TODO: objdetect.CV_HAAR_SCALE_IMAGE
new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
Rect[] faceArray = faces.toArray();
for (int i = 0; i < faceArray.length; i++)
Imgproc.rectangle(mRgba, faceArray[i].tl(), faceArray[i].br(), new Scalar(0, 255, 0, 255), 2);
}
return mRgba;
}
2粹断、捕獲人臉后自動拍照
捕獲人臉后自動拍照符欠,這個需求可能是最最常見的了,那在OpenCV里要如何實現(xiàn)呢瓶埋?首先我們來觀察一下JavaCameraView這個類希柿,它繼承自CameraBridgeViewBase
這個類,再往下翻會發(fā)現(xiàn)一個非常熟悉的Camera對象养筒,沒錯這個類里其實是使用了Android原生的API構造了一個相機對象(還好是原生的曾撤,至今還沒忘卻被JNI相機
一旦實現(xiàn)了PreviewCallback接口,肯定會有onPreviewFrame(byte[] frame,Camera camera)這個回調(diào)函數(shù)晕粪,這里面的字節(jié)數(shù)組frame對象挤悉,就是當前的視頻幀,注意這里是視頻幀巫湘,是YUV編碼的装悲,并不能直接轉(zhuǎn)換為Bitmap。這個回調(diào)函數(shù)在預覽過程中會一直被調(diào)用尚氛,那么只要確定了哪一幀有人臉诀诊,只需要在這里獲取就行,代碼如下:
private boolean takePhotoFlag = false;
@Override
public void onPreviewFrame(byte[] frame, Camera arg1) {
if (takePhotoFlag){
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;
YuvImage yuvimage = new YuvImage(
frame,
ImageFormat.NV21,
previewSize.width,
previewSize.height,
null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);
byte[] rawImage = baos.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bmp = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);
try {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileName));
bmp.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
bmp.recycle();
takePhotoFlag = false;
}
synchronized (this) {
mFrameChain[mChainIdx].put(0, 0, frame);
mCameraFrameReady = true;
this.notify();
}
if (mCamera != null)
mCamera.addCallbackBuffer(mBuffer);
}
這里我們先在外層聲明一個布爾類型的變量阅嘶,在無限的回調(diào)過程中属瓣,一旦次布爾值為真,就對該視頻幀保存為文件讯柔,上面的代碼就將YUV視頻幀轉(zhuǎn)換為Bitmap對象的方法抡蛙,然后將Bitmap存成文件,當然這也的結構不太合理魂迄,我只是為了展示方便這樣書寫粗截。
現(xiàn)在已經(jīng)可以抓取照片了,那么如何才能判斷是不是有人臉來修改這個布爾值呢极祸,我們再定義這樣一個方法:</pre>
public void takePhoto(String name){
fileName = name;
takePhotoFlag = true;
}
一旦調(diào)用了takePhoto這個方法慈格,傳入一個保存路徑,就能修改此布爾值遥金,完成拍照浴捆,我們離完成越來越接近了。那么回到我們一開始的Activity稿械,這里面包含剛剛修改的
JavaCameraView對象选泻,可以對他進行操作。然后找到Activity的onCameraFrame回調(diào)函數(shù),修改為:
int faceSerialCount = 0;
@Override
public Mat onCameraFrame(Mat aInputFrame) {
Imgproc.cvtColor(aInputFrame, grayscaleImage, Imgproc.COLOR_RGBA2RGB);
MatOfRect faces = new MatOfRect();
if (cascadeClassifier != null) {
cascadeClassifier.detectMultiScale(grayscaleImage, faces, 1.1, 2, 2,
new Size(absoluteFaceSize, absoluteFaceSize), new Size());
}
Rect[] facesArray = faces.toArray();
int faceCount = facesArray.length;
if (faceCount > 0) {
faceSerialCount++;
} else {
faceSerialCount = 0;
}
if (faceSerialCount > 6) {
openCvCameraView.takePhoto("sdcard/aaa.jpg");
faceSerialCount = -5000;
}
for (int i = 0; i < facesArray.length; i++) {
Imgproc.rectangle(aInputFrame, facesArray[i].tl(), facesArray[i].br(), new Scalar(0, 255, 0, 255), 3);
}
return aInputFrame;
}
一旦該變量大于0页眯,就讓faceSerialCount自增梯捕,else的話就清零,如果faceSerialCount>6就調(diào)用剛才我們定義的takePhoto方法進
3窝撵、OpenCV Android 打開前置后置攝像頭
mOpenCvCameraView.setCameraIndex(CameraBridgeViewBase.CAMERA_ID_FRONT);
//前置攝像頭 CameraBridgeViewBase.CAMERA_ID_BACK為后置攝像頭
兼容性適配
名詞解析:
NDK:Native Development Kit
JNI:Java Native Interface
ABI: Application Binary Interface 應用二進制接口
1傀顾、Android Studio使用so庫
1、使用和eclipse一樣在libs目錄下新建armeabi目錄的方式
需要在build.gradle中添加指定jni庫目錄的語句
sourceSets {
main.jniLibs.srcDirs = ['libs'] //指定libs為jni的存放目錄
}
2碌奉、使用AS默認的位置:src/main/jniLibs
直接在src/main/下新建jniLibs目錄短曾,將armeabi等目錄放到該目錄下即可
備注:AS可以直接右鍵新建同目錄下的jniLibs目錄,但該目錄不是編譯好的庫文件目錄赐劣,而是未編譯的本地代碼文件的目錄(這里指的是與java同級的jni目錄嫉拐,放置cpp代碼的)
android支持的cpu架構(目前是七種)
cpu | |
---|---|
armeabi | 第5代 ARM v5TE,使用軟件浮點運算魁兼,兼容所有ARM設備婉徘,通用性強,速度慢 |
armeabi-v7a | 第7代 ARM v7咐汞,使用硬件浮點運算盖呼,具有高級擴展功能 |
arm64-v8a | 第8代,64位化撕,包含AArch32塌计、AArch64兩個執(zhí)行狀態(tài)對應32、64bit |
x86 | intel 32位侯谁,一般用于平板 |
x86_64 | intel 64位阻塑,一般用于平板 |
mips | 少接觸 |
mips64 | 少接觸 |
安裝時的兼容性檢查:
安裝到系統(tǒng)中后蚣旱,so文件會被提取在:data/app/com.xxxxxxxx.app-x/lib/目錄下(5.0版本)、/data/app-lib/目錄下(4.2版本)却紧,其中armeabi和armeabi-v7a會生成arm目錄贱傀,arm64-v8a會生成arm64目錄惨撇。
安裝app的時候,如果app使用了so文件府寒,而不存在適合本機cpu架構的so文件魁衙,會報如下錯誤:
Installation failed with message INSTALL_FAILED_NO_MATCHING_ABIS.
例如:在x86模擬器上就必須有x86版本的so文件夾。不然無法安裝成功株搔。
運行時的兼容性檢查:
1剖淀、檢查目標目錄下是否存在的so庫文件
2、檢查存在的so文件是否符合當前cpu架構纤房。
對于情況一纵隔,一般規(guī)避的做法是:保證jnilibs目錄下x86、x84_64、armeabi捌刮、armeabi-v7a碰煌、arm64-v8a等目錄下的文件名稱數(shù)量是一致的。
例如:項目中使用了A绅作、B芦圾、C三個第三方庫。其中A俄认、B提供了armebi以及arm64-v8a版本的庫文件个少,而C只提供了armebi、armebi-v7a版本的庫文件梭依。這時候只能夠刪除原有的arm64-v8a目錄稍算,保留armeabi目錄,一般arm64的手機都能兼容使用armeabi版本的庫役拴『剑或者復制一份armeabi的so文件到缺少的目錄中(推薦)。
生成so文件:
NDK交叉編譯時選定APP_ABI := armeabi x86 ...可以生成支持相應芯片的so文件河闰。APP_ABI := all生成支持所有芯片指令集(目前七種)so文件科平。
Android加載so文件規(guī)則:
當你只提供了armeabi目錄時,armeabi-v7a姜性、arm64-v8a架構的程序都會去armeabi里尋找瞪慧,而當你同時也提供了armeabi-v7a、armeabi-v8a目錄部念,而里面又不存在對應的so庫時弃酌,系統(tǒng)就不會再去armeabi里面尋找了,直接找不到報錯儡炼。其他平臺也是如此妓湘。這里我踩了不少的坑,切記乌询。
一般來說榜贴,一些比較有名的第三方庫都會提供armeabi、armeabi-v7a妹田、x86這三種類型的so文件唬党,同時擁有這三種版本的app可以在所有機型上運行。另外鬼佣,越來越多的SDK會同時提供arm64-v8a版本驶拱。只包含armeabi的項目也可以在所有設備上運行。
2晶衷、ABI
Application.mk 文件如下
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a #這句是設置生成的cpu指令類型屯烦,提示坷随,目前絕大部分安卓手機支持armeabi,libs下太多類型驻龟,編譯進去 apk 包會過大
APP_PLATFORM := android-8 #這句是設置最低安卓平臺温眉,可以不弄
3、關于abiFilters的使用
在app的gradle的defaultConfig里面加上這么一句
ndk {
abiFilters "armeabi-v7a" // 指定要ndk需要兼容的架構(這樣其他依賴包里mips,x86,armeabi,arm-v8之類的so會被過濾掉)
}
這句話的意思就是指定ndk需要兼容的架構翁狐,把除了v7a以外的兼容包都過濾掉类溢,只剩下一個v7a的文件夾。
以上是一種ABI的添加露懒,多種如下
APP_ABI :=armeabi-v7a arm64-v8a armeabi mips mips64 x86 x86_64 //Application.mk中空格分開
gradle中逗號分開
ndk {
moduleName "detection_based_tracker"
abiFilters "armeabi-v7a","arm64-v8a","armeabi","mips","mips64","x86","x86_64"
}
官方說
參考文章
Android - OpenCV library
Android Camera動態(tài)人臉識別+人臉檢測基于OpenCV(無需OpenCVManager) - CSDN博客
Android Studio-—使用OpenCV的配置方法和demo以及開發(fā)過程中遇到的問題解決 - 奮斗者—cyf - 博客園
關于opencv的人臉識別的Demo配置(android端不需要Manager) - 簡書
Android Studio配置并運行OpenCV4Android的face-detection - Android移動開發(fā)技術文章_手機開發(fā) - 紅黑聯(lián)盟
Android NDK 入門與實踐
Android.mk | Android Developers
使用AndroidStudio編譯NDK的方法及錯誤解決方案 - 爐火純青 - 博客園
NDK各版本下載 - CSDN博客
OpenCV Android 打開前置后置攝像頭 - CSDN博客
OpenCV學習筆記(七)—— OpenCV for Android實時圖像處理 - CSDN博客
【安卓隨筆】使用OpenCV進行人臉跟蹤和自動拍照 - CSDN博客
Android OpenCV 實例筆記3 -- 攝像頭豎屏全屏的設置,更新完整代碼 - CSDN博客
OpenCV on Android 開發(fā) (4)豎屏預覽圖像問題解決方法-續(xù) - 簡書