JeVoisBase  1.20
JeVois Smart Embedded Machine Vision Toolkit Base Modules
Share this page:
PyPostYolo.py
Go to the documentation of this file.
1 import pyjevois
2 if pyjevois.pro: import libjevoispro as jevois
3 else: import libjevois as jevois
4 
5 import numpy as np
6 import 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
30 class PyPostYolo:
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.loadClasses)
60 
61  self.detecttype = jevois.Parameter(self, 'detecttype', 'str',
62  "Type of detection output format -- only RAWYOLO supported in Python",
63  'RAWYOLO', pc)
64  self.detecttype.setCallback(self.setDetectType)
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 
PyPostYolo.PyPostYolo.dthresh
dthresh
Definition: PyPostYolo.py:79
PyPostYolo.PyPostYolo.getLabel
def getLabel(self, id, conf)
Helper to get class name and confidence as a clean string, and a color that varies with class name.
Definition: PyPostYolo.py:170
PyPostYolo.PyPostYolo.confidences
confidences
Definition: PyPostYolo.py:36
split
std::vector< std::string > split(std::string const &input, std::string const &regex="\\s+")
PyPostYolo.PyPostYolo.loadClasses
def loadClasses(self, filename)
[Optional] Parameter callback: Load class names when 'classes' parameter value is changed by model zo...
Definition: PyPostYolo.py:98
PyPostYolo.PyPostYolo.init
def init(self)
[Optional] JeVois parameters initialization
Definition: PyPostYolo.py:49
PyPostYolo.PyPostYolo.__init__
def __init__(self)
[Optional] Constructor
Definition: PyPostYolo.py:33
PyPostYolo.PyPostYolo.classIds
classIds
Definition: PyPostYolo.py:35
jevois::ParameterCategory
PyPostYolo.PyPostYolo.freeze
def freeze(self, doit)
[Optional] Freeze some parameters that should not be changed at runtime.
Definition: PyPostYolo.py:91
PyPostYolo.PyPostYolo.cthresh
cthresh
Definition: PyPostYolo.py:75
PyPostYolo.PyPostYolo.imageh
imageh
Definition: PyPostYolo.py:124
PyPostYolo.PyPostYolo.process
def process(self, outs, preproc)
[Required] Main processing function: parse network output blobs and store resulting labels and scores...
Definition: PyPostYolo.py:115
PyPostYolo.PyPostYolo.maxnbox
maxnbox
Definition: PyPostYolo.py:70
PyPostYolo.PyPostYolo.detecttype
detecttype
Definition: PyPostYolo.py:61
PyPostYolo.PyPostYolo.yolopp
yolopp
Definition: PyPostYolo.py:45
PyPostYolo.PyPostYolo
Simple YOLO DNN post-processor written in python.
Definition: PyPostYolo.py:30
PyPostYolo.PyPostYolo.classmap
classmap
Definition: PyPostYolo.py:40
PyPostYolo.PyPostYolo.nms
nms
Definition: PyPostYolo.py:66
PyPostYolo.PyPostYolo.boxes
boxes
Definition: PyPostYolo.py:37
PyPostYolo.PyPostYolo.classes
classes
Definition: PyPostYolo.py:56
PyPostYolo.PyPostYolo.setDetectType
def setDetectType(self, dt)
[Optional] Parameter callback: set type of object detector
Definition: PyPostYolo.py:106
PyPostYolo.PyPostYolo.report
def report(self, outimg, helper, overlay, idle)
[Optional] Report the latest results obtained by process() by drawing them outimg is None or a RawIma...
Definition: PyPostYolo.py:189
PyPostYolo.PyPostYolo.classoffset
classoffset
Definition: PyPostYolo.py:52
yolo
Definition: Yolo.H:32