使用 OpenCV 和 Java 从 gif 帧中获取 Mats

Posted

技术标签:

【中文标题】使用 OpenCV 和 Java 从 gif 帧中获取 Mats【英文标题】:Getting Mats from frames in a gif using OpenCV and Java 【发布时间】:2015-08-06 10:18:06 【问题描述】:

我正在尝试使用 OpenCV 从 gif 中获取帧。我找到了Convert each animated GIF frame to a separate BufferedImage 并使用了第二个建议。我稍作修改以返回一个 Mats 数组而不是 BufferedImages。

我尝试了两种方法从 gif 中获取 bufferedImages。每个都提出了不同的问题。

    根据上一个帖子的建议

    BufferedImage fImage=ir.read(i);
    

    程序调用“ArrayIndexOutOfBoundsException: 4096”

    使用上一个线程的原始代码。

    BufferedImage fImage=ir.getRawImageType(i).createBufferedImage(ir.getWidth(i),ir.getHeight(i));
    

    每一帧都是单调的颜色(虽然不是全黑),从 BufferedImage 派生的垫子是空的。

    System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
    ArrayList<Mat> frames = new ArrayList<Mat>();
    ImageReader ir = new GIFImageReader(new GIFImageReaderSpi());
    ir.setInput(ImageIO.createImageInputStream(new File("ronPaulTestImage.gif")));
    
    for(int i = 0; i < ir.getNumImages(true); i++)
        BufferedImage fImage=ir.read(i);
        //BufferedImage fImage=ir.getRawImageType(i).createBufferedImage(ir.getWidth(i), ir.getHeight(i));
    
        fImage = toBufferedImageOfType(fImage, BufferedImage.TYPE_3BYTE_BGR);
        //byte[] pixels = ((DataBufferByte) r.getRaster().getDataBuffer()).getData();
        Mat m=new Mat();
        //m.put(0,0,pixels);
        m.put(0, 0,((DataBufferByte) fImage.getRaster().getDataBuffer()).getData());
    
        if(i==40)
        //a test, writes the mat and the image at the specified frame to files, exits
            ImageIO.write(fImage,"jpg",new File("TestError.jpg"));
            Imgcodecs.imwrite("TestErrorMat.jpg",m);
            System.exit(0);
    
    

这里是gif I used

【问题讨论】:

【参考方案1】:

按照 Spektre 的建议,我找到了一个更好的 gif,它修复了单色缓冲图像。缺少可查看的 Mats 是由于我在声明 Mat 时使用了默认构造函数造成的。

工作代码

    public static ArrayList<Mat> getFrames(File gif) throws IOException
    ArrayList<Mat> frames = new ArrayList<Mat>();
    ImageReader ir = new GIFImageReader(new GIFImageReaderSpi());
    ir.setInput(ImageIO.createImageInputStream(gif));
    for(int i = 0; i < ir.getNumImages(true); i++)
        BufferedImage fImage=ir.read(i);
        fImage = toBufferedImageOfType(fImage, BufferedImage.TYPE_3BYTE_BGR);
        byte[] pixels = ((DataBufferByte) fImage.getRaster().getDataBuffer()).getData();
        Mat m=new Mat(fImage.getHeight(), fImage.getWidth(), CvType.CV_8UC3);
        m.put(0,0,pixels);
        if(i==15)//a test, writes the mat and the image at the specified frame to files, exits
            ImageIO.write(fImage,"jpg",new File("TestError.jpg"));
            Imgcodecs.imwrite("TestErrorMat.jpg",m);
            System.exit(0);
        
        frames.add(m);
        
    return frames;

【讨论】:

嘿直到现在才看到你的答案。我自己的默认构造函数(在 C++ 中和一些令人讨厌的编译器错误)中遇到了相当多的问题,这导致了这个问题:bds 2006 C hidden memory manager conflicts 解决方案可能也会让您感兴趣(即使您使用的是 JAVA)【参考方案2】:

我没有使用 gif 库、Java 或 OpenCV 库,而是 ArrayIndexOutOfBoundsException: 4096

表示字典没有正确清除。你的 gif 是错误的,我对其进行了测试,它包含错误,某些帧没有足够的清晰代码。如果您的 GIF 解码器不检查/处理这种情况,那么它就会崩溃,因为它的字典增长超过 GIF 限制4096/12bit

尝试另一个 GIF,而不是一些错误的...

已经测试过你的 gif,每帧大约有 7 个清晰代码,总共包含 941 个错误(缺少清晰代码导致字典溢出)

如果您有 GIF 解码器的源代码

然后只需找到将新项目添加到字典并添加的解码器部分

if (dictionary_items<4096)

在它之前...如果您忽略错误的条目,图像看起来仍然可以,很可能是创建此图像的编码器没有正确编码。

【讨论】:

所以我找到了 Gif 解码器的 patched version,它可以处理损坏的 gif。尽管它似乎可以正确解码 gif(从每个帧中获取的 bufferedImages 和 Mat 看起来不错),但当将帧重新编译为 gif 时,从这些帧中重新编译的 Gif 只是模糊的静态。请注意,以前未受该错误影响的 gif 不会发生这种情况(它们可以正常工作)。 @lleontan 这很奇怪,很可能补丁不够好,并且解码器覆盖了内存中不应该检查的内容,提取帧将它们保存在某处,重新启动应用程序,然后重新编译图像回到 gif(这次不解码 gif)如果问题仍然存在,那么它是别的东西......如果问题消失,那么修补版本仍然泄漏内存或溢出/覆盖某些东西(只是不抛出异常)...... 我做了一些测试,并将问题隔离到我的 mat->BufferedImage 函数。现在都完成了!非常感谢您的帮助!【参考方案3】:

用bytedeco opencv,获取gif第一帧的简单方法:

import java.awt.image.BufferedImage, DataBufferByte
import java.io.ByteArrayInputStream
import com.sun.imageio.plugins.gif.GIFImageReader, GIFImageReaderSpi

import javax.imageio.ImageIO
import javax.swing.JFrame
import org.bytedeco.javacv.CanvasFrame, OpenCVFrameConverter
import org.bytedeco.opencv.opencv_core.Mat
import org.opencv.core.CvType

def toMat(bi: BufferedImage): Mat = 
  val convertedImage = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_3BYTE_BGR)
  val graphics = convertedImage.getGraphics
  graphics.drawImage(bi, 0, 0, null)
  graphics.dispose()

  val data = convertedImage.getRaster.getDataBuffer.asInstanceOf[DataBufferByte].getData
  val mat = new Mat(convertedImage.getHeight, convertedImage.getWidth, CvType.CV_8UC3)
  mat.data.put(data: _*)

  mat


def show(image: Mat, title: String): Unit = 
  val canvas = new CanvasFrame(title, 1)
  canvas.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE)
  canvas.showImage(new OpenCVFrameConverter.ToMat().convert(image))



val imageByteArrayOfGif = Array(....) // can be downloaded with java.net.HttpURLConnection

val ir = new GIFImageReader(new GIFImageReaderSpi())
val in = new ByteArrayInputStream(imageByteArray)
ir.setInput(ImageIO.createImageInputStream(in))

val bi = ir.read(0) // first frame of gif
val mat = toMat(bi)

show(mat, "buffered2mat")

【讨论】:

以上是关于使用 OpenCV 和 Java 从 gif 帧中获取 Mats的主要内容,如果未能解决你的问题,请参考以下文章

如何确定动画 GIF 帧中透明像素的数量?

opencv之从视频帧中截取图片

在 OpenCV Android 中的相机帧中查找主色

如何在 iOS 上的某个帧中显示 GIF,直到完全加载后才显示,并允许缩放?

当帧中没有人脸时,OpenCV 中的人脸检测器会变慢

c++结合opencv能给载入图像并处理,我想知道,c++结合啥,可以实现处理gif图像