Number Plate Detection using OpenCV & Python

Apoorva Sinha
QuikNapp
Published in
6 min readOct 1, 2020
Source: https://www.jenoptik.com/products/civil-security/anpr

With this step-by-step tutorial, you would be able to accept an image from the user, detect the license plate and then recognize the text written on the number plate(Trust me, it’s very simple). In this tutorial, we would be using

  • Python
  • OpenCV (for detecting the number plate)
  • Tkinter (for providing the GUI for taking an image as the input)
  • imutils (for image processing)
  • pytesseract (for recognizing the text on the number plate using OCR).

Make sure you have installed all of the above-mentioned, before delving further into the tutorial.

We will be using this image for testing our code.

Image taken from Google Images

Firstly, we would be importing all the modules needed for the program.

import numpy as np
import cv2
import imutils
from tkinter import Tk
from tkinter.filedialog import askopenfilename

What are we going to do: After importing all the necessary modules we would want to accept an image by the user. So, we would be using Tkinter for that.

Why this?: This would help us in letting the user choose the image, which would later be used for license plate detection.

Tk().withdraw()
filename = askopenfilename()

Here ‘filename’ stores the address of the file we would select.

What are we going to do: Now we would use the function cv2.imread(path_to_file) in order to read the image and imutils.resize(image, width = size you want) would resize the image.

Why this?: Resizing an image is a good practice, it helps with processing the image better and also helps the machine train on smaller images faster (We aren’t dealing with this aspect though, in this tutorial).

image = cv2.imread(filename)
image = imutils.resize(image, width=500)
cv2.imshow("Original Image", image)
cv2.waitKey(0)
This is our original image

What are we going to do: Now before detecting the number plate we would need to do some image processing so that detection becomes easy. First of all, we would convert our image from RGB to Grayscale and then would apply a Bilateral filter over it in order to reduce image noise.

By using cv2.Canny(image, lower, upper) we would detect the edges of the objects in the images.

Why this?: Edges help us in separating objects from another, and computers would like to do the same. With the help of the following code, the computer would be able to determine object boundaries and thus separate the object of interest.

gray_scaled = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray_scaled = cv2.bilateralFilter(gray_scaled, 15, 20, 20)
edges = cv2.Canny(gray_scaled, 170, 200)
cv2.imshow("Edged", edges)
cv2.waitKey(0)

What are we going to do: Now after detecting the edges of objects in the image, we would like to find the major contours in the processed image. We will be using cv2.findContours(image name, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) to do so.

Why this?: Contours are basically the curves that join all the continuous points having the same intensity or color,cv2.findContours(image name, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) join all the points along the boundary of an object, therefore using the image with edges detected is better.

contours, heirarchy  = cv2.findContours(edges.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

What are we going to do: Now after finding the contours, we would want to display them on the actual image. First, we would make a copy of the original image and then display the contours on it.

Why this?: We make a copy of the original image so that we don’t tamper the original image.

img1 = image.copy()
cv2.drawContours(img1, contours, -1, (0,255,0), 3)
cv2.imshow("All of the contours", img1)
cv2.waitKey(0)

What are we going to do: Now we would sort the contours according to their area, and then take the top 30 contours. Here, we would declare an empty variable that would later refer to the contour corresponding to the number plate.

Why this? : The above code would give us all the contours, which would include very small and insignificant ones as well so we would want to get rid of those and would want only the major contours. So we would loop over all the contours, and find out which contour is fit to be a license plate.

contours=sorted(contours, key = cv2.contourArea, reverse = True)[:30]
Number_Plate_Contour = 0

What are we going to do: Now, we would like to find the contour that is rectangular in shape, and we would be using the function cv2.approxPolyDP(current contour, maximum distance from contour to approximated contour, True) for this, which will approximate a polygon (in our case, a rectangle). The contour closest to being a license plate would be stored in the variable, “Number_Plate_Contour”.

Why this?: We here are assuming that all license plates are rectangular in shape, and therefore finding the contours that are in rectangular shape would help us in finding the license plate.

for current_contour in contours:        
perimeter = cv2.arcLength(current_contour, True)
approx = cv2.approxPolyDP(current_contour, 0.02 * perimeter, True)
if len(approx) == 4:
Number_Plate_Contour = approx
break

What are we going to do: Now a simple job to be done would be to extract the license plate, but for that, we would need to do masking, with the help of which we will be able to extract the license plate.

Why this: A mask is a binary image consisting of zero- and non-zero values. If a mask is applied to another binary or to a grayscale image of the same size, all pixels which are zero in the mask are set to zero in the output image. All others remain unchanged. So, we would apply masking to the Number_Plate_Contour, so that the pixel values of this particular contour would non-zeroes. Later on, by using cv2.bitwise_and(source1, source2, destination, mask = mask)(this performs the bitwise-AND operation on the pixels of two images) on the original image and the mask, we will be able to extract the license plate.

mask = np.zeros(gray_scaled.shape,np.uint8)
new_image1 = cv2.drawContours(mask,[Number_Plate_Contour],0,255,-1,)
new_image1 =cv2.bitwise_and(image,image,mask=mask)
cv2.imshow("Number Plate",new_image1)
cv2.waitKey(0)

We are almost done but in order for pytesseract to work properly, we would need to process the image of the license plate we have recently extracted.

What are we going to do: We are again going to process the image, but this time only the image of the license plate. First, convert the licensed image to grayscale and then get a binary image by using cv2.threshold(image, 10, 255, cv2.THRESH_BINARY).

Why this? : Thresholding is a type of image segmentation, where we change the pixels of an image to make the image easier to analyze. In thresholding, we convert an image from color or grayscale into a binary image. Pytesseract requires a bit of preprocessing to improve the OCR results, images need to be scaled appropriately, have as much image contrast as possible, etc.(a limitation of pytesseract, if you will). Therefore, we try to make the quality of the image better by applying a threshold, which eliminates image noises to a great extent.

gray_scaled1 = cv2.cvtColor(new_image1, cv2.COLOR_BGR2GRAY)
ret,processed_img = cv2.threshold(np.array(gray_scaled1), 125, 255, cv2.THRESH_BINARY)
cv2.imshow("Number Plate",processed_img)
cv2.waitKey(0)

What are we going to do: Now that we have an image of the processed license plate, we can use pytesseract to extract the license number from the number plate.

Why this? : Pytesseract is an optical character recognition (OCR) tool for python. It helps in recognizing the text and digits in an image.

text = pytesseract.image_to_string(processed_image)
print("Number is :", text)
cv2.waitKey(0)
The text on the license plate

So, this is how you extract the license number from an image of a car with a license plate.

Thank you for being so patient and reading it till the end! Hope it helped, and do give it a clap if you have learned something new!

--

--