为什么 analyze() 方法没有获取图像?
Why are images not being acquired by the analyze() method?
Summary/TL;DR:ML Object 检测应用程序无法检测到 object,因为图像未被 analyze()
方法。
背景
我目前正在使用 CameraX 和 Google 用 Java 编写的 ML 套件开发移动应用程序。该应用程序的目的是通过实时相机预览检测 objects。我使用标题为 "Detect and track objects with ML Kit on Android"(基本模型选项)的指南实现了 ML Kit,以检测应用程序中连续帧中的 objects。
但是,在 运行 应用程序启动后,它会在我的设备上启动并且相机预览继续工作,但应用程序不会执行实际检测 objects 并显示它的预期效果在我的屏幕上。为了尝试解决此问题,我发现 this Whosebug answer 与此问题非常相似。令我沮丧的是,用户使用自定义模型 (tflite) 构建了他们的应用程序。这与我的不同,因为我使用的是基本模型。根据我的研究,这使用了 ML Kit 的 on-device 的 object 检测。所应用的代码仅限于上述文档中的内容。由于我的 IDE (Android Studio) 没有在语法中显示任何错误,我不确定为什么我的应用程序中似乎没有任何 object 检测。下面显示的是已经使用过的必要代码:
代码
public class MainActivity extends AppCompatActivity {
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private class YourAnalyzer implements ImageAnalysis.Analyzer {
@Override
@ExperimentalGetImage
public void analyze(ImageProxy imageProxy) {
Image mediaImage = imageProxy.getImage();
if (mediaImage != null) {
InputImage image =
InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
//Pass image to an ML Kit Vision API
//...
ObjectDetectorOptions options =
new ObjectDetectorOptions.Builder()
.setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
.enableClassification() // Optional
.build();
ObjectDetector objectDetector = ObjectDetection.getClient(options);
objectDetector.process(image)
.addOnSuccessListener(
new OnSuccessListener<List<DetectedObject>>() {
@Override
public void onSuccess(List<DetectedObject> detectedObjects) {
Log.d("TAG", "onSuccess" + detectedObjects.size());
for (DetectedObject detectedObject : detectedObjects) {
Rect boundingBox = detectedObject.getBoundingBox();
Integer trackingId = detectedObject.getTrackingId();
for (DetectedObject.Label label : detectedObject.getLabels()) {
String text = label.getText();
if (PredefinedCategory.FOOD.equals(text)) { }
int index = label.getIndex();
if (PredefinedCategory.FOOD_INDEX == index) { }
float confidence = label.getConfidence();
}
}
imageProxy.close();
}
}
)
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("TAG", "onFailure" + e);
imageProxy.close();
}
}
);
}
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
PreviewView previewView = findViewById(R.id.previewView);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {}
}, ContextCompat.getMainExecutor(this));
}
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
PreviewView previewView = findViewById(R.id.previewView);
Preview preview = new Preview.Builder()
.build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(1280,720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new YourAnalyzer());
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview, imageAnalysis);
}
}
结束OBJECTIVE
如果需要任何类型的视觉示例来理解预期效果应该产生什么,请参见下图。
更新 [2021 年 4 月 11 日]: 在我尝试通过 Log.d(..)
ing OnSuccess
方法进行调试以确定 return object 列表大小,AS 控制台在 运行 应用程序的几秒钟内打印了 D/TAG: onSuccess0
多达 30 次。这是否意味着应用程序未检测到任何 object?自从我完全按照文档进行操作以来,这一直困扰着我。
更新 [2021 年 5 月 1 日]: DetectedObject[] results = new DetectedObject[0];
行已从 onSuccess
方法中删除。
for (DetectedObject detectedObject : results)
现在使用“检测到的Objects”而不是“结果”来反映文档中存在的代码。然而,onSuccess
仍在记录 D/TAG: onSuccess0
,这进一步增加了关于为什么该方法没有获取任何数据的问题。
如您链接的the other Whosebug question中所述,您需要绑定分析用例才能使其正常工作。
帮助调试的一个技巧是,您可以在 onSuccess 中添加一些 Log.d(..) 以检查返回的对象列表的大小,并在 onFailure 中添加一些以打印异常。然后当 运行 时,您可以使用 adb logcat 或 AS 日志选项卡检查信息以确保内容是 运行.
您可以进行的另一项改进是,您不需要在每一帧都创建一个新的对象检测器。您可以在分析方法之外创建一个并重新使用它。
根据@Steven 提供的this minimalized version of the Google ML Kit sample app,我能够通过实现 lambda 表达式并像这样最小化代码来解决这个问题;
objectDetector.process(image)
.addOnSuccessListener(detectedObjects -> {
Log.d("TAG", "onSuccess" + detectedObjects.size());
})
.addOnFailureListener(e -> Log.e("TAG", e.getLocalizedMessage()))
.addOnCompleteListener(result -> imageProxy.close());
在 运行 程序进行此更改后,应用程序成功启动并且我的 logcat 打印出 D/TAG: onSuccess1
表示确实检测到对象!
但是,我确实想补充一点,编写代码时如此细微的差异让我想知道究竟是什么导致了差异。如果有人能弄清楚为什么这段代码有效,而不是我在 OP 中发布的代码,我将不胜感激。
Summary/TL;DR:ML Object 检测应用程序无法检测到 object,因为图像未被 analyze()
方法。
背景
我目前正在使用 CameraX 和 Google 用 Java 编写的 ML 套件开发移动应用程序。该应用程序的目的是通过实时相机预览检测 objects。我使用标题为 "Detect and track objects with ML Kit on Android"(基本模型选项)的指南实现了 ML Kit,以检测应用程序中连续帧中的 objects。
但是,在 运行 应用程序启动后,它会在我的设备上启动并且相机预览继续工作,但应用程序不会执行实际检测 objects 并显示它的预期效果在我的屏幕上。为了尝试解决此问题,我发现 this Whosebug answer 与此问题非常相似。令我沮丧的是,用户使用自定义模型 (tflite) 构建了他们的应用程序。这与我的不同,因为我使用的是基本模型。根据我的研究,这使用了 ML Kit 的 on-device 的 object 检测。所应用的代码仅限于上述文档中的内容。由于我的 IDE (Android Studio) 没有在语法中显示任何错误,我不确定为什么我的应用程序中似乎没有任何 object 检测。下面显示的是已经使用过的必要代码:
代码
public class MainActivity extends AppCompatActivity {
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private class YourAnalyzer implements ImageAnalysis.Analyzer {
@Override
@ExperimentalGetImage
public void analyze(ImageProxy imageProxy) {
Image mediaImage = imageProxy.getImage();
if (mediaImage != null) {
InputImage image =
InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
//Pass image to an ML Kit Vision API
//...
ObjectDetectorOptions options =
new ObjectDetectorOptions.Builder()
.setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
.enableClassification() // Optional
.build();
ObjectDetector objectDetector = ObjectDetection.getClient(options);
objectDetector.process(image)
.addOnSuccessListener(
new OnSuccessListener<List<DetectedObject>>() {
@Override
public void onSuccess(List<DetectedObject> detectedObjects) {
Log.d("TAG", "onSuccess" + detectedObjects.size());
for (DetectedObject detectedObject : detectedObjects) {
Rect boundingBox = detectedObject.getBoundingBox();
Integer trackingId = detectedObject.getTrackingId();
for (DetectedObject.Label label : detectedObject.getLabels()) {
String text = label.getText();
if (PredefinedCategory.FOOD.equals(text)) { }
int index = label.getIndex();
if (PredefinedCategory.FOOD_INDEX == index) { }
float confidence = label.getConfidence();
}
}
imageProxy.close();
}
}
)
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.d("TAG", "onFailure" + e);
imageProxy.close();
}
}
);
}
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
PreviewView previewView = findViewById(R.id.previewView);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {}
}, ContextCompat.getMainExecutor(this));
}
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
PreviewView previewView = findViewById(R.id.previewView);
Preview preview = new Preview.Builder()
.build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(1280,720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new YourAnalyzer());
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview, imageAnalysis);
}
}
结束OBJECTIVE
如果需要任何类型的视觉示例来理解预期效果应该产生什么,请参见下图。
更新 [2021 年 4 月 11 日]: 在我尝试通过 Log.d(..)
ing OnSuccess
方法进行调试以确定 return object 列表大小,AS 控制台在 运行 应用程序的几秒钟内打印了 D/TAG: onSuccess0
多达 30 次。这是否意味着应用程序未检测到任何 object?自从我完全按照文档进行操作以来,这一直困扰着我。
更新 [2021 年 5 月 1 日]: DetectedObject[] results = new DetectedObject[0];
行已从 onSuccess
方法中删除。
for (DetectedObject detectedObject : results)
现在使用“检测到的Objects”而不是“结果”来反映文档中存在的代码。然而,onSuccess
仍在记录 D/TAG: onSuccess0
,这进一步增加了关于为什么该方法没有获取任何数据的问题。
如您链接的the other Whosebug question中所述,您需要绑定分析用例才能使其正常工作。
帮助调试的一个技巧是,您可以在 onSuccess 中添加一些 Log.d(..) 以检查返回的对象列表的大小,并在 onFailure 中添加一些以打印异常。然后当 运行 时,您可以使用 adb logcat 或 AS 日志选项卡检查信息以确保内容是 运行.
您可以进行的另一项改进是,您不需要在每一帧都创建一个新的对象检测器。您可以在分析方法之外创建一个并重新使用它。
根据@Steven 提供的this minimalized version of the Google ML Kit sample app,我能够通过实现 lambda 表达式并像这样最小化代码来解决这个问题;
objectDetector.process(image)
.addOnSuccessListener(detectedObjects -> {
Log.d("TAG", "onSuccess" + detectedObjects.size());
})
.addOnFailureListener(e -> Log.e("TAG", e.getLocalizedMessage()))
.addOnCompleteListener(result -> imageProxy.close());
在 运行 程序进行此更改后,应用程序成功启动并且我的 logcat 打印出 D/TAG: onSuccess1
表示确实检测到对象!
但是,我确实想补充一点,编写代码时如此细微的差异让我想知道究竟是什么导致了差异。如果有人能弄清楚为什么这段代码有效,而不是我在 OP 中发布的代码,我将不胜感激。