JeVoisBase  1.22
JeVois Smart Embedded Machine Vision Toolkit Base Modules
Share this page:
Loading...
Searching...
No Matches
PyPostYolo.py
Go to the documentation of this file.
1import pyjevois
2if pyjevois.pro: import libjevoispro as jevois
3else: import libjevois as jevois
4
5import numpy as np
6import cv2
7
8## Simple YOLO DNN post-processor written in python
9#
10# Compare this code to the C++ PostProcessorDetect (which has more functionality than here):
11# - Abstract base: https://github.com/jevois/jevois/blob/master/include/jevois/DNN/PostProcessor.H
12# - Header: https://github.com/jevois/jevois/blob/master/include/jevois/DNN/PostProcessorDetect.H
13# - Implementation: https://github.com/jevois/jevois/blob/master/src/jevois/DNN/PostProcessorDetect.C
14#
15# Instead of re-inventing the wheel, this code uses the YOLO post-processor that we have implemented in C++,
16# as that C++ code is quite complex and multi-threaded for speed.
17#
18# @author Laurent Itti
19#
20# @email itti\@usc.edu
21# @address University of Southern California, HNB-07A, 3641 Watt Way, Los Angeles, CA 90089-2520, USA
22# @copyright Copyright (C) 2022 by Laurent Itti, iLab and the University of Southern California
23# @mainurl http://jevois.org
24# @supporturl http://jevois.org/doc
25# @otherurl http://iLab.usc.edu
26# @license GPL v3
27# @distribution Unrestricted
28# @restrictions None
29# @ingroup pydnn
31 # ###################################################################################################
32 ## [Optional] Constructor
33 def __init__(self):
34 # results of process(), held here for use by report():
35 self.classIds = []
36 self.confidences = []
37 self.boxes = []
38
39 # map from class index to class name:
40 self.classmap = None
41
42 # Add a C++ YOLO post-processor. This instantiates a C++ PostProcessorDetectYOLOforPython.
43 # That one already creates parameters for anchors, scalexy, and sigmoid via an underlying
44 # C++ PostProcessorDetectYOLO
45 self.yolopp = jevois.PyPostYOLO()
46
47 # ###################################################################################################
48 ## [Optional] JeVois parameters initialization
49 def init(self):
50 pc = jevois.ParameterCategory("DNN Post-Processing Options", "")
51
52 self.classoffset = jevois.Parameter(self, 'classoffset', 'int',
53 "Offset to apply to class indices",
54 0, pc)
55
56 self.classes = jevois.Parameter(self, 'classes', 'str',
57 "Path to text file with names of object classes",
58 '', pc)
59 self.classes.setCallback(self.loadClassesloadClasses)
60
61 self.detecttype = jevois.Parameter(self, 'detecttype', 'str',
62 "Type of detection output format -- only RAWYOLO supported in Python",
63 'RAWYOLO', pc)
65
66 self.nms = jevois.Parameter(self, 'nms', 'float',
67 "Non-maximum suppression intersection-over-union threshold in percent",
68 45.0, pc)
69
70 self.maxnbox = jevois.Parameter(self, 'maxnbox', 'uint',
71 "Max number of top-scoring boxes to report (for YOLO flavors, "
72 "this is the max for each scale)",
73 500, pc);
74
75 self.cthresh = jevois.Parameter(self, 'cthresh', 'float',
76 "Classification threshold, in percent confidence",
77 20.0, pc)
78
79 self.dthresh = jevois.Parameter(self, 'dthresh', 'float',
80 "Detection box threshold (in percent confidence) above which "
81 "predictions will be reported. Not all networks use a separate box threshold, "
82 "many only use one threshold confidence threshold (cthresh parameter). The YOLO "
83 "family is an example that uses both box and classification confidences",
84 15.0, pc)
85
86 self.sigmoid = jevois.Parameter(self, 'sigmoid', 'bool',
87 "When true, apply sigmoid to class confidence scores",
88 False, pc)
89
90 # note: compared to the C++ code, only RAWYOLO detecttype is supported here.
91
92 # ###################################################################################################
93 ## [Optional] Freeze some parameters that should not be changed at runtime.
94 # The JeVois core will call this with doit being either True or False
95 def freeze(self, doit):
96 self.classes.freeze(doit)
97 self.detecttype.freeze(doit)
98 self.yolopp.freeze(doit)
99
100 # ###################################################################################################
101 ## [Optional] Parameter callback: Load class names when 'classes' parameter value is changed by model zoo
102 def loadClasses(self, filename):
103 if filename:
104 jevois.LINFO(f"Loading {filename}...")
105 f = open(pyjevois.share + '/' + filename, 'rt') # will throw if file not found
106 self.classmap = f.read().rstrip('\n').split('\n')
107
108 # ###################################################################################################
109 ## [Optional] Parameter callback: set type of object detector
110 def setDetectType(self, dt):
111 if dt != 'RAWYOLO':
112 jevois.LFATAL(f"Invalid detecttype {dt} -- only RAWYOLO is supported in Python")
113
114 # ###################################################################################################
115 ## [Required] Main processing function: parse network output blobs and store resulting labels and scores locally.
116 # outs is a list of numpy arrays for the network's outputs.
117 # preproc is a handle to the pre-processor that was used, useful to recover transforms from original image
118 # to cropped/resized network inputs.
119 def process(self, outs, preproc):
120 if (len(outs) == 0): jevois.LFATAL("No outputs received, we need at least one.");
121
122 # Clear any old results:
123 self.classIds.clear()
124 self.confidences.clear()
125 self.boxes.clear()
126
127 # To send serial messages, it may be useful to know the input image size:
128 self.imagew, self.imageh = preproc.imagesize()
129
130 # To draw boxes, we will need to:
131 # - scale from [0..1]x[0..1] to blobw x blobh
132 # - scale and center from blobw x blobh to input image w x h, provided by PreProcessor::b2i()
133 # - when using the GUI, we further scale and translate to OpenGL display coordinates using GUIhelper::i2d()
134 # Here we assume that the first blob sets the input size.
135 bw, bh = preproc.blobsize(0)
136
137 # Process the newly received network outputs:
138 # Note: boxes returned are (x, y, w, h), which is what NMSboxes() below wants:
139 classids, confs, boxes = self.yolopp.yolo(outs,
140 len(self.classmap),
141 self.dthresh.get() * 0.01,
142 self.cthresh.get() * 0.01,
143 bw, bh,
144 self.classoffset.get(),
145 self.maxnbox.get(),
146 self.sigmoid.get())
147
148 # Cleanup overlapping boxes:
149 indices = cv2.dnn.NMSBoxes(boxes, confs, self.cthresh.get() * 0.01, self.nms.get() * 0.01)
150
151 # Now clamp boxes to be within blob, and adjust the boxes from blob size to input image size:
152 for i in indices:
153 x, y, w, h = boxes[i]
154
155 # Clamp box coords to within network's input blob, and convert box to (x1, y1, x2, y2):
156 x1 = min(bw - 1, max(0, x))
157 x2 = min(bw - 1, max(0, x + w))
158 y1 = min(bh - 1, max(0, y))
159 y2 = min(bh - 1, max(0, y + h))
160
161 # Scale box from input blob to input image:
162 x1, y1 = preproc.b2i(x1, y1, 0)
163 x2, y2 = preproc.b2i(x2, y2, 0)
164
165 self.boxes.append( [x1, y1, x2, y2] )
166
167 # Note: further scaling and translation to OpenGL display coords is handled internally by GUI helper
168 # using GUIhelper::i2d() when we call helper.drawRect(), etc below in report()
169
170 self.classIds = [classids[i] for i in indices]
171 self.confidences = [confs[i] for i in indices]
172
173 # ###################################################################################################
174 ## Helper to get class name and confidence as a clean string, and a color that varies with class name
175 def getLabel(self, id, conf):
176 if self.classmap is None or id < 0 or id >= len(self.classmap): categ = 'unknown'
177 else: categ = self.classmap[id]
178
179 color = jevois.stringToRGBA(categ, 255)
180
181 return ( ("%s: %.2f" % (categ, conf * 100.0)), color & 0xffffffff)
182
183 # ###################################################################################################
184 ## [Optional] Report the latest results obtained by process() by drawing them
185 # outimg is None or a RawImage to draw into when in Legacy mode (drawing to an image sent to USB)
186 # helper is None or a GUIhelper to do OpenGL drawings when in JeVois-Pro GUI mode
187 # overlay is True if user wishes to see overlay text
188 # idle is true if keyboard/mouse have been idle for a while, which typically would reduce what is displayed
189 #
190 # Note that report() is called on every frame even though the network may run slower or take some time to load and
191 # initialize, thus you should be prepared for report() being called even before process() has ever been called
192 # (i.e., create some class member variables to hold the reported results, initialize them to some defaults in your
193 # constructor, report their current values here, and update their values in process()).
194 def report(self, outimg, helper, overlay, idle):
195
196 # Legacy JeVois mode: Write results into YUYV RawImage to send over USB:
197 if outimg is not None:
198 if overlay:
199 for i in range(len(self.classIds)):
200 label, color = self.getLabel(self.classIds[i], self.confidences[i])
201 x1, y1, x2, y2 = self.boxes[i]
202 jevois.drawRect(outimg, x1, y1, x2 - x1, y2 - y1, 2, jevois.YUYV.LightGreen)
203 jevois.writeText(outimg, label, x1 + 6, y1 + 2, jevois.YUYV.LightGreen, jevois.Font.Font10x20)
204
205 # JeVois-Pro mode: Write the results as OpenGL overlay boxes and text on top of the video:
206 if helper is not None:
207 if overlay:
208 for i in range(len(self.classIds)):
209 label, color = self.getLabel(self.classIds[i], self.confidences[i])
210 x1, y1, x2, y2 = self.boxes[i]
211 helper.drawRect(x1, y1, x2, y2, color & 0xffffffff, True)
212 helper.drawText(x1 + 3, y1 + 3, label, color & 0xffffffff)
213
214 # Could here send serial messages, or do some other processing over classIds, confidences and boxes
215
Simple YOLO DNN post-processor written in python.
Definition PyPostYolo.py:30
process(self, outs, preproc)
[Required] Main processing function: parse network output blobs and store resulting labels and scores...
setDetectType(self, dt)
[Optional] Parameter callback: set type of object detector
__init__(self)
[Optional] Constructor
Definition PyPostYolo.py:33
freeze(self, doit)
[Optional] Freeze some parameters that should not be changed at runtime.
Definition PyPostYolo.py:95
getLabel(self, id, conf)
Helper to get class name and confidence as a clean string, and a color that varies with class name.
report(self, outimg, helper, overlay, idle)
[Optional] Report the latest results obtained by process() by drawing them outimg is None or a RawIma...
loadClasses(self, filename)
[Optional] Parameter callback: Load class names when 'classes' parameter value is changed by model zo...
init(self)
[Optional] JeVois parameters initialization
Definition PyPostYolo.py:49
Definition Yolo.H:33