4

I have a lot of images that I will need to flip (on the fly) and so am looking for the fastest way possible to do this using Python.

What is the most efficient way to do this?

I have image files on disk and have tried to ways, shown in my own answer below, but these start with Numpy arrays and so may not be optimal. Are there better ways?

n1k31t4
  • 2,745
  • 2
  • 24
  • 38
  • How are the images stored? As a list of arrays or one big array? – Divakar Oct 30 '17 at 00:29
  • Stored as single `.jpg` files. For my own attempts below I read them into a numpy array once at the beginning, using `matplotlib.image.imread()` – n1k31t4 Oct 30 '17 at 00:33
  • Let me put it another way. You have `images` in your answer post. So, is `images` an array or a list? – Divakar Oct 30 '17 at 00:35
  • Ah ok - three images were in a single numpy array. I have added the `shape` to my answer. – n1k31t4 Oct 30 '17 at 00:40

2 Answers2

8

You can simply use slicing to flip the second last axis to get equivalent flipped view into the input array of images, as such won't be creating any new data in memory and hence an efficient one, like so -

images[...,::-1,:]

If you still need to make a copy, use .copy there, which would still be more efficient than np.fliplr and noticeable with small/decent sized arrays.

Runtime test -

It seems NumPy is the winner, so I will test it out against that one.

In [64]: images = np.random.randint(0,255,(3,200,400,3))

In [65]: out1 = np.array([np.fliplr(images[i]) for i in range(3)])

In [66]: out2 = images[...,::-1,:]

In [67]: np.allclose(out1, out2)
Out[67]: True

In [68]: %timeit np.array([np.fliplr(images[i]) for i in range(3)])
1000 loops, best of 3: 1.38 ms per loop

In [69]: %timeit images[...,::-1,:]
1000000 loops, best of 3: 259 ns per loop # virtually free

If you need copy -

In [76]: images = np.random.randint(0,255,(3,10,10,3))

In [77]: %timeit np.array([np.fliplr(images[i]) for i in range(3)])
100000 loops, best of 3: 5.76 µs per loop

In [78]: %timeit images[...,::-1,:].copy()
100000 loops, best of 3: 2.23 µs per loop

In [79]: images = np.random.randint(0,255,(3,100,100,3))

In [80]: %timeit np.array([np.fliplr(images[i]) for i in range(3)])
10000 loops, best of 3: 159 µs per loop

In [81]: %timeit images[...,::-1,:].copy()
10000 loops, best of 3: 152 µs per loop
Divakar
  • 218,885
  • 19
  • 262
  • 358
  • 1
    That's great! I tested it too... `%timeit -r 10 -n 100000 images[...,::-1,:]`. 369 ns ± 125 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each). For others like me not familiar with the three dots (_Ellipsis_ object), here are a few hints: [link1](https://stackoverflow.com/questions/772124/what-does-the-python-ellipsis-object-do), [link2](https://stackoverflow.com/questions/42190783/what-does-three-dots-in-python-mean-when-indexing-what-looks-like-a-number) – n1k31t4 Oct 30 '17 at 00:52
  • Thanks for the additional `copy` info. Also, accepted, as I don't believe we can beat _for free_! :) – n1k31t4 Oct 30 '17 at 00:54
  • Also worth looking at the [implementation of fliplr](https://github.com/numpy/numpy/blob/v1.13.0/numpy/lib/twodim_base.py#L82) to see that it's just a shorthand for slicing anyway, with extra checks – Eric Oct 30 '17 at 04:21
6

Here are two ways, using:

  1. OpenCV cv2.flip()
  2. Numpy np.fliplr()

Import the packages

import cv2
import numpy as np

I had a three images in a numpy array, each image with a resolution as shown here:

images.shape
(3, 200, 400, 3)

Using Jupyter's %%timeit module:

%%timeit -r 10 -n 100000
[cv2.flip(images[i], 1) for i in range(3)]

--> 70.4 µs ± 1.16 µs per loop (mean ± std. dev. of 10 runs, 100000 loops each)

%%timeit -r 10 -n 100000
[np.fliplr(images[i]) for i in range(3)]

--> 3.19 µs ± 288 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)

Running the following code ensures the results are identical:

x = [cv2.flip(images[i], 1) for i in range(3)]
y = [np.fliplr(images[i]) for i in range(3)]

for i in range(3):
    print(np.array_equal(x[i], y[i]))

# True
# True
# True

So numpy is approx. 20 times faster than opencv

n1k31t4
  • 2,745
  • 2
  • 24
  • 38
  • @scharette Yes you can answer straight away. SO is meant to help future users. If you struggled with a problem for a while and couldn't find an answer here, then figured it out yourself then you are allowed to ask a question with the intention of answering it yourself – DavidG Oct 30 '17 at 00:31
  • There's a note in the Help center regarding it being OK to answer your own question at https://stackoverflow.com/help/self-answer – paisanco Oct 30 '17 at 00:35
  • 1
    @scharette The question and answer obviously have to be good quality and have not been asked before. [This blog](https://stackoverflow.blog/2011/07/01/its-ok-to-ask-and-answer-your-own-questions/) might be of interest. – DavidG Oct 30 '17 at 00:35