NO.2 CameraX

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


step 1 (依赖)

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


}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LakJeqp-1585648545745)(media/15855483514011/15856133146946.jpg)]


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)

                }
            })

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6mu4URJ3-1585648545747)(media/15855483514011/15856186604700.jpg)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vkeTF1Ug-1585648545747)(media/15855483514011/15856187867860.jpg)]


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问题]**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HEFw042r-1585648545748)(media/15855483514011/15856444210458.jpg)]

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

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所以无法获取原生的正方形图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AY2wokYD-1585648545748)(media/15855483514011/15856482653665.jpg)]

beta01 移步 NO.2 CameraX (beta01补充)


🔗 前言
🔗 Android Temporary
🔗 NO.1 Retrofit
🔗 NO.2 CameraX (beta01补充)
🔗 NO.3 GreenDao(&加密问题)
🔗 NO.4 Tinker

E/CameraX: No default CameraXConfig.Provider specified in meta-data. The most likely cause is you did not include a default implementation in your build such as 'camera-camera2'. D/AndroidRuntime: Shutting down VM E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.photodiary, PID: 13938 java.lang.IllegalStateException: CameraX is not configured properly. The most likely cause is you did not include a default implementation in your build such as 'camera-camera2'. at androidx.camera.core.CameraX.<init>(CameraX.java:109) at androidx.camera.lifecycle.ProcessCameraProvider.getOrCreateCameraXInstance(ProcessCameraProvider.java:181) at androidx.camera.lifecycle.ProcessCameraProvider.getInstance(ProcessCameraProvider.java:167) at com.example.photodiary.Diary.startCamera(Diary.java:43) at com.example.photodiary.Diary.access$000(Diary.java:20) at com.example.photodiary.Diary$1.onClick(Diary.java:32) at android.view.View.performClick(View.java:7125) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1131) at android.view.View.performClickInternal(View.java:7102) at android.view.View.access$3500(View.java:801) at android.view.View$PerformClick.run(View.java:27336) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
05-24
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

零蚀zero eclipse

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

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

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

打赏作者

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

抵扣说明:

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

余额充值