JeVoisBase  1.21
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 # note: compared to the C++ code, only RAWYOLO detecttype is supported here.
87
88 # ###################################################################################################
89 ## [Optional] Freeze some parameters that should not be changed at runtime.
90 # The JeVois core will call this with doit being either True or False
91 def freeze(self, doit):
92 self.classes.freeze(doit)
93 self.detecttype.freeze(doit)
94 self.yolopp.freeze(doit)
95
96 # ###################################################################################################
97 ## [Optional] Parameter callback: Load class names when 'classes' parameter value is changed by model zoo
98 def loadClasses(self, filename):
99 if filename:
100 jevois.LINFO(f"Loading {filename}...")
101 f = open(pyjevois.share + '/' + filename, 'rt') # will throw if file not found
102 self.classmap = f.read().rstrip('\n').split('\n')
103
104 # ###################################################################################################
105 ## [Optional] Parameter callback: set type of object detector
106 def setDetectType(self, dt):
107 if dt != 'RAWYOLO':
108 jevois.LFATAL(f"Invalid detecttype {dt} -- only RAWYOLO is supported in Python")
109
110 # ###################################################################################################
111 ## [Required] Main processing function: parse network output blobs and store resulting labels and scores locally.
112 # outs is a list of numpy arrays for the network's outputs.
113 # preproc is a handle to the pre-processor that was used, useful to recover transforms from original image
114 # to cropped/resized network inputs.
115 def process(self, outs, preproc):
116 if (len(outs) == 0): jevois.LFATAL("No outputs received, we need at least one.");
117
118 # Clear any old results:
119 self.classIds.clear()
120 self.confidences.clear()
121 self.boxes.clear()
122
123 # To send serial messages, it may be useful to know the input image size:
124 self.imagew, self.imageh = preproc.imagesize()
125
126 # To draw boxes, we will need to:
127 # - scale from [0..1]x[0..1] to blobw x blobh
128 # - scale and center from blobw x blobh to input image w x h, provided by PreProcessor::b2i()
129 # - when using the GUI, we further scale and translate to OpenGL display coordinates using GUIhelper::i2d()
130 # Here we assume that the first blob sets the input size.
131 bw, bh = preproc.blobsize(0)
132
133 # Process the newly received network outputs:
134 # Note: boxes returned are (x, y, w, h), which is what NMSboxes() below wants:
135 classids, confs, boxes = self.yolopp.yolo(outs,
136 len(self.classmap),
137 self.dthresh.get() * 0.01,
138 self.cthresh.get() * 0.01,
139 bw, bh,
140 self.classoffset.get(),
141 self.maxnbox.get())
142
143 # Cleanup overlapping boxes:
144 indices = cv2.dnn.NMSBoxes(boxes, confs, self.cthresh.get() * 0.01, self.nms.get() * 0.01)
145
146 # Now clamp boxes to be within blob, and adjust the boxes from blob size to input image size:
147 for i in indices:
148 x, y, w, h = boxes[i]
149
150 # Clamp box coords to within network's input blob, and convert box to (x1, y1, x2, y2):
151 x1 = min(bw - 1, max(0, x))
152 x2 = min(bw - 1, max(0, x + w))
153 y1 = min(bh - 1, max(0, y))
154 y2 = min(bh - 1, max(0, y + h))
155
156 # Scale box from input blob to input image:
157 x1, y1 = preproc.b2i(x1, y1, 0)
158 x2, y2 = preproc.b2i(x2, y2, 0)
159
160 self.boxes.append( [x1, y1, x2, y2] )
161
162 # Note: further scaling and translation to OpenGL display coords is handled internally by GUI helper
163 # using GUIhelper::i2d() when we call helper.drawRect(), etc below in report()
164
165 self.classIds = [classids[i] for i in indices]
166 self.confidences = [confs[i] for i in indices]
167
168 # ###################################################################################################
169 ## Helper to get class name and confidence as a clean string, and a color that varies with class name
170 def getLabel(self, id, conf):
171 if self.classmap is None or id < 0 or id >= len(self.classmap): categ = 'unknown'
172 else: categ = self.classmap[id]
173
174 color = jevois.stringToRGBA(categ, 255)
175
176 return ( ("%s: %.2f" % (categ, conf * 100.0)), color & 0xffffffff)
177
178 # ###################################################################################################
179 ## [Optional] Report the latest results obtained by process() by drawing them
180 # outimg is None or a RawImage to draw into when in Legacy mode (drawing to an image sent to USB)
181 # helper is None or a GUIhelper to do OpenGL drawings when in JeVois-Pro GUI mode
182 # overlay is True if user wishes to see overlay text
183 # idle is true if keyboard/mouse have been idle for a while, which typically would reduce what is displayed
184 #
185 # Note that report() is called on every frame even though the network may run slower or take some time to load and
186 # initialize, thus you should be prepared for report() being called even before process() has ever been called
187 # (i.e., create some class member variables to hold the reported results, initialize them to some defaults in your
188 # constructor, report their current values here, and update their values in process()).
189 def report(self, outimg, helper, overlay, idle):
190
191 # Legacy JeVois mode: Write results into YUYV RawImage to send over USB:
192 if outimg is not None:
193 if overlay:
194 for i in range(len(self.classIds)):
195 label, color = self.getLabel(self.classIds[i], self.confidences[i])
196 x1, y1, x2, y2 = self.boxes[i]
197 jevois.drawRect(outimg, x1, y1, x2 - x1, y2 - y1, 2, jevois.YUYV.LightGreen)
198 jevois.writeText(outimg, label, x1 + 6, y1 + 2, jevois.YUYV.LightGreen, jevois.Font.Font10x20)
199
200 # JeVois-Pro mode: Write the results as OpenGL overlay boxes and text on top of the video:
201 if helper is not None:
202 if overlay:
203 for i in range(len(self.classIds)):
204 label, color = self.getLabel(self.classIds[i], self.confidences[i])
205 x1, y1, x2, y2 = self.boxes[i]
206 helper.drawRect(x1, y1, x2, y2, color & 0xffffffff, True)
207 helper.drawText(x1 + 3, y1 + 3, label, color & 0xffffffff)
208
209 # Could here send serial messages, or do some other processing over classIds, confidences and boxes
210
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:91
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...
Definition PyPostYolo.py:98
init(self)
[Optional] JeVois parameters initialization
Definition PyPostYolo.py:49
Definition Yolo.H:33