从视频中抽几张图
来源:互联网 发布:dazelu com域名更换 编辑:程序博客网 时间:2024/05/17 23:40
花满楼原创,转载须授权。
小白:那还不简单,播放视频再截图就行啦。
花满楼:如果要截个几百张,你是要写个截图程序来做吗?
小白:什么变态需求要几百张?
花满楼:……好吧,那如果在视频105秒的那一瞬间出现了一个美女,你能保证准确无误截下来吗?
小白:那要看是什么美女了,我可以多试几次的嘛,只要有恒心是吧?但如果是欣欣这样的美女,我可没兴趣!
花满楼:好了!不必这么有恒心,有更好的办法,用FFmpeg来弄。
本文解决:使用FFmpeg实现从视频中提取图片的功能。
(一)用FFmpeg的示例程序来实现
FFmpeg的组装程序ffmpeg提供了从视频中提取图片的功能,所以,实现提取图片,就是打个命令的事情。
小白:还好,我安装了FFmpeg!
花满楼:安装很简单,参考文章后的链接也可以。
提取图片可以这样,比如:
ffmpeg -ss 00:00:5 -i ../moments.mp4 -vframes 1 -f image2 -y a.png
- ss表示开始提取图片的时间点,既可以用时分秒方式,也可以是多少秒。如果使用到这个参数,那应该把它作为第一个参数,因为可以让FFmpeg提速。
- i表示输入文件,就是视频文件。
- vframes表示拿多少帧,也就是多少张图片。注意,这个参数要放在-i参数之后。
- f表示提取出来的图片的格式。
- y表示覆盖已有同名的图片。
比如:
ffmpeg -i xxx.mp4 -r 1 -y -f image2 -t 5 -s 240*320 pc%3d.jpg
- r表示每秒提取图片的帧数,即帧率,默认是25fps,上面设置为一秒拿一张图。
- t表现提取持续多少秒,也可以用时分秒的格式来表示。
- s表出来的图片的尺寸。
- 3%d表示以001、002这样的格式来命名输出的图片。
小白:那么说,如果我发现视频某个时间点有美女的话,那我就可以用ss从这个时间点再前一点,然后用t来持续提取5秒,或者用vframes来提取几十张,那就准没漏了!也就是这样:ffmpeg -ss 10 -t 5 -r 1 -i Movie-1.mp4 -f image2 -y pc-temp/image%3d.jpg
小白:提取到一些图片后,我想弄成视频,怎么办?
花满楼:很简单,前提是你的ffmpeg已经支持视频编码器(比如x264),参考FFmpeg的安装吧。
把图片制作成视频
ffmpeg -f image2 -i img%3d.jpg test.mp4
-img%d表示以‘img001’, ’img002‘这类的命名,按顺序使用。注意f参数要在i参数之前。
小白:很好!我提取到美女图片了!look!
(二)组装FFmpeg来实现
先上演示代码,再作解释:
#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include <stdio.h>#include <stdlib.h>typedef struct { unsigned int filesize; unsigned short reserved1; unsigned short reserved2; unsigned int dataoffset;}BITMAP_FILE_HEADER;typedef struct { unsigned int infosize; int width; int height; unsigned short planecount; unsigned short bitcount; unsigned int compressiontype; unsigned int imagedatasize; int xpixpermeter; int ypixpermeter; unsigned int colorusedcount; unsigned int colorimportantcount;}BITMAP_INFO;void extractpicture(const char* filepath) { av_register_all(); av_log_set_level(AV_LOG_DEBUG); AVFormatContext* formatContext = avformat_alloc_context(); int status = 0; int success = 0; int videostreamidx = -1; AVCodecContext* codecContext = NULL; status = avformat_open_input(&formatContext, filepath, NULL, NULL); if (status == 0) { status = avformat_find_stream_info(formatContext, NULL); if (status >= 0) { for (int i = 0; i < formatContext->nb_streams; i ++) { if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videostreamidx = i; break; } } if (videostreamidx > -1) { codecContext = formatContext->streams[videostreamidx]->codec; AVCodec* codec = avcodec_find_decoder(codecContext->codec_id); if (codec) { status = avcodec_open2(codecContext, codec, NULL); if (status == 0) { success = 1; } } } } else { av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n"); } } if (success) { av_dump_format(formatContext, 0, filepath, 0); int gotframe = 0; AVFrame* frame = av_frame_alloc(); int decodelen = 0; int limitcount = 10; int pcindex = 0; unsigned char* rgbdata = (unsigned char*)malloc(avpicture_get_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height)); AVFrame* rgbframe = av_frame_alloc(); avpicture_fill((AVPicture*)rgbframe, rgbdata, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height); struct SwsContext* swscontext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL); while (pcindex < limitcount) { AVPacket packet; av_init_packet( &packet ); status = av_read_frame(formatContext, &packet); if (status < 0) { if (status == AVERROR_EOF) { av_log(NULL, AV_LOG_DEBUG, "read end for file\n"); } else { av_log(NULL, AV_LOG_DEBUG, "av_read_frame error\n"); } av_packet_unref(&packet); break; } else { if (packet.stream_index == videostreamidx) { decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet); if (decodelen > 0 && gotframe) { frame->data[0] = frame->data[0] + frame->linesize[0] * (codecContext->height - 1); frame->data[1] = frame->data[1] + frame->linesize[1] * (codecContext->height / 2 - 1); frame->data[2] = frame->data[2] + frame->linesize[2] * (codecContext->height / 2 - 1); frame->linesize[0] *= -1; frame->linesize[1] *= -1; frame->linesize[2] *= -1; sws_scale(swscontext, frame->data, frame->linesize, 0, codecContext->height, rgbframe->data, rgbframe->linesize); char filename[12] = {0}; sprintf(filename, "pc%03d.bmp", ++ pcindex); FILE* file = fopen(filename, "wb"); if (file) { int pixcount = codecContext->width * codecContext->height; BITMAP_FILE_HEADER fileheader = {0}; fileheader.filesize = 2+sizeof(BITMAP_FILE_HEADER)+sizeof(BITMAP_INFO)+pixcount * 3; fileheader.dataoffset = 0x36; BITMAP_INFO bmpinfo = {0}; bmpinfo.infosize = sizeof(BITMAP_INFO); bmpinfo.width = codecContext->width; bmpinfo.height = codecContext->height; bmpinfo.planecount = 1; bmpinfo.bitcount = 24; bmpinfo.xpixpermeter = 5000; bmpinfo.ypixpermeter = 5000; unsigned short ftype = 0x4d42; fwrite(&ftype, sizeof ftype, 1, file); fwrite(&fileheader, sizeof fileheader, 1, file); fwrite(&bmpinfo, sizeof bmpinfo, 1, file); fwrite(rgbframe->data[0], pixcount*3, 1, file); fclose(file); } } } } av_packet_unref(&packet); } av_frame_free(&rgbframe); free(rgbdata); av_frame_free(&frame); sws_freeContext(swscontext); } avformat_free_context(formatContext);}int main(int argc, char *argv[]){ extractpicture("moments.mp4"); return 0;}
代码解释
- 程序演示了把解码后的图片数据保存成位图的过程。如果有必要可以做更多的修改,比如av_seek_frame到适当的位置再开始解码与保存位图,也可以控制多少帧后保存一张位图,等等。
- av_register_all注册“所有”,所有的编解码器、muxer与demuxer等等(要求configure编译时有enable才会真正使用到),这一步是关键的初始化工作,没有这一步,FFmpeg很可能不能如期工作。
- avformat_open_input打开输入。“输入”是一个抽象,这里具体成文件。这一步之后,就获得了一些文件格式信息。
- avformat_find_stream_info查找流的信息。多媒体数据由流组成,这一步就是获取媒体格式信息,可能耗时。这一步后,流使用的编解码器被确定。
- avcodec_find_decoder找到解码器。
- avcodec_open2找开解码器。
- av_read_frame读取一个packet,未解码。
- avcodec_decode_video2解码一个视频帧。
- sws_getContext获取并初始化一个SwsContext场景,swscontext不仅可以缩放图片,还可以转换颜色布局。
- sws_scale缩放或转换。
- 在调用sws_scale之前,对frame->data跟frame->linesize做的处理,是为了调整坐标系,让图片适合位图的坐标系(从下往上,从左往右),这样转换出来的位图才不会颠倒。
- 这里选择的是24位的位图(没有调色板),在写入rgb数据前,先把文件头与位图信息写好。
小白:这个……好多细节啊!
花满楼:一线程序员,是有必要了解所有的细节的!但不必在一篇文章内全部理解,多看多写才是好的办法。这里面还有很多东西也不展开了,可以在后续的文章中再具体介绍。
小白:突然觉得好难!
花满楼:把你刚才提取美女的决心与恒心拿出来!
小白:……
代码运行
这个示例程序的文件结构是这样的:
因为是在macos上运行,所以先要编译出x86_64的FFmpeg库。
makefile的内容是这样的:
out=video2imageobjs=video2image.c $(out):$(objs) gcc -o $(out) $(objs) -Iffmpeg/include/ -Lffmpeg -lffmpeg -liconv -lz -gclean: rm -f $(out) *.o
make后就可以生成执行程序,再执行即可在当前目录生成位图。
小白:怎么编译出x86_64的FFmpeg?
花满楼:参考我之前讲过的“组装FFmpeg,为我所用”,把编译脚本改一下就好啦。或者,你直接configure后再make也可以,只不过这时会有多个.a文件,你需要合一下,或者都link上。
小白:link是什么?
花满楼:这个是编译细节的问题……你找西门吹雪吧。
小白:又见吹神!
开心时间
“哎呀你这个小同志可真是的,我给你一万你扶不扶?” “不扶,老太太我跟你说,我不要你那点东西。”“三万,还不扶?”“不扶!”“五万!”“扶了!清一色!”宿舍一兄弟喜欢一女汉子,两人的关系也不错,经常一起打球,有一天,他鼓起勇气告白,谁知这女汉子说:“我只把你当兄弟啊!”他万念俱灰的低下头,片刻,他突然抬起头来,收起了之前的失落,试探的问:“兄弟,能搞基吗?”你可以不同意我的观点,但是我可以揍你。从前有一个人叫小明,小明没听见。
多媒体开发,关联阅读
FFmpeg第一次约会
ffplay看直播,简洁是美
摄像头启动,开始监控
调试FFmpeg
组装FFmpeg,为我所用
如果你对音视频开发有兴趣,那请关注我们的公众号,并在输入框中发送数字5,花满楼会加你到专业的音视频开发群中。
- 从视频中抽几张图
- 从视频中截图
- 从摄像头读入视频
- 从Twitter下载视频
- 从一个视频想到的
- 从大学生到程序员视频
- android从网上下载视频
- 从视频中保存图片
- OpenCV从摄像头读取视频
- opencv 从摄像头显示视频
- 从摄像头中获取视频
- 从视频中抓拍图片
- 从视频中提取mps
- 从视频中提取音频
- 从开发小白到音视频专家
- 从开发小白到音视频专家
- 手机视频的录制和从相册选着视频
- 拍摄视频和从相册中选本地视频
- bazel支持proxy代理
- 安卓沉浸式(透明)状态栏学习小结
- jeesite 如何读取属性文件
- 141. Linked List Cycle。
- JetBrains IDEA2017下载与安装
- 从视频中抽几张图
- 牛顿迭代公式计算平方根立方根
- 组内相关系数(ICC)在重复测量数据中的解释
- 画重点,Java方法的参数到底是值传递还是引用传递?
- 1003. 我要通过!(20)
- java与c++内存分配异同
- 一些时序题计算
- 大数据集群搭建之hadoop、tomcat、jdk等工具的安装(三)
- (译) JSON-RPC 2.0 规范(中文版)