]> Gitweb @ Texas Instruments - Open Source Git Repositories - git.TI.com/gitweb - tidl/tidl-api.git/blob - examples/ssd_multibox/main.cpp
PLSDK-2597
[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 /* Enable this macro to record individual output files and */
65 /* resized, cropped network input files                    */
66 #define DEBUG_FILES
68 std::unique_ptr<ObjectClasses> object_classes;
69 uint32_t orig_width;
70 uint32_t orig_height;
71 uint32_t num_frames_file;
74 bool RunConfiguration(const cmdline_opts_t& opts);
75 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c,
76                          int layers_group_id);
77 bool ReadFrame(ExecutionObjectPipeline& eop, uint32_t frame_idx,
78                const Configuration& c, const cmdline_opts_t& opts,
79                VideoCapture &cap, ifstream &ifs);
80 bool WriteFrameOutput(const ExecutionObjectPipeline& eop,
81                       const Configuration& c, const cmdline_opts_t& opts);
82 static void DisplayHelp();
84 /***************************************************************/
85 /* Slider to control detection confidence level                */
86 /***************************************************************/
87 int prob_slider = DEFAULT_OUTPUT_PROB_THRESHOLD;
88 int prob_slider_max = 100;
89 static void on_trackbar( int slider_id, void *inst )
90 {
91   //This function is invoked on every slider move. 
92   //No action required, since prob_slider is automatically updated.
93   //But, for any additional operation on slider move, this is the place to insert code.
94   //std::cout << "slider moved to:" << prob_slider << " max val is:" << prob_slider_max << endl;
95 }
98 int main(int argc, char *argv[])
99 {
100     // Catch ctrl-c to ensure a clean exit
101     signal(SIGABRT, exit);
102     signal(SIGTERM, exit);
104     // If there are no devices capable of offloading TIDL on the SoC, exit
105     uint32_t num_eves = Executor::GetNumDevices(DeviceType::EVE);
106     uint32_t num_dsps = Executor::GetNumDevices(DeviceType::DSP);
107     if (num_eves == 0 || num_dsps == 0)
108     {
109         cout << "ssd_multibox requires both EVE and DSP for execution." << endl;
110         return EXIT_SUCCESS;
111     }
113     // Process arguments
114     cmdline_opts_t opts;
115     opts.config = DEFAULT_CONFIG;
116     opts.object_classes_list_file = DEFAULT_OBJECT_CLASSES_LIST_FILE;
117     opts.num_eves = 1;
118     opts.num_dsps = 1;
119     opts.input_file = DEFAULT_INPUT;
120     opts.output_prob_threshold = DEFAULT_OUTPUT_PROB_THRESHOLD;
121     if (! ProcessArgs(argc, argv, opts))
122     {
123         DisplayHelp();
124         exit(EXIT_SUCCESS);
125     }
126     assert(opts.num_dsps != 0 && opts.num_eves != 0);
127     if (opts.num_frames == 0)
128         opts.num_frames = (opts.is_camera_input || opts.is_video_input) ?
129                           NUM_VIDEO_FRAMES :
130                           ((opts.input_file == DEFAULT_INPUT) ?
131                            DEFAULT_INPUT_FRAMES : 1);
132     cout << "Input: " << opts.input_file << endl;
134     // Get object classes list
135     object_classes = std::unique_ptr<ObjectClasses>(
136                              new ObjectClasses(opts.object_classes_list_file));
137     if (object_classes->GetNumClasses() == 0)
138     {
139         cout << "No object classes defined for this config." << endl;
140         return EXIT_FAILURE;
141     }
143     // Run network
144     bool status = RunConfiguration(opts);
145     if (!status)
146     {
147         cout << "ssd_multibox FAILED" << endl;
148         return EXIT_FAILURE;
149     }
151     cout << "ssd_multibox PASSED" << endl;
152     return EXIT_SUCCESS;
155 bool RunConfiguration(const cmdline_opts_t& opts)
157     // Read the TI DL configuration file
158     Configuration c;
159     std::string config_file = "../test/testvecs/config/infer/tidl_config_"
160                               + opts.config + ".txt";
161     bool status = c.ReadFromFile(config_file);
162     if (!status)
163     {
164         cerr << "Error in configuration file: " << config_file << endl;
165         return false;
166     }
167     c.enableApiTrace = opts.verbose;
168     // setup camera/video input
169     VideoCapture cap;
170     if (! SetVideoInputOutput(cap, opts, "SSD_Multibox"))  return false;
172     char TrackbarName[50];
173     prob_slider = (int)floor(opts.output_prob_threshold);
174     sprintf( TrackbarName, "Prob(%d %%)", prob_slider_max );
175     createTrackbar( TrackbarName, "SSD_Multibox", &prob_slider, prob_slider_max, on_trackbar );
177     // setup preprocessed input
178     ifstream ifs;
179     if (opts.is_preprocessed_input)
180     {
181         ifs.open(opts.input_file, ios::binary | ios::ate);
182         if (! ifs.good())
183         {
184             cerr << "Cannot open " << opts.input_file << endl;
185             return false;
186         }
187         num_frames_file = ((int) ifs.tellg()) /
188                           (c.inWidth * c.inHeight * c.inNumChannels);
189     }
191     try
192     {
193         // Create Executors with the approriate core type, number of cores
194         // and configuration specified
195         // EVE will run layersGroupId 1 in the network, while
196         // DSP will run layersGroupId 2 in the network
197         Executor* e_eve = CreateExecutor(DeviceType::EVE, opts.num_eves, c, 1);
198         Executor* e_dsp = CreateExecutor(DeviceType::DSP, opts.num_dsps, c, 2);
200         // Construct ExecutionObjectPipeline that utilizes multiple
201         // ExecutionObjects to process a single frame, each ExecutionObject
202         // processes one layerGroup of the network
203         //
204         // Pipeline depth can enable more optimized pipeline execution:
205         // Given one EVE and one DSP as an example, with different
206         //     pipeline_depth, we have different execution behavior:
207         // If pipeline_depth is set to 1,
208         //    we create one EOP: eop0 (eve0, dsp0)
209         //    pipeline execution of multiple frames over time is as follows:
210         //    --------------------- time ------------------->
211         //    eop0: [eve0...][dsp0]
212         //    eop0:                [eve0...][dsp0]
213         //    eop0:                               [eve0...][dsp0]
214         //    eop0:                                              [eve0...][dsp0]
215         // If pipeline_depth is set to 2,
216         //    we create two EOPs: eop0 (eve0, dsp0), eop1(eve0, dsp0)
217         //    pipeline execution of multiple frames over time is as follows:
218         //    --------------------- time ------------------->
219         //    eop0: [eve0...][dsp0]
220         //    eop1:          [eve0...][dsp0]
221         //    eop0:                   [eve0...][dsp0]
222         //    eop1:                            [eve0...][dsp0]
223         // Additional benefit of setting pipeline_depth to 2 is that
224         //    it can also overlap host ReadFrame() with device processing:
225         //    --------------------- time ------------------->
226         //    eop0: [RF][eve0...][dsp0]
227         //    eop1:     [RF]     [eve0...][dsp0]
228         //    eop0:                    [RF][eve0...][dsp0]
229         //    eop1:                             [RF][eve0...][dsp0]
230         vector<ExecutionObjectPipeline *> eops;
231         uint32_t pipeline_depth = 2;  // 2 EOs in EOP -> depth 2
232         for (uint32_t j = 0; j < pipeline_depth; j++)
233             for (uint32_t i = 0; i < max(opts.num_eves, opts.num_dsps); i++)
234                 eops.push_back(new ExecutionObjectPipeline(
235                       {(*e_eve)[i%opts.num_eves], (*e_dsp)[i%opts.num_dsps]}));
236         uint32_t num_eops = eops.size();
238         // Allocate input/output memory for each EOP
239         AllocateMemory(eops);
241         chrono::time_point<chrono::steady_clock> tloop0, tloop1;
242         tloop0 = chrono::steady_clock::now();
244         // Process frames with available eops in a pipelined manner
245         // additional num_eops iterations to flush pipeline (epilogue)
246         for (uint32_t frame_idx = 0;
247              frame_idx < opts.num_frames + num_eops; frame_idx++)
248         {
249             ExecutionObjectPipeline* eop = eops[frame_idx % num_eops];
251             // Wait for previous frame on the same eop to finish processing
252             if (eop->ProcessFrameWait())
253             {
254                 WriteFrameOutput(*eop, c, opts);
255             }
257             // Read a frame and start processing it with current eo
258             if (ReadFrame(*eop, frame_idx, c, opts, cap, ifs))
259                 eop->ProcessFrameStartAsync();
260         }
262         tloop1 = chrono::steady_clock::now();
263         chrono::duration<float> elapsed = tloop1 - tloop0;
264         cout << "Loop total time (including read/write/opencv/print/etc): "
265                   << setw(6) << setprecision(4)
266                   << (elapsed.count() * 1000) << "ms" << endl;
268         FreeMemory(eops);
269         for (auto eop : eops)  delete eop;
270         delete e_eve;
271         delete e_dsp;
272     }
273     catch (tidl::Exception &e)
274     {
275         cerr << e.what() << endl;
276         status = false;
277     }
279     return status;
282 // Create an Executor with the specified type and number of EOs
283 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c,
284                          int layers_group_id)
286     if (num == 0) return nullptr;
288     DeviceIds ids;
289     for (uint32_t i = 0; i < num; i++)
290         ids.insert(static_cast<DeviceId>(i));
292     return new Executor(dt, ids, c, layers_group_id);
295 bool ReadFrame(ExecutionObjectPipeline& eop, uint32_t frame_idx,
296                const Configuration& c, const cmdline_opts_t& opts,
297                VideoCapture &cap, ifstream &ifs)
299     if ((uint32_t)frame_idx >= opts.num_frames)
300         return false;
302     eop.SetFrameIndex(frame_idx);
304     char*  frame_buffer = eop.GetInputBufferPtr();
305     assert (frame_buffer != nullptr);
306     int channel_size = c.inWidth * c.inHeight;
307     int frame_size   = channel_size * c.inNumChannels;
309     Mat image;
310     if (!opts.is_camera_input && !opts.is_video_input)
311     {
312         if (opts.is_preprocessed_input)
313         {
314             orig_width  = c.inWidth;
315             orig_height = c.inHeight;
316             ifs.seekg((frame_idx % num_frames_file) * frame_size);
317             ifs.read(frame_buffer, frame_size);
318             return ifs.good();
319         }
320         else
321         {
322             image = cv::imread(opts.input_file, CV_LOAD_IMAGE_COLOR);
323             if (image.empty())
324             {
325                 cerr << "Unable to read from: " << opts.input_file << endl;
326                 return false;
327             }
328         }
329     }
330     else
331     {
332         if(opts.is_camera_input)
333         {
334            if (! cap.grab()) return false;
335            if (! cap.retrieve(image)) return false;
336         } 
337         else
338         { // Video clip
339            if (cap.grab()) 
340            {
341              if (! cap.retrieve(image)) return false;
342            } else {
343              //Rewind!
344              std::cout << "Video clip rewinded!" << std::endl;
345              cap.set(CAP_PROP_POS_FRAMES, 0);
346              if (! cap.grab()) return false;
347              if (! cap.retrieve(image)) return false;
348            }
349         }
350     }
352     // Scale to network input size:
353     // Preserve aspect ratio, by doing central cropping
354     // Choose vertical or horizontal central cropping based on dimension reduction
355     Mat s_image, bgr_frames[3];
356     orig_width  = image.cols;
357     orig_height = image.rows;
358     if(orig_width > orig_height)
359     {
360        float change_width  = (float)c.inWidth / (float)orig_width;
361        float change_height = (float)c.inHeight / (float)orig_height; 
362        if(change_width < change_height)
363        { // E.g. for 1920x1080->512x512, we first crop central part roi(420, 0, 1080, 1080), then resize to (512x512)
364          int offset_x = (int)round(0.5 * ((float)orig_width - ((float)orig_height * (float)c.inWidth / (float)c.inHeight)));
365          cv::resize(image(Rect(offset_x, 0, orig_width - 2 * offset_x, orig_height)), s_image, Size(c.inWidth, c.inHeight), 0, 0, cv::INTER_AREA);
366        } else {
367          // E.g. for 1920x1080->768x320, we first crop central part roi(0, 140, 1920, 800), then resize to (768x320)
368          int offset_y = (int)round(0.5 * ((float)orig_height - ((float)orig_width * (float)c.inHeight / (float)c.inWidth)));
369          cv::resize(image(Rect(0, offset_y, orig_width, orig_height - 2 * offset_y)), s_image, Size(c.inWidth, c.inHeight), 0, 0, cv::INTER_AREA);
370        }
371     }
373     #ifdef DEBUG_FILES
374     {
375       // Image files can be converted into video using, example script
376       // (on desktop Ubuntu, with ffmpeg installed):
377       // ffmpeg -i netin_%04d.png -vf "scale=(iw*sar)*max(768/(iw*sar)\,320/ih):ih*max(768/(iw*sar)\,320/ih), crop=768:320" -b:v 4000k out.mp4
378       // Update width 768, height 320, if necessary 
379       char netin_name[80];
380       sprintf(netin_name, "netin_%04d.png", frame_idx);
381       cv::imwrite(netin_name, s_image);
382       std::cout << "Video input, width:" << orig_width << " height:" << orig_height << " Network width:" << c.inWidth << " height:" << c.inHeight << std::endl;
383     }
384     #endif
386     cv::split(s_image, bgr_frames);
387     memcpy(frame_buffer,                bgr_frames[0].ptr(), channel_size);
388     memcpy(frame_buffer+1*channel_size, bgr_frames[1].ptr(), channel_size);
389     memcpy(frame_buffer+2*channel_size, bgr_frames[2].ptr(), channel_size);
390     return true;
393 // Create frame with boxes drawn around classified objects
394 bool WriteFrameOutput(const ExecutionObjectPipeline& eop,
395                       const Configuration& c, const cmdline_opts_t& opts)
397     // Asseembly original frame
398     int width  = c.inWidth;
399     int height = c.inHeight;
400     int channel_size = width * height;
401     Mat frame, r_frame, bgr[3];
403     unsigned char *in = (unsigned char *) eop.GetInputBufferPtr();
404     bgr[0] = Mat(height, width, CV_8UC(1), in);
405     bgr[1] = Mat(height, width, CV_8UC(1), in + channel_size);
406     bgr[2] = Mat(height, width, CV_8UC(1), in + channel_size*2);
407     cv::merge(bgr, 3, frame);
409     int frame_index = eop.GetFrameIndex();
410     char outfile_name[64];
411     if (opts.is_preprocessed_input)
412     {
413         snprintf(outfile_name, 64, "frame_%d.png", frame_index);
414         cv::imwrite(outfile_name, frame);
415         printf("Saving frame %d to: %s\n", frame_index, outfile_name);
416     }
418     // Draw boxes around classified objects
419     float *out = (float *) eop.GetOutputBufferPtr();
420     int num_floats = eop.GetOutputBufferSizeInBytes() / sizeof(float);
421     for (int i = 0; i < num_floats / 7; i++)
422     {
423         int index = (int)    out[i * 7 + 0];
424         if (index < 0)  break;
426         float score =        out[i * 7 + 2];
427         if (score * 100 < (float)prob_slider)  continue;
429         int   label = (int)  out[i * 7 + 1];
430         int   xmin  = (int) (out[i * 7 + 3] * width);
431         int   ymin  = (int) (out[i * 7 + 4] * height);
432         int   xmax  = (int) (out[i * 7 + 5] * width);
433         int   ymax  = (int) (out[i * 7 + 6] * height);
435         const ObjectClass& object_class = object_classes->At(label);
437         if(opts.verbose) {
438             printf("%2d: (%d, %d) -> (%d, %d): %s, score=%f\n",
439                i, xmin, ymin, xmax, ymax, object_class.label.c_str(), score);
440         }
442         if (xmin < 0)       xmin = 0;
443         if (ymin < 0)       ymin = 0;
444         if (xmax > width)   xmax = width;
445         if (ymax > height)  ymax = height;
446         cv::rectangle(frame, Point(xmin, ymin), Point(xmax, ymax),
447                       Scalar(object_class.color.blue,
448                              object_class.color.green,
449                              object_class.color.red), 2);
450     }
452     r_frame = frame;
453     if (opts.is_camera_input || opts.is_video_input)
454     {
455         cv::imshow("SSD_Multibox", r_frame);
456 #ifdef DEBUG_FILES
457         // Image files can be converted into video using, example script
458         // (on desktop Ubuntu, with ffmpeg installed):
459         // ffmpeg -i multibox_%04d.png -vf "scale=(iw*sar)*max(768/(iw*sar)\,320/ih):ih*max(768/(iw*sar)\,320/ih), crop=768:320" -b:v 4000k out.mp4
460         // Update width 768, height 320, if necessary 
461         snprintf(outfile_name, 64, "multibox_%04d.png", frame_index);
462         cv::imwrite(outfile_name, r_frame);
463 #endif
464         waitKey(1);
465     }
466     else
467     {
468         snprintf(outfile_name, 64, "multibox_%d.png", frame_index);
469         cv::imwrite(outfile_name, r_frame);
470         printf("Saving frame %d with SSD multiboxes to: %s\n",
471                frame_index, outfile_name);
472     }
474     return true;
477 void DisplayHelp()
479     std::cout <<
480     "Usage: ssd_multibox\n"
481     "  Will run partitioned ssd_multibox network to perform "
482     "multi-objects detection\n"
483     "  and classification.  First part of network "
484     "(layersGroupId 1) runs on EVE,\n"
485     "  second part (layersGroupId 2) runs on DSP.\n"
486     "  Use -c to run a different segmentation network.  Default is jdetnet_voc.\n"
487     "Optional arguments:\n"
488     " -c <config>          Valid configs: jdetnet_voc, jdetnet \n"
489     " -d <number>          Number of dsp cores to use\n"
490     " -e <number>          Number of eve cores to use\n"
491     " -i <image>           Path to the image file as input\n"
492     "                      Default are 9 frames in testvecs\n"
493     " -i camera<number>    Use camera as input\n"
494     "                      video input port: /dev/video<number>\n"
495     " -i <name>.{mp4,mov,avi}  Use video file as input\n"
496     " -l <objects_list>    Path to the object classes list file\n"
497     " -f <number>          Number of frames to process\n"
498     " -w <number>          Output image/video width\n"
499     " -p <number>          Output probability threshold in percentage\n"
500     "                      Default is 25 percent or higher\n"
501     " -v                   Verbose output during execution\n"
502     " -h                   Help\n";