Асинхронно читать и обрабатывать изображение в 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()
Это немного проще, чем ваша оригинальная идея. В этом примере производитель добавляет в очередь как можно быстрее, а не просто опережает потребителя. Это может быть проблемой, если продюсер продвинется настолько далеко, что вам не хватит памяти для хранения очереди. Если возникают проблемы, вы можете углубиться в многопроцессорную документацию, но этого должно быть достаточно для начала работы.