4.4. Contours : More Functions¶
In this chapter, we will learn about
Convexity defects and how to find them.
Finding shortest distance from a point to a polygon
Matching different shapes
4.4.1. Theory and Code¶
1. Convexity Defects¶
We saw what is convex hull in second chapter about contours. Any deviation of the object from this hull can be considered as convexity defect.
OpenCV comes with a ready-made function to find this, cv2.convexityDefects(). A basic function call would look like below:
>>> %matplotlib inline
>>> import matplotlib.pyplot as plt
>>>
>>> import cv2
>>> import numpy as np
>>> img = cv2.imread('/cvdata/star.png', 0)
>>> ret,thresh = cv2.threshold(img,127,255,0)
>>> contours,hierarchy = cv2.findContours(thresh, 1, 2)
>>> for cnt in contours:
>>> area = cv2.contourArea(cnt)
>>> print(area)
4.0
4.0
2.0
2.0
8.5
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
76185.5
>>> hull = cv2.convexHull(cnt,returnPoints = False)
>>> defects = cv2.convexityDefects(cnt,hull)
Note
Remember we have to pass returnPoints = False
while finding convex
hull, in order to find convexity defects.
It returns an array where each row contains these values - [ start
point, end point, farthest point, approximate distance to farthest point
]. We can visualize it using an image. We draw a line joining start
point and end point, then draw a circle at the farthest point. Remember
first three values returned are indices of cnt
. So we have to bring
those values from cnt
.
>>> import cv2
>>> import numpy as np
>>>
>>> img = cv2.imread('/cvdata/star.png')
>>> img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
>>> ret, thresh = cv2.threshold(img_gray, 127, 255,0)
>>> contours,hierarchy = cv2.findContours(thresh,2,1)
>>> for cnt in contours:
>>> area = cv2.contourArea(cnt)
>>> print(area)
76188.5
4.0
4.0
2.0
2.0
8.5
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
2.0
>>> hull = cv2.convexHull(cnt,returnPoints = False)
>>> hull
array([[0],
[1],
[2],
[3]], dtype=int32)
>>> defects = cv2.convexityDefects(cnt,hull)
>>> defects
>>> for i in range(defects.shape[0]):
>>> s,e,f,d = defects[i,0]
>>> start = tuple(cnt[s][0])
>>> end = tuple(cnt[e][0])
>>> far = tuple(cnt[f][0])
>>> cv2.line(img,start,end,[0,255,0],2)
>>> cv2.circle(img,far,5,[0,0,255],-1)
>>>
>>> # cv2.imshow('img',img)
>>> # cv2.waitKey(0)
>>> # cv2.destroyAllWindows()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In [20], line 1
----> 1 for i in range(defects.shape[0]):
2 s,e,f,d = defects[i,0]
3 start = tuple(cnt[s][0])
AttributeError: 'NoneType' object has no attribute 'shape'
And see the result:
2. Point Polygon Test¶
This function finds the shortest distance between a point in the image and a contour. It returns the distance which is negative when point is outside the contour, positive when point is inside and zero if point is on the contour.
For example, we can check the point (50,50) as follows:
>>> dist = cv2.pointPolygonTest(cnt,(50,50),True)
In the function, third argument is measureDist
. If it is True
,
it finds the signed distance. If False
, it finds whether the point
is inside or outside or on the contour (it returns +1, -1, 0
respectively).
Note
If you don’t want to find the distance, make sure third argument is
False
, because, it is a time consuming process. So, making it
False
gives about 2-3X speedup.
3. Match Shapes¶
OpenCV comes with a function cv2.matchShapes() which enables us to compare two shapes, or two contours and returns a metric showing the similarity. The lower the result, the better match it is. It is calculated based on the hu-moment values. Different measurement methods are explained in the docs.
>>> import cv2
>>> import numpy as np
>>>
>>> img1 = cv2.imread('star.jpg',0)
>>> img2 = cv2.imread('star2.jpg',0)
>>>
>>> ret, thresh = cv2.threshold(img1, 127, 255,0)
>>> ret, thresh2 = cv2.threshold(img2, 127, 255,0)
>>> contours,hierarchy = cv2.findContours(thresh,2,1)
>>> cnt1 = contours[0]
>>> contours,hierarchy = cv2.findContours(thresh2,2,1)
>>> cnt2 = contours[0]
>>>
>>> ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
>>> print (ret)
[ WARN:0@50.993] global ./modules/imgcodecs/src/loadsave.cpp (239) findDecoder imread_('star.jpg'): can't open/read file: check file path/integrity
[ WARN:0@50.993] global ./modules/imgcodecs/src/loadsave.cpp (239) findDecoder imread_('star2.jpg'): can't open/read file: check file path/integrity
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In [23], line 10
8 ret, thresh2 = cv2.threshold(img2, 127, 255,0)
9 contours,hierarchy = cv2.findContours(thresh,2,1)
---> 10 cnt1 = contours[0]
11 contours,hierarchy = cv2.findContours(thresh2,2,1)
12 cnt2 = contours[0]
IndexError: tuple index out of range
I tried matching shapes with different shapes given below:
I got following results:
Matching Image A with itself = 0.0
Matching Image A with Image B = 0.001946
Matching Image A with Image C = 0.326911
See, even image rotation doesn’t affect much on this comparison.
Hu-Moments are seven moments invariant to translation, rotation and scale. Seventh one is skew-invariant. Those values can be found using cv2.HuMoments() function.
4.4.2. Additional Resources¶
4.4.3. Exercises¶
Check the documentation for cv2.pointPolygonTest(), you can find a nice image in Red and Blue color. It represents the distance from all pixels to the white curve on it. All pixels inside curve is blue depending on the distance. Similarly outside points are red. Contour edges are marked with White. So problem is simple. Write a code to create such a representation of distance.
Compare images of digits or letters using cv2.matchShapes(). ( That would be a simple step towards OCR )