美文网首页AndroidAndroidAndroid开发
OpenCV -Android Studio 中使用(openc

OpenCV -Android Studio 中使用(openc

作者: 流水潺湲 | 来源:发表于2018-03-23 11:27 被阅读669次

    源码:https://github.com/huangshuyuan/OpenCvDemo-Master/

    一、导入程序

    1. 确定SdkVersion

    在导入程序之前,我们需要先确定待会的OpenCv工程中的一些和SdkVersion有关的配置,最好的办法就是先用AS建一个HelloWorld,也可以顺便熟悉一下Android Studio的开发流程。在工程中打开build.gradle,可以得到我们需要的信息:

    这个随意,和开发的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


    打开,直接下一步就行,我这边已经导入过了


    此时,我们在AS左侧的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:

    image.png

    原因在于,工程openCVSamplefacedetection依赖于库工程openCVLibrary340,而库工程openCVLibrary340的build.gradle配置也需要修改。这里不再赘述,找到openCVLibrary340下的build.gradle进行修改即可。

    修改完成后再次进行”Gradle project sync”,这一次”Gradle Sync”没有报错,随后AS会进行”Gradle build”,也顺利完成。

    3. jni的配置

    怎样解决上面出现的问题?
    首先,我们需要先配置NDK的路径,点击File->Project Structure,在如下的界面上配置NDK的路径。

    然后,在左侧的项目窗口中选中openCVSamplefacedetection,右键点击”Link C++ Project with Gradle”,在弹出的窗口中按照下图选择,点击”OK”。


    右击
    打开Android.mk文件

    ok,即可,会在gradle里面生成以下代码


     externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'
            }
        }
    

    接下来,在左右的项目窗口中的“External Build Files”下,选择Android.mk,修改OpenCV.mk的路径:

    include ../../sdk/native/jni/OpenCV.mk
    
    

    修改后的路径为,当然你需要根据自己电脑上的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部分的配置就全部完成,这时再点击”构建“,可以发现已经可以生成成功。

    二、剔除OpenCV Manager依赖

    在上一小节中,我们已经可以成功地配置fd工程,并且编译生成都成功,此时我们可以将apk部署到手机上进行运行。但是等等,你还想被OpenCV Manager所困扰吗?说实话,每次我想要找到适合自己手机的OpenCV Manager,都要上网查一大堆资料,费时又费劲。

    那么接下来我就基于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()函数中,修改以下部分

    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要在横屏时才能得到较好的结果,那么我可以先把竖屏时得到的图像顺时针旋转90度,这样就和横屏时一样了,然后我在把得到识别绿框的图像逆时针旋转90度,再输出这样就能做到竖屏时实现人脸检测了。
    所以修改FaActivity中的onCameraViewStarted和onCameraFrame()函数修改如下

       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里要如何实现呢?首先我们来观察一下JavaCameraView这个类,它继承自CameraBridgeViewBase

    这个类,再往下翻会发现一个非常熟悉的Camera对象,没错这个类里其实是使用了Android原生的API构造了一个相机对象(还好是原生的,至今还没忘却被JNI相机

    一旦实现了PreviewCallback接口,肯定会有onPreviewFrame(byte[] frame,Camera camera)这个回调函数,这里面的字节数组frame对象,就是当前的视频帧,注意这里是视频帧,是YUV编码的,并不能直接转换为Bitmap。这个回调函数在预览过程中会一直被调用,那么只要确定了哪一帧有人脸,只需要在这里获取就行,代码如下:

    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);  
        }  
    

    这里我们先在外层声明一个布尔类型的变量,在无限的回调过程中,一旦次布尔值为真,就对该视频帧保存为文件,上面的代码就将YUV视频帧转换为Bitmap对象的方法,然后将Bitmap存成文件,当然这也的结构不太合理,我只是为了展示方便这样书写。

    现在已经可以抓取照片了,那么如何才能判断是不是有人脸来修改这个布尔值呢,我们再定义这样一个方法:</pre>

    public void takePhoto(String name){  
           fileName = name;  
           takePhotoFlag = true;  
       }  
    

    一旦调用了takePhoto这个方法,传入一个保存路径,就能修改此布尔值,完成拍照,我们离完成越来越接近了。那么回到我们一开始的Activity,这里面包含刚刚修改的

    JavaCameraView对象,可以对他进行操作。然后找到Activity的onCameraFrame回调函数,修改为:

    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就调用刚才我们定义的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两个执行状态对应32、64bit
    x86 intel 32位,一般用于平板
    x86_64 intel 64位,一般用于平板
    mips 少接触
    mips64 少接触
    安装时的兼容性检查:

    安装到系统中后,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架构。
    对于情况一,一般规避的做法是:保证jnilibs目录下x86、x84_64、armeabi、armeabi-v7a、arm64-v8a等目录下的文件名称数量是一致的。
    例如:项目中使用了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文件规则:

    当你只提供了armeabi目录时,armeabi-v7a、arm64-v8a架构的程序都会去armeabi里寻找,而当你同时也提供了armeabi-v7a、armeabi-v8a目录,而里面又不存在对应的so库时,系统就不会再去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动态人脸识别+人脸检测基于OpenCV(无需OpenCVManager) - CSDN博客
    Android Studio-—使用OpenCV的配置方法和demo以及开发过程中遇到的问题解决 - 奋斗者—cyf - 博客园
    关于opencv的人脸识别的Demo配置(android端不需要Manager) - 简书
    Android Studio配置并运行OpenCV4Android的face-detection - Android移动开发技术文章_手机开发 - 红黑联盟
    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 开发 (4)竖屏预览图像问题解决方法-续 - 简书

    相关文章

      网友评论

      • bcd8b334f8b5:可以人脸识别么?不是人脸检测这个,就是比如和身份证比对这样
        流水潺湲:@fafuyuxing 你可以用百度的人脸识别,有sdk免费的
      • 魅力魔法0030:Demo的链接能发一下嘛?
        流水潺湲:@魅力魔法0030 https://github.com/huangshuyuan/OpenCvDemo-Master/
        流水潺湲:@魅力魔法0030 上次没有上传成功,太大了,好了贴源码
      • IT人故事会:经常看别人的分享.感谢别人的分享,感谢!关注了

      本文标题:OpenCV -Android Studio 中使用(openc

      本文链接:https://www.haomeiwen.com/subject/wzubqftx.html