Google 移动视觉无法使用 Yuv 数据?
Google mobile vision not working with Yuv data?
当我使用 setBitmap 给 Frame.builder() 一个位图时,一切都按预期工作。面部被检测到,微笑概率也起作用。但是,出于某种原因,带有 NV21 格式 YUV 图像的 ByteBuffer 的 .setImageData 不起作用。它不会抛出任何错误,但 YuvImage 数据未检测到人脸。
i420Frame 来自这里:https://media.twiliocdn.com/sdk/android/conversations/releases/0.8.1/docs/com/twilio/conversations/I420Frame.html
(我假设它与 Webrtc 的 i420Frame 非常相似,如果不完全相同的话)
这里是主要代码:
@Override
public void renderFrame(final I420Frame i420Frame) {
YuvImage yuvImage = i420ToYuvImage(i420Frame.yuvPlanes, i420Frame.yuvStrides, i420Frame.width, i420Frame.height);
// Set image data (YUV N21 format) -- NOT working. The commented bitmap line works.
Frame frame = new Frame.Builder().setImageData(ByteBuffer.wrap(yuvImage.getYuvData()), yuvImage.getWidth(), yuvImage.getHeight(), yuvImage.getYuvFormat()).build();
//Frame frame = new Frame.Builder().setBitmap(yuvImage).build();
// Detect faces
SparseArray<Face> faces = detector.detect(frame);
if (!detector.isOperational()) {
Log.e(TAG, "Detector is not operational!");
}
if (faces.size() > 0) {
Log.i("yuv", "Smiling %: " + faces.valueAt(0).getIsSmilingProbability());
}
i420Frame.release();
Log.i("yuv", "Faces detected: " + faces.size());
}
以下是用于 i420ToYuvImage 的函数(摘自 Twilio 的快速入门指南)。 i420 到 YuvImage 的代码不是我写的,但我相信它能正常工作,因为我把输出的 yuvimage 转换成 jpeg,再转换成位图,google 移动视觉库能够检测到位图中的面孔。但是进行所有这些转换会产生大量开销。所以我正在尝试使用 YuvImage 像上面那样直接输入移动视觉库。
private YuvImage i420ToYuvImage(ByteBuffer[] yuvPlanes, int[] yuvStrides, int width, int height) {
if (yuvStrides[0] != width) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
if (yuvStrides[1] != width / 2) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
if (yuvStrides[2] != width / 2) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
byte[] bytes = new byte[yuvStrides[0] * height +
yuvStrides[1] * height / 2 +
yuvStrides[2] * height / 2];
ByteBuffer tmp = ByteBuffer.wrap(bytes, 0, width * height);
copyPlane(yuvPlanes[0], tmp);
byte[] tmpBytes = new byte[width / 2 * height / 2];
tmp = ByteBuffer.wrap(tmpBytes, 0, width / 2 * height / 2);
copyPlane(yuvPlanes[2], tmp);
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2 ; col++) {
bytes[width * height + row * width + col * 2]
= tmpBytes[row * width / 2 + col];
}
}
copyPlane(yuvPlanes[1], tmp);
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2 ; col++) {
bytes[width * height + row * width + col * 2 + 1] =
tmpBytes[row * width / 2 + col];
}
}
return new YuvImage(bytes, NV21, width, height, null);
}
private YuvImage fastI420ToYuvImage(ByteBuffer[] yuvPlanes,
int[] yuvStrides,
int width,
int height) {
byte[] bytes = new byte[width * height * 3 / 2];
int i = 0;
for (int row = 0 ; row < height ; row++) {
for (int col = 0 ; col < width ; col++) {
bytes[i++] = yuvPlanes[0].get(col + row * yuvStrides[0]);
}
}
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2; col++) {
bytes[i++] = yuvPlanes[2].get(col + row * yuvStrides[2]);
bytes[i++] = yuvPlanes[1].get(col + row * yuvStrides[1]);
}
}
return new YuvImage(bytes, NV21, width, height, null);
}
private void copyPlane(ByteBuffer src, ByteBuffer dst) {
src.position(0).limit(src.capacity());
dst.put(src);
dst.position(0).limit(dst.capacity());
}
事实证明,Twilio 发送给 renderFrame() 的 i420frame 实际上旋转了 270 度。所以调用 .setRotation 解决了这个问题。我之前确实尝试过,但我调用的是 .setRotation(270),这对我来说很直观,但在检查文档后你必须执行 .setRotation(Frame.ROTATION_270) 或类似的操作。现在一切正常。这是完整的工作线:
Frame frame = new Frame.Builder().setImageData(ByteBuffer.wrap(yuvImage.getYuvData()), yuvImage.getWidth(), yuvImage.getHeight(), yuvImage.getYuvFormat()).setRotation(Frame.ROTATION_270).build();
当我使用 setBitmap 给 Frame.builder() 一个位图时,一切都按预期工作。面部被检测到,微笑概率也起作用。但是,出于某种原因,带有 NV21 格式 YUV 图像的 ByteBuffer 的 .setImageData 不起作用。它不会抛出任何错误,但 YuvImage 数据未检测到人脸。
i420Frame 来自这里:https://media.twiliocdn.com/sdk/android/conversations/releases/0.8.1/docs/com/twilio/conversations/I420Frame.html
(我假设它与 Webrtc 的 i420Frame 非常相似,如果不完全相同的话)
这里是主要代码:
@Override
public void renderFrame(final I420Frame i420Frame) {
YuvImage yuvImage = i420ToYuvImage(i420Frame.yuvPlanes, i420Frame.yuvStrides, i420Frame.width, i420Frame.height);
// Set image data (YUV N21 format) -- NOT working. The commented bitmap line works.
Frame frame = new Frame.Builder().setImageData(ByteBuffer.wrap(yuvImage.getYuvData()), yuvImage.getWidth(), yuvImage.getHeight(), yuvImage.getYuvFormat()).build();
//Frame frame = new Frame.Builder().setBitmap(yuvImage).build();
// Detect faces
SparseArray<Face> faces = detector.detect(frame);
if (!detector.isOperational()) {
Log.e(TAG, "Detector is not operational!");
}
if (faces.size() > 0) {
Log.i("yuv", "Smiling %: " + faces.valueAt(0).getIsSmilingProbability());
}
i420Frame.release();
Log.i("yuv", "Faces detected: " + faces.size());
}
以下是用于 i420ToYuvImage 的函数(摘自 Twilio 的快速入门指南)。 i420 到 YuvImage 的代码不是我写的,但我相信它能正常工作,因为我把输出的 yuvimage 转换成 jpeg,再转换成位图,google 移动视觉库能够检测到位图中的面孔。但是进行所有这些转换会产生大量开销。所以我正在尝试使用 YuvImage 像上面那样直接输入移动视觉库。
private YuvImage i420ToYuvImage(ByteBuffer[] yuvPlanes, int[] yuvStrides, int width, int height) {
if (yuvStrides[0] != width) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
if (yuvStrides[1] != width / 2) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
if (yuvStrides[2] != width / 2) {
return fastI420ToYuvImage(yuvPlanes, yuvStrides, width, height);
}
byte[] bytes = new byte[yuvStrides[0] * height +
yuvStrides[1] * height / 2 +
yuvStrides[2] * height / 2];
ByteBuffer tmp = ByteBuffer.wrap(bytes, 0, width * height);
copyPlane(yuvPlanes[0], tmp);
byte[] tmpBytes = new byte[width / 2 * height / 2];
tmp = ByteBuffer.wrap(tmpBytes, 0, width / 2 * height / 2);
copyPlane(yuvPlanes[2], tmp);
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2 ; col++) {
bytes[width * height + row * width + col * 2]
= tmpBytes[row * width / 2 + col];
}
}
copyPlane(yuvPlanes[1], tmp);
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2 ; col++) {
bytes[width * height + row * width + col * 2 + 1] =
tmpBytes[row * width / 2 + col];
}
}
return new YuvImage(bytes, NV21, width, height, null);
}
private YuvImage fastI420ToYuvImage(ByteBuffer[] yuvPlanes,
int[] yuvStrides,
int width,
int height) {
byte[] bytes = new byte[width * height * 3 / 2];
int i = 0;
for (int row = 0 ; row < height ; row++) {
for (int col = 0 ; col < width ; col++) {
bytes[i++] = yuvPlanes[0].get(col + row * yuvStrides[0]);
}
}
for (int row = 0 ; row < height / 2 ; row++) {
for (int col = 0 ; col < width / 2; col++) {
bytes[i++] = yuvPlanes[2].get(col + row * yuvStrides[2]);
bytes[i++] = yuvPlanes[1].get(col + row * yuvStrides[1]);
}
}
return new YuvImage(bytes, NV21, width, height, null);
}
private void copyPlane(ByteBuffer src, ByteBuffer dst) {
src.position(0).limit(src.capacity());
dst.put(src);
dst.position(0).limit(dst.capacity());
}
事实证明,Twilio 发送给 renderFrame() 的 i420frame 实际上旋转了 270 度。所以调用 .setRotation 解决了这个问题。我之前确实尝试过,但我调用的是 .setRotation(270),这对我来说很直观,但在检查文档后你必须执行 .setRotation(Frame.ROTATION_270) 或类似的操作。现在一切正常。这是完整的工作线:
Frame frame = new Frame.Builder().setImageData(ByteBuffer.wrap(yuvImage.getYuvData()), yuvImage.getWidth(), yuvImage.getHeight(), yuvImage.getYuvFormat()).setRotation(Frame.ROTATION_270).build();