无法使用OpenCV打开两个摄像头 - 多线程摄像头读取

时间:2017-10-31 09:53:44

标签: c++ multithreading opencv segmentation-fault

我正在尝试使用OpenCV通过单独的线程连续传输来自2台摄像机的视频。以下代码显示Segmentation fault (core dumped)

这是什么原因以及如何解决此问题?

的main.cpp

#include <iostream>
#include <pthread.h>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/videoio.hpp>

using namespace std;

struct thread_data {
  string path;
  int  thread_id;
};

void *capture(void *threadarg)
{
  struct thread_data *data;
  data = (struct thread_data *) threadarg;

  cv::VideoCapture cap(data->path);

  if( !cap.isOpened())
  {
    std::cout<<"Not good, open camera failed"<<std::endl;
  }
  std::cout<< "Opened IP camera successfully!"<<std::endl;
  cv::Mat frame;
  string ext = ".jpg";
  string result;
  while (true) {
    cap >> frame;
    cv::imshow("Frame",frame);
    cv::waitKey(1);
  }
  pthread_exit(NULL);
}

int main(void) {
  pthread_t threads[2];
  struct thread_data td[2];
  int rc=0;
  for( int i = 0; i < 2; i++ ) {
     cout <<"main() : creating thread, " << i << endl;
     td[i].thread_id = i;
     td[0].path = "rtsp://admin:opencv123@192.168.1.23:554/Streaming/Channels/101/";
     td[1].path = "rtsp://admin:opencv123@192.168.1.24:554/Streaming/Channels/101/";
     rc = pthread_create(&threads[i], NULL, capture, (void *)&td[i]);

     if (rc) {
        cout << "Error:unable to create thread," << rc << endl;
        exit(-1);
     }
  }
  pthread_exit(NULL);
  return 0;
}

日志:

main() : creating thread, 0    main() : creating thread, 1    
Segmentation fault (core dumped)

当我尝试多次运行时,我只能打开一个摄像头而且也不能连续播放。它会在几秒钟内启动和停止。

有时候我会收到错误消息

OpenCV Error: Insufficient memory (Failed to allocate 140703464366800 bytes) in OutOfMemoryError

我经历过各种Q&amp; As&#39;在 StackOverflow 但没有帮助。

2 个答案:

答案 0 :(得分:3)

这里的问题是代码面临竞争条件。我能够在我的系统上复制该问题,并确定了以下问题:

  1. OpenCV窗口标题不是唯一的。
  2. 产生的主题未加入
  3. 打开视频流时的竞争条件。
  4. 让我们详细研究这些问题。

    1

    OpenCV窗口由其标题唯一标识。在当前代码中,标题是硬编码字符串"Frame"。所以基本上,两个线程都以未知顺序创建/更新/销毁同一个窗口。这是一个竞争条件,可以通过向struct thread_data添加一个字符串字段来修复,该字段将作为唯一的窗口标识符。

    2

    在主线程中,子线程是异步创建的,因此for循环将在创建线程后立即退出,程序将在不等待生成线程完成执行的情况下提前退出。这个问题可以通过在程序退出之前添加函数调用来等待线程来解决。此过程称为加入,可以通过为每个衍生线程调用pthread_join来实现。

    3

    这个问题有点难以追查。由于某种原因,OpenCV使用的用于视频流捕获的后端库不以线程安全的方式运行。看起来,视频捕获打开过程容易出现竞争状态并需要同步锁定。通过在打开pthread_mutex_lock对象之前和之后调用函数pthread_mutex_unlockVideoCapture,可以轻松实现锁定。

    以下是修改后的代码,演示了所有上述问题的解决方案

    #include <iostream>
    #include <pthread.h>
    #include <opencv2/core.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/videoio.hpp>
    
    using namespace std;
    
    //Mutex for thread synchronization
    static pthread_mutex_t foo_mutex = PTHREAD_MUTEX_INITIALIZER;
    
    
    struct thread_data 
    {
      string path;
      int  thread_id;
      string window_title; //Unique window title for each thread
    };
    
    void *capture(void *threadarg)
    {
      struct thread_data *data;
      data = (struct thread_data *) threadarg;
    
      cv::VideoCapture cap;
    
    
      //Safely open video stream
      pthread_mutex_lock(&foo_mutex);
      cap.open(data->path);
      pthread_mutex_unlock(&foo_mutex);
    
      if( !cap.isOpened())
      {
        std::cout<<"Not good, open camera failed"<<std::endl;
      }
      std::cout<< "Opened IP camera successfully!"<<std::endl;
      cv::Mat frame;
      string ext = ".jpg";
      string result;
    
      //Create window with unique title
      cv::namedWindow(data->window_title);
    
      while (true) 
      {
        cap >> frame;
        cv::imshow(data->window_title,frame);
        cv::waitKey(10);
      }
    
      //Release VideoCapture object
      cap.release();
      //Destroy previously created window
      cv::destroyWindow(data->window_title);
    
      //Exit thread
      pthread_exit(NULL);
    }
    
    int main(void)
    {
        const int thread_count = 2;
    
        pthread_t threads[thread_count];
        struct thread_data td[thread_count];
    
    
        //Initialize thread data beforehand
        td[0].path = "rtsp://admin:opencv123@192.168.1.23:554/Streaming/Channels/101/";
        td[0].window_title = "First Window";
        td[1].path = "rtsp://admin:opencv123@192.168.1.24:554/Streaming/Channels/101/";
        td[1].window_title = "Second Window";
    
    
        int rc=0;
        for( int i = 0; i < thread_count; i++ ) 
        {
            cout <<"main() : creating thread, " << i << endl;
            td[i].thread_id = i;
    
            rc = pthread_create(&(threads[i]), NULL, capture, (void *)& (td[i]) );
    
            if (rc) 
            {
                cout << "Error:unable to create thread," << rc << endl;
                exit(-1);
            }
        }
    
        //Wait for the previously spawned threads to complete execution
        for( int i = 0; i < thread_count; i++ )
            pthread_join(threads[i], NULL);
    
        pthread_exit(NULL);
    
        return 0;
    }
    

答案 1 :(得分:0)

void writeFrametoDisk(const cv::Mat *frame, std::string path, int frameNum, std::string windowName)
{
  cv::imwrite(path.append(std::to_string(frameNum)).append(".png"), *frame);
  return;
}

void openCameraStream(int deviceId, std::string dirName)
{
  cv::VideoCapture cap;
  cap.open(deviceId);
  if(!cap.isOpened()){std::cerr << "Unable to open the camera " << std::endl;}

  std::cout << cap.get(cv::CAP_PROP_FRAME_WIDTH) << " " << cap.get(cv::CAP_PROP_FRAME_HEIGHT) << std::endl;

  std::string windowName = deviceId == 0 ? "Cam 0" : "Cam 1";
  std::string outputDir = dirName;

  bool frameStFg = false;
  while(!frameStFg)
  {
    cv::Mat frame;
    cap.read(frame);
    if(frame.empty()){std::cerr << "frame buffer empty " << std::endl;}
    else
    {
      frameStFg = true;
    }
  }

  cv::TickMeter timer;
  timer.start();
  int frameCount = 0;

  while(1)
  {
    cv::Mat frame;
    cap.read(frame);

    if(frame.empty()){std::cerr << "frame buffer empty " << std::endl;}
    frameCount++;

    std::thread th(writeFrametoDisk, &frame, outputDir, frameCount, windowName);
    th.join();
    //// a simple wayto exit the loop
    if(frameCount > 500)
    {
      break;
    }
  }
  timer.stop();
  std::cout << "Device id " << deviceId << " Capture ran for " << timer.getTimeSec() << " seconds" << std::endl;
  std::cout << "Device id " << deviceId << " Number of frames to be capture should be " << timer.getTimeSec() * 30 << std::endl;
  std::cout << "Device id " << deviceId << " Number of frames captured " << frameCount << std::endl;
  cap.release();
}

int main(int argc, char * argv[])
{
  std::string outputDir1 = "";
  std::string outputDir2 = "";

  std::thread camera1Thread(openCameraStream, 0, outputDir1);
  std::thread camera2Thread(openCameraStream, 1, outputDir2);
  camera1Thread.join();
  camera2Thread.join();
}

正如评论所提到的,没有图像流的任何imshow()的流都可以很好地工作而不会出现任何问题。我的设置包括使用两个USB摄像机运行两个线程,并且这两个线程分别启动一个新线程,以保存从摄像机读取的图像。我没有观察到任何帧丢失或错误,也能够以大约30 fps的速度捕获和写入。

稍微调试一下原因,建议仅在主线程中使用任何imshow()函数,即main()函数。希望这对任何人都有帮助。