0

I have a dataset of around 3000 images, of which I want to crop multiple areas of each image if I have the coordinates of the bounding boxes for their location. The only problem is my code is extremely slow, I've tried profiling and using Cython, but with marginal improvements. I'm using the Pillow library for cropping, is their perhaps a faster way of achieving this task?

The bounding box locations are stored in a CSV file. The code below iterates over every file

train_label=pd.read_csv("train.csv")
for i in range(len(train_label.index)):
    name=train_label["image_id"][i]; labels=train_label["labels"][i]; 
    split_images(name,labels)

And the function which does the heavily lifting as below.

def split_images(name, labels):
boundingboxes = np.array(labels.split(' ')).reshape(-1, 5)
for (unicode, x, y, w, h) in boundingboxes:
    try:

    # Create target Directory

        os.mkdir('unicodes/{}'.format(str(unicode)))
    except FileExistsError:
        None

    (x, y, w, h) = (int(x), int(y), int(w), int(h))
    imsource = Image.open('train_images/{}.jpg'.format(name))
    cropped_image = imsource.crop((x, y, x + w, y + h))
    cropped_image.save('unicodes/{}/{}.jpg'.format(unicode, name))

I'm running the code remotely on Google cloud platform if that helps.

Intent Filters
  • 189
  • 1
  • 15
  • Are the crop areas always the same for every image or at least always the same number of crops? With your code you are opening and closing the image multiple times, once for each crop. ImageMagick can open the image once and crop in multiple locations using parenthesis processing, if you create the command knowing all the crops ahead of time. You can also convert the image once to memory mapped format, so that it can be opened faster each time. You might also look into VIPS. It is even a faster tool, though I have no experience with it. – fmw42 Sep 16 '19 at 03:53
  • Different for each image I'm afraid. Cheers I'll check it out! – Intent Filters Sep 16 '19 at 09:26
  • Multi-threading or multiprocessing is likely to give you a significant speedup if you have a decent CPU... something similar to get you started here https://stackoverflow.com/a/51822265/2836621 – Mark Setchell Sep 17 '19 at 09:46

2 Answers2

1

How large are the images?

It might be worth investigating some of the other library suggestions on the pillow performance page especially with regards to efficiency.

ImageMagick was my first thought. It is quite powerful and can be run from command line (see docs). Batch cropping a whole folder is straightforward, although I'm not sure how to crop each image in a different area (there must be a way). A hacky but workable solution could be to use python to process the csv and generate ImageMagic calls.

Apologies for not having a clear solution.

vopsea
  • 85
  • 7
1

You are decoding the whole image for each crop position. Try changing your loop to be:

def split_images(name, labels):
    imsource = Image.open('train_images/{}.jpg'.format(name))
    boundingboxes = np.array(labels.split(' ')).reshape(-1, 5)

    for (unicode, x, y, w, h) in boundingboxes:
        try:
            # Create target Directory
            os.mkdir('unicodes/{}'.format(str(unicode)))
        except FileExistsError:
            None

        (x, y, w, h) = (int(x), int(y), int(w), int(h))
        cropped_image = imsource.crop((x, y, x + w, y + h))
        cropped_image.save('unicodes/{}/{}.jpg'.format(unicode, name))

You have the mkdir inside the loop too, it might help a bit to move that out, if you can. Maybe make one pass over labels first to get an array of unique directory names that need creating.

You could try in other image processing libraries, but crop and save is extremely simple, so I doubt if it would make much difference.

I guess this is training a network, is that right? You can get a very big speedup by sending the cropped patches directly to pytorch or whatever you are using rather than going via JPG files. You could consider generating the set of rotates and flips at the same time.

jcupitt
  • 10,213
  • 2
  • 23
  • 39