Асинхронно считывает и обрабатывает изображение в 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)
Ответы
Ответ 1
Ответ Филиппа - это хорошо, но создаст только пару процессов (одно чтение, одно вычисление), которое вряд ли позволит создать современную систему с двумя ядрами. Здесь используется альтернатива multiprocessing.Pool
(в частности, ее метод карты), который создает процессы, которые выполняют как аспекты чтения, так и вычисления, но которые должны лучше использовать все доступные вам ядра (при условии, что больше файлов, чем ядер).
#!/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 может рассказать вам, что действительно лучше для вас.
Ответ 2
Многопроцессорный пакет довольно прост в использовании. Посмотрите на пример очереди для руководства. Вы будете следовать за моделью потребителя производителя. Требуется, чтобы один (или более) производитель обрабатывал изображения для чтения, а один (или более) потребительские процессы выполняли обработку изображений.
Ваш пример будет выглядеть примерно так:
from multiprocessing import Process, Queue
import scipy
def process_images(q):
while not q.empty():
im = q.get()
# Do stuff with item from queue
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()
Это немного проще, чем ваша оригинальная идея. В этом примере производитель добавляет в очередь как можно быстрее, а не просто опережает потребителя. Это может стать проблемой, если продюсер продвинется настолько далеко, что вам не хватит памяти для хранения очереди. Если возникают проблемы, вы можете углубиться в многопроцессорную документацию, но этого должно быть достаточно для начала работы.