-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBlobProperties.py
295 lines (255 loc) · 12.6 KB
/
BlobProperties.py
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# <!--------------------------------------------------------------------------->
# <!-- ITU - IT University of Copenhage -->
# <!-- Computer Science Department -->
# <!-- Eye Information Research Group -->
# <!-- Introduction to Image Analysis and Machine Learning Course -->
# <!-- File : BlobProperties.py -->
# <!-- Description: Get descriptors of contour-based connected components -->
# <!-- Author : Fabricio Batista Narcizo -->
# <!-- : Rued Langgaards Vej 7 - 4D25 - DK-2300 - Kobenhavn S. -->
# <!-- : fabn[at]itu[dot]dk -->
# <!-- Responsable: Dan Witzner Hansen (witzner[at]itu[dot]dk) -->
# <!-- Fabricio Batista Narcizo (fabn[at]itu[dot]dk) -->
# <!-- Information: You DO NOT need to change this file -->
# <!-- Date : 12/03/2018 -->
# <!-- Change : 12/03/2018 - Creation of this class -->
# <!-- Review : 12/03/2018 - Finalized -->
# <!--------------------------------------------------------------------------->
__version__ = "$Revision: 2018031201 $"
########################################################################
import cv2
import math
from ClassProperty import ClassProperty
########################################################################
class BlobProperties:
"""BlobProperties Class is used to manager the blob properties extraction."""
# ----------------------------------------------------------------------#
# Class Attributes #
# ----------------------------------------------------------------------#
__Instance = None
# ----------------------------------------------------------------------#
# Static Class Methods #
# ----------------------------------------------------------------------#
@ClassProperty
def Instance(self):
"""Create an instance to manager the blob properties extraction."""
if self.__Instance is None:
self.__Instance = Properties()
return self.__Instance
# ----------------------------------------------------------------------#
# BlobProperties Class Constructor #
# ----------------------------------------------------------------------#
def __init__(self):
"""This constructor is never used by the system."""
pass
# ----------------------------------------------------------------------#
# Class Methods #
# ----------------------------------------------------------------------#
def __repr__(self):
"""Gets an object representation in a string format."""
return "IAMLTools.BlobProperties object."
class Properties(object):
"""
This class is used for getting descriptors of contour-based connected
components.
The main method to use is: getContourProperties(contour, properties=[]):
contour: the contours variable found by cv2.findContours()
properties: list of strings specifying which properties should be
calculated and returned.
The following properties can be specified:
Approximation: A contour shape to another shape with less number of vertices.
Area: Area within the contour - float.
Boundingbox: Bounding box around contour - 4 tuple (topleft.x, topleft.y,
width, height).
Centroid: The center of contour - (x, y).
Circle: The the circumcircle of an object.
Circularity: Used to check if the countour is a circle.
Convexhull: Calculates the convex hull of the contour points.
Extend: Ratio of the area and the area of the bounding box. Expresses how
spread out the contour is.
Ellipse: Fit an ellipse around the contour.
IsConvex: Boolean value specifying if the set of contour points is convex.
Length: Length of the contour
Moments: Dictionary of moments.
Perimeter: Permiter of the contour - equivalent to the length.
RotatedBox: Rotated rectangle as a Box2D structure.
Returns: Dictionary with key equal to the property name.
Example:
image, contours, hierarchy = cv2.findContours(image, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
goodContours = []
for contour in contours:
vals = IAMLTools.getContourProperties(contour, ["Area", "Length",
"Centroid", "Extend",
"ConvexHull"])
if vals["Area"] > 100 and vals["Area"] < 200:
goodContours.append(contour)
"""
# ----------------------------------------------------------------------#
# Properties Class Constructor #
# ----------------------------------------------------------------------#
def __init__(self):
"""Properties Class Constructor."""
pass
# ----------------------------------------------------------------------#
# Public Class Methods #
# ----------------------------------------------------------------------#
def getContourProperties(self, contour, properties=[]):
"""Calcule and return a list of strings specifying by properties."""
# Initial variables.
failInInput = False
props = {}
for prop in properties:
prop = str(prop).lower()
if prop == "approximation":
props.update({"Approximation": self.__CalculateApproximation(contour)})
if prop == "area":
props.update({"Area": self.__CalculateArea(contour)})
elif prop == "boundingbox":
props.update({"BoundingBox": self.__CalculateBoundingBox(contour)})
elif prop == "centroid":
props.update({"Centroid": self.__CalculateCentroid(contour)})
elif prop == "circle":
props.update({"Circle": self.__CalculateCircle(contour)})
elif prop == "circularity":
props.update({"Circularity": self.__CalculateCircularity(contour)})
elif prop == "convexhull":
props.update({"ConvexHull": self.__CalculateConvexHull(contour)})
elif prop == "extend":
props.update({"Extend": self.__CalculateExtend(contour)})
elif prop == "ellipse":
props.update({"Ellipse": self.__CalculateEllipse(contour)})
elif prop == "isconvex":
props.update({"IsConvex": self.__IsConvex(contour)})
elif prop == "length":
props.update({"Length": self.__CalculateLength(contour)})
elif prop == "moments":
props.update({"Moments": self.__CalculateMoments(contour)})
elif prop == "perimeter":
props.update({"Perimeter": self.__CalculatePerimeter(contour)})
elif prop == "rotatedbox":
props.update({"RotatedBox": self.__CalculateRotatedBox(contour)})
elif prop == "solidity":
props.update({"Solidity": self.__CalculateSolidity(contour)})
elif failInInput:
pass
else:
print("\t--" * 20)
print("\t*** PROPERTY ERROR " + prop + " DOES NOT EXIST ***")
print("\tTHIS ERROR MESSAGE WILL ONLY BE PRINTED ONCE")
print("\--" * 20)
failInInput = True
return props
# ----------------------------------------------------------------------#
# Private Class Methods #
# ----------------------------------------------------------------------#
def __CalculateApproximation(self, contour):
"""
Calculate the approximation of a contour shape to another shape with
less number of vertices depending upon the precision we specify.
"""
epsilon = 0.1 * cv2.arcLength(contour, True)
return cv2.approxPolyDP(contour, epsilon, True)
def __CalculateArea(self, contour):
"""
Calculate the contour area by the function cv2.contourArea() or
from moments, M["m00"].
"""
return cv2.contourArea(contour)
def __CalculateBoundingBox(self, contour):
"""
Calculate the bouding rectangle. It is a straight rectangle, it
doesn't consider the rotation of the object. So area of the bounding
rectangle won't be minimum. It is found by the function
cv2.boundingRect().
"""
return cv2.boundingRect(contour)
def __CalculateCentroid(self, contour):
"""
Calculates the centroid of the contour. Moments up to the third
order of a polygon or rasterized shape.
"""
moments = cv2.moments(contour)
centroid = (-1, -1)
if moments["m00"] != 0:
centroid = (int(round(moments["m10"] / moments["m00"])),
int(round(moments["m01"] / moments["m00"])))
return centroid
def __CalculateCircle(self, contour):
"""
Calculate the circumcircle of an object using the function
cv2.minEnclosingCircle(). It is a circle which completely covers
the object with minimum area.
"""
return cv2.minEnclosingCircle(contour)
def __CalculateConvexHull(self, contour):
"""
Finds the convex hull of a point set by checking a curve for
convexity defects and corrects it.
"""
return cv2.convexHull(contour)
def __CalculateCircularity(self, contour):
"""
Finds circularity of a given contour. The circularity of a contour defines how circular
a blob/contour is
"""
area = self.__CalculateArea(contour)
perimeter = self.__CalculatePerimeter(contour)
if perimeter <= 0.0:
return 0.
return (4 * math.pi * area) / (math.pow(perimeter, 2))
def __CalculateEllipse(self, contour):
"""
Fit an ellipse to an object. It returns the rotated rectangle
in which the ellipse is inscribed.
"""
if len(contour) > 5:
return cv2.fitEllipse(contour)
return cv2.minAreaRect(contour)
def __CalculateExtend(self, contour):
"""
Calculate the countour extend.
"""
area = self.__CalculateArea(contour)
boundingBox = self.__CalculateBoundingBox(contour)
return area / (boundingBox[2] * boundingBox[3])
def __IsConvex(self, contour):
"""
Check if a curve is convex or not.
"""
return cv2.isContourConvex(contour)
def __CalculateLength(self, curve):
"""
Calculate a contour perimeter or a curve length.
"""
return cv2.arcLength(curve, True)
def __CalculateMoments(self, contour):
"""
Calculate the contour moments to help you to calculate some features
like center of mass of the object, area of the object etc.
"""
return cv2.moments(contour)
def __CalculatePerimeter(self, curve):
"""Calculates a contour perimeter or a curve length."""
return cv2.arcLength(curve, True)
def __CalculateRotatedBox(self, contour):
"""
Calculate the rotated rectangle as a Box2D structure which contains
following detals: (center(x, y), (width, height), angle of rotation).
"""
rectangle = cv2.minAreaRect(contour)
box = cv2.boxPoints(rectangle)
return np.int0(box)
def __CalculateSolidity(self, contour):
"""
Calculate solidity of contour by taking ratio between convex hull of
the contour and area of the original contour.
"""
area = cv2.contourArea(contour)
hull = cv2.convexHull(contour)
hullArea = cv2.contourArea(hull)
if hullArea != 0:
solidity = area / float(hullArea)
return solidity
else:
return 0