android - Android:无限循环运行图像分类器
问题描述
我想在后台线程上无限循环运行图像分类器。该函数应在启动应用程序后立即调用。我想为分类器提供来自同时在 UI 线程中播放的预录制视频的当前帧,所以后台线程应该告诉 UI 线程,一旦完成,我可以用当前帧提供它并重新运行分类器。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private VideoView videoView;
private ImageView imageView;
private Uri uri_video;
private MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
private MediaController mMediaController;
private static volatile int currentPosition;
private static volatile Bitmap mBitmap;
private final Object lock = new Object();
private volatile boolean runClassifier = false;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private static final String HANDLE_THREAD_NAME = "ClassifierBackground";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
uri_video = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.kim);
mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(getApplication(), uri_video);
videoView = findViewById(R.id.videoView);
videoView.setVideoURI(uri_video);
mMediaController = new MediaController(this);
videoView.setMediaController(mMediaController);
videoView.setOnPreparedListener(MyVideoViewPreparedListener);
videoView.start();
startBackgroundThread();
}
/** Starts a background thread and its {@link Handler}. */
private void startBackgroundThread() {
backgroundThread = new HandlerThread(HANDLE_THREAD_NAME);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
synchronized (lock) {
runClassifier = true;
}
backgroundHandler.post(periodicClassify);
}
/** Stops the background thread and its {@link Handler}. */
private void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
synchronized (lock) {
runClassifier = false;
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted when stopping background thread", e);
}
}
private Runnable periodicClassify =
new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (runClassifier) {
// classifyFrame(); // This will be implemented later
Log.d(TAG, "run: Classifier is running");
SystemClock.sleep(100); // Instead I simulate the classifier via sleep
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
setImageViewToCurrentFrame();
}
});
backgroundHandler.post(periodicClassify);
}
};
private void setImageViewToCurrentFrame(){
currentPosition = videoView.getCurrentPosition(); //in millisecond
mBitmap = mediaMetadataRetriever
.getFrameAtTime(currentPosition * 1000); //unit in microsecond
imageView.setImageBitmap(mBitmap);
}
MediaPlayer.OnPreparedListener MyVideoViewPreparedListener =
new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
long duration = videoView.getDuration(); //in millisecond
Toast.makeText(MainActivity.this,
"Duration: " + duration + " (ms)",
Toast.LENGTH_LONG).show();
setImageViewToCurrentFrame();
}
};
@Override
protected void onDestroy() {
super.onDestroy();
stopBackgroundThread();
}
EDIT1:我从这些视频
中对如何做到这一点有了一些粗略的了解。似乎我需要一个具有 backgroundHandler (Handler) 与 UI 线程通信的 backgroundThread (HandlerThread) 和一个 Looper 以保持后台线程处于活动状态。用于更新.setImageViewToCurrentFrame
videoView.getCurrentPosition()
mBitmap
SystemClock.sleep(100)
然而,与分类器的运行时间(需要 100 毫秒)相比,更新非常慢(> 10 秒)。
EDIT2: 问题似乎是 ImageView 的性能似乎更新得很慢。用 TextView 替换它,使后台线程和 UI 线程保持同步。我现在会寻找除 ImageView 之外的其他解决方案
解决方案
这是我的解决方案的要点。需要在该SystemClock.sleep
部分适合实际的图像分类器。诀窍是使用TextureView
代替ImageView
or VideoView
,因为它更快更灵活
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_playback);
initializeBottomSheet();
uri_video = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.my_demo_video);
textureView = findViewById(R.id.textureView);
textureView.setSurfaceTextureListener(this);
mediaPlayer = new MediaPlayer();
assert textureView != null;
startBackgroundThread();
}
/** Starts a background thread and its {@link Handler}. */
private void startBackgroundThread() {
backgroundThread = new HandlerThread(HANDLE_THREAD_NAME);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
synchronized (lock) {
runClassifier = true;
}
backgroundHandler.post(periodicClassify);
}
/** Stops the background thread and its {@link Handler}. */
private void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
synchronized (lock) {
runClassifier = false;
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted when stopping background thread", e);
}
}
private Runnable periodicClassify =
new Runnable() {
@Override
public void run() {
// Get current frame from video playback
mBitmap = textureView.getBitmap();
if (classifier != null && mBitmap != null) {
Log.d(TAG, "Classifier: Start thread");
SystemClock.sleep(3000); // Instead I simulate the classifier via sleep
}
backgroundHandler.post(periodicClassify);
}
};
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Surface surface = new Surface(surfaceTexture);
Context context = getApplicationContext();
try {
mediaPlayer.setDataSource(context, uri_video);
mediaPlayer.setSurface(surface);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start();
textureView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mediaPlayer.isPlaying()){
mediaPlayer.pause();
}else{
mediaPlayer.start();
}
}
});
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopBackgroundThread();
if(mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
@Override
protected void onPause() {
super.onPause();
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
@Override
protected void onResume() {
super.onResume();
if(mediaPlayer != null && mediaPlayer.isPlaying()){
mediaPlayer.start();
}
startBackgroundThread();
}
推荐阅读
- jquery - 检查 Array 中的多个值并在 html 中附加第三个值
- laravel-dusk - Laravel Dusk:迁移和种子测试数据库一次
- docusignapi - 将签名者分配给特定的签名表单字段
- c# - 即使在应用程序关闭后仍附加多个消费者。活动MQ
- r - 为什么/如何一些包在无名环境中定义它们的功能?
- amazon-web-services - 如何监控/监听 aws 服务?
- html - 使用线性渐变将顶部切角移动到底部
- mongodb - AWS EC2 SSH 隧道堡垒服务器
- javascript - 循环遍历数字数组以在纯javascript中构造索引节点的对象
- jenkins - Jenkins 作业应该从 Spinnaker 管道接收部署状态(成功/错误)