6f7a5aff76b32499b3db35b70b8338ff0694eb85
[tidl/tidl-api.git] / examples / ssd_multibox / main.cpp
1 /******************************************************************************
2  * Copyright (c) 2018, Texas Instruments Incorporated - http://www.ti.com/
3  *   All rights reserved.
4  *
5  *   Redistribution and use in source and binary forms, with or without
6  *   modification, are permitted provided that the following conditions are met:
7  *       * Redistributions of source code must retain the above copyright
8  *         notice, this list of conditions and the following disclaimer.
9  *       * Redistributions in binary form must reproduce the above copyright
10  *         notice, this list of conditions and the following disclaimer in the
11  *         documentation and/or other materials provided with the distribution.
12  *       * Neither the name of Texas Instruments Incorporated nor the
13  *         names of its contributors may be used to endorse or promote products
14  *         derived from this software without specific prior written permission.
15  *
16  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  *   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26  *   THE POSSIBILITY OF SUCH DAMAGE.
27  *****************************************************************************/
28 #include <signal.h>
29 #include <iostream>
30 #include <iomanip>
31 #include <fstream>
32 #include <cassert>
33 #include <string>
34 #include <functional>
35 #include <algorithm>
36 #include <time.h>
37 #include <unistd.h>
39 #include <queue>
40 #include <vector>
41 #include <cstdio>
42 #include <chrono>
44 #include "executor.h"
45 #include "execution_object.h"
46 #include "execution_object_pipeline.h"
47 #include "configuration.h"
48 #include "../common/object_classes.h"
49 #include "../common/utils.h"
50 #include "../common/video_utils.h"
52 using namespace std;
53 using namespace tidl;
54 using namespace cv;
57 #define NUM_VIDEO_FRAMES  100
58 #define DEFAULT_CONFIG    "jdetnet_voc"
59 #define DEFAULT_INPUT     "../test/testvecs/input/horse_768x320.y"
60 #define DEFAULT_INPUT_FRAMES (1)
61 #define DEFAULT_OBJECT_CLASSES_LIST_FILE "./jdetnet_voc_objects.json"
62 #define DEFAULT_OUTPUT_PROB_THRESHOLD  25
64 std::unique_ptr<ObjectClasses> object_classes;
65 uint32_t orig_width;
66 uint32_t orig_height;
67 uint32_t num_frames_file;
70 bool RunConfiguration(const cmdline_opts_t& opts);
71 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c,
72                          int layers_group_id);
73 bool ReadFrame(ExecutionObjectPipeline& eop, uint32_t frame_idx,
74                const Configuration& c, const cmdline_opts_t& opts,
75                VideoCapture &cap, ifstream &ifs);
76 bool WriteFrameOutput(const ExecutionObjectPipeline& eop,
77                       const Configuration& c, const cmdline_opts_t& opts);
78 static void DisplayHelp();
81 int main(int argc, char *argv[])
82 {
83     // Catch ctrl-c to ensure a clean exit
84     signal(SIGABRT, exit);
85     signal(SIGTERM, exit);
87     // If there are no devices capable of offloading TIDL on the SoC, exit
88     uint32_t num_eves = Executor::GetNumDevices(DeviceType::EVE);
89     uint32_t num_dsps = Executor::GetNumDevices(DeviceType::DSP);
90     if (num_eves == 0 || num_dsps == 0)
91     {
92         cout << "ssd_multibox requires both EVE and DSP for execution." << endl;
93         return EXIT_SUCCESS;
94     }
96     // Process arguments
97     cmdline_opts_t opts;
98     opts.config = DEFAULT_CONFIG;
99     opts.object_classes_list_file = DEFAULT_OBJECT_CLASSES_LIST_FILE;
100     opts.num_eves = 1;
101     opts.num_dsps = 1;
102     opts.input_file = DEFAULT_INPUT;
103     opts.output_prob_threshold = DEFAULT_OUTPUT_PROB_THRESHOLD;
104     if (! ProcessArgs(argc, argv, opts))
105     {
106         DisplayHelp();
107         exit(EXIT_SUCCESS);
108     }
109     assert(opts.num_dsps != 0 && opts.num_eves != 0);
110     if (opts.num_frames == 0)
111         opts.num_frames = (opts.is_camera_input || opts.is_video_input) ?
112                           NUM_VIDEO_FRAMES :
113                           ((opts.input_file == DEFAULT_INPUT) ?
114                            DEFAULT_INPUT_FRAMES : 1);
115     cout << "Input: " << opts.input_file << endl;
117     // Get object classes list
118     object_classes = std::unique_ptr<ObjectClasses>(
119                              new ObjectClasses(opts.object_classes_list_file));
120     if (object_classes->GetNumClasses() == 0)
121     {
122         cout << "No object classes defined for this config." << endl;
123         return EXIT_FAILURE;
124     }
126     // Run network
127     bool status = RunConfiguration(opts);
128     if (!status)
129     {
130         cout << "ssd_multibox FAILED" << endl;
131         return EXIT_FAILURE;
132     }
134     cout << "ssd_multibox PASSED" << endl;
135     return EXIT_SUCCESS;
138 bool RunConfiguration(const cmdline_opts_t& opts)
140     // Read the TI DL configuration file
141     Configuration c;
142     std::string config_file = "../test/testvecs/config/infer/tidl_config_"
143                               + opts.config + ".txt";
144     bool status = c.ReadFromFile(config_file);
145     if (!status)
146     {
147         cerr << "Error in configuration file: " << config_file << endl;
148         return false;
149     }
150     c.enableApiTrace = opts.verbose;
152     // setup camera/video input
153     VideoCapture cap;
154     if (! SetVideoInputOutput(cap, opts, "SSD_Multibox"))  return false;
156     // setup preprocessed input
157     ifstream ifs;
158     if (opts.is_preprocessed_input)
159     {
160         ifs.open(opts.input_file, ios::binary | ios::ate);
161         if (! ifs.good())
162         {
163             cerr << "Cannot open " << opts.input_file << endl;
164             return false;
165         }
166         num_frames_file = ((int) ifs.tellg()) /
167                           (c.inWidth * c.inHeight * c.inNumChannels);
168     }
170     try
171     {
172         // Create Executors with the approriate core type, number of cores
173         // and configuration specified
174         // EVE will run layersGroupId 1 in the network, while
175         // DSP will run layersGroupId 2 in the network
176         Executor* e_eve = CreateExecutor(DeviceType::EVE, opts.num_eves, c, 1);
177         Executor* e_dsp = CreateExecutor(DeviceType::DSP, opts.num_dsps, c, 2);
179         // Construct ExecutionObjectPipeline that utilizes multiple
180         // ExecutionObjects to process a single frame, each ExecutionObject
181         // processes one layerGroup of the network
182         //
183         // Pipeline depth can enable more optimized pipeline execution:
184         // Given one EVE and one DSP as an example, with different
185         //     pipeline_depth, we have different execution behavior:
186         // If pipeline_depth is set to 1,
187         //    we create one EOP: eop0 (eve0, dsp0)
188         //    pipeline execution of multiple frames over time is as follows:
189         //    --------------------- time ------------------->
190         //    eop0: [eve0...][dsp0]
191         //    eop0:                [eve0...][dsp0]
192         //    eop0:                               [eve0...][dsp0]
193         //    eop0:                                              [eve0...][dsp0]
194         // If pipeline_depth is set to 2,
195         //    we create two EOPs: eop0 (eve0, dsp0), eop1(eve0, dsp0)
196         //    pipeline execution of multiple frames over time is as follows:
197         //    --------------------- time ------------------->
198         //    eop0: [eve0...][dsp0]
199         //    eop1:          [eve0...][dsp0]
200         //    eop0:                   [eve0...][dsp0]
201         //    eop1:                            [eve0...][dsp0]
202         // Additional benefit of setting pipeline_depth to 2 is that
203         //    it can also overlap host ReadFrame() with device processing:
204         //    --------------------- time ------------------->
205         //    eop0: [RF][eve0...][dsp0]
206         //    eop1:     [RF]     [eve0...][dsp0]
207         //    eop0:                    [RF][eve0...][dsp0]
208         //    eop1:                             [RF][eve0...][dsp0]
209         vector<ExecutionObjectPipeline *> eops;
210         uint32_t pipeline_depth = 2;  // 2 EOs in EOP -> depth 2
211         for (uint32_t j = 0; j < pipeline_depth; j++)
212             for (uint32_t i = 0; i < max(opts.num_eves, opts.num_dsps); i++)
213                 eops.push_back(new ExecutionObjectPipeline(
214                       {(*e_eve)[i%opts.num_eves], (*e_dsp)[i%opts.num_dsps]}));
215         uint32_t num_eops = eops.size();
217         // Allocate input/output memory for each EOP
218         AllocateMemory(eops);
220         chrono::time_point<chrono::steady_clock> tloop0, tloop1;
221         tloop0 = chrono::steady_clock::now();
223         // Process frames with available eops in a pipelined manner
224         // additional num_eops iterations to flush pipeline (epilogue)
225         for (uint32_t frame_idx = 0;
226              frame_idx < opts.num_frames + num_eops; frame_idx++)
227         {
228             ExecutionObjectPipeline* eop = eops[frame_idx % num_eops];
230             // Wait for previous frame on the same eop to finish processing
231             if (eop->ProcessFrameWait())
232             {
233                 WriteFrameOutput(*eop, c, opts);
234             }
236             // Read a frame and start processing it with current eo
237             if (ReadFrame(*eop, frame_idx, c, opts, cap, ifs))
238                 eop->ProcessFrameStartAsync();
239         }
241         tloop1 = chrono::steady_clock::now();
242         chrono::duration<float> elapsed = tloop1 - tloop0;
243         cout << "Loop total time (including read/write/opencv/print/etc): "
244                   << setw(6) << setprecision(4)
245                   << (elapsed.count() * 1000) << "ms" << endl;
247         FreeMemory(eops);
248         for (auto eop : eops)  delete eop;
249         delete e_eve;
250         delete e_dsp;
251     }
252     catch (tidl::Exception &e)
253     {
254         cerr << e.what() << endl;
255         status = false;
256     }
258     return status;
261 // Create an Executor with the specified type and number of EOs
262 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c,
263                          int layers_group_id)
265     if (num == 0) return nullptr;
267     DeviceIds ids;
268     for (uint32_t i = 0; i < num; i++)
269         ids.insert(static_cast<DeviceId>(i));
271     return new Executor(dt, ids, c, layers_group_id);
274 bool ReadFrame(ExecutionObjectPipeline& eop, uint32_t frame_idx,
275                const Configuration& c, const cmdline_opts_t& opts,
276                VideoCapture &cap, ifstream &ifs)
278     if ((uint32_t)frame_idx >= opts.num_frames)
279         return false;
281     eop.SetFrameIndex(frame_idx);
283     char*  frame_buffer = eop.GetInputBufferPtr();
284     assert (frame_buffer != nullptr);
285     int channel_size = c.inWidth * c.inHeight;
286     int frame_size   = channel_size * c.inNumChannels;
288     Mat image;
289     if (!opts.is_camera_input && !opts.is_video_input)
290     {
291         if (opts.is_preprocessed_input)
292         {
293             orig_width  = c.inWidth;
294             orig_height = c.inHeight;
295             ifs.seekg((frame_idx % num_frames_file) * frame_size);
296             ifs.read(frame_buffer, frame_size);
297             return ifs.good();
298         }
299         else
300         {
301             image = cv::imread(opts.input_file, CV_LOAD_IMAGE_COLOR);
302             if (image.empty())
303             {
304                 cerr << "Unable to read from: " << opts.input_file << endl;
305                 return false;
306             }
307         }
308     }
309     else
310     {
311         // 640x480 camera input, process one in every 5 frames,
312         // can adjust number of skipped frames to match real time processing
313         if (! cap.grab())  return false;
314         if (! cap.grab())  return false;
315         if (! cap.grab())  return false;
316         if (! cap.grab())  return false;
317         if (! cap.grab())  return false;
318         if (! cap.retrieve(image)) return false;
319     }
321     // scale to network input size
322     Mat s_image, bgr_frames[3];
323     orig_width  = image.cols;
324     orig_height = image.rows;
325     cv::resize(image, s_image, Size(c.inWidth, c.inHeight),
326                0, 0, cv::INTER_AREA);
327     cv::split(s_image, bgr_frames);
328     memcpy(frame_buffer,                bgr_frames[0].ptr(), channel_size);
329     memcpy(frame_buffer+1*channel_size, bgr_frames[1].ptr(), channel_size);
330     memcpy(frame_buffer+2*channel_size, bgr_frames[2].ptr(), channel_size);
331     return true;
334 // Create frame with boxes drawn around classified objects
335 bool WriteFrameOutput(const ExecutionObjectPipeline& eop,
336                       const Configuration& c, const cmdline_opts_t& opts)
338     // Asseembly original frame
339     int width  = c.inWidth;
340     int height = c.inHeight;
341     int channel_size = width * height;
342     Mat frame, r_frame, bgr[3];
344     unsigned char *in = (unsigned char *) eop.GetInputBufferPtr();
345     bgr[0] = Mat(height, width, CV_8UC(1), in);
346     bgr[1] = Mat(height, width, CV_8UC(1), in + channel_size);
347     bgr[2] = Mat(height, width, CV_8UC(1), in + channel_size*2);
348     cv::merge(bgr, 3, frame);
350     int frame_index = eop.GetFrameIndex();
351     char outfile_name[64];
352     if (opts.is_preprocessed_input)
353     {
354         snprintf(outfile_name, 64, "frame_%d.png", frame_index);
355         cv::imwrite(outfile_name, frame);
356         printf("Saving frame %d to: %s\n", frame_index, outfile_name);
357     }
359     // Draw boxes around classified objects
360     float *out = (float *) eop.GetOutputBufferPtr();
361     int num_floats = eop.GetOutputBufferSizeInBytes() / sizeof(float);
362     for (int i = 0; i < num_floats / 7; i++)
363     {
364         int index = (int)    out[i * 7 + 0];
365         if (index < 0)  break;
367         float score =        out[i * 7 + 2];
368         if (score * 100 < opts.output_prob_threshold)  continue;
370         int   label = (int)  out[i * 7 + 1];
371         int   xmin  = (int) (out[i * 7 + 3] * width);
372         int   ymin  = (int) (out[i * 7 + 4] * height);
373         int   xmax  = (int) (out[i * 7 + 5] * width);
374         int   ymax  = (int) (out[i * 7 + 6] * height);
376         const ObjectClass& object_class = object_classes->At(label);
378 #if 0
379         printf("%2d: (%d, %d) -> (%d, %d): %s, score=%f\n",
380                i, xmin, ymin, xmax, ymax, object_class.label.c_str(), score);
381 #endif
383         if (xmin < 0)       xmin = 0;
384         if (ymin < 0)       ymin = 0;
385         if (xmax > width)   xmax = width;
386         if (ymax > height)  ymax = height;
387         cv::rectangle(frame, Point(xmin, ymin), Point(xmax, ymax),
388                       Scalar(object_class.color.blue,
389                              object_class.color.green,
390                              object_class.color.red), 2);
391     }
393     // Resize to output width/height, keep aspect ratio
394     uint32_t output_width = opts.output_width;
395     if (output_width == 0)  output_width = orig_width;
396     uint32_t output_height = (output_width*1.0f) / orig_width * orig_height;
397     cv::resize(frame, r_frame, Size(output_width, output_height));
399     if (opts.is_camera_input || opts.is_video_input)
400     {
401         cv::imshow("SSD_Multibox", r_frame);
402         waitKey(1);
403     }
404     else
405     {
406         snprintf(outfile_name, 64, "multibox_%d.png", frame_index);
407         cv::imwrite(outfile_name, r_frame);
408         printf("Saving frame %d with SSD multiboxes to: %s\n",
409                frame_index, outfile_name);
410     }
412     return true;
415 void DisplayHelp()
417     std::cout <<
418     "Usage: ssd_multibox\n"
419     "  Will run partitioned ssd_multibox network to perform "
420     "multi-objects detection\n"
421     "  and classification.  First part of network "
422     "(layersGroupId 1) runs on EVE,\n"
423     "  second part (layersGroupId 2) runs on DSP.\n"
424     "  Use -c to run a different segmentation network.  Default is jdetnet_voc.\n"
425     "Optional arguments:\n"
426     " -c <config>          Valid configs: jdetnet_voc, jdetnet \n"
427     " -d <number>          Number of dsp cores to use\n"
428     " -e <number>          Number of eve cores to use\n"
429     " -i <image>           Path to the image file as input\n"
430     "                      Default are 9 frames in testvecs\n"
431     " -i camera<number>    Use camera as input\n"
432     "                      video input port: /dev/video<number>\n"
433     " -i <name>.{mp4,mov,avi}  Use video file as input\n"
434     " -l <objects_list>    Path to the object classes list file\n"
435     " -f <number>          Number of frames to process\n"
436     " -w <number>          Output image/video width\n"
437     " -p <number>          Output probability threshold in percentage\n"
438     "                      Default is 25 percent or higher\n"
439     " -v                   Verbose output during execution\n"
440     " -h                   Help\n";