073a6efa0198596d8e03c488ed594090ae5dd7ec
[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 "../segmentation/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"
59 #define DEFAULT_INPUT     "../test/testvecs/input/preproc_0_768x320.y"
60 #define DEFAULT_INPUT_FRAMES (1)
62 object_class_table_t *object_class_table;
63 uint32_t orig_width;
64 uint32_t orig_height;
67 bool RunConfiguration(const cmdline_opts_t& opts);
68 Executor* CreateExecutor(DeviceType dt, int num, const Configuration& c,
69                          int layers_group_id);
70 bool ReadFrame(ExecutionObjectPipeline& eop, int frame_idx,
71                const Configuration& c, const cmdline_opts_t& opts,
72                VideoCapture &cap);
73 bool WriteFrameOutput(const ExecutionObjectPipeline& eop,
74                       const Configuration& c, const cmdline_opts_t& opts);
75 static void DisplayHelp();
78 int main(int argc, char *argv[])
79 {
80     // Catch ctrl-c to ensure a clean exit
81     signal(SIGABRT, exit);
82     signal(SIGTERM, exit);
84     // If there are no devices capable of offloading TIDL on the SoC, exit
85     uint32_t num_eves = Executor::GetNumDevices(DeviceType::EVE);
86     uint32_t num_dsps = Executor::GetNumDevices(DeviceType::DSP);
87     if (num_eves == 0 || num_dsps == 0)
88     {
89         cout << "ssd_multibox requires both EVE and DSP for execution." << endl;
90         return EXIT_SUCCESS;
91     }
93     // Process arguments
94     cmdline_opts_t opts;
95     opts.config = DEFAULT_CONFIG;
96     opts.num_eves = 1;
97     opts.num_dsps = 1;
98     if (! ProcessArgs(argc, argv, opts))
99     {
100         DisplayHelp();
101         exit(EXIT_SUCCESS);
102     }
103     assert(opts.num_dsps != 0 && opts.num_eves != 0);
104     if (opts.num_frames == 0)
105         opts.num_frames = (opts.is_camera_input || opts.is_video_input) ?
106                           NUM_VIDEO_FRAMES :
107                           (opts.input_file.empty() ? DEFAULT_INPUT_FRAMES : 1);
108     if (opts.input_file.empty())
109         cout << "Input: " << DEFAULT_INPUT << endl;
110     else
111         cout << "Input: " << opts.input_file << endl;
113     // Get object class table
114     if ((object_class_table = GetObjectClassTable(opts.config)) == nullptr)
115     {
116         cout << "No object classes defined for this config." << endl;
117         return EXIT_FAILURE;
118     }
120     // Run network
121     bool status = RunConfiguration(opts);
122     if (!status)
123     {
124         cout << "ssd_multibox FAILED" << endl;
125         return EXIT_FAILURE;
126     }
128     cout << "ssd_multibox PASSED" << endl;
129     return EXIT_SUCCESS;
132 bool RunConfiguration(const cmdline_opts_t& opts)
134     // Read the TI DL configuration file
135     Configuration c;
136     std::string config_file = "../test/testvecs/config/infer/tidl_config_"
137                               + opts.config + ".txt";
138     bool status = c.ReadFromFile(config_file);
139     if (!status)
140     {
141         cerr << "Error in configuration file: " << config_file << endl;
142         return false;
143     }
144     c.enableApiTrace = opts.verbose;
146     // setup camera/video input
147     VideoCapture cap;
148     if (! SetVideoInputOutput(cap, opts, "SSD_Multibox"))  return false;
150     try
151     {
152         // Create Executors with the approriate core type, number of cores
153         // and configuration specified
154         // EVE will run layersGroupId 1 in the network, while
155         // DSP will run layersGroupId 2 in the network
156         Executor* e_eve = CreateExecutor(DeviceType::EVE, opts.num_eves, c, 1);
157         Executor* e_dsp = CreateExecutor(DeviceType::DSP, opts.num_dsps, c, 2);
159         // Construct ExecutionObjectPipeline that utilizes multiple
160         // ExecutionObjects to process a single frame, each ExecutionObject
161         // processes one layerGroup of the network
162         vector<ExecutionObjectPipeline *> eops;
163         for (uint32_t i = 0; i < max(opts.num_eves, opts.num_dsps); i++)
164             eops.push_back(new ExecutionObjectPipeline(
165                       {(*e_eve)[i%opts.num_eves], (*e_dsp)[i%opts.num_dsps]}));
166         uint32_t num_eops = eops.size();
168         // Allocate input/output memory for each EOP
169         AllocateMemory(eops);
171         chrono::time_point<chrono::steady_clock> tloop0, tloop1;
172         tloop0 = chrono::steady_clock::now();
174         // Process frames with available eops in a pipelined manner
175         // additional num_eops iterations to flush pipeline (epilogue)
176         for (uint32_t frame_idx = 0;
177              frame_idx < opts.num_frames + num_eops; frame_idx++)
178         {
179             ExecutionObjectPipeline* eop = eops[frame_idx % num_eops];
181             // Wait for previous frame on the same eop to finish processing
182             if (eop->ProcessFrameWait())
183             {
184                 ReportTime(eop);
185                 WriteFrameOutput(*eop, c, opts);
186             }
188             // Read a frame and start processing it with current eo
189             if (ReadFrame(*eop, frame_idx, c, opts, cap))
190                 eop->ProcessFrameStartAsync();
191         }
193         tloop1 = chrono::steady_clock::now();
194         chrono::duration<float> elapsed = tloop1 - tloop0;
195         cout << "Loop total time (including read/write/opencv/print/etc): "
196                   << setw(6) << setprecision(4)
197                   << (elapsed.count() * 1000) << "ms" << endl;
199         FreeMemory(eops);
200         for (auto eop : eops)  delete eop;
201         delete e_eve;
202         delete e_dsp;
203     }
204     catch (tidl::Exception &e)
205     {
206         cerr << e.what() << endl;
207         status = false;
208     }
210     return status;
213 // Create an Executor with the specified type and number of EOs
214 Executor* CreateExecutor(DeviceType dt, int num, const Configuration& c,
215                          int layers_group_id)
217     if (num == 0) return nullptr;
219     DeviceIds ids;
220     for (uint32_t i = 0; i < num; i++)
221         ids.insert(static_cast<DeviceId>(i));
223     return new Executor(dt, ids, c, layers_group_id);
226 bool ReadFrame(ExecutionObjectPipeline& eop, int frame_idx,
227                const Configuration& c, const cmdline_opts_t& opts,
228                VideoCapture &cap)
230     if (frame_idx >= opts.num_frames)
231         return false;
232     eop.SetFrameIndex(frame_idx);
234     char*  frame_buffer = eop.GetInputBufferPtr();
235     assert (frame_buffer != nullptr);
236     int channel_size = c.inWidth * c.inHeight;
238     Mat image;
239     if (!opts.is_camera_input && !opts.is_video_input)
240     {
241         if (opts.input_file.empty())
242         {
243             ifstream ifs(DEFAULT_INPUT, ios::binary);
244             ifs.seekg((frame_idx % DEFAULT_INPUT_FRAMES) * channel_size * 3);
245             ifs.read(frame_buffer, channel_size * 3);
246             bool ifs_status = ifs.good();
247             ifs.close();
248             orig_width  = c.inWidth;
249             orig_height = c.inHeight;
250             return ifs_status;  // already PreProc-ed
251         }
252         else
253         {
254             image = cv::imread(opts.input_file, CV_LOAD_IMAGE_COLOR);
255             if (image.empty())
256             {
257                 cerr << "Unable to read from: " << opts.input_file << endl;
258                 return false;
259             }
260         }
261     }
262     else
263     {
264         // 640x480 camera input, process one in every 5 frames,
265         // can adjust number of skipped frames to match real time processing
266         if (! cap.grab())  return false;
267         if (! cap.grab())  return false;
268         if (! cap.grab())  return false;
269         if (! cap.grab())  return false;
270         if (! cap.grab())  return false;
271         if (! cap.retrieve(image)) return false;
272     }
274     // scale to network input size
275     Mat s_image, bgr_frames[3];
276     orig_width  = image.cols;
277     orig_height = image.rows;
278     cv::resize(image, s_image, Size(c.inWidth, c.inHeight),
279                0, 0, cv::INTER_AREA);
280     cv::split(s_image, bgr_frames);
281     memcpy(frame_buffer,                bgr_frames[0].ptr(), channel_size);
282     memcpy(frame_buffer+1*channel_size, bgr_frames[1].ptr(), channel_size);
283     memcpy(frame_buffer+2*channel_size, bgr_frames[2].ptr(), channel_size);
284     return true;
287 // Create frame with boxes drawn around classified objects
288 bool WriteFrameOutput(const ExecutionObjectPipeline& eop,
289                       const Configuration& c, const cmdline_opts_t& opts)
291     // Asseembly original frame
292     int width  = c.inWidth;
293     int height = c.inHeight;
294     int channel_size = width * height;
295     Mat frame, r_frame, bgr[3];
297     unsigned char *in = (unsigned char *) eop.GetInputBufferPtr();
298     bgr[0] = Mat(height, width, CV_8UC(1), in);
299     bgr[1] = Mat(height, width, CV_8UC(1), in + channel_size);
300     bgr[2] = Mat(height, width, CV_8UC(1), in + channel_size*2);
301     cv::merge(bgr, 3, frame);
303     int frame_index = eop.GetFrameIndex();
304     char outfile_name[64];
305     if (opts.input_file.empty())
306     {
307         snprintf(outfile_name, 64, "frame_%d.png", frame_index);
308         cv::imwrite(outfile_name, frame);
309         printf("Saving frame %d to: %s\n", frame_index, outfile_name);
310     }
312     // Draw boxes around classified objects
313     float *out = (float *) eop.GetOutputBufferPtr();
314     int num_floats = eop.GetOutputBufferSizeInBytes() / sizeof(float);
315     for (int i = 0; i < num_floats / 7; i++)
316     {
317         int index = (int)    out[i * 7 + 0];
318         if (index < 0)  break;
320         int   label = (int)  out[i * 7 + 1];
321         int   xmin  = (int) (out[i * 7 + 3] * width);
322         int   ymin  = (int) (out[i * 7 + 4] * height);
323         int   xmax  = (int) (out[i * 7 + 5] * width);
324         int   ymax  = (int) (out[i * 7 + 6] * height);
326         object_class_t *object_class = GetObjectClass(object_class_table,
327                                                       label);
328         if (object_class == nullptr)  continue;
330 #if 0
331         printf("(%d, %d) -> (%d, %d): %s, score=%f\n",
332                xmin, ymin, xmax, ymax, object_class->label, score);
333 #endif
335         cv::rectangle(frame, Point(xmin, ymin), Point(xmax, ymax),
336                       Scalar(object_class->color.blue,
337                              object_class->color.green,
338                              object_class->color.red), 2);
339     }
341     // Resize to output width/height, keep aspect ratio
342     uint32_t output_width = opts.output_width;
343     if (output_width == 0)  output_width = orig_width;
344     uint32_t output_height = (output_width*1.0f) / orig_width * orig_height;
345     cv::resize(frame, r_frame, Size(output_width, output_height));
347     if (opts.is_camera_input || opts.is_video_input)
348     {
349         cv::imshow("SSD_Multibox", r_frame);
350         waitKey(1);
351     }
352     else
353     {
354         snprintf(outfile_name, 64, "multibox_%d.png", frame_index);
355         cv::imwrite(outfile_name, r_frame);
356         printf("Saving frame %d with SSD multiboxes to: %s\n",
357                frame_index, outfile_name);
358     }
360     return true;
363 void DisplayHelp()
365     std::cout <<
366     "Usage: ssd_multibox\n"
367     "  Will run partitioned ssd_multibox network to perform "
368     "multi-objects detection\n"
369     "  and classification.  First part of network "
370     "(layersGroupId 1) runs on EVE,\n"
371     "  second part (layersGroupId 2) runs on DSP.\n"
372     "  Use -c to run a different segmentation network.  Default is jdetnet.\n"
373     "Optional arguments:\n"
374     " -c <config>          Valid configs: jdetnet \n"
375     " -d <number>          Number of dsp cores to use\n"
376     " -e <number>          Number of eve cores to use\n"
377     " -i <image>           Path to the image file as input\n"
378     "                      Default are 9 frames in testvecs\n"
379     " -i camera<number>    Use camera as input\n"
380     "                      video input port: /dev/video<number>\n"
381     " -i <name>.{mp4,mov,avi}  Use video file as input\n"
382     " -f <number>          Number of frames to process\n"
383     " -w <number>          Output image/video width\n"
384     " -v                   Verbose output during execution\n"
385     " -h                   Help\n";