4
4
import sys
5
5
import cv2
6
6
7
+ import tags_constraint
8
+
7
9
"""
8
10
You can change it.
9
11
If IGNORE_ERRORS are True, opencv_draw_tools tried to solve the problems or
@@ -29,7 +31,50 @@ def get_lighter_color(color):
29
31
add = min (add ,30 )
30
32
return (color [0 ] + add , color [1 ] + add , color [2 ] + add )
31
33
32
- def add_tags (frame , position , tags , tag_position = None , alpha = 0.75 , color = (20 , 20 , 20 ), margin = 5 , font_info = (cv2 .FONT_HERSHEY_COMPLEX_SMALL , 0.75 , (255 ,255 ,255 ), 1 )):
34
+ def get_shape_tags (position , tags , margin = 5 ,
35
+ font_info = (cv2 .FONT_HERSHEY_COMPLEX_SMALL , 0.75 , (255 ,255 ,255 ), 1 )):
36
+ """Get information about how much the list of tags will occupy (width and height)
37
+ with the current configuration.
38
+
39
+ Keyword arguments:
40
+ position -- touple with 4 elements (x1, y1, x2, y2)
41
+ This elements must be between 0 and frame height/width.
42
+ tags -- list of strings/tags you want to check shape.
43
+ margin -- extra margin in pixels to be separeted with the selected zone. (default 5)
44
+ font_info -- touple with 4 elements (font, font_scale, font_color, thickness)
45
+ font -- opencv font (default cv2.FONT_HARSHEY_COMPLEX_SMALL)
46
+ font_scale -- scale of the fontm between 0 and 1 (default 0.75)
47
+ font_color -- color of the tags text, touple with 3 elements BGR (default (255,255,255) -> white)
48
+ BGR = Blue - Green - Red
49
+ thickness -- thickness of the text in pixels (default 1)
50
+ Return:
51
+ Return shape of the given tags with the given font information
52
+
53
+ """
54
+ font , font_scale , font_color , thickness = font_info
55
+ x1 , y1 , x2 , y2 = position
56
+
57
+ aux_tags = []
58
+ for tag in tags :
59
+ line = [x + '\n ' for x in tag .split ('\n ' )]
60
+ line [0 ] = line [0 ][:- 1 ]
61
+ for element in line :
62
+ aux_tags .append (element )
63
+ tags = aux_tags
64
+
65
+ text_width = - 1
66
+ text_height = - 1
67
+ line_height = - 1
68
+ for tag in tags :
69
+ size = cv2 .getTextSize (tag , font , font_scale , thickness )
70
+ text_width = max (text_width ,size [0 ][0 ])
71
+ text_height = max (text_height , size [0 ][1 ])
72
+ line_height = max (line_height , text_height + size [1 ] + margin )
73
+
74
+ return (text_width + margin * 3 , (margin + text_height )* (len (tags ) - 1 ) + 2 * text_height + margin * (len (tags )- 1 ))
75
+
76
+ def add_tags (frame , position , tags , tag_position = None , alpha = 0.75 , color = (20 , 20 , 20 ),
77
+ margin = 5 , font_info = (cv2 .FONT_HERSHEY_COMPLEX_SMALL , 0.75 , (255 ,255 ,255 ), 1 )):
33
78
"""Add tags to selected zone.
34
79
35
80
It was originally intended as an auxiliary method to add details to the select_zone()
@@ -40,7 +85,7 @@ def add_tags(frame, position, tags, tag_position=None, alpha=0.75, color=(20, 20
40
85
position -- touple with 4 elements (x1, y1, x2, y2)
41
86
This elements must be between 0 and 1 in case it is normalized
42
87
or between 0 and frame height/width.
43
- tags -- list of strings/tags you want to associate to the selected zone
88
+ tags -- list of strings/tags you want to associate to the selected zone.
44
89
tag_position -- position where you want to add the tags, relatively to the selected zone (default None)
45
90
If None provided it will auto select the zone where it fits better:
46
91
- First try to put the text on the Bottom Rigth corner
@@ -95,7 +140,8 @@ def add_tags(frame, position, tags, tag_position=None, alpha=0.75, color=(20, 20
95
140
fits_right = x2 + text_width + margin * 3 <= f_width
96
141
fits_left = x1 - (text_width + margin * 3 ) >= 0
97
142
fits_below = (text_height + margin )* len (tags ) - margin <= y2 - thickness
98
- fits_inside = x1 + text_width + margin * 3 <= x2 - thickness and y1 + (margin * 2 + text_height )* len (tags ) + text_height - margin <= y2 - thickness
143
+ fits_inside = x1 + text_width + margin * 3 <= x2 - thickness and \
144
+ y1 + (margin * 2 + text_height )* len (tags ) + text_height - margin <= y2 - thickness
99
145
100
146
if fits_right and fits_below :
101
147
tag_position = 'bottom_right'
@@ -115,12 +161,12 @@ def add_tags(frame, position, tags, tag_position=None, alpha=0.75, color=(20, 20
115
161
116
162
# Add triangle to know to whom each tag belongs
117
163
if tag_position == 'bottom_right' :
118
- pt1 = (x2 + margin - 1 , y2 - (margin * 2 + text_height )* len (tags ) - text_height - margin )
119
- pt2 = (pt1 [0 ], pt1 [1 ] + text_height + margin * 2 )
164
+ pt1 = (x2 + margin - 1 , y2 - (margin + text_height )* len (tags ) - text_height - margin * ( len ( tags ) - 1 ) )
165
+ pt2 = (pt1 [0 ], pt1 [1 ] + text_height + margin )
120
166
pt3 = (pt1 [0 ] - margin + 1 , pt1 [1 ] + int (text_height / 2 )+ margin )
121
167
elif tag_position == 'bottom_left' :
122
- pt1 = (x1 - margin + 1 , y2 - (margin * 2 + text_height )* len (tags ) - text_height - margin )
123
- pt2 = (pt1 [0 ], pt1 [1 ] + text_height + margin * 2 )
168
+ pt1 = (x1 - margin + 1 , y2 - (margin + text_height )* len (tags ) - text_height - margin * ( len ( tags ) - 1 ) )
169
+ pt2 = (pt1 [0 ], pt1 [1 ] + text_height + margin )
124
170
pt3 = (pt1 [0 ] + margin - 1 , pt1 [1 ] + int (text_height / 2 )+ margin )
125
171
elif tag_position == 'top' :
126
172
pt1 = (x1 + margin + int (text_width / 3 ), y1 - margin * 2 + 1 )
@@ -138,35 +184,38 @@ def add_tags(frame, position, tags, tag_position=None, alpha=0.75, color=(20, 20
138
184
extra_adjustment = 2 if tag [- 1 ] == '\n ' else 1
139
185
if tag_position == 'top' :
140
186
cv2 .rectangle (overlay , (x1 + margin , y1 - (margin + text_height )* reverse_i - margin * (reverse_i - 1 ) - text_height - margin * (extra_adjustment - 1 )),
141
- (x1 + text_width + margin * 3 , y1 - (margin + text_height )* reverse_i - margin * (reverse_i ) + text_height ), color ,- 1 )
187
+ (x1 + text_width + margin * 3 , y1 - (margin + text_height )* reverse_i - margin * (reverse_i ) + text_height ), color ,- 1 )
142
188
elif tag_position == 'inside' :
143
- cv2 .rectangle (overlay , (x1 + margin , y1 + (margin * 2 + text_height )* (i + 1 ) - text_height - margin - extra_adjustment ),
144
- (x1 + text_width + margin * 3 , y1 + (margin * 2 + text_height )* (i + 1 ) + text_height - margin ), color ,- 1 )
189
+ cv2 .rectangle (overlay , (x1 + margin , y1 + (margin * 2 + text_height )* (i + 1 ) + margin * i - text_height - margin * extra_adjustment ),
190
+ (x1 + text_width + margin * 3 , y1 + (margin * 2 + text_height )* (i + 1 ) + margin * i + text_height - margin ), color ,- 1 )
145
191
elif tag_position == 'bottom_left' :
146
- cv2 .rectangle (overlay , (x1 - (text_width + margin * 3 ), y2 - (margin * 2 + text_height )* reverse_i - text_height - margin * extra_adjustment ),
147
- (x1 - margin , y2 - (margin * 2 + text_height )* reverse_i + text_height - margin ), color ,- 1 )
192
+ cv2 .rectangle (overlay , (x1 - (text_width + margin * 3 ), y2 - (margin + text_height )* reverse_i - margin * ( reverse_i - 1 ) - text_height - margin * ( extra_adjustment - 1 ) ),
193
+ (x1 - margin , y2 - (margin + text_height )* reverse_i - margin * ( reverse_i ) + text_height ), color ,- 1 )
148
194
elif tag_position == 'bottom_right' :
149
- cv2 .rectangle (overlay , (x2 + margin , y2 - (margin * 2 + text_height )* reverse_i - text_height - margin * extra_adjustment ),
150
- (x2 + text_width + margin * 3 , y2 - (margin * 2 + text_height )* reverse_i + text_height - margin ), color ,- 1 )
151
- if tag_position != 'top' :
152
- y1 += margin
153
- y2 += margin
195
+ cv2 .rectangle (overlay , (x2 + margin , y2 - (margin + text_height )* reverse_i - margin * (reverse_i - 1 ) - text_height - margin * (extra_adjustment - 1 )),
196
+ (x2 + text_width + margin * 3 , y2 - (margin + text_height )* reverse_i - margin * (reverse_i ) + text_height ), color ,- 1 )
197
+
154
198
cv2 .addWeighted (overlay , alpha , frame , 1 - alpha , 0 , frame )
155
199
x1 , y1 , x2 , y2 = position
156
200
for i , tag in enumerate (tags ):
157
201
reverse_i = len (tags ) - i
158
202
extra_adjustment = int (margin * ( 0.5 if tag [- 1 ] == '\n ' else 0 ))
159
203
if tag_position == 'top' :
160
- cv2 .putText (frame , tag .replace ('\n ' ,'' ), (x1 + margin * 2 , y1 - (margin + text_height )* reverse_i - margin * (reverse_i - 1 ) + int (margin / 2 ) - extra_adjustment ), font , font_scale , font_color , thickness )
204
+ cv2 .putText (frame , tag .replace ('\n ' ,'' ),
205
+ (x1 + margin * 2 , y1 - (margin + text_height )* reverse_i - margin * (reverse_i - 1 ) + int (margin / 2 ) - extra_adjustment ),
206
+ font , font_scale , font_color , thickness )
161
207
elif tag_position == 'inside' :
162
- cv2 .putText (frame , tag .replace ('\n ' ,'' ), (x1 + margin * 2 , y1 + (margin * 2 + text_height )* (i + 1 ) - extra_adjustment ), font , font_scale , font_color , thickness )
208
+ cv2 .putText (frame , tag .replace ('\n ' ,'' ),
209
+ (x1 + margin * 2 , y1 + (margin * 2 + text_height )* (i + 1 ) + margin * i - extra_adjustment ),
210
+ font , font_scale , font_color , thickness )
163
211
elif tag_position == 'bottom_left' :
164
- cv2 .putText (frame , tag .replace ('\n ' ,'' ), (x1 - (text_width + margin * 2 ), y2 - (margin * 2 + text_height )* reverse_i - extra_adjustment ), font , font_scale , font_color , thickness )
212
+ cv2 .putText (frame , tag .replace ('\n ' ,'' ),
213
+ (x1 - (text_width + margin * 2 ), y2 - (margin + text_height )* reverse_i - margin * (reverse_i - 1 ) + int (margin / 2 ) - extra_adjustment ),
214
+ font , font_scale , font_color , thickness )
165
215
elif tag_position == 'bottom_right' :
166
- cv2 .putText (frame , tag .replace ('\n ' ,'' ), (x2 + margin * 2 , y2 - (margin * 2 + text_height )* reverse_i - extra_adjustment ), font , font_scale , font_color , thickness )
167
- if tag_position != 'top' :
168
- y1 += margin
169
- y2 += margin
216
+ cv2 .putText (frame , tag .replace ('\n ' ,'' ),
217
+ (x2 + margin * 2 , y2 - (margin + text_height )* reverse_i - margin * (reverse_i - 1 ) + int (margin / 2 ) - extra_adjustment ),
218
+ font , font_scale , font_color , thickness )
170
219
171
220
return frame
172
221
@@ -186,7 +235,8 @@ def add_peephole(frame, position, alpha=0.5, color=(110,70,45), thickness=2, lin
186
235
1 means totally visible and 0 totally invisible
187
236
color -- color of the selected zone, touple with 3 elements BGR (default (110,70,45) -> dark blue)
188
237
BGR = Blue - Green - Red
189
- normalized -- boolean parameter, if True, position provided normalized (between 0 and 1) else you shold provide concrete values (default False)
238
+ normalized -- boolean parameter, if True, position provided normalized (between 0 and 1)
239
+ else you shold provide concrete values (default False)
190
240
thickness -- thickness of the drawing in pixels (default 2)
191
241
corners -- boolean parameter, if True, also draw the corners of the rectangle
192
242
@@ -228,7 +278,8 @@ def adjust_position(shape, position, normalized=False, thickness=0):
228
278
position -- touple with 4 elements (x1, y1, x2, y2)
229
279
This elements must be between 0 and 1 in case it is normalized
230
280
or between 0 and frame height/width.
231
- normalized -- boolean parameter, if True, position provided normalized (between 0 and 1) else you shold provide concrete values (default False)
281
+ normalized -- boolean parameter, if True, position provided normalized (between 0 and 1)
282
+ else you shold provide concrete values (default False)
232
283
thickness -- thickness of the drawing in pixels (default 0)
233
284
234
285
Return:
@@ -270,7 +321,8 @@ def adjust_position(shape, position, normalized=False, thickness=0):
270
321
y1 = int (min (max (y1 , thickness ), y2 - thickness ))
271
322
return (x1 , y1 , x2 , y2 )
272
323
273
- def select_zone (frame , position , tags = [], tag_position = None , alpha = 0.9 , color = (110 ,70 ,45 ), normalized = False , thickness = 2 , filled = False , peephole = True ):
324
+ def select_zone (frame , position , tags = [], tag_position = None , alpha = 0.9 , color = (110 ,70 ,45 ),
325
+ normalized = False , thickness = 2 , filled = False , peephole = True , margin = 5 ):
274
326
"""Draw better rectangles to select zones.
275
327
276
328
Keyword arguments:
@@ -289,10 +341,12 @@ def select_zone(frame, position, tags=[], tag_position=None, alpha=0.9, color=(1
289
341
1 means totally visible and 0 totally invisible
290
342
color -- color of the selected zone, touple with 3 elements BGR (default (110,70,45) -> dark blue)
291
343
BGR = Blue - Green - Red
292
- normalized -- boolean parameter, if True, position provided normalized (between 0 and 1) else you should provide concrete values (default False)
344
+ normalized -- boolean parameter, if True, position provided normalized (between 0 and 1)
345
+ else you should provide concrete values (default False)
293
346
thickness -- thickness of the drawing in pixels (default 2)
294
347
filled -- boolean parameter, if True, will draw a filled rectangle with one-third opacity compared to the rectangle (default False)
295
348
peephole -- boolean parameter, if True, also draw additional effect, so it looks like a peephole
349
+ margin -- extra margin in pixels to be separeted with the selected zone (default 5)
296
350
297
351
Return:
298
352
A new drawed Frame
@@ -311,7 +365,56 @@ def select_zone(frame, position, tags=[], tag_position=None, alpha=0.9, color=(1
311
365
cv2 .rectangle (overlay , (x1 , y1 ), (x2 , y2 ), color ,thickness = thickness )
312
366
cv2 .addWeighted (overlay , alpha , frame , 1 - alpha , 0 , frame )
313
367
314
- frame = add_tags (frame , position , tags , tag_position = tag_position )
368
+ frame = add_tags (frame , position , tags , tag_position = tag_position , margin = margin )
369
+ return frame
370
+
371
+ def select_multiple_zones (frame , all_selected_zones , all_tags = None , alpha = 0.9 , color = (110 ,70 ,45 ),
372
+ normalized = False , thickness = 2 , filled = False , peephole = True , margin = 5 ):
373
+ """Draw better rectangles to select multiple zones at the same time.
374
+ It will put tags to the rectangles as better as possible, avoiding (if it is possible) overwritten information.
375
+
376
+ Keyword arguments:
377
+ frame -- opencv frame object where you want to draw
378
+ all_selected_zones -- list of touple with 4 elements (x1, y1, x2, y2)
379
+ This elements must be between 0 and 1 in case it is normalized
380
+ or between 0 and frame height/width in other case.
381
+ all_tags -- list of lists of strings/tags you want to associate to the selected zone.
382
+ The first element of the list is associated with the first element of all_selected_zones.
383
+ Therefore, both lists should have the same lenght. (default None)
384
+ alpha -- transparency of the selected zone on the image (default 0.9)
385
+ 1 means totally visible and 0 totally invisible
386
+ color -- color of the selected zones, touple with 3 elements BGR (default (110,70,45) -> dark blue)
387
+ BGR = Blue - Green - Red
388
+ normalized -- boolean parameter, if True, position provided normalized (between 0 and 1)
389
+ else you should provide concrete values (default False)
390
+ thickness -- thickness of the drawing in pixels (default 2)
391
+ filled -- boolean parameter, if True, will draw a filled rectangle with one-third opacity compared to the rectangle (default False)
392
+ peephole -- boolean parameter, if True, also draw additional effect, so it looks like a peephole
393
+ margin -- extra margin in pixels to be separeted with the selected zone (default 5)
394
+
395
+ Return:
396
+ A new drawed Frame
397
+
398
+ """
399
+ all_selected_zones = [adjust_position (frame .shape [:2 ], zone , normalized = normalized , thickness = thickness ) for zone in all_selected_zones ]
400
+ if not all_tags :
401
+ for zone in all_selected_zones :
402
+ frame = select_zone (frame , zone , alpha = alpha , color = color , normalized = normalized ,
403
+ thickness = thickness , filled = filled , peephole = peephole )
404
+ else :
405
+ f_height , f_width = frame .shape [:2 ]
406
+ all_tags_shapes = []
407
+ for i , zone in enumerate (all_selected_zones ):
408
+ all_tags_shapes .append (get_shape_tags (zone , all_tags [i ], margin = margin ))
409
+ # Here you could pass the frame if you want to see where tags_constraint thinks the tags will be. Just: frame=frame \/
410
+ best_position = tags_constraint .get_possible_positions (f_width , f_height , all_selected_zones , all_tags_shapes , margin = margin , frame = [])
411
+ for i , zone in enumerate (all_selected_zones ):
412
+ if best_position :
413
+ position = best_position [i ]
414
+ else :
415
+ position = None
416
+ frame = select_zone (frame , zone , tags = all_tags [i ], tag_position = position , alpha = alpha , color = color ,
417
+ thickness = thickness , filled = filled , peephole = peephole , margin = margin )
315
418
return frame
316
419
317
420
def webcam_test ():
@@ -326,9 +429,12 @@ def webcam_test():
326
429
frame = cv2 .flip (frame , 1 )
327
430
if ret :
328
431
keystroke = cv2 .waitKey (1 )
329
- position = (0.33 ,0.2 ,0.66 ,0.8 )
330
- tags = ['MIT License' , '(C) Copyright\n Fernando\n Perez\n Gutierrez' ]
331
- frame = select_zone (frame , position , tags = tags , color = (130 ,58 ,14 ), thickness = 2 , filled = True , normalized = True )
432
+ position1 = (0.33 ,0.2 ,0.66 ,0.8 )
433
+ tags1 = ['MIT License' , '(C) Copyright\n Fernando\n Perez\n Gutierrez\n hola\n ' , 'mundo' ]
434
+ position2 = (0.22 ,0.4 ,0.3 ,0.88 )
435
+ tags2 = ['Release' , 'v1.0.0' ]
436
+ frame = select_multiple_zones (frame , [position1 ,position2 ], all_tags = [tags1 ,tags2 ], color = (14 ,28 ,200 ),
437
+ thickness = 2 , filled = True , normalized = True )
332
438
cv2 .imshow (window_name , frame )
333
439
# True if escape 'esc' is pressed
334
440
if keystroke == 27 :
@@ -343,6 +449,9 @@ def get_complete_help():
343
449
* select_zone:
344
450
{}
345
451
452
+ * select_multiple_zones:
453
+ {}
454
+
346
455
* add_peephole:
347
456
{}
348
457
@@ -362,7 +471,7 @@ def get_complete_help():
362
471
* webcam_test:
363
472
{}
364
473
365
- ''' .format (select_zone .__doc__ , add_peephole .__doc__ ,
474
+ ''' .format (select_zone .__doc__ , select_multiple_zones . __doc__ , add_peephole .__doc__ ,
366
475
add_tags .__doc__ , get_lighter_color .__doc__ , adjust_position .__doc__ ,
367
476
webcam_test .__doc__ )
368
477
0 commit comments