javacv使用详解

笔者之前遇到一个抓取视频图片,然后视频图片倒置的问题,之后查了些博文找到解决办法。不过这不是主要的,主要的是javacv的代码笔者当时没有读懂,现在发一篇有详细注释的文档,分享一下使用心得。

目录

  • 一、先上源码,每一行都有注释
  • 二、使用注意事项
    • 1、使用javacv之前记得倒入依赖
    • 2、注意视屏中的Rotate属性
    • 3、最容易忽视的一点:取帧(Frame)


一、先上源码,每一行都有注释

package com.muyichen.demo.javacv;

import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.IplImage;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.bytedeco.opencv.global.opencv_core.cvFlip;
import static org.bytedeco.opencv.global.opencv_core.cvTranspose;

/**
 * 抓取视频图片工具类
 * @author muyichen
 * @version 1.0
 */
public class FrameGrabberUtil {

    /**
     * 抓取视频图片工具
     * @param filePath          视频文件路径(加上文件名)
     * @param targetFilePath    取出图片的存放路径
     * @param targetFileName    取出图片的名称(全名:当前参数+帧数)
     * @param randomSize        取出多少张图片(数量)
     * @throws FrameGrabber.Exception
     */
    public static void randomGrabberFFmpegImage(String filePath, String targetFilePath, String targetFileName, int randomSize) throws FrameGrabber.Exception {
        //根据路径读取视频文件
        FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
        //启动FFmpegFrameGrabber(抓帧器)
        ff.start();
        //获取rotate属性
        String rotate =ff.getVideoMetadata("rotate");
        //获取视频中包含有多少帧
        int ffLength = ff.getLengthInFrames();
        //随机选出五个取帧位置
        List<Integer> randomGrab = random(ffLength, randomSize);
        //标记最后读取的位置(在这个位置跳出循环)
        int maxRandomGrab =randomGrab.get(randomGrab.size() - 1);
        //遍历随机选出来的帧位置
        Frame f;
        int i = 0;
        while (i <ffLength) {
            //取出对应位置的帧
            f = ff.grabImage();
            //当前帧是想要获取的帧则将其读取出来
            if (randomGrab.contains(i)) {
                //是否存在rotate属性,存在则需要对图片进行修正处理
                if (!StringUtils.isEmpty(rotate)) {
                    //创建一个CVF帧-图片转换器
                    OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
                    //使用转换器将获取出的帧(Frame)对象转换为图片对象
                    IplImage src = converter.convert(f);
                    //将图片对象修正处理完成后,再转换为帧(Frame)对象
                    f = converter.convert(rotate(src, Integer.parseInt(rotate)));
                }
                //将帧对象输出为具体的图片文件
                doExecuteFrame(f, targetFilePath, targetFileName, i);
            }
            //如果到需要的帧取完了则跳出循环
            if (i >= maxRandomGrab) {
                break;
            }
            i++;
        };
        ff.stop();
    }

    /**
     * 将图片根据rotate属性还原为它原来的状态
     * @param src       原始图片对象
     * @param rotate    旋转角度
     * @return
     */
    public static IplImage rotate(IplImage src, int rotate) {
        //创建一个和原图片宽高互换的图片框架
        IplImage img = IplImage.create(src.height(), src.width(), src.depth(), src.nChannels());
        //将原图片中元素的位置进行矩阵转置后放入新创建的图片框架中
        cvTranspose(src, img);
        //将转置好后的图片根据rotate属性的值旋转对应的度数(根据纵轴旋转rotate度数)
        cvFlip(img, null, rotate);
        //返回最终生成的图片
        return img;
    }

    /**
     * 将帧对象输出为具体的图片文件
     * @param f                 帧对象
     * @param targetFilePath    输出图片文件存放路径
     * @param targetFileName    输出图片文件名
     * @param index             帧位置(视频的第几帧)
     */
    public static void doExecuteFrame(Frame f, String targetFilePath, String targetFileName, int index) {
        if (null == f || null == f.image) {
            return;
        }
        //创建一个2D帧转换器
        Java2DFrameConverter converter = new Java2DFrameConverter();
        //设定图片的输出格式
        String imageMat = "png";
        //设定图片的全路径+图片名
        String FileName = targetFilePath + File.separator + targetFileName + "_" + index + "." + imageMat;
        //使用2D帧转换器将视频中的帧转换为图片缓冲对象
        BufferedImage bi = converter.getBufferedImage(f);
        //根据图片的全路径名创建一个图片文件
        File output = new File(FileName);
        try {
            //使用图片IO将图片缓冲对象写入新创建的图片文件中
            ImageIO.write(bi, imageMat, output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 随机选出指定长度范围类的几个数字
     * @param baseNum   随机选出的数字个数
     * @param length    指定长度范围
     * @return
     */
    public static List<Integer> random(int baseNum, int length) {
        //创建指定长度的接收数字集合对象
        List<Integer> list = new ArrayList<>(length);
        //当集合对象未满时,循环选出
        while (list.size() < length) {
            //获取指定范围类的随机数
            Integer next = (int) (Math.random() * baseNum);
            //判断集合中是否存在当前随机出来的数,有的话则跳出当前循环进入下一循环
            if (list.contains(next)) {
                continue;
            }
            //将指定范围随机数放入集合中
            list.add(next);
        }
        //对集合进行排序
        Collections.sort(list);
        return list;
    }

    public static void main(String[] args) throws Exception {
        randomGrabberFFmpegImage("/Users/muyichen/Downloads/0420上传.mp4", "/Users/muyichen/Downloads", "screenshot", 5);
    }

}

二、使用注意事项

1、使用javacv之前记得倒入依赖

<dependency>
	<groupId>org.bytedecogroupId>
	<artifactId>javacv-platformartifactId>
	<version>1.5.3version>
dependency>

注意各个版本之间的类方法位置可能不同,比如1.3.1版本中的IplImage是一个内部类,但是1.5.3的这个类却独立出来了。

2、注意视屏中的Rotate属性

Roate属性是导致图片由竖屏变横屏的主要原因,上述代码中有着详细的处理方法。

3、最容易忽视的一点:取帧(Frame)

使用FFmpegFrameGrabber的时候,需要将它当成一个流来处理,FFmpegFrameGrabber无法指定从确定位置取帧,只能一帧一帧的读取。grabImage()这个方法相当于流中的read()方法,它每读完一帧后都会从当前读取帧的下一帧位置开始读取。(也就是说如果要读取100帧位置的图片,那么他会先将前面的99帧都先读一遍后才能读到第100帧的图片

你可能感兴趣的:(Java,java,opencv,ffmpeg)