学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3743字,预计阅读9分钟
前言
好久没用Kotlin写Android的代码了,刚开始写起来还有点生,不过适应了一阵也算是恢复过来了。今天这篇主要是说说Android JepPack组件中CameraX的使用,其实网上也有不少简单的例子,本篇也是参考了网的一篇文章后实现的。主要要说的还是后面,怎么在原有的图像上进行编辑显示,文末有源码的链接。
实现效果
划重点
要在CameraX中实现图像上显示修改的图像,需要在PreviewView的上层再自己写一个View,使用Canvas.draw的方式进行绘制显示,而无法直接在Preview中进行图像的更改。这个和我以前文章《Android通过OpenCV和TesserartOCR实时进行识别》直接在OpenCV中修改了图像后在通过SurfaceView显示是完全不一样的。
CameraX的使用
微卡智享
01
导入依赖包
在项目的build.gradle中加入依赖项
代码语言:javascript复制 implementation "androidx.camera:camera-camera2:1.0.0-beta12"
implementation "androidx.camera:camera-view:1.0.0-alpha19"
implementation "androidx.camera:camera-extensions:1.0.0-alpha19"
implementation "androidx.camera:camera-lifecycle:1.0.0-beta12"
02
申请权限
在Androidmanifests下的manifests的节点加申请权限内容
代码语言:javascript复制 <!-- 摄像头权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 具备摄像头 -->
<uses-feature android:name="android.hardware.camera.any" />
<!-- 存储图像或者视频权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 录制音频权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
在Android的6.0后需要动态申请权限,所以要加入动态申请权限的代码
动态申请权限相关代码
代码语言:javascript复制 //权限列表
companion object {
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO
)
}
//判断是否有权限
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
//请求权限返回的函数
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "未开启权限.", Toast.LENGTH_SHORT).show()
finish()
}
}
}
03
创建实时图像上层的View
我们先创建一个ViewOverLay继承自View,用于实现摄像机上层修改的显示图层,其中定义了显示的文字,输出的坐标及drawtext的绘制文字方法
代码语言:javascript复制package dem.vaccae.camerax
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.graphics.red
/**
* 作者:Vaccae
* 邮箱:3657447@qq.com
* 创建时间:2020-11-26 14:51
* 功能模块说明:
*/
class ViewOverLay constructor(context: Context?, attributeSet: AttributeSet?) :
View(context, attributeSet) {
private var mText: String? = null
private var mPoint: PointF? = null
private val textpaint = Paint().apply {
style = Paint.Style.FILL
color = ContextCompat.getColor(context!!, android.R.color.holo_blue_light)
strokeWidth = 10f
textSize = 150f
isFakeBoldText = true
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mText?.let {
canvas?.drawText(it, mPoint!!.x, mPoint!!.y, textpaint)
}
}
fun drawText(str: String, ptr: PointF) {
mText = str
mPoint = ptr;
invalidate()
}
}
04
布局文件
代码语言:javascript复制<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.camera.view.PreviewView
android:id="@ id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</androidx.camera.view.PreviewView>
<dem.vaccae.camerax.ViewOverLay
android:id="@ id/viewOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
布局文件中除了加入PreviewView,然后再加入了刚才创建的ViewOverLay,两个都是全屏的显示。
05
MainActivity关键代码
在OnCreate加载时需要注意自己创建的View一定要显示在PreviewView的上层,所以要加上bringToFront()
代码语言:javascript复制 override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewFinder = findViewById(R.id.viewFinder)
vOverLay = findViewById(R.id.viewOverlay)
//显示在最上层
vOverLay.bringToFront()
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
}
全局变量
代码语言:javascript复制 private lateinit var cameraExecutor: ExecutorService
var cameraProvider: ProcessCameraProvider? = null//相机信息
var preview: Preview? = null//预览对象
var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//当前相机
var camera: Camera? = null//相机对象
private var imageCapture: ImageCapture? = null//拍照用例
var videoCapture: VideoCapture? = null//录像用例
var imageAnalyzer: ImageAnalysis? = null//图片分析
开启摄像机
代码语言:javascript复制 private fun startCamera() {
cameraExecutor = Executors.newSingleThreadExecutor()
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
cameraProvider = cameraProviderFuture.get()//获取相机信息
//预览配置
preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
//拍照用例配置
imageCapture = ImageCapture.Builder().build()
//图像分析接口
imageAnalyzer = ImageAnalysis.Builder()
.build()
.also { it ->
it.setAnalyzer(cameraExecutor, { p->
count ;
//每20帧执行一次
if (count % 20 == 0) {
val idx = idxarray % 4
vOverLay.drawText(strarray[idx], ptrarray[idx])
idxarray
}
//这里的ImageProxy如果不close,不会显示下一帧
p.close()
})
}
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//使用后置摄像头
videoCapture = VideoCapture.Builder()//录像用例配置
/* .setTargetAspectRatio(AspectRatio.RATIO_16_9) //设置高宽比
.setTargetRotation(viewFinder.display.rotation)//设置旋转角度
.setAudioRecordSource(AudioSource.MIC)//设置音频源麦克风*/
.build()
try {
cameraProvider?.unbindAll()//先解绑所有用例
camera = cameraProvider?.bindToLifecycle(
this, cameraSelector, preview, imageCapture,
imageAnalyzer
)//绑定用例
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
划重点
进行图像的分析是通过ImageAnalysis的接口实现,上图中Analyzer里的使用lambda表达中p代表着传入的参数ImageProxy,每当我们处理完当前帧操作时,要记得将其close,否则后面是无变化的。
上面代码就是实现了每20帧更新显示一个新的汉字的效果,就是我们开头的动图中的效果实现。
通过上面的代码,一个简单的Camera的效果就实现了,可以看出来,用CameraX的调用,要比原来Camera写起来简单了好多,不过要注意一点是CameraX就是Camera2的封装,所以Android的最低版本要求是5.1。
接下来我会用CameraX调用摄像头配合OpenCV,做点小东西了。
源码地址
https://github.com/Vaccae/AndroidCameraXDemo.git
完