Python 中如何在内存中优雅地提取视频帧? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
Haku
V2EX    Python

Python 中如何在内存中优雅地提取视频帧?

  •  
  •   Haku 2024-08-02 10:01:59 +08:00 3688 次点击
    这是一个创建于 434 天前的主题,其中的信息可能已经有所发展或是发生改变。

    通过网络传输过来的视频,如何在不保存为文件的情况下,用工具提取视频帧呢?

    一开始以为很容易,但是找了很久发现 Opencv 和 ffmpeg 都没有直接从内存中提取的方法,都需要通过一个临时文件才能提取。而目目前需求上需要优化处理视频的时间,所以想着修改这个步骤。

    32 条回复    2024-08-08 08:51:26 +08:00
    Doraismydora
        1
    Doraismydora  
       2024-08-02 10:05:17 +08:00
    sws_sacle 把 AVFrame 转成 rgb32 然后弄一个 cv::Mat 复制过去
    qsnow6
        2
    qsnow6  
       2024-08-02 10:08:35 +08:00
    用虚拟文件交换就行了
    Latin
        3
    Latin  
       2024-08-02 10:14:13 +08:00
    网络传输中原先的格式是什么链路
    Haku
        4
    Haku  
    OP
       2024-08-02 10:19:31 +08:00
    @Latin 以 HTTP 的方式将整段视频以二进制字节流完整的传输过来。
    SenseHu
        5
    SenseHu  
       2024-08-02 10:19:32 +08:00
    场景没描述清, 提取之后是被谁消费, 落盘? 其他程序用? 其他程序是什么框架
    Haku
        6
    Haku  
    OP
       2024-08-02 10:21:27 +08:00
    @SenseHu 提取之后会由 AI 进行处理,所以还会被转换成对应的图片格式保存在内存中。整个过程都在 Python 上处理。
    Huelse
        7
    Huelse  
       2024-08-02 10:23:18 +08:00
    直接用内存映射不就行了,然后就是正常的 IO 操作
    somebody1
        8
    somebody1  
       2024-08-02 10:24:34 +08:00
    你能访问视频程序的内存内容,那别的程序不一样能访问,那你的内存不就成了透明的嘛!

    这最基础的访问限制都闹不明白吗!!!
    sweelia
        9
    sweelia  
       2024-08-02 10:27:20 +08:00   1
    @Haku 可以参考 https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/avio_read_callback.c ,主要是 read_packet 函数,buffer 的装载逻辑( av_file_map )换成你自己的,可以流式加载,不用一次性读完。如果对 c 不熟悉,可以找个 python binding 来使用。decode 之后拿到 AVFrame ,随便玩
    Haku
        10
    Haku  
    OP
       2024-08-02 10:27:40 +08:00
    @somebody1 啥?我觉得你可能没理解,我这里指我的服务器收到了视频文件,然后我不希望把他写入到硬盘中,而是直接将传输过来的文件进行视频帧提取。
    stebest
        11
    stebest  
       2024-08-02 10:31:08 +08:00
    额,这个倒是跟视频没啥关系,不就是想用 bytesIO 嘛。
    somebody1
        12
    somebody1  
       2024-08-02 10:32:22 +08:00   1
    @Haku #10
    那就是 11 楼的答案了
    mightybruce
        13
    mightybruce  
       2024-08-02 10:33:18 +08:00
    看起来你对 opencv 和 ffmpeg 都不熟悉,先说说你传来的视频流是什么协议吧
    stebest
        14
    stebest  
       2024-08-02 10:34:03 +08:00   1
    @stebest 挺简单的,给个示例吧 import numpy as np
    import cv2 as cv
    import io

    image_stream = io.BytesIO()
    image_stream.write(connection.read(image_len))
    image_stream.seek(0)
    file_bytes = np.asarray(bytearray(image_stream.read()), dtype=np.uint8)
    img = cv.imdecode(file_bytes, cv.IMREAD_COLOR)
    mightybruce
        15
    mightybruce  
       2024-08-02 10:34:45 +08:00   2
    直接读完整的视频就是 bytesIO 二进制流了, 转换一下变成 numpy array 就行
    Haku
        16
    Haku  
    OP
       2024-08-02 10:35:08 +08:00
    @stebest 好的谢谢,我试试。
    Haku
        17
    Haku  
    OP
       2024-08-02 10:39:42 +08:00
    @stebest @mightybruce
    谢谢两位,可能是我一开始哪里搞错了,我用 cv2 和 ffmpeg 去进行提取时,都报错了,网上查询也没找到一个很好的方法。
    NessajCN
        18
    NessajCN  
       2024-08-02 10:40:55 +08:00
    Python 这语言本来就不适用于直接对内存进行操作
    建议换 C 或 Rust
    https://ffmpeg.org/doxygen/6.1/group__lavc__decoding.html#ga11e6542c4e66d3028668788a1a74217c
    你可能想要这个方法
    cooljiang
        19
    cooljiang  
       2024-08-02 10:46:53 +08:00   1
    @stebest 确实这个是正解,创建一个 io 字节流。
    Anarchy
        20
    Anarchy  
       2024-08-02 10:49:45 +08:00
    用 pyav 很简单的
    Anarchy
        21
    Anarchy  
       2024-08-02 10:54:00 +08:00   1
    @Anarchy 给个例子:
    with av.open(url, optiOns=options) as container:
    video = container.streams.video[0]
    for i,t in enumerate([10,15,20,25,30]):
    container.seek(int(t/video.time_base)+video.start_time, backward=True, stream=video)
    frame = next(container.decode(video))
    frame.to_image().save(target_folder/f'frame{i}.png')
    Haku
        22
    Haku  
    OP
       2024-08-02 11:19:47 +08:00
    @stebest
    请问,cv2 读取视频我只找到了 VideoCapture ,但是 VideoCapture 却只能读文件了。
    还是说能用读图片的方式,用 imdecode 等从视频二进制流里面直接读取到图片吗?
    Haku
        23
    Haku  
    OP
       2024-08-02 11:29:09 +08:00
    @Anarchy pyav 我知道,不过我这里环境特殊,装不上这个包所以暂时没考虑。
    sweelia
        24
    sweelia  
       2024-08-02 12:16:02 +08:00   1
    @Haku https://github.com/opencv/opencv/pull/25584 不知多久才能合入主线,有条件还是建议走 ffmpeg 的路子,控制更精细,遇到问题也有解决方案。按经验,有些推流器封装不太标准,需要野路子 hack 兼容。
    stebest
        25
    stebest  
       2024-08-02 13:51:33 +08:00
    @Haku 如果只是希望在内存中方便读取,使用 tmpfile import tempfile
    import cv2

    my_video_bytes = download_video_in_memory()

    with tempfile.NamedTemporaryFile() as temp:
    temp.write(my_video_bytes)

    video_stream = cv2.VideoCapture(temp.name)

    # do your stuff.
    stebest
        26
    stebest  
       2024-08-02 13:55:08 +08:00
    不过实际上还是会在本地创建文件,video capture 本身不支持直接从 memory 中读取,如果是自己控制数据流,用一个迭代器或者生成器把每一帧转 cv mat 就可以
    stebest
        27
    stebest  
       2024-08-02 14:05:10 +08:00   1
    如果还是希望用 opencv video capture 处理,1.可以将视频流使用虚拟摄像头输出,用 opencv 打开即可。
    2.使用流媒体协议,opencv video capture 应该也直接支持。
    3.使用 videogear 的 netgear ,一个比较简单的例子,具体可以去看文档
    server end

    ```
    # import required libraries
    from vidgear.gears import VideoGear
    from vidgear.gears import NetGear

    # open any valid video stream(for e.g `test.mp4` file)
    stream = VideoGear(source="test.mp4").start()

    # Define Netgear Server with default parameters
    server = NetGear()

    # loop over until KeyBoard Interrupted
    while True:

    try:

    # read frames from stream
    frame = stream.read()

    # check for frame if Nonetype
    if frame is None:
    break

    # {do something with the frame here}

    # send frame to server
    server.send(frame)

    except KeyboardInterrupt:
    break

    # safely close video stream
    stream.stop()

    # safely close server
    server.close()
    ```

    client end

    ```
    # import required libraries
    from vidgear.gears import NetGear
    import cv2


    # define Netgear Client with `receive_mode = True` and default parameter
    client = NetGear(receive_mode=True)

    # loop over
    while True:

    # receive frames from network
    frame = client.recv()

    # check for received frame if Nonetype
    if frame is None:
    break

    # {do something with the frame here}

    # Show output window
    cv2.imshow("Output Frame", frame)

    # check for 'q' key if pressed
    key = cv2.waitKey(1) & 0xFF
    if key == ord("q"):
    break

    # close output window
    cv2.destroyAllWindows()

    # safely close cliet
    client.close()
    ```
    vituralfuture
        28
    vituralfuture  
       2024-08-02 14:59:42 +08:00 via Android   1
    楼上 bytesio 是一个方法,直接放到 tmpfs 里也行,tmpfs 的 backend 就是内存
    ClericPy
        29
    ClericPy  
       2024-08-02 19:18:45 +08:00   1
    tmpfs 比较简单,也就是常见的 /dev/shm
    SP00F
        30
    SP00F  
       2024-08-02 23:30:31 +08:00
    走内存,在并发大文件的情况下。。怎么解决内存占用的问题……

    走内存例如 golang 中的 gin 上传文件一样,在读取文件都是 io ,调用 ffmpeg 或者 openvc 读取文件最后不都是 io 吗(个人拙见)
    Lihanx9
        32
    Lihanx9  
       2024-08-08 08:51:26 +08:00
    感觉没有那么复杂吧...

    VideoCapture 本身支持使用 HTTP 协议的地址作为视频文件路径,如果没有特殊的请求限制,直接让 VideoCapture 自己处理流,然后直接遍历帧、或者设置 cv2.CAP_PROP_POS_FRAMES 或者 cv2.CAP_PROP_POS_MSEC 读指定帧都是可以的。不知道这种方式是不是符合需求。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     884 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 20:45 PVG 04:45 LAX 13:45 JFK 16:45
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86