Hang Zhou commited on
Commit
39fac4c
·
verified ·
1 Parent(s): 3dad927

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. util/box_ops.py +111 -0
  2. util/cityscapes_ops.py +261 -0
util/box_ops.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+ def compute_iou_matrix(boxes):
5
+ '''
6
+ Given a set of bboxes (in [x1, y1, x2, y2]), output an IOU matrix,
7
+ while ignoring pairs where one box fully contains the other.
8
+ '''
9
+ N = boxes.shape[0]
10
+ iou_matrix = np.zeros((N, N), dtype=np.float32)
11
+
12
+ def is_contained(box_a, box_b):
13
+ return (
14
+ box_a[0] <= box_b[0] and box_a[1] <= box_b[1] and
15
+ box_a[2] >= box_b[2] and box_a[3] >= box_b[3]
16
+ )
17
+
18
+ def is_almost_contained(inner, outer, epsilon=2):
19
+ x1_i, y1_i, w_i, h_i = inner
20
+ x1_o, y1_o, w_o, h_o = outer
21
+
22
+ x2_i, y2_i = x1_i + w_i, y1_i + h_i
23
+ x2_o, y2_o = x1_o + w_o, y1_o + h_o
24
+
25
+ return (
26
+ x1_i >= x1_o - epsilon and
27
+ y1_i >= y1_o - epsilon and
28
+ x2_i <= x2_o + epsilon and
29
+ y2_i <= y2_o + epsilon
30
+ )
31
+
32
+ for i in range(N):
33
+ x1_i, y1_i, x2_i, y2_i = boxes[i]
34
+ area_i = (x2_i - x1_i) * (y2_i - y1_i)
35
+ for j in range(i + 1, N):
36
+ x1_j, y1_j, x2_j, y2_j = boxes[j]
37
+ area_j = (x2_j - x1_j) * (y2_j - y1_j)
38
+
39
+ box_i = boxes[i]
40
+ box_j = boxes[j]
41
+
42
+ # Skip if one box fully contains the other
43
+ if is_almost_contained(box_i, box_j) or is_almost_contained(box_j, box_i):
44
+ iou = 0.0
45
+ else:
46
+ inter_x1 = max(x1_i, x1_j)
47
+ inter_y1 = max(y1_i, y1_j)
48
+ inter_x2 = min(x2_i, x2_j)
49
+ inter_y2 = min(y2_i, y2_j)
50
+
51
+ inter_w = max(0, inter_x2 - inter_x1)
52
+ inter_h = max(0, inter_y2 - inter_y1)
53
+ inter_area = inter_w * inter_h
54
+
55
+ union_area = area_i + area_j - inter_area
56
+ iou = inter_area / union_area if union_area > 0 else 0.0
57
+
58
+ iou_matrix[i, j] = iou
59
+ iou_matrix[j, i] = iou # symmetric
60
+
61
+ return iou_matrix
62
+
63
+
64
+
65
+ def draw_bboxes(image, bbox_xyxy, color=(0, 255, 0), thickness=2):
66
+ '''
67
+ given an image and set of bboxes, output a bbox annotated image
68
+ '''
69
+ image_copy = image.copy()
70
+ for box in bbox_xyxy:
71
+ x1, y1, x2, y2 = map(int, box)
72
+ cv2.rectangle(image_copy, (x1, y1), (x2, y2), color, thickness)
73
+ return image_copy
74
+
75
+
76
+ def add_black_border(patch, border_width=1):
77
+ """
78
+ Set the outermost border of the patch to white.
79
+
80
+ Args:
81
+ patch (np.ndarray): An image patch of shape (H, W, 3) for RGB.
82
+ border_width (int): Width of the white border (default: 1 pixel).
83
+
84
+ Returns:
85
+ np.ndarray: The patch with a black border.
86
+ """
87
+ patch[:border_width, :, 0:3] = 255 # Top border
88
+ patch[-border_width:, :, 0:3] = 255 # Bottom border
89
+ patch[:, :border_width, 0:3] = 255 # Left border
90
+ patch[:, -border_width:, 0:3] = 255 # Right border
91
+ return patch
92
+
93
+ def mask_to_bbox_xywh(mask):
94
+ """
95
+ Obtain bbox (xywh) from the mask.
96
+
97
+ Args:
98
+ mask (np.ndarray)
99
+
100
+ Returns:
101
+ np.ndarray: a bbox of shape (1, 4), in the format of [x, y, w, h]。
102
+ if empty, return shape (0, 4)。
103
+ """
104
+ ys, xs = np.where(mask)
105
+ if len(xs) == 0 or len(ys) == 0:
106
+ return np.zeros((0, 4), dtype=np.int32)
107
+
108
+ x_min, x_max = xs.min(), xs.max()
109
+ y_min, y_max = ys.min(), ys.max()
110
+ bbox = [x_min, y_min, x_max - x_min, y_max - y_min]
111
+ return np.array(bbox, dtype=np.int32)
util/cityscapes_ops.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import namedtuple
2
+ from abc import ABCMeta, abstractmethod
3
+ import os
4
+ import json
5
+
6
+ Label = namedtuple( 'Label' , [
7
+
8
+ 'name' , # The identifier of this label, e.g. 'car', 'person', ... .
9
+ # We use them to uniquely name a class
10
+
11
+ 'id' , # An integer ID that is associated with this label.
12
+ # The IDs are used to represent the label in ground truth images
13
+ # An ID of -1 means that this label does not have an ID and thus
14
+ # is ignored when creating ground truth images (e.g. license plate).
15
+ # Do not modify these IDs, since exactly these IDs are expected by the
16
+ # evaluation server.
17
+
18
+ 'trainId' , # Feel free to modify these IDs as suitable for your method. Then create
19
+ # ground truth images with train IDs, using the tools provided in the
20
+ # 'preparation' folder. However, make sure to validate or submit results
21
+ # to our evaluation server using the regular IDs above!
22
+ # For trainIds, multiple labels might have the same ID. Then, these labels
23
+ # are mapped to the same class in the ground truth images. For the inverse
24
+ # mapping, we use the label that is defined first in the list below.
25
+ # For example, mapping all void-type classes to the same ID in training,
26
+ # might make sense for some approaches.
27
+ # Max value is 255!
28
+
29
+ 'category' , # The name of the category that this label belongs to
30
+
31
+ 'categoryId' , # The ID of this category. Used to create ground truth images
32
+ # on category level.
33
+
34
+ 'hasInstances', # Whether this label distinguishes between single instances or not
35
+
36
+ 'ignoreInEval', # Whether pixels having this class as ground truth label are ignored
37
+ # during evaluations or not
38
+
39
+ 'color' , # The color of this label
40
+ ] )
41
+
42
+ labels = [
43
+ # name id trainId category catId hasInstances ignoreInEval color
44
+ Label( 'unlabeled' , 0 , 255 , 'void' , 0 , False , True , ( 0, 0, 0) ),
45
+ Label( 'ego vehicle' , 1 , 255 , 'void' , 0 , False , True , ( 0, 0, 0) ),
46
+ Label( 'rectification border' , 2 , 255 , 'void' , 0 , False , True , ( 0, 0, 0) ),
47
+ Label( 'out of roi' , 3 , 255 , 'void' , 0 , False , True , ( 0, 0, 0) ),
48
+ Label( 'static' , 4 , 255 , 'void' , 0 , False , True , ( 0, 0, 0) ),
49
+ Label( 'dynamic' , 5 , 255 , 'void' , 0 , False , True , (111, 74, 0) ),
50
+ Label( 'ground' , 6 , 255 , 'void' , 0 , False , True , ( 81, 0, 81) ),
51
+ Label( 'road' , 7 , 0 , 'flat' , 1 , False , False , (128, 64,128) ),
52
+ Label( 'sidewalk' , 8 , 1 , 'flat' , 1 , False , False , (244, 35,232) ),
53
+ Label( 'parking' , 9 , 255 , 'flat' , 1 , False , True , (250,170,160) ),
54
+ Label( 'rail track' , 10 , 255 , 'flat' , 1 , False , True , (230,150,140) ),
55
+ Label( 'building' , 11 , 2 , 'construction' , 2 , False , False , ( 70, 70, 70) ),
56
+ Label( 'wall' , 12 , 3 , 'construction' , 2 , False , False , (102,102,156) ),
57
+ Label( 'fence' , 13 , 4 , 'construction' , 2 , False , False , (190,153,153) ),
58
+ Label( 'guard rail' , 14 , 255 , 'construction' , 2 , False , True , (180,165,180) ),
59
+ Label( 'bridge' , 15 , 255 , 'construction' , 2 , False , True , (150,100,100) ),
60
+ Label( 'tunnel' , 16 , 255 , 'construction' , 2 , False , True , (150,120, 90) ),
61
+ Label( 'pole' , 17 , 5 , 'object' , 3 , False , False , (153,153,153) ),
62
+ Label( 'polegroup' , 18 , 255 , 'object' , 3 , False , True , (153,153,153) ),
63
+ Label( 'traffic light' , 19 , 6 , 'object' , 3 , False , False , (250,170, 30) ),
64
+ Label( 'traffic sign' , 20 , 7 , 'object' , 3 , False , False , (220,220, 0) ),
65
+ Label( 'vegetation' , 21 , 8 , 'nature' , 4 , False , False , (107,142, 35) ),
66
+ Label( 'terrain' , 22 , 9 , 'nature' , 4 , False , False , (152,251,152) ),
67
+ Label( 'sky' , 23 , 10 , 'sky' , 5 , False , False , ( 70,130,180) ),
68
+ Label( 'person' , 24 , 11 , 'human' , 6 , True , False , (220, 20, 60) ),
69
+ Label( 'rider' , 25 , 12 , 'human' , 6 , True , False , (255, 0, 0) ),
70
+ Label( 'car' , 26 , 13 , 'vehicle' , 7 , True , False , ( 0, 0,142) ),
71
+ Label( 'truck' , 27 , 14 , 'vehicle' , 7 , True , False , ( 0, 0, 70) ),
72
+ Label( 'bus' , 28 , 15 , 'vehicle' , 7 , True , False , ( 0, 60,100) ),
73
+ Label( 'caravan' , 29 , 255 , 'vehicle' , 7 , True , True , ( 0, 0, 90) ),
74
+ Label( 'trailer' , 30 , 255 , 'vehicle' , 7 , True , True , ( 0, 0,110) ),
75
+ Label( 'train' , 31 , 16 , 'vehicle' , 7 , True , False , ( 0, 80,100) ),
76
+ Label( 'motorcycle' , 32 , 17 , 'vehicle' , 7 , True , False , ( 0, 0,230) ),
77
+ Label( 'bicycle' , 33 , 18 , 'vehicle' , 7 , True , False , (119, 11, 32) ),
78
+ Label( 'license plate' , -1 , -1 , 'vehicle' , 7 , False , True , ( 0, 0,142) ),
79
+ ]
80
+
81
+
82
+ # name to label object
83
+ name2label = { label.name : label for label in labels }
84
+
85
+ # A point in a polygon
86
+ Point = namedtuple('Point', ['x', 'y'])
87
+
88
+ class CsObjectType():
89
+ POLY = 1 # polygon
90
+ BBOX = 2 # bounding box
91
+
92
+
93
+ # Abstract base class for annotation objects
94
+ class CsObject:
95
+ __metaclass__ = ABCMeta
96
+
97
+ def __init__(self, objType):
98
+ self.objectType = objType
99
+ # the label
100
+ self.label = ""
101
+
102
+ # If deleted or not
103
+ self.deleted = 0
104
+ # If verified or not
105
+ self.verified = 0
106
+ # The date string
107
+ self.date = ""
108
+ # The username
109
+ self.user = ""
110
+ # Draw the object
111
+ # Not read from or written to JSON
112
+ # Set to False if deleted object
113
+ # Might be set to False by the application for other reasons
114
+ self.draw = True
115
+
116
+ @abstractmethod
117
+ def __str__(self): pass
118
+
119
+ @abstractmethod
120
+ def fromJsonText(self, jsonText, objId=-1): pass
121
+
122
+ @abstractmethod
123
+ def toJsonText(self): pass
124
+
125
+ def updateDate( self ):
126
+ try:
127
+ locale.setlocale( locale.LC_ALL , 'en_US.utf8' )
128
+ except locale.Error:
129
+ locale.setlocale( locale.LC_ALL , 'en_US' )
130
+ except locale.Error:
131
+ locale.setlocale( locale.LC_ALL , 'us_us.utf8' )
132
+ except locale.Error:
133
+ locale.setlocale( locale.LC_ALL , 'us_us' )
134
+ except:
135
+ pass
136
+ self.date = datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S")
137
+
138
+ # Mark the object as deleted
139
+ def delete(self):
140
+ self.deleted = 1
141
+ self.draw = False
142
+
143
+
144
+ # Class that contains the information of a single annotated object as polygon
145
+ class CsPoly(CsObject):
146
+ # Constructor
147
+ def __init__(self):
148
+ CsObject.__init__(self, CsObjectType.POLY)
149
+ # the polygon as list of points
150
+ self.polygon = []
151
+ # the object ID
152
+ self.id = -1
153
+
154
+ def __str__(self):
155
+ polyText = ""
156
+ if self.polygon:
157
+ if len(self.polygon) <= 4:
158
+ for p in self.polygon:
159
+ polyText += '({},{}) '.format( p.x , p.y )
160
+ else:
161
+ polyText += '({},{}) ({},{}) ... ({},{}) ({},{})'.format(
162
+ self.polygon[ 0].x , self.polygon[ 0].y ,
163
+ self.polygon[ 1].x , self.polygon[ 1].y ,
164
+ self.polygon[-2].x , self.polygon[-2].y ,
165
+ self.polygon[-1].x , self.polygon[-1].y )
166
+ else:
167
+ polyText = "none"
168
+ text = "Object: {} - {}".format( self.label , polyText )
169
+ return text
170
+
171
+ def fromJsonText(self, jsonText, objId):
172
+ self.id = objId
173
+ self.label = str(jsonText['label'])
174
+ self.polygon = [ Point(p[0],p[1]) for p in jsonText['polygon'] ]
175
+ if 'deleted' in jsonText.keys():
176
+ self.deleted = jsonText['deleted']
177
+ else:
178
+ self.deleted = 0
179
+ if 'verified' in jsonText.keys():
180
+ self.verified = jsonText['verified']
181
+ else:
182
+ self.verified = 1
183
+ if 'user' in jsonText.keys():
184
+ self.user = jsonText['user']
185
+ else:
186
+ self.user = ''
187
+ if 'date' in jsonText.keys():
188
+ self.date = jsonText['date']
189
+ else:
190
+ self.date = ''
191
+ if self.deleted == 1:
192
+ self.draw = False
193
+ else:
194
+ self.draw = True
195
+
196
+ def toJsonText(self):
197
+ objDict = {}
198
+ objDict['label'] = self.label
199
+ objDict['id'] = self.id
200
+ objDict['deleted'] = self.deleted
201
+ objDict['verified'] = self.verified
202
+ objDict['user'] = self.user
203
+ objDict['date'] = self.date
204
+ objDict['polygon'] = []
205
+ for pt in self.polygon:
206
+ objDict['polygon'].append([pt.x, pt.y])
207
+
208
+ return objDict
209
+
210
+ # The annotation of a whole image (doesn't support mixed annotations, i.e. combining CsPoly and CsBbox)
211
+ class Annotation:
212
+ # Constructor
213
+ def __init__(self, objType=CsObjectType.POLY):
214
+ # the width of that image and thus of the label image
215
+ self.imgWidth = 0
216
+ # the height of that image and thus of the label image
217
+ self.imgHeight = 0
218
+ # the list of objects
219
+ self.objects = []
220
+ assert objType in CsObjectType.__dict__.values()
221
+ self.objectType = objType
222
+
223
+ def toJson(self):
224
+ return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
225
+
226
+ def fromJsonText(self, jsonText):
227
+ jsonDict = json.loads(jsonText)
228
+ self.imgWidth = int(jsonDict['imgWidth'])
229
+ self.imgHeight = int(jsonDict['imgHeight'])
230
+ self.objects = []
231
+ for objId, objIn in enumerate(jsonDict[ 'objects' ]):
232
+ if self.objectType == CsObjectType.POLY:
233
+ obj = CsPoly()
234
+ elif self.objectType == CsObjectType.BBOX:
235
+ obj = CsBbox()
236
+ obj.fromJsonText(objIn, objId)
237
+ self.objects.append(obj)
238
+
239
+ def toJsonText(self):
240
+ jsonDict = {}
241
+ jsonDict['imgWidth'] = self.imgWidth
242
+ jsonDict['imgHeight'] = self.imgHeight
243
+ jsonDict['objects'] = []
244
+ for obj in self.objects:
245
+ objDict = obj.toJsonText()
246
+ jsonDict['objects'].append(objDict)
247
+
248
+ return jsonDict
249
+
250
+ # Read a json formatted polygon file and return the annotation
251
+ def fromJsonFile(self, jsonFile):
252
+ if not os.path.isfile(jsonFile):
253
+ # print('Given json file not found: {}'.format(jsonFile))
254
+ return
255
+ with open(jsonFile, 'r') as f:
256
+ jsonText = f.read()
257
+ self.fromJsonText(jsonText)
258
+
259
+ def toJsonFile(self, jsonFile):
260
+ with open(jsonFile, 'w') as f:
261
+ f.write(self.toJson())