3
3
import warnings
4
4
from concurrent .futures import ThreadPoolExecutor , as_completed
5
5
from typing import Tuple , List , Sequence , Optional
6
+ from numpy .typing import NDArray
6
7
7
8
import cv2
8
9
import numpy as np
16
17
17
18
# Define custom "types" for type hints
18
19
BBox = Tuple [int , int , int , int ] # bounding box in the form (x,y,width,height) with x,y top left corner
20
+ TemplateTuple = Tuple [str , NDArray , Optional [NDArray ]]
19
21
20
- def _findLocalMax_ (corrMap , score_threshold = 0.6 ):
22
+ def _findLocalMax_ (corrMap : NDArray , score_threshold = 0.6 ):
21
23
"""Get coordinates of the local maximas with values above a threshold in the image of the correlation map."""
22
24
# If depending on the shape of the correlation map
23
25
if corrMap .shape == (1 ,1 ): ## Template size = Image size -> Correlation map is a single digit')
@@ -46,12 +48,12 @@ def _findLocalMax_(corrMap, score_threshold=0.6):
46
48
47
49
48
50
49
- def _findLocalMin_ (corrMap , score_threshold = 0.4 ):
51
+ def _findLocalMin_ (corrMap : NDArray , score_threshold = 0.4 ):
50
52
"""Find coordinates of local minimas with values below a threshold in the image of the correlation map."""
51
53
return _findLocalMax_ (- corrMap , - score_threshold )
52
54
53
55
54
- def computeScoreMap (template , image , method :int = cv2 .TM_CCOEFF_NORMED , mask = None ):
56
+ def computeScoreMap (template : NDArray , image : NDArray , method :int = cv2 .TM_CCOEFF_NORMED , mask = None ):
55
57
"""
56
58
Compute score map provided numpy array for template and image (automatically converts images if necessary).
57
59
The template must be smaller or as large as the image.
@@ -90,7 +92,7 @@ def computeScoreMap(template, image, method:int = cv2.TM_CCOEFF_NORMED, mask=Non
90
92
return cv2 .matchTemplate (image , template , method , mask = mask )
91
93
92
94
93
- def findMatches (listTemplates , image , method :int = cv2 .TM_CCOEFF_NORMED , N_object = float ("inf" ), score_threshold :float = 0.5 , searchBox :Optional [BBox ] = None ) -> List [Hit ]:
95
+ def findMatches (listTemplates : Sequence [ TemplateTuple ] , image : NDArray , method :int = cv2 .TM_CCOEFF_NORMED , N_object = float ("inf" ), score_threshold :float = 0.5 , searchBox :Optional [BBox ] = None ) -> List [Hit ]:
94
96
"""
95
97
Find all possible templates locations satisfying the score threshold provided a list of templates to search and an image.
96
98
@@ -174,7 +176,7 @@ def findMatches(listTemplates, image, method:int = cv2.TM_CCOEFF_NORMED, N_objec
174
176
175
177
return listHit # All possible hits before Non-Maxima Supression
176
178
177
- def _multi_compute (tempTuple , image , method :int , N_object :int , score_threshold :float , xOffset :int , yOffset :int , listHit :Sequence [Hit ]):
179
+ def _multi_compute (tempTuple : Sequence [ TemplateTuple ] , image : NDArray , method :int , N_object :int , score_threshold :float , xOffset :int , yOffset :int , listHit :Sequence [Hit ]):
178
180
"""
179
181
Find all possible template locations satisfying the score threshold provided a template to search and an image.
180
182
Add the hits found to the provided listHit, this function is running in parallel each instance for a different templates.
@@ -242,7 +244,7 @@ def _multi_compute(tempTuple, image, method:int, N_object:int, score_threshold:f
242
244
listHit .extend (newHits )
243
245
244
246
245
- def matchTemplates (listTemplates , image , method :int = cv2 .TM_CCOEFF_NORMED , N_object = float ("inf" ), score_threshold :float = 0.5 , maxOverlap :float = 0.25 , searchBox :Optional [BBox ] = None ) -> List [Hit ]:
247
+ def matchTemplates (listTemplates : List [ TemplateTuple ] , image : NDArray , method :int = cv2 .TM_CCOEFF_NORMED , N_object = float ("inf" ), score_threshold :float = 0.5 , maxOverlap :float = 0.25 , searchBox :Optional [BBox ] = None ) -> List [Hit ]:
246
248
"""
247
249
Search each template in the image, and return the best N_object locations which offer the best score and which do not overlap above the maxOverlap threshold.
248
250
@@ -294,7 +296,7 @@ def matchTemplates(listTemplates, image, method:int = cv2.TM_CCOEFF_NORMED, N_ob
294
296
return NMS (listHits , score_threshold , sortAscending , N_object , maxOverlap )
295
297
296
298
297
- def drawBoxesOnRGB (image , listHit :Sequence [Hit ], boxThickness :int = 2 , boxColor :Tuple [int ,int ,int ] = (255 , 255 , 00 ), showLabel :bool = False , labelColor = (255 , 255 , 0 ), labelScale = 0.5 ):
299
+ def drawBoxesOnRGB (image : NDArray , listHit :Sequence [Hit ], boxThickness :int = 2 , boxColor :Tuple [int ,int ,int ] = (255 , 255 , 00 ), showLabel :bool = False , labelColor = (255 , 255 , 0 ), labelScale = 0.5 ):
298
300
"""
299
301
Return a copy of the image with predicted template locations as bounding boxes overlaid on the image
300
302
The name of the template can also be displayed on top of the bounding box with showLabel=True
@@ -341,7 +343,7 @@ def drawBoxesOnRGB(image, listHit:Sequence[Hit], boxThickness:int=2, boxColor:Tu
341
343
return outImage
342
344
343
345
344
- def drawBoxesOnGray (image , tableHit , boxThickness = 2 , boxColor = 255 , showLabel = False , labelColor = 255 , labelScale = 0.5 ):
346
+ def drawBoxesOnGray (image : NDArray , listHit : Sequence [ Hit ] , boxThickness = 2 , boxColor = 255 , showLabel = False , labelColor = 255 , labelScale = 0.5 ):
345
347
"""
346
348
Same as drawBoxesOnRGB but with Graylevel.
347
349
If a RGB image is provided, the output image will be a grayscale image
@@ -350,7 +352,7 @@ def drawBoxesOnGray(image, tableHit, boxThickness=2, boxColor=255, showLabel=Fal
350
352
----------
351
353
- image : image in which the search was performed
352
354
353
- - tableHit : list of hit as returned by matchTemplates or findMatches
355
+ - listHit : list of hit as returned by matchTemplates or findMatches
354
356
355
357
- boxThickness: int
356
358
thickness of bounding box contour in pixels
@@ -370,12 +372,20 @@ def drawBoxesOnGray(image, tableHit, boxThickness=2, boxColor=255, showLabel=Fal
370
372
original image with predicted template locations depicted as bounding boxes
371
373
"""
372
374
# Convert RGB to grayscale
373
- if image .ndim == 3 : outImage = cv2 .cvtColor (image , cv2 .COLOR_RGB2GRAY ) # convert to RGB to be able to show detections as color box on grayscale image
374
- else : outImage = image .copy ()
375
+ outImage = cv2 .cvtColor (image , cv2 .COLOR_RGB2GRAY ) if image .ndim == 3 else image .copy ()
375
376
376
- for _ , row in tableHit .iterrows ():
377
- x ,y ,w ,h = row ['BBox' ]
377
+ for label , bbox , _ in listHit :
378
+
379
+ x ,y ,w ,h = bbox
378
380
cv2 .rectangle (outImage , (x , y ), (x + w , y + h ), color = boxColor , thickness = boxThickness )
379
- if showLabel : cv2 .putText (outImage , text = row ['TemplateName' ], org = (x , y ), fontFace = cv2 .FONT_HERSHEY_SIMPLEX , fontScale = labelScale , color = labelColor , lineType = cv2 .LINE_AA )
381
+
382
+ if showLabel :
383
+ cv2 .putText (outImage ,
384
+ text = label ,
385
+ org = (x , y ),
386
+ fontFace = cv2 .FONT_HERSHEY_SIMPLEX ,
387
+ fontScale = labelScale ,
388
+ color = labelColor ,
389
+ lineType = cv2 .LINE_AA )
380
390
381
- return outImage
391
+ return outImage
0 commit comments