// ------------------------- OpenPose Library Tutorial - Thread - Example 4 - User Input Processing And Output ------------------------- // This fourth example shows the user how to: // 1. Read folder of images / video / webcam (`producer` module) // 2. Use the processing implemented by the user // 3. Display the rendered pose (`gui` module) // Everything in a multi-thread scenario (`thread` module) // In addition to the previous OpenPose modules, we also need to use: // 1. `core` module: for the Datum struct that the `thread` module sends between the queues // 2. `utilities` module: for the error & logging functions, i.e. op::error & op::log respectively // 3rdparty dependencies #include // DEFINE_bool, DEFINE_int32, DEFINE_int64, DEFINE_uint64, DEFINE_double, DEFINE_string #include // google::InitGoogleLogging // OpenPose dependencies // Option a) Importing all modules #include // Option b) Manually importing the desired modules. Recommended if you only intend to use a few modules. // #include // #include // #include // #include // #include // #include // #include // See all the available parameter options withe the `--help` flag. E.g. `./build/examples/openpose/openpose.bin --help`. // Note: This command will show you flags for other unnecessary 3rdparty files. Check only the flags for the OpenPose // executable. E.g. for `openpose.bin`, look for `Flags from examples/openpose/openpose.cpp:`. // Debugging DEFINE_int32(logging_level, 3, "The logging level. Integer in the range [0, 255]. 0 will output any log() message, while" " 255 will not output any. Current OpenPose library messages are in the range 0-4: 1 for" " low priority messages and 4 for important ones."); // Producer DEFINE_string(image_dir, "examples/media/", "Process a directory of images. Read all standard formats (jpg, png, bmp, etc.)."); // Consumer DEFINE_bool(fullscreen, false, "Run in full-screen mode (press f during runtime to toggle)."); // If the user needs his own variables, he can inherit the op::Datum struct and add them // UserDatum can be directly used by the OpenPose wrapper because it inherits from op::Datum, just define Wrapper instead of // Wrapper struct UserDatum : public op::Datum { bool boolThatUserNeedsForSomeReason; UserDatum(const bool boolThatUserNeedsForSomeReason_ = false) : boolThatUserNeedsForSomeReason{boolThatUserNeedsForSomeReason_} {} }; // The W-classes can be implemented either as a template or as simple classes given // that the user usually knows which kind of data he will move between the queues, // in this case we assume a std::shared_ptr of a std::vector of UserDatum // This worker will just read and return all the jpg files in a directory class WUserInput : public op::WorkerProducer>> { public: WUserInput(const std::string& directoryPath) : mImageFiles{op::getFilesOnDirectory(directoryPath, "jpg")}, // mImageFiles{op::getFilesOnDirectory(directoryPath, std::vector{"jpg", "png"})}, // If we want "jpg" + "png" images mCounter{0} { if (mImageFiles.empty()) op::error("No images found on: " + directoryPath, __LINE__, __FUNCTION__, __FILE__); } void initializationOnThread() {} std::shared_ptr> workProducer() { try { // Close program when empty frame if (mImageFiles.size() <= mCounter) { op::log("Last frame read and added to queue. Closing program after it is processed.", op::Priority::High); // This funtion stops this worker, which will eventually stop the whole thread system once all the frames have been processed this->stop(); return nullptr; } else { // Create new datum auto datumsPtr = std::make_shared>(); datumsPtr->emplace_back(); auto& datum = datumsPtr->at(0); // Fill datum datum.cvInputData = cv::imread(mImageFiles.at(mCounter++)); // If empty frame -> return nullptr if (datum.cvInputData.empty()) { op::log("Empty frame detected on path: " + mImageFiles.at(mCounter-1) + ". Closing program.", op::Priority::High); this->stop(); datumsPtr = nullptr; } return datumsPtr; } } catch (const std::exception& e) { op::log("Some kind of unexpected error happened."); this->stop(); op::error(e.what(), __LINE__, __FUNCTION__, __FILE__); return nullptr; } } private: const std::vector mImageFiles; unsigned long long mCounter; }; // This worker will just invert the image class WUserPostProcessing : public op::Worker>> { public: WUserPostProcessing() { // User's constructor here } void initializationOnThread() {} void work(std::shared_ptr>& datumsPtr) { // User's post-processing (after OpenPose processing & before OpenPose outputs) here // datum.cvOutputData: rendered frame with pose or heatmaps // datum.poseKeypoints: Array with the estimated pose try { if (datumsPtr != nullptr && !datumsPtr->empty()) for (auto& datum : *datumsPtr) cv::bitwise_not(datum.cvInputData, datum.cvOutputData); } catch (const std::exception& e) { op::log("Some kind of unexpected error happened."); this->stop(); op::error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } }; // This worker will just read and return all the jpg files in a directory class WUserOutput : public op::WorkerConsumer>> { public: void initializationOnThread() {} void workConsumer(const std::shared_ptr>& datumsPtr) { try { // User's displaying/saving/other processing here // datum.cvOutputData: rendered frame with pose or heatmaps // datum.poseKeypoints: Array with the estimated pose if (datumsPtr != nullptr && !datumsPtr->empty()) { cv::imshow("User worker GUI", datumsPtr->at(0).cvOutputData); cv::waitKey(1); // It displays the image and sleeps at least 1 ms (it usually sleeps ~5-10 msec to display the image) } } catch (const std::exception& e) { op::log("Some kind of unexpected error happened."); this->stop(); op::error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } }; int openPoseTutorialThread4() { op::log("OpenPose Library Tutorial - Example 3.", op::Priority::High); // ------------------------- INITIALIZATION ------------------------- // Step 1 - Set logging level // - 0 will output all the logging messages // - 255 will output nothing op::check(0 <= FLAGS_logging_level && FLAGS_logging_level <= 255, "Wrong logging_level value.", __LINE__, __FUNCTION__, __FILE__); op::ConfigureLog::setPriorityThreshold((op::Priority)FLAGS_logging_level); // Step 2 - Setting thread workers && manager typedef std::shared_ptr> TypedefDatums; typedef std::shared_ptr> TypedefWorker; op::ThreadManager threadManager; // Step 3 - Initializing the worker classes // Frames producer (e.g. video, webcam, ...) TypedefWorker wUserInput = std::make_shared(FLAGS_image_dir); // Processing TypedefWorker wUserProcessing = std::make_shared(); // GUI (Display) TypedefWorker wUserOutput = std::make_shared(); // ------------------------- CONFIGURING THREADING ------------------------- // In this simple multi-thread example, we will do the following: // 3 (virtual) queues: 0, 1, 2 // 1 real queue: 1. The first and last queue ids (in this case 0 and 2) are not actual queues, but the beginning and end of the processing // sequence // 2 threads: 0, 1 // wUserInput will generate frames (there is no real queue 0) and push them on queue 1 // wGui will pop frames from queue 1 and process them (there is no real queue 2) auto threadId = 0ull; auto queueIn = 0ull; auto queueOut = 1ull; threadManager.add(threadId++, wUserInput, queueIn++, queueOut++); // Thread 0, queues 0 -> 1 threadManager.add(threadId++, wUserProcessing, queueIn++, queueOut++); // Thread 1, queues 1 -> 2 threadManager.add(threadId++, wUserOutput, queueIn++, queueOut++); // Thread 2, queues 2 -> 3 // ------------------------- STARTING AND STOPPING THREADING ------------------------- op::log("Starting thread(s)", op::Priority::High); // Two different ways of running the program on multithread environment // Option a) Using the main thread (this thread) for processing (it saves 1 thread, recommended) threadManager.exec(); // It blocks this thread until all threads have finished // Option b) Giving to the user the control of this thread // // VERY IMPORTANT NOTE: if OpenCV is compiled with Qt support, this option will not work. Qt needs the main thread to // // plot visual results, so the final GUI (which uses OpenCV) would return an exception similar to: // // `QMetaMethod::invoke: Unable to invoke methods with return values in queued connections` // // Start threads // threadManager.start(); // // Keep program alive while running threads. Here the user could perform any other desired function // while (threadManager.isRunning()) // std::this_thread::sleep_for(std::chrono::milliseconds{33}); // // Stop and join threads // op::log("Stopping thread(s)", op::Priority::High); // threadManager.stop(); // ------------------------- CLOSING ------------------------- // Logging information message op::log("Example 4 successfully finished.", op::Priority::High); // Return successful message return 0; } int main(int argc, char *argv[]) { // Initializing google logging (Caffe uses it for logging) google::InitGoogleLogging("openPoseTutorialThread4"); // Parsing command line flags gflags::ParseCommandLineFlags(&argc, &argv, true); // Running openPoseTutorialThread4 return openPoseTutorialThread4(); }