如何从视频(mov)中获取帧样本(jpeg)?

28

我希望用Java从视频文件(mov)中获取一帧样本(jpeg)。有没有简单的方法可以做到这一点? 当我在谷歌上搜索时,我只能找到将多个jpg制作成mov的方法。 我不知道,也许我找不到正确的关键字。


1
可能我用了错误的方式来提问。我不是要在屏幕上播放视频文件,而是想将其作为文件读取和处理,并从中获取一个样本帧,然后将此样本帧保存为JPEG格式。 - Nuri Tasdemir
这里有一个类似的问题,看起来没有得到完整的答案,但可能会帮助你入手。链接 - rhashimoto
2
我使用Xoggler完成了它。我在回答部分提供了代码链接。 - Nuri Tasdemir
6个回答

71

我知道原来的问题已经解决,但是我还是想回答一下,以防其他人像我一样卡住了。

昨天起,我尝试了所有可以尝试的方法,我的意思是真的所有方法。所有可用的Java库要么过时,不再维护,要么缺乏任何有用的文档(说真的?!?!)

我尝试了JFM(古老且无用),JCodec(没有文档),JJMpeg(看起来很有前途,但由于缺乏Java类文档而难以使用),OpenCV自动Java构建和其他一些我记不清楚的库。

最后,我决定看看 JavaCVGithub 链接)的类。它包含具有详细文档的FFMPEG绑定。

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv</artifactId>
    <version>1.0</version>
</dependency>

原来从视频文件中提取视频帧并将其转换为BufferedImage甚至JPEG文件非常容易。可以使用类FFmpegFrameGrabber轻松地抓取单个帧并将其转换为BufferedImage。 以下是代码示例:

FFmpegFrameGrabber g = new FFmpegFrameGrabber("textures/video/anim.mp4");
g.start();

Java2DFrameConverter converter = new Java2DFrameConverter();

for (int i = 0 ; i < 50 ; i++) {
    
    Frame frame = g.grabImage(); // It is important to use grabImage() to get a frame that can be turned into a BufferedImage

    BufferedImage bi = converter.convert(frame);

    ImageIO.write(bi, "png", new File("frame-dump/video-frame-" + System.currentTimeMillis() + ".png"));
}

g.stop();

这段代码基本上会把视频的前50帧提取出来,并将它们保存为PNG文件。好处是内部的寻找函数,能够在实际帧而非关键帧上运行(这是我在使用JCodec时遇到的问题)。

你可以参考JavaCV主页,了解其他可用于从WebCams等设备中捕获帧的类。希望这个回答有所帮助 :-)


@oberger 我相信库中有一种方法可以获取文件中帧的总数。我现在脑海中记不起来了。 - Maghoumi
5
截至2015年6月的稳定版本0.11中,grab()现在返回一个名为Frame的对象,该对象没有getBufferedImage()方法。这里还有一个已知缺陷。如果您需要FFmpegFrameGrabber,暂时请使用版本0.10 - Aprel
@Aprel 当前版本包括一个新的FrameConverter类,它允许从Frame转换为BufferedImage或其他图像格式。 - RealSkeptic
8
请使用new org.bytedeco.javacv.Java2DFrameConverter创建对象,并调用其中的getBufferedImage方法。 - 0__
1
在当前版本下:Java2DFrameConverter c = new Java2DFrameConverter; c.convert(g.grab());我还发现两件特别有用的事情:1. 如果您喜欢获取几个代表帧而不是每一帧,可以使用g.grabKeyFrame();而不是g.grab();。 2. @oberger如果您已经到达视频的结尾,grab();grabKeyFrame();将返回null。只需使用一个if语句并在返回null时中断循环即可。(确保将grab()的结果保存在变量中,否则您将跳过一帧。) - Alexander Jank
显示剩余6条评论

6
Xuggler可以胜任这项工作。他们甚至提供了一个示例代码,完全满足我的需求。链接如下。

http://xuggle.googlecode.com/svn/trunk/java/xuggle-xuggler/src/com/xuggle/mediatool/demos/DecodeAndCaptureFrames.java

我已经修改了这个链接中的代码,使其仅保存视频的第一帧。

import javax.imageio.ImageIO;

import java.io.File;

import java.awt.image.BufferedImage;

import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.MediaListenerAdapter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.mediatool.event.IVideoPictureEvent;
import com.xuggle.xuggler.Global;

/**
 *  * @author aclarke
 *    @author trebor
 */

public class DecodeAndCaptureFrames extends MediaListenerAdapter
{
  private int mVideoStreamIndex = -1;
  private boolean gotFirst = false;
  private String saveFile;
  private Exception e;
  /** Construct a DecodeAndCaptureFrames which reads and captures
   * frames from a video file.
   * 
   * @param filename the name of the media file to read
   */

  public DecodeAndCaptureFrames(String videoFile, String saveFile)throws Exception
  {
    // create a media reader for processing video
    this.saveFile = saveFile;
    this.e = null;
     IMediaReader reader = ToolFactory.makeReader(videoFile);

    // stipulate that we want BufferedImages created in BGR 24bit color space
    reader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);


    // note that DecodeAndCaptureFrames is derived from
    // MediaReader.ListenerAdapter and thus may be added as a listener
    // to the MediaReader. DecodeAndCaptureFrames implements
    // onVideoPicture().

    reader.addListener(this);

    // read out the contents of the media file, note that nothing else
    // happens here.  action happens in the onVideoPicture() method
    // which is called when complete video pictures are extracted from
    // the media source

      while (reader.readPacket() == null && !gotFirst);

      if (e != null)
          throw e;
  }



  /** 
   * Called after a video frame has been decoded from a media stream.
   * Optionally a BufferedImage version of the frame may be passed
   * if the calling {@link IMediaReader} instance was configured to
   * create BufferedImages.
   * 
   * This method blocks, so return quickly.
   */

  public void onVideoPicture(IVideoPictureEvent event)
  {
    try
    {
      // if the stream index does not match the selected stream index,
      // then have a closer look

      if (event.getStreamIndex() != mVideoStreamIndex)
      {
        // if the selected video stream id is not yet set, go ahead an
        // select this lucky video stream

        if (-1 == mVideoStreamIndex)
          mVideoStreamIndex = event.getStreamIndex();

        // otherwise return, no need to show frames from this video stream

        else
          return;
      }

      ImageIO.write(event.getImage(), "jpg", new File(saveFile));
      gotFirst = true;

    }
    catch (Exception e)
    {
      this.e = e;
    }
  }
}

我们只使用 mov 格式的文件(iPhone 录制)。我没有尝试过用 avi 格式。不幸的是,我没有方便的方法去尝试。 - Nuri Tasdemir
如果我需要从 AVI 视频中获取单个帧(以便创建该视频的缩略图),有什么建议可以提供吗?(始终在Java中) - user2556079
1
我无法帮助您处理那个问题。现在我正在处理完全不同的事情。我的建议是检查 Stack Overflow,如果没有这样的问题,则将其作为新问题提出。 - Nuri Tasdemir
4
这不再是正确的解决方案。该库已不再受支持。 - Aleksandr Movsesyan

2
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.bytedeco.javacpp.opencv_core.IplImage;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FrameGrabber.Exception;

public class Read{
    public static void main(String []args) throws IOException, Exception
    {
        FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber("C:/Users/Digilog/Downloads/Test.mp4");
        frameGrabber.start();
        IplImage i;
        try {

            i = frameGrabber.grab();
            BufferedImage  bi = i.getBufferedImage();
            ImageIO.write(bi,"png", new File("D:/Img.png"));
            frameGrabber.stop();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }
}

此程序将生成您的视频的第一帧。我已经使用mp4格式作为示例。 - kousik Mridha
2
谢谢,但是包含哪些第三方库对此会很有帮助。 - Smig

2

Here's how with BoofCV:

String fileName = UtilIO.pathExample("tracking/chipmunk.mjpeg");

MediaManager media = DefaultMediaManager.INSTANCE;

ConfigBackgroundBasic configBasic = new ConfigBackgroundBasic(30, 0.005f);
ImageType imageType = ImageType.single(GrayF32.class);
BackgroundModelMoving background = FactoryBackgroundModel.movingBasic(configBasic, new PointTransformHomography_F32(), imageType);

SimpleImageSequence video = media.openVideo(fileName, background.getImageType());

ImageBase nextFrame;
while(video.hasNext()) {
    nextFrame = video.next();

    // Now do something with it...
}

0
以下是从媒体文件请求帧的基本代码。
获取完整源代码和视频演示,请参阅:
"媒体文件处理"实例使用Marvin Framework
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();
    }
}

0

也许这会对你有所帮助:

Buffer buf = frameGrabber.grabFrame();
// Convert frame to an buffered image so it can be processed and saved
Image img = (new BufferToImage((VideoFormat) buf.getFormat()).createImage(buf));
buffImg = new BufferedImage(img.getWidth(this), img.getHeight(this), BufferedImage.TYPE_INT_RGB);
//TODO saving the buffImg

更多信息请参见:

如何从网络摄像头中获取单个快照?


我打算尝试一下。我会发布结果的。无论如何感谢。 - Nuri Tasdemir
2
不幸的是,“如何从网络摄像头获取单个快照?”中提供的链接已经失效。另一方面,我找到了这个链接,里面有一个答案:http://osdir.com/ml/java.imagej/2005-01/msg00242.html。但是它需要Quicktime for Java,而苹果不再提供此软件了:( - Nuri Tasdemir

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