Android: Run image classifier in infinite loop

泪湿孤枕 提交于 2019-12-11 15:40:41

问题


I want to run a image classifier in a infinite loop on a background thread. The function should be called immediately after launching the app. I want to feed the classifier with current frames from a prerecorded video which is simultaneously playing in the UI-thread, so the background thread should tell the UI-thread, once it's done, so I can feed it with the current frame and rerun the classifier.

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:
I got some rough idea on how to do it from these videos. It seems like I need a backgroundThread (HandlerThread) that has a backgroundHandler (Handler) to communicate with the UI-thread and a Looper to keep the background thread alive. setImageViewToCurrentFrame uses videoView.getCurrentPosition() to update the mBitmap.

However the update is very slow (>10 seconds) compared to the runtime of the classifier (SystemClock.sleep(100) which takes 100ms).

EDIT2: The problem seems to be the performance of ImageView which seems to be updated very slowly. Replacing it with TextView, keeps both the background thread and the UI-thread in sync. I will look for other solutions than ImageView now


回答1:


Here is the gist of my solution. One needs to fit in the actual image classifier at the SystemClock.sleep part. The trick is to use TextureView instead of ImageView or VideoView, since it's faster and more flexible

@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();
}


来源:https://stackoverflow.com/questions/57788197/android-run-image-classifier-in-infinite-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!