OpenCV camera capture from within a thread

2020-07-22 18:55发布

问题:

This is a small part of the code I'm trying to get to work. This is also one of my first times working with C++. I'm used to higher-level languages, like Java or C#.

The main version is meant to be run as a shared object or DLL. The idea is that an external program (in C#) will start the main loops. The frames from the camera will be captured in a thread. Information will processed inside of that thread and copied to an array ("dataArray"). This copy process will be done while a class mutex is locked. Then, another function called externally will copy that saved array ("dataArray") to a second array ("outArray") and return a pointer to the second array. The external program will use the pointer to copy the data from the second Array, which will not be modified until the function is called again.

But for all that to work, I need the frames to constantly be captured. I realized that I needed something to keep my main function going, so I'm keeping an infinite loop in there. In the "real" version, the keepRunning variable will be changed by the external program running the library.

I was recently lectured on StackOverflow about not making global variables, so I'm keeping the one instance of my class in a static member. That's pretty standard in Java. I don't know if it's bad practice in C++. I was also taken by surprise as to how C++ threads start as soon as they're created, without an explicit "start" instructions. That's why I'm putting my only thread in a vector. That seems to be what most people recommend.

I understand that without keepRunning never being actually changed, the threads will never be joined, but I'll dear with that later. I'm running this on a Mac, but I'll need it to eventually run on Windows, Mac and Linux.

Here's my header:

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <thread>
#include <vector>

using namespace cv;
using namespace std;

class MyCap {
public:
  MyCap();
  VideoCapture cap;
  static MyCap * instance;
  void run();
  static void RunThreads(MyCap * cap);
  bool keepRunning = true; // Will be changed by the external program.
  vector<thread> capThreads;
private:
  Mat frame;
};

And here's my code:

#include "theheader.h"

MyCap * MyCap::instance = NULL;

int main(int argc, char** argv) {
  MyCap::instance = new MyCap();
  MyCap::instance->capThreads.push_back(thread(MyCap::RunThreads, MyCap::instance));
  // Outside loop.
  while(MyCap::instance->keepRunning) {
  }
  for (int i = 0; i < MyCap::instance->capThreads.size(); i++) {
    MyCap::instance->capThreads[i].join();
  }
}

MyCap::MyCap() {
  namedWindow("flow", 1);
  cap.open(0);
}

void MyCap::RunThreads(MyCap * cap) {
  cap->run();
}

void MyCap::run() {
  // Inside loop.
  while(keepRunning) {
    cap >> frame;
    imshow("flow", frame);
    if (waitKey(30) >= 0) {
      break;
    }
  }
}

With this code, I get a black screen. If I run cap.open(0) from within the run method, I don't even get that. I'm obviously doing something very wrong. But what really puzzles me is: why does it make a difference where that same code is called from? If I run what is now in run inside of main it will work. If I change the call of cap.open(0) from the constructor to run, that changes what the method does. Also the waitKey condition stops working from within the thread. What big thing am I missing?

Version 2

Based on the suggestions of @darien-pardibas, I made this second version:

Header:

#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <thread>
#include <vector>

using namespace cv;
using namespace std;

class MyCap {
public:
  MyCap();
  void run();
  bool keepRunning = true; // Will be changed by the external program.
  static void RunThreads(MyCap * cap);
  static vector<thread> capThreads;
  static MyCap * getInstance();
private:
  static MyCap * instance;
};

The main file:

#include "theprogram.h" // I'll admit that, even for a placeholder, it was a bad name.

MyCap * MyCap::instance = NULL;

vector<thread> MyCap::capThreads;

MyCap::MyCap() {
  cout << "Instantiate" << endl;
}

MyCap * MyCap::getInstance() {
  if (MyCap::instance == NULL) {
    MyCap::instance = new MyCap;
  }
  return MyCap::instance;
}

void MyCap::RunThreads(MyCap * cap) {
  cap->run();
}

void MyCap::run() {
  cout << "Run" << endl;
  namedWindow("flow", 1);
  cout << "Window created." << endl;
  VideoCapture cap(0); // HANGS HERE!
  cout << "Camera open." << endl; // This never gets printed.
  // Inside loop.
  Mat frame;
  while(keepRunning) {
    cap >> frame;
    imshow("flow", frame);
    if (waitKey(30) >= 0) {
      break;
    }
  }
}

int main(int argc, char** argv) {
  MyCap::capThreads.push_back(thread(&MyCap::RunThreads, MyCap::getInstance()));
  for (int i = 0; i < MyCap::capThreads.size(); i++) {
    MyCap::capThreads[i].join();
  }
}

This prints:

Instantiate
Run
Window created.

And hangs there.

But if I move the code from run to main and change keepRunning to true, then it works as expected. I think I'm missing something else, and I'm guessing it has something to do with how C++ works.

回答1:

Okay, without looking at resolving all design patterns issues I can see in your code, I can confirm that the code below works. I think the main problem was that you needed to create the namedWindow in the same thread where you will be capturing the image and remove the while loop you had in your main method.

// "theheader.h"

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <thread>
#include <vector>

class MyCap {
public:
    void run();
    static void RunThreads(MyCap * cap);
    bool keepRunning = true; // Will be changed by the external program.
    std::vector<std::thread> capThreads;
private:
    cv::Mat frame;
    cv::VideoCapture cap;
    MyCap() { }
    static MyCap * s_instance;
public:
    static MyCap *instance();
};

// "theheader.cpp"

#include "theheader.h"
#pragma comment(lib, "opencv_core248d")
#pragma comment(lib, "opencv_highgui248d")

using namespace std;
using namespace cv;

MyCap * MyCap::s_instance = NULL;
MyCap* MyCap::instance() {
    if (s_instance == NULL)
        s_instance = new MyCap();
    return s_instance;
}

void MyCap::RunThreads(MyCap * cap) {
    cap->run();
}

void MyCap::run() {
    namedWindow("flow", 1);
    cap.open(0);

    // Inside loop.
    while (keepRunning) {
        cap >> frame;
        imshow("flow", frame);
        if (waitKey(30) >= 0) {
            break;
        }
    }
}

int main(int argc, char** argv) {
    MyCap::instance()->capThreads.push_back(thread(&MyCap::RunThreads, MyCap::instance()));

    for (int i = 0; i < MyCap::instance()->capThreads.size(); i++) {
        MyCap::instance()->capThreads[i].join();
    }
}