INCLUDEPATH += /usr/local/include \
/usr/local/include/opencv
/usr/local/include/opencv2
LIBS += /usr/local/lib/libopencv_* \
左边是Canny边缘检测结果,右边是 L a p l a c e Laplace Laplace算子(梯度的散度)结果,分别用QLabel显示图像。下面是两个QPushButton,参考运行结果。
几个重要的点:
这里使用第一条,即将对象附属到子线程。
括号中…点表示携带参数,对于Mat类型这里传递的是指针,传引用时失败了。
Canny, J. “A Computational Approach To Edge Detection”(《一种边缘检测的计算方法》). IEEE Trans. Pattern Analysis and Machine Intelligence. 1986, (8): 679–714
二阶微分算子,定义为梯度的散度。具有各向同性
△ f = ▽ ∙ ▽ f = ∂ 2 f ∂ x 2 + ∂ 2 f ∂ y 2 \bigtriangleup f= \bigtriangledown \bullet \bigtriangledown f= \frac{\partial ^2f}{\partial x^2}+\frac{\partial ^2f}{\partial y^2} △f=▽∙▽f=∂x2∂2f+∂y2∂2f
离散后,对应的卷积核为
0 | 1 | 0 |
1 | -4 | 1 |
0 | 1 | 0 |
worker.cpp如下
#include "worker.h"
#include
#include
Worker::Worker(QObject *parent) : QObject(parent)
{
}
//flag是一个任务类型标识,0的时候做Canny边缘检测,1的时候做拉普拉斯算子
void Worker::task(cv::Mat* src,cv::Mat* dst, uchar flag)
{
if (src->data == NULL)
return;
if (flag == 0) //Canny边缘检测
{
cv::Canny(*src,*dst,20,40,3); //3是直径,应在3~7范围内,否则会报错
// qDebug()<
}
if (flag == 1 ) //拉普拉斯算子,梯度的散度
{
this->laplaceGrad(*src,*dst);
}
emit threadId((int*)QThread::currentThreadId()); //当前线程id返回
}
//laplace任务
void Worker::laplaceGrad(cv::Mat& src,cv::Mat& dst)
{
if (src.channels()!=1)
return;
int rows = src.rows;
int cols = src.cols;
dst = cv::Mat::zeros(rows,cols,CV_32FC1);
for (int i=1;i<rows-1;i++)
{
for (int j=1;j<cols-1;j++)
{
// [1,1,1;1,-8,1;1,1,1]
dst.ptr<float>(i)[j] = 1.0*src.ptr<uchar>(i-1)[j-1]+1.0*src.ptr<uchar>(i-1)[j]+1.0*src.ptr<uchar>(i-1)[j+1]
+1.0*src.ptr<uchar>(i)[j-1]-8.0*src.ptr<uchar>(i)[j]+1.0*src.ptr<uchar>(i)[j+1]
+1.0*src.ptr<uchar>(i+1)[j-1]+1.0*src.ptr<uchar>(i+1)[j]+1.0*src.ptr<uchar>(i+1)[j+1];
}
}
cv::convertScaleAbs( dst, dst, 1, 0 ); // 从CV_32FC1转换到CV_8UC1
}
worker.h
#ifndef WORKER_H
#define WORKER_H
#include
#include
#include
using namespace cv;
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
void laplaceGrad(cv::Mat& src,cv::Mat& dst);
signals:
void threadId(int*); //这个信号用来返回线程id标识
public slots:
//槽函数,子线程任务在这个函数中处理
void task(cv::Mat* src,cv::Mat* dst, uchar flag);
};
#endif // WORKER_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include "mythread.h"
#include"worker.h"
#include
void mat2qimg(Mat &src,QImage &qimg);
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//打开摄像头
connect(ui->openCamBtn,&QPushButton::clicked,this,[=](){
VideoCapture capture(0);
Mat frame,canny,laplace,bi; //bi表示双边滤波结果
int rows = ui->cLabel->height();
int cols = ui->cLabel->width();
// laplace = Mat::zeros(rows,cols,CV_32FC1);
QImage qimgc,qimgla;
//建立线程1和线程2
MyThread* thread1 = new MyThread;
MyThread* thread2 = new MyThread;
//建立两个worker
Worker* worker1 = new Worker;
Worker* worker2 = new Worker;
//将两个worker移动到线程1和线程2中
worker1->moveToThread(thread1);
worker2->moveToThread(thread2);
//将主线程的信号与worker连接起来,注意传递的参数类型要一致
connect(this,&MainWindow::operate1,worker1,&Worker::task); //信号1绑定线程1中任务
connect(this,&MainWindow::operate2,worker2,&Worker::task); //信号2绑定线程2中任务
//显示线程标识
connect(worker1,&Worker::threadId,[=](int* id1){
QString id = QString::number(*id1,16); //16进制表示
ui->t1Label->setText(QString("线程标识:%1").arg(id)); //+QString::number(*id1)
});
connect(worker2,&Worker::threadId,[=](int* id2){
QString id = QString::number(*id2,16); //16进制表示
ui->t2Label->setText(QString("线程标识:%1").arg(id));
});
//启动两个线程
thread1->start();
thread2->start();
while (1)
{
capture >> frame;
if (frame.empty())
continue;
//一些预处理操作,获得双边滤波后图像
cv::resize(frame,frame,Size(cols,rows));
cv::cvtColor(frame,frame,CV_BGR2GRAY);
cv::bilateralFilter( frame, bi, 15, 21,21);
//向线程1和线程2发送信号,分别处理数据。
//参数传递的是指针,传递引用时出错。这里两个线程没有同时修改同一个内存地址。应当注意避免出现加锁的情况
emit operate1(&bi,&canny,0);
emit operate2(&bi,&laplace,1);
//主线程阻塞,等待子线程消息队列中没有任务
thread1->wait(20);
thread2->wait(20);
// qDebug()<<"before qimg";
//转成QImage
mat2qimg(canny,qimgc);
mat2qimg(laplace,qimgla);
//用QLabel显示
ui->cLabel->setPixmap(QPixmap::fromImage(qimgc));
ui->lapLabel->setPixmap(QPixmap::fromImage(qimgla));
waitKey(5);
}
});
//退出
connect(ui->closeBtn,&QPushButton::clicked,this,[=](){
exit(0);
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void mat2qimg(Mat &src,QImage &qimg)
{
if (src.channels()==3)
{
cvtColor(src,src,CV_BGR2RGB);
qimg = QImage((const unsigned char*)(src.data), src.cols, src.rows, QImage::Format_RGB888 );
}
else
{
qimg = QImage((const unsigned char*)(src.data), src.cols, src.rows, QImage::Format_Grayscale8 );
}
return;
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
using namespace cv;
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
signals:
//两个信号,operate1 发送给线程1,operate2发送给线程2
void operate1(cv::Mat* src,cv::Mat* dst, uchar flag);
void operate2(cv::Mat* src,cv::Mat* dst, uchar flag);
};
#endif // MAINWINDOW_H