Skip to content

Commit

Permalink
Added functions to perform automatic Canny edge detection, convert a …
Browse files Browse the repository at this point in the history
…URL to image, apply a 4-point perspective transform, and sort contours
  • Loading branch information
jrosebr1 committed May 4, 2015
1 parent cf72609 commit 2d23d20
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 100 deletions.
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ A series of convenience functions to make basic image processing functions such
For more information, along with a detailed code review check out the following posts on the [PyImageSearch.com](http://www.pyimagesearch.com) blog:

- [http://www.pyimagesearch.com/2015/02/02/just-open-sourced-personal-imutils-package-series-opencv-convenience-functions/](http://www.pyimagesearch.com/2015/02/02/just-open-sourced-personal-imutils-package-series-opencv-convenience-functions/)
- [http://www.pyimagesearch.com/2015/03/02/convert-url-to-image-with-python-and-opencv/](http://www.pyimagesearch.com/2015/03/02/convert-url-to-image-with-python-and-opencv/)
- [http://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/](http://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/)
- [http://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/](http://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/)

## Translation
Translation is the shifting of an image in either the *x* or *y* direction. To translate an image in OpenCV you would need to supply the *(x, y)*-shift, denoted as *(t<sub>x</sub>, t<sub>y</sub>)* to construct the translation matrix *M*:
Expand Down Expand Up @@ -50,7 +53,7 @@ for width in (400, 300, 200, 100):
<img src="docs/images/resizing.png?raw=true" alt="Resizing example"/ style="max-width: 500px;">

## Skeletonization
Skeletonization is the process of constructing the "topological skeleton" of an object in an image, where the object is presumed to be white on a black background. OpenCV does not provide a function to explicity construct the skeleton, but does provide the morphological and binary functions to do so.
Skeletonization is the process of constructing the "topological skeleton" of an object in an image, where the object is presumed to be white on a black background. OpenCV does not provide a function to explicitly construct the skeleton, but does provide the morphological and binary functions to do so.

For convenience, the `skeletonize` function of `imutils` can be used to construct the topological skeleton of the image.

Expand Down Expand Up @@ -80,3 +83,45 @@ plt.show()</pre>

#### Output:
<img src="docs/images/matplotlib.png?raw=true" alt="Matplotlib example"/ style="max-width: 500px;">

## URL to Image
This the `url_to_image` function accepts a single parameter: the `url` of the image we want to download and convert to a NumPy array in OpenCV format. This function performs the download in-memory. The `url_to_image` function has been detailed [here](http://www.pyimagesearch.com/2015/03/02/convert-url-to-image-with-python-and-opencv/) on the PyImageSearch blog.

#### Example:
<pre>url = "http://pyimagesearch.com/static/pyimagesearch_logo_github.png"
logo = imutils.url_to_image(url)
cv2.imshow("URL to Image", logo)
cv2.waitKey(0)</pre>

#### Output:
<img src="docs/images/url_to_image.png?raw=true" alt="Matplotlib example"/ style="max-width: 500px;">

## Automatic Canny Edge Detection
The Canny edge detector requires two parameters when performing hysteresis. However, tuning these two parameters to obtain an optimal edge map is non-trivial, especially when working with a dataset of images. Instead, we can use the `auto_canny` function which uses the median of the grayscale pixel intensities to derive the upper and lower thresholds. You can read more about the `auto_canny` function [here](http://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/).

#### Example:
<pre>gray = cv2.cvtColor(logo, cv2.COLOR_BGR2GRAY)
edgeMap = imutils.auto_canny(gray)
cv2.imshow("Original", logo)
cv2.imshow("Automatic Edge Map", edgeMap)</pre>

#### Output:
<img src="docs/images/auto_canny.png?raw=true" alt="Matplotlib example"/ style="max-width: 500px;">

## 4-point Perspective Transform
A common task in computer vision and image processing is to perform a 4-point perspective transform of a ROI in an image and obtain a top-down, "birds eye view" of the ROI. The `perspective` module takes care of this for you. A real-world example of applying a 4-point perspective transform can be bound in this blog on on [building a kick-ass mobile document scanner](http://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/).

#### Example
See the contents of `demos/perspective_transform.py`

#### Output:
<img src="docs/images/perspective_transform.png?raw=true" alt="Matplotlib example"/ style="max-width: 500px;">

## Sorting Contours
The contours returned from `cv2.findContours` are unsorted. By using the `contours` module the the `sort_contours` function we can sort a list of contours from left-to-right, right-to-left, top-to-bottom, and bottom-to-top, respectively.

#### Example:
See the contents of `demos/sorting_contours.py`

#### Output:
<img src="docs/images/sorting_contours.png?raw=true" alt="Matplotlib example"/ style="max-width: 500px;">
30 changes: 24 additions & 6 deletions demo.py → demos/image_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
# website: http://www.pyimagesearch.com

# USAGE
# python demo.py
# BE SURE TO INSTALL 'imutils' PRIOR TO EXECUTING THIS COMMAND
# python image_basics.py

# import the necessary packages
import matplotlib.pyplot as plt
import imutils
import cv2

# load the example images
bridge = cv2.imread("demo_images/bridge.jpg")
cactus = cv2.imread("demo_images/cactus.jpg")
logo = cv2.imread("demo_images/pyimagesearch_logo.jpg")
workspace = cv2.imread("demo_images/workspace.jpg")
bridge = cv2.imread("../demo_images/bridge.jpg")
cactus = cv2.imread("../demo_images/cactus.jpg")
logo = cv2.imread("../demo_images/pyimagesearch_logo.jpg")
workspace = cv2.imread("../demo_images/workspace.jpg")

# 1. TRANSLATION
# show the original image
Expand Down Expand Up @@ -69,4 +70,21 @@
# CORRECT: convert color spaces before using plt.imshow
plt.figure("Correct")
plt.imshow(imutils.opencv2matplotlib(cactus))
plt.show()
plt.show()

# 6. URL TO IMAGE
# load an image from a URL, convert it to OpenCV, format, and
# display it
url = "http://pyimagesearch.com/static/pyimagesearch_logo_github.png"
logo = imutils.url_to_image(url)
cv2.imshow("URL to Image", logo)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 7. AUTO CANNY
# convert the logo to grayscale and automatically detect edges
gray = cv2.cvtColor(logo, cv2.COLOR_BGR2GRAY)
edgeMap = imutils.auto_canny(gray)
cv2.imshow("Original", logo)
cv2.imshow("Automatic Edge Map", edgeMap)
cv2.waitKey(0)
30 changes: 30 additions & 0 deletions demos/perspective_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# author: Adrian Rosebrock
# website: http://www.pyimagesearch.com

# USAGE
# BE SURE TO INSTALL 'imutils' PRIOR TO EXECUTING THIS COMMAND
# python perspective_transform.py

# import the necessary packages
from imutils import perspective
import numpy as np
import cv2

# load the notecard code image, clone it, and initialize the 4 points
# that correspond to the 4 corners of the notecard
notecard = cv2.imread("../demo_images/notecard.png")
clone = notecard.copy()
pts = np.array([(73, 239), (356, 117), (475, 265), (187, 443)])

# loop over the points and draw them on the cloned image
for (x, y) in pts:
cv2.circle(clone, (x, y), 5, (0, 255, 0), -1)

# apply the four point tranform to obtain a "birds eye view" of
# the notecard
warped = perspective.four_point_transform(notecard, pts)

# show the original and warped images
cv2.imshow("Original", clone)
cv2.imshow("Warped", warped)
cv2.waitKey(0)
45 changes: 45 additions & 0 deletions demos/sorting_contours.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# author: Adrian Rosebrock
# website: http://www.pyimagesearch.com

# USAGE
# BE SURE TO INSTALL 'imutils' PRIOR TO EXECUTING THIS COMMAND
# python sorting_contours.py

# import the necessary packages
from imutils import contours
import imutils
import cv2

# load the shapes image clone it, convert it to grayscale, and
# detect edges in the image
image = cv2.imread("../demo_images/shapes.png")
orig = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = imutils.auto_canny(gray)

# find contours in the edge map
(cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)

# loop over the (unsorted) contours and label them
for (i, c) in enumerate(cnts):
orig = contours.label_contour(orig, c, i, color=(240, 0, 159))

# show the original image
cv2.imshow("Original", orig)

# loop over the sorting methods
for method in ("left-to-right", "right-to-left", "top-to-bottom", "bottom-to-top"):
# sort the contours
(cnts, boundingBoxes) = contours.sort_contours(cnts, method=method)
clone = image.copy()

# loop over the sorted contours and label them
for (i, c) in enumerate(cnts):
sortedImage = contours.label_contour(clone, c, i, color=(240, 0, 159))

# show the sorted contour image
cv2.imshow(method, sortedImage)

# wait for a keypress
cv2.waitKey(0)
100 changes: 7 additions & 93 deletions imutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,96 +2,10 @@
# website: http://www.pyimagesearch.com

# import the necessary packages
import numpy as np
import cv2

def translate(image, x, y):
# define the translation matrix and perform the translation
M = np.float32([[1, 0, x], [0, 1, y]])
shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

# return the translated image
return shifted

def rotate(image, angle, center=None, scale=1.0):
# grab the dimensions of the image
(h, w) = image.shape[:2]

# if the center is None, initialize it as the center of
# the image
if center is None:
center = (w / 2, h / 2)

# perform the rotation
M = cv2.getRotationMatrix2D(center, angle, scale)
rotated = cv2.warpAffine(image, M, (w, h))

# return the rotated image
return rotated

def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
# initialize the dimensions of the image to be resized and
# grab the image size
dim = None
(h, w) = image.shape[:2]

# if both the width and height are None, then return the
# original image
if width is None and height is None:
return image

# check to see if the width is None
if width is None:
# calculate the ratio of the height and construct the
# dimensions
r = height / float(h)
dim = (int(w * r), height)

# otherwise, the height is None
else:
# calculate the ratio of the width and construct the
# dimensions
r = width / float(w)
dim = (width, int(h * r))

# resize the image
resized = cv2.resize(image, dim, interpolation=inter)

# return the resized image
return resized

def skeletonize(image, size, structuring=cv2.MORPH_RECT):
# determine the area (i.e. total number of pixels in the image),
# initialize the output skeletonized image, and construct the
# morphological structuring element
area = image.shape[0] * image.shape[1]
skeleton = np.zeros(image.shape, dtype="uint8")
elem = cv2.getStructuringElement(structuring, size)

# keep looping until the erosions remove all pixels from the
# image
while True:
# erode and dilate the image using the structuring element
eroded = cv2.erode(image, elem)
temp = cv2.dilate(eroded, elem)

# subtract the temporary image from the original, eroded
# image, then take the bitwise 'or' between the skeleton
# and the temporary image
temp = cv2.subtract(image, temp)
skeleton = cv2.bitwise_or(skeleton, temp)
image = eroded.copy()

# if there are no more 'white' pixels in the image, then
# break from the loop
if area == area - cv2.countNonZero(image):
break

# return the skeletonized image
return skeleton

def opencv2matplotlib(image):
# OpenCV represents images in BGR order; however, Matplotlib
# expects the image in RGB order, so simply convert from BGR
# to RGB and return
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
from convenience import translate
from convenience import rotate
from convenience import resize
from convenience import skeletonize
from convenience import opencv2matplotlib
from convenience import url_to_image
from convenience import auto_canny
Binary file modified imutils/__init__.pyc
Binary file not shown.

0 comments on commit 2d23d20

Please sign in to comment.