'Efficient way to shift image pixels in python

Trying to find a way to efficiently shift image pixels in python. Ultimately want to display the results on screen after each shift. For example, pixel at 1920,1080 becomes pixel at 1920,1079 and 1920,1080 is filled with a new value, at the end pixel at 0,0 is discarded and filled with pixel from 0,1. Looking to achieve 30fps or greater on a slow processor like the raspi 3.

My intuition is to move everything to a 1D array, shift everything "left", add the new pixel at the end and turn back into an image.

Crude drawing of the "Z" shifting effect I'm looking for

Crude drawing of the "Z" shifting effect I'm looking for

Here is my naive implementation of just the pixel shifting, which is too slow:

from PIL import Image
im = Image.new("RGBA", (1920, 1080), "black") #create blank image
pix = im.load() #load image
#iterate over width and height
for x in xrange(1,1919):
 pix[x,y] = pix[x+1, y] #shift x
    for y in xrange(1,1079):
        pix[x,y] = pix[x, y+1] #shift y
pix[1919,1079] = (255,255,255) #set new pixel white


Solution 1:[1]

I don't think you are going to get anywhere close to 30fps on a raspberry pi, but I would look at numpy and pandas for this.

Solution 2:[2]

I thought of a couple of ways of doing this but can only get 40fps on a desktop Mac at the moment.

Here's the first method which uses Numpy roll() at its heart in order to roll the top-left pixel down to the bottom right corner and then overwrite it with white.

#!/usr/bin/env python3

import cv2
import numpy as np
from PIL import Image

w, h = 640, 480
im = Image.new("L", (w, h), 127)

# Just do 1,000 shifts so it can be timed
for frame in range(1000):
    # Ravel image out to row vector, roll it left, and reshape
    im = np.roll(np.ravel(im),-1).reshape(h,w)
    # Make bottom right pixel white
    im[-1,-1] = 255
    cv2.imshow("Slidey Pushy",im)
    cv2.waitKey(1)

I then thought of adding in all the new pixels right at the beginning and then just changing the offset into the image on each iteration, but surprisingly (to me at least) this was no faster.

#!/usr/bin/env python3

import cv2
import numpy as np
from PIL import Image

w, h = 640, 480
im = Image.new("L", (w, h), 127)

# Allocate space for and append 10 rows of white pixels up front
im = np.concatenate((im,np.ones((10*h,w),dtype=np.uint8)*255), axis=0)
im = np.ravel(im)

# Slide in 1,000 white pixels so it can be timed
for frame in range(1000):
    frame = im[frame:frame+w*h].reshape(h,w)
    cv2.imshow("Slidey Pushy 2",frame)
    cv2.waitKey(1)

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Brown
Solution 2