Reduce complexity of ssd_multibox example
[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 <getopt.h>
30 #include <iostream>
31 #include <iomanip>
32 #include <fstream>
33 #include <cassert>
34 #include <string>
35 #include <functional>
36 #include <algorithm>
37 #include <time.h>
38 #include <unistd.h>
40 #include <queue>
41 #include <vector>
42 #include <cstdio>
44 #include "executor.h"
45 #include "execution_object.h"
46 #include "configuration.h"
47 #include "../segmentation/object_classes.h"
49 #include "opencv2/core.hpp"
50 #include "opencv2/imgproc.hpp"
51 #include "opencv2/highgui.hpp"
52 #include "opencv2/videoio.hpp"
54 #define NUM_VIDEO_FRAMES  100
55 #define DEFAULT_CONFIG    "jdetnet"
56 #define DEFAULT_INPUT     "../test/testvecs/input/preproc_0_768x320.y"
58 bool __TI_show_debug_ = false;
59 bool is_default_input = false;
60 bool is_preprocessed_input = false;
61 bool is_camera_input       = false;
62 int  orig_width;
63 int  orig_height;
64 object_class_table_t *object_class_table;
66 using namespace tinn;
67 using namespace cv;
70 bool RunConfiguration(const std::string& config_file, uint32_t num_devices,
71                       DeviceType device_type, std::string& input_file);
72 bool ReadFrame(ExecutionObject& eo, int frame_idx,
73                const Configuration& configuration, int num_frames,
74                std::string& image_file, VideoCapture &cap);
75 bool WriteFrameOutput(const ExecutionObject &eo_in,
76                       const ExecutionObject &eo_out,
77                       const Configuration& configuration);
79 void ReportTime(int frame_index, std::string device_name, double elapsed_host,
80                 double elapsed_device);
82 static void ProcessArgs(int argc, char *argv[],
83                         std::string& config,
84                         uint32_t& num_devices,
85                         DeviceType& device_type,
86                         std::string& input_file);
88 static void DisplayHelp();
90 static double ms_diff(struct timespec &t0, struct timespec &t1)
91 { return (t1.tv_sec - t0.tv_sec) * 1e3 + (t1.tv_nsec - t0.tv_nsec) / 1e6; }
94 int main(int argc, char *argv[])
95 {
96     // Catch ctrl-c to ensure a clean exit
97     signal(SIGABRT, exit);
98     signal(SIGTERM, exit);
100     // If there are no devices capable of offloading TIDL on the SoC, exit
101     uint32_t num_dla = Executor::GetNumDevices(DeviceType::DLA);
102     uint32_t num_dsp = Executor::GetNumDevices(DeviceType::DSP);
103     if (num_dla == 0 && num_dsp == 0)
104     {
105         std::cout << "TI DL not supported on this SoC." << std::endl;
106         return EXIT_SUCCESS;
107     }
109     // Process arguments
110     std::string config      = DEFAULT_CONFIG;
111     std::string input_file  = DEFAULT_INPUT;
112     uint32_t num_devices    = 1;
113     DeviceType  device_type = DeviceType::DLA;
114     ProcessArgs(argc, argv, config, num_devices, device_type, input_file);
116     // Use same number of DLAs and DSPs
117     num_devices = std::min(num_devices, std::min(num_dla, num_dsp));
118     if (num_devices == 0)
119     {
120         std::cout << "Partitioned execution requires at least 1 DLA and 1 DSP."
121                   << std::endl;
122         return EXIT_FAILURE;
123     }
124     if ((object_class_table = GetObjectClassTable(config)) == nullptr)
125     {
126         std::cout << "No object classes defined for this config." << std::endl;
127         return EXIT_FAILURE;
128     }
130     if (input_file == DEFAULT_INPUT)  is_default_input = true;
131     if (input_file == "camera")       is_camera_input = true;
132     if (input_file.length() > 2 &&
133         input_file.compare(input_file.length() - 2, 2, ".y") == 0)
134         is_preprocessed_input = true;
135     std::cout << "Input: " << input_file << std::endl;
136     std::string config_file = "../test/testvecs/config/infer/tidl_config_"
137                               + config + ".txt";
138     bool status = RunConfiguration(config_file, num_devices, device_type,
139                                    input_file);
141     if (!status)
142     {
143         std::cout << "ssd_multibox FAILED" << std::endl;
144         return EXIT_FAILURE;
145     }
147     std::cout << "ssd_multibox PASSED" << std::endl;
148     return EXIT_SUCCESS;
151 bool RunConfiguration(const std::string& config_file, uint32_t num_devices,
152                       DeviceType device_type, std::string& input_file)
154     DeviceIds ids;
155     for (int i = 0; i < num_devices; i++)
156         ids.insert(static_cast<DeviceId>(i));
158     // Read the TI DL configuration file
159     Configuration configuration;
160     bool status = configuration.ReadFromFile(config_file);
161     if (!status)
162     {
163         std::cerr << "Error in configuration file: " << config_file
164                   << std::endl;
165         return false;
166     }
168     // setup input
169     int num_frames = is_default_input ? 3 : 1;
170     VideoCapture cap;
171     std::string image_file;
172     if (is_camera_input)
173     {
174         cap = VideoCapture(1);  // cap = VideoCapture("test.mp4");
175         if (! cap.isOpened())
176         {
177             std::cerr << "Cannot open camera input." << std::endl;
178             return false;
179         }
180         num_frames = NUM_VIDEO_FRAMES;
181         namedWindow("SSD_Multibox", WINDOW_AUTOSIZE | CV_GUI_NORMAL);
182     }
183     else
184     {
185         image_file = input_file;
186     }
188     try
189     {
190         // Create a executor with the approriate core type, number of cores
191         // and configuration specified
192         // DLA will run layersGroupId 1 in the network, while
193         // DSP will run layersGroupId 2 in the network
194         Executor executor_dla(DeviceType::DLA, ids, configuration, 1);
195         Executor executor_dsp(DeviceType::DSP, ids, configuration, 2);
197         // Query Executor for set of ExecutionObjects created
198         const ExecutionObjects& execution_objects_dla =
199                                             executor_dla.GetExecutionObjects();
200         const ExecutionObjects& execution_objects_dsp =
201                                             executor_dsp.GetExecutionObjects();
202         int num_eos = execution_objects_dla.size();
204         // Allocate input and output buffers for each execution object
205         // Note that "out" is both the output of eo_dla and the input of eo_dsp
206         // This is how two layersGroupIds, 1 and 2, are tied together
207         std::vector<void *> buffers;
208         for (int i = 0; i < num_eos; i++)
209         {
210             ExecutionObject *eo_dla = execution_objects_dla[i].get();
211             size_t in_size  = eo_dla->GetInputBufferSizeInBytes();
212             size_t out_size = eo_dla->GetOutputBufferSizeInBytes();
213             ArgInfo in  = { ArgInfo(malloc(in_size),  in_size)  };
214             ArgInfo out = { ArgInfo(malloc(out_size), out_size) };
215             eo_dla->SetInputOutputBuffer(in, out);
217             ExecutionObject *eo_dsp = execution_objects_dsp[i].get();
218             size_t out2_size = eo_dsp->GetOutputBufferSizeInBytes();
219             ArgInfo out2 = { ArgInfo(malloc(out2_size), out2_size) };
220             eo_dsp->SetInputOutputBuffer(out, out2);
222             buffers.push_back(in.ptr());
223             buffers.push_back(out.ptr());
224             buffers.push_back(out2.ptr());
225         }
227         #define MAX_NUM_EOS  4
228         struct timespec t0[MAX_NUM_EOS], t1, tloop0, tloop1;
229         clock_gettime(CLOCK_MONOTONIC, &tloop0);
231         // Process frames with available execution objects in a pipelined manner
232         // additional num_eos iterations to flush the pipeline (epilogue)
233         ExecutionObject *eo_dla, *eo_dsp, *eo_input;
234         for (int frame_idx = 0;
235              frame_idx < num_frames + num_eos; frame_idx++)
236         {
237             eo_dla = execution_objects_dla[frame_idx % num_eos].get();
238             eo_dsp = execution_objects_dsp[frame_idx % num_eos].get();
240             // Wait for previous frame on the same eo to finish processing
241             if (eo_dsp->ProcessFrameWait())
242             {
243                 int finished_idx = eo_dsp->GetFrameIndex();
244                 clock_gettime(CLOCK_MONOTONIC, &t1);
245                 ReportTime(finished_idx, "DSP",
246                            ms_diff(t0[finished_idx % num_eos], t1),
247                            eo_dsp->GetProcessTimeInMilliSeconds());
249                 eo_input = execution_objects_dla[finished_idx % num_eos].get();
250                 WriteFrameOutput(*eo_input, *eo_dsp, configuration);
251             }
253             // Read a frame and start processing it with current eo
254             if (ReadFrame(*eo_dla, frame_idx, configuration, num_frames,
255                           image_file, cap))
256             {
257                 clock_gettime(CLOCK_MONOTONIC, &t0[frame_idx % num_eos]);
258                 eo_dla->ProcessFrameStartAsync();
260                 if (eo_dla->ProcessFrameWait())
261                 {
262                     clock_gettime(CLOCK_MONOTONIC, &t1);
263                     ReportTime(frame_idx, "DLA",
264                                ms_diff(t0[frame_idx % num_eos], t1),
265                                eo_dla->GetProcessTimeInMilliSeconds());
267                     clock_gettime(CLOCK_MONOTONIC, &t0[frame_idx % num_eos]);
268                     eo_dsp->ProcessFrameStartAsync();
269                 }
270             }
271         }
273         clock_gettime(CLOCK_MONOTONIC, &tloop1);
274         std::cout << "Loop total time (including read/write/print/etc): "
275                   << std::setw(6) << std::setprecision(4)
276                   << ms_diff(tloop0, tloop1) << "ms" << std::endl;
278         for (auto b : buffers)
279             free(b);
280     }
281     catch (tinn::Exception &e)
282     {
283         std::cerr << e.what() << std::endl;
284         status = false;
285     }
287     return status;
290 void ReportTime(int frame_index, std::string device_name, double elapsed_host,
291                 double elapsed_device)
293     double overhead = 100 - (elapsed_device/elapsed_host*100);
294     std::cout << "frame[" << frame_index << "]: "
295               << "Time on " << device_name << ": "
296               << std::setw(6) << std::setprecision(4)
297               << elapsed_device << "ms, "
298               << "host: "
299               << std::setw(6) << std::setprecision(4)
300               << elapsed_host << "ms ";
301     std::cout << "API overhead: "
302               << std::setw(6) << std::setprecision(3)
303               << overhead << " %" << std::endl;
307 bool ReadFrame(ExecutionObject &eo, int frame_idx,
308                const Configuration& configuration, int num_frames,
309                std::string& image_file, VideoCapture &cap)
311     if (frame_idx >= num_frames)
312         return false;
313     eo.SetFrameIndex(frame_idx);
315     char*  frame_buffer = eo.GetInputBufferPtr();
316     assert (frame_buffer != nullptr);
317     int channel_size = configuration.inWidth * configuration.inHeight;
319     Mat image;
320     if (! image_file.empty())
321     {
322         if (is_preprocessed_input)
323         {
324             std::ifstream ifs(image_file, std::ios::binary);
325             ifs.seekg(frame_idx * channel_size * 3);
326             ifs.read(frame_buffer, channel_size * 3);
327             bool ifs_status = ifs.good();
328             ifs.close();
329             orig_width  = configuration.inWidth;
330             orig_height = configuration.inHeight;
331             return ifs_status;  // already PreProc-ed
332         }
333         else
334         {
335             image = cv::imread(image_file, CV_LOAD_IMAGE_COLOR);
336             if (image.empty())
337             {
338                 std::cerr << "Unable to read from: " << image_file << std::endl;
339                 return false;
340             }
341         }
342     }
343     else
344     {
345         // 640x480 camera input, process one in every 5 frames,
346         // can adjust number of skipped frames to match real time processing
347         if (! cap.grab())  return false;
348         if (! cap.grab())  return false;
349         if (! cap.grab())  return false;
350         if (! cap.grab())  return false;
351         if (! cap.grab())  return false;
352         if (! cap.retrieve(image)) return false;
353     }
355     // scale to network input size
356     Mat s_image, bgr_frames[3];
357     orig_width  = image.cols;
358     orig_height = image.rows;
359     cv::resize(image, s_image,
360                Size(configuration.inWidth, configuration.inHeight),
361                0, 0, cv::INTER_AREA);
362     cv::split(s_image, bgr_frames);
363     memcpy(frame_buffer,                bgr_frames[0].ptr(), channel_size);
364     memcpy(frame_buffer+1*channel_size, bgr_frames[1].ptr(), channel_size);
365     memcpy(frame_buffer+2*channel_size, bgr_frames[2].ptr(), channel_size);
366     return true;
369 // Create frame with boxes drawn around classified objects
370 bool WriteFrameOutput(const ExecutionObject &eo_in,
371                       const ExecutionObject &eo_out,
372                       const Configuration& configuration)
374     // Asseembly original frame
375     int width  = configuration.inWidth;
376     int height = configuration.inHeight;
377     int channel_size = width * height;
378     Mat frame, r_frame, bgr[3];
380     unsigned char *in = (unsigned char *) eo_in.GetInputBufferPtr();
381     bgr[0] = Mat(height, width, CV_8UC(1), in);
382     bgr[1] = Mat(height, width, CV_8UC(1), in + channel_size);
383     bgr[2] = Mat(height, width, CV_8UC(1), in + channel_size*2);
384     cv::merge(bgr, 3, frame);
386     int frame_index = eo_in.GetFrameIndex();
387     char outfile_name[64];
388     if (! is_camera_input && is_preprocessed_input)
389     {
390         snprintf(outfile_name, 64, "frame_%d.png", frame_index);
391         cv::imwrite(outfile_name, frame);
392         printf("Saving frame %d to: %s\n", frame_index, outfile_name);
393     }
395     // Draw boxes around classified objects
396     float *out = (float *) eo_out.GetOutputBufferPtr();
397     int num_floats = eo_out.GetOutputBufferSizeInBytes() / sizeof(float);
398     for (int i = 0; i < num_floats / 7; i++)
399     {
400         int index = (int)    out[i * 7 + 0];
401         if (index < 0)  break;
403         int   label = (int)  out[i * 7 + 1];
404         float score =        out[i * 7 + 2];
405         int   xmin  = (int) (out[i * 7 + 3] * width);
406         int   ymin  = (int) (out[i * 7 + 4] * height);
407         int   xmax  = (int) (out[i * 7 + 5] * width);
408         int   ymax  = (int) (out[i * 7 + 6] * height);
410         object_class_t *object_class = GetObjectClass(object_class_table,
411                                                       label);
412         if (object_class == nullptr)  continue;
414 #if 0
415         printf("(%d, %d) -> (%d, %d): %s, score=%f\n",
416                xmin, ymin, xmax, ymax, object_class->label, score);
417 #endif
419         cv::rectangle(frame, Point(xmin, ymin), Point(xmax, ymax),
420                       Scalar(object_class->color.blue,
421                              object_class->color.green,
422                              object_class->color.red), 2);
423     }
425     // output
426     cv::resize(frame, r_frame, Size(orig_width, orig_height));
427     if (is_camera_input)
428     {
429         cv::imshow("SSD_Multibox", r_frame);
430         waitKey(1);
431     }
432     else
433     {
434         snprintf(outfile_name, 64, "multibox_%d.png", frame_index);
435         cv::imwrite(outfile_name, r_frame);
436         printf("Saving frame %d with SSD multiboxes to: %s\n",
437                frame_index, outfile_name);
438     }
440     return true;
444 void ProcessArgs(int argc, char *argv[], std::string& config,
445                  uint32_t& num_devices, DeviceType& device_type,
446                  std::string& input_file)
448     const struct option long_options[] =
449     {
450         {"config",      required_argument, 0, 'c'},
451         {"num_devices", required_argument, 0, 'n'},
452         {"image_file",  required_argument, 0, 'i'},
453         {"help",        no_argument,       0, 'h'},
454         {"verbose",     no_argument,       0, 'v'},
455         {0, 0, 0, 0}
456     };
458     int option_index = 0;
460     while (true)
461     {
462         int c = getopt_long(argc, argv, "c:n:i:hv", long_options, &option_index);
464         if (c == -1)
465             break;
467         switch (c)
468         {
469             case 'c': config = optarg;
470                       break;
472             case 'n': num_devices = atoi(optarg);
473                       assert (num_devices > 0 && num_devices <= 4);
474                       break;
476             case 'i': input_file = optarg;
477                       break;
479             case 'v': __TI_show_debug_ = true;
480                       break;
482             case 'h': DisplayHelp();
483                       exit(EXIT_SUCCESS);
484                       break;
486             case '?': // Error in getopt_long
487                       exit(EXIT_FAILURE);
488                       break;
490             default:
491                       std::cerr << "Unsupported option: " << c << std::endl;
492                       break;
493         }
494     }
497 void DisplayHelp()
499     std::cout << "Usage: ssd_multibox\n"
500                  "  Will run partitioned ssd_multibox network to perform "
501                  "multi-objects detection\n"
502                  "  and classification.  First part of network "
503                  "(layersGroupId 1) runs on DLA,\n"
504                  "  second part (layersGroupId 2) runs on DSP.\n"
505                  "  Use -c to run a different segmentation network. "
506                  "Default is jdetnet.\n"
507                  "Optional arguments:\n"
508                  " -c <config>          Valid configs: jdetnet \n"
509                  " -n <number of cores> Number of cores to use (1 - 4)\n"
510                  " -i <image>           Path to the image file\n"
511                  "                      Default is 1 frame in testvecs\n"
512                  " -i camera            Use camera as input\n"
513                  " -v                   Verbose output during execution\n"
514                  " -h                   Help\n";