Learn how you can become a Python programmer in just 12 weeks.

    We respect your privacy. Unsubscribe at anytime.

    Create a Moving Photo Slideshow with Weighted Transitions in OpenCV

    What will we cover in this tutorial?

    In this tutorial you will learn how to make a slideshow of your favorite photos moving across the screen with weighted transitions. This will be done in Python with OpenCV.

    See the result in the video below.

    Step 1: A simple approach without moving effect

    If you want to build something great, start with something simple first. The reason for that is that you will learn along the way. It is difficult to understand all aspects from the beginning.

    Start small. Start simple. Learn from each step.

    Here we assume that you have all your favorite photos in a folder called pics. In the first run, you just want to show them on your screen one-by-one.

    import cv2
    import glob
    import os
    def process():
        path = "pics"
        filenames = glob.glob(os.path.join(path, "*"))
        for filename in filenames:
            print(filename)
            img = cv2.imread(filename)
            cv2.imshow("Slideshow", img)
            if cv2.waitKey(1000) == ord('q'):
                return
    
    process()
    

    As you will realize, this will show the photos in the size they stored. Hence, when photos change, the dimensions of the window will change as well (unless the two consecutive photos have the exact same dimensions). This will not make a good user experience. Also, if the photos dimensions are larger than your screen resolution, they will not be fully visible.

    Step 2: Scaling images to fit inside the screen

    We want the window size where we show the photos to have fixed size. This is not as simple as it sounds.

    Image a photo has dimensions 1000 x 2000 pixels. Then the next one has 2000 x 1000. How would you scale it down? If you scale it down to 500 x 500 by default, then the objects in the images will be flatten or narrowed together.

    Hence, what we do in our first attempt, is to scale it based on the dimensions. That is, a photo with dimensions 1000 x 2000 will become 500 x 1000. And a photo of dimensions 2000 x 1000 will become 1000 x 500. Then we will crop it to fit the 500 x 500 dimension. The cropping will take the middle of the photo.

    import cv2
    import glob
    import os
    def process():
        path = "pics"
        filenames = glob.glob(os.path.join(path, "*"))
        for filename in filenames:
            print(filename)
            img = cv2.imread(filename)
            height, width, _ = img.shape
            if width < height:
                height = int(height*500/width)
                width = 500
                img = cv2.resize(img, (width, height))
                shift = height - 500
                img = img[shift//2:-shift//2,:,:]
            else:
                width = int(width*500/height)
                height = 500
                shift = width - 500
                img = cv2.resize(img, (width, height))
                img = img[:,shift//2:-shift//2,:]
            cv2.imshow("Slideshow", img)
            if cv2.waitKey(1000) == ord('q'):
                return
    process()
    

    This gives a better experience, but not perfect.

    Step 3: Make a weighted transition between image switches

    To make a weighted transition we make it by adding a transition phase of the photos.

    import cv2
    import glob
    import os
    import numpy as np
    def process():
        path = "pics"
        filenames = glob.glob(os.path.join(path, "*"))
        prev_image = np.zeros((500, 500, 3), np.uint8)
        for filename in filenames:
            print(filename)
            img = cv2.imread(filename)
            height, width, _ = img.shape
            if width < height:
                height = int(height*500/width)
                width = 500
                img = cv2.resize(img, (width, height))
                shift = height - 500
                img = img[shift//2:-shift//2,:,:]
            else:
                width = int(width*500/height)
                height = 500
                shift = width - 500
                img = cv2.resize(img, (width, height))
                img = img[:,shift//2:-shift//2,:]
            for i in range(101):
                alpha = i/100
                beta = 1.0 - alpha
                dst = cv2.addWeighted(img, alpha, prev_image, beta, 0.0)
                cv2.imshow("Slideshow", dst)
                if cv2.waitKey(1) == ord('q'):
                    return
            prev_image = img
            if cv2.waitKey(1000) == ord('q'):
                return
    process()
    

    Notice the prev_image variable that is needed. It is set to a black image when it enters the loop. The transition is made by using cv2.addWeighted(…) to get the effect.

    Step 4: Make the photos move while showing

    The idea is to let the photo move. Say, if the photo is scaled to dimension 500 x 1000. Then we want to create a view of that photo of size 500 x 500 that slides from one end to the other while it is showing.

    This requires that we have a state for the photo, which stores where we are in of the current view.

    For this purpose we create a class to represent a photo that keeps the current view. It also includes the resizing.

    import cv2
    import numpy as np
    import glob
    import os
    import random
    
    class Image:
        def __init__(self, filename, time=500, size=500):
            self.size = size
            self.time = time
            self.shifted = 0.0
            self.img = cv2.imread(filename)
            self.height, self.width, _ = self.img.shape
            if self.width < self.height:
                self.height = int(self.height*size/self.width)
                self.width = size
                self.img = cv2.resize(self.img, (self.width, self.height))
                self.shift = self.height - size
                self.shift_height = True
            else:
                self.width = int(self.width*size/self.height)
                self.height = size
                self.shift = self.width - size
                self.img = cv2.resize(self.img, (self.width, self.height))
                self.shift_height = False
            self.delta_shift = self.shift/self.time
        def reset(self):
            if random.randint(0, 1) == 0:
                self.shifted = 0.0
                self.delta_shift = abs(self.delta_shift)
            else:
                self.shifted = self.shift
                self.delta_shift = -abs(self.delta_shift)
        def get_frame(self):
            if self.shift_height:
                roi = self.img[int(self.shifted):int(self.shifted) + self.size, :, :]
            else:
                roi = self.img[:, int(self.shifted):int(self.shifted) + self.size, :]
            self.shifted += self.delta_shift
            if self.shifted > self.shift:
                self.shifted = self.shift
            if self.shifted < 0:
                self.shifted = 0
            return roi
    
    def process():
        path = "pics"
        filenames = glob.glob(os.path.join(path, "*"))
        cnt = 0
        images = []
        for filename in filenames:
            print(filename)
            img = Image(filename)
            images.append(img)
            if cnt > 300:
                break
            cnt += 1
        prev_image = images[random.randrange(0, len(images))]
        prev_image.reset()
        while True:
            while True:
                img = images[random.randrange(0, len(images))]
                if img != prev_image:
                    break
            img.reset()
            for i in range(100):
                alpha = i/100
                beta = 1.0 - alpha
                dst = cv2.addWeighted(img.get_frame(), alpha, prev_image.get_frame(), beta, 0.0)
                cv2.imshow("Slide", dst)
                if cv2.waitKey(1) == ord('q'):
                    return
            prev_image = img
            for _ in range(300):
                cv2.imshow("Slide", img.get_frame())
                if cv2.waitKey(1) == ord('q'):
                    return
    
    process()
    

    This results in a nice way where the photos slowly move through the view. It also has added some randomness. First of all, it takes a random photo. Also, the direction is set to be random.

    This is a good start of having a nice slideshow of your favorite photos.

    Python for Finance: Unlock Financial Freedom and Build Your Dream Life

    Discover the key to financial freedom and secure your dream life with Python for Finance!

    Say goodbye to financial anxiety and embrace a future filled with confidence and success. If you’re tired of struggling to pay bills and longing for a life of leisure, it’s time to take action.

    Imagine breaking free from that dead-end job and opening doors to endless opportunities. With Python for Finance, you can acquire the invaluable skill of financial analysis that will revolutionize your life.

    Make informed investment decisions, unlock the secrets of business financial performance, and maximize your money like never before. Gain the knowledge sought after by companies worldwide and become an indispensable asset in today’s competitive market.

    Don’t let your dreams slip away. Master Python for Finance and pave your way to a profitable and fulfilling career. Start building the future you deserve today!

    Python for Finance a 21 hours course that teaches investing with Python.

    Learn pandas, NumPy, Matplotlib for Financial Analysis & learn how to Automate Value Investing.

    “Excellent course for anyone trying to learn coding and investing.” – Lorenzo B.

    12 thoughts on “Create a Moving Photo Slideshow with Weighted Transitions in OpenCV”

    1. Hello, Rune!
      Thank you for sharing this. This is an excellent solution.
      Is there a version of this that can be converted to a video?
      Suppose you want to save this slideshow as an MP4 file.
      Best regards!

      Reply
    2. Yes it should be possible.

      You need to create a video writer (before while-loop):

      # Using width = 500 and height = 500
      video=cv2.VideoWriter(‘video.avi’,-1,1,(width,height))

      Then you can write each frame (image) as follows:
      # replacing: cv2.imshow(“Slide”, img.get_frame())
      frame = img.get_frame()
      cv2.imshow(“Slide”, frame)
      video.write(frame)

      And then
      video.release() after the while-loop.

      Reply
      • The full code is in step 4. It does not make an MP4 file. You could change it to do that. Is that what you want – to make a movie?

        The purpose of this code is to make a window and a slideshow in that window. If you copy the code in Step 4 and run it, a window should open and there should be a slideshow. Of course, you need some images.

        Let me know what problem you encounter when you run the code in Step 4.

        Reply
      • The full code is in step 4. It does not make an MP4 file. You could change it to do that. Is that what you want – to make a movie?

        The purpose of this code is to make a window and a slideshow in that window. If you copy the code in Step 4 and run it, a window should open and there should be a slideshow. Of course, you need some images.

        Let me know what problem you encounter when you run the code in Step 4.

        Reply
    3. Amazing! I’m curious though – how hard would it be to have the program slideshow images from a folder that contains multiple subfolders which contain images?

      Reply
      • I guess the real question is how?

        One way you can modify it like this.

        def process():
        path = "pics"
        folders = glob.glob(os.path.join(path, "*"))

        for folder in folders:
        filenames = glob.glob(os.path.join(folder, "*"))
        for filename in filenames:
        print(filename)

        Reply
    4. Would it also be possible to display this slide show in a tkinter Window so that you have space for 3-4 labels next to the slide show for other things?

      Reply

    Leave a Comment