Title: Basic motion detection and tracking with Python and OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2015/05/25/basic-motion-detection-and-tracking-with-python-and-opencv/

Published Time: 2015-05-25T14:00:23+00:00

Markdown Content:
Last updated on July 8, 2021.

[![Image 1: animated_motion_02](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20614%20448'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/animated_motion_02.gif)

> _**That son of a bitch. I knew he took my last beer.**_

These are words a man should never, _ever_ have to say. But I muttered them to myself in an exasperated sigh of disgust as I closed the door to my refrigerator.

You see, I had just spent over 12 hours writing content for the upcoming [PyImageSearch Gurus course](https://pyimagesearch.com/pyimagesearch-gurus/). My brain was fried, practically leaking out my ears like half cooked scrambled eggs. And after calling it quits for the night, all I wanted was to do relax and watch my all-time favorite movie, _Jurassic Park_, while sipping an ice cold Finestkind IPA from Smuttynose, a brewery I have become quite fond of as of late.

But that _son of a bitch James_ had come over last night and drank my last beer.

Well, **_allegedly_**.

**I couldn’t actually prove anything**. In reality, I didn’t _really see_ him drink the beer as my face was buried in my laptop, fingers floating above the keyboard, feverishly pounding out tutorials and articles. _But I had a feeling he was the culprit._ He is my only (ex-)friend who drinks IPAs.

So I did what any man would do.

_**I mounted a Raspberry Pi to the top of my kitchen cabinets to automatically detect if he tried to pull that beer stealing shit again:**_

[![Image 2: Figure 1: Don't steal my damn beer. Otherwise I'll mount a Raspberry Pi + camera on top of my kitchen cabinets and catch you.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20810%201080'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/raspberry_pi_cabinents.jpg)

**Figure 1:** Don’t steal my damn beer. Otherwise I’ll mount a Raspberry Pi + camera on top of my kitchen cabinets and catch you.

Excessive?

Perhaps.

But I take my beer seriously. And if James tries to steal my beer again, I’ll catch him redhanded.

A dataset of video sequences is fundamental for understanding basic motion detection and tracking. It allows us to observe how movement is detected and tracked over time.

[Roboflow](https://roboflow.com/?ref=pyimagesearch) has free tools for each stage of the computer vision pipeline that will streamline your workflows and supercharge your productivity.

Sign up or Log in to your [Roboflow account](https://roboflow.com/?ref=pyimagesearch) to access state of the art dataset libaries and revolutionize your computer vision pipeline.

You can start by choosing your own datasets or using our [PyimageSearch’s assorted library of useful datasets](https://universe.roboflow.com/pyimagesearch?ref=pyimagesearch).

Bring data in any of 40+ formats to [Roboflow](https://roboflow.com/?ref=pyimagesearch), train using any state-of-the-art model architectures, deploy across multiple platforms (API, NVIDIA, browser, iOS, etc), and connect to applications or 3rd party tools.

*   **Update July 2021:** Added new sections on alternative background subtraction and motion detection algorithms we can use with OpenCV.

![Image 3](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)

A 2-part series on motion detection
-----------------------------------

**This is the _first post_ in a _two part series_ on building a motion detection and tracking system for home surveillance.**

The remainder of this article will detail how to build a basic motion detection and tracking system for home surveillance using computer vision techniques. This example will work with both _pre-recorded videos_ and _live streams from your webcam_; however, we’ll be developing this system on our laptops/desktops.

In the second post in this series I’ll show you how to update the code to work with your Raspberry Pi and camera board — _and how to extend your home surveillance system to capture any detected motion and upload it to your personal Dropbox._

And maybe at the end of all this we can catch James red handed…

A little bit about background subtraction
-----------------------------------------

Background subtraction is critical in many computer vision applications. We use it to count the number of cars passing through a toll booth. We use it to count the number of people walking in and out of a store.

_And we use it for motion detection._

Before we get started coding in this post, let me say that there are many, _many_ ways to perform motion detection, tracking, and analysis in OpenCV. Some are very simple. And others are very complicated. The two primary methods are forms of Gaussian Mixture Model-based foreground and background segmentation:

1.  [_An improved adaptive background mixture model for real-time tracking with shadow detection_](http://www.ee.surrey.ac.uk/CVSSP/Publications/papers/KaewTraKulPong-AVBS01.pdf) by KaewTraKulPong et al., available through the `cv2.BackgroundSubtractorMOG` function.
2.  [_Improved adaptive Gaussian mixture model for background subtraction_](http://www.zoranz.net/Publications/zivkovic2004ICPR.pdf) by Zivkovic, and _[Efficient Adaptive Density Estimation per Image Pixel for the Task of Background Subtraction](http://www.zoranz.net/Publications/zivkovicPRL2006.pdf)_, also by Zivkovic, available through the `cv2.BackgroundSubtractorMOG2` function.

And in newer versions of OpenCV we have Bayesian (probability) based foreground and background segmentation, implemented from Godbehere et al.’s 2012 paper, _[Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive Audio Art Installation](http://goldberg.berkeley.edu/pubs/acc-2012-visual-tracking-final.pdf)_. We can find this implementation in the `cv2.createBackgroundSubtractorGMG` function (we’ll be waiting for OpenCV 3 to fully play with this function though).

All of these methods are concerned with _segmenting the background from the foreground_ (and they even provide mechanisms for us to discern between actual motion and just shadowing and small lighting changes)!

So why is this so important? And why do we care what pixels belong to the _foreground_ and what pixels are part of the _background_?

Well, in motion detection, we tend to make the following assumption:

**The _background_ of our video stream is largely _static and unchanging_ over consecutive frames of a video. Therefore, if we can model the background, we monitor it for substantial changes. If there is a substantial change, we can detect it — this change normally corresponds to _motion_ on our video.**

Now obviously in the real-world this assumption can easily fail. Due to shadowing, reflections, lighting conditions, and any other possible change in the environment, our background can look quite different in various frames of a video. And if the background appears to be different, it can throw our algorithms off. That’s why the most successful background subtraction/foreground detection systems _utilize fixed mounted cameras_ and in _controlled lighting conditions._

The methods I mentioned above, while very powerful, are also computationally expensive. And since our end goal is to deploy this system to a Raspberry Pi at the end of this 2 part series, it’s best that we stick to simple approaches. We’ll return to these more powerful methods in future blog posts, but for the time being we are going to keep it simple and efficient.

In the rest of this blog post, I’m going to detail (arguably) the most basic motion detection and tracking system you can build. It won’t be perfect, but it will be able to run on a Pi and still deliver good results.

Alright, are you ready to help me develop a home surveillance system to catch that beer stealing jackass?

Open up a editor, create a new file, name it `motion_detector.py` , and let’s get coding:

\# import the necessary packages
from imutils.video import VideoStream
import argparse
import datetime
import imutils
import time
import cv2

# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-v", "--video", help="path to the video file")
ap.add\_argument("-a", "--min-area", type=int, default=500, help="minimum area size")
args = vars(ap.parse\_args())

# if the video argument is None, then we are reading from webcam
if args.get("video", None) is None:
	vs = VideoStream(src=0).start()
	time.sleep(2.0)

# otherwise, we are reading from a video file
else:
	vs = cv2.VideoCapture(args\["video"\])

# initialize the first frame in the video stream
firstFrame = None

**Lines 2-7** import our necessary packages. All of these should look pretty familiar, except perhaps the `imutils` package, which is a set of convenience functions that I have created to make basic image processing tasks easier. If you do not already have [imutils](https://github.com/jrosebr1/imutils) installed on your system, you can install it via pip: `pip install imutils` .

Next up, we’ll parse our [command line arguments](https://pyimagesearch.com/2018/03/12/python-argparse-command-line-arguments/) on **Lines 10-13**. We’ll define two switches here. The first, `--video` , is optional. It simply defines a path to a pre-recorded video file that we can detect motion in. If you _do not_ supply a path to a video file, then OpenCV will utilize your webcam to detect motion.

We’ll also define `--min-area` , which is the minimum size (in pixels) for a region of an image to be considered actual “motion”. As I’ll discuss later in this tutorial, we’ll often find small regions of an image that have changed substantially, likely due to noise or changes in lighting conditions. In reality, these small regions are not actual motion at all — so we’ll define a minimum size of a region to combat and filter out these false-positives.

**Lines 16-22** handle grabbing a reference to our `vs` object. In the case that a video file path is _not_ supplied (**Lines 16-18**), we’ll grab a reference to the webcam and wait for it to warm up. And if a video file _is_ supplied, then we’ll create a pointer to it on **Lines 21 and 22**.

Lastly, we’ll end this code snippet by defining a variable called `firstFrame` .

Any guesses as to what `firstFrame` is?

If you guessed that it stores the first frame of the video file/webcam stream, you’re right.

_**Assumption:** The first frame of our video file will contain_ no motion _and_ just background _— therefore, we can model the background of our video stream using only the first frame of the video_.

Obviously we are making a pretty big assumption here. But again, our goal is to run this system on a Raspberry Pi, so we can’t get too complicated. And as you’ll see in the results section of this post, we are able to easily detect motion while tracking a person as they walk around the room.

\# loop over the frames of the video
while True:
	# grab the current frame and initialize the occupied/unoccupied
	# text
	frame = vs.read()
	frame = frame if args.get("video", None) is None else frame\[1\]
	text = "Unoccupied"

	# if the frame could not be grabbed, then we have reached the end
	# of the video
	if frame is None:
		break

	# resize the frame, convert it to grayscale, and blur it
	frame = imutils.resize(frame, width=500)
	gray = cv2.cvtColor(frame, cv2.COLOR\_BGR2GRAY)
	gray = cv2.GaussianBlur(gray, (21, 21), 0)

	# if the first frame is None, initialize it
	if firstFrame is None:
		firstFrame = gray
		continue

So now that we have a reference to our video file/webcam stream, we can start looping over each of the frames on **Line 28**.

A call to `vs.read()` on **Line 31** returns a frame that we ensure we are grabbing properly on **Line 32**.

We’ll also define a string named `text` and initialize it to indicate that the room we are monitoring is “Unoccupied”. If there is indeed activity in the room, we can update this string.

And in the case that a frame is not successfully read from the video file, we’ll break from the loop on **Lines 37 and 38**.

Now we can start processing our frame and preparing it for motion analysis (**Lines 41-43**). We’ll first resize it down to have a width of 500 pixels — there is no need to process the large, raw images straight from the video stream. We’ll also convert the image to grayscale since color has no bearing on our motion detection algorithm. Finally, we’ll apply Gaussian blurring to smooth our images.

It’s important to understand that even _consecutive frames of a video stream will not be identical!_

Due to tiny variations in the digital camera sensors, no two frames will be 100% the same — some pixels will _most certainly_ have different intensity values. That said, we need to account for this and apply Gaussian smoothing to average pixel intensities across an _21 x 21_ region (**Line 43**). This helps smooth out high frequency noise that could throw our motion detection algorithm off.

As I mentioned above, we need to model the background of our image somehow. Again, we’ll make the assumption that the first frame of the video stream contains _no motion_ and is a _good example_ of what our background looks like. If the `firstFrame` is not initialized, we’ll store it for reference and continue on to processing the next frame of the video stream (**Lines 46-48**).

Here’s an example of the first frame of an example video:

[![Image 4: Figure 2: Example first frame of a video file. Notice how it's a still shot of the background, no motion is taking place.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20312'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/first_frame_example_02.jpg)

**Figure 2:** Example first frame of a video file. Notice how it’s a still-shot of the background, no motion is taking place.

The above frame satisfies the assumption that the first frame of the video is simply the static background — no motion is taking place.

Given this static background image, we’re now ready to actually perform motion detection and tracking:

	# compute the absolute difference between the current frame and
	# first frame
	frameDelta = cv2.absdiff(firstFrame, gray)
	thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH\_BINARY)\[1\]

	# dilate the thresholded image to fill in holes, then find contours
	# on thresholded image
	thresh = cv2.dilate(thresh, None, iterations=2)
	cnts = cv2.findContours(thresh.copy(), cv2.RETR\_EXTERNAL,
		cv2.CHAIN\_APPROX\_SIMPLE)
	cnts = imutils.grab\_contours(cnts)

	# loop over the contours
	for c in cnts:
		# if the contour is too small, ignore it
		if cv2.contourArea(c) < args\["min\_area"\]:
			continue

		# compute the bounding box for the contour, draw it on the frame,
		# and update the text
		(x, y, w, h) = cv2.boundingRect(c)
		cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
		text = "Occupied"

Now that we have our background modeled via the `firstFrame` variable, we can utilize it to compute the difference between the initial frame and subsequent new frames from the video stream.

Computing the difference between two frames is a simple subtraction, where we take the absolute value of their corresponding pixel intensity differences (**Line 52**):

_delta = |background\_model – current\_frame|_

An example of a frame delta can be seen below:

[![Image 5: Figure 3: An example of the frame delta, the difference between the original first frame and the current frame.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20221'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/frame_delta_example.jpg)

**Figure 3:** An example of the frame delta, the difference between the original first frame and the current frame.

Notice how the background of the image is clearly _black_. However, regions that contain motion (such as the region of myself walking through the room) is much _lighter_. This implies that larger frame deltas indicate that motion is taking place in the image.

We’ll then threshold the `frameDelta` on **Line 53** to reveal regions of the image that only have _significant_ changes in pixel intensity values. If the delta is less than _25_, we discard the pixel and set it to black (i.e. background). If the delta is greater than _25_, we’ll set it to white (i.e. foreground). An example of our thresholded delta image can be seen below:

[![Image 6: Figure 4: Thresholding the frame delta image to segment the foreground from the background.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20213'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/frame_delta_thresholded.jpg)

**Figure 4:** Thresholding the frame delta image to segment the foreground from the background.

Again, note that the background of the image is black, whereas the foreground (and where the motion is taking place) is white.

Given this thresholded image, it’s simple to apply contour detection to to find the outlines of these white regions (**Lines 58-60**).

We start looping over each of the contours on **Line 63**, where we’ll filter the small, irrelevant contours on **Line 65 and 66**.

If the contour area is larger than our supplied `--min-area` , we’ll draw the bounding box surrounding the foreground and motion region on **Lines 70 and 71**. We’ll also update our `text` status string to indicate that the room is “Occupied”.

	# draw the text and timestamp on the frame
	cv2.putText(frame, "Room Status: {}".format(text), (10, 20),
		cv2.FONT\_HERSHEY\_SIMPLEX, 0.5, (0, 0, 255), 2)
	cv2.putText(frame, datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"),
		(10, frame.shape\[0\] - 10), cv2.FONT\_HERSHEY\_SIMPLEX, 0.35, (0, 0, 255), 1)

	# show the frame and record if the user presses a key
	cv2.imshow("Security Feed", frame)
	cv2.imshow("Thresh", thresh)
	cv2.imshow("Frame Delta", frameDelta)
	key = cv2.waitKey(1) & 0xFF

	# if the \`q\` key is pressed, break from the lop
	if key == ord("q"):
		break

# cleanup the camera and close any open windows
vs.stop() if args.get("video", None) is None else vs.release()
cv2.destroyAllWindows()

The remainder of this example simply wraps everything up. We draw the room status on the image in the top-left corner, followed by a timestamp (to make it feel like “real” security footage) on the bottom-left.

**Lines 81-83** display the results of our work, allowing us to visualize if any motion was detected in our video, along with the frame delta and thresholded image so we can debug our script.

_**Note:** If you download the code to this post and intend to apply it to your own video files, you’ll likely need to tune the values for `cv2.threshold` and the `--min-area` argument to obtain the best results for your lighting conditions._

Finally, **Lines 91 and 92** cleanup and release the video stream pointer.

### Results

Obviously I want to make sure that our motion detection system is working before James, the beer stealer, pays me a visit again — we’ll save that for Part 2 of this series. To test out our motion detection system using Python and OpenCV, I have created two video files.

The first, `example_01.mp4` monitors the front door of my apartment and detects when the door opens. The second, `example_02.mp4` was captured using a Raspberry Pi mounted to my kitchen cabinets. It looks down on the kitchen and living room, detecting motion as people move and walk around.

Let’s give our simple detector a try. Open up a terminal and execute the following command:

$ python motion\_detector.py --video videos/example\_01.mp4

Below is a .gif of a few still frames from the motion detection:

[![Image 7: Figure 5: A few example frames of our motion detection system in Python and OpenCV in action.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20438'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/animated_motion_01.gif)

**Figure 5:** A few example frames of our motion detection system in Python and OpenCV in action.

Notice how that no motion is detected until the door opens — then we are able to detect myself walking through the door. You can see the full video here:

Now, what about when I mount the camera such that it’s looking down on the kitchen and living room? Let’s find out. Just issue the following command:

$ python motion\_detector.py --video videos/example\_02.mp4

A sampling of the results from the second video file can be seen below:

[![Image 8: animated_motion_02](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20614%20448'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/05/animated_motion_02.gif)

**Figure 6:** Again, our motion detection system is able to track a person as they walk around a room.

And again, here is the full vide of our motion detection results:

So as you can see, our motion detection system is performing fairly well despite how simplistic it is! We are able to detect as I am entering and leaving a room without a problem.

However, to be realistic, the results are far from perfect. We get _multiple_ bounding boxes even though there is only _one_ person moving around the room — this is far from ideal. And we can clearly see that small changes to the lighting, such as shadows and reflections on the wall, trigger false-positive motion detections.

To combat this, we can lean on the more powerful background subtractions methods in OpenCV which can actually account for shadowing and small amounts of reflection (I’ll be covering the more advanced background subtraction/foreground detection methods in future blog posts).

**But for the meantime, consider our end goal.**

This system, while developed on our laptop/desktop systems, is meant to be deployed to a Raspberry Pi where the computational resources are very limited. Because of this, we need to keep our motion detection methods _simple_ and _fast_. An unfortunate downside to this is that our motion detection system is not perfect, but it still does a fairly good job for this particular project.

Finally, if you want to perform motion detection on your own raw video stream from your webcam, just leave off the `--video` switch:

$ python motion\_detector.py

**Alternative motion detection algorithms in OpenCV**
-----------------------------------------------------

The motion detection algorithm we implemented here today, while simple, is unfortunately _very sensitive_ to any changes in the input frames.

This is primarily due to the fact that we are grabbing the very first frame from our camera sensor, treating it as our background, and then comparing the background to every subsequent frame, looking for any changes. If a change is detected, we record it as motion.

**However, this method can quickly fall apart if you are working with varying lighting conditions.**

For example, suppose you are monitoring the garage outside your house for intruders. Since your garage is outside, lighting conditions will change due to rain, clouds, the movement of the sun, nighttime, etc.

If you were to choose a _single static frame_ and treat it as your background in such a condition, then it’s likely that within hours (and maybe even minutes, depending on the situation) _that the brightness of the entire outdoor scene would change_, and thus cause false-positive motion detections.

**The way you get around this problem is to maintain a rolling average of the past** **_N_** **frames and treat this “averaged frame” as your background.** You then compare the averaged set of frames to the current frame, looking for substantial differences.

[The following tutorial will teach you how to implement the method I just discussed](https://pyimagesearch.com/2015/06/01/home-surveillance-and-motion-detection-with-the-raspberry-pi-python-and-opencv/).

Alternatively, OpenCV implements a number of background subtraction algorithms that you can use:

*   [OpenCV: How to Use Background Subtraction Methods](https://docs.opencv.org/3.4/d1/dc5/tutorial_background_subtraction.html)
*   [Background Subtraction with OpenCV and BGS Libraries](https://learnopencv.com/background-subtraction-with-opencv-and-bgs-libraries/)

### 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 this blog post we found out that my friend James is a beer stealer. What an asshole.

And in order to catch him red handed, we have decided to build a motion detection and tracking system using Python and OpenCV. While basic, this system is capable of taking video streams and analyzing them for motion while obtaining fairly reasonable results given the limitations of the method we utilized.

The end goal if this system is to deploy it to a Raspberry Pi, so we did not leverage some of the more advanced background subtraction methods in OpenCV. Instead, we relied on a simple yet reasonably effective assumption — _that the first frame of our video stream contains the background we want to model and nothing more._

Under this assumption we were able to perform background subtraction, detect motion in our images, and draw a bounding box surrounding the region of the image that contains motion.

In the **second part** of this series on motion detection, we’ll be **updating this code to run on the Raspberry Pi.**

We’ll also be **integrating with the Dropbox API**, allowing us to monitor our home surveillance system and receive real-time updates whenever our system detects motion.

Stay tuned!

![Image 9](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!