逐帧读取视频的方法是什么?

7
我希望能够使用Java8-64位逐帧读取MP4文件,并将每一帧作为JPG写入硬盘。我的第一次尝试是使用JavaFX 2.2媒体播放器在View组件上播放文件。我认为可能会有一个选项可以注册观察者,以便在每次加载新帧并准备好在组件表面绘制时获得事件,但似乎没有这样的方法。只需要抓取那些被绘制在组件上的帧/像素即可。
是否可以通过使用媒体播放器来完成这个任务?我之所以使用媒体播放器是因为它是我找到的最简单的解决方案。我尝试了VLCJ、只有32位和GStreamer,但都没有成功:(
目前为止,我已经做到了:
public class VideoGrabber extends extends JFrame { 

// code for scene setup omitted

final MediaView view = createMediaView(...)

// some other stuff happens here

// now start the video
view.getMediaPlayer().seek(Duration.ZERO);
view.getMediaPlayer().play();
view.getMediaPlayer().setOnEndOfMedia(new Runnable()
{ // save image when done
  BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_BGR 
  view.paint(img.getGraphics());
  ImageIO.write(img, "JPEG", new File("pic-"+System.currentTimeMillis()+".jpg"));
});

// somewhere else to create 
private MediaView createMediaView(String url)
{
  final Media clip = new Media(url);
  final MediaPlayer player = new MediaPlayer(clip);
  final MediaView view = new MediaView(player);
  view.setFitWidth(VID_WIDTH);
  view.setFitHeight(VID_HEIGHT);
  return view;
}

有没有一种方法可以做到以下内容:
player.setOnNextFrameReady(final Event evt) { writeImage(evt.getFrame()) };

谢谢!

3个回答

9
您可以使用MediaView的snapshot()方法。首先将mediaPlayer连接到MediaView组件,然后使用mediaPlayer.seek()来寻找视频位置。然后,您可以使用以下代码提取图像帧:
    int width = mediaPlayer.getMedia().getWidth();
    int height = mediaPlayer.getMedia().getHeight();
    WritableImage wim = new WritableImage(width, height);
    MediaView mv = new MediaView();
    mv.setFitWidth(width);
    mv.setFitHeight(height);
    mv.setMediaPlayer(mediaPlayer);
    mv.snapshot(null, wim);
    try {
        ImageIO.write(SwingFXUtils.fromFXImage(wim, null), "png", new File("/test.png"));
    } catch (Exception s) {
        System.out.println(s);
    }

非常被低估的答案。谢谢。 - Smig

5

Marvin Framework 提供了逐帧处理媒体文件的方法。下面是一个简单的视频处理示例,突出显示了斯诺克球的路径。

enter image description here

左边的视频以30毫秒的间隔逐帧播放。右边的视频也是逐帧播放,但通过简单的图像处理方法保留了白球的所有位置。

源代码可以在这里查看,视频可以在这里查看。

以下是使用Marvin请求媒体文件帧的基本源代码:

public class MediaFileExample implements Runnable{

    private MarvinVideoInterface    videoAdapter;
    private MarvinImage             videoFrame;

    public MediaFileExample(){
        try{
            // Create the VideoAdapter used to load the video file
            videoAdapter = new MarvinJavaCVAdapter();
            videoAdapter.loadResource("./res/snooker.wmv");

            // Start the thread for requesting the video frames 
            new Thread(this).start();
        }
        catch(MarvinVideoInterfaceException e){e.printStackTrace();}
    }

    @Override
    public void run() {
        try{
            while(true){
                // Request a video frame
                videoFrame = videoAdapter.getFrame();
            }
        }catch(MarvinVideoInterfaceException e){e.printStackTrace();}
    }

    public static void main(String[] args) {
        MediaFileExample m = new MediaFileExample();
    }
}

非常好,我会尝试一下。 - Chris
链接已经失效了,这个项目是不是停止了,还是只是网站挂了? - user637338
从在SourceForge上的搜索结果来看,该项目已经停止更新了。https://sourceforge.net/directory/os:mac/?q=marvin 最后一次更新是在2016年。 - user637338
该项目并未停止。网站由于Sourceforge维护而宕机。代码库已迁移到Github。您可以在网站上找到所有这些信息。 - Gabriel Archanjo
我认为它现在已经死了或者太老了,我无法使用它。 - Soheil Rahsaz

1

ffmpeg这样的框架带有命令行工具,可以将视频拆分为单个帧(即文件夹中的图像)。

请参阅此问题:使用已知帧速率将AVI拆分为帧

回答中包含了封装ffmpeg的Java框架的链接。


你可以尝试使用 https://code.google.com/p/jjmpeg/。我不建议使用命令行逐帧提取到管道中,因为在视频流中进行查找会非常耗费资源。 - Aaron Digulla
再次感谢。不幸的是,使用jjmpeg意味着我必须花更多时间进行逆向工程以了解如何使用它(没有可用的文档),而不是花时间执行实际任务。也许会有其他答案出现,提供有趣的解决方案。但还是谢谢! - Chris
项目的源代码中有一个示例。请查看我的编辑。 - Aaron Digulla
jjmpeg的链接已经失效 :/ - bspkrs
@bspkrs 谢谢,已修复。 - Aaron Digulla
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接