lleontan
lleontan

Reputation: 133

Getting Mats from frames in a gif using OpenCV and Java

I am trying to get frames from a gif using OpenCV. I found Convert each animated GIF frame to a separate BufferedImage and used the second suggestion. I modified it slightly to return an array of Mats instead of BufferedImages.

I tried two methods to get bufferedImages from the gif. Each presented different problems.

  1. With the previous thread's suggestion

    BufferedImage fImage=ir.read(i);
    

    The program calls a "ArrayIndexOutOfBoundsException: 4096"

  2. With the original code from the previous thread.

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

    Each frame is a monotone color(not all black though) and the mat derived from the BufferedImage is empty.

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

Here is the gif I used

Upvotes: 2

Views: 1601

Answers (3)

Eric
Eric

Reputation: 401

with bytedeco opencv, simple way to get the first frame of 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")

enter image description here

Upvotes: 0

Spektre
Spektre

Reputation: 51863

I am not using libs for gif nor Java nor OpenCV but the ArrayIndexOutOfBoundsException: 4096

means that the dictionary is not cleared properly. The gif of yours is buggy I tested it and it contains errors not enough clear codes are present for some frames. If your GIF decoder does not check/handle such case then it simply crash because its dictionary growth more then GIF limit 4096/12bit

Try another GIF not some buggy ones ...

have tested your gif and it has around 7 clear codes per frame and contains 941 errors in total (absence of clear code resulting in dictionary overrun)

If you have source code for the GIF decoder

then just find part of decoder where new item is added to dictionary and add

if (dictionary_items<4096)

before it ... If you ignore the wrong entries the image looks still OK most likely the encoder in which this was created was not properly coded.

Upvotes: 1

lleontan
lleontan

Reputation: 133

Following Spektre's advice I found a better gif which fixed the monochromatic bufferedImages. The lack of viewable Mats was caused by my usage of the default constructor when declaring the Mat.

Working Code

    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;
}

Upvotes: 3

Related Questions