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 · 143 天前 · 3000 次点击
    这是一个创建于 143 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    这最基础的访问限制都闹不明白吗!!!
    sweelia
        9
    sweelia  
       143 天前   ❤️ 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
       143 天前
    @somebody1 啥?我觉得你可能没理解,我这里指我的服务器收到了视频文件,然后我不希望把他写入到硬盘中,而是直接将传输过来的文件进行视频帧提取。
    stebest
        11
    stebest  
       143 天前
    额,这个倒是跟视频没啥关系,不就是想用 bytesIO 嘛。
    somebody1
        12
    somebody1  
       143 天前   ❤️ 1
    @Haku #10
    那就是 11 楼的答案了
    mightybruce
        13
    mightybruce  
       143 天前
    看起来你对 opencv 和 ffmpeg 都不熟悉,先说说你传来的视频流是什么协议吧
    stebest
        14
    stebest  
       143 天前   ❤️ 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  
       143 天前   ❤️ 2
    直接读完整的视频就是 bytesIO 二进制流了, 转换一下变成 numpy array 就行
    Haku
        16
    Haku  
    OP
       143 天前
    @stebest 好的谢谢,我试试。
    Haku
        17
    Haku  
    OP
       143 天前
    @stebest @mightybruce
    谢谢两位,可能是我一开始哪里搞错了,我用 cv2 和 ffmpeg 去进行提取时,都报错了,网上查询也没找到一个很好的方法。
    NessajCN
        18
    NessajCN  
       143 天前
    Python 这语言本来就不适用于直接对内存进行操作
    建议换 C 或 Rust
    https://ffmpeg.org/doxygen/6.1/group__lavc__decoding.html#ga11e6542c4e66d3028668788a1a74217c
    你可能想要这个方法
    cooljiang
        19
    cooljiang  
       143 天前   ❤️ 1
    @stebest 确实这个是正解,创建一个 io 字节流。
    Anarchy
        20
    Anarchy  
       143 天前
    用 pyav 很简单的
    Anarchy
        21
    Anarchy  
       143 天前   ❤️ 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
       143 天前
    @stebest
    请问下,cv2 读取视频我只找到了 VideoCapture ,但是 VideoCapture 却只能读文件了。
    还是说能用读图片的方式,用 imdecode 等从视频二进制流里面直接读取到图片吗?
    Haku
        23
    Haku  
    OP
       143 天前
    @Anarchy pyav 我知道,不过我这里环境特殊,装不上这个包所以暂时没考虑。
    sweelia
        24
    sweelia  
       143 天前   ❤️ 1
    @Haku https://github.com/opencv/opencv/pull/25584 不知多久才能合入主线,有条件还是建议走 ffmpeg 的路子,控制更精细,遇到问题也有解决方案。按经验,有些推流器封装不太标准,需要野路子 hack 兼容。
    stebest
        25
    stebest  
       143 天前
    @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  
       143 天前
    不过实际上还是会在本地创建文件,video capture 本身不支持直接从 memory 中读取,如果是自己控制数据流,用一个迭代器或者生成器把每一帧转 cv mat 就可以
    stebest
        27
    stebest  
       143 天前   ❤️ 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 client
    client.close()
    ```
    vituralfuture
        28
    vituralfuture  
       143 天前 via Android   ❤️ 1
    楼上 bytesio 是一个方法,直接放到 tmpfs 里也行,tmpfs 的 backend 就是内存
    ClericPy
        29
    ClericPy  
       143 天前   ❤️ 1
    tmpfs 比较简单,也就是常见的 /dev/shm
    SP00F
        30
    SP00F  
       143 天前
    走内存,在并发大文件的情况下。。怎么解决内存占用的问题……

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

    VideoCapture 本身支持使用 HTTP 协议的地址作为视频文件路径,如果没有特殊的请求限制,直接让 VideoCapture 自己处理流,然后直接遍历帧、或者设置 cv2.CAP_PROP_POS_FRAMES 或者 cv2.CAP_PROP_POS_MSEC 读指定帧都是可以的。不知道这种方式是不是符合需求。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1072 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 19:04 · PVG 03:04 · LAX 11:04 · JFK 14:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.