#ifndef OPENPOSE_WRAPPER_WRAPPER_HAND_FROM_JSON_TEST_HPP #define OPENPOSE_WRAPPER_WRAPPER_HAND_FROM_JSON_TEST_HPP #include namespace op { template>>, typename TQueue = Queue>> class WrapperHandFromJsonTest { public: /** * Constructor. */ explicit WrapperHandFromJsonTest(); /** * Destructor. * It automatically frees resources. */ ~WrapperHandFromJsonTest(); void configure(const WrapperStructPose& wrapperStructPose, const WrapperStructHand& wrapperStructHand, const std::shared_ptr& producerSharedPtr, const std::string& handGroundTruth, const std::string& writeJson, const DisplayMode displayMode = DisplayMode::NoDisplay); /** * Function to start multi-threading. * Similar to start(), but exec() blocks the thread that calls the function (it saves 1 thread). Use exec() instead of * start() if the calling thread will otherwise be waiting for the WrapperHandFromJsonTest to end. */ void exec(); private: ThreadManager> mThreadManager; // Workers TWorker wDatumProducer; TWorker spWIdGenerator; TWorker spWScaleAndSizeExtractor; TWorker spWCvMatToOpInput; TWorker spWCvMatToOpOutput; std::vector> spWPoses; std::vector mPostProcessingWs; std::vector mOutputWs; TWorker spWGui; /** * Frees TWorker variables (private internal function). * For most cases, this class is non-necessary, since std::shared_ptr are automatically cleaned on destruction of each class. * However, it might be useful if the same WrapperHandFromJsonTest is gonna be started twice (not recommended on most cases). */ void reset(); /** * Set ThreadManager from TWorkers (private internal function). * After any configure() has been called, the TWorkers are initialized. This function resets the ThreadManager and adds them. * Common code for start() and exec(). */ void configureThreadManager(); /** * TWorker concatenator (private internal function). * Auxiliary function that concatenate std::vectors of TWorker. Since TWorker is some kind of smart pointer (usually * std::shared_ptr), its copy still shares the same internal data. It will not work for TWorker classes that do not share * the data when moved. * @param workersA First std::shared_ptr element to be concatenated. * @param workersB Second std::shared_ptr element to be concatenated. * @return Concatenated std::vector of both workersA and workersB. */ std::vector mergeWorkers(const std::vector& workersA, const std::vector& workersB); DELETE_COPY(WrapperHandFromJsonTest); }; } // Implementation #include #include #include #include #include #include #include #include #include #include namespace op { template WrapperHandFromJsonTest::WrapperHandFromJsonTest() { } template WrapperHandFromJsonTest::~WrapperHandFromJsonTest() { try { mThreadManager.stop(); reset(); } catch (const std::exception& e) { error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } template void WrapperHandFromJsonTest::configure(const WrapperStructPose& wrapperStructPose, const WrapperStructHand& wrapperStructHand, const std::shared_ptr& producerSharedPtr, const std::string& handGroundTruth, const std::string& writeJson, const DisplayMode displayMode) { try { log("", Priority::Low, __LINE__, __FUNCTION__, __FILE__); // Shortcut typedef std::shared_ptr TDatumsPtr; // Check no wrong/contradictory flags enabled if (wrapperStructPose.scaleGap <= 0.f && wrapperStructPose.scalesNumber > 1) error("The scale gap must be greater than 0 (it has no effect if the number of scales is 1).", __LINE__, __FUNCTION__, __FILE__); const std::string additionalMessage = " You could also set mThreadManagerMode = mThreadManagerMode::Asynchronous(Out)" " and/or add your own output worker class before calling this function."; const auto savingSomething = !writeJson.empty(); const auto displayGui = (displayMode != DisplayMode::NoDisplay); if (!displayGui && !savingSomething) { const auto message = "No output is selected (`--display 0`) and no results are generated (no `write_X` flags enabled). Thus," " no output would be generated." + additionalMessage; error(message, __LINE__, __FUNCTION__, __FILE__); } // Get number GPUs auto gpuNumber = wrapperStructPose.gpuNumber; auto gpuNumberStart = wrapperStructPose.gpuNumberStart; // If number GPU < 0 --> set it to all the available GPUs if (gpuNumber < 0) { // Get total number GPUs gpuNumber = getGpuNumber(); // Reset initial GPU to 0 (we want them all) gpuNumberStart = 0; // Logging message log("Auto-detecting GPUs... Detected " + std::to_string(gpuNumber) + " GPU(s), using them all.", Priority::High); } // Proper format const auto writeJsonCleaned = formatAsDirectory(writeJson); // Common parameters const auto finalOutputSize = wrapperStructPose.outputSize; const Point producerSize{(int)producerSharedPtr->get(CV_CAP_PROP_FRAME_WIDTH), (int)producerSharedPtr->get(CV_CAP_PROP_FRAME_HEIGHT)}; if (finalOutputSize.x == -1 || finalOutputSize.y == -1) { const auto message = "Output resolution cannot be (-1 x -1) unless producerSharedPtr is also set."; error(message, __LINE__, __FUNCTION__, __FILE__); } // Producer const auto datumProducer = std::make_shared>(producerSharedPtr); wDatumProducer = std::make_shared>(datumProducer); // Get input scales and sizes const auto scaleAndSizeExtractor = std::make_shared( wrapperStructPose.netInputSize, finalOutputSize, wrapperStructPose.scalesNumber, wrapperStructPose.scaleGap ); spWScaleAndSizeExtractor = std::make_shared>(scaleAndSizeExtractor); // Input cvMat to OpenPose format const auto cvMatToOpInput = std::make_shared(wrapperStructPose.poseModel); spWCvMatToOpInput = std::make_shared>(cvMatToOpInput); if (displayGui) { const auto cvMatToOpOutput = std::make_shared(); spWCvMatToOpOutput = std::make_shared>(cvMatToOpOutput); } // Hand extractor(s) if (wrapperStructHand.enable) { spWPoses.resize(gpuNumber); const auto handDetector = std::make_shared(handGroundTruth); for (auto gpuId = 0u; gpuId < spWPoses.size(); gpuId++) { // Hand detector // If tracking if (wrapperStructHand.tracking) error("Tracking not valid for hand detector from JSON files.", __LINE__, __FUNCTION__, __FILE__); // If detection else spWPoses.at(gpuId) = {std::make_shared>(handDetector)}; // Hand keypoint extractor const auto netOutputSize = wrapperStructHand.netInputSize; const auto handExtractor = std::make_shared( wrapperStructHand.netInputSize, netOutputSize, wrapperStructPose.modelFolder, gpuId + gpuNumberStart, wrapperStructHand.scalesNumber, wrapperStructHand.scaleRange ); spWPoses.at(gpuId).emplace_back(std::make_shared>(handExtractor)); } } // Hand renderer(s) std::vector cpuRenderers; if (displayGui) { // Construct hand renderer const auto handRenderer = std::make_shared(wrapperStructHand.renderThreshold, wrapperStructHand.alphaKeypoint, wrapperStructHand.alphaHeatMap); // Add worker cpuRenderers.emplace_back(std::make_shared>(handRenderer)); } // Itermediate workers (e.g., OpenPose format to cv::Mat, json & frames recorder, ...) mPostProcessingWs.clear(); // Frame buffer and ordering if (spWPoses.size() > 1) mPostProcessingWs.emplace_back(std::make_shared>()); // Frames processor (OpenPose format -> cv::Mat format) if (displayGui) { mPostProcessingWs = mergeWorkers(mPostProcessingWs, cpuRenderers); const auto opOutputToCvMat = std::make_shared(); mPostProcessingWs.emplace_back(std::make_shared>(opOutputToCvMat)); } // Re-scale pose if desired if (wrapperStructPose.keypointScale != ScaleMode::InputResolution) error("Only wrapperStructPose.keypointScale == ScaleMode::InputResolution.", __LINE__, __FUNCTION__, __FILE__); mOutputWs.clear(); // Write people pose data on disk (json format) if (!writeJsonCleaned.empty()) { const auto jsonSaver = std::make_shared(writeJsonCleaned); mOutputWs.emplace_back(std::make_shared>(jsonSaver)); } // Minimal graphical user interface (GUI) spWGui = nullptr; if (displayGui) { const auto guiInfoAdder = std::make_shared(gpuNumber, displayGui); mOutputWs.emplace_back(std::make_shared>(guiInfoAdder)); const auto gui = std::make_shared( finalOutputSize, false, mThreadManager.getIsRunningSharedPtr() ); spWGui = {std::make_shared>(gui)}; } log("", Priority::Low, __LINE__, __FUNCTION__, __FILE__); } catch (const std::exception& e) { error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } template void WrapperHandFromJsonTest::exec() { try { configureThreadManager(); mThreadManager.exec(); } catch (const std::exception& e) { error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } template void WrapperHandFromJsonTest::reset() { try { mThreadManager.reset(); // Reset wDatumProducer = nullptr; spWScaleAndSizeExtractor = nullptr; spWCvMatToOpInput = nullptr; spWCvMatToOpOutput = nullptr; spWPoses.clear(); mPostProcessingWs.clear(); mOutputWs.clear(); spWGui = nullptr; } catch (const std::exception& e) { error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } template void WrapperHandFromJsonTest::configureThreadManager() { try { // Sanity checks if (spWCvMatToOpInput == nullptr) error("Configure the WrapperHandFromJsonTest class before calling `start()`.", __LINE__, __FUNCTION__, __FILE__); if (wDatumProducer == nullptr) { const auto message = "You need to use the OpenPose default producer."; error(message, __LINE__, __FUNCTION__, __FILE__); } if (mOutputWs.empty() && spWGui == nullptr) { error("No output selected.", __LINE__, __FUNCTION__, __FILE__); } // Thread Manager: // Clean previous thread manager (avoid configure to crash the program if used more than once) mThreadManager.reset(); auto threadId = 0ull; auto queueIn = 0ull; auto queueOut = 1ull; // If custom user Worker in same thread or producer on same thread spWIdGenerator = std::make_shared>>(); // OpenPose producer // Thread 0 or 1, queues 0 -> 1 if (spWCvMatToOpOutput == nullptr) mThreadManager.add(threadId++, {wDatumProducer, spWIdGenerator, spWScaleAndSizeExtractor, spWCvMatToOpInput}, queueIn++, queueOut++); else mThreadManager.add(threadId++, {wDatumProducer, spWIdGenerator, spWScaleAndSizeExtractor, spWCvMatToOpInput, spWCvMatToOpOutput}, queueIn++, queueOut++); // Pose estimation & rendering // Thread 1 or 2...X, queues 1 -> 2, X = 2 + #GPUs if (!spWPoses.empty()) { for (auto& wPose : spWPoses) mThreadManager.add(threadId++, wPose, queueIn, queueOut); queueIn++; queueOut++; } // If custom user Worker in same thread or producer on same thread // Post processing workers + User post processing workers + Output workers // Thread 2 or 3, queues 2 -> 3 mThreadManager.add(threadId++, mergeWorkers(mPostProcessingWs, mOutputWs), queueIn++, queueOut++); // OpenPose GUI // Thread Y+1, queues Q+1 -> Q+2 if (spWGui != nullptr) mThreadManager.add(threadId++, spWGui, queueIn++, queueOut++); log("", Priority::Low, __LINE__, __FUNCTION__, __FILE__); } catch (const std::exception& e) { error(e.what(), __LINE__, __FUNCTION__, __FILE__); } } template std::vector WrapperHandFromJsonTest::mergeWorkers(const std::vector& workersA, const std::vector& workersB) { try { auto workersToReturn(workersA); for (auto& worker : workersB) workersToReturn.emplace_back(worker); return workersToReturn; } catch (const std::exception& e) { error(e.what(), __LINE__, __FUNCTION__, __FILE__); return std::vector{}; } } } #endif // OPENPOSE_WRAPPER_WRAPPER_HAND_FROM_JSON_TEST_HPP