我正在 Python 中进行一些fast图像处理(使用 Numpy/Scipy + OpenCV)。 有几千张形状完全相同的图像 - 一旦我读到第一张图像,我就确切地知道所有其他图像会是什么样子。
问题是从磁盘读取每个下一个图像会导致分配新内存(这很慢)。有没有办法通过将每个下一个图像直接读取到已经存在的内存中来避免这种情况(ndarray
)?我知道cv2.imdecode 在 C++ 中可以接受指向预分配 Mat 的指针,但它似乎没有 Python 绑定(唯一的选择是返回一个全新的数组)。 我需要这个来进行多重处理 - 我想将图像读入共享内存,然后在工作进程中对它们进行一些繁重的工作。现在,我被迫将
cv2.imread 分配和返回的数组中的数据复制到共享内存中,这又需要时间。我希望能够直接在那里写信。
import argparse
from contextlib import contextmanager
from multiprocessing import JoinableQueue, Process
from multiprocessing.shared_memory import SharedMemory
from pathlib import Path
import cv2
import numpy as np
@contextmanager
def video_capture_context(videopath: str):
video_capture = cv2.VideoCapture(videopath, cv2.CAP_ANY)
try:
yield video_capture
finally:
video_capture.release()
def _vid_reader(
videopath: Path,
shared_memory_name: str,
output_queue: JoinableQueue,
):
with video_capture_context(str(videopath)) as video_capture:
fps = float(video_capture.get(cv2.CAP_PROP_FPS))
x = cv2.CAP_PROP_FRAME_WIDTH
y = cv2.CAP_PROP_FRAME_HEIGHT
frame_no = 0
frame_time = 0.0
shared_memory = None
shared_array = None
sucess = True
while sucess:
# first frame is read without shared memory
sucess, frame = video_capture.read(shared_array)
if sucess:
if frame is not None and shared_memory is None:
# create the shared memory _after_ reading the first frame
# so we know how much shared memory we need
shared_memory = SharedMemory(name=shared_memory_name, create=True, size=frame.nbytes)
shared_array = np.ndarray(frame.shape, dtype=frame.dtype, buffer=shared_memory.buf)
shared_array[:] = frame[:]
output_queue.put((frame_no, frame_time, frame.shape, frame.dtype))
# wait for the queue to be read from
output_queue.join()
# move on to next frame
frame_no += 1
frame_time = frame_no / fps
# put the sentinel into the queue
output_queue.put(None)
# wait for the sentinel to be read
# otherwise shared memory of the last frame will close too soon
output_queue.join()
shared_memory.close()
shared_memory.unlink()
def _vid_saver(
shared_memory_name: str,
output_queue: JoinableQueue,
):
shared_memory = None
shared_array = None
# get things in the queue until the sentinel
while item := output_queue.get():
frame_no, frame_time, shape, dtype = item
if not shared_memory:
shared_memory = SharedMemory(name=shared_memory_name, create=False)
shared_array = np.ndarray(shape, dtype=dtype, buffer=shared_memory.buf)
print(frame_no)
print(shared_memory.buf)
print(shared_array.sum())
output_queue.task_done()
shared_memory.close()
# say we've processed the sentinel
output_queue.task_done()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("filename")
args = parser.parse_args()
queue_reader = JoinableQueue(1)
p_reader = Process(
target=_vid_reader,
args=(Path(args.filename), "frame", queue_reader),
)
p_printer = Process(target=_vid_saver, args=("frame", queue_reader))
p_reader.start()
p_printer.start()
p_printer.join()
p_reader.join()
我认为没有 cv2.imread()
的等效项,但也许可以将图像预处理为视频格式,以便在上面的代码中使用。注意:因为只有单个图像大小的共享数组,所以没有任何方法可以同时在两个进程中操作图像。可以在少量共享内存实例之间切换是可以进行的进一步改进。
height, width = (50, 50)
image = np.zeros((height, width))
id(image)
# outputs: 140411457307552
image[:, :] = np.ones((height, width))
id(image)
# outputs: 140411457307552
image = np.ones((height, width))
id(image)
# outputs -> 140411437723280
# when reading from disk (assuming your images are 50x50 pixels)
image[:, :] = cv2.imread("/home/.../your_im_50x50.png")
通过解决每个图像的尺寸,Python 将尝试将给定的数组存储到现有的数组中。这会导致内存分配到预分配的内存区域。如果数组没有相同的形状,则会引发 ValueError。 当仅提及变量名时,会创建对数组的新引用,从而在内存中产生一个新对象(cf ids)