-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathContour Detection.py
More file actions
160 lines (138 loc) · 9.65 KB
/
Contour Detection.py
File metadata and controls
160 lines (138 loc) · 9.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import cv2 as cv
import numpy as np
# Loading, Rescaling and Showing the main image
img = cv.imread("large_image.png")
img = cv.resize(img, (int(img.shape[1] * 0.2), int(img.shape[0] * 0.2)), interpolation=cv.INTER_AREA)
cv.imshow("Image", img)
# Ok, What are Contours and How are they different from edges?
# Edges are computed as points that are extrema of the image gradient in
# the direction of the gradient
#
# Contours are often obtained from edges, but they are aimed at being object contours.
# Thus, they need to be closed curves. We can think of them as
# boundaries. When they are obtained from edges, you need to connect the
# edges in order to obtain a closed contour.
# Converting the image to greyscale
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cv.imshow("Grayscale image", gray)
mean_val = np.mean(gray)
min_val = 0.66 * mean_val
max_val = 1.33 * mean_val
# Grabbing the edges in the picture
canny = cv.Canny(gray, min_val, max_val)
cv.imshow("Canny Edges", canny)
#_______________________IMPORTANT STUFF_____________________________#
# FINDING CONTOURS
# cv.findContours() finds contours in a binary image
contours, hierarchies = cv.findContours(canny, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# OK. Lots of new terms used. Let's explain the use of each one of them.
# Contours can be explained simply as a curve joining all the continuous
# points (along the boundary), having same color or intensity.
# That's why they're used for object detection and are obtained from edges
# cv.findContours returns 2 things :
# 1. Contours : Detected contours. Each contour is stored as a
# vector of points (a list)
# 2. Hierarchy: Hierarchy shows how different contours are
# linked to each other (if some contour is within
# some other contour or if it contains other contours)
# It has as many elements as the number of
# contours. It's an optional output vector
#
# It accepts the following parameters :
# 1. image : Source image of which you'll find the contours.
# You can use compare, inRange, threshold ,
# adaptiveThreshold, Canny, and others to create
# a binary image out of a grayscale or color one.
# 2. Mode : How the contours are obtained. 3 main types
#
# |---------------------------------------------------------------------------------|
# | cv.RETR_EXTERNAL | retrieves only the extreme outer contours. |
# |________________________|________________________________________________________|
# | cv.RETR_LIST | retrieves all of the contours without establishing any |
# | | hierarchical relationships. |
# |________________________|________________________________________________________|
# | cv.RETR_TREE | retrieves all of the contours and reconstructs a full |
# | | hierarchy of nested contours. |
# |------------------------|--------------------------------------------------------|
#
# 3. Method : Contour approximation method. 2 main types
#
# |------------------------|--------------------------------------------------------|
# | cv.CHAIN_APPROX_NONE | stores absolutely all the contour points. |
# |_______________________ |________________________________________________________|
# | cv.CHAIN_APPROX_SIMPLE | compresses horizontal, vertical, and diagonal segments |
# | | and leaves only their end points. For example, an |
# | | up-right rectangular contour is encoded with 4 points. |
# |------------------------|--------------------------------------------------------|
print(f'{len(contours)} contour(s) found in original b&w image using Canny Edges')
# If you're using the same image as mine, you'll notice that the there are 1084 contours found...
# which is, kind of, a lot. Let's blur the b&w image and then find the contours again
blur = cv.GaussianBlur(gray, (5, 5), cv.BORDER_DEFAULT)
cv.imshow("Blurred", blur)
canny2 = cv.Canny(blur, min_val, max_val)
cv.imshow("Canny Edges",canny2)
contours2, hierarchies2 = cv.findContours(canny2, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(f'{len(contours2)} contour(s) found after blurring the b&w image and using Canny Edges')
# Now let's find the contours using a Threshold
# It return 2 values retval and thresholded Image (We'll explain the use of retval later)
ret, thresh = cv.threshold(gray, # source image (IT SHOULD BE A GREYSCALE IMAGE)
30, # minimum value below which everything will be blacked out
# changing this value gives wildly different pictures
255, # max value to which the values (above min values) will be increased
cv.THRESH_BINARY) # Type of threshold we want
# There's also cv.THRESH_BINARY_INV which just inverts
# the results (the black would be white and vice-versa)
cv.imshow("Threshold", thresh)
contours3, hierarchies3 = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print(f'{len(contours3)} contour(s) found after regular threshold of the b&w image')
# Drawing the Contours found from Binary threshold image
blank_image = np.zeros(img.shape, dtype='uint8') # blank image
cv.drawContours(blank_image, # image to draw over
contours3, # takes in a list of contours (which is already a list)
-1, # contours index, to decide how many contours you want to show
# We want to show all of them, hence, -1
(255, 0, 0), # setting the colour of contours (b,g,r)
1) # Thickness
cv.imshow("Contours by thresholding", blank_image)
# Did you notice how wildly the images differed when we changed the threshold value?
# So, how can we know if the selected threshold value is ideal or not?
# Answer is, trial and error method. But consider a bimodal image
# (In simple words, bimodal image is an image whose histogram has two peaks).
# For that image, we can approximately take a value in the middle of those peaks as threshold
# value, right ? That is what Otsu binarization does.
# So in simple words, it automatically calculates a threshold value from image histogram
# for a bimodal image. (For images which are not bimodal, binarization won’t be accurate.)
# For threshold value, simply pass zero.
# Then the algorithm finds the optimal threshold value and returns you as the second output, retVal.
# If Otsu thresholding is not used, retVal is same as the threshold value you used.
ret2, thresh2 = cv.threshold(gray, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)
cv.imshow("Otsu Threshold greyscale image", thresh2)
contours4, hierarchies4 = cv.findContours(thresh2, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print(f'{len(contours4)} contour(s) found after Otsu thresholding of the b&w image')
ret3, thresh3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)
cv.imshow("Otsu Threshold on Blurred Image", thresh3)
contours5, hierarchies5 = cv.findContours(thresh3, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print(f'{len(contours5)} contour(s) found after Otsu thresholding the blurred b&w image')
# Trying Out Adaptive Thresholding
# In this, the algorithm calculate the threshold for a small regions of the image (determined by the block size).
# So we get different thresholds for different regions of the same image and it gives us
# better results for images with varying illumination.
# For OCR (Optical Character Recognition) it is useful to eliminate the background that is not homogeneous in nature
thresh4 = cv.adaptiveThreshold(gray, 255,
cv.ADAPTIVE_THRESH_GAUSSIAN_C, # Threshold value is the
# weighted sum of neighbourhood values
# where weights are a gaussian window.
# There's also cv2.ADAPTIVE_THRESH_MEAN_C
# where threshold value is the mean of
# neighbourhood area.
cv.THRESH_BINARY, # Type of threshold we want
# There's also cv.THRESH_BINARY_INV which just inverts
# the results (the black would be white and vice-versa)
11, # here, we are working with a block size of 11 pixels
# in general, the bigger the block size, the more noise we'll have, but the feaures would be more clear
2) # here, 2 is the constant value that gets removed from the threshold value
# the smaller this number, the more noise we have, but with a number too big, it can also remove certain features
cv.imshow("Adaptive Threshold", thresh4)
contours6, hierarchies6 = cv.findContours(thresh4, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print(f'{len(contours6)} contour(s) found after dynamically thresholding the b&w image')
cv.waitKey(0)