javacv使用笔记
一.前言
最近在做一个视频审核的功能,但是运营觉得每个视频都要看一篇太浪费时间了,于是提出了这样一个需求,给每个视频随机截取5张图片展示出来,根据这5张图片决定是否需要继续观看视频内容,以提高审核效率。既然运营提出了这样的需求,就得尽力去完成。
二.准备
首先从感性的角度分析该需求肯定可以实现的,毕竟软件开发技术已经是相当成熟了。只是暂时不知道什么技术可以实现该功能。于是,只能向度娘去请教了。经过一个时间的搜索发现有个叫javacv的开源框架似乎可以满足我的需求,那么就要花更多的时间去学习并动手实践一下。
三.开始
1.首先创建一个maven工程,工程名随意
2. 引入javacv需求的jar包,在pom.xml文件中添加
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
这里使用的是最新版本1.3.1
3. 从百度上搜索到一段代码
package com.javacv.test;
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 javax.imageio.ImageIO;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber.Exception;
import org.bytedeco.javacv.Java2DFrameConverter;
public abstract class FrameGrabberKit {
public static void main(String[] args) throws Exception {
// randomGrabberFFmpegImage("e:/ffmpeg/aa.mp4", "./target", "screenshot", 5);
randomGrabberFFmpegImage("e:/ffmpeg/ffmpeg.mp4", "./target", "screenshot", 5);
}
public static void randomGrabberFFmpegImage(String filePath, String targerFilePath, String targetFileName, int randomSize)
throws Exception {
FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
ff.start();
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)) {
doExecuteFrame(f, targerFilePath, targetFileName, i);
}
if (i >= maxRandomGrab) {
break;
}
i++;
}
ff.stop();
}
public static void doExecuteFrame(Frame f, String targerFilePath, String targetFileName, int index) {
if (null == f || null == f.image) {
return;
}
Java2DFrameConverter converter = new Java2DFrameConverter();
String imageMat = "png";
String FileName = targerFilePath + File.separator + targetFileName + "_" + index + "." + imageMat;
BufferedImage bi = converter.getBufferedImage(f);
File output = new File(FileName);
try {
ImageIO.write(bi, imageMat, output);
} catch (IOException e) {
e.printStackTrace();
}
}
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;
}
}
注:该段代码只需要将main方法中的视频源地址修改成自己的地址即可运行。
运气还不错,代码能成功运行且能成功截图。本以为到此可以告一段落了,但经过几次的测试发现一个问题,截取出来的图片被旋转了。这可不是我想要的结果啊,没办法,只能继续去请教度娘。
4. 解决图片旋转问题
通过一段时间的搜索了解到,如果拍摄的视频中带有旋转(rotate)信息,那么截取出来的图片就会被旋转。通过查询API发现FFmpegFrameGrabber的getVideoMetadata("rotate")方法可以获取到视频的旋转信息。根据获取到的rotate信息对ff.grabImage()得到的Frame进行旋转,但是Frame并没有提供旋转接口。但有一个IpImage对象提供了旋转方法
public static IplImage rotate(IplImage src, int rotate) {
IplImage img = IplImage.create(src.height(), src.width(), src.depth(), src.nChannels());
opencv_core.cvTranspose(src, img);
opencv_core.cvFlip(img, img, angle);
return img;
}
那么现在需要解决的就是把Frame转换成IpImage,旋转后在转回Frame。
再次查看API发现OpenCVFrameConverter.ToIplImage提供了相互转换的接口
OpenCVFrameConverter.ToIplImageconverter =new OpenCVFrameConverter.ToIplImage();
converter有两个重载的方法converter(IplImage img)和converter(Frame frame)可以实现IpImage和Frame的相互转换。
至此,基本满足了所有需求,最终代码如下:
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 javax.imageio.ImageIO;
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_core.IplImage;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber.Exception;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;
public abstract class FrameGrabberKit {
public static void main(String[]args)throws Exception {
// randomGrabberFFmpegImage("e:/ffmpeg/aa.mp4", "./target", "screenshot", 5);
randomGrabberFFmpegImage("e:/ffmpeg/ffmpeg.mp4","./target","screenshot", 5);
}
public static void randomGrabberFFmpegImage(StringfilePath, StringtargerFilePath, StringtargetFileName,int randomSize)
throws Exception {
FFmpegFrameGrabberff = FFmpegFrameGrabber.createDefault(filePath);
ff.start();
Stringrotate =ff.getVideoMetadata("rotate");
int ffLength =ff.getLengthInFrames();
List<Integer>randomGrab =random(ffLength,randomSize);
int maxRandomGrab =randomGrab.get(randomGrab.size() - 1);
Framef;
int i = 0;
while (i <ffLength) {
f =ff.grabImage();
if (randomGrab.contains(i)) {
if(null !=rotate &&rotate.length() > 1) {
OpenCVFrameConverter.ToIplImageconverter =new OpenCVFrameConverter.ToIplImage();
IplImagesrc =converter.convert(f);
f =converter.convert(rotate(src, Integer.valueOf(rotate)));
}
doExecuteFrame(f,targerFilePath,targetFileName,i);
}
if (i >=maxRandomGrab) {
break;
}
i++;
}
ff.stop();
}
public static IplImage rotate(IplImage src,int angle) {
IplImageimg = IplImage.create(src.height(),src.width(),src.depth(),src.nChannels());
opencv_core.cvTranspose(src,img);
opencv_core.cvFlip(img,img,angle);
return img;
}
public static void doExecuteFrame(Framef, StringtargerFilePath, StringtargetFileName,int index) {
if (null ==f ||null ==f.image) {
return;
}
Java2DFrameConverterconverter =new Java2DFrameConverter();
StringimageMat ="png";
StringFileName =targerFilePath + File.separator +targetFileName +"_" +index +"." +imageMat;
BufferedImagebi =converter.getBufferedImage(f);
Fileoutput =new File(FileName);
try {
ImageIO.write(bi,imageMat,output);
}catch (IOExceptione) {
e.printStackTrace();
}
}
public static List<Integer> random(int baseNum,int length) {
List<Integer>list =new ArrayList<>(length);
while (list.size() < length) {
Integernext = (int) (Math.random() *baseNum);
if (list.contains(next)) {
continue;
}
list.add(next);
}
Collections.sort(list);
return list;
}
}