zoukankan      html  css  js  c++  java
  • javacv之于视频/GIF解帧及重新拼接生成GIF实现

      预备接手表情包处理业务,前期处理并不复杂,流程包括 : GIF动图与视频的解帧 , 逐帧处理, 组合各帧得到新的GIF. 经过调研, 整合了ffmpeg的Java CV 可完美处理解帧 , animated-gif-lib 组件包含gif生成的成熟方案 , 进而问题解决.

      animated-gif-lib + Java CV

      animated-gif-lib.jar是用来拆分和合成GIF的工具包,主要用到其中的GifDecoder/AnimatedGifEncoder.

      Java CV 常用于音频/图片等处理,其中整合了常用的c++类库,例如音频处理的ffmpeg,且可与Open CV配合使用.这里主要用到FFmpegFrameGrabber来取帧/Java2DFrameConverter来类型转换.

      其实,GifDecoder也可以完成对GIF的解帧,但无法对视频进行操作,且实际使用中发现各帧颜色处理上有偏差,但并不影响最后新GIF的合成.综上,为了代码的复用性,采用Java CV来解帧,只使用其中AnimatedGifEncoder来完成合成GIF的操作.

      代码实现

      解帧,FFmpegFrameGrabber获取GIF总帧数时异常(),故而采用GifDecoder获取

            String gifPath = "/home/lab/test/11.gif";
            String dirPath = "/home/lab/test/gif/";
            // 用以解帧
            FFmpegFrameGrabber grabberGif = new FFmpegFrameGrabber(gifPath);
            grabberGif.start();
            Frame frame ;
            // 用以获取GIF总帧数
            GifDecoder decoder = new GifDecoder();
            int status = decoder.read(gifPath);
            if (status != GifDecoder.STATUS_OK) {
                throw new IOException("read image " + gifPath + " error!");
            }
            // 类型转换,Frame -> BufferedImage
            Java2DFrameConverter converter = new Java2DFrameConverter();
            int frameCount = decoder.getFrameCount();
            for (int i = 0 ; i < frameCount ; i++) {
                String fileName = dirPath + "img_" + i + ".jpg";
                File outPut = new File(fileName);
                frame = grabberGif.grabImage();
                if (frame != null) {
                    ImageIO.write(converter.getBufferedImage(frame),"jpg",outPut);
                }
            }
            grabberGif.stop();

      合成GIF

            int frameRate = 20;// 新GIF总帧数
            String resGif = "/home/lab/test/22.gif";
            FileOutputStream targetFile = new FileOutputStream(resGif); // 目标文件流
            int margin = 2; // 间隔帧数
            AnimatedGifEncoder en = new AnimatedGifEncoder();
            en.setFrameRate(frameRate);
            en.start(targetFile);
            for (int i = 0; i < frameRate; i++) {
                en.addFrame(converter.convert(grabberGif.grabImage()));
                grabberGif.setFrameNumber(grabberGif.getFrameNumber() + margin);
            }
            en.finish();
            grabberGif.stop();
            targetFile.close();

      原GIF倒序得到新GIF

            String gifPath = "/home/lab/test/11.gif";
            // 用以解帧
            FFmpegFrameGrabber grabberGif = new FFmpegFrameGrabber(gifPath);
            grabberGif.start();
            // 用以获取GIF总帧数
            GifDecoder decoder = new GifDecoder();
            int status = decoder.read(gifPath);
            if (status != GifDecoder.STATUS_OK) {
                throw new IOException("read image " + gifPath + " error!");
            }
            // 类型转换,Frame -> BufferedImage
            Java2DFrameConverter converter = new Java2DFrameConverter();
            int frameCount = decoder.getFrameCount();
            String resGif = "/home/lab/test/22.gif";
            FileOutputStream targetFile = new FileOutputStream(resGif); // 目标文件流
            AnimatedGifEncoder en = new AnimatedGifEncoder();
            en.setFrameRate(frameCount);
            en.start(targetFile);
            for (int i = frameCount - 1; i >= 0; i--) {
                grabberGif.setFrameNumber(i);
                en.addFrame(converter.convert(grabberGif.grabImage()));
            }
            en.finish();
            grabberGif.stop();
            targetFile.close();

      基于GifDecoder和AnimatedGifEncoder实现的gif倒序

            String outputPath = "/home/lab/test/001.gif";
            String imagePath = "/home/lab/test/33.gif";
            GifDecoder decoder = new GifDecoder();
            int status = decoder.read(imagePath);
            if (status != GifDecoder.STATUS_OK) {
                throw new IOException("read image " + imagePath + " error!");
            }
            // 拆分一帧一帧的压缩之后合成
            AnimatedGifEncoder encoder = new AnimatedGifEncoder();
            encoder.start(outputPath);
            encoder.setRepeat(decoder.getLoopCount());
            for (int i = decoder.getFrameCount() -1; i >= 0; i--) {
                encoder.setDelay(decoder.getDelay(i));// 设置播放延迟时间
                BufferedImage bufferedImage = decoder.getFrame(i);// 获取每帧BufferedImage流
                int height = bufferedImage.getHeight();
                int width = bufferedImage.getWidth();
                BufferedImage zoomImage = new BufferedImage(width, height, bufferedImage.getType());
                Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
                Graphics gc = zoomImage.getGraphics();
                gc.setColor(Color.WHITE);
                gc.drawImage(image, 0, 0, null);
                encoder.addFrame(zoomImage);
            }
            encoder.finish();
            File outFile = new File(outputPath);
            BufferedImage image = ImageIO.read(outFile);
            ImageIO.write(image, outFile.getName(), outFile);

       视频转gif

            String videpPath = "/home/lab/test/t1.mp4";
            String gifPath = "/home/lab/test/test.gif";
            FileOutputStream targetFile = new FileOutputStream(gifPath);
            FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videpPath);
            grabber.start();
            Frame frame;
            int frames = grabber.getLengthInFrames();
            AnimatedGifEncoder encoder = new AnimatedGifEncoder();
            encoder.setFrameRate(frames);
            encoder.start(targetFile);
            Java2DFrameConverter converter = new Java2DFrameConverter();
            for (int i = 0 ; i < frames ; i++) {
                encoder.setDelay((int) grabber.getDelayedTime());
                grabber.setFrameNumber(i);
                frame = grabber.grabImage();
                encoder.addFrame(converter.convert(frame));
            }
            encoder.finish();
            targetFile.close();
            grabber.close();

      pom依赖

      因 javacv-platform依赖过重,实际引入的时候推荐指定系统版本的即可.开发机为64位Ubuntu,依赖如下

            <dependency>
                <groupId>org.bytedeco</groupId>
                <artifactId>javacv</artifactId>
                <version>1.4.3</version>
            </dependency>
            <dependency>
                <groupId>org.bytedeco.javacpp-presets</groupId>
                <artifactId>opencv</artifactId>
                <version>3.4.3-1.4.3</version>
                <classifier>linux-x86_64</classifier>
            </dependency>
            <dependency>
                <groupId>org.bytedeco.javacpp-presets</groupId>
                <artifactId>ffmpeg</artifactId>
                <version>4.0.2-1.4.3</version>
                <classifier>linux-x86_64</classifier>
            </dependency>
            <dependency>
                <groupId>org.bytedeco</groupId>
                <artifactId>javacpp</artifactId>
                <version>1.4.3</version>
            </dependency>
            <dependency>
                <groupId>org.bytedeco.javacpp-presets</groupId>
                <artifactId>ffmpeg</artifactId>
                <version>4.0.2-1.4.3</version>
                <classifier>linux-x86_64</classifier>
            </dependency>
            <!-- gif -->
            <dependency>
                <groupId>com.madgag</groupId>
                <artifactId>animated-gif-lib</artifactId>
                <version>1.4</version>
            </dependency>
  • 相关阅读:
    c语言中的rewind函数,Win CE 不支持,可用fseek函数替换
    接口隔离原则(转)
    接口设计的 11 种原则 (转)
    设计模式六大原则/接口设计六大原则 之 组合/聚集复用原则(转)
    C++ 求幂的运算符是什么?
    设计模式六大原则/接口设计六大原则 之 迪米特法则(转)
    解决mysql出现“the table is full”的问题
    tomcat远程调试设置
    这些习惯很伤肾 要警觉
    从ie临时文件夹一次复制多个文件
  • 原文地址:https://www.cnblogs.com/nyatom/p/10837363.html
Copyright © 2011-2022 走看看