Асинхронно читать и обрабатывать изображение в Python

контекст

Я часто оказывался в следующей ситуации:

  • У меня есть список имен файлов изображений, которые мне нужно обработать
  • Я читаю каждое изображение последовательно, используя, например, scipy.misc.imread
  • Затем я делаю какую-то обработку каждого изображения и возвращаю результат
  • Я сохраняю результат по имени файла изображения в полку

Проблема в том, что простое считывание изображения занимает немалое количество времени, иногда сопоставимое или даже больше, чем обработка изображения.

Вопрос

Поэтому я подумал, что в идеале я мог бы прочитать изображение n + 1 при обработке изображения n. Или даже лучше обрабатывать и считывать несколько изображений одновременно автоматически определенным оптимальным способом?

Я читал о многопроцессорности, потоках, витой, Gevent и тому подобное, но я не могу понять, какой из них использовать и как реализовать эту идею. У кого-нибудь есть решение этой проблемы?

Минимальный пример

# generate a list of images
scipy.misc.imsave("lena.png", scipy.misc.lena())
files = ['lena.png'] * 100

# a simple image processing task
def process_image(im, threshold=128):
    label, n = scipy.ndimage.label(im > threshold)
    return n

# my current main loop
for f in files:
    im = scipy.misc.imread(f)
    print process_image(im)

2 ответа

Решение

Ответ Филиппа хорош, но он создаст только пару процессов (одно чтение, одно вычисление), которые вряд ли превзойдут современную>2-ядерную систему. Вот альтернативное использование multiprocessing.Pool (в частности, его метод map), который создает процессы, которые выполняют как чтение, так и вычисления, но которые должны лучше использовать все имеющиеся у вас ядра (при условии, что файлов больше, чем ядер).

#!/usr/bin/env python

import multiprocessing
import scipy
import scipy.misc
import scipy.ndimage

class Processor:
    def __init__(self,threshold):
        self._threshold=threshold

    def __call__(self,filename):
        im = scipy.misc.imread(filename)
        label,n = scipy.ndimage.label(im > self._threshold)
        return n

def main():
    scipy.misc.imsave("lena.png", scipy.misc.lena())
    files = ['lena.png'] * 100

    proc=Processor(128)
    pool=multiprocessing.Pool()
    results=pool.map(proc,files)

    print results

if __name__ == "__main__":
    main()

Если я увеличу количество изображений до 500 и использую processes=N аргумент Pool тогда я получаю

Processes   Runtime
   1         6.2s
   2         3.2s
   4         1.8s
   8         1.5s

на моем четырехъядерном гиперпоточном i7.

Если вы попали в более реалистичные сценарии использования (то есть фактически разные изображения), ваши процессы могут тратить больше времени на ожидание загрузки данных из образа из хранилища (в моем тестировании они загружаются практически мгновенно с кэшированного диска), а затем это может быть Стоит явно создать больше процессов, чем ядер, чтобы получить большее совпадение вычислений и нагрузки. Только ваши собственные тесты на масштабируемость на реалистичной нагрузке и HW могут сказать вам, что на самом деле лучше для вас.

Многопроцессорный пакет довольно прост в использовании. Посмотрите на пример очереди для руководства. Вы будете следовать за моделью потребителя производителя. Вы хотите, чтобы один (или более) производитель обрабатывал изображения для чтения, а один (или более) потребительские процессы выполняли обработку изображений.

Ваш пример будет выглядеть примерно так:

from multiprocessing import Process, Queue
import scipy

def process_images(q):
    while not q.empty:
        im = q.get()
        # Do stuff

def read_images(q, files):
    for f in files:
        q.put(scipy.misc.imread(f))

if __name__ == '__main__':
    q = Queue()

    producer = Process(target=read_images, args=(q, files))
    producer.start()

    consumer = Process(target=process_images, args=(q))
    consumer.start()

Это немного проще, чем ваша оригинальная идея. В этом примере производитель добавляет в очередь как можно быстрее, а не просто опережает потребителя. Это может быть проблемой, если продюсер продвинется настолько далеко, что вам не хватит памяти для хранения очереди. Если возникают проблемы, вы можете углубиться в многопроцессорную документацию, но этого должно быть достаточно для начала работы.

Другие вопросы по тегам