<tbody id="86a2i"></tbody>


<dd id="86a2i"></dd>
<progress id="86a2i"><track id="86a2i"></track></progress>

<dd id="86a2i"></dd>
<em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

    <dd id="86a2i"></dd>

    為什么需要多線程處理視頻流

    在之前有寫過一篇文章Python環境下OpenCV視頻流的多線程處理方式,上面簡單記錄了如何使用Python實現對OpenCV視頻流的多線程處理。簡單來說,在目標檢測等任務中,如果視頻流的捕獲、解碼以及檢測都在同一個線程中,那么很可能出現目標檢測器實時性不高導致的檢測時延問題。使用多線程處理,將視頻幀的捕獲和解碼放在一個線程,推理放在一個線程,可以有效緩解時延的問題,使得目標檢測的實時性看似有所提升。

    C++的多線程處理方式

    C++的處理方式與Python大致相同,但卻可能遇到一些問題,如使用OpneCV多線程時X11庫報錯、OpenCV顯示卡死等問題,這些問題可能的解決方法會在后面簡單提一下。在本文中,使用的多線程是c++11中引入的thread標準庫,實現方式則包括函數封裝和類封裝兩種。

    函數封裝的實現方式

    函數封裝的實現方式相比類封裝要更為簡潔,當然可復用性也會降低。簡單的示例代碼如下:

    // 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線程函數中進行。在c++11中,有關pthread的線程操作都封裝在thread標準庫中,線程的開啟方式也由執行pthread_create()函數變為對thread類的操作。使用thread類時,第一個參數為線程函數的指針,后續的參數為傳入線程函數的參數。需要注意的是:如果要傳入參數引用,則需要使用std::ref()對參數進行包裝;如果傳入類成員函數時,則thread類構造函數的第二個參數必須為this。

    使用多線程時還需要考慮線程之間的同步問題,在上面的程序中,兩個線程會同時訪問pFrame指向的緩存空間,使用mutex可確保同一時刻下僅有一個線程能訪問到緩存空間。另外,使用atomic_bool在多線程中進行狀態切換也是必要的,原子操作使得對布爾變量的賦值在臨界區中進行,可消除線程之間競爭訪問或訪問結果不一致的情況。

    在上面的程序中,由于主線程會先訪問pFrame變量,因此需要預先為pFrame申請空間,不然程序開始執行時出現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

    根據OpenCV版本和安裝位置的不同,需要相應修改頭文件和庫文件的位置,例如對于OpenCV4,頭文件目錄應修改為/usr/local/include/opencv4。在Jetson平臺上,頭文件的位置在/usr/include/opencv4,庫文件則在/usr/lib/aarch64-linux-gnu。如果有配置pkg-config,那么還可以使用如下方式進行編譯:

    g++ video_test.cpp -std=c++11 `pkg-config --cflags opencv` -pthread `pkg-config --libs opencv` -o video_test

    類封裝的實現方式

    同函數封裝的方式相似,類封裝的方式僅是將線程函數和線程同步變量變為類成員,從而提升程序的可復用性。簡單的示例代碼如下:

    // 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;
    }

    在上面的代碼中,線程函數和線程間同步變量都是類成員,不同的地方在于:攝像頭是在主線程中打開,在子線程中捕獲和解碼幀,但實際效果和函數封裝的方式沒有區別。

    可能遇到的問題

    使用C++編寫OpenCV的多線程程序時可能會遇到一些問題,例如我在Jetson AGX上運行時會報錯,提示需要進行XInitThreads的初始化。出現這樣的情況時,需要在cpp文件中添加#include <X11/Xlib.h>頭文件,并在main函數開頭添加XInitThreads()函數調用,在編譯時還需要添加-lX11鏈接庫。我在Jetson Nano上運行時還遇到顯示窗口卡死的情況,既imshow函數出現問題,點擊關閉窗戶后又會重新打開新窗口正常顯示。遇到這樣的情況,可在main函數開頭添加一行代碼cv::setNumThreads(1),設置OpenCV在單線程的模式下運行可緩解窗口卡死的情況。

    原文地址:https://blog.csdn.net/hlld__/article/details/112600447

    相關文章:

    免费一级a片在线播放视频|亚洲娇小性XXXX色|曰本无码毛片道毛片视频清|亚洲一级a片视频免费观看
    <tbody id="86a2i"></tbody>

    
    
    <dd id="86a2i"></dd>
    <progress id="86a2i"><track id="86a2i"></track></progress>

    <dd id="86a2i"></dd>
    <em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

      <dd id="86a2i"></dd>