Stamp Detection using Computer Vision and Python
Published: 2021-05-21 20:38:34

# Background

A friend of mine reach out and asked me whether I could write a program to detect the number of Rubber stamps in an image. Apparently, these invoice receipts will be categorized based on the number of stamps on them. Initially, I thought of building a Deep Learning Segmentation model, but soon I realized that it is not worth the effort. 

The images are generated in a controlled environment so few computer vision algorithms should do the trick. To illustrate the computer vision algorithms used in detecting the stamps, I will be using a sample image downloaded from Google as the original image is company property. The goal is to identify two stamps in the sample image.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/836aqc88lj5kycf5wn5r.gif)

# Solution

High level solution steps are:

1. Read the image.
2. Blur & detect the edges.
3. Find all contours and remove the smaller contours.
4. Fill the area inside contours & Close the blobs.
5. Filter the stamps.

Before we start let us import the necessary packages.

{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Libraries.py %}

## 1. Read the image

Read the color image using imread function. To display the image we will use Matplotlib. Matplotlib expects the color image channels to be of the order RGB, but OpenCV reads the image as BGR, so we will write a helper function for the conversion.

{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Read_and_display_the_image.py %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4alaedmpj9w673ei80cl.png)

## 2. Blur & detect the edges

First, we need to convert the image to grayscale using [cvtColor](https://docs.opencv.org/3.4/d8/d01/group__imgproc__color__conversions.html#ga397ae87e1288a81d2363b61574eb8cab) function. Then, we will use [bilateralFilter](https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga9d7064d478c95d60003cf839430737ed) to reduce noise in the image. Bilateral filter is preferred over Gaussian because it preserves the edges much better. Finally, we will use [canny](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_canny/py_canny.html) edge detector to threshold the image and detect edges. The normal canny edge detector requires two threshold parameters which is hard to tune so we will use the one from [Zero-parameter, automatic Canny edge detection with Python and OpenCV](https://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv)

{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=edge.py %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dgfhyfclqmuvchufwrc7.png)





## 3. Find all contours and remove the smaller contours

[findContours](https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#ga17ed9f5d79ae97bd4c7cf18403e1689a) function can find all contours in the image. The outer most contours are good enough for our use case so we will use the retrieval mode **RETR_EXTERNAL**. **CHAIN_APPROX_NONE** mode is preferred as we don't want to lose any point on the contour due to approximation. To remove the unwanted smaller contours, we can filter the contours by area.


{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Find_Contours.py      %}

> Total nr of contours found: 408



## 4. Fill the area inside contours & Close the blobs

Instead of working on the original binary image, we will draw the top contours on a image with black background and use this as base. Any disconnect in the contours are easier to identify when fill the area inside the contours using [drawContours](https://docs.opencv.org/3.4/d6/d6e/group__imgproc__draw.html#ga746c0625f1781f1ffc9056259103edbc) function.


{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Fill_the_area_inside_contours.py %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cru5i8c1acge9zqb4epd.png)


As suspected, the top stamp has a thin black line passing through it. We need to close this blob so that the top stamp is considered as one contour instead of two different ones. [Morphological Closing](https://docs.opencv.org/master/d4/d86/group__imgproc__filter.html#ga67493776e3ad1a3df63883829375201f) operation is perfect for achieving this.


{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Close_Blobs.py %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajy46hg5tkbocck3h6jo.png)

## 5. Filter the stamps

To isolate the stamp contours, we can identify all the contours from the latest binary image and filter for contours with more than 5 points as the stamp is an ellipse. 


{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Filter_Stamps.py %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3p14r126berlqccbsro2.png)

# Bonus - Highlight the identified stamps

For demo of this program, wouldn't it be cool if we can highlight only the stamped area of the image? Since we agree that it is indeed cool, let us see how we can achieve that. The steps involved are:

1. Duplicate the original image and blur the entire image.
2. Loop through the blurred image and for the points on or inside the image (using [pointPolygonTest](https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#ga1a539e8db2135af2566103705d7a5722) to check) we replace it with pixel values from the output image. We are using pixel values from the output image because we need the blue lines drawn over the stamps.


{% gist https://gist.github.com/6aravind/c8c929aad312e697336cc13d14b20c0c file=Highlight_the_stamped_region.py %}

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdbontp5yipn7rebdc51.png)

# Conclusion

Yep, that's it for this post. You can access this notebook from [here](https://github.com/6aravind/tidbits/blob/main/cv/Stamp%20detection%20using%20computer%20vision%20and%20python.ipynb).