Friday, 1 November 2013

Python - Getting started with OpenCV

A couple of years ago a colleague of mine created a program to ensure that an item I had designed was calibrated properly. The program used a webcam to check a bracket was in the right position and reported back a pass or a fail. I did not know much about the workings of the program, other than it used something called a "Hough Transform". As a non-programmer at the time, I was impressed. I thought this was a very cool program.

A few weeks ago I needed to do something similar. I wanted to check that I could repeatedly position an item in the same location. My colleague has long left the company, so I thought it might be a good opportunity to see if I could write a similar program in Python. It was also an opportunity to finally put my Raspberry Pi camera module to good use.

If you don't have a Raspberry Pi camera, don't worry, you can test this out on pictures taken with a normal camera. I will also supply you some images later on for you to use. One of these I took with a camera phone, so you could do the same if you want to practice.

My starting point for this program was I knew that my colleague had used a Hough Transform, and that there were some good vision libraries available called openCV. I also knew these were available on Python. My program uses a Hough Circle Transform as opposed to a Hough Line Transform.

The objective I was trying to achieve was to be able to check the position of an item and to determine its offset in x, y and any rotation (theta) of the object.

I knew I was able to add some fiducial to the item, so opted for two circles. To make my circles a universal size, rather than drawing around coins to make my circles, I used the inside of a CD as a template. However I will show you how to tweak your program for other circles later!



The first thing you need to do it to install the openCv libraries onto your Raspberry Pi.

sudo apt-get install libopencv-dev python-opencv

To begin with I struggled to find information to get me started, and there seemed to be a lot of confusing information scattered about the web. What I did find out which makes things a little easier to understand is that openCV has released two types of Python interface called cv and cv2. If you are googling for further information it's worth keeping this in mind. We are going to use cv2 in this tutorial.

First of all here is my code and then we will analyse it line by line.

import os
import cv2
import math

##Resize with resize command
def resizeImage(img):
    dst = cv2.resize(img,None, fx=0.25, fy=0.25, interpolation = cv2.INTER_LINEAR)
    return dst

##Take image with Raspberry Pi camera
os.system("raspistill -o image.jpg")

##Load image
img = cv2.imread("/home/pi/Desktop/image.jpg") 
grey = cv2.imread("/home/pi/Desktop/image.jpg",0) #0 for grayscale

##Run Threshold on image to make it black and white
ret, thresh = cv2.threshold(grey,50,255,cv2.THRESH_BINARY)

##Use houghcircles to determine centre of circle
circles = cv2.HoughCircles(thresh,cv2.cv.CV_HOUGH_GRADIENT,1,75,param1=50,param2=13,minRadius=0,maxRadius=175)
for i in circles[0,:]:
    #draw the outer circle
    cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2)
    #draw the centre of the circle
    cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)

##Determine co-ordinates for centre of circle
x1 = circles[0][0][0]
y1 = circles[0][0][1]
x2 = circles[0][1][0]
y2 = circles[0][1][1]
##Angle betwen two circles
theta = math.degrees(math.atan((y2-y1)/(x2-x1)))

##print information
print "x1 = ",x1
print "y1 = ",y1
print "x2 = ",x2
print "y2 = ",y2
print theta
print circles

##Resize image
img = resizeImage(img)
thresh = resizeImage(thresh)
##Show Images 
cv2.imshow("thresh",thresh)
cv2.imshow("img",img)

cv2.waitKey(0)

First we import 3 modules - os, cv2 and math.
import os
import cv2
import math

Now we create a function to resize images. Although we do our analysis on the full image, we will make the images smaller before we display them on the screen.

##Resize with resize command
def resizeImage(img):
    dst = cv2.resize(img,None, fx=0.25, fy=0.25, interpolation = cv2.INTER_LINEAR)
    return dst

There is a more in-depth explanation of the resize function on the geometric image transformations help page in the openCV documentation.

http://docs.opencv.org/modules/imgproc/doc/geometric_transformations.html

However the important things to note are:
  • img is the image we want to resize.
  • fx=0.25 and fy=0.25 are the factors that x and y are multiplied by. 0.25 makes the image 1/4 size.

Next we take an image using the Raspberry Pi camera.

##Take image with Raspberry Pi camera
os.system("raspistill -o image.jpg")

os.system allows us to input a command into the command line. We know from the camera documentation that "raspistill -o image.jpg" will take an image with the camera and store it as image.jpg.

Now we load the image twice into our program. The first time as a colour image, the second as a grey-scale image.

##Load image
img = cv2.imread("/home/pi/Desktop/image.jpg") 
grey = cv2.imread("/home/pi/Desktop/image.jpg",0) #0 for grayscale

We then use cv2.threshold to turn our image into black and white.

##Run Threshold on image to make it black and white
ret, thresh = cv2.threshold(grey,50,255,cv2.THRESH_BINARY)

There is more information about the threshold function on the transformations help page on the openCV documentation.

http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html

The important parts of the code are:
  • grey - the image we are converting.
  • 50 - is the threshold value. This is between 0 and 255. 0 is white and 255 is black. You may need to modify this value depending on your lighting conditions.
  • 255 - If a pixel is above the threshold value, in our case 50, we will make it 255 (black). Else is it 0 (white)
On this black and white image we now run hough circles.

This line in the code carries out the HoughCircle transform. This is the most important line in the code, and the one you are most likely to have to modify to suit your image.

##Use houghcircles to determine centre of circle
circles = cv2.HoughCircles(thresh,cv2.cv.CV_HOUGH_GRADIENT,1,75,param1=50,param2=13,minRadius=0,maxRadius=175)

  • thresh - refers to the fact we are carrying out the hough transform on the black and white image.
  • 75 - refers to the minimum distance allowed between circles. If you are getting too many circles close together you may want to increase this and vice versa.
  • Param1 = 50. This is one of the parameters which determines where the circles are, you can play around with this figure if needs be.
  • Param2 = 13. This is the more important of the parameters. If you are getting too many circles then increase this number, and vice versa. Small changes to this make large differences!
  • minRadius - the smallest radius allowed for a circle.
  • MaxRadius - the largest radius allowed for a circle.

HoughCircle returns a x and y co-ordinate for each circle. It also returns a radius. We are storing these values in the variable called circles.

Again there is more information available about HoughCircle on the function detection page of the openCV documentation under HoughCircles.

http://docs.opencv.org/modules/imgproc/doc/feature_detection.html

We now go through each of these circles in order. For each of the circles we draw its circumference and its centre onto the coloured image we stored in the variable img.

for i in circles[0,:]:
    #draw the outer circle
    cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2)
    #draw the centre of the circle
    cv2.circle(img,(i[0],i[1]),2,(0,0,255),3)

More information on the circle command is seen on the OpenCV docs page which covers drawing functions.

http://docs.opencv.org/modules/core/doc/drawing_functions.html

Now as I stated at the start of this blog there was an actual reason for writing this program. I wanted to be able to log the x and y co-ordinates of the circles to see how they varied when putting in different items. I also wanted to work out the angle between the circles, and see how that varied.

The next lines of code separate out the x and y co-ordinates. They also work out the angle between them using simple trigonometry.

##Determine co-ordinates for centre of circle
x1 = circles[0][0][0]
y1 = circles[0][0][1]
x2 = circles[0][1][0]
y2 = circles[0][1][1]
##Angle betwen two circles
theta = math.degrees(math.atan((y2-y1)/(x2-x1)))

Finally to display the images nicely on the Raspberry Pi I pass each image through the function to resize them...

##Resize image
img = resizeImage(img)
thresh = resizeImage(thresh)

...then I display the images.

##Show Images 
cv2.imshow("thresh",thresh)
cv2.imshow("img",img)

There is no need to display the images if you don't want to. However I think its good to see what the thresh image and the final image looks like. These are good to help any fault finding. 

You also need the next line for the code to work.

cv2.waitKey(0)

Here is a link to the code, so you can download it rather than type it.


If you don't have a Raspberry Pi Camera there are a few images below which you can use to test your code on. Just change the code to comment out the line about taking the image with the camera, and change the name of the files you are opening to suit.

The first one is taken using my Raspberry Pi camera.

Hough_image1.jpg

The second is from my phone.

Hough_image2.jpg

If you have any problems with the code, try it on these two images first, as I know these work. I think most problems you will have will be to do with lighting. There was a fair amount of playing around with certain parameters, particularly param2 to get this to work.

Keep an eye on my blog, as there could well be some more openCV programs at some point!