JeVoisBase  1.22
JeVois Smart Embedded Machine Vision Toolkit Base Modules
Share this page:
Loading...
Searching...
No Matches
CustomDNN.C
Go to the documentation of this file.
1// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2//
3// JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2016 by Laurent Itti, the University of Southern
4// California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5//
6// This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7// redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8// Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10// License for more details. You should have received a copy of the GNU General Public License along with this program;
11// if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12//
13// Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14// Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16/*! \file */
17
18#include <jevois/Core/Module.H>
19#include <jevois/Debug/Timer.H>
21#include <jevois/DNN/Pipeline.H>
23#include <opencv2/imgproc/imgproc.hpp>
24
25// icon from opencv
26
27// ####################################################################################################
28// A custom PostProcessorDetect that allows access to detected boxes
30{
31 public:
32 // Use inherited constructor
33 using jevois::dnn::PostProcessorDetect::PostProcessorDetect;
34
35 // Virtual destructor for safe inheritance
36 virtual ~MyPostProc()
37 { }
38
39 // Get access to the detections. Beware that this is not thread safe.
40 /* Note: since this tutorial was written, we have added latestDetections() to PostProcessorDetect which
41 achieves the same thing as what we are doing here. We are keeping this tutorial as still a valid example of how
42 to use derived post-processor classes to customize DNN processing. */
43 std::vector<jevois::ObjDetect> const & getDetections()
44 { return itsDetections; }
45};
46
47// ####################################################################################################
48// A modified Pipeline class that loads our custom MyPostProc for detection networks
50{
51 public:
52 // Use inherited constructor
54
55 // Virtual destructor
56 virtual ~MyPipeline()
57 { }
58
59 // When postproc is Detect, instantiate MyPostProc
60 /* This callback is invoked when "set(val)" is called on our postproc parameter, which happens when a model is
61 selected in the JeVois-Pro GUI by setting the "pipe" parameter of Pipeline, and the definition of that model is
62 parsed from YAML files and instantiated. */
63 void onParamChange(jevois::dnn::pipeline::postproc const & param,
64 jevois::dnn::pipeline::PostProc const & val) override
65 {
66 // Override default behavior when a Detect post-processor is desired:
67 if (val == jevois::dnn::pipeline::PostProc::Detect)
68 {
69 // Model your code here to follow that in Pipeline.C:
70
71 // If currently processing async net, wait until done:
73
74 // Nuke any old post-processor:
75 itsPostProcessor.reset(); removeSubComponent("postproc", false);
76
77 // Instantiate our custom post-processor:
78 itsPostProcessor = addSubComponent<MyPostProc>("postproc");
79 LINFO("Instantiated post-processor of type " << itsPostProcessor->className());
80 }
81 else
82 {
83 // Default behavior from base class for other post-processors:
85 }
86 }
87
88 // Allow external users to gain access to our post processor
89 std::shared_ptr<jevois::dnn::PostProcessor> getPostProcessor()
90 { return itsPostProcessor; }
91
92 // Allow external users to gain access to our pre processor
93 std::shared_ptr<jevois::dnn::PreProcessor> getPreProcessor()
94 { return itsPreProcessor; }
95};
96
97
98//! Example of modified DNN module with custom post-processing
99/*! This example shows you how to customize the JeVois DNN processing framework. Here, we create a custom post-processor
100 that enhances the functionality of PostProcessorDetect by extracting regions of interest for each detected
101 object. For this example, we simply compute an edge map for each detection box and display it as an overlay. Other
102 processing is possible, for example, detected regions of interest could be sent to another neural network for
103 additional processing.
104
105 The main goal of this module is as a tutorial for how to implement custom operations within the JeVois DNN framework
106 in C++. The main idea is to create derived classes over Pipeline and PostProcessorDetect.
107
108 @author Laurent Itti
109
110 @displayname Custom DNN
111 @videomapping JVUI 0 0 30.0 CropScale=RGB24@1024x576:YUYV 1920 1080 30.0 JeVois CustomDNN
112 @email itti\@usc.edu
113 @address University of Southern California, HNB-07A, 3641 Watt Way, Los Angeles, CA 90089-2520, USA
114 @copyright Copyright (C) 2024 by Laurent Itti, iLab and the University of Southern California
115 @mainurl http://jevois.org
116 @supporturl http://jevois.org/doc
117 @otherurl http://iLab.usc.edu
118 @license GPL v3
119 @distribution Unrestricted
120 @restrictions None
121 \ingroup modules */
123{
124 public:
125 // ####################################################################################################
126 //! Constructor
127 // ####################################################################################################
128 CustomDNN(std::string const & instance) : jevois::StdModule(instance)
129 {
130 // Modified here, we use our customized MyPipeline instead of jevois::dnn::Pipeline:
131 itsPipeline = addSubComponent<MyPipeline>("pipeline");
132 }
133
134 // ####################################################################################################
135 //! Virtual destructor for safe inheritance
136 // ####################################################################################################
137 virtual ~CustomDNN()
138 { }
139
140 // ####################################################################################################
141 //! Processing function implementation
142 // ####################################################################################################
143 void doprocess(jevois::InputFrame const & inframe, jevois::RawImage * outimg,
144 jevois::OptGUIhelper * helper, bool idle)
145 {
146 // Here, we add code to extract regions of interest and draw edge maps:
147
148 // If we have a second (scaled) image, assume this is the one we want to process:
149 jevois::RawImage const inimg = inframe.getp();
150
151 // Get a handle to the post-processor and check whether it is of derived type MyPostProc:
152 std::shared_ptr<MyPostProc> mpp = std::dynamic_pointer_cast<MyPostProc>(itsPipeline->getPostProcessor());
153
154 // If the dynamic cast succeeded, current pipeline is indeed using a post processor of type MyPostProc. Otherwise,
155 // just run the standard processing as in the regular DNN module, and return:
156 if ( ! mpp)
157 {
158 // Selected post-processor not of type MyPostProc, run regular processing:
159 itsPipeline->process(inimg, this, outimg, helper, idle);
160
161 return;
162 }
163
164 // If we make it here, the selected pipeline is using MyPostProc:
165
166 // In a parallel thread, convert full HD input frame to RGB while the net runs:
167 auto fut = jevois::async([&]() { return inframe.getCvRGB(); }); // could also use getCvBGR(), getCvGray(), etc
168
169 // Meanwhile, run the network:
170 itsPipeline->process(inimg, this, outimg, helper, idle);
171
172 // Get the converted frame (may block until ready):
173 cv::Mat inhd = fut.get();
174 if (jevois::frameNum() % 30 == 0) LINFO("Input frame is " << inhd.cols << 'x' << inhd.rows);
175
176 // Get the latest detections:
177 std::vector<jevois::ObjDetect> const & detections = mpp->getDetections();
178 if (jevois::frameNum() % 30 == 0) LINFO("Got " << detections.size() << " detections");
179
180 // As noted above, beware of thread safety. If you need to keep the detections across multiple video frames,
181 // make a deep copy of that "detections" vector. Here, it's just a zero-copy reference to the vector internally
182 // stored in the post processor. That vector will be overwritten on the next video frame.
183
184 // add your code here....
185 // full HD input image is in cv::Mat "inhd"
186 // object detection boxes are in "detections"
187
188 // If you need access to the PreProcessor, which will allow you to do coordinate transforms from full HD image
189 // to blob that was sent to the deep net, use itsPipeline->getPreProcessor()
190
191 // Here for example, we compute edge maps within each detected object and display them as overlays:
192 int i = 0;
193 for (jevois::ObjDetect const & d : detections)
194 {
195 // Get a crop from the full HD image:
196 cv::Rect r(d.tlx * inhd.cols / inimg.width,
197 d.tly * inhd.rows / inimg.height,
198 (d.brx - d.tlx) * inhd.cols / inimg.width,
199 (d.bry - d.tly) * inhd.rows / inimg.height);
200 cv::Mat roi = inhd(r); // no need to clone() here, cvtColor() below can handle this zero-copy cropping;
201 // but you may have to clone if you want to store the roi across multiple video frames.
202
203 // Some ROIs may be empty and openCV does not like that, so skip those:
204 if (roi.empty()) continue;
205
206 // For demo, compute Canny edges and create an RGBA image that is all transparent except for the edges:
207 cv::Mat gray_roi; cv::cvtColor(roi, gray_roi, cv::COLOR_BGR2GRAY);
208 cv::Mat edges; cv::Canny(gray_roi, edges, 50, 100, 3);
209 cv::Mat chans[4] { edges, edges, edges, edges };
210 cv::Mat mask; cv::merge(chans, 4, mask);
211
212 // Anything that is using the helper should be marked as for JEVOIS_PRO only:
213#ifdef JEVOIS_PRO
214 if (helper) // need this test to support headless mode (no display, no helper)
215 {
216 // Give a unique name to each edge ROI and display it. Note: if is_overlay is true in the call to drawImage()
217 // below, then coordinates for subsequent drawings will remain relative to the origin of the full frame drawn
218 // above using drawInputFrame(), and will automatically be scaled from processing resolution (imimg) to
219 // display resolution (inhd). If false, then coordinates will shift to be relative to the origin of the last
220 // drawn image. Here, we want is_overlay=true because we will be drawing several ROIs (one per detection) and
221 // we do not want the origin of the next ROI to be relative to the previous ROI; we want all ROIs to be drawn
222 // at specific locations relative to the input frame:
223 std::string roi_name = "roi" + std::to_string(i);
224 unsigned short rw = mask.cols, rh = mask.rows;
225
226 helper->drawImage(roi_name.c_str(), mask, true, r.x, r.y, rw, rh, false /*noalias*/, true /*is_overlay*/);
227
228 // Also draw a circle over each ROI to test that drawing works. Because is_overlay was true in drawImage, the
229 // coordinates here are just as if we were drawing on the processing resolution (inimg):
230 helper->drawCircle((d.tlx + d.brx)/2, (d.tly+d.bry)/2, std::min(d.brx - d.tlx, d.bry - d.tly)/2);
231 }
232#endif
233
234 // Test serial: if you want to send modified messages, perhaps the easiest is to modify the contents of the
235 // ObjDetect object (make a copy first as here d is just a const ref):
236 sendSerialObjDetImg2D(inhd.cols, inhd.rows, d);
237 }
238 }
239
240 // ####################################################################################################
241 //! Processing function, no video output
242 // ####################################################################################################
243 virtual void process(jevois::InputFrame && inframe) override
244 {
245 // Same code here as in base DNN module:
246
247 doprocess(inframe, nullptr, nullptr, false);
248 }
249
250 // ####################################################################################################
251 //! Processing function with video output to USB on JeVois-A33
252 // ####################################################################################################
253 virtual void process(jevois::InputFrame && inframe, jevois::OutputFrame && outframe) override
254 {
255 // Same code here as in base DNN module:
256
257 // Get the input frame:
258 jevois::RawImage const & inimg = inframe.get();
259 unsigned int const w = inimg.width, h = inimg.height;
260
261 // Get the output image:
262 jevois::RawImage outimg = outframe.get();
263
264 // Input and output sizes and format must match:
265 outimg.require("output", w, h, inimg.fmt);
266
267 // Copy input to output:
268 jevois::rawimage::paste(inimg, outimg, 0, 0);
269
270 // Process and draw any results (e.g., detected boxes) into outimg:
271 doprocess(inframe, &outimg, nullptr, false);
272
273 // Send the output image with our processing results to the host over USB:
274 outframe.send();
275 }
276
277#ifdef JEVOIS_PRO
278 // ####################################################################################################
279 //! Processing function with zero-copy and GUI on JeVois-Pro
280 // ####################################################################################################
281 virtual void process(jevois::InputFrame && inframe, jevois::GUIhelper & helper) override
282 {
283 // Same code here as in base DNN module:
284
285 // Compute overall frame rate, CPU usage, etc:
286 static jevois::Timer timer("main", 20, LOG_DEBUG);
287 std::string const & fpscpu = timer.stop();
288 timer.start();
289
290 // Start the display frame: winw, winh will be set by startFrame() to the display size, e.g., 1920x1080
291 unsigned short winw, winh;
292 bool idle = helper.startFrame(winw, winh);
293
294 // Display the camera input frame: if all zeros, x, y, w, h will be set by drawInputFrame() so as to show the
295 // video frame as large as possible and centered within the display (of size winw,winh)
296 int x = 0, y = 0; unsigned short w = 0, h = 0;
297 helper.drawInputFrame("c", inframe, x, y, w, h, true);
298
299 // Process and draw any results (e.g., detected boxes) as OpenGL overlay:
300 doprocess(inframe, nullptr, &helper, idle);
301
302 // Show overall frame rate, CPU, camera resolution, and display resolution, at bottom of screen:
303 helper.iinfo(inframe, fpscpu, winw, winh);
304
305 // Render the image and GUI:
306 helper.endFrame();
307 }
308#endif
309
310 // ####################################################################################################
311 protected:
312 // Modified here, we use MyPipeline instead of jevois::dnn::Pipeline:
313 std::shared_ptr<MyPipeline> itsPipeline;
314};
315
316// Allow the module to be loaded as a shared object (.so) file:
JEVOIS_REGISTER_MODULE(ArUcoBlob)
int h
Example of modified DNN module with custom post-processing.
Definition CustomDNN.C:123
void doprocess(jevois::InputFrame const &inframe, jevois::RawImage *outimg, jevois::OptGUIhelper *helper, bool idle)
Processing function implementation.
Definition CustomDNN.C:143
virtual void process(jevois::InputFrame &&inframe, jevois::GUIhelper &helper) override
Processing function with zero-copy and GUI on JeVois-Pro.
Definition CustomDNN.C:281
virtual void process(jevois::InputFrame &&inframe) override
Processing function, no video output.
Definition CustomDNN.C:243
CustomDNN(std::string const &instance)
Constructor.
Definition CustomDNN.C:128
virtual void process(jevois::InputFrame &&inframe, jevois::OutputFrame &&outframe) override
Processing function with video output to USB on JeVois-A33.
Definition CustomDNN.C:253
virtual ~CustomDNN()
Virtual destructor for safe inheritance.
Definition CustomDNN.C:137
std::shared_ptr< MyPipeline > itsPipeline
Definition CustomDNN.C:313
std::shared_ptr< jevois::dnn::PostProcessor > getPostProcessor()
Definition CustomDNN.C:89
void onParamChange(jevois::dnn::pipeline::postproc const &param, jevois::dnn::pipeline::PostProc const &val) override
Definition CustomDNN.C:63
std::shared_ptr< jevois::dnn::PreProcessor > getPreProcessor()
Definition CustomDNN.C:93
virtual ~MyPipeline()
Definition CustomDNN.C:56
virtual ~MyPostProc()
Definition CustomDNN.C:36
std::vector< jevois::ObjDetect > const & getDetections()
Definition CustomDNN.C:43
void removeSubComponent(std::shared_ptr< Comp > &component)
void drawCircle(float x, float y, float r, ImU32 col=IM_COL32(128, 255, 128, 255), bool filled=true)
void drawInputFrame(char const *name, InputFrame const &frame, int &x, int &y, unsigned short &w, unsigned short &h, bool noalias=false, bool casync=false)
bool startFrame(unsigned short &w, unsigned short &h)
void iinfo(jevois::InputFrame const &inframe, std::string const &fpscpu, unsigned short winw=0, unsigned short winh=0)
void drawImage(char const *name, RawImage const &img, int &x, int &y, unsigned short &w, unsigned short &h, bool noalias=false, bool isoverlay=false)
RawImage const & getp(bool casync=false) const
cv::Mat getCvRGB(bool casync=false) const
unsigned int fmt
unsigned int width
unsigned int height
void require(char const *info, unsigned int w, unsigned int h, unsigned int f) const
void sendSerialObjDetImg2D(unsigned int camw, unsigned int camh, float x, float y, float w, float h, std::vector< ObjReco > const &res)
StdModule(std::string const &instance)
std::string const & stop(double *seconds)
void onParamChange(pipeline::zooroot const &param, std::string const &val) override
Pipeline(std::string const &instance)
std::shared_ptr< PostProcessor > itsPostProcessor
std::shared_ptr< PreProcessor > itsPreProcessor
std::vector< ObjDetect > itsDetections
#define LINFO(msg)
void paste(RawImage const &src, RawImage &dest, int dx, int dy)
std::future< std::invoke_result_t< std::decay_t< Function >, std::decay_t< Args >... > > async(Function &&f, Args &&... args)