937f4671fdb2bda3099d4c3e6874bf8853d4296c
[tidl/tidl-api.git] / examples / imagenet / 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 <chrono>
43 #include "executor.h"
44 #include "execution_object.h"
45 #include "execution_object_pipeline.h"
46 #include "configuration.h"
47 #include "../common/object_classes.h"
48 #include "imgutil.h"
49 #include "../common/video_utils.h"
51 #include "opencv2/core.hpp"
52 #include "opencv2/imgproc.hpp"
53 #include "opencv2/highgui.hpp"
54 #include "opencv2/videoio.hpp"
56 using namespace std;
57 using namespace tidl;
58 using namespace cv;
60 #define NUM_VIDEO_FRAMES  300
61 #define DEFAULT_CONFIG    "j11_v2"
62 #define NUM_DEFAULT_INPUTS  1
63 #define DEFAULT_OBJECT_CLASSES_LIST_FILE "imagenet_objects.json"
64 #define DEFAULT_OUTPUT_PROB_THRESHOLD  5
65 const char *default_inputs[NUM_DEFAULT_INPUTS] =
66 {
67     "../test/testvecs/input/objects/cat-pet-animal-domestic-104827.jpeg"
68 };
69 std::unique_ptr<ObjectClasses> object_classes;
72 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c);
73 bool RunConfiguration(cmdline_opts_t& opts);
74 bool ReadFrame(ExecutionObjectPipeline& eop,
75                uint32_t frame_idx, const Configuration& c,
76                const cmdline_opts_t& opts, VideoCapture &cap);
77 bool WriteFrameOutput(const ExecutionObjectPipeline &eop,
78                       const cmdline_opts_t& opts);
79 void DisplayHelp();
82 int main(int argc, char *argv[])
83 {
84     // Catch ctrl-c to ensure a clean exit
85     signal(SIGABRT, exit);
86     signal(SIGTERM, exit);
88     // If there are no devices capable of offloading TIDL on the SoC, exit
89     uint32_t num_eves = Executor::GetNumDevices(DeviceType::EVE);
90     uint32_t num_dsps = Executor::GetNumDevices(DeviceType::DSP);
91     if (num_eves == 0 && num_dsps == 0)
92     {
93         cout << "TI DL not supported on this SoC." << endl;
94         return EXIT_SUCCESS;
95     }
97     // Process arguments
98     cmdline_opts_t opts;
99     opts.config = DEFAULT_CONFIG;
100     opts.object_classes_list_file = DEFAULT_OBJECT_CLASSES_LIST_FILE;
101     opts.output_prob_threshold = DEFAULT_OUTPUT_PROB_THRESHOLD;
102     if (num_eves != 0) { opts.num_eves = 1;  opts.num_dsps = 0; }
103     else               { opts.num_eves = 0;  opts.num_dsps = 1; }
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 : 1;
113     if (opts.input_file.empty())
114         cout << "Input: " << default_inputs[0] << endl;
115     else
116         cout << "Input: " << opts.input_file << endl;
118     // Get object classes list
119     object_classes = std::unique_ptr<ObjectClasses>(
120                              new ObjectClasses(opts.object_classes_list_file));
121     if (object_classes->GetNumClasses() == 0)
122     {
123         cout << "No object classes defined for this config." << endl;
124         return EXIT_FAILURE;
125     }
127     // Run network
128     bool status = RunConfiguration(opts);
129     if (!status)
130     {
131         cout << "imagenet FAILED" << endl;
132         return EXIT_FAILURE;
133     }
135     cout << "imagenet PASSED" << endl;
136     return EXIT_SUCCESS;
139 bool RunConfiguration(cmdline_opts_t& opts)
141     // Read the TI DL configuration file
142     Configuration c;
143     string config_file = "../test/testvecs/config/infer/tidl_config_"
144                               + opts.config + ".txt";
145     bool status = c.ReadFromFile(config_file);
146     if (!status)
147     {
148         cerr << "Error in configuration file: " << config_file << endl;
149         return false;
150     }
151     c.enableApiTrace = opts.verbose;
153     // setup camera/video input/output
154     VideoCapture cap;
155     if (! SetVideoInputOutput(cap, opts, "ImageNet"))  return false;
157     try
158     {
159         // Create Executors with the approriate core type, number of cores
160         // and configuration specified
161         Executor* e_eve = CreateExecutor(DeviceType::EVE, opts.num_eves, c);
162         Executor* e_dsp = CreateExecutor(DeviceType::DSP, opts.num_dsps, c);
164         // Get ExecutionObjects from Executors
165         vector<ExecutionObject*> eos;
166         for (uint32_t i = 0; i < opts.num_eves; i++) eos.push_back((*e_eve)[i]);
167         for (uint32_t i = 0; i < opts.num_dsps; i++) eos.push_back((*e_dsp)[i]);
168         uint32_t num_eos = eos.size();
170         // Use duplicate EOPs to do double buffering on frame input/output
171         //    because each EOP has its own set of input/output buffers,
172         //    so that host ReadFrame() can be overlapped with device processing
173         // Use one EO as an example, with different buffer_factor,
174         //    we have different execution behavior:
175         // If buffer_factor is set to 1 -> single buffering
176         //    we create one EOP: eop0 (eo0)
177         //    pipeline execution of multiple frames over time is as follows:
178         //    --------------------- time ------------------->
179         //    eop0: [RF][eo0.....][WF]
180         //    eop0:                   [RF][eo0.....][WF]
181         //    eop0:                                     [RF][eo0.....][WF]
182         // If buffer_factor is set to 2 -> double buffering
183         //    we create two EOPs: eop0 (eo0), eop1(eo0)
184         //    pipeline execution of multiple frames over time is as follows:
185         //    --------------------- time ------------------->
186         //    eop0: [RF][eo0.....][WF]
187         //    eop1:     [RF]      [eo0.....][WF]
188         //    eop0:                   [RF]  [eo0.....][WF]
189         //    eop1:                             [RF]  [eo0.....][WF]
190         vector<ExecutionObjectPipeline *> eops;
191         uint32_t buffer_factor = 2;  // set to 1 for single buffering
192         for (uint32_t j = 0; j < buffer_factor; j++)
193             for (uint32_t i = 0; i < num_eos; i++)
194                 eops.push_back(new ExecutionObjectPipeline({eos[i]}));
195         uint32_t num_eops = eops.size();
197         // Allocate input and output buffers for each EOP
198         AllocateMemory(eops);
200         chrono::time_point<chrono::steady_clock> tloop0, tloop1;
201         tloop0 = chrono::steady_clock::now();
203         // Process frames with available eops in a pipelined manner
204         // additional num_eos iterations to flush the pipeline (epilogue)
205         for (uint32_t frame_idx = 0;
206              frame_idx < opts.num_frames + num_eops; frame_idx++)
207         {
208             ExecutionObjectPipeline* eop = eops[frame_idx % num_eops];
210             // Wait for previous frame on the same eop to finish processing
211             if (eop->ProcessFrameWait())
212             {
213                 WriteFrameOutput(*eop, opts);
214             }
216             // Read a frame and start processing it with current eop
217             if (ReadFrame(*eop, frame_idx, c, opts, cap))
218                 eop->ProcessFrameStartAsync();
219         }
221         tloop1 = chrono::steady_clock::now();
222         chrono::duration<float> elapsed = tloop1 - tloop0;
223         cout << "Loop total time (including read/write/opencv/print/etc): "
224                   << setw(6) << setprecision(4)
225                   << (elapsed.count() * 1000) << "ms" << endl;
227         FreeMemory(eops);
228         for (auto eop : eops)  delete eop;
229         delete e_eve;
230         delete e_dsp;
231     }
232     catch (tidl::Exception &e)
233     {
234         cerr << e.what() << endl;
235         status = false;
236     }
238     return status;
241 // Create an Executor with the specified type and number of EOs
242 Executor* CreateExecutor(DeviceType dt, uint32_t num, const Configuration& c)
244     if (num == 0) return nullptr;
246     DeviceIds ids;
247     for (uint32_t i = 0; i < num; i++)
248         ids.insert(static_cast<DeviceId>(i));
250     return new Executor(dt, ids, c);
253 bool ReadFrame(ExecutionObjectPipeline &eop,
254                uint32_t frame_idx, const Configuration& c,
255                const cmdline_opts_t& opts, VideoCapture &cap)
257     if (frame_idx >= opts.num_frames)
258         return false;
260     eop.SetFrameIndex(frame_idx);
262     char*  frame_buffer = eop.GetInputBufferPtr();
263     assert (frame_buffer != nullptr);
265     Mat image;
266     if (! opts.is_camera_input && ! opts.is_video_input)
267     {
268         if (opts.input_file.empty())
269             image = cv::imread(default_inputs[frame_idx % NUM_DEFAULT_INPUTS],
270                                CV_LOAD_IMAGE_COLOR);
271         else
272             image = cv::imread(opts.input_file, CV_LOAD_IMAGE_COLOR);
273         if (image.empty())
274         {
275             cerr << "Unable to read input image" << endl;
276             return false;
277         }
278     }
279     else
280     {
281         Mat v_image;
282         if (! cap.grab())  return false;
283         if (! cap.retrieve(v_image)) return false;
284         int orig_width  = v_image.cols;
285         int orig_height = v_image.rows;
286         // Crop camera/video input to center 256x256 input
287         if (orig_width > 256 && orig_height > 256)
288         {
289             image = Mat(v_image, Rect((orig_width-256)/2, (orig_height-256)/2,
290                                        256, 256));
291         }
292         else
293             image = v_image;
294         cv::imshow("ImageNet", image);
295         waitKey(2);
296     }
298     // TI DL image preprocessing, into frame_buffer
299     return imgutil::PreprocessImage(image, frame_buffer, c);
302 // Display top 5 classified imagenet classes with probabilities 5% or higher
303 bool WriteFrameOutput(const ExecutionObjectPipeline &eop,
304                       const cmdline_opts_t& opts)
306     const int k = 5;
307     unsigned char *out = (unsigned char *) eop.GetOutputBufferPtr();
308     int out_size = eop.GetOutputBufferSizeInBytes();
310     // sort and get k largest values and corresponding indices
311     typedef pair<unsigned char, int> val_index;
312     auto constexpr cmp = [](val_index &left, val_index &right)
313                          { return left.first > right.first; };
314     priority_queue<val_index, vector<val_index>, decltype(cmp)> queue(cmp);
315     // initialize priority queue with smallest value on top
316     for (int i = 0; i < k; i++)
317         queue.push(val_index(out[i], i));
319     // for rest output, if larger than current min, pop min, push new val
320     for (int i = k; i < out_size; i++)
321     {
322         if (out[i] > queue.top().first)
323         {
324           queue.pop();
325           queue.push(val_index(out[i], i));
326         }
327     }
329     // output top k values in reverse order: largest val first
330     vector<val_index> sorted;
331     while (! queue.empty())
332     {
333       sorted.push_back(queue.top());
334       queue.pop();
335     }
337     unsigned int min_prob_255 = opts.output_prob_threshold * 255;
338     for (int i = k - 1; i >= 0; i--)
339     {
340         if (sorted[i].first * 100 < min_prob_255)  break;
341         cout << k-i << ": "
342              << object_classes->At(sorted[i].second).label
343              << ",   prob = " << setprecision(4)
344              << ((sorted[i].first * 100) / 255.0f) << "%" << endl;
345     }
347     return true;
350 void DisplayHelp()
352     cout <<
353     "Usage: imagenet\n"
354     "  Will run imagenet network to predict top 5 object"
355     " classes for the input.\n  Use -c to run a"
356     "  different imagenet network. Default is j11_v2.\n"
357     "Optional arguments:\n"
358     " -c <config>          Valid configs: j11_bn, j11_prelu, j11_v2\n"
359     " -d <number>          Number of dsp cores to use\n"
360     " -e <number>          Number of eve cores to use\n"
361     " -i <image>           Path to the image file as input\n"
362     " -i camera<number>    Use camera as input\n"
363     "                      video input port: /dev/video<number>\n"
364     " -i <name>.{mp4,mov,avi}  Use video file as input\n"
365     " -l <objects_list>    Path to the object classes list file\n"
366     " -f <number>          Number of frames to process\n"
367     " -p <number>          Output probablity threshold in percentage\n"
368     "                      Default is 5 percent or higher.\n"
369     " -v                   Verbose output during execution\n"
370     " -h                   Help\n";