Title: Creating GIFs with OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2018/11/05/creating-gifs-with-opencv/

Published Time: 2018-11-05T15:00:30+00:00

Markdown Content:
In this tutorial, you will learn how to create animated GIFs using OpenCV, Python, and ImageMagick.

You’ll then combine all of these techniques to build a meme generator with OpenCV!

We all need a good laugh every now and then. And perhaps the best way to find LOLs is from memes.

Some of my favorite all-time memes include:

*   Kermit the Frog’s _“But That’s None of My Business”_
*   Grumpy Cat
*   Epic Fail
*   Good Guy Greg

**But for me personally, none of these memes compare to the _“Deal With It”_ meme (an example of which is at the top of this post), normally:**

1.  Used as a response or retort to someone being disapproving of something you did/said
2.  Involving putting on sunglasses as you walk away, leaving them to “deal with it”

A few years ago I saw a lighthearted blog post from an author I now cannot remember on how to generate these memes with computer vision. Last week I couldn’t find the tutorial anywhere, so, as a blogger, computer vision expert, and meme connoisseur, _I decided to create my own!_ (BTW, if you do happen to know the original source of the idea please let me know so I can credit the author **UPDATE:** I just found out the original article I’m thinking of is from Kirk Kaiser’s blog, [MakeArtWithPython](https://www.makeartwithpython.com/blog/deal-with-it-generator-face-recognition/)).

Building a Deal With It meme generator using OpenCV can teach us a number of valuable techniques used in practice, including:

1.  How to **perform deep learning-based face detection**
2.  How to use the dlib library to **apply facial landmark detection and extract the eye regions**
3.  How to take these two regions and **compute the rotation angle between the eyes**
4.  And finally, how to **generate animated GIFs** with OpenCV (with a little help from ImageMagick)

Today’s tutorial is meant to be fun, jovial, and entertaining — all while teaching you valuable  
computer vision skills that are used in the real world.

**To learn how to generate GIFs and memes with OpenCV, _just keep reading!_**

![Image 1](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

In today’s blog post, we are going to create animated GIFs with OpenCV, dlib, and the ImageMagick toolbox.

In the first part of the tutorial, we’ll discuss the prerequisites and dependencies for this project, including how to properly configure your development environment.

From there we’ll review the project/directory structure for our OpenCV GIF creator.

Once we understand the project structure we’ll review (1) our configuration file, and (2) our Python script responsible for creating GIFs with OpenCV.

Finally, we’ll take a look at the results of our OpenCV GIF creator by building a meme generator for the popular “Deal With It” meme.

### Prerequisites and dependencies

[![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20400'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/10/opencv_gif_libs.png)

**Figure 1:** To create GIFs with OpenCV we’ll be taking advantage of OpenCV, dlib, and ImageMagick.

#### OpenCV and dlib

OpenCV will be used for face detection and basic image processing. Be sure to follow one of my [OpenCV installation guides](https://pyimagesearch.com/opencv-tutorials-resources-guides/) if you do not have OpenCV installed on your system.

Dlib will be utilized for detecting facial landmarks, enabling us to find the two eyes of a face and lower the sunglasses on top of them. You can [install dlib with this guide](https://pyimagesearch.com/2018/01/22/install-dlib-easy-complete-guide/).

#### ImageMagick

If you aren’t familiar with [ImageMagick](https://www.imagemagick.org/script/index.php), you should be. ImageMagick is a cross-platform command line based tool providing quite a bit of image processing functionality.

Want to turn a PNG/JPG into a PDF with a single command? No problem.

Have multiple images you’d like to turn into PDF slides? That’s easy.

Do you have a need to draw polygons, lines, and other shapes? Go for it.

What about batch color adjustments or adjusting the spatial dimensions of an _entire_ image dataset in a single command? There’s no point in writing multiple lines of Python to use OpenCV for that.

Using ImageMagick we can generate GIF images via a set of input images as well.

To install ImageMagick on Ubuntu (or Raspbian) simply use apt:

$ sudo apt-get install imagemagick

Or if you’re on macOS, you may utilize HomeBrew:

$ brew install imagemagick

#### imutils

Much of my blog posts and book/course content take advantage of my handy package of image processing convenience functions called [imutils](https://github.com/jrosebr1/imutils). You can install imutils on your system or virtual environment by using pip:

$ pip install imutils

### Project structure

[![Image 3](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20237'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/10/opencv_gif_project_structure.png)

**Figure 2:** Our OpenCV GIF generator project structure consists of two directories, a config file, and a single Python script.

Our project has two directories:

*   `images/` : Our example input images that we wish to create the animated “Deal With it” GIF for. I’ve provided a selection of images of me but feel free to add your own.
*   `assets/` : This folder contains our face detector, facial landmark detector and all images + associated masks. We’ll be overlaying the “sunglasses” and “Deal With It” text on input image with these assets.

Due to the number of configurable parameters, I decided to create a JSON configuration file (1) making it easier to edit parameters, and (2) requiring fewer command line arguments. All configuration parameters we’ll need for this project are contained inside `config.json` .

Today we’ll be reviewing both `config.json` and `create_gif.py` .

The entire project is available for you to hack with in the _**“Downloads”**_ section of today’s tutorial!

### Generating GIFs with OpenCV

Let’s go ahead and get started implementing our OpenCV GIF generator!

#### Understanding our JSON configuration file

We’ll start with the JSON configuration file first and from there move into the Python script.

Go ahead and open a new file called `config.json` and then insert the following key/value pairs:

{
	"face\_detector\_prototxt": "assets/deploy.prototxt",
	"face\_detector\_weights": "assets/res10\_300x300\_ssd\_iter\_140000.caffemodel",
	"landmark\_predictor": "assets/shape\_predictor\_68\_face\_landmarks.dat",

**Lines 2 and 3** are [OpenCV’s deep learning face detector](https://pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/) model files.

**Line 4** is the path to [dlib’s facial landmark predictor](https://pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/).

And now we have some image file paths:

	"sunglasses": "assets/sunglasses.png",
	"sunglasses\_mask": "assets/sunglasses\_mask.png",
	"deal\_with\_it": "assets/deal\_with\_it.png",
	"deal\_with\_it\_mask": "assets/deal\_with\_it\_mask.png",

**Lines 5-8** are the paths to our Deal With It sunglasses, text, and associated masks for both, respectively — each of which is pictured below.

First our fancy sunglasses and associated mask:

[![Image 4](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%2058'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/10/sunglasses.png)

**Figure 3:** Do you dislike pixelated sunglasses? Deal with it.

[![Image 5](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%2058'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/10/sunglasses_mask.png)

**Figure 4:** Are you wondering WTH this sunglasses mask is for? Deal with it — or just read the rest of the blog post for the answer.

And now our _“DEAL WITH IT”_ text and mask:

[![Image 6](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20400%2060'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/10/deal_with_it.png)

**Figure 5:** Do you loathe Helvetica Neue Condensed? Deal with it.

[![Image 7](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20400%2060'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/10/deal_with_it_mask.png)

**Figure 6:** This mask will allow for a border. Oh, I’m guessing you don’t want a border around your text. Deal with it.

We need the masks in order to overlay the corresponding image on the photo, a process I’ll show you how to perform later in today’s tutorial.

Now let’s set some parameters for our meme generator:

	"min\_confidence": 0.5,
	"steps": 20,
	"delay": 5,
	"final\_delay": 250,
	"loop": 0,
	"temp\_dir": "temp"
}

Below you can find definitions for each of the parameters:

*   `min_confidence` : Minimum required probability of a positive face detection.
*   `steps` : # of frames we’ll be generating for the output GIF. Each “step” will move the sunglasses from the top of the frame farther down towards the target (i.e., the eyes).
*   `delay` : # of hundredths of a second delay between each frame.
*   `final_delay` : # of hundredths of a second delay for the final frame (useful in this context since we want the “Deal With It” text to be visible for longer than the rest of the frames).
*   `loop` : Whether or not the GIF will loop (a value of zero indicates the GIF will loop forever, otherwise supply a positive integer for the number of times the GIF is allowed to loop).
*   `temp_dir` : Temporary output directory where each of the frames will be stored prior to constructing the final GIF image.

#### Memes, GIFs, and OpenCV

Now that we’ve created our JSON configuration file, let’s move into the actual code.

Open up a new file, name it `create_gif.py` , and insert the following code:

\# import the necessary packages
from imutils import face\_utils
from imutils import paths
import numpy as np
import argparse
import imutils
import shutil
import json
import dlib
import cv2
import sys
import os

On **Lines 2-12** we import our necessary packages. Notably, we’ll be using imutils, dlib, and OpenCV. To install these dependencies see the _“Prerequisites and dependencies”_ section above.

Now that our script has the required packages at our disposal, let’s define the `overlay_image` function:

def overlay\_image(bg, fg, fgMask, coords):
	# grab the foreground spatial dimensions (width and height),
	# then unpack the coordinates tuple (i.e., where in the image
	# the foreground will be placed)
	(sH, sW) = fg.shape\[:2\]
	(x, y) = coords

	# the overlay should be the same width and height as the input
	# image and be totally blank \*except\* for the foreground which
	# we add to the overlay via array slicing
	overlay = np.zeros(bg.shape, dtype="uint8")
	overlay\[y:y + sH, x:x + sW\] = fg

	# the alpha channel, which controls \*where\* and \*how much\*
	# transparency a given region has, should also be the same
	# width and height as our input image, but will contain only
	# our foreground mask
	alpha = np.zeros(bg.shape\[:2\], dtype="uint8")
	alpha\[y:y + sH, x:x + sW\] = fgMask
	alpha = np.dstack(\[alpha\] \* 3)

	# perform alpha blending to merge the foreground, background,
	# and alpha channel together
	output = alpha\_blend(overlay, bg, alpha)

	# return the output image
	return output

The `overlay_image` function overlays a foreground image (`fg` ) on top of a background image (`bg` ) at location `coords` (which are _(x, y)_\-coordinates), allowing for alpha transparency via the foreground mask `fgMask` .

To review OpenCV basics such as working with masks, be sure to give [this tutorial a read](https://pyimagesearch.com/2018/07/19/opencv-tutorial-a-guide-to-learn-opencv/).

To finish the overlay process we need to apply alpha blending:

def alpha\_blend(fg, bg, alpha):
	# convert the foreground, background, and alpha layers from
	# unsigned 8-bit integers to floats, making sure to scale the
	# alpha layer to the range \[0, 1\]
	fg = fg.astype("float")
	bg = bg.astype("float")
	alpha = alpha.astype("float") / 255

	# perform alpha blending
	fg = cv2.multiply(alpha, fg)
	bg = cv2.multiply(1 - alpha, bg)

	# add the foreground and background to obtain the final output
	# image
	output = cv2.add(fg, bg)
	
	# return the output image
	return output.astype("uint8")

This implementation of alpha blending is also covered on the [LearnOpenCV blog](https://www.learnopencv.com/alpha-blending-using-opencv-cpp-python/).

In essence, we’ll convert the foreground, background, and alpha layers to floats in the range of _\[0, 1\]_ (**Lines 46-48**). Then we’ll perform the actual alpha blending (**Lines 51 and 52**). And finally, we’ll add the foreground and background to obtain our output which we then return to the calling function (**Lines 56-59**).

Let’s also create a helper function that will enable us to generate a GIF from a set of image paths using ImageMagick and the `convert` command:

def create\_gif(inputPath, outputPath, delay, finalDelay, loop):
	# grab all image paths in the input directory
	imagePaths = sorted(list(paths.list\_images(inputPath)))
	
	# remove the last image path in the list
	lastPath = imagePaths\[-1\]
	imagePaths = imagePaths\[:-1\]

	# construct the image magick 'convert' command that will be used
	# generate our output GIF, giving a larger delay to the final
	# frame (if so desired)
	cmd = "convert -delay {} {} -delay {} {} -loop {} {}".format(
		delay, " ".join(imagePaths), finalDelay, lastPath, loop,
		outputPath)
	os.system(cmd)

The `create_gif` function takes a set of images and assembles them in a GIF animation with the specified delay between frames and loops if necessary. All of this is handled by ImageMagick — we’re simply wrapping the command line `convert` command with a function that dynamically handles different parameters.

To review the available command line arguments for the `convert` command, [refer to the documentation](https://imagemagick.org/script/convert.php). As you can tell from the docs, there is a lot of functionality built into `convert` !

Specifically, in this function we:

*   Grab `imagePaths` (**Line 63**).
*   Grab the last image path since it will have a separate delay (**Line 66**).
*   Reassign the `imagePaths` to exclude the last image path (**Line 67**).
*   Assemble the command string with command line arguments and then instruct the operating system to execute `convert` to create the GIF (**Lines 72-75**).

Let’s construct our own script’s command line arguments:

\# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-c", "--config", required=True,
	help="path to configuration file")
ap.add\_argument("-i", "--image", required=True,
	help="path to input image")
ap.add\_argument("-o", "--output", required=True,
	help="path to output GIF")
args = vars(ap.parse\_args())

We have three [command line arguments](https://pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/) which are processed at runtime:

*   `--config` : The path to our JSON configuration file. We reviewed the config file in the previous section.
*   `--image` : Path to our input image. We’ll be creating our animation with this image (i.e. finding the face + adding sunglasses and then adding the “Deal With It” text).
*   `--output` : The target path to our output GIF.

Each of these arguments is required when you execute the script in your command line/terminal.

Let’s load the config file as well as our sunglasses + associated mask:

\# load the JSON configuration file and the "Deal With It" sunglasses
# and associated mask
config = json.loads(open(args\["config"\]).read())
sg = cv2.imread(config\["sunglasses"\])
sgMask = cv2.imread(config\["sunglasses\_mask"\])

# delete any existing temporary directory (if it exists) and then
# create a new, empty directory where we'll store each individual
# frame in the GIF
shutil.rmtree(config\["temp\_dir"\], ignore\_errors=True)
os.makedirs(config\["temp\_dir"\])

Here we load the configuration file (which can be accessed like a Python dictionary from here forward) on **Line 89**. Then we load the sunglasses image and sunglasses mask (**Lines 90 and 91**).

In case there are any remnants from a previous run of the script, we remove the temporary directory from disk and then recreate an empty temporary directory (**Lines 96 and 97**). The temporary folder will hold each individual frame in the GIF.

Now let’s load [OpenCV’s deep learning face detector](https://pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/) into memory:

\# load our OpenCV face detector and dlib facial landmark predictor
print("\[INFO\] loading models...")
detector = cv2.dnn.readNetFromCaffe(config\["face\_detector\_prototxt"\],
	config\["face\_detector\_weights"\])
predictor = dlib.shape\_predictor(config\["landmark\_predictor"\])

To load OpenCV’s deep learning face detector, we call `cv2.dnn.readNetFromCaffe` (**Lines 101 and 102**). The `dnn` module is only accessible in OpenCV 3.3 or later. The face detector will enable us to detect the presence of faces in images:

[![Image 8](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20622'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2018/02/deep_learning_face_detection_example_02.jpg)

**Figure 7:** The OpenCV DNN face detector finds faces in images.

Then, on **Line 103**, we load [dlib’s facial landmark predictor](https://pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/). The facial landmark predictor, on the other hand, will enable us to localize structures of the face, such as eyes, eyebrows, nose, mouth, and jawline:

[![Image 9](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20568'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2017/03/detect_face_parts_visualize_landmarks.jpg)

**Figure 8:** Shown on my face are overlays of the facial landmarks detected by dlib.

Later in this script, we’ll be extracting just the eye regions.

Moving on, let’s detect the face:

\# load the input image and construct an input blob from the image
image = cv2.imread(args\["image"\])
(H, W) = image.shape\[:2\]
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0,
	(300, 300), (104.0, 177.0, 123.0))

# pass the blob through the network and obtain the detections
print("\[INFO\] computing object detections...")
detector.setInput(blob)
detections = detector.forward()

# we'll assume there is only one face we'll be applying the "Deal
# With It" sunglasses to so let's find the detection with the largest
# probability
i = np.argmax(detections\[0, 0, :, 2\])
confidence = detections\[0, 0, i, 2\]

# filter out weak detections
if confidence < config\["min\_confidence"\]:
	print("\[INFO\] no reliable faces found")
	sys.exit(0)

In this block we:

*   Load input `image` (**Line 106**).
*   Construct a `blob` to send through the face detector neural network (**Lines 108 and 109**). You can learn how OpenCV’s `blobFromImage` works in [this blog post](https://pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/).
*   Perform face detection (**Lines 113 and 114**).
*   Determine the face detection with the largest probability and check versus the confidence threshold (**Lines 119-124**). If the criteria isn’t met, we simply exit the script (**Line 125**). Otherwise, we’ll continue on.

Let’s extract the face and calculate facial landmarks:

\# compute the (x, y)-coordinates of the bounding box for the face
box = detections\[0, 0, i, 3:7\] \* np.array(\[W, H, W, H\])
(startX, startY, endX, endY) = box.astype("int")

# construct a dlib rectangle object from our bounding box coordinates
# and then determine the facial landmarks for the face region
rect = dlib.rectangle(int(startX), int(startY), int(endX), int(endY))
shape = predictor(image, rect)
shape = face\_utils.shape\_to\_np(shape)

# grab the indexes of the facial landmarks for the left and right
# eye, respectively, then extract (x, y)-coordinates for each eye
(lStart, lEnd) = face\_utils.FACIAL\_LANDMARKS\_IDXS\["left\_eye"\]
(rStart, rEnd) = face\_utils.FACIAL\_LANDMARKS\_IDXS\["right\_eye"\]
leftEyePts = shape\[lStart:lEnd\]
rightEyePts = shape\[rStart:rEnd\]

To extract the face and find facial landmarks, we:

*   Extract the bounding box coordinates of the face (**Lines 128 and 129**).
*   Construct a dlib `rectangle` object (**Line 133**) and **apply facial landmark localization** (**Lines 134 and 135**).
*   Extract the _(x, y)_\-coordinates for the `leftEyePts` and `rightEyePts` respectively (**Lines 139-142**).

Given the coordinates of the eyes we can calculate _where_ and _how_ the sunglasses need to be placed:

\# compute the center of mass for each eye
leftEyeCenter = leftEyePts.mean(axis=0).astype("int")
rightEyeCenter = rightEyePts.mean(axis=0).astype("int")

# compute the angle between the eye centroids
dY = rightEyeCenter\[1\] - leftEyeCenter\[1\]
dX = rightEyeCenter\[0\] - leftEyeCenter\[0\]
angle = np.degrees(np.arctan2(dY, dX)) - 180

# rotate the sunglasses image by our computed angle, ensuring the
# sunglasses will align with how the head is tilted
sg = imutils.rotate\_bound(sg, angle)

# the sunglasses shouldn't be the \*entire\* width of the face and
# ideally should just cover the eyes -- here we'll do a quick
# approximation and use 90% of the face width for the sunglasses
# width
sgW = int((endX - startX) \* 0.9)
sg = imutils.resize(sg, width=sgW)

# our sunglasses contain transparency (the bottom parts, underneath
# the lenses and nose) so in order to achieve that transparency in
# the output image we need a mask which we'll use in conjunction with
# alpha blending to obtain the desired result -- here we're binarizing
# our mask and performing the same image processing operations as
# above
sgMask = cv2.cvtColor(sgMask, cv2.COLOR\_BGR2GRAY)
sgMask = cv2.threshold(sgMask, 0, 255, cv2.THRESH\_BINARY)\[1\]
sgMask = imutils.rotate\_bound(sgMask, angle)
sgMask = imutils.resize(sgMask, width=sgW, inter=cv2.INTER\_NEAREST)

First, we compute the center of each eye then calculate the angle between the eye centroids (**Lines 145-151**), just as if we were performing [face alignment](https://pyimagesearch.com/2017/05/22/face-alignment-with-opencv-and-python/).

From there we can rotate (**Line 155**) and resize (**Lines 161 and 162**) the sunglasses. Notice that we’re using the [rotate\_bound function](https://pyimagesearch.com/2017/01/02/rotate-images-correctly-with-opencv-and-python/). We’re using `rotate_bound` here instead of just `rotate` to ensure OpenCV does not clip off parts of the image that are out of view after the affine transformation.

The same operations we applied to the sunglasses themselves need to be applied to the mask. But first, we need to convert the mask to grayscale and binarize it (**Lines 170 and 171**) since masks are always _binary_. Then we proceed to rotate and resize the mask exactly as we did to the sunglasses on **Lines 172 and 173**.

_**Note:**_ _Notice that we are using nearest neighbor interpolation when resizing the mask. This is because our mask should only have two values (0 and 255). Other interpolation methods may be more aesthetically pleasing to the eye but will actually be harmful to our mask. You can [read more about nearest neighbor interpolation here](https://www.cambridgeincolour.com/tutorials/image-interpolation.htm)._

The remaining three code blocks will create the frames of our GIF:

\# our sunglasses will drop down from the top of the frame so let's
# define N equally spaced steps between the top of the frame and the
# desired end location
steps = np.linspace(0, rightEyeCenter\[1\], config\["steps"\],
	dtype="int")

# start looping over the steps
for (i, y) in enumerate(steps):
	# compute our translation values to move the sunglasses both
	# slighty to the left and slightly up -- the reason why we are
	# doing this is so the sunglasses don't \*start\* directly at
	# the center of our eye, translation helps us shift the
	# sunglasses to adequately cover our entire eyes (otherwise
	# what good are sunglasses!)
	shiftX = int(sg.shape\[1\] \* 0.25)
	shiftY = int(sg.shape\[0\] \* 0.35)
	y = max(0, y - shiftY)

	# add the sunglasses to the image
	output = overlay\_image(image, sg, sgMask,
		(rightEyeCenter\[0\] - shiftX, y))

Our sunglasses will drop down from the top of the image. Each frame, in turn, will show the sunglasses progressively getting closer to the face until they cover the eyes. Using our JSON configuration variable `"steps"` (the number of `steps` ), let’s generate evenly spaced _y_\-values to place the sunglasses on each respective frame. This is handled on **Lines 178 and 179** where we take advantage of NumPy’s `linspace` function effortlessly.

Given the `steps` , we’ll iterate over them (they are simply _y_\-values) to compute the translation for the sunglasses.

Glancing at **Lines 189 and 190**, you might be thinking, “WTH?”

Referring to my code documentation comment on the preceding lines, I simply explain that we’re ensuring that the sunglasses cover each entire eye rather than just reaching the point at which the eye center is. I determined the percentage values to calculate both the _x_\-shift and _y_\-shift on **Lines 189 and 190** empirically. **Line 191** is applied to ensure we do not have negative values.

Taking advantage of our `overlay_image` function, we generate our `output` frame on **Lines 194 and 195**.

Our final output frame is a special case as it as the _“DEAL WITH IT”_ text which we’ll draw on by the frame by means of another masking operation:

	# if this is the final step then we need to add the "DEAL WITH
	# IT" text to the bottom of the frame
	if i == len(steps) - 1:
		# load both the "DEAL WITH IT" image and mask from disk,
		# ensuring we threshold the mask as we did for the sunglasses
		dwi = cv2.imread(config\["deal\_with\_it"\])
		dwiMask = cv2.imread(config\["deal\_with\_it\_mask"\])
		dwiMask = cv2.cvtColor(dwiMask, cv2.COLOR\_BGR2GRAY)
		dwiMask = cv2.threshold(dwiMask, 0, 255,
			cv2.THRESH\_BINARY)\[1\]

		# resize both the text image and mask to be 80% the width of
		# the output image
		oW = int(W \* 0.8)
		dwi = imutils.resize(dwi, width=oW)
		dwiMask = imutils.resize(dwiMask, width=oW,
			inter=cv2.INTER\_NEAREST)

		# compute the coordinates of where the text will go on the
		# output image and then add the text to the image
		oX = int(W \* 0.1)
		oY = int(H \* 0.8)
		output = overlay\_image(output, dwi, dwiMask, (oX, oY))

If we’re on the last step (**Line 199**), we need to overlay our _“DEAL WITH IT”_ image (**Figure 5**) — this is again a special case.

The “text” is actually another “image” here.

I opted to use an image since OpenCV’s font rendering capacities are quite limited, and furthermore, I wanted to add a drop shadow and border to the text which again, is something OpenCV cannot do.

The rest of the above code block loads both the image and mask and then proceeds to perform alpha blending in order to generate the final frame output.

And now we just need to output each frame to disk followed by creating our GIF:

	# write the output image to our temporary directory
	p = os.path.sep.join(\[config\["temp\_dir"\], "{}.jpg".format(
		str(i).zfill(8))\])
	cv2.imwrite(p, output)

# now that all of our frames have been written to disk we can finally
# create our output GIF image
print("\[INFO\] creating GIF...")
create\_gif(config\["temp\_dir"\], args\["output"\], config\["delay"\],
	config\["final\_delay"\], config\["loop"\])

# cleanup by deleting our temporary directory
print("\[INFO\] cleaning up...")
shutil.rmtree(config\["temp\_dir"\], ignore\_errors=True)

On **Lines 222-224** we write the loop’s `output` frame to disk.

Once all frames have been generated, we call our `create_gif` function to generate the GIF animation file (**Lines 229 and 230**). Remember, the `create_gif` function is a wrapper that passes parameters to ImageMagick’s `convert` command line tool.

Finally, we clean up by deleting the temporary output directory + individual image files.

### OpenCV GIF Results

Now for the fun part — let’s take a look at what our meme generator created!

Make sure you use the _**“Downloads”**_ section of this blog post to download the source code, example images, and deep learning models. From there, open up your terminal and execute the following command:

$ python create\_gif.py --config config.json --image images/adrian.jpg \\
	--output adrian\_out.gif
\[INFO\] loading models...
\[INFO\] computing object detections...
\[INFO\] creating GIF...
\[INFO\] cleaning up...

If you have followed along up to this point you’ll see we have created a GIF with OpenCV and ImageMagick showing that:

1.  My face has been correctly detected.
2.  My eyes have been localized and their centers computed.
3.  The sunglasses are correctly lowered down across my face.

If you have had any problems with this project please consider getting extra help at my custom [Python and Deep Learning Training Program](https://pyimagesearch.com/pyimagesearch-university/) where you can get extra help, code downloads, and a supportive community of learners.

Many of you know that I’m a huge _Jurassic Park_ nerd and often include _Jurassic Park_ references inside my books, courses, and tutorials.

Don’t like _Jurassic Park_?

Well, here’s my response:

$ python create\_gif.py --config config.json --image images/adrian\_jp.jpg \\
	--output adrian\_jp\_out.gif
\[INFO\] loading models...
\[INFO\] computing object detections...
\[INFO\] creating GIF...
\[INFO\] cleaning up...

Here I am at the opening of _Jurassic Park: Fallen Kingdom_ sporting my special _Jurassic Park_ shirt, pint glass, and collectors edition book.

Fun story:

Five or six years ago my now wife and I visited EPCOT Center in Walt Disney World in Orlando, FL.

We decided to take the trip to get away from the harsh Connecticut winters — we were in desperate need of sunlight.

Unfortunately, it rained the entire time we in FL and the weather barely exceeded 50F degrees.

Trisha took the following photo of me outside of the “Canadian gardens” at Epcot — she says I look like I vampire with my pale skin, dark clothes, and hood up, in contrast to the lush gardens behind me:

$ python create\_gif.py --config config.json --image images/vampire.jpg \\
	--output vampire\_out.gif
\[INFO\] loading models...
\[INFO\] computing object detections...
\[INFO\] creating GIF...
\[INFO\] cleaning up...

Trisha decided to post the photo on social media later that evening — I was left to deal with it.

For those of you that attended PyImageConf 2018 ([read the recap here](https://pyimagesearch.com/2018/10/01/pyimageconf-2018-recap/)), you know that I’m always one for a joke. Here’s a good joke:

> Q: Why did the rooster cross the road?

$ python create\_gif.py --config config.json --image images/rooster.jpg \\
	--output rooster\_out.gif
\[INFO\] loading models...
\[INFO\] computing object detections...
\[INFO\] creating GIF...
\[INFO\] cleaning up...

> A: I’m not telling you the answer — deal with it.

Finally, let’s wrap up today’s tutorial on creating GIFs with OpenCV with a good-hearted meme.

Approximately six years ago my dad and I adopted the family beagle, Jemma.

Here you can see tiny Jemma puppy sitting on my shoulder:

$ python create\_gif.py --config config.json --image images/pupper.jpg \\
	--output pupper\_out.gif
\[INFO\] loading models...
\[INFO\] computing object detections...
\[INFO\] creating GIF...
\[INFO\] cleaning up...

Don’t think she’s a cute puppy? Deal with it.

#### Did you encounter an AttributeError?

Not to worry!

If you saw the following error:

$ python create\_gif.py --config config.json --image images/adrian.jpg \\
	--output adrian\_out.gif
...
Traceback (most recent call last):
  File "create\_gif.py", line 142, in <module>
    (lStart, lEnd) = face\_utils.FACIAL\_LANDMARKS\_IDXS\["left\_eye"\]
AttributeError: module 'imutils.face\_utils' has no attribute 'FACIAL\_LANDMARKS\_IDXS'

Then you just need to upgrade the imutils package:

$ pip install --upgrade imutils
Collecting imutils
...
Successfully installed imutils-0.5.1

Why, you ask?

By default `imutils.face_utils` will use the 68-point landmark detector built into dlib (as does this blog post). There is also a [faster dlib 5-point landmark detector](https://pyimagesearch.com/2018/04/02/faster-facial-landmark-detector-with-dlib/) that now works with imutils as well. I recently updated imutils to support both (which is the reason why you may see the error).

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

Summary
-------

In today’s tutorial, you learned how to create GIFs using OpenCV.

To keep the post lighthearted and fun, we learned how to use OpenCV to generate GIFs of the “Deal With It” meme, a popular meme (and also my personal favorite) that can be found in some capacity on nearly every social media website.

In order to build our meme generator, we leveraged computer vision and deep learning in a number of practical ways, including:

*   Face detection
*   Facial landmark prediction
*   Extracting regions of the face (in this case, the eyes)
*   Computing the angle between the eyes, a requirement for face alignment
*   Generating transparent overlays via alpha blending

Finally, we took our set of generated images and then created an animated GIF using OpenCV and ImageMagick.

I hope you enjoyed today’s tutorial on GIF creation with OpenCV!

If you enjoyed it, please do leave a comment and let me know.

And if you didn’t enjoy the tutorial, whatever, deal with it 😉

**To download the source code to today’s post, and be notified when future tutorials are published here on PyImageSearch, _just enter your email address in the form below!_**

![Image 10](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!