项目背景:
用于检测地震,火山喷发,海啸,等任何能产生次声波得现象。
本项目就是利用摄像头观察水面得震动来判断有没有次声波(水面可以放漂浮物也可以不放)
一,移植opencv
首先要使用Opencv那么就必须移植,因为QT原本不支持,这里最主要看.pro文件中要添加的opencv路径
TARGET_ARCH = $${QT_ARCH}
contains(TARGET_ARCH, arm){
CONFIG += link_pkgconfig
PKGCONFIG += opencv
INCLUDEPATH += /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi/usr/include
} else {
LIBS += -L/usr/local/lib \
-lopencv_core \
-lopencv_highgui \
-lopencv_imgproc \
-lopencv_videoio \
-lopencv_imgcodecs
#INCLUDEPATH可写可不写,系统会到找到此路径
INCLUDEPATH += /usr/local/include
}
二、摄像头得到的QImage转化为Mat类型
我们知道opencv中最继承的类型就是mat,使用OpenCV进行图像处理那么我们必须将QImage转化为Mat。
在摄像头中添加一个信号:
我们这个震动显示的项目其实是基于摄像头项目的。在那个项目中,摄像头类中通过一定时器,以一定的频率将QImage发生给mainwindow,然后mainwindow进行渲染图像。
void Camera::timerTimeOut()
{
/* 如果摄像头没有打开,停止定时器,返回 */
if (!capture->isOpened()) {
timer->stop();
return;
}
static cv :: Mat frame;
*capture >> frame;
if (frame.cols)
{
/* 发送图片信号 */
emit readyImage(matToQImage(frame));
//emit check(matToQImage(frame),frame);
}
}
而我们要实现图像处理我们当然得在mainwindow添加一个新的处理函数,然后在摄像头类中添加一个信号与其连接。
1.添加连接
connect(camera, SIGNAL(check(QImage,cv::Mat)),this, SLOT(showchanged(QImage,cv::Mat)));
2.添加信号
emit check(matToQImage(frame),frame);
三、两种方法进行震动检测
一、帧差法+背景减除法
其中帧差法主要用于数值上得震动判断,而背景减除法主要用于视觉上得震动判断。
1.帧差法:
原理其实很简单,我们知道mat类型数据其实保存着每个像素点的值。而我们判断视频有没有动只需要将两帧图像的mat相减就行了。
但是这里有个注意点:
那就是,即使视频没有发生改变,我们两帧图像相减得到的数值还是不为0,只是很小。所有我们判断视频有没有震动,或者震动的剧烈情况,我们就要将两帧相减的值与我们设定的阈值比较。如果大于这个阈值就说明是否发生震动,或者发生几级震动。
步骤:
1.将上一次的mat与这一次得到的mat一起进行灰度化。(进行灰度化的原因在于:灰度化之后,mat的信息更小,这样再进行图像处理误差会更小)
2.将两帧图像进行相减
3.将相减后的mat的所有像素点取平均值
cv::Mat mat1_grey,mat2_grey,mat_diff;
//先转化为灰度图再做判断
cvtColor(mat1, mat1_grey, COLOR_BGR2GRAY);
cvtColor(mat2, mat2_grey, COLOR_BGR2GRAY);
absdiff(mat1_grey,mat2_grey,mat_diff);
avg = mean(mat_diff)[0];
4.然后再根据阈值进行判断,分支处理 (当然这些阈值也是要经过实验之后确定的,比如是否震动的阈值)
if(avg>(jiaoyan_avg+0.02))
qDebug()<<"avg"<<avg<<"震动";
else
qDebug()<<"avg"<<avg;
if(avg>(jiaoyan_avg+5))
{
flag = 10;
sprintf(buffer, "%f:>%f\n",(jiaoyan_avg+30));
question2->setText(buffer);
goto label1;
return true;
}
else if(avg>(jiaoyan_avg+2))
{
flag = 10;
sprintf(buffer, "%f:>%f\n",(jiaoyan_avg+10));
question2->setText(buffer);
return true;
}
else if(avg>(jiaoyan_avg+1))
{
flag = 10;
sprintf(buffer, "%f:>%f\n",(jiaoyan_avg+5));
question2->setText(buffer);
return true;
}
else if(avg>(jiaoyan_avg+0.02))
{
flag = 10;
sprintf(buffer, "%f:>%f\n",avg,(jiaoyan_avg+0.02));
question2->setText(buffer);
return true;
}
2.背景减除法:
背景减除法要实现的效果就是先将图片二值化(二值化就是黑白图片),将动的部分显示出来。
步骤:
1.或得帧差法得到得差值mat
2.讲差值mat进行二值化
3.再进行腐蚀操作(作用在于去除那些微弱得干扰)
4.渲染
avg = mean(mat_diff)[0];
//相减后的mat进行阈值处理
threshold(mat_diff,mat_diff,40,255,THRESH_BINARY);
//腐蚀
Mat kernel_erode = getStructuringElement(MORPH_RECT, Size(3, 3));
erode(mat_diff,mat_diff,kernel_erode);
//处理后的mat转化成Qimage
back_dec = Mat2QImage(mat_diff);
二、漂浮物几何中心相减法
上面的方法是要记住上一次的mat,而几何中心法是要先描绘出水面的漂浮物的几何中心,然后记录这个几何中心,并与上一次的几何中心进行比较。
同时视觉上也要显示出勾勒出的几何轮廓以及几何中心。
步骤:
1.对获得的mat进行灰度化
2.对灰度化的mat进行平滑处理(作用在与去除微弱的干扰)
3.对平滑处理后的mat进行二值化(注意这里的二值化阈值要根据自己的情况来定,不同的场景二值化的阈值也要设置不同的,原因在于我们要突出漂浮物在其它背景上,而漂浮物与背景的颜色就决定了阈值不是固定的)
4.获取图像中所有的外接多边形,以及外接多边形的内切圆的圆心和半径(轮廓其实就是外接多边形,而有多个的原因在于图像或多或少是存在干扰的,而我们放的漂浮物最好就一个)
5.根据我们需要的漂浮物的外接多边形的内切圆的半径,筛选出只有我们想要的轮廓,并且获取其圆心(当然这个半径也需要我们自己调试之后才能发现)
6.渲染漂浮物的内切圆以及它的几何中心。
void MainWindow::CenterCompare(const cv::Mat mat)
{
char buffer[256];
static int contours_size=0;
float x_change = 0 ;
float y_change = 0;
/*找出漂浮物的轮廓与几何中心*/
Mat image_gray;
cvtColor(mat, image_gray, cv::COLOR_BGR2GRAY);
blur( image_gray, image_gray, Size(3,3) );//平滑处理
Mat threshold_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
threshold( image_gray, threshold_output, 25/*这个量要自己设置,我用的是黑色正方形的的漂浮物*/, 255, THRESH_BINARY );//二值化处理,阈值为20
findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
vector<vector<Point> > contours_poly( contours.size() );
vector<Rect> boundRect( contours.size() );
vector<Point2f>center( contours.size() );
vector<float>radius( contours.size() );
for( size_t i = 0; i < contours.size(); i++ )
{
approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );
boundRect[i] = boundingRect( Mat(contours_poly[i]) );
minEnclosingCircle( contours_poly[i], center[i], radius[i] );
}
qDebug()<<"contours"<<contours.size();
/*排除干扰*/
for( size_t i = 0; i< contours.size(); i++ )
{
if(abs(radius[i]-26)<1)//这个26是要根据自己的漂浮物找出来的,如果没有筛选会出现contours.size个轮廓,你必须找出自己漂浮物的轮廓的大致半径
{
contours_size++;
if(contours_size>1)//我们只允许出现我们想要的轮廓
{
contours_size = 0;
return;
}
}
}
contours_size = 0;
for( size_t i = 0; i< contours.size(); i++ )
{
if(abs(radius[i]-26)<1)
{
if(last_center.x==0&&last_center.y==0)//如果是第一次那么直接返回
{
last_center.x = center[i].x;
last_center.y = center[i].y;
return;
}
x_change = abs(center[i].x-last_center.x);
y_change = abs(center[i].y-last_center.y);
if(x_change>0.5&&y_change>0.5)//如果都大于0.5表示震动
{
flag = true;
sprintf(buffer, "振幅: %f\n",x_change+y_change);//直接用x,y改变量之和表示振幅
question2->setText(buffer);
}
else
flag = false;
center_point.x = last_center.x = center[i].x;
center_point.y = last_center.y = center[i].y;
Scalar color = Scalar(0,255,0);//给绿色
rectangle( mat, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );//画出轮廓外接多边形
circle( mat, center[i], (int)radius[i], color, 2, 8, 0 );//画出多边形内切圆
circle( mat, center_point, 1, color, 2, 8, 0 );
qDebug()<<"r:"<<radius[i];
qDebug()<<center[i].x<<","<<center[i].y;
}
}
//mat转 Qimage
cvtColor(mat, mat, CV_BGR2RGB);
QImage imaged(mat.data,
mat.cols,
mat.rows,
mat.step,
QImage::Format_RGB888);
displayLabel->setPixmap(QPixmap::fromImage(imaged));
if(flag)
{
photoLabel1->setPixmap(QPixmap::fromImage(imaged));
}
else
{
photoLabel1->setPixmap(QPixmap::fromImage(*img));
}
}
源码地址:(gittee下载)
背景消除法:
https://gitee.com/hktk-cww/qt-opencv-backdec.git
描绘轮廓法:
https://gitee.com/hktk-cww/qt-opencv-contour.git