Android JetPack组件CameraX使用及修改显示图像

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为3743,预计阅读9分钟

前言

好久没用Kotlin写Android的代码了,刚开始写起来还有点生,不过适应了一阵也算是恢复过来了。今天这篇主要是说说Android JepPack组件中CameraX的使用,其实网上也有不少简单的例子,本篇也是参考了网的一篇文章后实现的。主要要说的还是后面,怎么在原有的图像上进行编辑显示,文末有源码的链接。

format,png

实现效果

format,png

划重点

要在CameraX中实现图像上显示修改的图像,需要在PreviewView的上层再自己写一个View,使用Canvas.draw的方式进行绘制显示,而无法直接在Preview中进行图像的更改。这个和我以前文章《Android通过OpenCV和TesserartOCR实时进行识别》直接在OpenCV中修改了图像后在通过SurfaceView显示是完全不一样的。

CameraX的使用

format,png

微卡智享

01

导入依赖包

在项目的build.gradle中加入依赖项

    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的节点加申请权限内容

    <!-- 摄像头权限 -->
    <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后需要动态申请权限,所以要加入动态申请权限的代码

动态申请权限相关代码

    //权限列表
    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的绘制文字方法

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

布局文件

<?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()

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

全局变量

    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//图片分析

开启摄像机

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

划重点

format,png

进行图像的分析是通过ImageAnalysis的接口实现,上图中Analyzer里的使用lambda表达中p代表着传入的参数ImageProxy,每当我们处理完当前帧操作时,要记得将其close,否则后面是无变化的。

上面代码就是实现了每20帧更新显示一个新的汉字的效果,就是我们开头的动图中的效果实现。

format,png

通过上面的代码,一个简单的Camera的效果就实现了,可以看出来,用CameraX的调用,要比原来Camera写起来简单了好多,不过要注意一点是CameraX就是Camera2的封装,所以Android的最低版本要求是5.1。

接下来我会用CameraX调用摄像头配合OpenCV,做点小东西了。

源码地址

https://github.com/Vaccae/AndroidCameraXDemo.git

format,png

扫描二维码

获取更多精彩

微卡智享

format,png

「 往期文章 」

OpenCV图片动态特效显示(四)-- 中间扩张和栅格显示效果

OpenCV图片动态特效显示(三)-- 平移显示及拉伸显示效果

OpenCV图片动态特效显示(二)--渐显和马赛克显示

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值