Изменить размер изображения быстрее в OpenCV Python - PullRequest
0 голосов
/ 04 ноября 2018

У меня много файлов изображений в папке (5M +). Эти изображения имеют разные размеры. Я хочу изменить размеры этих изображений до 128x128.

Я использовал следующую функцию в цикле для изменения размера в Python с использованием OpenCV

def read_image(img_path):
    # print(img_path)
    img = cv2.imread(img_path)
    img = cv2.resize(img, (128, 128))
    return img

for file in tqdm(glob.glob('train-images//*.jpg')):
    img = read_image(file)
    img = cv2.imwrite(file, img)

Но это займет более 7 часов. Мне было интересно, есть ли способ ускорить этот процесс.

Могу ли я реализовать параллельную обработку, чтобы сделать это эффективно с dask или чем-то еще.? Если это так, как это возможно .?

Ответы [ 2 ]

0 голосов
/ 04 ноября 2018

Если вы абсолютно намерены сделать это на Python, тогда, пожалуйста, просто проигнорируйте мой ответ. Если вы заинтересованы в том, чтобы сделать работу просто и быстро, читайте дальше ...

Я бы предложил GNU Parallel , если у вас есть много вещей, которые нужно сделать параллельно, и даже больше, так как процессоры становятся "жирнее" с большим количеством ядер, а не " выше " с более высокими тактовыми частотами (ГГц).

В простейшем случае вы можете использовать ImageMagick просто из командной строки в Linux, macOS и Windows, например, для изменения размера группы изображений:

magick mogrify -resize 128x128\! *.jpg

Если у вас есть сотни изображений, вам лучше запустить параллельное, что будет:

parallel magick mogrify -resize 128x128\! ::: *.jpg

Если у вас есть миллионы изображений, расширение *.jpg переполнит командный буфер вашей оболочки, так что вы можете использовать следующее для подачи имен изображений в stdin вместо передачи их в качестве параметров:

find -iname \*.jpg -print0 | parallel -0 -X --eta magick mogrify -resize 128x128\!

Здесь есть две "хитрости" здесь:

  • Я использую find ... -print0 вместе с parallel -0 для имен файлов с нулевым символом в конце, поэтому в них нет проблем с пробелами,

  • Я использую parallel -X, что означает, вместо того, чтобы запускать совершенно новый mogrify процесс для каждого изображения, GNU Parallel определяет, сколько имен файлов mogrify может принять, и дает его столько в партиях.

Я рекомендую вам оба инструмента.


В то время как аспекты ImageMagick вышеприведенного ответа работают в Windows, я не использую Windows и не уверен в том, что GNU Parallel там. Я думаю это может работать под git-bash и / или возможно под Cygwin - вы можете попробовать задать отдельный вопрос - они бесплатны!

Что касается части ImageMagick, я думаю, что вы можете получить список всех имен файлов JPEG в файле, используя эту команду:

DIR /S /B *.JPG > filenames.txt

Затем вы можете возможно обработать их (не параллельно) следующим образом:

magick mogrify -resize 128x128\! @filenames.txt

И если вы узнаете, как запустить GNU Parallel в Windows, вы можете , вероятно, обработать их параллельно, используя что-то вроде этого:

parallel --eta -a filenames.txt magick mogrify -resize 128x128\!
0 голосов
/ 04 ноября 2018

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

В противном случае вы всегда можете выбросить проблему в пул обработки, чтобы использовать несколько ядер:

from multiprocessing.dummy import Pool
from multiprocessing.sharedctypes import Value
from ctypes import c_int
import time, cv2, os

wdir = r'C:\folder full of large images'
os.chdir(wdir)

def read_imagecv2(img_path, counter):
    # print(img_path)
    img = cv2.imread(img_path)
    img = cv2.resize(img, (128, 128))
    cv2.imwrite('resized_'+img_path, img) #write the image in the child process (I didn't want to overwrite my images)
    with counter.get_lock(): #processing pools give no way to check up on progress, so we make our own
        counter.value += 1

if __name__ == '__main__':
    # start 4 worker processes
    with Pool(processes=4) as pool: #this should be the same as your processor cores (or less)
        counter = Value(c_int, 0) #using sharedctypes with mp.dummy isn't needed anymore, but we already wrote the code once...
        chunksize = 4 #making this larger might improve speed (less important the longer a single function call takes)
        result = pool.starmap_async(read_imagecv2, #function to send to the worker pool
                                    ((file, counter) for file in os.listdir(os.getcwd()) if file.endswith('.jpg')),  #generator to fill in function args
                                    chunksize) #how many jobs to submit to each worker at once
        while not result.ready(): #print out progress to indicate program is still working.
            #with counter.get_lock(): #you could lock here but you're not modifying the value, so nothing bad will happen if a write occurs simultaneously
            #just don't `time.sleep()` while you're holding the lock
            print("\rcompleted {} images   ".format(counter.value), end='')
            time.sleep(.5)
        print('\nCompleted all images')

Из-за известной проблемы с cv2, которая не очень хорошо работает с многопроцессорностью, мы можем использовать потоки вместо процессов, заменив multiprocessing.Pool на multiprocessing.dummy.Pool. В любом случае многие функции openCV выпускают GIL, поэтому мы все равно должны увидеть вычислительные преимущества использования нескольких ядер одновременно. Кроме того, это уменьшает некоторые накладные расходы, поскольку потоки не так тяжелы, как процессы. После некоторых исследований я не нашел библиотеку изображений, которая бы хорошо играла с процессами. Кажется, что все они терпят неудачу при попытке выбора функции для отправки дочерним процессам (как элементы работы отправляются дочерним процессам для вычисления).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...