Python中的命名内存映射文件?

3
我正在使用OpenCV处理Web服务中的一些视频数据。在调用OpenCV之前,视频已经被加载到一个bytearray缓冲区中,我希望将其传递给VideoCapture对象:
# The following raises cv2.error because it can't convert '_io.BytesIO' to 'str' for 'filename'
cap = cv2.VideoCapture(buffer) 

很遗憾,VideoCapture()期望一个字符串文件名,而不是缓冲区。目前,我将bytearray保存到临时文件中,并将其名称传递给VideoCapture()

问题:

  • 有没有一种方法可以在Python中创建具有命名的内存文件,以便我可以平息OpenCV?
  • 或者,是否有另一个支持缓冲区的OpenCV API?
4个回答

4
注意:仅适用于POSIX!由于您没有提供操作系统标签,我假设这是可以的。
根据这个答案(以及这个shm_overview手册),系统上始终存在/dev/shm。那是一个tmpfs映射在共享(不是Python进程内存)内存池中,正如这里所建议的,但好处是您不需要创建它,因此无需有趣地发明以下内容:
  • os.system("mount ...")
  • Popen(["mount", ...])包装器。
只需像这样使用tempfile.NamedTemporaryFile()
from tempfile import NamedTemporaryFile
with NamedTemporaryFile(dir="/dev/shm") as file:
    print(file.name)
    # /dev/shm/tmp2m86e0e0

你可以将其输入到OpenCV的API包装器中。或者,作为更全面的设备/FS封装器,可以使用pyfilesystem

multiprocessing.heap.Arena也在使用它,所以如果它不起作用,那么会有更多麻烦。 对于Windows,请检查此实现,它使用了winapi

关于/dev/shm的大小:

根据sudo ipcs,如果您不使用套接字、管道或磁盘,则这很可能是您希望在进程间共享东西的方式。

由于它是POSIX,因此应该可以在符合POSIX的系统上工作,因此也可以在MacOS上(不行)或Solaris上运行,但我没有任何手段来尝试。


1
使用/dev/shm是个好主意。我没有提出它,只是因为你(据我所知)不能说它有多大,这可能很重要。我宁愿自己创建一个tmpfs并且知道我不会破坏系统。但是,你的情况可能有所不同。 - 2e0byo
@2e0byo 说得好。如果我找到内核源代码中的某些常量或设置并编辑答案,我会尝试挖掘一下。目前我只添加了linuxquestions.org的参考。 - Peter Badida
有趣的是,我的系统上运行 cat /proc/mounts | grep tmpfs 命令的输出表明 /dev/shm 没有被挂载选项限制(但可能会被其他东西限制),而 /tmp 被限制了,而 /run/user/1000 更受限制:tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0 tmpfs /tmp tmpfs rw,nosuid,nodev,nr_inodes=409600,inode64 0 0 tmpfs /run/user/1000 tmpfs rw,nosuid,nodev,relatime,size=376112k,nr_inodes=94028,mode=700,uid=1000,gid=1000,inode64 0 0 - 2e0byo
1
shm.h 似乎包含最大大小常量,所以我认为它是有限制的。 - 2e0byo
@2e0byo 我认为必须是有限制的。否则,如果我只是执行 cat /dev/urandom > /dev/shm/stuff 或类似的操作,内核本身就会因为没有内存而挂起。虽然很可能是特定于发行版/家族或者代码本身的限制,同时在 tmpfs 挂载时留下了“无限制”的区域。或者也许它从内核中的一个大内存池开始,为所有 tmpfs 实例分配一部分,例如 /dev/shm/tmp 等等。 - Peter Badida

2
部分回答问题:我不知道在Python中有没有办法创建指向内存的命名文件对象:这是操作系统要做的事情。在大多数现代Unix系统中,有一种非常简单的方法来做类似于创建命名内存映射文件的事情:将文件保存到/tmp目录下。这些天,/tmp目录几乎总是一个ramdisk。但当然它可能是zram(基本上是一个压缩的ramdisk),你可能想先检查一下。无论如何,这比磁盘抖动或依赖操作系统缓存要好得多。
顺便说一句,制作专用的ramdisk也很容易,只需要使用“mount -t tmpfs -o size=1G tmpfs /path/to/tmpfs”或类似的命令使用“ramfs”。
调查后发现,使用替代API的成功率不高,因为文件名的使用一直延伸到cap.cpp,例如:
VideoCapture::VideoCapture(const String& filename, int apiPreference) : throwOnFail(false)
{
    CV_TRACE_FUNCTION();
    open(filename, apiPreference);
}

看起来 Python 绑定只是在这之上的一个薄层。但我愿意被证明是错误的!

参考文献

https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap.cpp#L72


2
如果VideoCapture是一个普通的Python对象,并且除了路径之外还接受“类文件对象”,那么你可以将“类文件对象”传递给它,它就可以从中读取。
Python的StringIO和BytesIO是内存中的“类文件对象”。这是值得记住的有用信息 ;)
OpenCV专门需要一个文件系统路径,因此这是不可能的。
OpenCV是一个计算机视觉库,而不是处理视频文件的库。
你应该看一下PyAV。它是ffmpeg库的一个(正确的!)封装器。你可以直接将数据输入其中,它会进行解码。这里有一些例子和这里有一些演示更多功能的测试。它的文档很少,因为大多数用法已经被ffmpeg本身记录了。

感谢您推荐PyAV。我正在使用OpenCV,因为我们可能需要视觉方面的支持。 - bavaza

1
您也许可以使用命名管道。您可以使用os.mkfifo创建一个命名管道,然后使用multiprocess模块来生成一个后台进程将视频文件输入其中。请注意,mkfifo不支持Windows系统。
最重要的限制是管道不支持寻址,因此您的视频无法进行快进或者倒带。同时,其是否实际可行可能取决于视频格式以及OpenCV使用的后端(gstreamer、v4l2等)。

从文件对象/文件系统的角度来看,它看起来像是一辆坏掉的自行车。不过,如果只需要一个简单的读取一次的缓冲区,那么这就是管道的一个目的(至少之一)。+1,我肯定会在未来使用它,即使是这样。有点像套接字的行为。 - Peter Badida
1
确实。至于“坏自行车”比喻:在Unix中,文件系统上并不是所有东西都必须是“常规文件”。例如,/dev/stdin和/dev/stdout也是流,大多数与硬件通信的设备节点也是流。但某些库确实会假定您传递给它的文件是可寻址的。 - Thomas
是的,这就是为什么要用隐喻。它是软件任何两个端点之间快速缓冲的一种非常好的方式,但除此之外,它并不是真正有用的(与内存文件或共享内存相比)。它可能是最便宜的操作系统特定形式,因此在某些情况下甚至可能比/dev/shm更受欢迎。所以就像没有刹车或没有链条的自行车一样,你仍然可以滑下山坡,它仍然可以正常工作。只是其他东西不行。:D - Peter Badida

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接