我正在构建一个应用程序,它在一个线程中使用OpenCV从IP摄像头获取图像,然后在处理后将Mat
转换为QImage
,然后将QImage
发送到信号,然后图像将由相应的插槽在GUI线程中接收。
信号在处理映像线程类中声明:
void sendImage(const QImage &frame);
在GUI线程类中声明了插槽:
void onGetImage(const QImage &img);
但之后,当我在QLabel
上显示图像或将图像保存到磁盘时,程序崩溃了。
我对C++
有点新,所以对QImage
对象的传递引用有什么问题吗?如果是,那么在没有任何复制(重新分配内存)开销的情况下在线程之间传递对象的正确方法是什么?因为我想在长时间运行的应用程序中防止甚至很小的开销。
更新:有时会运行一段时间,然后崩溃,有时甚至会在第一帧崩溃。
谢谢!
代码:处理图片
class ImageProcess
{
public:
ImageProcess(std::function<void(cv::Mat&)> imgCallback):imgCallback(imgCallback){}
public:
void start()
{
while(start) // loop till start become false
{
videpCapture.read(frame);
// .... some process on image
imgCallback(frame)
}
}
private:
cv::Mat frame;
std::function<void(cv::Mat&)> imgCallback;
};
代码:在图像处理单元和GUI之间进行通信
class WorkerThread: public QObject
{
Q_OBJECT
public:
WorkerThread()
{
imgProcess = new ImageProcess(std::bind(.....)); // bind the callback
}
public slots:
void onStart()
{
if(imProcessThread != nullptr)
{
imgProcess.start = false; // stop if running
imProcessThread->quit(); // quit the thread
imProcessThread->wait(); // wait to finish
delete imProcessThread; // delete the pointer
}
imProcessThread = new ImageProcessThread(imgProcess); // create
imProcessThread->start(); // start thread
}
signals:
void sendMessage(const QString& msg, const int& code);
private:
ImageProcess *imgProcess;
void frameCallback(const cv::Mat& frame); // frame callback
{
emit sendImage(matToQImage(frame)); // send the image to UI
}
// Mat to QImage converter
QImage matToQImage(const cv::Mat &mat)
{
cv::Mat rgbMat;
if(mat.channels() == 1) { // if grayscale image
return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step,
QImage::Format_Indexed8);// declare and return a QImage
} else if(mat.channels() == 3) { // if 3 channel color image
cv::cvtColor(mat, rgbMat, CV_BGR2RGB); // invert BGR to RGB
return QImage((uchar*)rgbMat.data, mat.cols, mat.rows,
(int)mat.step, QImage::Format_RGB888);// declare and return a QImage
}
return QImage();
}
// image process thread
class ImageProcessThread : public QThread
{
public:
ImageProcessThread(ImageProcess *ip) : ip(ip){}
protected:
void run()
{
ip->start();
}
private:
ImageProcess *ip;
} *imProcessThread = nullptr;
};
代码:用户界面
class Camera : public QWidget
{
Q_OBJECT
public:
explicit Camera(QWidget *parent = 0)
{
wt = new WorkerThread;
thread = new QThread;
//conncet the signal-slots
connect(wt, &WorkerThread::sendImage, this, &Camera::onGetImage);
connect(ui->btStart, &QPushButton::clicked, wt, &WorkerThread::start);
}
private slots:
void onGetImage(const QImage &img)
{
// set the image to QLabel
}
private:
WorkerThread *wt;
QThread *thread;
};
wt.moveToThread(thread);
thread->start();
那么为此目的可以设计一个goog?
答案 0 :(得分:3)
由于您没有提供mcve我猜,但您对matToQImage
的实施看起来有点怀疑。你有......
QImage matToQImage (const cv::Mat &mat)
{
cv::Mat rgbMat;
if (mat.channels() == 1) {
return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_Indexed8);
} else if (mat.channels() == 3) {
cv::cvtColor(mat, rgbMat, CV_BGR2RGB);
return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_RGB888);
}
return QImage();
}
但是,从documentation开始,QImage
构造函数用于转换cv::Mat
...
使用给定的宽度,高度和格式构造图像 现有的内存缓冲区,数据。宽度和高度必须是 以像素为单位指定bytesPerLine指定每个字节数 line(stride)。
缓冲区必须在QImage的整个生命周期内保持有效 未经修改或以其他方式分离的副本 原始缓冲区 [我的重点]。图像不会在销毁时删除缓冲区。 你可以提供一个函数指针cleanupFunction和一个额外的 指针cleanupInfo将在最后一个副本时被调用 破坏。
所以返回的QImage
可能在调用cv::Mat
析构函数后引用了悬空指针。
如果这是问题,那么最简单的解决方案是制作QImage
的{{3}}并返回...
QImage matToQImage (const cv::Mat &mat)
{
cv::Mat rgbMat;
if (mat.channels() == 1) {
return QImage((uchar*)mat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_Indexed8).copy();
} else if (mat.channels() == 3) {
cv::cvtColor(mat, rgbMat, CV_BGR2RGB);
return QImage((uchar*)rgbMat.data, mat.cols, mat.rows, (int)mat.step, QImage::Format_RGB888).copy();
}
return QImage();
}
答案 1 :(得分:0)
接受 @ G.M。的回答,我想补充一些我在这个问题中发现的细节,以帮助其他人提出同样的问题。
问题在于将cv::Mat
转换为QImage
时,图片数据未复制到QImage,因此cv:Mat
和QImage
共享相同的数据。因为有两个线程同时运行,那么当创建QImage然后它发送到UI
线程时,此时,处理映像线程(后台工作线程)已经清理了以前的数据在UI
线程完成刷新图像作业之前的下一帧。所以该计划已经崩溃。