需要在使用 MLKit 和 Camera2 进行人脸检测期间捕获静止图像
Posted
技术标签:
【中文标题】需要在使用 MLKit 和 Camera2 进行人脸检测期间捕获静止图像【英文标题】:Need to capture a still image during face detection with MLKit and Camera2 【发布时间】:2020-08-08 04:07:31 【问题描述】:我正在使用 Camera2 和 MLKit 开发人脸检测功能。
在开发者指南的Performance Tips 部分中,他们说如果使用Camera2 API,则以ImageFormat.YUV_420_888
格式捕获图像,这是我的情况。
然后,在Face Detector 部分,他们建议使用尺寸至少为 480x360 像素的图像进行实时人脸识别,这也是我的情况。
好的,我们走吧!这是我的代码,运行良好
private fun initializeCamera() = lifecycleScope.launch(Dispatchers.Main)
// Open the selected camera
cameraDevice = openCamera(cameraManager, getCameraId(), cameraHandler)
val previewSize = if (isPortrait)
Size(RECOMMANDED_CAPTURE_SIZE.width, RECOMMANDED_CAPTURE_SIZE.height)
else
Size(RECOMMANDED_CAPTURE_SIZE.height, RECOMMANDED_CAPTURE_SIZE.width)
// Initialize an image reader which will be used to display a preview
imageReader = ImageReader.newInstance(
previewSize.width, previewSize.height, ImageFormat.YUV_420_888, IMAGE_BUFFER_SIZE)
// Retrieve preview's frame and run detector
imageReader.setOnImageAvailableListener( reader ->
lifecycleScope.launch(Dispatchers.Main)
val image = reader.acquireNextImage()
logD "Image available: $image.timestamp"
faceDetector.runFaceDetection(image, getRotationCompensation())
image.close()
, imageReaderHandler)
// Creates list of Surfaces where the camera will output frames
val targets = listOf(viewfinder.holder.surface, imageReader.surface)
// Start a capture session using our open camera and list of Surfaces where frames will go
session = createCaptureSession(cameraDevice, targets, cameraHandler)
val captureRequest = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW).apply
addTarget(viewfinder.holder.surface)
addTarget(imageReader.surface)
// This will keep sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
session.setRepeatingRequest(captureRequest.build(), null, cameraHandler)
现在,我想捕捉一张静止图像……这是我的问题,因为理想情况下,我想要:
全分辨率图像,或至少大于 480x360 以 JPEG 格式保存Camera2Basic sample 演示了如何捕获图像(视频和慢动作的示例正在崩溃),MLKit sample 使用了如此古老的相机 API!幸运的是,我成功地混合了这些样本来开发我的功能,但我未能捕捉到具有不同分辨率的静止图像。
我想我必须停止预览会话才能重新创建一个以进行图像捕获,但我不确定...
我所做的是以下,但它是在 480x360 中捕获图像:
session.stopRepeating()
// Unset the image reader listener
imageReader.setOnImageAvailableListener(null, null)
// Initialize an new image reader which will be used to capture still photos
// imageReader = ImageReader.newInstance(768, 1024, ImageFormat.JPEG, IMAGE_BUFFER_SIZE)
// Start a new image queue
val imageQueue = ArrayBlockingQueue<Image>(IMAGE_BUFFER_SIZE)
imageReader.setOnImageAvailableListener( reader - >
val image = reader.acquireNextImage()
logD "[Still] Image available in queue: $image.timestamp"
if (imageQueue.size >= IMAGE_BUFFER_SIZE - 1)
imageQueue.take().close()
imageQueue.add(image)
, imageReaderHandler)
// Creates list of Surfaces where the camera will output frames
val targets = listOf(viewfinder.holder.surface, imageReader.surface)
val captureRequest = createStillCaptureRequest(cameraDevice, targets)
session.capture(captureRequest, object: CameraCaptureSession.CaptureCallback()
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult)
super.onCaptureCompleted(session, request, result)
val resultTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP)
logD "Capture result received: $resultTimestamp"
// Set a timeout in case image captured is dropped from the pipeline
val exc = TimeoutException("Image dequeuing took too long")
val timeoutRunnable = Runnable
continuation.resumeWithException(exc)
imageReaderHandler.postDelayed(timeoutRunnable, IMAGE_CAPTURE_TIMEOUT_MILLIS)
// Loop in the coroutine's context until an image with matching timestamp comes
// We need to launch the coroutine context again because the callback is done in
// the handler provided to the `capture` method, not in our coroutine context
@ Suppress("BlockingMethodInNonBlockingContext")
lifecycleScope.launch(continuation.context)
while (true)
// Dequeue images while timestamps don't match
val image = imageQueue.take()
if (image.timestamp != resultTimestamp)
continue
logD "Matching image dequeued: $image.timestamp"
// Unset the image reader listener
imageReaderHandler.removeCallbacks(timeoutRunnable)
imageReader.setOnImageAvailableListener(null, null)
// Clear the queue of images, if there are left
while (imageQueue.size > 0)
imageQueue.take()
.close()
// Compute EXIF orientation metadata
val rotation = getRotationCompensation()
val mirrored = cameraFacing == CameraCharacteristics.LENS_FACING_FRONT
val exifOrientation = computeExifOrientation(rotation, mirrored)
logE "captured image size (w/h): $image.width / $image.height"
// Build the result and resume progress
continuation.resume(CombinedCaptureResult(
image, result, exifOrientation, imageReader.imageFormat))
// There is no need to break out of the loop, this coroutine will suspend
, cameraHandler)
如果我取消注释新的 ImageReader 实例,我有这个例外:
java.lang.IllegalArgumentException: CaptureRequest 包含 未配置的输入/输出表面!
谁能帮帮我?
【问题讨论】:
Camera 2
真是让人头疼。 @Martin 指出,由于图像分析器,Camera X
似乎很有希望。如果您想享受生活,请考虑使用CameraView
github.com/natario1/CameraView,您应该只需几行代码即可启动并运行。
【参考方案1】:
这个IllegalArgumentException
:
java.lang.IllegalArgumentException:CaptureRequest 包含未配置的输入/输出表面!
...显然是指imageReader.surface
。
Meanhile(使用 CameraX)这会有所不同,请参阅 CameraFragment.kt ...
问题 #197:Firebase Face Detection Api issue while using cameraX API;
可能很快就会有一个与您的用例相匹配的示例应用程序。
【讨论】:
谢谢@Martin,我会检查的。但是,出于操作原因,我不想使用 CameraX,因为PreviewView
处于 alpha 阶段。是的,异常是指 imageReader...但是为什么呢?【参考方案2】:
ImageReader 对格式的选择和/或使用标志的组合很敏感。 documentation 指出某些格式组合可能不受支持。对于某些 android 设备(可能是一些较旧的手机型号),您可能会发现 IllegalArgumentException
不是使用 JPEG 格式抛出的。但这并没有多大帮助——你想要多才多艺的东西。
我过去所做的是使用ImageFormat.YUV_420_888
格式(这将得到硬件和ImageReader 实现的支持)。此格式不包含阻止应用程序通过内部平面阵列访问图像的预优化。我注意到您已经在 initializeCamera()
方法中成功使用了它。
然后你可以从你想要的帧中提取图像数据
Image.Plane[] planes = img.getPlanes();
byte[] data = planes[0].getBuffer().array();
然后通过位图使用 JPEG 压缩、PNG 或您选择的任何编码创建静止图像。
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, width, height, null);
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
byte[] imageBytes = out.toByteArray();
Bitmap bitmap= BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 75, out2);
【讨论】:
感谢您的回答...但是如何以不同的分辨率捕获帧?当然可以捕获大于 480x360 像素的帧,但我不知道如何... 从原始位图创建缩放位图的方法不止一种。那就足够了吗?根据方法的不同,当性能是关键时,它有时会成为瓶颈。还是专门针对 ImageReader 的问题? 我不想放大 480x360 的图像,我想以另一种格式捕获更大的图像 是的,ImageReader 肯定支持大于 480x360 像素。但是由于提供的原因,您应该使用适当的格式,然后转换为您需要的输出格式。以上是关于需要在使用 MLKit 和 Camera2 进行人脸检测期间捕获静止图像的主要内容,如果未能解决你的问题,请参考以下文章
MLKit 旋转面部图像使其笔直(iOS 和 Android)
关于使用Android新版Camera即Camera2的使用介绍 暨解决Camera.PreviewCallback和MediaRecorder无法同时进行
如何使用Android中的Camera2 API在不预览的情况下拍摄多张照片?