本博文由 youngpan1101 出品,转载请注明出处。
文章链接: http://blog.csdn.net/youngpan1101/article/details/72729735
作者: 宋洋鹏(youngpan1101)
邮箱: yangpeng_song@163.com
运行环境
- ubuntu 14.04.4 64bit(通过
$ lsb_release -a
命令进行查看) - gcc : 4.9.4 (通过
$ gcc --version
命令进行查看 ) - g++: 4.9.4 (通过
$ g++ --version
命令进行查看)
多线程流程框图
在主线程下有两个线程:跟踪 和 检测,该工程主要功能是通过检测机制检测出目标后,将目标位置发送给跟踪线程进行跟踪,当然跟踪过程中会一直进行目标的检测,以免出现跟踪过程中的跟丢现象。
CMake 工程代码 【BaiduYun: Code Download】
CMakeLists.txt 【Ubuntu 14.04 安装 OpenCV-3.2.0】
cmake_minimum_required(VERSION 2.8) project( MultiThread ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -pthread") set(CMAKE_VERBOSE_MAKEFILE off) # 寻找OpenCV库 # set(CMAKE_PREFIX_PATH "/home/hwj/3rdParty/OpenCV/v2.4.13/lib") # find_package( OpenCV 2.4.13 REQUIRED ) find_package( OpenCV 3.2 REQUIRED ) # 添加头文件 include_directories( ${OpenCV_INCLUDE_DIRS} ) include_directories( ${PROJECT_SOURCE_DIR} ) add_executable( MultiThreadDemo main.cpp ) add_library( ObjectTracker SHARED ObjectTracker.cpp ) add_library( ObjectDectector SHARED ObjectDectector.cpp ) add_library( RobotTracker SHARED RobotTracker.cpp ) # 链接OpenCV库 target_link_libraries( ObjectTracker ${OpenCV_LIBS} ) target_link_libraries( ObjectDectector ${OpenCV_LIBS} ) target_link_libraries( RobotTracker ObjectDectector ObjectTracker ${OpenCV_LIBS} ) target_link_libraries( MultiThreadDemo RobotTracker)
若 gcc 版本是 4.8.4 可能会报错
terminate called after throwing an instance of 'std::system_error' what(): Enable multithreading to use std::thread: Operation not permitted Aborted (core dumped)
这是 gcc 的一个 bug,对于 ld 自动加上了 as-needed 选项,可以修改 CMakeLists.txt :
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -Wall -std=c++11 -pthread")
另外一种解决方法就是升级 gcc 到版本 4.9.2,详细操作可以参考 【Ubuntu 14.04 中升级 gcc 到版本 4.9.2 并切换使用它们】
Main.cpp
通过读取视频流获取图像帧:
#include "RobotTracker.h" int main(int argc, char** argv) { std::string filePath = "../hanwuji_robot.avi"; RobotTracker m_Tracker(filePath); return 1; }
RobotTracker.h
#ifndef _ROBOT_TRACKER_H #define _ROBOT_TRACKER_H #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> #include <thread> // for std::thread #include <mutex> // for std::mutex #include <queue> // for std::queue #include <condition_variable> // for std::condition_variable #include <functional> // for std::bind #include "ObjectDectector.h" #include "ObjectTracker.h" typedef enum{ RAW_IMAGE = 1, IMAGE_AFTER_DETECTOR_WITH_TARGET_BOX, IMAGE_AFTER_DETECTOR_WITHOUT_TARGET_BOX, }eImageFlag; typedef struct{ cv::Mat _Image; cv::Rect _TargetBox; eImageFlag _ImageFlag; }stData; typedef enum{ TRACKING_PREPARE = 1, // preparation because of no given target bounding box to initial TRACKING_RUNNING, }eTrackerStatus; typedef enum{ TRACKER_REQUIRE_REINITIAL_TARGET_BOX = 1, DETECTOR_FINISH_TARGET_BOX, }eThreadsStatus; class RobotTracker{ public: RobotTracker(const std::string &VideoPath); ~RobotTracker(); private: void ObjectTrackerThread(const std::string &VideoPath); void ObjectDetectionThread(); bool isDataQueueEmpty(); bool isDataQueueNotEmpty(); void pushDataToQueue(const stData &Data); bool popDataFromQueue(stData &Data); eThreadsStatus getThreadsStatus(); void setThreadsStatus(eThreadsStatus status); bool getExitTrackerStatus(); void setExitTrackerStatus(bool status); std::string m_ImgWinName; std::mutex m_Mutex; std::mutex m_Mutex4Queue; std::mutex m_Mutex4ThreadsStatus; std::mutex m_Mutex4ExitTracker; std::queue<stData> m_DataQueue; // 用于线程间通信的队列 std::condition_variable m_ConVar; eThreadsStatus m_ThreadsStatus; bool m_isExitTracker; std::thread m_TrackerThread; std::thread m_DetectorThread; }; #endif
- 类成员变量定义的顺序,mutex 和 condition_variable 必须在 thread 的前面:
如果线程的定义在前面,线程初始化完成之后立马会执行线程函数,而线程函数里用到了 mutex 和condition_variable ,此时如果 mutex 和 condition_variable 还没初始化完成,就会出现内存错误。
- 类成员变量定义的顺序,mutex 和 condition_variable 必须在 thread 的前面:
RobotTracker.cpp
#include "RobotTracker.h" RobotTracker::RobotTracker(const std::string &VideoPath) { std::cout << "RobotTracker construction" << std::endl; // ps_01 m_TrackerThread = std::thread( std::bind(&RobotTracker::ObjectTrackerThread, this, VideoPath) ); m_DetectorThread = std::thread( std::bind(&RobotTracker::ObjectDetectionThread, this) ); } RobotTracker::~RobotTracker() { m_TrackerThread.join(); //ps_02 m_DetectorThread.join(); std::cout << "RobotTracker deconstruction" << std::endl; } void RobotTracker::ObjectTrackerThread(const std::string &VideoPath) { m_ImgWinName = (std::string)"RobotTracker"; setExitTrackerStatus(false); setThreadsStatus(TRACKER_REQUIRE_REINITIAL_TARGET_BOX); cv::namedWindow(m_ImgWinName, 1); cv::Mat matTmp; ObjectTracker m_tracker(VideoPath); m_tracker.openCamera(); if(m_tracker.getNextFrameFromOrbbecCamera(matTmp)) { std::cout << "ObjectTrackerThread: The first frame " << std::endl; //ps_03 数据准备好后,使用 unique_lock 来锁定信号量,将数据插入队列之中 std::unique_lock<std::mutex> lockTmp(m_Mutex); stData data; matTmp.copyTo(data._Image); data._TargetBox = cv::Rect(0,0,0,0); data._ImageFlag = RAW_IMAGE; pushDataToQueue(data); //ps_05 // 通过条件变量通知其它等待的线程 std::cout << "ObjectTrackerThread: notify_all" << std::endl; m_ConVar.notify_all(); //ps_04 lockTmp.unlock(); } eTrackerStatus TrackStatus = TRACKING_PREPARE; stData stDataAfterDetection; while(1) { if(m_tracker.getNextFrameFromOrbbecCamera(matTmp)) { if(getThreadsStatus()==DETECTOR_FINISH_TARGET_BOX && popDataFromQueue(stDataAfterDetection)) { m_tracker.initTracking(stDataAfterDetection._TargetBox); TrackStatus = TRACKING_RUNNING; stData stDataToPush; matTmp.copyTo(stDataToPush._Image); stDataToPush._TargetBox = cv::Rect(0,0,0,0); stDataToPush._ImageFlag = RAW_IMAGE; pushDataToQueue(stDataToPush); setThreadsStatus(TRACKER_REQUIRE_REINITIAL_TARGET_BOX); } if(TrackStatus == TRACKING_RUNNING) { cv::rectangle(matTmp, m_tracker.updateTargetBoundingBox(), cv::Scalar(0, 0, 255), 2, 1); // update ds-kcf tracking algorithm std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 休眠, unit:ms } cv::imshow(m_ImgWinName, matTmp); int key = cv::waitKey(10); if(key == 27) { setExitTrackerStatus(true); break; } } else { setExitTrackerStatus(true); break; } } } void RobotTracker::ObjectDetectionThread() { std::cout << "ObjectDetectionThread:begin" << std::endl; // 使用 unique_lock 来锁定信号量 std::unique_lock<std::mutex> lockTmp_1(m_Mutex); std::cout << "ObjectDetectionThread: before wait" << std::endl; // ps_03 等待条件满足,unique_lock 和 std::function object,判断数据队列是否为空 #if 1 // method 1 while(isDataQueueEmpty()) m_ConVar.wait(lockTmp_1); #endif #if 0 // method 2 m_ConVar.wait(lockTmp_1,std::bind(&RobotTracker::isDataQueueNotEmpty,this)); #endif #if 0 // method 3 if(isDataQueueEmpty()) m_ConVar.wait(lockTmp_1); #endif std::cout << "ObjectDetectionThread: pass wait" << std::endl; // 解锁 mutex lockTmp_1.unlock(); // cv::namedWindow 也会因为不同线程同时共享而报错,这里需要错开时间,或者使用 std::mutex std::string strWinName = "DetectorView"; cv::namedWindow(strWinName, 1); cv::moveWindow(strWinName, 710, 0); bool isStartDetection = false; ObjectDectector m_detector; while(1) { stData data; if(getExitTrackerStatus()) break; if(getThreadsStatus()==TRACKER_REQUIRE_REINITIAL_TARGET_BOX && popDataFromQueue(data)) { isStartDetection = true; } if(isStartDetection) { cv::Mat matTmp; data._Image.copyTo(matTmp); data._TargetBox = m_detector.getPersonDetectionBoundingBox(data._Image); cv::rectangle(matTmp, data._TargetBox, cv::Scalar(0, 0, 255), 2, 1); cv::imshow(strWinName, matTmp); std::this_thread::sleep_for(std::chrono::milliseconds(400)); // 休眠, unit:ms data._ImageFlag = IMAGE_AFTER_DETECTOR_WITH_TARGET_BOX; pushDataToQueue(data); isStartDetection = false; setThreadsStatus(DETECTOR_FINISH_TARGET_BOX); } } } bool RobotTracker::isDataQueueEmpty() { std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue); std::cout << "RobotTracker::isDataQueueEmpty " << std::endl; return m_DataQueue.empty(); } bool RobotTracker::isDataQueueNotEmpty() { std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue); std::cout << "RobotTracker::isDataQueueNotEmpty " << std::endl; return !m_DataQueue.empty(); } void RobotTracker::pushDataToQueue(const stData &Data) { // ps_06 使用 lock_guard 来锁定信号量,将数据插入队列之中 std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue); std::cout << " pushDataToQueue " << std::endl; m_DataQueue.push(Data); } bool RobotTracker::popDataFromQueue(stData &Data) { // 使用 lock_guard 来锁定信号量,将数据从队列中取出 std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue); std::cout << " popDataFromQueue " << std::endl; if(m_DataQueue.size()) { Data = m_DataQueue.front(); m_DataQueue.pop(); return true; } else return false; } eThreadsStatus RobotTracker::getThreadsStatus() { std::lock_guard<std::mutex> lockTmp(m_Mutex4ThreadsStatus); return m_ThreadsStatus; } void RobotTracker::setThreadsStatus(eThreadsStatus status) { std::lock_guard<std::mutex> lockTmp(m_Mutex4ThreadsStatus); m_ThreadsStatus = status; } bool RobotTracker::getExitTrackerStatus() { std::lock_guard<std::mutex> lockTmp(m_Mutex4ExitTracker); return m_isExitTracker; } void RobotTracker::setExitTrackerStatus(bool status) { std::lock_guard<std::mutex> lockTmp(m_Mutex4ExitTracker); m_isExitTracker = status; }
- ps_01:【使用std::function和std::bind实现局部函数做回调】【C++11 学习笔记 std::function和bind绑定器】
(1)std::bind 用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用 std::function 进行保存,并延迟调用到任何我们需要的时候。
(2)【thread 的四种构造方式】 - ps_02:【[C++11 并发编程] 02 - join】
(1)join() 函数等待线程完成,若不等待线程完成,我们就需要确保该线程访问的数据都是有效的,直到该线程完成为止。若线程函数持有局部变量的指针或引用,当函数退出时,线程尚未执行完成。
(2)对一个给定的线程,只能调用一次 join(),一旦调用了 join(),此 std::thread 对象不再是可连接的,如果调用其的 joinable() 将返回 false。 - ps_03:【[C++11 并发编程] 11 - 线程间同步 - 等待一个消息或某种条件】
(1)有些时候,我们需要在线程之间进行同步操作。一个线程等待另一个线程完成某项工作后,再继续自己的工作。使用C++标准库提供的机制-条件变量来实现等待操作。条件变量关联到某种事件或者其它条件,一个或多个线程可以通过这个变量来等待条件的发生。某个线程在发现该条件满足后,可以通知其它因等待该条件而挂起的线程,唤醒它们让它们继续执行。
(2)wait() 中的条件变量在 wait 的时候会释放锁的,其他线程可以继续执行,代码中 method 1 和 method 2 的实现是一样的,method 3 在多核 CPU 下会存在虚假唤醒( spurious wakes)的情况,所以建议使用 method 1 ,while() 不仅仅在等待条件变量前检查条件变量,实际上在等待条件变量后也检查条件变量。,具体资料可以参考 【线程的虚假唤醒】
(3) std::unique_lock 的灵活性还在于我们可以主动的调用 unlock() 方法来释放 mutex,因为锁的时间越长,越会影响程序的性能,在一些特殊情况下,提前释放 mutex 可以提高程序执行的效率,参考 【[C++11 并发编程] 08 - Mutex std::unique_lock】 - ps_04:notify 第1种是 notify_one, 只唤醒一个在 wait 的线程; 第2种是 notify_all,唤醒所有在 wait 的线程。
- ps_05:std::queue 用于线程间通信的队列, 【[C++11 并发编程] 11 - 线程间同步 - 等待一个消息或某种条件】
- ps_06:由于同时有两个线程需要操作 m_DataQueue 成员变量,所以在读写的时候要加锁。
(1)lock_guard 模板类使用 RAII 手法封装互斥量,在实例化对象的时候帮你加锁,并且能保证在离开作用域的时候自动解锁。参考 【thread 提供了四种不同的互斥量】
- ps_01:【使用std::function和std::bind实现局部函数做回调】【C++11 学习笔记 std::function和bind绑定器】
Build and Run
Build
$ cd build $ cmake .. $ make
Run the project
$ sudo ./MultiThreadDemo
Reference
-
在主线程中处理辅助线程抛出的 C++ 异常和怎样在线程间传递异常。