【问题标题】:Unable To Open Two Cameras Using OpenCV - Multithreading Camera Reads无法使用 OpenCV 打开两个摄像头 - 多线程摄像头读取
【发布时间】:2018-04-12 10:39:16
【问题描述】:

我正在尝试使用 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

我在 StackOverflow 上浏览了各种问答,但没有任何帮助。

【问题讨论】:

  • 如果你是新手,那么你需要学习如何使用调试器并获得回溯。
  • @underscore_d 你不认为我们是边干边学吗?我这样做并陷入了上述问题。无论如何,谢谢!
  • 当然,学习如何调试是一项基本技能,它将显着提高您的学习质量和速度。相比之下,SO 上的读者必须为您调试代码并不会获得相同的好处。
  • 首先,因为您没有等待线程启动 - 您的 thread_data 有时会在您开始使用之前被销毁。
  • @underscore_d 完全同意。我被卡住了,所以我不得不询问并尝试使用谷歌搜索以及我能做的一切但没有找到解决方案。

标签: c++ multithreading opencv segmentation-fault


【解决方案1】:

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

  1. OpenCV 窗口标题不是唯一的。
  2. 衍生线程未加入
  3. 打开视频流时出现竞争状况。

让我们详细研究一下这些问题。

1.

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

2.

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

3.

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

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

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

【讨论】:

  • 我尝试了修改后的代码,但没有帮助。虽然它两次显示“已成功打开 IP 摄像机”,但它仅从一台摄像机流式传输,也不是连续流式传输。它会持续播放几秒钟,然后消失并卡住。
  • @AkritiRao... 我已经在离线视频文件上测试了这段代码,它工作正常。可能是 IP 流的处理方式有所不同。您能否确认它是否适用于离线视频文件?
  • 我在 TX2 上测试过,它打开了两个摄像头,几秒钟后其中一个卡住了。即使它被卡住了,它仍然在流式传输。我通过编写从两个线程获取的图像来检查。所以问题似乎出在“imshow”中。
  • @AkritiRao ...我无法使用 TX2,所以不能多说。但也许您也可以尝试锁定imshow 呼叫。
【解决方案2】:

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();
}

正如 cmets 所提到的,没有任何图像流的 imshow() 的流式传输运行良好,没有任何问题。我的设置包括,使用两个 USB 摄像头运行两个线程,并且这两个线程分别启动一个新线程来保存从摄像头读取的图像。我没有观察到任何丢帧或错误,并且能够以大约 30 fps 的速度进行捕获和写入。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-25
    • 1970-01-01
    • 2023-02-10
    • 1970-01-01
    • 2020-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多