CameraX预览模糊实现失败:捕获预览Bitmap为空问题求助
CameraX 实现模糊相机预览的正确方案
我正在使用CameraX构建相机预览,想要给预览添加模糊效果。尝试过捕获PreviewView的TextureView转Bitmap再用RenderScript模糊,但日志一直显示"Camera preview bitmap is null",参考的旧方法也不生效。
问题根源
- PreviewView 内部实现不确定:CameraX的PreviewView默认使用SurfaceView而非TextureView,所以
cameraPreview.getChildAt(0) as? TextureView大概率返回null,导致无法获取Bitmap。 - 重复绑定相机:原代码中
startCamera和cameraProviderFuture回调都执行了相机绑定逻辑,会导致冲突。 - 帧捕获方式错误:依赖TextureView的
bitmap属性获取预览帧不可靠,应该用CameraX的ImageAnalysis用例稳定获取每一帧。
修正方案
1. 用ImageAnalysis替代TextureView捕获帧
通过CameraX的ImageAnalysis用例直接获取相机帧,避免依赖PreviewView的内部视图结构。
2. 统一相机绑定逻辑
移除重复的绑定代码,确保权限通过后只绑定一次相机用例(Preview + ImageAnalysis)。
3. 正确处理YUV图像转Bitmap
CameraX返回的图像是YUV_420_888格式,需要转换为RGB Bitmap才能正常应用模糊效果。
4. 优化模糊性能与内存管理
缩小图片后再模糊提升性能,同时及时回收Bitmap、关闭ImageProxy、销毁RenderScript资源,避免内存泄漏。
修正后的完整代码
import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.ImageFormat import android.graphics.Matrix import android.graphics.Rect import android.graphics.YuvImage import android.os.Bundle import android.util.Log import android.view.View import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import android.renderscript.* import java.io.ByteArrayOutputStream import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class WelcomeActivity : AppCompatActivity() { private lateinit var cameraPreview: PreviewView private lateinit var cameraBtn: View private lateinit var blurOverlayImageView: ImageView private lateinit var cameraProviderFuture: androidx.camera.core.impl.ListenableFuture<ProcessCameraProvider> private lateinit var cameraExecutor: ExecutorService private var blurLevel: Int = 12 private val CAMERA_PERM_CODE = 101 private val TAG = "CameraXBlur" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_welcome) cameraPreview = findViewById(R.id.cameraPreview) cameraBtn = findViewById(R.id.cameraButton) blurOverlayImageView = findViewById(R.id.blurOverlay) cameraExecutor = Executors.newSingleThreadExecutor() if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA), CAMERA_PERM_CODE ) } cameraBtn.setOnClickListener { startActivity(Intent(this, CameraActivity::class.java)) } } private fun startCamera() { cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() // 配置预览用例 val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(cameraPreview.surfaceProvider) } // 配置图像分析用例,获取预览帧 val imageAnalysis = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { it.setAnalyzer(cameraExecutor) { imageProxy -> processImageProxy(imageProxy) } } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis ) } catch (exc: Exception) { Log.e(TAG, "相机绑定失败", exc) } }, ContextCompat.getMainExecutor(this)) initializeBlurAnimator() } private fun processImageProxy(imageProxy: ImageProxy) { val bitmap = imageProxy.toBitmap() bitmap?.let { val blurredBitmap = blurRenderScript(it, blurLevel) runOnUiThread { blurOverlayImageView.setImageBitmap(blurredBitmap) } it.recycle() blurredBitmap?.recycle() } imageProxy.close() } // 将ImageProxy转换为Bitmap并修正方向 private fun ImageProxy.toBitmap(): Bitmap? { val image = this.image ?: return null val yuvImage = YuvImage( image.planes[0].buffer, ImageFormat.NV21, image.width, image.height, null ) val outputStream = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, image.width, image.height), 100, outputStream) val bitmap = BitmapFactory.decodeByteArray(outputStream.toByteArray(), 0, outputStream.size) val matrix = Matrix() matrix.postRotate(imageInfo.rotationDegrees.toFloat()) val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) bitmap.recycle() return rotatedBitmap } // RenderScript模糊处理 private fun blurRenderScript(inputBitmap: Bitmap, radius: Int): Bitmap? { val maxRadius = 25 val safeRadius = if (radius > maxRadius) maxRadius else radius // 缩小图片提升性能 val scale = 0.2f val scaledWidth = (inputBitmap.width * scale).toInt() val scaledHeight = (inputBitmap.height * scale).toInt() val scaledBitmap = Bitmap.createScaledBitmap(inputBitmap, scaledWidth, scaledHeight, false) val outputBitmap = Bitmap.createBitmap(scaledBitmap) var renderScript: RenderScript? = null try { renderScript = RenderScript.create(this) val intrinsic = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) val tmpIn = Allocation.createFromBitmap(renderScript, scaledBitmap) val tmpOut = Allocation.createFromBitmap(renderScript, outputBitmap) intrinsic.setRadius(safeRadius.toFloat()) intrinsic.setInput(tmpIn) intrinsic.forEach(tmpOut) tmpOut.copyTo(outputBitmap) return Bitmap.createScaledBitmap(outputBitmap, inputBitmap.width, inputBitmap.height, false) } catch (e: Exception) { Log.e(TAG, "模糊处理失败", e) return null } finally { scaledBitmap.recycle() outputBitmap.recycle() renderScript?.destroy() } } // 模糊程度动画 private fun initializeBlurAnimator() { val animator = androidx.animation.ValueAnimator.ofInt(6, 12, 18, 24, 25) animator.repeatCount = androidx.animation.ValueAnimator.INFINITE animator.repeatMode = androidx.animation.ValueAnimator.RESTART animator.duration = 3000 animator.addUpdateListener { blurLevel = it.animatedValue as Int } animator.start() } private fun allPermissionsGranted() = ActivityCompat.checkSelfPermission( this, Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == CAMERA_PERM_CODE) { if (allPermissionsGranted()) { startCamera() } else { Toast.makeText(this, "需要相机权限才能使用相机", Toast.LENGTH_SHORT).show() } } } override fun onDestroy() { super.onDestroy() cameraExecutor.shutdown() } }
关键说明
- ImageAnalysis 稳定性:直接从相机获取帧数据,不受PreviewView内部视图变化影响。
- 图像格式转换:处理YUV转RGB的过程,同时修正图像旋转角度,保证预览方向正确。
- 性能优化:先缩小图片再模糊,大幅降低计算量,模糊完成后再放大回原尺寸。
- 内存安全:及时回收Bitmap资源、关闭ImageProxy、销毁RenderScript,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Ka hei Tong
相关产品推荐
相关产品推荐

