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