为什么保存在 Android phone Redmi 9T 中的 CameraX 图像失真?
Why CameraX image distorted when saved in Android phone Redmi 9T?
我使用的是新机红米9T。
按照 this post 的基本设置,我设法启动并 运行 使用 CameraX 的应用程序。一切正常,直到我想在 setAnalyzer
函数
中将帧保存到本地
private void bindImageAnalysis(@NonNull ProcessCameraProvider cameraProvider) {
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder().setTargetResolution(new Size(720, 1280))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy imageProxy) {
@SuppressLint("UnsafeExperimentalUsageError") Image image = imageProxy.getImage();
Bitmap finalBitmap = getFinalScaledRotatedBitmap(image,270); //rotate it properly (portrait)
saveBitmap(finalBitmap, "test"); //save with a dummy name
imageProxy.close();
}
});
OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int orientation) {
textView.setText(Integer.toString(orientation));
}
};
orientationEventListener.enable();
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT).build();
preview.setSurfaceProvider(previewView.createSurfaceProvider());
cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector,
imageAnalysis, preview);
}
/*
* Rotate the image with a rotation degree
*/
private Bitmap getFinalScaledRotatedBitmap(Image imageData, int viewRotation){
Bitmap originalBitmap = toBitmap(imageData);
Matrix matrix = new Matrix();
matrix.postRotate(viewRotation);
matrix.postScale(-1.0f, 1.0f);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, originalBitmap.getWidth(), originalBitmap.getHeight(), true);
Bitmap finalRotatedBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
return finalRotatedBitmap;
}
/*
* Convert Image format to Bitmap
*/
private Bitmap toBitmap(Image image) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer yBuffer = planes[0].getBuffer();
ByteBuffer uBuffer = planes[1].getBuffer();
ByteBuffer vBuffer = planes[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21 = new byte[ySize + uSize + vSize];
//U and V are swapped
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 100, out);
byte[] imageBytes = out.toByteArray();
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}
/*
* Save Bitmap to local
*/
public void saveBitmap(Bitmap bitmap, String personName) {
String ROOT =
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "cameraX";
File myDir = new File(ROOT + File.separator + personName);
if (!myDir.mkdirs()) {
Log.e("FileUtil", "save dir fails");
}
String fileName = (new SimpleDateFormat("yyyyMMdd_HHmmss_SSS")).format(Calendar.getInstance().getTime()) + ".jpeg";
File file = new File(myDir, fileName);
if (file.exists()) {
file.delete();
}
try {
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (Exception var6) {
Log.e("fileUtil", var6.getMessage());
}
}
输出? (不分镜头前后)
事实是,它在荣耀 8x 或 realme 等其他设备上运行良好。
那么,可能会出现什么问题?
问题可能出在您的转换方法 toBitmpa()
上,它假设图像的格式是 NV21,但不幸的是,并非每个 YUV_888_420 缓冲区都是 NV21 格式。也可以是NV12、YU12或YV12格式。
官方 CameraX 文档已经提供了一种将 YUV 图像转换为 RGB 位图的方法,您应该使用它,它位于文档 this section 的底部。
For sample code that shows how to convert a Media.Image object from
YUV_420_888 format to an RGB Bitmap object, see YuvToRgbConverter.kt.
我使用的是新机红米9T。
按照 this post 的基本设置,我设法启动并 运行 使用 CameraX 的应用程序。一切正常,直到我想在 setAnalyzer
函数
private void bindImageAnalysis(@NonNull ProcessCameraProvider cameraProvider) {
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder().setTargetResolution(new Size(720, 1280))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy imageProxy) {
@SuppressLint("UnsafeExperimentalUsageError") Image image = imageProxy.getImage();
Bitmap finalBitmap = getFinalScaledRotatedBitmap(image,270); //rotate it properly (portrait)
saveBitmap(finalBitmap, "test"); //save with a dummy name
imageProxy.close();
}
});
OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int orientation) {
textView.setText(Integer.toString(orientation));
}
};
orientationEventListener.enable();
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT).build();
preview.setSurfaceProvider(previewView.createSurfaceProvider());
cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector,
imageAnalysis, preview);
}
/*
* Rotate the image with a rotation degree
*/
private Bitmap getFinalScaledRotatedBitmap(Image imageData, int viewRotation){
Bitmap originalBitmap = toBitmap(imageData);
Matrix matrix = new Matrix();
matrix.postRotate(viewRotation);
matrix.postScale(-1.0f, 1.0f);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, originalBitmap.getWidth(), originalBitmap.getHeight(), true);
Bitmap finalRotatedBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
return finalRotatedBitmap;
}
/*
* Convert Image format to Bitmap
*/
private Bitmap toBitmap(Image image) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer yBuffer = planes[0].getBuffer();
ByteBuffer uBuffer = planes[1].getBuffer();
ByteBuffer vBuffer = planes[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21 = new byte[ySize + uSize + vSize];
//U and V are swapped
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 100, out);
byte[] imageBytes = out.toByteArray();
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}
/*
* Save Bitmap to local
*/
public void saveBitmap(Bitmap bitmap, String personName) {
String ROOT =
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "cameraX";
File myDir = new File(ROOT + File.separator + personName);
if (!myDir.mkdirs()) {
Log.e("FileUtil", "save dir fails");
}
String fileName = (new SimpleDateFormat("yyyyMMdd_HHmmss_SSS")).format(Calendar.getInstance().getTime()) + ".jpeg";
File file = new File(myDir, fileName);
if (file.exists()) {
file.delete();
}
try {
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (Exception var6) {
Log.e("fileUtil", var6.getMessage());
}
}
输出? (不分镜头前后)
事实是,它在荣耀 8x 或 realme 等其他设备上运行良好。 那么,可能会出现什么问题?
问题可能出在您的转换方法 toBitmpa()
上,它假设图像的格式是 NV21,但不幸的是,并非每个 YUV_888_420 缓冲区都是 NV21 格式。也可以是NV12、YU12或YV12格式。
官方 CameraX 文档已经提供了一种将 YUV 图像转换为 RGB 位图的方法,您应该使用它,它位于文档 this section 的底部。
For sample code that shows how to convert a Media.Image object from YUV_420_888 format to an RGB Bitmap object, see YuvToRgbConverter.kt.