欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NO.2 CameraX

程序员文章站 2023-12-25 20:27:51
...

零蚀
[camerax 官方文档(*)](这是一个高能网站,很多相关的google教程都在这)
配置 ->动态权限 ->预览->文件->YUV_420_888转bitmap


step 1 (依赖)

导入最新的camerax 版本

// Use the most recent version of CameraX, currently that is alpha06.
ext {
        amerax_version = '1.0.0-alpha06'
}
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"


还需要jdk1.8的支持

// android 下
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}   

camera显示控件TextureView

<TextureView
        android:id="@+id/view_finder"
        android:layout_width="640px"
        android:layout_height="640px"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

step 2(权限)

权限(动态权限)

<uses-permission android:name="android.permission.CAMERA" />
 // 获取权限
if(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        !=PERMISSION_GRANTED){
    ActivityCompat.requestPermissions(this, arrayOf(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
    ),10001)
}else{
    LogUtil.d("已经申请了权限")
    normalInit()
}

// 权限返回值
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if(requestCode==10001){

        permissions.forEachIndexed { index, it ->
            if(grantResults[index]==0) {
                LogUtil.d("权限: $it 申请成功")
            }else{
                LogUtil.d("权限: $it 申请失败")
            }
        }
        // 初始化摄像头等操作
        normalInit()
    }
}

step 3(最简demo)

添加监听

object CameraBaseUtil{

    .....

    fun initCameraListener(view:TextureView){
        view.addOnLayoutChangeListener { v,
                                         left, top, right, bottom,
                                         oldLeft, oldTop, oldRight, oldBottom ->
            LogUtil.d("camera的监听发生改变")
        }
    }
    
    ....

构建camera,启动,变化,结束的demo

object CameraWorkUtil {

    private var preview:Preview?=null
    
    enum class CameraStatus {
        START,
        UPDATE,
        FINISH
    }


    fun initCameraListener(view: TextureView) {
        view.addOnLayoutChangeListener { v,
                                         left, top, right, bottom,
                                         oldLeft, oldTop, oldRight, oldBottom ->

            //action(this,view,CameraStatus.UPDATE)
            LogUtil.d("camera的监听发生改变")
        }
    }

    fun action(owner: LifecycleOwner, view: TextureView, status: CameraStatus) {
        when (status) {
            CameraStatus.START -> startCamera(owner, view)
            CameraStatus.UPDATE -> upDataCamera()
            CameraStatus.FINISH -> finishCamera()
        }
    }

    private fun finishCamera() {
        if (preview!=null) {
            CameraX.unbind(preview)
        }
    }

    private fun upDataCamera() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }


    private fun startCamera(owner: LifecycleOwner, viewFinder: TextureView) {
        // 创建配置对象,供给TextureView使用
        val previewConfig = PreviewConfig.Builder().apply {
            setTargetResolution(Size(640, 480))
        }.build()

        // 构建使用场景
        preview = Preview(previewConfig)

        // 每当发生变化都会使得,重新计算&布局
        preview.setOnPreviewOutputUpdateListener {
            val parent = viewFinder.parent as ViewGroup
            parent.removeView(viewFinder)
            parent.addView(viewFinder, 0)

            viewFinder.surfaceTexture = it.surfaceTexture
            //upDataCamera()
        }

        CameraX.bindToLifecycle(owner,preview)
    }


}

NO.2 CameraX


step 4(生成文件)

获取图像的文件操作

private fun startCamera(owner: LifecycleOwner, viewFinder: TextureView) {


    val previewConfig = PreviewConfig.Builder().apply {
        setTargetResolution(Size(640,640))
    }.build()

    val preview=Preview(previewConfig)

    // 每当发生变化都会使得,重新计算&布局
    preview.setOnPreviewOutputUpdateListener {
        val parent = viewFinder.parent as ViewGroup
        parent.removeView(viewFinder)
        parent.addView(viewFinder, 0)

        viewFinder.surfaceTexture = it.surfaceTexture
        //upDataCamera()
    }

    // 捕获图像(它自己氛围低质和高质两种图像,差异在于获取的时间)
    // 这里不在设置分辨率,而是相位定量
    val imageCaptureConfig = ImageCaptureConfig.Builder()
            .apply {
                    setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
            }.build()

    // 将图像捕捉的场景依附于点击监听上。
    imageCapture = ImageCapture(imageCaptureConfig)

    CameraX.bindToLifecycle(owner, preview , imageCapture)
}




fun getPictureConfig(file:File,listener:OnCameraCaptureInter){

    imageCapture?.takePicture(file, Executors.newCachedThreadPool(),
             object :ImageCapture.OnImageSavedListener{
                override fun onImageSaved(file: File) {
                    listener.success(file)

                }

                override fun onError(imageCaptureError: ImageCapture.ImageCaptureError, message: String, cause: Throwable?) {
                    listener.error(message)

                }
            })

}

NO.2 CameraX

ivCaptureButton.setOnClickListener {


    // 内存地址都是固定的,不能创建文件夹
    val file=File(Environment.getExternalStorageDirectory().absolutePath+
                    File.separator+
                    Environment.DIRECTORY_PICTURES,
                    "${System.currentTimeMillis()}.jpg")

    LogUtil.e(file.absolutePath)
    CameraBaseUtil.getPictureConfig(file,object:OnCameraCaptureInter{
    	override fun success(file: File) {

        	toast("the file's path is ${file.path}")
    	}

    	override fun error(message: String) {
        	toast(message)
    	}
  	})
}

NO.2 CameraX


step 5(YUV_420_888)

获取图像像素矩阵,并转为bitmap(划重点,官网没有)

首先我们要知道camerax默认的iamgeFormat是YUV_420_888,而一般我们转为图片的格式只有NV21和YUY2所以采用YuvImage来转图片的byteArray是行不通的。(我们从camera直接拿到的byteArray是不能进行直接的Bitmap的,可以了解一下YUV),所以为了转换计算方便需要用到NDK来处理这些byte转换问题

具体转化方法可以看**[???? Android的YUV_420_888图片转换Bitmap时的rowStride问题]**

NO.2 CameraX

通过图像代理类,获取到位图

object BitmapUtils {

    

    fun imageToBitmap(image: ImageProxy): Bitmap? {


        val yBuffer = image.planes[0].buffer

        val uBuffer = image.planes[1].buffer
        val uStride = image.planes[1].pixelStride

        val vBuffer = image.planes[2].buffer
        val vStride = image.planes[2].pixelStride

        val buffer = ByteArray(image.width * image.height * 3 / 2)


        val rowStride = image.planes[0].rowStride
        val padding = rowStride - image.width
        var pos = 0

        if (padding == 0) {
            pos = yBuffer.remaining()
            yBuffer.get(buffer, 0, pos)
        } else {
            var yBufferPos = 0
            for (row in 0 until image.height) {
                yBuffer.position(yBufferPos)
                yBuffer.get(buffer, pos, image.width)
                yBufferPos += rowStride
                pos += image.width
            }
        }

        var i = 0

        val uRemaining = uBuffer.remaining()
        while (i < uRemaining) {
            buffer[pos++] = uBuffer[i]
            i += uStride

            if (padding == 0) continue

            val rowLen = i % rowStride
            if (rowLen >= image.width) {
                i += padding
            }
        }

        i = 0
        val vRemaining = vBuffer.remaining()
        while (i < vRemaining) {
            buffer[pos++] = vBuffer[i]
            i += vStride

            if (padding == 0) continue

            val rowLen = i % rowStride
            if (rowLen >= image.width) {
                i += padding
            }
        }
        return ColorConvertUtil.yuv420pToBitmap(buffer, image.width, image.height)
    }

}

设置analyze,并设置获取图像的数据频率,

class CameraPicAnalyzer(pixel:PixelCameraInter): ImageAnalysis.Analyzer{

    // 图像的平面(像素矩阵)缓冲
    private fun ByteBuffer.toByteArray(): ByteArray {
        rewind()    // Rewind the buffer to zero
        val data = ByteArray(remaining())
        get(data)   // Copy the buffer into a byte array
        return data // Return the byte array
    }

    private var lastAnalyzedTimestamp:Long=0
    private val listener =pixel
    
    override fun analyze(image: ImageProxy?, rotationDegrees: Int) {
        val currentTimestamp = System.currentTimeMillis()

        //计算平均频率不超过每秒一次
        if (currentTimestamp - lastAnalyzedTimestamp >=
                TimeUnit.SECONDS.toMillis(1)) {

            if(image!=null){
                val bitmap = BitmapUtils.imageToBitmap(image)
                listener.data(bitmap!!)
            }

            lastAnalyzedTimestamp = currentTimestamp
        }
    }
}

启动摄像头时添加获取数据的场景

 private fun startCamera(owner: LifecycleOwner, viewFinder: TextureView) {



        val previewConfig = PreviewConfig.Builder().apply {
            // 宽高比只有16:9和4:3
            setTargetAspectRatio(AspectRatio.RATIO_4_3)
            //设置分辨率
            setTargetResolution(Size(1280,1280))
        }.build()

        val preview=Preview(previewConfig)

        // 每当发生变化都会使得,重新计算&布局
        preview.setOnPreviewOutputUpdateListener {
            val parent = viewFinder.parent as ViewGroup
            parent.removeView(viewFinder)
            parent.addView(viewFinder, 0)

            viewFinder.surfaceTexture = it.surfaceTexture
            //upDataCamera()
        }


        // 捕获图像(它自己氛围低质和高质两种图像,差异在于获取的时间)
        // 这里不在设置分辨率,而是相位定量
        val imageCaptureConfig = ImageCaptureConfig.Builder()
                .apply {
                    setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
                }.build()

        // 将图像捕捉的场景依附于点击监听上。
         imageCapture = ImageCapture(imageCaptureConfig)


        // 图像的数据设置场景
        val analyzerConfig = ImageAnalysisConfig.Builder().apply {
            setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
        }.build()

        val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
            setAnalyzer(executors, analyze)
        }

        CameraX.bindToLifecycle(owner, preview , imageCapture, analyzerUseCase)
    }

调用方法

fun getPictureConfig():Bitmap{
    val matrix = Matrix()
    matrix.postRotate(90f)
    return Bitmap.createBitmap(data, 0, 0, data!!.width, data!!.height,matrix, true)
}

//布局中调用
 ivCapture.setOnClickListener {
        image.setImageBitmap(cameraFactory.getPictureConfig())
}

这样我们就能获取到他的bitmap并且在这转化中就可以对这bitmap进行人像操作等,进行诸多设计。

由于只支持4:3和16:9所以无法获取原生的正方形图。

NO.2 CameraX


???? 前言
???? Android Temporary
???? NO.1 Retrofit

相关标签: Android Temporary

上一篇:

下一篇: