├── .gitignore ├── README.md ├── atlas.json ├── book ├── filters │ ├── adaptive_threshold.md │ ├── basic_output.md │ ├── blur.md │ ├── brightness_contrast.md │ ├── dilate_erode.md │ ├── edge_detection.md │ ├── equalize_histogram.md │ ├── filters-part.html │ ├── in_range.md │ ├── region_of_interest.md │ └── threshold.md ├── projects │ ├── kinograph_1.md │ ├── kinograph_2.md │ ├── kinograph_3.md │ ├── kinograph_4.md │ └── projects-part.html ├── toc.md └── tracking │ ├── background_subtraction.md │ ├── brightest_point.md │ ├── contours_and_lines.md │ ├── face_detection.md │ ├── hsv_color.md │ └── tracking-part.html ├── code ├── brightness_tracking │ ├── BrightnessTracking │ │ ├── BrightnessTracking.pde │ │ └── data │ │ │ └── flashlight.jpg │ └── versions │ │ ├── BrightnessTracking1.pde │ │ ├── BrightnessTracking2.pde │ │ ├── BrightnessTracking3.pde │ │ ├── BrightnessTracking4.pde │ │ ├── LumaVsRGB.pde │ │ ├── image1.png │ │ └── image2.png ├── face_tracking │ ├── FaceTracking │ │ └── FaceTracking.pde │ └── versions │ │ ├── FaceTracking1.pde │ │ ├── FaceTracking2.pde │ │ ├── FaceTracking3.pde │ │ ├── image1.png │ │ ├── image2.png │ │ └── image3.png ├── hsv_color_tracking │ ├── HSVColorTracking │ │ └── HSVColorTracking.pde │ └── versions │ │ └── HSVColorTracking1.pde └── threshold │ ├── Threshold │ ├── Threshold.pde │ └── skulls.png │ └── versions │ ├── Threshold1.pde │ ├── Threshold2.pde │ ├── Threshold3.pde │ ├── Threshold4.pde │ ├── Threshold5.pde │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ └── image5.png ├── index.html ├── scripts ├── brightness_tracking_script.md ├── face_detection_script.md └── hsv_color_script.md └── theme └── html ├── assets └── logo-white.svg ├── javascripts ├── bootstrap.min.js ├── disqus.js └── jquery.js ├── layout.html └── stylesheets └── atlas.css /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.DS_Store 3 | *.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Computer Vision 2 | 3 | This book aspires to provide an easy entry point for creative experimentation with computer vision. It introduces the code and concepts necessary to build computer vision projects with the [Processing](http://processing.org) creative coding environment and [OpenCV](http://opencv.org), the definitive open source computer vision library. It is designed to be accessible to a beginner audience, but also to instill the fundamental concepts of the field, enabling readers to not just follow recipes, but build their own projects. 4 | 5 | This book is being developed in parallel with [OpenCV for Processing](http://github.com/atduskgreg/opencv-processing), a Processing library that provides access to OpenCV. All of its code examples use that OpenCV for Processing and it acts as the definitive documentation for that library. 6 | 7 | _Getting Started with Computer Vision_ is an in-progress community project begun in May of 2013. It is generously supported by [O'Reilly Media](http://oreill.com) and will be published for public consumption by them. It's text is available freely here under a Creative Commons license. 8 | 9 | ## Contributions Welcome 10 | 11 | _Getting Started with Computer Vision_ is currently highly incomplete but under rapid development. Suggestions, contributions, corrections, and pull requests are highly welcome. If you're looking for places to help, check out [the proposed table of contents](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/toc.md) Contributions are especially welcome in the form of examples, particularly in the area of camera calibration and machine learning. 12 | 13 | ## Format 14 | 15 | This book is organized to be useful both as reference and as tutorial. Each page introduces its topic in a manner that should stand alone, providing links to other relevant pages that may be required as prerequisites. The overall order of the book is designed to lead a beginner through the material in a sensible and useful way if read linearly. 16 | 17 | Given the visual nature of computer vision work, each page also takes maximum advantage of the multi-media capabilities of the web to cover its material, including video and interactive code examples as well as text explanations, documentation, and links. 18 | 19 | -------------------------------------------------------------------------------- /atlas.json: -------------------------------------------------------------------------------- 1 | { 2 | "branch": "master", 3 | "files": [ 4 | "book/filters/filters-part.html", 5 | "book/filters/basic_output.md", 6 | "book/filters/blur.md", 7 | "book/filters/dilate_erode.md", 8 | "book/filters/edge_detection.md", 9 | "book/filters/equalize_histogram.md", 10 | "book/filters/in_range.md", 11 | "book/filters/region_of_interest.md", 12 | "book/filters/threshold.md", 13 | "book/tracking/brightest_point.md", 14 | "book/tracking/tracking-part.html", 15 | "book/tracking/face_detection.md", 16 | "book/projects/projects-part.html", 17 | "book/projects/kinograph_1.md" 18 | ], 19 | "formats": { 20 | "pdf": { 21 | "version": false, 22 | "index": false, 23 | "toc": false 24 | }, 25 | "epub": { 26 | "index": false, 27 | "toc": false, 28 | "epubcheck": false 29 | }, 30 | "mobi": { 31 | "index": false, 32 | "toc": false 33 | }, 34 | "html": { 35 | "index": false, 36 | "toc": true 37 | } 38 | }, 39 | "theme": "", 40 | "title": "Getting Started with Computer Vision with OpenCV and Processing" 41 | } -------------------------------------------------------------------------------- /book/filters/adaptive_threshold.md: -------------------------------------------------------------------------------- 1 | # Adaptive Threshold 2 | 3 | ## Related Functions 4 | 5 | * [threshold()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/threshold.md) 6 | * [inRange()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/in_range.md) -------------------------------------------------------------------------------- /book/filters/basic_output.md: -------------------------------------------------------------------------------- 1 | # Basic Output 2 | 3 | Let's start with the absolute basics. Here's a sketch that loads an image file, passes it into OpenCV, and then displays the output. It's structured like most of our OpenCV sketches will be. We create an OpenCV object, pass it the image, and then use it to do filtering, and show the results. See the inline comments for line-by-line details. 4 | 5 | 6 | 7 | And here's the result from running it. 8 | 9 | Screen Shot 2013-07-01 at 5.36.48 PM 10 | 11 | In order to display the results of our work with OpenCV, we call opencv.getOutput(). This function returns a PImage with the current state of the image we're processing with OpenCV. Processing's image() function can display this result just like any other PImage. 12 | 13 | Take note: OpenCV for Processing automatically converted the image to grayscale. It does this by default because a lot of OpenCV operations only work with grayscale images. -------------------------------------------------------------------------------- /book/filters/blur.md: -------------------------------------------------------------------------------- 1 | # Blur 2 | 3 | 4 | 5 | Blurring is a surprisingly useful operation in computer vision. It might seem like blurring an image would obscure exactly the information that you're seeking to extract. To the contrary, blurring is similar to taking an average: it combines the value of each pixel with its neighbors. This is really useful for eliminating small variations or noise that might hurt the results of other operations like edge detection, contour finding, etc. 6 | 7 | ## Blur Strength and Direction - Convolutions and Kernels 8 | 9 | OpenCV's blur() function uses a type of blur known as a "box blur". In order to understand its effects -- and the various options available when applying a blur -- you'll need to know a little bit about how OpenCV applies blurs and other image filters. 10 | 11 | OpenCV blurs an image by applying what's called [a Kernel](http://en.wikipedia.org/wiki/Kernel_(image_processing)). A Kernel tells you how to change the value of any given pixel by combining it with different amounts of the neighboring pixels. The kernel is applied to every pixel in the image one-by-one to produce the final image (this operation known as a [convolution](http://en.wikipedia.org/wiki/Convolution)). 12 | 13 | Kernels are frequently represented with diagrams like this: 14 | 15 | no_blur_kernel 16 | 17 | The middle box represents the current pixel. The values in each box indicate what portion of each pixel should be used to produce the output. The case shown here is the most boring possible kernel. It uses all of the current pixel and none of the neighbors. Applying this kernel would leave the image unchanged. 18 | 19 | Here's the most basic kernel for applying a box blur: 20 | 21 | box_blur_1 22 | 23 | This kernel makes the pixel into an average of itself and each of the eight neighboring pixels. In other words, each of the nine pixels in the kernel contribute 1/9th of the final value. 24 | 25 | We describe this as a kernel size of one since it includes one pixel in each direction away from the current pixel. If we wanted to blur the image more, we could expand the size of our kernel, adding in more adjacent pixels. 26 | 27 | Here's a kernel for a 2-pixel box blur: 28 | 29 | box_blur_2 30 | 31 | Expanding to a 2-pixel radius results in a kernel that covers 25 pixels. Hence, each pixel contributes 1/25th of our final value. 32 | 33 | The GIF at the top of this page showed a blur with a 10-pixel kernel. Here's what a blur with a 50-pixel radius kernel looks like: 34 | 35 | 36 | 37 | In addition to kernels that mix all the pixels equally, we can use other kernels that have different effects. For example, we could construct a kernel that only mixed-in the pixels on the same row as the current pixel like so: 38 | 39 | horizontal_blur 40 | 41 | The result would be a horizontal blur like this: 42 | 43 | 44 | 45 | And likewise, we could do the same thing in the vertical direction with this kernel: 46 | 47 | vertical_blur 48 | 49 | producing something like this: 50 | 51 | 52 | 53 | ### Color Images 54 | 55 | It's also worth noting that, unlike some other filters, blur() can be applied to color images: 56 | 57 | 58 | 59 | ## Parameters 60 | 61 | OpenCV for Processing's blur() function has two different forms. Passing a single argument applies a simple box blur to the image with a kernel size determined by the argument you pass in. 62 | 63 | opencv.blur(10); 64 | 65 | To blur an image vertically or horizontally, you need to create a non-square kernel. The second form of blur() takes two arguments: one for the size of the kernel in the x- and y-dimensions. 66 | 67 | opencv.blur(5, 1); 68 | 69 | would apply a horizontal blur with the kernel we showed above: five pixels wide, all on the same row. 70 | 71 | opencv.blur(1,5); 72 | 73 | would apply a vertical blur: one pixel wide and five tall, just like we saw in the vertical kernel. 74 | 75 | Neither argument to this function can be zero, or an error will be raised. How can you construct a kernel that takes up zero rows or zero columns? 76 | 77 | ## Related Functions 78 | 79 | * [dilate()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/dilate_erode.md) 80 | * [erode()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/dilate_erode.md) 81 | -------------------------------------------------------------------------------- /book/filters/brightness_contrast.md: -------------------------------------------------------------------------------- 1 | # Brightness and Contrast -------------------------------------------------------------------------------- /book/filters/dilate_erode.md: -------------------------------------------------------------------------------- 1 | # Dilate and Erode 2 | 3 | 4 | 5 | Dilation and erosion are both examples of "morphological" filters, functions that affect the shape of areas of pixels in the image. This can be a hard idea to get your head around on first exposure. How can an image filter change the shape of something in the image? 6 | 7 | The key to understanding it is to keep in mind that these filters don't change the shape of the objects pictured in the image; they affect areas in the image where the pixels are similar. Each morphological filter changes the shape of these regions in different ways. 8 | 9 | Dilation (shown above on the left) expands the white areas of the image. Examine the edge of the man's face and the stripes on his pajamas. When the filter is on, there's more white in the areas. The edge of his face spreads and smooths and the stripes on the pajamas get fat. 10 | 11 | (_NOTE: All of these illustrations are applying erosion and dilation to an image that has already been thresholded both because this makes their effects more visible and because those filters are particularly useful in combination. You can run erosion and dilation on non-binary grayscale images and even on color images. See the [threshold page](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/threshold.md) for more on that filter._) 12 | 13 | Erosion does the opposite. It contracts or thins these white areas. Compare the stripes in the image on the right to those on the left. When the erode filter is on, the stripes get thinner, more black is visible between them. And look at the man's left eye. The small spot of white visible within it disappears and the black shadow expands. 14 | 15 | Now, how is this useful? Dilation and erosion are frequently used together in order to close holes in an image. Take a look at this example of them both being applied to a scan in sequence: 16 | 17 | 18 | 19 | On first glance, this appears to have done a lot less than each filter in isolation. But look closely at those stripes on the pajamas. When the filters are applied, the black holes in them get filled up, like they did under dilate, but the stripes don't spread out. In other areas of the image, a bunch of small isolated bits of black and white disappeared. The image is less noisy and the large areas of black or white are smoother and more continuous. -------------------------------------------------------------------------------- /book/filters/edge_detection.md: -------------------------------------------------------------------------------- 1 | # Edge Detection 2 | 3 | OpenCV includes a number of filters meant to reveal the edges of objects including the Canny filter, the Scharr filter, and the Sobel filter. Each of these has different properties and is useful in different situations. 4 | 5 | ## Canny Edge Detection 6 | 7 | Unlike the other two edge detection filters, the Canny filter returns a binary image: all-white pixels where it found an edge, and all-black pixels where it did not find an edge. Also, uniquely, the Canny filter has no directional options 8 | 9 | ## Sobel Edge Detection 10 | 11 | 12 | 13 | Notice the differences between the two modes, how each one affects the sides of the film frame and sprocket holes, the vertical stripes in the actor's shirt, and the lines in the soundtrack on the left of the frame. 14 | 15 | ### Parameters 16 | 17 | The parameters for findSobelEdges() can be a little bit counterintuitive. 18 | 19 | The Sobel edge-detection algorithm works by amplifying changes in the values of neighboring pixels. It can look at horizontal neighbors, vertical neighbors, or both. Counterintuitively, looking for changes between horizontal neighbors ends up detecting vertical edges and vice versa. (Imagine an image with a strong vertical edge in it. The pixels that make up that edge differ strongly not from the ones immediately above and below them (their vertical neighbors) but from those to their left and right (their horizontal neighbors).) 20 | 21 | The findSobelEdges() function takes two arguments, each an integer in the range 0-2. The first argument tells the Sobel algorithm how many horizontal neighbors to look at and the second how many vertical neighbors. The fewer neighbors you look at, the thicker and more intense the edges will be. 22 | 23 | sobel edge direction comparison 24 | 25 | So, putting this into action, we can find horizontal edges like this: 26 | 27 | opencv.findSobelEdges(0,1); 28 | 29 | vertical edges: 30 | 31 | opencv.findSobelEdges(1,0); 32 | 33 | thinner vertical edges: 34 | 35 | opencv.findSobelEdges(2,0); 36 | 37 | and edges in both directions equally: 38 | 39 | opencv.findSobelEdges(1,1); 40 | 41 | ## Scharr Edge Detection 42 | -------------------------------------------------------------------------------- /book/filters/equalize_histogram.md: -------------------------------------------------------------------------------- 1 | # Histogram Equalization 2 | 3 | You've probably seen histograms used to represent information about images in photo editing applications like Photoshop. Histograms show the amount of pixels at each different level of brightness in a complete image or in a particular color channel. 4 | 5 | The following illustration shows four histograms for a picture of me: one for brightness (shown in gray) and one each for the red, green, and blue channels. 6 | 7 | gray, red, green, blue histograms 8 | 9 | Taller regions of the histogram mean that there are more pixels in the image at that level of brightness. Looking at the histograms for this image we can see that it is pretty dark; there are no lines on the rightmost side of any of the histograms where the brightest pixels are represented. You can also see that the red histogram is taller than the green and blue ones. This reflects how much of the image is taken up with my pink-ish face and the red and purple hues of the print on the wall behind me. 10 | 11 | Manipulating this distribution of pixels is a powerful way to change to brightness and contrast of an image. Specifically, OpenCV provides us with a function to "equalize" the histogram. Equalizing a histogram takes its values and spreads them out over the full possible range. Here's the result when we run it on our scanned film frame: 12 | 13 | 14 | 15 | Kind of disappointing, huh? The image is certainly brighter. And, if we look closely, we can see that certain areas have significantly more detail visible (especially the lower partial frame). 16 | 17 | However, if we look at the actual histogram for this image, we can see the effect of equalizing it: 18 | 19 | 20 | 21 | The values of the graph are shifted significantly towards the right, or brighter, side. The equalization had relatively little visible effect because this image was pretty evenly exposed to begin with. Between the dark black of the frame separators, the bright white of the sprocket holes, and the smooth grays of the frame itself, the original scan covered the full range of the histogram. 22 | 23 | Let's look at what histogram equalization does to that badly underexposed image of me from before. 24 | 25 | 26 | 27 | Now, here you can see real results. The image is much brighter and much higher contrast. If we select the right input, histogram equalization can make things much more visible, especially in woefully under or overexposed images. -------------------------------------------------------------------------------- /book/filters/filters-part.html: -------------------------------------------------------------------------------- 1 |
2 |

World of Filters

3 | 4 |

Filters are the fundamental building block of any computer vision system.

5 |
-------------------------------------------------------------------------------- /book/filters/in_range.md: -------------------------------------------------------------------------------- 1 | # In Range 2 | 3 | 4 | 5 | OpenCV's inRange() function is very similar to [threshold()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/threshold.md). It also takes a grayscale image and converts it into a "binary" image: one where every pixel is either all-white or all-black based on whether or not the pixel is within the given range of of values. 6 | 7 | Where threshold() selects for pixels that are above a given value, inRange() lets you specify a maximum as well as a minimum value. 8 | 9 | To see the difference, compare the same image filtered with inRange(35, 50) and with a simple threshold(35): 10 | 11 | 12 | 13 | Where threshold(35) catches the bright near-white of the wall as well as the light gray of my face, inRange(35,50), leaves the wall as black, i.e. un-selected, along with the highlight on the upper-right side of my face. 14 | 15 | ## Use with Color Images and the Hue Channel 16 | 17 | 18 | 19 | Like most of the other OpenCV filter functions, inRange() can be used on any single-channel image, not just grayscale versions of color images. In particular, inRange() is especially useful when applied to the Hue channel. For more about this technique see [Color Tracking in HSV Color Space](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/hsv_color.md). 20 | 21 | ## Parameters 22 | 23 | inRange(int min, int max) takes two arguments: the first represents the lower bound of the range, the second represents the upper bound. It will result in a binary OpenCV output image with white where the pixels were in-range and black where they were out-of-range. 24 | 25 | ## Related Material 26 | 27 | * [threshold()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/threshold.md) 28 | * [adaptiveThreshold()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/adaptive_threshold.md) 29 | * useColor() - sets the color mode. Options are HSB for Hue/Saturation/Brightness and RGB for Red/Green/Blue. 30 | * [Color Tracking in HSV Color Space](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/hsv_color.md) -------------------------------------------------------------------------------- /book/filters/region_of_interest.md: -------------------------------------------------------------------------------- 1 | # Region of Interest 2 | 3 | 4 | 5 | When filtering images, sometimes we don't want to apply our filters to the entire image. Sometimes we'd rather select a particular area of the image just filter it. Maybe we only care about a feature if it appears in a particular spot. Or maybe we're running filtering operations that will have different results depending on their input (like we just saw above with histogram equalization). 6 | 7 | OpenCV lets us select a Region of Interest (ROI) in order to do just that. After we set our ROI, subsequent filters will only apply to that area. In the GIF above you can see our threshold filter applied just to the area around the actors face. 8 | 9 | ## Combined with Histogram Equalization 10 | 11 | 12 | 13 | As we saw, the effects of histogram equalization depend on the range of grays in the image being processed. Our original frame scans have a wide range of gray values and therefore the filter had little effect. But, if we select the right Region of Interest, histogram equalization can really help us bring out the sprocket holes. 14 | 15 | In the GIF above, I've selected a small square on the right as an ROI. This area captures the edge of the frame and one of the sprocket holes. Unlike our earlier attempt on the whole image, running histogram equalization on just this area has a dramatic effect. It significantly darkens the edges of the sprocket hole making it much easier to detect. 16 | 17 | Using an ROI like this, makes our image filters work a little bit more like our eyes, which are constantly refocusing and re-adjusting to different objects and light conditions in front of us. 18 | 19 | ## Parameters 20 | 21 | A Region of Interest is a rectangular area of the image we select to exclusively receive OpenCV operations. Hence, the arguments to setROI() mirror those to rect(), the Processing function for drawing rectangles: x, y, width, height. 22 | 23 | opencv.setROI(400,250,150,150); 24 | opencv.threshold(50); 25 | 26 | _Note: there's also a corresponding opencv.releaseROI() function that ends the use of the Region of Interest. After calling it, further OpenCV functions will affect the entire image._ -------------------------------------------------------------------------------- /book/filters/threshold.md: -------------------------------------------------------------------------------- 1 | # Threshold 2 | 3 | 4 | 5 | Thresholding is maybe the single most common image filter used with OpenCV. It takes a grayscale image and converts it into a "binary" image: one where every pixel is either all-white or all-black based on whether it's lighter or darker than the given threshold. 6 | 7 | Many super-useful OpenCV functions require a binary image, like [contour finding](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/contours_and_lines.md). 8 | 9 | In the animated GIF above, we're applying a threshold at 50 to a scanned frame of film from [the Kinograph project](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/projects/kinograph_1.md). When we threshold our scan, the subtle grays that make up the area with the sprocket holes on the right of the frame disappear completely into white. And the black bars of the frame separators become even more prominent. 10 | 11 | Thresholding is not limited to grayscale images. You can apply it to color images and individual color channels as well (see the code samples below). 12 | 13 | ## Related Functions 14 | 15 | * [adaptiveThreshold()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/adaptive_threshold.md) 16 | * [inRange()](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/in_range.md) 17 | 18 | ## Parameters 19 | 20 | The threshold() function takes a single argument: an integer value from 0-255: 21 | 22 | opencv.threshold(50); 23 | 24 | This value represents a value of gray (0 being black and 255 white). After threshold() is applied, pixels below this value will be black and those above it will be white. 25 | 26 | ## Code 27 | -------------------------------------------------------------------------------- /book/projects/kinograph_1.md: -------------------------------------------------------------------------------- 1 | # Test Case 1: Film Scanner Frame Extractor 2 | 3 | ## Part 1: Understanding the Problem 4 | 5 | ### Kinograph: DIY Film Scanner 6 | 7 | [Kinograph](http://mepler.com/Kinograph) is an open source DIY film scanner currently under development by Matt Epler, an archivist and hacker. Film scanners digitize analogue movies by photographing each frame of film one at a time. Existing models cost upwards of $100,000. Much of the cost arises from the need for precision hardware to place each frame of film in exactly the same location every time. 8 | 9 | Using computer vision, we can reduce the need for this precision. Instead of having to get each film frame into exactly the same spot, we can write a program that finds each frame within an image and extracts it. 10 | 11 | Kinograph's software uses exactly these techniques. The result is that Kinograph's hardware costs less than $1,000 and will be available to many more archivists enabling the preservation of many films that would otherwise be lost. 12 | 13 | In this test case, we'll approach the problem of detecting and extracting frames from images of a film captured with Kinograph. We'll see how OpenCV's image filtering, edge detection, contour detection, and polygon approximation functions can be used to align and extract individual frames from the scanned film. 14 | 15 | ### The Problem: Rotation and Overscan 16 | 17 | Kinograph source image 18 | 19 | Raw Kinograph scans (as seen above) present two main challenges for frame extraction: rotation and overscan. 20 | 21 | As the film moves through the machine it does not always stay perfectly parallel to the camera that captures the scans. In order to correct this problem, we'll have to figure out the orientation of the frame of film within each scan so we can rotate the image to make the frame parallel with its edges. 22 | 23 | Each Kinograph scan also captures more than just the current frame. As you can see in the image above, half of the previous and next frames are also visible. This excess area is called "overscan" in the digitization biz. It's necessary for other processes like stitching the audio track together, but it's exactly what we have to eliminate to produce a video file that looks like the original movie. In order to extract each frame, we'll have to find its borders within the larger scan. 24 | 25 | ### How to Think about the Problem 26 | 27 | So, how can we go about detecting the rotation and frame boundary of each scanned frame? Let's look critically at our example scan above. What parts of this image might help us extract the information we need? 28 | 29 | Well, first of all, we know that we'll be processing multiple frames with different content within each frame. This is a moving picture, after all. So, we need to look for parts of the image that will stay consistent even as the content of the frame changes. 30 | 31 | Secondly, we need parts of the image that stick out sharply from their surroundings. Unlike the human vision system, computer vision techniques are highly sensitive to subtle variations in the image. Uneven exposure, image noise, subtle gradations of brightness -- they can all make image features that seem obvious to the eye dissolve into a mixed-up jumble of pixels. 32 | 33 | And, finally, we need to pick parts of the image that will actually help us find the orientation and location of the image. That means straight lines and other geometric forms that correspond to the edges of the frame and the direction of the strip of film within the scan. 34 | 35 | #### Quiz 36 | 37 | Click on two parts of the image that seem promising for detecting the orientation and location of the frame. Remember, you're looking for parts of the image that are: 38 | 39 | * Consistent even as the content of the frame changes 40 | * Stark enough to be resistant to variations in image quality 41 | * Useful in detecting the orientation and location of the film frame. 42 | 43 | _ANSWER: frame separators, sprocket holes, frame edges_ 44 | 45 | #### Answer 46 | 47 | The three parts of the image that best fit our criteria are: 48 | 49 | * The horizontal frame separators 50 | * The frame's vertical edges 51 | * The sprocket holes 52 | 53 | _CLOSE-UP IMAGE OF FRAME SEPARATOR_ 54 | 55 | As big black horizontal bars, the frame separators are maybe the starkest portion of the image. Their edges make-up straight horizontal lines, so finding them would tell us the orientation of the frame with the scan. And they border the frame itself on top and bottom, giving us half of what we need to find the frame's location. However, the frame separators have a downside too: what happens when the frame goes completely black? Fade outs, credit sequences, and title cards are all common situations in which black areas of the frame would bleed into the frame borders, screwing up our detection. 56 | 57 | _CLOSE-UP IMAGE OF FRAME EDGE_ 58 | 59 | The frame's vertical edges aren't as stark as the separators, but they do contrast nicely with the neighboring translucent area of the film. As straight lines, they also provide solid information about the orientation of the scan. And, as edges of the frame, they give us its left and right bounds. In fact, in combination with the vertical information from the frame separators (or any other source), the frame separators would give us enough to find the complete frame position. 60 | 61 | _CLOSE-UP IMAGE OF SPROCKET HOLE_ 62 | 63 | The last part of the image that seems promising is the sprocket holes. They are geometric in shape and certain of them correspond to the top and bottom of the frame. Compared to the frame separators and edges, they're much lower contrast. On the other hand, they're located on a part of the film that won't be affected by changes within the frame. Also, the sprocket holes were designed specifically to register each frame in the projector so we can rely on them being positioned correctly. One major downside of the sprocket holes is that there are more of them than we need. In order to use them, we'd need a way to distinguish the holes at the top and bottom of the frame from all the others that appear in each scan. -------------------------------------------------------------------------------- /book/projects/kinograph_2.md: -------------------------------------------------------------------------------- 1 | # Test Case 1: Film Scanner Frame Extractor 2 | 3 | ## Part 2: Filtering the Image 4 | 5 | ### Why Filter? 6 | 7 | Now that we know what parts of the image we'd like to work with, how do we go about detecting them? 8 | 9 | In the end, what we're looking for is geometric data: we want the position, orientation, and scale of the film frame so we can extract it from the scan. This will take the form of angles, x-y coordinates, and scaling factors. 10 | 11 | There are lots of techniques for extracting data like these from images -- both simple ones that we can implement ourselves (like checking the brightness of particular pixels) and more complex ones provided by OpenCV (eg. contour finding). 12 | 13 | But before we're ready to use any of those, we need to prepare our image so that those techniques will actually provide us information about the parts of the image we're curious about. To do that, we'll apply a series of filters to our source image meant to bring out the frame separators, frame edges, and sprocket holes. 14 | 15 | ### Image Filters to Find Frame Features 16 | 17 | OpenCV provides a large selection of image filtering functions. In this section, I'll introduce you to the ones that'll be most useful for working with the Kinograph scans. We won't be writing code quite yet -- I just want you to get a feel for each of these filtering functions, how they effect our scans, and how they might be useful. 18 | 19 | These image filters are some of the most commonly used tools in the OpenCV toolbelt. The goal is for you to develop an intuition about when to reach for which tool. We'll be using [thresholding](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/threshold.md), [Sobel edge detection](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/edge_detection.md), [dilation and erosion](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/dilate_erode.md), [histogram equalization](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/equalize_histogram.md), and [region of interest](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/region_of_interest.md). If you haven't used these filters before, review those pages about them before proceeding. -------------------------------------------------------------------------------- /book/projects/kinograph_3.md: -------------------------------------------------------------------------------- 1 | # Test Case 1: Film Scanner Frame Extractor 2 | 3 | ## Part 3: Applying Filters 4 | 5 | We've been talking for awhile here without looking at any code. In this section, we're going to remedy that. We'll look at the basics of using OpenCV in Processing. We'll see how to load up an image, apply filters to it, and see the output. 6 | 7 | Once you've got those basics down, we'll take another look at the filters introduced in the last section. This time, I'll show you the parameters for each one and explain what they do. 8 | 9 | While you're reading keep this question in mind: how could use these filters (alone or in combination) to accentuate the frame borders, sprocket holes, and frame edges in our scan? There'll be a quiz at the end that asks you to do exactly that. 10 | 11 | ### Quiz: Filter for Frame Features 12 | 13 | As we saw in the last section, the purpose of all of these filters is to bring out the parts of an image from which we can derive useful data. In the case of Kinograph, those parts are sprocket holes, the frame borders, and the frame edges. 14 | 15 | In this quiz, your job is to apply what you just learned about these filter functions and their parameters to a Kinograph scan. You can use them individually or in combination. After you've applied your filters, we'll evaluate the results to see how much you've revealed each image feature. Keep trying until you get all three (either with a single combination of filters or in multiple tries). 16 | 17 | _Interactive THINGY!_ -------------------------------------------------------------------------------- /book/projects/kinograph_4.md: -------------------------------------------------------------------------------- 1 | # Test Case 1: Film Scanner Frame Extractor 2 | 3 | ## Part 4: Finding Contours -------------------------------------------------------------------------------- /book/projects/projects-part.html: -------------------------------------------------------------------------------- 1 |
2 |

Projects

3 | 4 |

In this section we explore a number of projects that apply computer vision concepts to real applications.

5 |
-------------------------------------------------------------------------------- /book/toc.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | * Why computer vision? 4 | * More Pixels Law 5 | * Capture, Filter, Calibrate, Track, Shape, Train, Combine 6 | * Follow in the Footsteps of Giants 7 | * What you should know about Processing 8 | 9 | ## World of Filters 10 | 11 | * [Threshold](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/threshold.md) 12 | * [Adaptive Threshold](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/adaptive_threshold.md) 13 | * [Blur](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/blur.md) 14 | * [Dilate and Erode](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/dilate_erode.md) 15 | * [Histogram Equalization](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/equalize_histogram.md) 16 | * [Edge detection](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/edge_detection.md) 17 | * [Brightness/contrast](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/brightness_contrast.md) 18 | * [Region of Interest](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/filters/region_of_interest.md) 19 | 20 | ## Calibrate 21 | 22 | * Barrel and perspective distortion correction 23 | * Calibrating a camera with grids 24 | 25 | ## Track All The Things 26 | 27 | * Deciding which detector to use 28 | * [Color Tracking in HSV Color Space](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/hsv_color.md) 29 | * [Brightness Tracking](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/brightest_point.md) 30 | * Feature Tracking 31 | * [Background Subtraction](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/background_subtraction.md) 32 | * Running average of background 33 | * Motion Detection 34 | * Temporal Coherence for Blobs 35 | * Windowed search 36 | * Skin detection 37 | * [Face detection](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/face_detection.md) 38 | * [Contours and Lines](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/tracking/contours_and_lines.md) 39 | 40 | ## The Shape of Things 41 | 42 | * Point of Maximum Curvature 43 | * Convexity Defects 44 | * Top Pixel 45 | * Contour Winding Holes 46 | * Contour smoothing 47 | 48 | ## Training Program 49 | 50 | * Histograms for SVM (color, contour) 51 | * Histogram of Oriented Gradients for SVM 52 | * K-Nearest Neighbor 53 | * Neural Networks in OpenCV 54 | 55 | ## Projects 56 | 57 | * [Kinograph](https://github.com/atduskgreg/opencv-processing-book/blob/master/book/projects/kinograph_1.md) 58 | * Overhead Robot Tracking 59 | * Food Cart Calendar 60 | * Text Rain 61 | * AR Marker Tracking 62 | * Playing card detection 63 | -------------------------------------------------------------------------------- /book/tracking/background_subtraction.md: -------------------------------------------------------------------------------- 1 | # Background Subtraction 2 | 3 | _When processing video, we frequently want to separate people and objects that move (the foreground) from the fixed environment (the background). Separating foreground from background is an important technique that enables many applications such as motion detection and object and person tracking. Here we learn how background subtraction can separate the foreground from the background in a way that is robust to changes in light and shifts in long-still objects._ 4 | 5 | ## Video Script 6 | 7 | [demo of final version?] 8 | * Separating an image into foreground and background components 9 | 10 | [diff video] 11 | 12 | * A naive way to 13 | 14 | ## Video Summary 15 | 16 | * It's hard to figure out which part of the image is the background 17 | * Naive version: save the background and then do a diff with each frame. 18 | * Need a clean background frame for reference. What happens if objects are permanently removed or shifted? What about as the light changes? 19 | * More sophisticated models of the background: adapt as the background changes. 20 | * We'll use a background subtraction technique built-in to OpenCV that does just that. 21 | * The original paper: [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). 22 | * Lessen the effect of small repetitive motions like moving trees and bushes. 23 | * Adaptive Gaussian Mixture Model - explain each pixel in the scene as the sum of a series of colors that fall off in a Gaussian distribution. Background colors are ones which stay longer and more static. Moving objects change frequently because of the changing angles of their surfaces that reflect the light. 24 | * When using background subtraction, we can choose how many frames affect our calculation of the background and how much effect each new frame should have. 25 | * Background subtraction produces a binary image: white where frame is changing, black elsewhere 26 | * We can then shape up what we find with dilate and erode to close holes in each blob 27 | * And run contour finding on them so we have it as data. 28 | * A next step would be to group together these clusters of contours and to add "temporal coherence": to know how blobs move from frame to frame. 29 | 30 | ## Quiz 31 | Q: In which of the following conditions is background subtraction superior to a simple diff: A) When you have very few frames to work with. B) When light conditions are changing. C) When you need to record the change in color within the image. D) All of the above. 32 | 33 | Answer: B 34 | 35 | Q: Which of these factors will not affect the performance of background subtraction: A) The amount of frames of history taken into account. B) The size of the input image. C) The number of moving objects in the scene. D) All affect it. 36 | 37 | Answer: C 38 | 39 | Q: True or false: background subtraction gives you the location of a particular object as it crosses the scene? 40 | 41 | Answer: False 42 | 43 | ## Code 44 | 45 | ### Important Functions 46 | 47 | * opencv.startBackgroundSubtraction() - Setup the background subtraction process. 48 | * opencv.updateBackground() - Update the background based on the current image. 49 | * opencv.dilate() - Thicken the shapes in the current image. 50 | * opencv.erode() - Thin the shapes in the current image. When used in combination with opencv.dilate() this will close holes. 51 | * opencv.findContours() - Find contours based on the current gray image. 52 | 53 | ### Browse the Code -------------------------------------------------------------------------------- /book/tracking/brightest_point.md: -------------------------------------------------------------------------------- 1 | # Track the Brightest Point 2 | 3 | _Probably the single simplest way to track an object with OpenCV is based on brightness. Brightness tracking is highly sensitive to lighting conditions and other changes in the scene, but it can work effectively in situations where you have a lot of control over the environment. In this section, we'll use OpenCV's max() function to track the brightest point in an image. We'll also see how a variation of brightness tracking, applied to the individual color channels of an image, can be used for a crude form of color tracking._ 4 | 5 | 6 | 7 | ## Video Summary 8 | 9 | * Brightness tracking is one of the simplest ways to track an object. 10 | * It's very fragile (affected by changing lighting conditions) so if you want to use it you'll need to control the environment 11 | * By default, our OpenCV object works off of a grayscale version of the input image. 12 | * Grayscale pixel values range from 0 (for black) to 255 (for white). 13 | * The pixel with the maximum value in this image will be the brightest point. (See the note about Luma as a measure of brightness below.) 14 | * If multiple pixels share the same maximum value (usually all white or all black if the image itself is all black), then the top-leftmost pixel will be selected. 15 | * We can make this dynamic by processing each new frame of a Capture object. 16 | * This technique works for any grayscale image. 17 | * OpenCV gives us the red, blue, and green channels of our image as individual grayscale images. 18 | * Finding the max of these individual channels will find the reddest, greenest, and bluest channel respectively. 19 | * This form of color tracking is very crude because of the limitations of RGB color space 20 | * See the Color Tracking in HSV Color Space for a more robust technique for tracking color. 21 | 22 | ### Luma: A More Accurate Measure of Brightness 23 | 24 | [Luma](http://en.wikipedia.org/wiki/Luma_%28video%29) is a representation of the brightness of a pixel that matches human perception more accurately than its gray value, which is a simple average of the reg, green, and blue components. Luma can be calculated as an unequal combination of the red, green and blue components of the pixel according to the following formula: 0.2126\*R + 0.7152\*G + 0.0722\*B. To use Luma for brightness tracking with our OpenCV image, you could iterate through the image and calculate the Luma value for each pixel, which would be slow. Or, you could use OpenCV's [Imgproc.cvtColor()](http://docs.opencv.org/java/org/opencv/imgproc/Imgproc.html#cvtColor) function in concert with opencv.getBufferColor() to convert the image to the [LAB color space](https://en.wikipedia.org/wiki/Lab_color_space), which includes Luma as one of its three channels. 25 | 26 | This image demonstrates the comparison. The difference can be subtle. Click through to the larger size to make sure you see it. Look carefully at the right side of the image around the flashlight. 27 | 28 | Gray vs Luma 29 | 30 | The code for this example (showing how to access the Luma channel) is here: [LumaVsGray.pde](https://github.com/atduskgreg/OpenCVPro/blob/library_rename/examples/LumaVsGray/LumaVsGray.pde). 31 | 32 | In practice, the grayscale average of RGB is usually used due to its convenience. 33 | 34 | ## Quiz 35 | 36 | Q: What qualities of our input image could cause problems with brightness tracking: A) The presence of many glowing objects. B) Moving shadows cast by passersby. C) The auto-exposure on our camera triggering. D) All of the above. 37 | 38 | Answer: D, all of the above. 39 | 40 | Q: Which are easier to track with this version of color tracking: bright red or dark red objects? 41 | 42 | Answer: Light red. Dark red objects will converge with the shadows in the scene where R, G, and B components are all near 0. 43 | 44 | Q: What are some techniques we could use to prevent the brightest point from jumping around so much when tracking an object with this method? 45 | 46 | Answer: 1) Lerp the x- and y-components of the brightest point between sequential frames. 2) Blur the image before finding the max to smooth over small differences between values. 3) Filter out large jumps in the position of the point, as they're probably due to glitches rather than the continuous motion of the tracked object. 47 | 48 | ## Code 49 | 50 | ### Important Functions 51 | 52 | * opencv.max() - Find the brightest point in the current gray image. 53 | * opencv.setBufferGray() - Set the current gray image to another channel. 54 | * opencv.getBufferG() - Get the green channel of the current image. Useful for passing to opencv.setBufferGray(). 55 | 56 | ### Browse the Code 57 | 58 | 1. capture image and load it into opencv 59 | 2. find and draw the max point 60 | 3. Switch to Capture instead of a still image 61 | 4. Use the green channel to track a green object 62 | 63 | -------------------------------------------------------------------------------- /book/tracking/contours_and_lines.md: -------------------------------------------------------------------------------- 1 | # Finding Contours and Lines 2 | 3 | ## Video Script 4 | 5 | * Figure out the shapes of things 6 | * Enables shape analysis like AR marker detection, shape analysis, image segmentation, perspective analysis, stylization like drawing (Kyle coffee bot) 7 | * 8 | 9 | ## Video Summary 10 | 11 | ## Quiz 12 | 13 | ## Code 14 | 15 | ### Important Functions 16 | 17 | * opencv.findContours() 18 | * contour.getPolygonApproximation() 19 | * contour.getPoints() 20 | * opencv.findLines() 21 | * line.angle 22 | * line.start 23 | * line.end 24 | 25 | ### Browse the Code 26 | -------------------------------------------------------------------------------- /book/tracking/face_detection.md: -------------------------------------------------------------------------------- 1 | # Face Detection 2 | 3 | _Detecting faces in images is one of the most ubiquitous applications of computer vision. We see it in our digital cameras for autofocus, on our social networks to identify our friends, in public surveillance systems, and many other places. In this project, we'll learn how to detect faces using OpenCV. We'll see a little bit about how the face detection process works, how it can fail, and how we can apply it to detect other things besides just faces._ 4 | 5 | 6 | 7 | _Video and images from Adam Harvey's [CV Dazzle](http://cvdazzle.com) used with permission. Clock photo by [jaqian](http://www.flickr.com/photos/jaqian/7292320/) and pedestrian photo by [gandalphcunningham](http://www.flickr.com/photos/gandalfcunningham/2527870434/)._ 8 | 9 | ## Video Summary 10 | 11 | * Face detection in OpenCV is easy to use. 12 | * It is necessary to learn about how it is implemented in order to use it effectively and understand its limitations. 13 | * OpenCV's face detector uses the Viola-Jones algorithm, originally introduced in the 2001 paper [Rapid Object Detection Using a Boosted 14 | Cascade of Simple Features](http://www.merl.com/papers/docs/TR2004-043.pdf). 15 | * Viola-Jones works by matching a defined series of patterns of light and dark rectangles to different areas of the image at different scales. 16 | * Faces have a consistent arrangement of highlights and shadows based on how light falls on the facial features. 17 | * Viola-Jones can be trained to recognize these patterns. 18 | * The training process requires a large set of input images and takes a long time and a lot of computing resources. 19 | * The training process produces a "cascade" file (usually stored as XML), which can be loaded into OpenCV for realtime detection. 20 | * The most commonly used cascade is for frontal faces. 21 | * Artist Adam Harvey's [CV Dazzle](http://cvdazzle.com) project consists of makeup designed to camouflage the wearer from Viola-Jones face detection. 22 | * CV Dazzle works by introducing dark and light patches onto the face where the Viola-Jones frontal face cascade does not expect them to be. 23 | * Harvey produced [visualizations](https://vimeo.com/34545827) that illustrate how the Viola-Jones detection process works. 24 | * For more about CV Dazzle and how the Viola-Jones algorithm works, see [this Makematics interview with Adam Harvey](http://makematics.com/research/viola-jones/). 25 | * Viola-Jones can detect multiple faces. 26 | * Find more faces will slow down the processing of each image. 27 | * Viola-Jones can also be used to detect other objects than faces. 28 | * Researchers have created cascades for noses, ears, pedestrians, clock faces, and many other objects. 29 | 30 | ## The Politics of Face Detection 31 | 32 | Face detection brings out strong, sometimes contradictory, reactions in people. It triggers our fear of being observed, of surveillance by governments, corporations, and others figures of authority. In response to these fears, there's something of a tradition of creative projects that produce inventive ways of avoiding face detection. 33 | 34 | Beyond Adam Harvey's CV Dazzle, demonstrated above, artists [Kyle McDonald](http://kylemcdonald.net) and [Aram Bartholl](http://datenform.de/) of the [F.A.T Lab collective](http://fffff.at/) created a video on "[How To Avoid Facial Recognition](http://fffff.at/how-to-avoid-facial-recognition/)" 35 | 36 | 37 | 38 | Very different in style and tone from CV Dazzle, the combination of humor and punk rock attitude in this video simultaneously expresses and mocks the idea of face detection as a form of technological oppression. 39 | 40 | At the end of the video McDonald and Bartholl demonstrate that most face detection systems fail if the face is tilted by more than 15 degrees or so. This is a practical concern when using OpenCV's face detection functions. It can be overcome by rotating the image before processing, but that technique is computationally expensive and rarely done. 41 | 42 | In addition to the fear of being observed by face detection systems, there's a different kind of fear in being invisible to them. 43 | 44 | In 2009, two employees of a computer store uploaded a video called [HP Computers are Racist](http://www.youtube.com/watch?v=t4DT3tQqgRM) in which they demonstrated an HP MediaSmart computer failing to track the face of a black man, but succeeding on a white woman. 45 | 46 | 47 | 48 | [HP responded](http://www.pcmag.com/article2/0,2817,2357429,00.asp) to the video by explaining the technical roots of the problem and insisting it wasn't intentional: 49 | 50 | >"The technology we use is built on standard algorithms that measure the difference in intensity of contrast between the eyes and the upper cheek and nose. We believe that the camera might have difficulty 'seeing' contrast in conditions where there is insufficient foreground lighting." 51 | 52 | ## Quiz 53 | 54 | Q: Which of the following conditions will not make OpenCV face detection run faster: A) A smaller input image. B) Fewer faces present in the image. C) Good lighting. 55 |

Reveal the Answer

57 | 58 | Q: Is it easier to detect objects with a cascade or to train a new cascade? 59 |

Reveal the Answer

61 | 62 | Q: What is the minimum angle of orientation that will cause the frontal face cascade to fail to detect the face? 63 |

Reveal the Answer

65 | 66 | ## Code 67 | 68 | ### Important Functions 69 | 70 | * opencv.loadCascade() - Setup for face tracking (or other cascade-based tracking). 71 | * OpenCV.CASCADE_FRONTALFACE_ALT - A constant referring to the standard cascade for detecting faces. Pass this to opencv.loadCascade() to setup for face detection. 72 | * opencv.detect() - Detect objects in the current image based on the configured cascade. Returns an array of Rectangle objects. 73 | 74 | ### Browse the Code 75 | 76 | * Detect faces in video 77 | * Scaling trick to do it faster 78 | * Detect clocks with a different cascade 79 | -------------------------------------------------------------------------------- /book/tracking/hsv_color.md: -------------------------------------------------------------------------------- 1 | # Color Tracking in HSV Color Space 2 | 3 | _One of the simplest ways to track an object with OpenCV is based on its color. However, the color space we use to represent the colors in an image can have a big effect on how easy it is to implement color tracking. In this section, we'll prepare an image for color tracking by converting it to HSV color space and filtering it based on a range of Hues. After filtering, we'll be able to easily track our object's position by finding contours._ 4 | 5 | 6 | 7 | ## Video Summary 8 | 9 | * Color tracking is useful when you can control the color of the tracked-object or it already sticks out from the background. 10 | * A "color space" describes how we represent a single color as a mix of different elements. 11 | * The most common color space is RGB. 12 | * RGB is the default color space in Processing. 13 | * RGB is flawed for color tracking because changes in color that look small to the eye effect all three channels significantly. 14 | * HSV is a more useful color space for tracking. 15 | * HSV stands for Hue, Saturation, and Value. 16 | * Hue is color. Saturation is intensity. Value is brightness. 17 | * For color tracking we can just use the Hue channel. 18 | * A Histogram is a graph that shows frequency of each color in an image. 19 | * OpenCV provides that lets us filter an image based on a range of values. 20 | * Filtering a colorful image by a range of hues reveals which hue corresponds to which color. 21 | * Hue 0 is red and then it ranges up through orange, yellow, green, white (around 90), and up to blue. 22 | * In OpenCV, hue ranges from 0-180. 23 | * To track an object in live video we apply a range filter to the hue, selecting the color range of our object. 24 | * Then we find contours in that filtered image. 25 | * The contour with the largest area will be the object we want to track 26 | * We can draw the bounding box of that contour. 27 | * The center of the contour's bounding box will be the object's location. 28 | 29 | ## Quiz 30 | 31 | Q: What is the default color space in Processing? 32 | 33 | Answer: RGB 34 | 35 | Q: Are there colors that can be represented in HSV color space that are impossible to represent in RGB color space? 36 | 37 | Answer: No 38 | 39 | Q: Using OpenCV's scale, what color is represented by the following HSV values: 215, 50, 30? 40 | 41 | Answer: None. Hue ranges from 0-180. 42 | 43 | ## Code 44 | 45 | The code sample below will walk you step-by-step through implementing Hue-based color tracking. 46 | 47 | ### Important Functions 48 | 49 | * opencv.useColor() - Tell OpenCV which color space to use. 50 | * opencv.getBufferH() - Access the Hue channel of the image. 51 | * opencv.setBufferGray() - Set the gray channel of the image to the values of another channel. 52 | * opencv.inRange() - Filter the image based on a range of values. 53 | * opencv.findContours() - Find contours in the current image. 54 | * contour.getBoundingBox() - Get the rectangular bounding box around a particular contour. 55 | 56 | ### Browse the Code 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /book/tracking/tracking-part.html: -------------------------------------------------------------------------------- 1 |
2 |

Track all the Things

3 | 4 |

Tracking allows computer vision systems to follow objects from frame to frame.

5 |
-------------------------------------------------------------------------------- /code/brightness_tracking/BrightnessTracking/BrightnessTracking.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | // Import the OpenCV Improc class, 3 | // it has the cvtColor() function we need. 4 | import org.opencv.imgproc.Imgproc; 5 | 6 | OpenCV opencv; 7 | PImage colorImage, grayImage; 8 | 9 | void setup() { 10 | colorImage = loadImage("flashlight.jpg"); 11 | opencv = new OpenCV(this, colorImage); 12 | size(opencv.width, opencv.height, P2D); 13 | // Save the gray image so we can compare it to Luma 14 | grayImage = opencv.getSnapshot(); 15 | // Use built-in OpenCV function to conver the color image from BGR to LAB color space. 16 | Imgproc.cvtColor(opencv.getColor(), opencv.getColor(), Imgproc.COLOR_BGR2Lab); 17 | // Since the channels start out in the order BGRA, 18 | // Converting to LAB will put the Luma in the B channel 19 | opencv.setGray(opencv.getB()); 20 | } 21 | 22 | void draw() { 23 | background(0); 24 | pushMatrix(); 25 | scale(0.5); 26 | image(colorImage, colorImage.width/2, 0); 27 | image(grayImage, 0, colorImage.height); 28 | image(opencv.getOutput(), colorImage.width, colorImage.height); 29 | popMatrix(); 30 | 31 | fill(255); 32 | text("GRAY", 30, height -25); 33 | text("LUMA", width/2 + 30, height - 25); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /code/brightness_tracking/BrightnessTracking/data/flashlight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/brightness_tracking/BrightnessTracking/data/flashlight.jpg -------------------------------------------------------------------------------- /code/brightness_tracking/versions/BrightnessTracking1.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup() { 6 | opencv = new OpenCV(this, "flashlight.jpg"); 7 | size(opencv.width, opencv.height, P2D); 8 | } 9 | 10 | void draw() { 11 | image(opencv.getOutput(), 0, 0); 12 | } -------------------------------------------------------------------------------- /code/brightness_tracking/versions/BrightnessTracking2.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup() { 6 | opencv = new OpenCV(this, "flashlight.jpg"); 7 | size(opencv.width, opencv.height, P2D); 8 | } 9 | 10 | void draw() { 11 | image(opencv.getOutput(), 0, 0); 12 | PVector brightestPoint = opencv.max(); 13 | 14 | noStroke(); 15 | fill(255, 0, 0); 16 | ellipse(brightestPoint.x, brightestPoint.y, 20, 20); 17 | } -------------------------------------------------------------------------------- /code/brightness_tracking/versions/BrightnessTracking3.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | 4 | Capture video; 5 | OpenCV opencv; 6 | 7 | void setup() { 8 | video = new Capture(this, 1280, 720); 9 | opencv = new OpenCV(this, video.width, video.height); 10 | size(opencv.width, opencv.height, P2D); 11 | 12 | video.start(); 13 | } 14 | 15 | void draw() { 16 | opencv.loadImage(video); 17 | image(opencv.getOutput(), 0, 0); 18 | PVector brightestPoint = opencv.max(); 19 | 20 | noStroke(); 21 | fill(255, 0, 0); 22 | ellipse(brightestPoint.x, brightestPoint.y, 20, 20); 23 | } 24 | 25 | void captureEvent(Capture c) { 26 | c.read(); 27 | } -------------------------------------------------------------------------------- /code/brightness_tracking/versions/BrightnessTracking4.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | 4 | Capture video; 5 | OpenCV opencv; 6 | 7 | void setup() { 8 | video = new Capture(this, 1280, 720); 9 | opencv = new OpenCV(this, video.width, video.height); 10 | size(opencv.width, opencv.height, P2D); 11 | 12 | video.start(); 13 | } 14 | 15 | void draw() { 16 | opencv.loadImage(video); 17 | opencv.setBufferGray(opencv.getBufferG()); 18 | image(opencv.getOutput(), 0, 0); 19 | PVector brightestPoint = opencv.max(); 20 | 21 | noStroke(); 22 | fill(255, 0, 0); 23 | ellipse(brightestPoint.x, brightestPoint.y, 20, 20); 24 | } 25 | 26 | void captureEvent(Capture c) { 27 | c.read(); 28 | } -------------------------------------------------------------------------------- /code/brightness_tracking/versions/LumaVsRGB.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | // Import the OpenCV Improc class, 3 | // it has the cvtColor() function we need. 4 | import org.opencv.imgproc.Imgproc; 5 | 6 | OpenCV opencv; 7 | PImage colorImage, grayImage; 8 | 9 | void setup() { 10 | colorImage = loadImage("flashlight.jpg"); 11 | opencv = new OpenCV(this, colorImage); 12 | size(opencv.width, opencv.height, P2D); 13 | // Save the gray image so we can compare it to Luma 14 | grayImage = opencv.getSnapshot(); 15 | // Use built-in OpenCV function to conver the color image from BGR to LAB color space. 16 | Imgproc.cvtColor(opencv.getColor(), opencv.getColor(), Imgproc.COLOR_BGR2Lab); 17 | // Since the channels start out in the order BGRA, 18 | // Converting to LAB will put the Luma in the B channel 19 | opencv.setGray(opencv.getB()); 20 | } 21 | 22 | void draw() { 23 | background(0); 24 | pushMatrix(); 25 | scale(0.5); 26 | image(colorImage, colorImage.width/2, 0); 27 | image(grayImage, 0, colorImage.height); 28 | image(opencv.getOutput(), colorImage.width, colorImage.height); 29 | popMatrix(); 30 | 31 | fill(255); 32 | text("GRAY", 30, height -25); 33 | text("LUMA", width/2 + 30, height - 25); 34 | } 35 | -------------------------------------------------------------------------------- /code/brightness_tracking/versions/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/brightness_tracking/versions/image1.png -------------------------------------------------------------------------------- /code/brightness_tracking/versions/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/brightness_tracking/versions/image2.png -------------------------------------------------------------------------------- /code/face_tracking/FaceTracking/FaceTracking.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | import java.awt.*; 4 | 5 | Capture video; 6 | OpenCV opencv; 7 | 8 | PImage small; 9 | int scaleFactor = 4; 10 | 11 | void setup() { 12 | size(640, 480, P2D); 13 | video = new Capture(this, 640, 480); 14 | opencv = new OpenCV(this, video.width/scaleFactor, video.height/scaleFactor); 15 | opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE_ALT); 16 | 17 | small = createImage(opencv.width, opencv.height, ARGB); 18 | 19 | video.start(); 20 | } 21 | 22 | void draw() { 23 | image(video, 0, 0 ); 24 | 25 | small.copy(video, 0, 0, video.width, video.height, 0, 0, small.width, small.height); 26 | opencv.loadImage(small); 27 | 28 | noFill(); 29 | scale(scaleFactor); 30 | stroke(0, 255, 0); 31 | Rectangle[] faces = opencv.detect(); 32 | println(faces.length); 33 | 34 | for (int i = 0; i < faces.length; i++) { 35 | rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height); 36 | } 37 | } 38 | 39 | void captureEvent(Capture c) { 40 | c.read(); 41 | } 42 | -------------------------------------------------------------------------------- /code/face_tracking/versions/FaceTracking1.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | import java.awt.*; 4 | 5 | Capture video; 6 | OpenCV opencv; 7 | 8 | void setup() { 9 | size(640, 480, P2D); 10 | video = new Capture(this, 640, 480); 11 | opencv = new OpenCV(this, video.width, video.height); 12 | opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE); 13 | 14 | video.start(); 15 | } 16 | 17 | void draw() { 18 | image(video, 0, 0 ); 19 | 20 | opencv.loadImage(video); 21 | 22 | noFill(); 23 | stroke(0, 255, 0); 24 | Rectangle[] faces = opencv.detect(); 25 | println(faces.length); 26 | 27 | for (int i = 0; i < faces.length; i++) { 28 | rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height); 29 | } 30 | } 31 | 32 | void captureEvent(Capture c) { 33 | c.read(); 34 | } 35 | -------------------------------------------------------------------------------- /code/face_tracking/versions/FaceTracking2.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | import java.awt.*; 4 | 5 | Capture video; 6 | OpenCV opencv; 7 | 8 | void setup() { 9 | size(640, 480, P2D); 10 | video = new Capture(this, 640, 480); 11 | opencv = new OpenCV(this, video.width, video.height); 12 | opencv.loadCascade(OpenCV.CASCADE_RIGHT_EAR); 13 | 14 | video.start(); 15 | } 16 | 17 | void draw() { 18 | image(video, 0, 0 ); 19 | 20 | opencv.loadImage(video); 21 | 22 | noFill(); 23 | stroke(0, 255, 0); 24 | Rectangle[] ears = opencv.detect(); 25 | println(faces.length); 26 | 27 | for (int i = 0; i < ears.length; i++) { 28 | rect(ears[i].x, ears[i].y, ears[i].width, ears[i].height); 29 | } 30 | } 31 | 32 | void captureEvent(Capture c) { 33 | c.read(); 34 | } 35 | -------------------------------------------------------------------------------- /code/face_tracking/versions/FaceTracking3.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | import java.awt.*; 4 | 5 | Capture video; 6 | OpenCV opencv; 7 | 8 | PImage small; 9 | int scaleFactor = 4; 10 | 11 | void setup() { 12 | size(640, 480, P2D); 13 | video = new Capture(this, 640, 480); 14 | opencv = new OpenCV(this, video.width/scaleFactor, video.height/scaleFactor); 15 | opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE); 16 | 17 | small = createImage(opencv.width, opencv.height, ARGB); 18 | 19 | video.start(); 20 | } 21 | 22 | void draw() { 23 | image(video, 0, 0 ); 24 | 25 | small.copy(video, 0, 0, video.width, video.height, 0, 0, small.width, small.height); 26 | opencv.loadImage(small); 27 | 28 | noFill(); 29 | scale(scaleFactor); 30 | stroke(0, 255, 0); 31 | Rectangle[] faces = opencv.detect(); 32 | println(faces.length); 33 | 34 | for (int i = 0; i < faces.length; i++) { 35 | rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height); 36 | } 37 | } 38 | 39 | void captureEvent(Capture c) { 40 | c.read(); 41 | } 42 | -------------------------------------------------------------------------------- /code/face_tracking/versions/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/face_tracking/versions/image1.png -------------------------------------------------------------------------------- /code/face_tracking/versions/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/face_tracking/versions/image2.png -------------------------------------------------------------------------------- /code/face_tracking/versions/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/face_tracking/versions/image3.png -------------------------------------------------------------------------------- /code/hsv_color_tracking/HSVColorTracking/HSVColorTracking.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | import java.awt.Rectangle; 4 | 5 | Capture video; 6 | OpenCV opencv; 7 | ArrayList contours; 8 | 9 | // <1> Set the range of Hue values for our filter 10 | int rangeLow = 20; 11 | int rangeHigh = 35; 12 | 13 | void setup() { 14 | video = new Capture(this, 640, 480); 15 | opencv = new OpenCV(this, video.width, video.height); 16 | size(opencv.width, opencv.height, P2D); 17 | contours = new ArrayList(); 18 | 19 | video.start(); 20 | } 21 | 22 | void draw() { 23 | image(video, 0, 0); 24 | 25 | // <2> Load the new frame of our movie in to OpenCV 26 | opencv.loadImage(video); 27 | // <3> Tell OpenCV to work in HSV color space. 28 | opencv.useColor(HSB); 29 | // <4> Copy the Hue channel of our image into 30 | // the gray channel, which we process. 31 | opencv.setGray(opencv.getH().clone()); 32 | // <5> Filter the image based on the range of 33 | // hue values that match the object we want to track. 34 | opencv.inRange(rangeLow, rangeHigh); 35 | // <6> Display the processed image for reference. 36 | image(opencv.getOutput(), 3*width/4, 3*height/4, width/4, height/4); 37 | // <7> Find contours in our range image. 38 | // Passing 'true' sorts them by descending area. 39 | contours = opencv.findContours(true,true); 40 | // <8> Check to make sure we've found any contours 41 | if (contours.size() > 0) { 42 | // <9> Get the first contour, which will be the largest one 43 | Contour biggestContour = contours.get(0); 44 | // <10> Find the bounding box of the largest contour, 45 | // and hence our object. 46 | Rectangle r = biggestContour.getBoundingBox(); 47 | // <11> Draw the bounding box of our object 48 | noFill(); 49 | strokeWeight(2); 50 | stroke(255, 0, 0); 51 | rect(r.x, r.y, r.width, r.height); 52 | // <12> Draw a dot in the middle of the bounding box, on the object. 53 | noStroke(); 54 | fill(255, 0, 0); 55 | ellipse(r.x + r.width/2, r.y + r.height/2, 30, 30); 56 | } 57 | } 58 | 59 | void captureEvent(Capture c) { 60 | c.read(); 61 | } 62 | -------------------------------------------------------------------------------- /code/hsv_color_tracking/versions/HSVColorTracking1.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | import processing.video.*; 3 | import java.awt.Rectangle; 4 | 5 | Capture video; 6 | OpenCV opencv; 7 | ArrayList contours; 8 | 9 | // <1> Set the range of Hue values for our filter 10 | int rangeLow = 20; 11 | int rangeHigh = 35; 12 | 13 | void setup() { 14 | video = new Capture(this, 640, 480); 15 | opencv = new OpenCV(this, video.width, video.height); 16 | size(opencv.width, opencv.height, P2D); 17 | contours = new ArrayList(); 18 | 19 | video.start(); 20 | } 21 | 22 | void draw() { 23 | image(video, 0, 0); 24 | 25 | // <2> Load the new frame of our movie in to OpenCV 26 | opencv.loadImage(video.image()); 27 | // <3> Tell OpenCV to work in HSV color space. 28 | opencv.useColor(HSB); 29 | // <4> Copy the Hue channel of our image into 30 | // the gray channel, which we process. 31 | opencv.setGray(opencv.getH().clone()); 32 | // <5> Filter the image based on the range of 33 | // hue values that match the object we want to track. 34 | opencv.inRange(rangeLow, rangeHigh); 35 | // <6> Display the processed image for reference. 36 | image(opencv.getOutput(), 3*width/4, 3*height/4, width/4, height/4); 37 | // <7> Find contours in our range image. 38 | // Passing 'true' sorts them by descending area. 39 | contours = opencv.findContours(true, true); 40 | // <8> Check to make sure we've found any contours 41 | if (contours.size() > 0) { 42 | // <9> Get the first contour, which will be the largest one 43 | Contour biggestContour = contours.get(0); 44 | // <10> Find the bounding box of the largest contour, 45 | // and hence our object. 46 | Rectangle r = biggestContour.getBoundingBox(); 47 | // <11> Draw the bounding box of our object 48 | noFill(); 49 | strokeWeight(2); 50 | stroke(255, 0, 0); 51 | rect(r.x, r.y, r.width, r.height); 52 | // <12> Draw a dot in the middle of the bounding box, on the object. 53 | noStroke(); 54 | fill(255, 0, 0); 55 | ellipse(r.x + r.width/2, r.y + r.height/2, 30, 30); 56 | } 57 | } 58 | 59 | void captureEvent(Capture c) { 60 | c.read(); 61 | } 62 | -------------------------------------------------------------------------------- /code/threshold/Threshold/Threshold.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup(){ 6 | opencv = new OpenCV(this, "skulls.png"); 7 | size(opencv.width, opencv.height, P2D); 8 | 9 | opencv.useColor(); 10 | opencv.setGray(opencv.getG()); 11 | opencv.threshold(150); 12 | } 13 | 14 | void draw(){ 15 | image(opencv.getOutput(), 0,0); 16 | } 17 | -------------------------------------------------------------------------------- /code/threshold/Threshold/skulls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/threshold/Threshold/skulls.png -------------------------------------------------------------------------------- /code/threshold/versions/Threshold1.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup(){ 6 | opencv = new OpenCV(this, "skulls.png"); 7 | size(opencv.width, opencv.height, P2D); 8 | } 9 | 10 | void draw(){ 11 | image(opencv.getOutput(), 0,0); 12 | } -------------------------------------------------------------------------------- /code/threshold/versions/Threshold2.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup(){ 6 | opencv = new OpenCV(this, "skulls.png"); 7 | size(opencv.width, opencv.height, P2D); 8 | 9 | opencv.threshold(150); 10 | } 11 | 12 | void draw(){ 13 | image(opencv.getOutput(), 0,0); 14 | } -------------------------------------------------------------------------------- /code/threshold/versions/Threshold3.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup(){ 6 | opencv = new OpenCV(this, "skulls.png"); 7 | size(opencv.width, opencv.height, P2D); 8 | 9 | opencv.useColor(); 10 | } 11 | 12 | void draw(){ 13 | image(opencv.getOutput(), 0,0); 14 | } -------------------------------------------------------------------------------- /code/threshold/versions/Threshold4.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup(){ 6 | opencv = new OpenCV(this, "skulls.png"); 7 | size(opencv.width, opencv.height, P2D); 8 | 9 | opencv.useColor(); 10 | opencv.threshold(150); 11 | } 12 | 13 | void draw(){ 14 | image(opencv.getOutput(), 0,0); 15 | } -------------------------------------------------------------------------------- /code/threshold/versions/Threshold5.pde: -------------------------------------------------------------------------------- 1 | import gab.opencv.*; 2 | 3 | OpenCV opencv; 4 | 5 | void setup(){ 6 | opencv = new OpenCV(this, "skulls.png"); 7 | size(opencv.width, opencv.height, P2D); 8 | 9 | opencv.useColor(); 10 | opencv.setGray(opencv.getG()); 11 | opencv.threshold(150); 12 | } 13 | 14 | void draw(){ 15 | image(opencv.getOutput(), 0,0); 16 | } -------------------------------------------------------------------------------- /code/threshold/versions/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/threshold/versions/image1.png -------------------------------------------------------------------------------- /code/threshold/versions/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/threshold/versions/image2.png -------------------------------------------------------------------------------- /code/threshold/versions/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/threshold/versions/image3.png -------------------------------------------------------------------------------- /code/threshold/versions/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/threshold/versions/image4.png -------------------------------------------------------------------------------- /code/threshold/versions/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atduskgreg/opencv-processing-book/7f4505c52772b4095868fa6201597d2f695c7b64/code/threshold/versions/image5.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | OpenCV Processing Book 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Discuss on GitHub 27 | 28 | 36 | 37 | 38 |
39 | 40 |
41 |
42 |

This project is a practical introduction to computer vision using OpenCV and Processing.

43 |

44 | Read the early draft 45 |

46 |
 
47 |
48 |

Sign up for updates

49 | This project is under active development. If you'd like to be notified about updates, please subscribe to the mailing list. 50 | 51 | 52 | 53 | 58 |
59 |
60 | 61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
75 | Introduction covers: 76 |
    77 |
  • Why computer vision?
  • 78 |
  • More Pixels Law
  • 79 |
  • Capture, Filter, Calibrate, Track, Shape, Train, Combine
  • 80 |
  • Follow in the Footsteps of Giants
  • 81 |
  • What you should know about Processing
  • 82 |
83 |
84 |
 
85 |
86 | 87 |
88 | 101 |
102 | 103 | 104 | 105 |
106 |
107 | 108 |
109 |
110 | The Calibrate section covers: 111 |
    112 |
  • Barrel and perspective distortion correction
  • 113 |
  • Calibrating a camera with grids
  • 114 |
115 |
116 |
 
117 |
118 | 119 |
120 |
121 | The Track all the Things section covers: 122 | 131 |
132 | 133 | 134 | 135 | 136 |
137 |
138 | 139 |
140 |
141 | The Shape of Things section covers: 142 |
    143 |
  • Point of Maximum Curvature
  • 144 |
  • Convexity Defects
  • 145 |
  • Top Pixel
  • 146 |
  • Contour Winding Holes
  • 147 |
  • Contour smoothing
  • 148 |
149 |
150 |
 
151 |
152 | 153 |
154 |
155 | The Training Program section covers: 156 |
    157 |
  • Histograms for SVM (color, contour)
  • 158 |
  • Histogram of Oriented Gradients for SVM
  • 159 |
  • K-Nearest Neighbor
  • 160 |
  • Neural Networks in OpenCV
  • 161 |
162 |
163 | 164 |
165 | 166 |
167 |
168 | 169 |
170 |
171 | The Projects section covers: 172 |
    173 |
  • Kinograph
  • 174 |
  • Overhead Robot Tracking
  • 175 |
  • Food Cart Calendar
  • 176 |
  • Text Rain
  • 177 |
  • AR Marker Tracking
  • 178 |
  • Playing card detection
  • 179 |
180 |
181 |
 
182 |
183 | 184 | 185 |
186 | 187 | 188 |
189 | 190 | 191 | 192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 | 200 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /scripts/brightness_tracking_script.md: -------------------------------------------------------------------------------- 1 | ### Video Script 2 | 3 | [brightness tracking video] 4 | 5 | * In this video, we're going to track the brightest object in an image. 6 | * Brightness tracking is one of the simplest forms of object tracking. 7 | * It's also very fragile. Changes in lighting conditions or the presence of other bright objects can totally wreck its effectiveness. 8 | * But in a controlled environment, where you can attach a light to the object you want to track (or where that object glows already) it can work quite well. 9 | 10 | [still image: color] 11 | 12 | * We'll start by looking at a single still frame. 13 | 14 | [still image: gray] 15 | 16 | * By default, OpenCV for Processing applies all image processing operations to the grayscale version of the input image. 17 | * A lot of computer vision techniques only work on single channel images, and the one we'll be using to find the brightest pixel is no exception. 18 | 19 | * The pixels in a grayscale image range from 0 to 255 in value, with 0 being black and 255 being white. 20 | 21 | [still image: max()] 22 | 23 | * OpenCV provides a function called max which finds the pixel with the highest value along this spectrum. 24 | * When we're looking at the grayscale image, that'll be the brightest pixel. 25 | * If there are multiple pixels in the image that share the maximum value, we'll find the top-leftmost one. 26 | 27 | [brightness tracking video] 28 | 29 | * When we apply this to every frame from a live camera, we get interactive tracking of the brightest object. 30 | * Notice how when I remove the flashlight, this technique still finds a point to track. There's always a brightest pixel in any image. 31 | 32 | [green guy in color] 33 | 34 | * We can use a variation of this technique for a crude form of color tracking. 35 | * For example, under good lighting and background conditions, we can track this neon green 3D print of my head. 36 | * Notice that I've changed into a white t-shirt, improved the lighting, and cleaned up a little bit behind me to create the clean background necessary to make this work. 37 | 38 | [R, G, and B channels split out] 39 | 40 | * This max() pixel technique we've been using works on any grayscale image 41 | * In addition to a gray image representing brightness, OpenCV will also give us the red, green, and blue channels of our input image. 42 | * Each one of these is represented by their own grayscale image that I'm tinting their respective color here. 43 | * Look at how bright the 3D print is in the green channel. It's actually brighter than my white t-shirt. 44 | 45 | [green-tracking video] 46 | 47 | * If we load the green channel from each frame of video into opencv, then that same max function will find the greenest point in our image. 48 | * As long as this neon green 3D print is present, that point will track it. 49 | * If we had objects of equally strong red and blue color we could track them as well independently. 50 | * When it comes to real applications, this is a very crude tracking methods because of the limitations of RGB color space. 51 | * See the Color Tracking in HSV Color Space example for a more robust technique for tracking color. -------------------------------------------------------------------------------- /scripts/face_detection_script.md: -------------------------------------------------------------------------------- 1 | ### Face Tracking Video Script 2 | 3 | [Face detection running] 4 | 5 | * In this project we'll learn how to detect faces in still images and live video. 6 | * OpenCV includes a function for detecting faces that's very easy to use. 7 | * However in order to understand its limitations and the situations where it will work, you need to learn a little bit about how face detection actually works. 8 | 9 | [viola-jones paper scroll?] 10 | 11 | * OpenCV's face detector uses an algorithm called Viola-Jones that was originally published in this paper here. 12 | * It works by matching patterns of light and dark squares to different areas of the image at different scales. 13 | * Faces have a consistent arrangement of highlights and shadows based on how light falls on the nose, cheeks, forehead, and other facial features. 14 | * The Viola-Jones algorithm can be trained to recognize these patterns by being shown lots of pictures of faces. This process is very resource intensive and takes a long time to complete. 15 | * When complete, this training process results in a file called a "cascade", which can be loaded into OpenCV and used for realtime detection without having to go through the painstaking process of training each time. 16 | * The cascade that's used most often is trained on frontal faces. 17 | 18 | 19 | 20 | [cv dazzle makeup image] 21 | 22 | * For his CV Dazzle project, artist Adam Harvey designed makeup that camouflages the wearer from Viola-Jones face detection. 23 | * Harvey's designs work by introducing dark and light patches on the face in areas where they don't usually appear, frustrating the detection algorithm. 24 | 25 | [cv dazzle before video] 26 | 27 | * In order to design these patterns, Harvey created visualizations, like this one, that clearly show how the Viola-Jones algorithm works. 28 | * What you're seeing here represents what happens when we detect faces just slowed down to the point where we can understand it. 29 | * The algorithm is searching across the image, in different locations and scales. 30 | * At each point, it compares a series of black and white patterns with the image, looking for equivalent areas of brightness and darkness of the image. 31 | * When it finds enough of these, overlapping just the right way, it's matched a face. 32 | 33 | [itp picture] 34 | 35 | * This detection technique isn't limited to just one face. 36 | * Here you can see it finding 79 faces in a single image. 37 | * It will find every face in the image that it can. Keep in mind, though: the more faces that are present, the longer it will take to process each image. 38 | 39 | [Alternative cascade montage] 40 | 41 | * This technique is not just limited to whole faces. 42 | * Researchers have trained cascades for: 43 | * individual parts of the face like nose and ears 44 | * Pedestrians on the street 45 | * even clock faces. 46 | 47 | [face detection running] 48 | 49 | * Face detection is one of the most useful and most widely deployed computer vision techniques in the world. 50 | * In another project, we'll look at how to keep track of which face is which as we track them over time. -------------------------------------------------------------------------------- /scripts/hsv_color_script.md: -------------------------------------------------------------------------------- 1 | ### Color Tracking with HSV Video Script 2 | 3 | [Complete tracking dot video] 4 | * Goal: Track objects based on their color 5 | * Useful whenever you can control the color of the object you want to track or it already sticks out from the background on its own 6 | 7 | * What you're seeing here is the final result: a dot that tracks our orange bottle as I move it around the screen. 8 | 9 | * Our approach will be based on using an alternative color space called 'HSV' for Hue, Saturation, Value. 10 | 11 | [Graph of RGB color space] 12 | * What's a 'color space'? 13 | * A color space describes how we represent a single color as a mix of different elements 14 | * The Most common one is RGB: each color as a mix of red, green, and blue 15 | * This is the one you're used to using with the Processing color() function. 16 | * Each of the arguments represents one axis of the RGB color space and the combination of them produces the final color 17 | 18 | [image color picker] 19 | * The problem with using RGB color space for color tracking is that even small changes in perceived color will affect all three axes of the color space. 20 | * See how much the red, green, and blue levels of each pixel varies, even amongst the pixels on the orange juice bottle? 21 | * We'd have to do some sophisticated processing on these numbers to distinguish the orange bottle from the rest of the image. 22 | 23 | [HSV color space picture] 24 | * Luckily there are other color spaces. 25 | 26 | * HSV color space, for example, represents the Hue (or color) with just one axis. 27 | * The other two axes represent Saturation (or intensity) and Value (or brightness) 28 | 29 | * When we explore the HSV levels of our orange bottle, we can see that the Hue stays basically the same, while the Saturation and Value change where the bottle is darker or lighter. 30 | 31 | * Working in HSV color space means that we only have to process the Hue channel to track objects by their color, making our programming job much easier and our results much more reliable. 32 | 33 | * Now how can we apply this with OpenCV? 34 | * Here's Processing sketch showing a picture of some colorful balls 35 | * On the left here, we've got a graph showing us which Hues show up most in the image. 36 | * This sketch is using OpenCV to filter the image based on the range of Hues we select 37 | * You can see the output of that selection on the right here 38 | * The white is what's within the selected range, the black is everything else. 39 | * Now look at our hue histogram. It's mostly flat at 0, but there are a few spikes 40 | * The biggest spike here, around 90, represents white. 41 | * Selecting it filters out the balls altogether. 42 | * All the way on the left here, near hue 0, is blue. 43 | * And, as we move to the right we pass through green to yellow, through white to orange and red. 44 | * The values top off at 180. 45 | * Unlike RGB which go to 255, OpenCV represents hue in a range from 0 to 180. 46 | 47 | * Let's use this to track a colored object in video! 48 | 49 | * Once we've figured out the range that contains the color we want to track, we can use it to filter each frame of our video. 50 | * In this case the hue of my orange juice bottle ranged between 20 and 35. 51 | * The image in the bottom right shows the video's Hue channel filtered by that range. The orange bottle jumps right out from the background. 52 | 53 | * After filtering the hue channel for our selected range, we can look for contours in the resulting image. 54 | * The center of the biggest contour will be the position of the object we want to track. 55 | 56 | * Here's the bounding box around the largest contour we found in that filtered image. 57 | 58 | * And here's the center of that bounding box displayed on top of our input video. 59 | 60 | * Boom. A dot that tracks my orange juice bottle. 61 | -------------------------------------------------------------------------------- /theme/html/assets/logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 20 | 23 | 25 | 27 | 28 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /theme/html/javascripts/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('