CameraX ImageAnalysis输出损坏碎片化 部分设备二维码扫描异常
问题根源
你的图像破碎问题是由相机YUV帧的行步长(rowStride)对齐机制导致的:
- 多数设备(比如Pixel)输出的YUV帧行步长等于图像宽度,无额外填充字节,所以直接读取整个buffer的逻辑可以正常运行
- 少数设备为了硬件性能对齐,会在每行YUV数据末尾添加填充字节,你直接读取整个buffer的逻辑会把填充字节也算入有效图像数据,传入zxing的
PlanarYUVLuminanceSource后就会出现画面错位、破碎的问题
修复方案
1. 修正Y通道数据读取逻辑
将原来直接读取整个buffer的逻辑替换为逐行拷贝,跳过每行的填充字节,代码修改如下:
override fun analyze(image: ImageProxy) { val yPlane = image.planes[0] val rowStride = yPlane.rowStride val pixelStride = yPlane.pixelStride val imageWidth = image.width val imageHeight = image.height // 初始化刚好容纳有效Y数据的数组,大小固定为宽*高 if (imageData.size != imageWidth * imageHeight) { imageData = ByteArray(imageWidth * imageHeight) } val buffer = yPlane.buffer buffer.rewind() // 逐行拷贝有效像素,跳过填充字节 for (row in 0 until imageHeight) { val rowOffset = row * imageWidth // 定位到当前行的起始位置 buffer.position(row * rowStride) if (pixelStride == 1) { // 绝大多数设备Y通道像素步长为1,直接拷贝整行有效数据 buffer.get(imageData, rowOffset, imageWidth) } else { // 兼容特殊设备,逐像素读取跳过间隔 for (col in 0 until imageWidth) { imageData[rowOffset + col] = buffer.get() buffer.position(buffer.position() + pixelStride - 1) } } } // 以下是你原来的裁剪计算逻辑,保留即可 val iFact = if (mActivity.getOverlayView().width <= mActivity.getOverlayView().height) { imageWidth / mActivity.getOverlayView().width.toDouble() } else { imageHeight / mActivity.getOverlayView().height.toDouble() } val size = mActivity.getOverlayView().size * iFact // 增加边界校验,避免裁剪区域超出图像范围 val cropLeft = ((imageWidth - size) / 2).toInt().coerceAtLeast(0) val cropTop = ((imageHeight - size) / 2).toInt().coerceAtLeast(0) val cropSize = size.toInt().coerceAtMost(minOf(imageWidth - cropLeft, imageHeight - cropTop)) val source = PlanarYUVLuminanceSource( imageData, imageWidth, imageHeight, cropLeft, cropTop, cropSize, cropSize, false ) // 剩余原有逻辑保持不变 // ... 缩略图渲染、二维码解码、FPS计算、image.close()等 }
2. 优化ImageAnalysis配置
在构建ImageAnalysis实例时增加格式和旋转配置,确保输出图像符合预期:
val imageAnalysis = ImageAnalysis.Builder() .setTargetResolution(Size(960, 960)) // 固定输出YUV420格式,避免不同设备返回其他格式 .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) // 开启自动旋转,输出图像方向自动适配屏幕方向,无需额外转换 .setOutputImageRotationEnabled(true) .build()
修改完成后即可兼容所有设备的YUV帧输出,裁剪后的图像不会再出现破碎问题。
内容的提问来源于stack exchange,提问作者Shyam Singh
相关产品推荐
相关产品推荐

