Monday, April 22, 2013

Watermarking images with OpenCV and NumPy

Here's a simple Python example of adding a watermark to an image using NumPy and OpenCV. I created a black/white version of the OpenCV logo for this example but feel free to use any image you like. In this case I wanted to have a white watermark image, white text, and the rest of the image unchanged. If you have watermark images in color or grayscale the same process should work. Here's the code using the OpenCV license plate sample image:
import numpy as np
import cv2

# read images
original = cv2.imread('data/licenseplate_motion.jpg')
mark = cv2.imread('logo.png') 

m,n = original.shape[:2]

# create overlay image with mark at the upper left corner, use uint16 to hold sum
overlay = np.zeros_like(original, "uint16")
overlay[:mark.shape[0],:mark.shape[1]] = mark

# add the images and clip (to avoid uint8 wrapping)
watermarked = np.array(np.clip(original+overlay, 0, 255), "uint8")

# add some text 5 pixels in from the bottom left
cv2.putText(watermarked, "Watermarking with OpenCV", (5,m-5), cv2.FONT_HERSHEY_PLAIN, fontScale=1.0, color=(255,255,255), thickness=1)

cv2.imshow("original", original)
cv2.imshow("watermarked", watermarked)
cv2.waitKey()
cv2.destroyAllWindows()
Note that I created an array of type "uint16" to hold the sum of the two images before clipping with NumPy's clip function. The text is added with OpenCV's putText function which will need position, font (out of the available OpenCV fonts) and text scale and a color tuple (here white). The result looks like this:

Tuesday, September 18, 2012

Installing PIL on OS X Mountain Lion

Installing PIL, the Python Imaging Library, on OS X sometimes gives problems with missing JPEG support. I have run into this problem before but could not find my solution so I'm adding it here as a note to self with "PIL" and "install" tags so I can locate the trick again in the future.
Here's the procedure.

1. Get libjpeg from http://www.ijg.org/files/jpegsrc.v8c.tar.gz. Then in the unpacked folder:
./configure
make
sudo make install

2. Download PIL from http://effbot.org/downloads/Imaging-1.1.7.tar.gz. (Remove PIL first if installed already.)

3. In the unpacked folder:
python setup.py build --force
sudo python setup.py install
The magic part for me was "--force". Without it it doesn't work. If you still don't get "JPEG support available" in the console when you run the first build command, then you can try adding the path to libjpeg in setup.py (basically just replace the "None" with your path). For example like this:
JPEG_ROOT = "usr/local/lib"

Sunday, August 12, 2012

Reading Gauges - Detecting Lines and Circles

I received a question from a reader on how I would approach reading a simple gauge with one needle on a good frontal image of a circular gauge meter. This makes a good example to introduce Hough transforms. Detecting circles or lines using OpenCV and Python is conceptually simple (each particular use-case requires some parameter tuning though). Below is a simple example using the OpenCV Python interface for detecting lines, line segments and circles. The documentation for the three relevant functions are here. You can also find more on using the Python interface and the plotting commands in Chapter 10 of my book.
import numpy as np
import cv2

"""
Script using OpenCV's Hough transforms for reading images of 
simple dials.
"""

# load grayscale image
im = cv2.imread("gauge1.jpg")
gray_im = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)

# create version to draw on and blurred version
draw_im = cv2.cvtColor(gray_im, cv2.COLOR_GRAY2BGR)
blur = cv2.GaussianBlur(gray_im, (0,0), 5)

m,n = gray_im.shape

# Hough transform for circles
circles = cv2.HoughCircles(gray_im, cv2.cv.CV_HOUGH_GRADIENT, 2, 10, np.array([]), 20, 60, m/10)[0]

# Hough transform for lines (regular and probabilistic)
edges = cv2.Canny(blur, 20, 60)
lines = cv2.HoughLines(edges, 2, np.pi/90, 40)[0]
plines = cv2.HoughLinesP(edges, 1, np.pi/180, 20, np.array([]), 10)[0]

# draw 
for c in circles[:3]:
 # green for circles (only draw the 3 strongest)
 cv2.circle(draw_im, (c[0],c[1]), c[2], (0,255,0), 2) 

for (rho, theta) in lines[:5]:
 # blue for infinite lines (only draw the 5 strongest)
 x0 = np.cos(theta)*rho 
 y0 = np.sin(theta)*rho
 pt1 = ( int(x0 + (m+n)*(-np.sin(theta))), int(y0 + (m+n)*np.cos(theta)) )
 pt2 = ( int(x0 - (m+n)*(-np.sin(theta))), int(y0 - (m+n)*np.cos(theta)) )
 cv2.line(draw_im, pt1, pt2, (255,0,0), 2) 

for l in plines:
 # red for line segments
 cv2.line(draw_im, (l[0],l[1]), (l[2],l[3]), (0,0,255), 2)
  
cv2.imshow("circles",draw_im)
cv2.waitKey()

# save the resulting image
cv2.imwrite("res.jpg",draw_im)
This will in turn; read an image, create a graylevel version for the detectors, detect circles using HoughCircles(), run edge detection using Canny(), detect lines with HoughLines(), detect line segments with HoughLinesP(), draw the result (green circles, blue lines, red line segments), show the result and save an image. The result can look like this: From these features, you should be able to get an estimate on the gauge reading. If you have large images, you should probably scale them down first. If the images are noisy, you should adjust the blurring for the edge detection. There are also threshold parameters to play with, check the documentation for what they mean. Good luck.

Monday, June 25, 2012

Book: Programming Computer Vision with Python

Finally, after many nights and weekends, my O'Reilly book is out! You can buy it from O'Reilly here.

Thanks to everyone who helped with feedback and comments on the draft versions I put online. The code and datasets are available from programmingcomputervision.com.

Sunday, June 24, 2012

Arnold's Cat Map

Arnold's cat map is a fun mapping to randomize images by shuffling the pixels around. It distorts the image by shearing and then moves the pieces outside the original image region back using the integer mod function. Applied iteratively, this results in randomizing the image in a way that eventually returns the original. Here's a code example that iteratively applies the mapping and saves intermediate images:
import Image
from numpy import *
from scipy.misc import lena,imsave

# load image
im = array(Image.open("cat.jpg"))
N = im.shape[0]

# create x and y components of Arnold's cat mapping
x,y = meshgrid(range(N),range(N))
xmap = (2*x+y) % N
ymap = (x+y) % N

for i in xrange(N+1):
 imsave("cat_{0}.png".format(i),im)
 im = im[xmap,ymap]
Tradition has it that this mapping should be applied to pictures of cats. (The name comes from Vladimir Arnold, who demonstrated the mapping on an image of a cat)
Cat image (128*128) at iteration 0, 1, 64, 126, 127, 128. (original image here, credit CC Flickr @outlier*)

Sunday, June 3, 2012

Simple Panoramas with OpenCV

The latest 2.4 release of OpenCV contains some good stuff. The stitching module is really useful. In the OpenCV sample folder there is a great script called stitching_detailed.cpp. This does the whole pipeline for creating panoramas including feature extraction, matching, warping and blending. If you have OpenCV properly installed you can compile the file for your platform and try it out from your command line like this:

$ ./stitching_detailed Univ*.jpg

This will use the default parameters and settings and create a file result.jpg with your panorama created from all jpeg images starting with "Univ". To see the defaults and what options you can set:

$ ./stitching_detailed --help

For example, you can change the projection used (default is spherical). This will use a Mercator projection:

$ ./stitching_detailed Univ*.jpg --warp mercator

Here are some sample results using these images of the University building in Lund. This is the same example used in Chapter 3.3 of my book.


Cylindrical projection.

Planar projection.

Mercator projection.

Wednesday, March 21, 2012

Isomap with scikit-learn

The scikit-learn project for Python contains many machine learning algorithms neatly packaged. There are several manifold learning algorithms in the manifold module. Here's a simple example of Isomap on the classic Swiss roll 3D point set.

from pylab import *
from mpl_toolkits.mplot3d import Axes3D
from sklearn import manifold, datasets

"""
Based on scikits.learn example at
http://scikit-learn.org/stable/auto_examples/manifold/plot_swissroll.html
"""

# load Swiss roll dataset
X, color = datasets.samples_generator.make_swiss_roll(n_samples=1500)

# run Isomap on the points in X with 2 dim output
n_neighbors = 10
Y = manifold.Isomap(n_neighbors, 2).fit_transform(X)

# 3D plot
fig = figure()
ax = fig.gca(projection='3d')
ax.scatter(X[:,0], X[:,1], X[:,2], c=color)

# 2D projection
figure()
scatter(Y[:,0], Y[:,1], c=color)

show()

The code generates these plots (3D plotting with Matplotlib/Pylab is explained here).


As you can see Isomap finds a nice 2D manifold. Pretty neat.