前言
:fire: :fire: :fire: 随着人工智能的快速发展以及终端设备硬件水平的不断提升,在终端设备上直接运行智能系统成为可能,端侧智能具备低延时,隐私安全等特点。同时降低了云端智能存在的网络传输不可靠风险,使得端侧智能越来越得到重视。端侧智能比较成熟的领域就是 NLP 以及 CV。在 CV 领域 OpenCV 作为开源且强大的跨平台计算机视觉库,在图像处理以及图像识别方向得到了广泛应用。但是在 Android 平台 OpenCV 官方 SDK 在图像预览方面存在诸多缺陷。
SmartOpenCV 是什么
SmartOpenCV 是一个 OpenCV 在 Android 端的增强库,解决了 OpenCV Android SDK 在图像预览方面存在的诸多问题,而且无需修改 OpenCV SDK 源码,与 OpenCV 的 SDK 解耦,只需替换 xml 中原 OpenCV 的JavaCameraView
/JavaCamera2View
即可达到具备 OpenCV 官方 SDK 的原功能以及 SmartOpenCV 的增强功能。
OpenCV 官方 SDK 存在的问题
OpenCV Android 端 SDK 虽然很容易上手和使用,但是预览存在很多问题,常见问题如下:
默认横屏显示,且无法通过接口修改预览方向
预览绘制存在黑边:OpenCV 默认绘制算法在绘制预览帧图像到 Canvas 时存在一定的偏移,在视觉上表现就是预览帧只会占 SurfaceView 控件的一部分区域,偏移部分区域会显示为黑色
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 默认算法是选择小于预览控件(或设置的最大帧大小)的最大预览,这将导致在很多情况下预览图像的显示不能铺满整个控件甚至远小于控件大小, 在绝大部分业务场景下,这种算法不能满足实际需求
protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) { int calcWidth = 0; int calcHeight = 0; int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth; int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight; for (Object size : supportedSizes) { int width = accessor.getWidth(size); int height = accessor.getHeight(size); Log.d(TAG, "trying size: " + width + "x" + height); if (width <= maxAllowedWidth && height <= maxAllowedHeight) { if (width >= calcWidth && height >= calcHeight) { calcWidth = (int) width; calcHeight = (int) height; } } } if ((calcWidth == 0 || calcHeight == 0) && supportedSizes.size() > 0) { Log.i(TAG, "fallback to the first frame size"); Object size = supportedSizes.get(0); calcWidth = accessor.getWidth(size); calcHeight = accessor.getHeight(size); } return new Size(calcWidth, calcHeight); }
SmartOpenCV 的特点
易使用:如果你项目中之前使用的是 OpenCV 的官方 SDK,那么引入 SmartOpenCV 后只需将 xml 文件中的
JavaCameraView
/JavaCamera2View
替换为 SmartOpenCV 的CamerPreview
/Camera2Preview
即可达到与使用官方 SDK 相同的效果功能增强:
- 预览自适应:自动根据前后摄像头,横竖屏以及不同摄像头参数来调整与适配预览方向以及大小,开发者无需写任何额外代码
- 可扩展预览绘制算法:SmartOpenCV 内置了一种默认的预览帧绘制算法,同时提供策略接口让开发者根据自己的业务场景自定义预览绘制算法
- 可扩展预览帧大小选择算法:SmartOpenCV 内置了一种默认的预览帧大小计算算法,同时提供策略接口让开发者根据自己的业务自定义预览帧大小计算算法
- 支持USB 摄像头:USB 摄像头作为外设接入设备,和手机/平板等移动设备内置摄像头存在差异,SDK 内部在处理移动设备摄像头的逻辑时也兼容了对闸机等的 USB 摄像头的处理
提供更友好的 API 接口:在继承 OpenCV 官方接口的同时,SmartOpenCV 将众多繁杂操作统一通过 CameraConfiguration 来配置,提供更友好的 Fluent API 接口,让开发者能够更灵活的控制预览显示相关参数与配置
不直接依赖官方 SDK,方便升级官方 SDK:与 OpenCV 官方 SDK 解耦,只要官方 SDK 内部核心逻辑未做修改,那么 SmartOpenCV 可以兼容所有版本的官方 SDK,使用 SmartOpenCV 后如果以后打算升级依赖的 OpenCV 为更新版本,只需将 OpenCV 的依赖更新为新版本即可,代码无需做任何改动
效果对比
以人脸识别为例
横屏 | 竖屏 | |
---|---|---|
OpenCV | 即使宽与高都设置为 match_parent 也无法全屏,存在黑边 |
存在黑边,且默认不支持竖屏 |
SmartOpenCV |
Demo 对比体验
smartopencv-app-debug.apk
opencv-app-debug.apk
Integration
Step1:在项目根目录的 build.gradle 中添加对 jitpack 仓库的配置
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step2:在需要使用SmartOpenCV
库的模块中添加依赖
dependencies {
implementation('com.github.HuTianQi:SmartOpenCV:1.0.1') { // 版本号建议使用已 release 的最新版本
exclude module: 'openCVLibrary411' // 由于目前多模块依赖时 jitpack 打包存在 bug,排除打包时依赖的该模块
}
}
Usage
基础用法
在项目中需要使用预览的 xml 中用 SmartOpenCV 的CameraPreview
/Camera2Preview
替换 OpenCV 的JavaCameraView
/JavaCamera2View
即可,就这么简单,其余的什么都不用做
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--<org.opencv.android.JavaCameraView-->
<!--android:id="@+id/fd_activity_surface_view"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="match_parent" />-->
<tech.huqi.smartopencv.core.preview.CameraPreview
android:id="@+id/fd_activity_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
高级用法
如果打算通过 SmartOpenCV 提供的接口来更灵活的控制预览显示相关参数与配置,那么调用SmartOpenCV.getInstance().init()
传入前面获取的预览控件对象即可,用法如下:
SmartOpenCV.getInstance().init(mOpenCvCameraView, new CameraConfiguration.Builder()
.debug(true)
.cameraIndex(0) // 设置摄像头索引,主要用于多摄像头设备,优先级低于 frontCamera
.keepScreenOn(false) // 是否保持屏幕常亮
.frontCamera(true) // 是否使用前置摄像头
.openCvDefaultDrawStrategy(false) // 是否使用 OpenCV 默认的预览图像绘制策略
.openCvDefaultPreviewCalculator(false) // 是否使用 OpenCV 默认的预览帧大小计算策略
.landscape(false) // 是否横屏显示
.enableFpsMeter(true) // 开启预览帧率的显示
.usbCamera(false) // 是否使用 USB 摄像头,当设备接入的是 USB 摄像头时将其设置为 true
.maxFrameSize(400, 300) // 设置预览帧的最大大小
.cvCameraViewListener(this) // 设置 OpenCV 回调监听器
.previewSizeCalculator(new IPreviewSizeCalculator() { // 自定义预览帧大小计算策略
@Override
public Size calculateCameraFrameSize(List<Size> supportedSizes, int surfaceWidth, int surfaceHeight) {
// 若需要根据自己的具体业务场景改写览帧大小,覆写该方法逻辑
return new Size(1080,1920);
}
})
.drawStrategy(new IDrawStrategy() { // 自定义绘制策略
@Override
public void drawBitmap(Canvas canvas, Bitmap frameBitmap, int surfaceWidth, int surfaceHeight) {
// 若需根据自己的具体业务场景绘制预览帧图像,覆写该方法逻辑
}
})
.build());
LICENSE
公众号