精华内容
下载资源
问答
  • wasm + ffmpeg实现前端截取视频功能

    千次阅读 2019-09-20 15:47:23
    经过笔者的一番摸索,基本实现了这个功能,一个完整的demo:ffmpeg wasm截取视频功能: 支持mp4/mov/mkv/avi等文件。基本的思想是这样的: 使用一个file input让用户选择一个视频文件,然后读取为Arr....

    有没有那么一种可能,在前端页面处理音视频?例如用户选择一个视频,然后支持他设置视频的任意一帧作为封面,就不用把整一个视频上传到后端处理了。经过笔者的一番摸索,基本实现了这个功能,一个完整的demo:ffmpeg wasm截取视频帧功能

     

     

    支持mp4/mov/mkv/avi等文件。基本的思想是这样的:

     

    使用一个file input让用户选择一个视频文件,然后读取为ArrayBuffer,传给ffmpeg.wasm处理,处理完之后,输出rgb数据画到canvas上或者是转成base64当做img标签的src属性就形成图片了。(Canvas可以直接把video dom当作drawImage的对象进而得到视频帧,不过video能播放的格式比较少,本文重点讨论ffmpeg方案的实现,因为ffmpeg还可做其它的事情,这只是一个例子。)

    这里有一个问题,为什么要借助ffmpeg呢,而不直接用JS写?因为多媒体处理的C库比较成熟,ffmpeg就是其中一个,还是开源的,而wasm刚好可以把它转化格式,在网页上使用,多媒体处理相关的JS库比较少,自己写一个多路解复用(demux)和解码视频的复杂度可想而知,JS直接编解码也会比较耗时。所以有现成的先用现成的。

    第1步是编译(如果你对编译过程不感兴趣的话,可以直接跳到第2步)

    1. 编译ffmpeg为wasm版本

    我一开始以为难度会很大,后来发现并没有那么大,因为有一个videoconverter.js已经转过了(它是一个借助ffmpeg在网页实现音视频转码的),关键在于把一些没用的特性在configure的时候给disable掉,不然编译的时候会报语法错误。这里使用的是emsdk转的wasm,emsdk的安装方法在它的安装教程已经说得很明白,主要是使用脚本判定系统下载不同编译好的文件。下载好之后就会有几个可执行文件,包括emcc、emc++、emar等命令,emcc是C的编译器,emc++是C++的编译器,而emar是用于把不同的.o库文件打包成一个.a文件的。

    先要在ffmpeg的官网下载源码。

    (1)configure

    解压进入目录,然后执行以下命令:

    emconfigure ./configure --cc="emcc" --enable-cross-compile --target-os=none --arch=x86_32 --cpu=generic \
        --disable-ffplay --disable-ffprobe --disable-asm --disable-doc --disable-devices --disable-pthreads --disable-w32threads --disable-network \
        --disable-hwaccels --disable-parsers --disable-bsfs --disable-debug --disable-protocols --disable-indevs --disable-outdevs --enable-protocol=file

    通常configure的作用是生成Makefile——configure阶段确认一些编译的环境和参数,然后生成编译命令放到Makefile里面。

    而前面的emconfigure的主要作用是把编译器指定为emcc,但只是这样是不够的,因为ffmpeg里面有一些子模块,并不能彻底地把所有的编译器都指定为emcc,好在ffmpeg的configure可以通过--cc的参数指定自定义的编译器,在Mac上C编译器一般是使用/usr/bin/clang,这里指定为emcc。

    后面的disable是把一些不支持wasm的特性给禁掉了,例如--disable-asm是把使用汇编代码的部分给禁掉了,因为那些汇编语法emcc不兼容,没有禁掉的话编译会报错语法错误。另外一个--disable-hwaccels是把硬解码禁用了,有些显卡支持直接解码,不需要应用程序解码(软解码),硬解码性能明显会比软解码的高,这个禁了之后,会导致后面使用的时候报了一个warning:

    [swscaler @ 0x105c480] No accelerated colorspace conversion found from yuv420p to rgb24.

    但是不影响使用。

    (执行configure的过程会报一个segment fault,但后续的过程中发现没有影响。)

    等待configure命令执行完了,就会生成Makefile和相关的一些配置文件。

    (2)make

    make是开始编译的阶段,执行以下命令进行编译:

    emmake make

    在Mac上执行,你会发现最后把多个.o文件组装成.a文件的时候会报错:

    AR libavdevice/libavdevice.a
    fatal error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar: fatal error in /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib

    解决这个问题需要把打包的命令从ar改成emar,然后再把一个ranlib的过程去掉就行,修改ffbuild/config.mak文件:

    # 修改ar为emar
    - AR=ar
    + AR=emar
    
    # 去掉ranlib
    - RANLIB=ranlib
    + #RANLIB=ranlib

    然后再重新make就可以了。

    编译完成之后,会在ffmpeg目录生成一个总的ffmpeg文件,在ffmpeg的libavcodec等目录会生成libavcodec.a等文件,这些文件是后面我们要使用的bitcode文件,bitcode是一种已编译程序的中间代码。

    (最后在执行strip -o ffmpeg ffmpeg_g命令会挂掉,但是不要紧,strip改成cp ffmpeg_g ffmpeg就好了)

    2. 使用ffmpeg

    ffmpeg主要是由几个lib目录组成的:

    • libavcodec: 提供编解码功能
    • libavformat:多路解复用(demux)和多路复用(mux)
    • libswscale:图像伸缩和像素格式转化

    以一个mp4文件为例,mp4是一种容器格式,首先使用libavformat的API把mp4进行多路解复用,得到音视频在这个文件存放的位置等信息,视频一般是使用h264等进行编码的,所以需要再使用libavcodec进行解码得到图像的yuv格式,最后再借助libswscale转成rgb格式。

    这里有两个使用ffmpeg的方式,第一种是直接把第一步得到的ffmpeg文件编译成wasm:

    # 需要拷贝一个.bc后缀,因为emcc是根据后缀区分文件格式的
    cp ffmpeg_g ffmpeg.bc
    emcc ffmpeg.bc -o ffmpeg.html

    然后就会生成一个ffmpeg.js和ffpmeg.wasm,ffmpeg.js是用来加载和编译wasm文件以及提供一个全局的Module对象用来操控wasm里面ffmpeg API的功能的。有了这个之后,在JS里面通过Module调用ffmpeg的API。

    但是我感觉这个方式比较麻烦,JS的数据类型和C的数据类型差异比较多,在JS里面频繁地调C的API,需要让数据传来传去比较麻烦,因为要实现一个截取功能要调很多ffmpeg的API。

    所以我用的是第二种方式,先写C代码,在C里面把功能实现了,最后再暴露一个接口给JS使用,这样JS和WASM只需要通过一个接口API进行通信就好了,不用像第一种方式一样频繁地调用。

    所以问题就转化成两步:

    第一步是使用C语言写一个ffmpeg保存视频帧图像的功能

    第二步是编译成wasm和js进行数据的交互

    第一步的实现主要参考了一个ffmpeg的教程:ffmpeg tutorial。里面的代码都是现成的直接拷过来就好,有一些小问题是他用的ffmpeg版本稍老,部分API的参数需要修改一下。代码已上传到github,可见:cfile/simple.c

    使用方法已在readme里面进行介绍,通过以下命令编译成一个可执行文件simple:

    gcc simple.c -lavutil -lavformat -lavcodec `pkg-config --libs --cflags libavutil` `pkg-config --libs --cflags libavformat` `pkg-config --libs --cflags libavcodec` `pkg-config --libs --cflags libswscale` -o simple

    然后使用的时候传一个视频文件的位置就可以了:

    ./simple mountain.mp4

    就会在当前目录生成一张pcm格式的图片。

    这个simple.c是调用的ffmpeg自动读取硬盘文件的api,需要改成从内存读取文件内容,即我们自己读到内存的buffer然后传给ffmpeg,后面才能把数据传输改成从JS的buffer获取,这个的实现可见:simple-from-memory.c. 具体的C代码这里就不分析了,就是调调API,相对来说还是比较简单,就是要知道怎么用,ffmpeg网上的开发文档相对较少。

    这样第一步就算完成了,接着第二步,把数据的输入改成从JS获取,输出改成返回给JS.

    3. js和wasm的交互

    wasm版的具体实现是在web.c(还有一个proccess.c是把simple.c的一些功能拆了出去),在web.c里面有一个暴露给JS调用的函数,姑且起名叫setFile,这个setFile就是给JS调的:

    EMSCRIPTEN_KEEPALIVE // 这个宏表示这个函数要作为导出的函数
    ImageData *setFile(uint8_t *buff, const int buffLength, int timestamp) {
        // process ...
        return result;
    }

    需要传递三个参数:

    • buff:原始的视频数据(通过JS的ArrayBuffer传进来)
    • buffLength:视频buff的总大小(单位字节)
    • timestamp:是希望截取第几秒的视频帧

    最后处理完了返回一个ImageData的数据结构:

    typedef struct {
        uint32_t width;
        uint32_t height;
        uint8_t *data;
    } ImageData;

    里面有三个字段:图片的宽高和rgb数据。

    写好这些C文件后进行编译:

    emcc web.c process.c ../lib/libavformat.bc ../lib/libavcodec.bc ../lib/libswscale.bc ../lib/libswresample.bc ../lib/libavutil.bc \
        -Os -s WASM=1 -o index.html -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=16777216

    使用第1步编译生成的那些libavcode.bc等文件,这些文件有依赖顺序,前后不能颠倒,被依赖的要放在后面。这里面有些参数说明一下:

    -o index.html表示导出hmtl文件,同时会导出index.jsindex.wasm,主要使用这两个,生成的index.html是没用的;

    -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"] 表示要导出ccall和cwrap这两个函数,这两个函数的功能是为了调用上面C里面写的setFile函数;

    -s TOTAL_MEMORY=16777216 表示wasm总内存大小为约16MB,这个也是默认值,这个需要是64的倍数;

    -s ALLOW_MEMORY_GROWTH=1 当内存超出总大小时自动扩容。

    编译好之后写一个main.html,加入input[type=file]等控件,并引入上面生成的index.js,它会去加载index.wasm,并提供一个全局的Module对象操控wasm的API,包括上面在编译的时候指定导出的函数,如下代码所示:

    <!DOCType html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>ffmpeg wasm截取视频帧功能</title>
    </head>
    <body>
    <form>
        <p>请选择一个视频(本地操作不会上传)</p>
        <input type="file" required name="file">
        <label>时间(秒)</label><input type="number" step="1" value="0" required name="time">
        <input type="submit" value="获取图像" style="font-size:16px;">
    </form>
    <!--这个canvas用来画导出的图像-->
    <canvas width="600" height="400" id="canvas"></canvas>
    <!--引入index.js-->
    <script src="index.js"></script>
    <script>
    <script>
    !function() {
       let setFile = null;
       // WASM下载并解析完毕
       Module.onRuntimeInitialized = function () {
            console.log('WASM initialized done!');
            // 导出的核心处理函数
            setFile = Module.cwrap('setFile', 'number',
                          ['number', 'number', 'number']);
       };
    }();
    </script>

    需要在wasm下载并解析完成之后才能开始操作,它提供了一个onRuntimeInitialized的回调。

    为了能够使用C文件里面导出的函数,可以使用Module.cwrap,第一个参数是函数名,第二个参数是返回类型,由于返回的是一个指针地址,这里是一个32位的数字,所以用js的number类型,第三个参数是传参类型。

    接着读取input的文件内容到放到一个buffer里面:

    let form = document.querySelector('form');
    // 监听onchange事件
    form.file.onchange = function () {
        if (!setFile) {
            console.warn('WASM未加载解析完毕,请稍候');
            return;
        }
        let fileReader = new FileReader();
        fileReader.onload = function () {
            // 得到文件的原始二进制数据ArrayBuffer
            // 并放在buffer的Unit8Array里面
            let buffer = new Uint8Array(this.result);
            // ...
        };
        // 读取文件
        fileReader.readAsArrayBuffer(form.file.files[0]);
    };
    

    读取得到的buffer放在了一个Uint8Array,它是一个数组,数组里面每个元素都是unit8类型的即无符号8位整型,就是一个字节的0101的数字大小。

    接下来的关键问题是:怎么把这个buffer传给wasm的setFile函数?这个需要理解wasm的内存堆模型。

    4. wasm的内存堆模型

    上面在编译的时候指定的wasm使用的总内存大小,内存里面的内容可以通过Module.buffer和Module.HEAP8查看:

     

     

    这个东西就是JS和WASM数据交互的关键,在JS里面把数据放到这个HEAP8的数组里面,然后告诉WASM数据的指针地址在哪里和占用的内存大小,即在这个HEAP8数组的index和占用长度,反过来WASM想要返回数据给JS也是被放到这个HEA8里面,然后返回指针地址和和长度。

    但是我们不能随便指定一个位置,需要用它提供的API进行分配和扩容。在JS里面通过Module._molloc或者Module.dynamicMalloc申请内存,如下代码所示:

    // 得到文件的原始二进制数据,放在buffer里面
    let buffer = new Uint8Array(this.result);
    // 在HEAP里面申请一块指定大小的内存空间
    // 返回起始指针地址
    let offset = Module._malloc(buffer.length);
    // 填充数据
    Module.HEAP8.set(buffer, offset); 
    // 最后调WASM的函数
    let ptr = setFile(offset, buffer.length, +form.time.value * 1000);
    

    调用malloc,传需要的内存空间大小,然后会返回分配好的内存起始地址offset,这个offset其实就是HEAP8数组里的index,然后调用Uint8Array的set方法填充数据。接着把这个offset的指针地址传给setFile,并告知内存大小。这样就实现了JS向WASM传数据。

    调用setFile之后返回值是一个指针地址,指向一个struct的数据结构:

    typedef struct {
        uint32_t width;
        uint32_t height;
        uint8_t *data;
    } ImageData;

    它的前4个字节,用来表示宽度,紧接着的4个字节是高度,后面的是图片的rgb数据的指针,指针的大小也是4个字节,这个省略了数据长度,因为可以通过width * height * 3得到。

    所以[ptr, ptr + 4)存的内容是宽度,[ptr + 4, ptr + 8)存的内容是长度,[ptr + 8, ptr + 12)存的内容是指向图像数据的指针,如下代码所示:

    let ptr = setFile(offset, buffer.length, +form.time.value * 1000);
    let width = Module.HEAPU32[ptr / 4]
        height = Module.HEAPU32[ptr / 4 + 1],
        imgBufferPtr = Module.HEAPU32[ptr / 4 + 2],
        imageBuffer = Module.HEAPU8.subarray(imgBufferPtr, 
                          imgBufferPtr + width * height * 3);

    HEAPU32和上面的HEAP8是类似的,只不过它是每个32位就读一个数,由于我们上面都是32位的数字,所以用这个刚刚好,它是4个字节一个单位,而ptr是一个字节一个单位,所以ptr / 4就得到index。这里不用担心不能够被4整除,因为它是64位对齐的。

    这样我们就拿到图片的rgb数据内容了,然后用canvas画一下。

    5. Canvas画图像

    利用Canvas的ImageData类,如下代码所示:

    function drawImage(width, height, buffer) {
        let imageData = ctx.createImageData(width, height);
        let k = 0;
        // 把buffer内存放到ImageData
        for (let i = 0; i < buffer.length; i++) {
            // 注意buffer数据是rgb的,而ImageData是rgba的
            if (i && i % 3 === 0) {
                imageData.data[k++] = 255;
            }
            imageData.data[k++] = buffer[i];
        }
        imageData.data[k] = 255;
        memCanvas.width = width;
        memCanvas.height = height;
        canvas.height = canvas.width * height / width;
        memContext.putImageData(imageData, 0, 0, 0, 0, width, height);
        ctx.drawImage(memCanvas, 0, 0, width, height, 0, 0, canvas.width, canvas.height);
    }
    drawImage(width, height, imageBuffer);
    

    这样基本就完工了,但是还有一个很重要的事情要做,就是把申请的内存给释放,不然反复操作几次之后,网页的内存就飙到一两个G,然后就抛内存不够用异常了,所以在drawImage后之后把申请的内存释放了:

    drawImage(width, height, imageBuffer);
    // 释放内存
    Module._free(offset);
    Module._free(ptr);
    Module._free(imgBufferPtr);
    

    在C里面写的代码也要释放掉中间过程申请的内存,不然这个内存泄露还是挺厉害的。如果正确free之后,每次执行malloc的地址都是16358200,没有free的话,每次都会重新扩容,返回递增的offset地址。

    但是这个东西整体消耗的内存还是比较大。

    6. 存在的问题

    初始化ffmpeg之后,网页使用的内存就飙到500MB,如果选了一个300MB的文件处理,内存就会飙到1.3GB,因为在调setFile的时候需要malloc一个300MB大小的内存,然后在C代码的setFile执行过程中又会malloc一个300MB大小的context变量,因为要处理mov/m4v格式的话为了获取moov信息需要这么大的,暂时没优化,这几个加起来就超过1GB了,并且WebAssembly.Memory只能grow,不能shrink,即只能往大扩,不能往小缩,扩充后的内存就一直在那里了。而对于普通的mp4文件,context变量只需要1MB,这个可以把内存控制在1GB以内。

    第二个问题是生成的wasm的文件比较大,原始有12.6MB,gzip之后还有5MB,如下图所示:

     

     

    因为ffmpeg本身比较大,如果能够深入研究源码,然后把一些没用的功能disable掉或者不要include进来应该就可以给它瘦身,或者是只提取有用的代码,这个难度可能略高。

    第三个问题是代码的稳健性,除了想办法把内存降下来,还需要考虑一些内存访问越界的问题,因为有时候跑着跑着就抛了这个异常:

    Uncaught RuntimeError: memory access out of bounds

    虽然存在一些问题,但是起码已经跑起来,可能暂时还不具备部署生产环境的价值,后面可以慢慢优化。

    除了本文这个例子外,还可以利用ffmpeg实现其它一些功能,让网页也能够直接处理多媒体。基本上只要ffmpeg能做的,在网页也是能跑,并且wasm的性能要比直接跑JS的高。

    编辑于 2018-07

    来源: 影音视频技术空间
    文章作者: YUV420.COM
    更多文章: https://www.yuv420.com/
     

    展开全文
  • Python在windows下使用ffmpeg进行视频截取帧照片 ffmpeg的一些命令行是在终端使用的并且大多数是以sh文件作为脚本文件运行,在windows下运行linux脚本较为麻烦,所以我写了一个可以遍历文件夹的py脚本文件在windows...

    Python在windows下使用ffmpeg进行视频截取帧照片并保存到相应文件夹

    ffmpeg的一些命令行是在终端使用的并且大多数是以sh文件作为脚本文件运行,在windows下运行linux脚本较为麻烦,所以我写了一个可以遍历文件夹的py脚本文件在windows下使用ffmpeg进行视频帧的截取图片,并创建在有完全相同的数据集目录。实际上也可以利用ffmpeg做其他操作包括录屏等等,可以自己去查询ffmpeg的使用方法。

    起因是我在跑C3D项目代码的时候需要对UCF-101数据集进行视频到帧图片的transform,但是项目中给的视频转帧的脚本是sh文件,如果下git倒也是可以直接在windows下运行,但我运行的时候还是有错且我想做一个文件遍历并创建相同目录的数据集,所以我想不如我自己也写一个py脚本以后处理视频数据集更为方便(在虚拟机上跑也行但是怕电脑配置跟不上,数据集较大)下面先贴出sh文件的源码:

    原sh文件代码

    C3D项目地址:
    https://github.com/hx173149/C3D-tensorflow/blob/master/README.md;
    所做的事是把每个动作(此数据集为人体行为数据集作为例子,包括101个动作)的每个训练视频进行image转换并创建相应训练视频同名的文件夹存入

    #!/bin/bash
    
    # convert the avi video to images
    #   Usage (sudo for the remove priviledge):
    #       sudo ./convert_video_to_images.sh path/to/video fps
    #   Example Usage:
    #       sudo ./convert_video_to_images.sh ~/document/videofile/ 5
    #   Example Output:
    #       ~/document/videofile/walk/video1.avi 
    #       #=>
    #       ~/document/videofile/walk/video1/00001.jpg
    #       ~/document/videofile/walk/video1/00002.jpg
    #       ~/document/videofile/walk/video1/00003.jpg
    #       ~/document/videofile/walk/video1/00004.jpg
    #       ~/document/videofile/walk/video1/00005.jpg
    #       ...
    
    for folder in $1/*
    do
        for file in "$folder"/*.avi
        do
            if [[ ! -d "${file[@]%.avi}" ]]; then
                mkdir -p "${file[@]%.avi}"
            fi
            ffmpeg -i "$file" -vf fps=$2 "${file[@]%.avi}"/%05d.jpg
            rm "$file"
        done
    done
    

    例子:原数据集目录

    • root
      • UCF-101
        • ApplyEyeMakeup
        • ApplyLipstick
        • Archery
        • BabyCrawling

    文章最后有完整py代码:)

    import os
    from ffmpy import FFmpeg
    

    先创建三个列表用来存储视频文件名称以及视频地址

    #创建三个列表用来存储视频文件名以及视频地址
    file_list = []
    file_list_path=[]
    filelist = []
    

    定义输入输出目录

    # 源文件目录
    dir_path = 'root\\UCF101'
    
    # 输出截取之后的文件目录
    put_path = 'D:\\output\\'
    

    三个遍历方法

    #利用递归获取该目录下所有文件的path并且存入列表
    def get_all_file_path(dir_path):
        for file in os.listdir(dir_path):
            #print(file)
            filepath = os.path.join(dir_path, file)
            #print(filepath)
            if os.path.isdir(filepath):
                get_all_file_path(filepath)
            else:
                file_list_path.append(filepath)
        return file_list_path
    
    #利用递归获取该目录下所有文件的名称并存入列表
    def get_file_name(dir_path):
        for file in os.listdir(dir_path):
            filepath = os.path.join(dir_path, file)
            if os.path.isdir(filepath):
                get_file_name(filepath)
            else:
                file_list.append(file)
        return file_list
    
    #获取该path目录下文件名称并存入列表
    def get_file(dir_path):
        for file in os.listdir(dir_path):
            #print(file)
            filelist.append(file)
        return filelist
    

    重写mkdir函数创建文件夹

    #重写创建文件夹方法,用来检测是否重复建立文件夹
    def mkdir(path):
        import os
        # 去除首位空格
        path = path.strip()
        path = path.rstrip("\\")
        # 判断路径是否存在
        # 存在     True
        # 不存在   False
        isExists = os.path.exists(path)
        # 判断结果
        if not isExists:
            # 如果不存在则创建目录
             # 创建目录操作函数
            os.makedirs(path)
            return True
        else:
            # 如果目录存在则不创建,并提示目录已存在
            return False
    

    先把数据集遍历到列表中

    #file_list先存入数据集中所有的文件名
    file_list = get_file_name(dir_path)
    
    #file_list_path先存入数据集中所有文件的path
    file_list_path = get_all_file_path(dir_path)
    
    #filelist中存入该输入目录的文件名
    filelist = get_file(dir_path)
    
    #分别计算三个列表中元素的个数
    lenn = len(file_list)
    lenn2 = len(filelist)
    lenn3 = len(file_list_path)
    # print(lenn3)
    # print(lenn)
    # print(filelist)
    # print(file_list)
    # print(file_list_path)
    

    定义cmd命令字符串

    第一个路径是ffmpeg.exe的路径,或者在系统环境变量中添加了ffmpeg也可直接写ffmpeg,
    5可以换其他数字,代表每秒多少帧,5s的视频就会生成25张图片。输出的图片以‘00001.jpg’命名,两个{}的填充后面会提到

    #cmd命令存入str字符串
    str = 'F:\\ffmpeg-20200826-8f2c1f2-win64-static\\bin\\ffmpeg.exe ' + '-i {} -r 5 -f image2 {}%05d.jpg'
    

    创建文件夹

    #创建filelist空文件夹
    for i in range(lenn2):
        mkdir(put_path+"".join(filelist[i]))
    
    

    两个for循环遍历所有相应文件夹并在相应文件夹中创建与训练视频同名的文件夹,把ffmpeg转换好的图片放入

    
    '''
    循环遍历把每个文件的视频截取图片存入相应的文件夹
    外循环遍历不同动作类别,内循环遍历每个动作不同的训练数据并创建文件夹存入
    '''
    for j in range(lenn2):
        #parent存储当前动作名
        parent = "".join(filelist[j])
        #print(parent)
        #对文件名和路径列表清空
        file_list.clear()
        file_list_path.clear()
        #根据当前动作子路径查找其目录下文件名更新file_list
        file_list = get_file_name(dir_path + "\\" + parent + "\\")
        #更新file_list_path
        file_list_path = get_all_file_path(dir_path+"\\"+parent+"\\")
        #print(file_list_path)
    
        #内循环遍历该动作
        for i in range(len(file_list)):
            a = "".join(file_list[i])
            a, _ = a.split(".")
            #创建不同训练数据文件夹
            mkdir(put_path + parent + "\\" + a)
            # # print(filelist[j])
            # print(put_path+parent+"\\"+a+"\\")
            #填充ffmpeg命令的输入输出信息并运行
            str_cmd = str.format(file_list_path[i], put_path + parent + "\\" + a + "\\")
            os.popen(str_cmd)
            # print(str_cmd)
        #清空两个列表
        file_list.clear()
        file_list_path.clear()
        print("\n\n")
    
    

    不同训练数据,每个对应一个avi文件视频帧图片

    完整代码

    import os
    from ffmpy import FFmpeg
    # 创建三个列表用来存储视频文件以及视频地址
    file_list = []
    file_list_path=[]
    filelist = []
    # 源文件目录
    dir_path = 'root\\UCF-101'
    
    # 输出截取之后的文件目录
    put_path = 'D:\\output\\'
    
    #利用递归获取该目录下所有文件的path并且存入列表
    def get_all_file_path(dir_path):
        for file in os.listdir(dir_path):
            #print(file)
            filepath = os.path.join(dir_path, file)
            #print(filepath)
            if os.path.isdir(filepath):
                get_all_file_path(filepath)
            else:
                file_list_path.append(filepath)
        return file_list_path
    
    #利用递归获取该目录下所有文件的名称并存入列表
    def get_file_name(dir_path):
        for file in os.listdir(dir_path):
            filepath = os.path.join(dir_path, file)
            if os.path.isdir(filepath):
                get_file_name(filepath)
            else:
                file_list.append(file)
        return file_list
    
    #获取该path目录下文件名称
    def get_file(dir_path):
        for file in os.listdir(dir_path):
            #print(file)
            filelist.append(file)
        return filelist
    
    #重写创建文件夹方法,用来检测是否重复建立文件夹
    def mkdir(path):
        import os
        # 去除首位空格
        path = path.strip()
        path = path.rstrip("\\")
        # 判断路径是否存在
        # 存在     True
        # 不存在   False
        isExists = os.path.exists(path)
        # 判断结果
        if not isExists:
            # 如果不存在则创建目录
             # 创建目录操作函数
            os.makedirs(path)
            return True
        else:
            # 如果目录存在则不创建,并提示目录已存在
            return False
    
    
    #file_list先存入数据集中所有的文件名
    file_list = get_file_name(dir_path)
    
    #file_list_path先存入数据集中所有文件的path
    file_list_path = get_all_file_path(dir_path)
    
    #filelist中存入该输入目录的文件名
    filelist = get_file(dir_path)
    
    #分别计算三个列表中元素的个数
    lenn = len(file_list)
    lenn2 = len(filelist)
    lenn3 = len(file_list_path)
    # print(lenn3)
    # print(lenn)
    # print(filelist)
    # print(file_list)
    # print(file_list_path)
    
    #cmd命令存入str字符串
    str = 'F:\\ffmpeg-20200826-8f2c1f2-win64-static\\bin\\ffmpeg.exe ' + '-i {} -r 5 -f image2 {}%05d.jpg'
    
    #创建filelist空文件夹
    for i in range(lenn2):
        mkdir(put_path+"".join(filelist[i]))
    
    '''
    循环遍历把每个文件的视频截取图片存入相应的文件夹
    外循环遍历不同动作类别,内循环遍历每个动作不同的训练数据并创建文件夹存入
    '''
    for j in range(lenn2):
        #parent存储当前动作名
        parent = "".join(filelist[j])
        #print(parent)
        #对文件名和路径列表清空
        file_list.clear()
        file_list_path.clear()
        #根据当前动作子路径查找其目录下文件名更新file_list
        file_list = get_file_name(dir_path + "\\" + parent + "\\")
        #更新file_list_path
        file_list_path = get_all_file_path(dir_path+"\\"+parent+"\\")
        #print(file_list_path)
    
        #内循环遍历该动作
        for i in range(len(file_list)):
            a = "".join(file_list[i])
            a, _ = a.split(".")
            #创建不同训练数据文件夹
            mkdir(put_path + parent + "\\" + a)
            # # print(filelist[j])
            # print(put_path+parent+"\\"+a+"\\")
            #填充ffmpeg命令的输入输出信息并运行
            str_cmd = str.format(file_list_path[i], put_path + parent + "\\" + a + "\\")
            os.popen(str_cmd)
            # print(str_cmd)
        #清空两个列表
        file_list.clear()
        file_list_path.clear()
        print("\n\n")
    
    
    展开全文
  • 使用ffmpeg从视频中截取图像

    万次阅读 2017-09-04 08:49:02
    使用ffmpeg从视频中截取图像 1.问题 从视频中抽取图像,并按照指定命名规则保存。 2. 环境 centos 6.3 + ffmpeg 0.6.5 3. 方法 1)安装ffmpeg ffmpeg 位于rpmforge中,如果你的centos没有配置rpmforge,...

    使用ffmpeg从视频中截取图像帧
    1.问题
    从视频中抽取图像帧,并按照指定命名规则保存。
    2. 环境
    centos 6.3 + ffmpeg 0.6.5
    3. 方法
    1)安装ffmpeg
    ffmpeg 位于rpmforge中,如果你的centos没有配置rpmforge,请先配置rpmforge。
    yum -y install ffmpeg
    并安装对应的依赖包。
    2)使用场景
    1. ffmpeg -i inputfile.avi -r 1 -f image2 image-%05d.jpeg
    -r 指定抽取的帧率,即从视频中每秒钟抽取图片的数量。1代表每秒抽取一帧。
    -f 指定保存图片使用的格式,可忽略。
    image-%05d.jpeg,指定文件的输出名字。
    2. ffmpeg -i inputfile.avi -r 1 -s 4cif -f image2 image-%05d.jpeg
    4cif 代表帧的尺寸为705x576.其他可用尺寸如下。
    3. ffmpeg -i inputfile.avi -r 1 -t 4 -f image2 image-%05d.jpeg
    -t 代表持续时间,单位为秒。
    4. ffmpeg -i inputfile.avi -r 1 -ss 01:30:14 -f image2 image-%05d.jpeg
    -ss 指定起始时间
    5.ffmpeg -i inputfile.avi -r 1 -ss 01:30:14 -vframes 120 4cif -f image2 image-%05d.jpeg
    -vframes 指定抽取的帧数

    展开全文
  • 使用ffmpeg截取视频

    2020-07-08 17:00:44
    ffmpeg -i pitch.mp4 -r 30 -f image2 image-%05d.jpeg -r 指定抽取的帧率,即从视频中每秒钟抽取图片的...1代表每秒抽取一。 -f 指定保存图片使用的格式,可忽略。 image-%05d.jpeg,指定文件的输出名字 ...
    ffmpeg -i pitch.mp4 -r 30 -f image2 image-%05d.jpeg
    
            -r 指定抽取的帧率,即从视频中每秒钟抽取图片的数量。1代表每秒抽取一帧。
            -f 指定保存图片使用的格式,可忽略。
            image-%05d.jpeg,指定文件的输出名字
    
    展开全文
  • ​ 由于项目的需求是要从IP摄像头的视频流隔1秒截取帧数据并解码存为jpg供分析用,第一时间就想到了ffmpeg去实现。 准备工作 ​ ubuntu16.04的系统 ​ 一台IP摄像头 安装FFMEPG sudo apt-get install ffmpeg 隔...
  • 参考:ffmpeg视频抽 下面的方法抽图片画质很差,这里提供更好的命令,提高分辨率: ffmpeg -i /data/video_...使用ffmpeg从视频中截取图像 普通的命令:(画质差) ffmpeg -i inputfile.avi -r 1 ...
  • 众所周知,视频就是由一组连续的图片和音频组成,很多需求是将视频的第一图片作为视频封面,最近做的项目是十秒截取一张图片,依旧是采用ffmpeg,不得不说ffmpeg这个工具真的很强大,唯一的缺点就是太耗费cpu...
  • ffmpeg -i test2.asf -y -f image2 -ss 08.010 -t 0.001 -s 352x240 b.jpg 那么从任意一截图的问题也就解决了.只要-ss后的时间参数是随机产生,并且在视频的有效时间内,就可以了.  另外,-ss后跟的时间单位为...
  • 输入的视频流源地址,可以是文件或者...设置帧率,也就是每秒截取图片的数量(默认25)ffmpeg.exe -i D:\test.mp4 -f image2 -an -r 1 D:\pic\%10d.jpg 这样子每1s截取1张图片还可以设置截取间隔,起止 -ss 设定时间位置
  • <br />利用FFmpeg -h > ffmpeg.txt,把FFmpeg的命令打印出来后,才发现了这一参数:...如: <br />ffmpeg -i test2.asf -y -f image2 -ss 08.010 -t 0.001 -s 352x240 b.jpg <br /> 那么从任意一
  • 客户端上传的视频没有封面,因此需要在上传视频后截取视频的某一作为封面并返回给客户端。 这样的需求在实际开发中也是很常见的。 代码如下: public class FFmpegVideo { public static final String FFMPEG_...
  • 利用FFmpeg将视频按帧截取保存为图片 ...-r: 指定抽取的 即从视频中每秒抽取的图片数量 5:代表每秒抽取5 -f:保存图片使用的格式 可省略 image-%03d.jpg :指定文件的输出名字 仅供学习参考 ...
  • ffmpeg隔几取一

    千次阅读 2020-11-04 17:27:04
    FFmpeg再合适不过了,当然它的功能是很强大的,也可以截取音频,视频格式转换等等,这里只记录一下使用ffmpeg命令从视频中取的常用参数,以及一个用python调用命令行截取视频的小脚本,我想隔几取一图像,搜索...
  • FFmpeg 视频截取

    2020-07-27 21:32:35
    6、在循环遍历输入文件的,对一个packet进行时间基的转换 7、将处理好的pkt写入输出文件 8、超过要结束的时间跳出循环 9、写入新的多媒体文件尾 10、释放相关资源 源码: #include "SeekVideo.h" #include &...
  • 前段时间写过一篇文章,介绍了FFmpeg的几个常用的命令行。最近,项目里需要做一个把视频片段...ffmpeg -ss 25 -t 10 -i D:\Media\bear.wmv -f gif D:\a.gif 意思是:将D:\Media目录下的源文件bear.wmv,从第25的...
  • FFmpeg】mac系统安装FFmpeg并对视频进行转码、对视频截图 https://blog.csdn.net/weixin_43210113/article/details/109802138 ✨java+ffmpeg使用记录 https://blog.csdn.net/u011424614/article/details/108006131...
  • 1代表每秒抽取一。  -ss 指定起始时间  -vframes 指定抽取的数  */  videoLen, _ := GenerateLength(ffmpegPath, urlpath, "1111111111")  testFfmpegParams(urlpath, path, ffmpegPath, 60, videoLen...
  • ffmpeg里获取完整一的例子

    热门讨论 2009-01-07 22:55:22
    ffmpeg里获取完整一并保存成图片的例子
  • 参考文章:... 从时间xx:xx:xx开始,截取视频中的n张图片: ffmpeg  - i test.asf  - y  - f image2  - ss xx:xx:xx   - vframes  n  test%d.jpg
  • ffmpeg视频截取切片

    2021-07-30 14:40:18
    ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 test_outpout-%d.mp4 查看分片文件的开始时间跟结束时间 开始时间: ffprobe -v quiet -show_packets -select_streams v test_outpout-0.mp4 2&...
  • ##1.导入maven依赖 org.bytedeco javacv 1.4.1 org.bytedeco javacpp ...org.bytedeco.javacpp-presets ...ffmpeg-platform 3.4.2-1.4.1 2.代码 package videoFfmpeg; import java.awt.Image; import java.awt.im

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,468
精华内容 587
关键字:

ffmpeg每秒截取10帧