如何使用 ARCore 使用相机拍照
Posted
技术标签:
【中文标题】如何使用 ARCore 使用相机拍照【英文标题】:How to take picture with camera using ARCore 【发布时间】:2018-06-19 20:45:24 【问题描述】:ARCore 相机似乎不支持 takePicture。 https://developers.google.com/ar/reference/java/com/google/ar/core/Camera
有人知道我如何使用 ARCore 拍照吗?
【问题讨论】:
【参考方案1】:获取图像缓冲区
在最新的 ARCore SDK 中,我们可以通过公共类 Frame 访问图像缓冲区。下面是让我们访问图像缓冲区的示例代码。
private void onSceneUpdate(FrameTime frameTime)
try
Frame currentFrame = sceneView.getArFrame();
Image currentImage = currentFrame.acquireCameraImage();
int imageFormat = currentImage.getFormat();
if (imageFormat == ImageFormat.YUV_420_888)
Log.d("ImageFormat", "Image format is YUV_420_888");
如果您将其注册到setOnUpdateListener() 回调,则每次更新都会调用onSceneUpdate()
。图像将采用 YUV_420_888 格式,但将具有原生高分辨率相机的全视野。
也不要忘记通过调用currentImage.close()
关闭接收到的图像资源。否则,您将在下一次运行onSceneUpdate
时收到ResourceExhaustedException
。
将获取的图像缓冲区写入文件 以下实现将 YUV 缓冲区转换为压缩的 JPEG 字节数组
private static byte[] NV21toJPEG(byte[] nv21, int width, int height)
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
return out.toByteArray();
public static void WriteImageInformation(Image image, String path)
byte[] data = null;
data = NV21toJPEG(YUV_420_888toNV21(image),
image.getWidth(), image.getHeight());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
bos.write(data);
bos.flush();
bos.close();
private static byte[] YUV_420_888toNV21(Image image)
byte[] nv21;
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
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);
return nv21;
【讨论】:
缺少方法/类 YUV_420_888toNV21。另外,什么时候经常调用 onSceneUpdate,我们是否应该将图像保存到字段/属性并在我们想要保存图像时调用“WriteImageInformation”? @MichaelThePotato 我已经更新了答案。何时将更新的图像缓冲区写入文件由您决定。 如何在纵向模式下使用上述代码? AcquireCameraImage 方向始终为横向 (640 x 480)。我必须先旋转图像吗?如果是,如何? @Kushagra ARCore 以横向模式处理图像。您无法在纵向模式下获取图像。获取缓冲区后可能需要手动旋转图像 感谢@nbsrujan 我设法使用此代码Image image =Objects.requireNonNull(ArCoreManager.getInstance().getArFrame()).acquireCameraImage(); if(isPortrait()) Matrix matrix = new Matrix(); matrix.postRotate(90); imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, image.getWidth(), image.getHeight(), matrix, true);
获得了纵向图像【参考方案2】:
抱歉回复晚了。您可以在ARCore中使用代码点击图片:
private String generateFilename()
String date =
new SimpleDateFormat("yyyyMMddHHmmss", java.util.Locale.getDefault()).format(new Date());
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES) + File.separator + "Sceneform/" + date + "_screenshot.jpg";
private void saveBitmapToDisk(Bitmap bitmap, String filename) throws IOException
File out = new File(filename);
if (!out.getParentFile().exists())
out.getParentFile().mkdirs();
try (FileOutputStream outputStream = new FileOutputStream(filename);
ByteArrayOutputStream outputData = new ByteArrayOutputStream())
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputData);
outputData.writeTo(outputStream);
outputStream.flush();
outputStream.close();
catch (IOException ex)
throw new IOException("Failed to save bitmap to disk", ex);
private void takePhoto()
final String filename = generateFilename();
/*ArSceneView view = fragment.getArSceneView();*/
mSurfaceView = findViewById(R.id.surfaceview);
// Create a bitmap the size of the scene view.
final Bitmap bitmap = Bitmap.createBitmap(mSurfaceView.getWidth(), mSurfaceView.getHeight(),
Bitmap.Config.ARGB_8888);
// Create a handler thread to offload the processing of the image.
final HandlerThread handlerThread = new HandlerThread("PixelCopier");
handlerThread.start();
// Make the request to copy.
PixelCopy.request(mSurfaceView, bitmap, (copyResult) ->
if (copyResult == PixelCopy.SUCCESS)
try
saveBitmapToDisk(bitmap, filename);
catch (IOException e)
Toast toast = Toast.makeText(DrawAR.this, e.toString(),
Toast.LENGTH_LONG);
toast.show();
return;
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),
"Photo saved", Snackbar.LENGTH_LONG);
snackbar.setAction("Open in Photos", v ->
File photoFile = new File(filename);
Uri photoURI = FileProvider.getUriForFile(DrawAR.this,
DrawAR.this.getPackageName() + ".ar.codelab.name.provider",
photoFile);
Intent intent = new Intent(Intent.ACTION_VIEW, photoURI);
intent.setDataAndType(photoURI, "image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
);
snackbar.show();
else
Log.d("DrawAR", "Failed to copyPixels: " + copyResult);
Toast toast = Toast.makeText(DrawAR.this,
"Failed to copyPixels: " + copyResult, Toast.LENGTH_LONG);
toast.show();
handlerThread.quitSafely();
, new Handler(handlerThread.getLooper()));
【讨论】:
更多细节可以在这里找到:codelabs.developers.google.com/codelabs/sceneform-intro/… 生成的图像怎么会在我放置对象的区域有空白?【参考方案3】:我假设您的意思是相机所见内容和 AR 对象的图片。在较高级别上,您需要获得写入外部存储以保存图片的权限,从 OpenGL 复制帧,然后将其保存为 png(例如)。具体如下:
将WRITE_EXTERNAL_STORAGE
权限添加到AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
然后更改 CameraPermissionHelper 以迭代 CAMERA 和 WRITE_EXTERNAL_STORAGE 权限以确保它们被授予
private static final String REQUIRED_PERMISSIONS[] =
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
;
/**
* Check to see we have the necessary permissions for this app.
*/
public static boolean hasCameraPermission(Activity activity)
for (String p : REQUIRED_PERMISSIONS)
if (ContextCompat.checkSelfPermission(activity, p) !=
PackageManager.PERMISSION_GRANTED)
return false;
return true;
/**
* Check to see we have the necessary permissions for this app,
* and ask for them if we don't.
*/
public static void requestCameraPermission(Activity activity)
ActivityCompat.requestPermissions(activity, REQUIRED_PERMISSIONS,
CAMERA_PERMISSION_CODE);
/**
* Check to see if we need to show the rationale for this permission.
*/
public static boolean shouldShowRequestPermissionRationale(Activity activity)
for (String p : REQUIRED_PERMISSIONS)
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, p))
return true;
return false;
接下来,向HelloARActivity
添加几个字段以跟踪框架的尺寸,并添加布尔值以指示何时保存图片。
private int mWidth;
private int mHeight;
private boolean capturePicture = false;
在onSurfaceChanged()
中设置宽高
public void onSurfaceChanged(GL10 gl, int width, int height)
mDisplayRotationHelper.onSurfaceChanged(width, height);
GLES20.glViewport(0, 0, width, height);
mWidth = width;
mHeight = height;
在onDrawFrame()
的底部,添加对捕获标志的检查。这应该在所有其他绘图发生后完成。
if (capturePicture)
capturePicture = false;
SavePicture();
然后添加按钮拍照的onClick方法,以及保存图片的实际代码:
public void onSavePicture(View view)
// Here just a set a flag so we can copy
// the image from the onDrawFrame() method.
// This is required for OpenGL so we are on the rendering thread.
this.capturePicture = true;
/**
* Call from the GLThread to save a picture of the current frame.
*/
public void SavePicture() throws IOException
int pixelData[] = new int[mWidth * mHeight];
// Read the pixels from the current GL frame.
IntBuffer buf = IntBuffer.wrap(pixelData);
buf.position(0);
GLES20.glReadPixels(0, 0, mWidth, mHeight,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
// Create a file in the Pictures/HelloAR album.
final File out = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES) + "/HelloAR", "Img" +
Long.toHexString(System.currentTimeMillis()) + ".png");
// Make sure the directory exists
if (!out.getParentFile().exists())
out.getParentFile().mkdirs();
// Convert the pixel data from RGBA to what Android wants, ARGB.
int bitmapData[] = new int[pixelData.length];
for (int i = 0; i < mHeight; i++)
for (int j = 0; j < mWidth; j++)
int p = pixelData[i * mWidth + j];
int b = (p & 0x00ff0000) >> 16;
int r = (p & 0x000000ff) << 16;
int ga = p & 0xff00ff00;
bitmapData[(mHeight - i - 1) * mWidth + j] = ga | r | b;
// Create a bitmap.
Bitmap bmp = Bitmap.createBitmap(bitmapData,
mWidth, mHeight, Bitmap.Config.ARGB_8888);
// Write it to disk.
FileOutputStream fos = new FileOutputStream(out);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
runOnUiThread(new Runnable()
@Override
public void run()
showSnackbarMessage("Wrote " + out.getName(), false);
);
最后一步是将按钮添加到activity_main.xml
布局的末尾
<Button
android:id="@+id/fboRecord_button"
android:layout_
android:layout_
android:layout_alignStart="@+id/surfaceview"
android:layout_alignTop="@+id/surfaceview"
android:onClick="onSavePicture"
android:text="Snap"
tools:ignore="OnClick"/>
【讨论】:
新代码没有 onSurfaceChanged 函数,如果另外编写,则永远不会被调用以上是关于如何使用 ARCore 使用相机拍照的主要内容,如果未能解决你的问题,请参考以下文章
如何在拍照之前使用 imagepicker 获取相机视图的大小
如何使用相机在 Android 上的后台服务中拍照? [复制]