網(wǎng)站首頁 編程語言 正文
為什么需要多線程處理視頻流
在之前有寫過一篇文章Python環(huán)境下OpenCV視頻流的多線程處理方式,上面簡單記錄了如何使用Python實(shí)現(xiàn)對(duì)OpenCV視頻流的多線程處理。簡單來說,在目標(biāo)檢測等任務(wù)中,如果視頻流的捕獲、解碼以及檢測都在同一個(gè)線程中,那么很可能出現(xiàn)目標(biāo)檢測器實(shí)時(shí)性不高導(dǎo)致的檢測時(shí)延問題。使用多線程處理,將視頻幀的捕獲和解碼放在一個(gè)線程,推理放在一個(gè)線程,可以有效緩解時(shí)延的問題,使得目標(biāo)檢測的實(shí)時(shí)性看似有所提升。
C++的多線程處理方式
C++的處理方式與Python大致相同,但卻可能遇到一些問題,如使用OpneCV多線程時(shí)X11庫報(bào)錯(cuò)、OpenCV顯示卡死等問題,這些問題可能的解決方法會(huì)在后面簡單提一下。在本文中,使用的多線程是c++11中引入的thread標(biāo)準(zhǔn)庫,實(shí)現(xiàn)方式則包括函數(shù)封裝和類封裝兩種。
函數(shù)封裝的實(shí)現(xiàn)方式
函數(shù)封裝的實(shí)現(xiàn)方式相比類封裝要更為簡潔,當(dāng)然可復(fù)用性也會(huì)降低。簡單的示例代碼如下:
// video_test.cpp #include <iostream> #include <thread> #include <mutex> #include <atomic> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> static std::mutex mutex; static std::atomic_bool isOpen; static void cameraThreadFunc(int camId, int height, int width, cv::Mat* pFrame) { cv::VideoCapture capture(camId); capture.set(cv::CAP_PROP_FOURCC, CV_FOURCC('M', 'J', 'P', 'G')); capture.set(cv::CAP_PROP_FRAME_WIDTH, width); capture.set(cv::CAP_PROP_FRAME_HEIGHT, height); capture.set(cv::CAP_PROP_FPS, 30); if (!capture.isOpened()) { isOpen = false; std::cout << "Failed to open camera with index " << camId << std::endl; } cv::Mat frame; while (isOpen) { capture >> frame; if (mutex.try_lock()) { frame.copyTo(*pFrame); mutex.unlock(); } cv::waitKey(5); } capture.release(); } int main(int argc, char* argv[]) { isOpen = true; cv::Mat frame(480, 640, CV_8UC3), gray; std::thread thread(cameraThreadFunc, 0, 480, 640, &frame); while (isOpen) { mutex.lock(); frame.copyTo(gray); mutex.unlock(); if (gray.empty()) { break; } cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); cv::blur(gray, gray, cv::Size(3, 3)); cv::Canny(gray, gray, 5 , 38 , 3); cv::waitKey(100); cv::imshow("video", gray); if (cv::waitKey(1) == 'q') { break; } } isOpen = false; thread.join(); return 0; }
在上面的代碼中,攝像頭的打開、幀捕獲及解碼都在cameraThreadFunc
線程函數(shù)中進(jìn)行。在c++11中,有關(guān)pthread
的線程操作都封裝在thread標(biāo)準(zhǔn)庫中,線程的開啟方式也由執(zhí)行pthread_create()
函數(shù)變?yōu)閷?duì)thread
類的操作。使用thread類時(shí),第一個(gè)參數(shù)為線程函數(shù)的指針,后續(xù)的參數(shù)為傳入線程函數(shù)的參數(shù)。需要注意的是:如果要傳入?yún)?shù)引用,則需要使用std::ref()
對(duì)參數(shù)進(jìn)行包裝;如果傳入類成員函數(shù)時(shí),則thread類構(gòu)造函數(shù)的第二個(gè)參數(shù)必須為this
。
使用多線程時(shí)還需要考慮線程之間的同步問題,在上面的程序中,兩個(gè)線程會(huì)同時(shí)訪問pFrame
指向的緩存空間,使用mutex
可確保同一時(shí)刻下僅有一個(gè)線程能訪問到緩存空間。另外,使用atomic_bool
在多線程中進(jìn)行狀態(tài)切換也是必要的,原子操作使得對(duì)布爾變量的賦值在臨界區(qū)中進(jìn)行,可消除線程之間競爭訪問或訪問結(jié)果不一致的情況。
在上面的程序中,由于主線程會(huì)先訪問pFrame
變量,因此需要預(yù)先為pFrame申請(qǐng)空間,不然程序開始執(zhí)行時(shí)出現(xiàn)pFrame為空的情況。在Ubuntu中使用g++編譯的方法如下:
g++ video_test.cpp -std=c++11 -I/usr/local/include/ -lpthread -L/usr/local/lib -lopencv_highgui -lopencv_core -lopencv_imgproc -lopencv_videoio -o video_test
根據(jù)OpenCV版本和安裝位置的不同,需要相應(yīng)修改頭文件和庫文件的位置,例如對(duì)于OpenCV4,頭文件目錄應(yīng)修改為/usr/local/include/opencv4
。在Jetson平臺(tái)上,頭文件的位置在/usr/include/opencv4
,庫文件則在/usr/lib/aarch64-linux-gnu
。如果有配置pkg-config,那么還可以使用如下方式進(jìn)行編譯:
g++ video_test.cpp -std=c++11 `pkg-config --cflags opencv` -pthread `pkg-config --libs opencv` -o video_test
類封裝的實(shí)現(xiàn)方式
同函數(shù)封裝的方式相似,類封裝的方式僅是將線程函數(shù)和線程同步變量變?yōu)轭惓蓡T,從而提升程序的可復(fù)用性。簡單的示例代碼如下:
// video_test.cpp #include <iostream> #include <string> #include <thread> #include <mutex> #include <atomic> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> class VideoCaptureMT { public: VideoCaptureMT(int index, int height=480, int width=640); VideoCaptureMT(std::string filePath, int height=480, int width=640); ~VideoCaptureMT(); bool isOpened() { return m_IsOpen; } void release() { m_IsOpen = false; } bool read(cv::Mat& frame); private: void captureInit(int index, std::string filePath, int height, int width); void captureFrame(); cv::VideoCapture* m_pCapture; cv::Mat* m_pFrame; std::mutex* m_pMutex; std::thread* m_pThread; std::atomic_bool m_IsOpen; }; VideoCaptureMT::VideoCaptureMT(int index, int height, int width) { captureInit(index, std::string(), height, width); } VideoCaptureMT::VideoCaptureMT(std::string filePath, int height, int width) { captureInit(0, filePath, height, width); } VideoCaptureMT::~VideoCaptureMT() { m_IsOpen = false; m_pThread->join(); if (m_pCapture->isOpened()) { m_pCapture->release(); } delete m_pThread; delete m_pMutex; delete m_pCapture; delete m_pFrame; } void VideoCaptureMT::captureInit(int index, std::string filePath, int height, int width) { if (!filePath.empty()) { m_pCapture = new cv::VideoCapture(filePath); } else { m_pCapture = new cv::VideoCapture(index); } m_pCapture->set(cv::CAP_PROP_FRAME_WIDTH, width); m_pCapture->set(cv::CAP_PROP_FRAME_HEIGHT, height); m_pCapture->set(cv::CAP_PROP_FPS, 30); m_IsOpen = true; m_pFrame = new cv::Mat(height, width, CV_8UC3); m_pMutex = new std::mutex(); m_pThread = new std::thread(&VideoCaptureMT::captureFrame, this); } void VideoCaptureMT::captureFrame() { cv::Mat frameBuff; while (m_IsOpen) { (*m_pCapture) >> frameBuff; if (m_pMutex->try_lock()) { frameBuff.copyTo(*m_pFrame); m_pMutex->unlock(); } cv::waitKey(5); } } bool VideoCaptureMT::read(cv::Mat& frame) { if (m_pFrame->empty()) { m_IsOpen = false; } else { m_pMutex->lock(); m_pFrame->copyTo(frame); m_pMutex->unlock(); } return m_IsOpen; } int main(int argc, char* argv[]) { VideoCaptureMT capture(0); cv::Mat frame, gray; while (capture.isOpened()) { if (!capture.read(frame)) { break; } cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); cv::blur(gray, gray, cv::Size(3, 3)); cv::Canny(gray, gray, 5 , 38 , 3); cv::waitKey(100); cv::imshow("image", gray); if (cv::waitKey(5) == 'q') { break; } } capture.release(); return 0; }
在上面的代碼中,線程函數(shù)和線程間同步變量都是類成員,不同的地方在于:攝像頭是在主線程中打開,在子線程中捕獲和解碼幀,但實(shí)際效果和函數(shù)封裝的方式?jīng)]有區(qū)別。
可能遇到的問題
使用C++編寫OpenCV的多線程程序時(shí)可能會(huì)遇到一些問題,例如我在Jetson AGX上運(yùn)行時(shí)會(huì)報(bào)錯(cuò),提示需要進(jìn)行XInitThreads
的初始化。出現(xiàn)這樣的情況時(shí),需要在cpp文件中添加#include <X11/Xlib.h>
頭文件,并在main函數(shù)開頭添加XInitThreads()
函數(shù)調(diào)用,在編譯時(shí)還需要添加-lX11
鏈接庫。我在Jetson Nano上運(yùn)行時(shí)還遇到顯示窗口卡死的情況,既imshow
函數(shù)出現(xiàn)問題,點(diǎn)擊關(guān)閉窗戶后又會(huì)重新打開新窗口正常顯示。遇到這樣的情況,可在main函數(shù)開頭添加一行代碼cv::setNumThreads(1)
,設(shè)置OpenCV在單線程的模式下運(yùn)行可緩解窗口卡死的情況。
原文鏈接:https://blog.csdn.net/hlld__/article/details/112600447
相關(guān)推薦
- 2023-07-29 koa2+sequelize中websocket的使用
- 2022-09-16 C#中Path類的使用方法_C#教程
- 2022-11-01 Kotlin語言使用BroadcastReceiver示例介紹_Android
- 2022-11-01 Kotlin?ContentProvider使用方法介紹_Android
- 2022-10-12 golang中隨機(jī)數(shù)rand的使用_Golang
- 2022-11-01 UE4?Unlua?調(diào)用異步藍(lán)圖節(jié)點(diǎn)AIMoveTo函數(shù)示例詳解_C 語言
- 2022-08-31 在.Net?Framework應(yīng)用中請(qǐng)求HTTP2站點(diǎn)的問題解析_實(shí)用技巧
- 2022-05-02 Python中pyautogui庫的使用方法匯總_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支