OpenCV使用DNN模块加载SDD模型Android示例

前言

前两天照着OpenCV官方的How to run deep networks on Android device教程,在自己的小米手机上进行了测试,可能是由于手机的API兼容问题,使用OpenCV封装的JavaCameraView控件不能正常打开摄像头,始终是黑屏的状态,起初我以为是SELinux在作妖,直到后面使用Camera2 API才顺利捕获到摄像头帧完成测试。此外,使用OpenCV的官方代码会始终提示要安装OpenCV Manager,即使我已经加载OpenCV的动态链接库,后来直接跳过OpenCV Manager的初始化,程序才运行正常。这篇文章简单记录了下OpenCV使用DNN模块加载SSD模型涉及的用法,作为自己的一点积累。

DNN模型加载

自OpenCV4.2.0版本后,DNN模块的使用逐渐完善。目前,DNN模块支持包括OpenCL、OpenVINO、CUDA等后端,可加载包括Caffe、TensorFlow、ONNX等格式的模型进行推理,可以说功能全面,非常值得一试。使用OpenCV Java接口加载SSD模型的代码可参考官方例程,本文仅对一些需要注意的地方做一些记录。

加载OpenCV运行库

官方提供的示例代码会提示需要额外安装OpenCV Manager APP,但对于有强迫症的开发人员来说,这是不可能的。。因此在确保程序正常运行的同时,需要做一些简单处理,跳过OpenCV Manager的初始化,示例代码如下:

public void openCVInit() {
    // Initialize OpenCV manager.
    BaseLoaderCallback loaderCallback = new BaseLoaderCallback(this) {
      @Override
      public void onManagerConnected(int status) {
        switch (status) {
          case LoaderCallbackInterface.SUCCESS: {
            Log.i(TAG, "OpenCV loaded successfully");
            break;
          }
          default: {
            super.onManagerConnected(status);
            break;
          }
        }
      }
    };
    if (OpenCVLoader.initDebug()) {
      System.loadLibrary("opencv_java4");
      loaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
    }
    else {
      OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, loaderCallback);
    }
  }

上面的openCVInit函数同官方代码不同的地方在于,跳过了OpenCV Manager的初始化,同时手动加载OpenCV 运行库。在编写安卓APP时,可在类构造函数或Activity的onResume函数中调用此函数完成OpenCV的初始化。

设置OpenCV 后端

在官方代码中使用的是默认的后端,在实际应用中,可能会尝试在不同的后端运行,以确定效果最好的运行后端。可在官方loadModel函数中添加设置后端的代码,具体代码如下:

public void loadModel() {
    String proto = getPath("MobileNetSSD_deploy.prototxt", context);
    String weights = getPath("MobileNetSSD_deploy.caffemodel", context);
    net = Dnn.readNetFromCaffe(proto, weights);
    net.setPreferableBackend(Dnn.DNN_BACKEND_OPENCV);
    net.setPreferableTarget(Dnn.DNN_TARGET_CPU);
    Log.i(TAG, "Network loaded successfully");
}

上面的setPreferableTarget即指定模型在什么设备上运行,查看官方源码可以看到,可选的运行设备有如下:

public static final int
            DNN_TARGET_CPU = 0,
            DNN_TARGET_OPENCL = 0+1,
            DNN_TARGET_OPENCL_FP16 = 0+2,
            DNN_TARGET_MYRIAD = 0+3,
            DNN_TARGET_VULKAN = 0+4,
            DNN_TARGET_FPGA = 0+5,
            DNN_TARGET_CUDA = 0+6,
            DNN_TARGET_CUDA_FP16 = 0+7;

如果设备支持,应选择DNN_TARGET_CUDA_FP16 或DNN_TARGET_OPENCL_FP16使用半精度浮点数计算,可在精度无损失的前提下进一步提升推理实时性。

Bitmap转Mat

如果不使用OpenCV提供的视频预览控件,那么会涉及到如何将捕获到的Bitmap对象转换为Mat的问题。当然,OpenCV已经准备好相关的接口,用户只需调用函数即可完成转换,具体的代码如下:

import org.opencv.android.Utils;
import org.opencv.imgproc.Imgproc;

public Mat convertBitmapToMat(Bitmap inputFrame) {
	Mat frame = new Mat();
	Utils.bitmapToMat(inputFrame, frame);
	Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGBA2RGB);
	return frame;
}

在上面的代码中,bitmapToMat函数负责具体的Bitmap转Mat工作,传入的Mat对象可以为空。通常Bitmap都是RGBA的格式,而模型输入则是RGB格式,因此还需要调用cvtColor函数对转换后的Mat进行格式转换。

模型推理

在经过OpenCV初始化、模型加载和数据获取后,即可进行模型推理的步骤,使用DNN模块进行模型推理的方法也是非常简单,具体的代码如下:

final int IN_WIDTH = 300;
final int IN_HEIGHT = 300;
final double IN_SCALE_FACTOR = 0.007843;
final double MEAN_VAL = 127.5;

Mat blob = Dnn.blobFromImage(
	frame, 
	IN_SCALE_FACTOR,
	new Size(IN_WIDTH, IN_HEIGHT),
	new Scalar(MEAN_VAL, MEAN_VAL, MEAN_VAL), 
	/*swapRB*/false, 
	/*crop*/false
);
net.setInput(blob);
Mat detections = net.forward();

可以看出DNN的推理流程与TensorFlow较为相似,可理解为先定义模型的输入占位符,接着是传入图像张量开始推理,模型推理的结果最终以Mat的形式返回。

结语

得益于OpenCV社区的推进,使用OpenCV进行模型推理的步骤已经相当简单,对于OpenCV的用户而言,使用Mat作为模型输入输出简直不能太友好,同时OpenCV也支持多样的后端设备,使得它的实用性进一步增强。不过在官方示例中需要吐槽的是,提供的模型性能太烂,且在安卓设备上使用DNN_TARGET_OPENCL或者DNN_TARGET_OPENCL_FP16选项推理时几乎没有看到实时性的提升,可能是未适配安卓设备的OPENCL接口的原因,处理一帧图片大约需要220ms,跟TensorFlow-Lite官方示例一比简直是被按在地上摩擦,后者仅需大约50ms,只能说还有相当大的进步空间。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值