前言
本文主要简述基于C++结合opencv做的一个机器视觉的标准软件,主要实现功能是工件的定位,在自动化生产线中做视觉检测,本次功能实现的有3中定位算法:形状匹配,灰度匹配,可旋转匹配,界面开发使用标准的QT框架,当然此项目精度是没法保证的,如果想保障精度应用到生产环境中,建议使用halcon,halcon也会在后续的文章中提到,此项目仅用于学习
一、项目工程及目录结构
1.编程环境
环境安装步骤百度一大堆我这里就不描述了,而且百度出来的安装教程基本都能用。
IDE:VisualStudio 2010
QT:V5.3
opencv:2.4
编程环境使用比较低的版本,本人测试过可以往高版本配置,代码不需要修改,只需要修改对应的库即可,
;本人测试过的环境:
IDE:VisualStudio 2019
QT:V5.12
opencv:4.2
2.项目目录结构及介绍
本项目比较简单,从目录看,主要分为2个界面,主界面+标定界面,标定算法可以用在工业上的,因为里边其实就是普通的矩阵计算,没有设计什么算法优化。
还有一个封装的QT线程类,因为原生的线程本人认为不太好用也不太好控制,因此自己继承QThread后重写线程启动的方法。
二、代码构建
1.界面的编辑
RobitVision.ui
此界面比较简单,中间窗体只有一个QLabel控件,然后还有一个菜单栏,此菜单栏就包含了本项目的功能:
从以上信息即可看到有哪些功能
2.代码构建
1.连接相机+预览
//连接相机
bool ConnectedCamera(int Type,int Num)
{
if(Type == 0)
{
try{
cap.open(Num);
OpenCameraFlag = true;
CameraType = Type;
CameraNum = Num;
}catch(...){}
}
return OpenCameraFlag;
}
//断开连接
bool DisConnectedCamera()
{
if(CameraType == 0)
{
try{
cap.release();
OpenCameraFlag = false;
}catch(...){}
}
return !OpenCameraFlag;
}
//预览相机图像
void RobotVision::on_QAction_Open_Close_Video_triggered()
{
if(ui.QAction_Open_Close_Video->text() == QStringLiteral("开始预览") && CtuLibLink::CameraIsConnected())
{
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->SetThreadFunc(&GetImage);
workerThread->ThreadFlag = true;
workerThread->start();
ui.QAction_Open_Close_Video->setText(QStringLiteral("停止预览"));
}
else
{
disconnect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->stop();
ui.QAction_Open_Close_Video->setText(QStringLiteral("开始预览"));
}
}
workerThread是自定义的线程类,线程类代码如下:
QtThread::QtThread(QObject *parent)
: QThread(parent)
{
ThreadFlag = false;
SleepTime = 50;
pp = NULL;
}
QtThread::~QtThread()
{
requestInterruption();
stop();
quit();
wait();
}
void QtThread::run()
{
while(ThreadFlag)
{
msleep(SleepTime);
(*pp)();
}
}
void QtThread::stop()
{
m_bStopped = true;
ThreadFlag = false;
}
void QtThread::SetThreadFunc(Fun pCallback)
{
pp = pCallback;
}
我这里使用了一个函数指针,方便函数的回调,而且可让用户自定义,使用及其方便
2.定位算法
//模板的保存方式
void RobotVision::on_QAction_CreateModel_triggered()
{
std::string fileName= "";
switch(Current_ModelNum)
{
case 0:
fileName = "Model1.yml";
break;
case 1:
fileName = "Model2.yml";
break;
case 2:
fileName = "Model3.yml";
break;
default:
return;
}
cv::FileStorage fs(fileName,cv::FileStorage::WRITE);
fs<<"ModelNum"<< Current_ModelNum;
fs<<"ModelFunc"<<Current_ModelFunc;
fs<<"OrigianImage"<<ho_img;
fs<<"ModelImage"<<model_roi;
fs<<"ModelRect"<<Model_Rect;
fs<<"Target_Point"<<Target_Point;
fs<<"SeartchImage"<<Seartch_roi;
fs<<"Seartch_Rect"<<Seartch_Rect;
fs.release();
myModelCom[Current_ModelNum].Current_ModelFunc = Current_ModelFunc;
myModelCom[Current_ModelNum].O_Image = ho_img.clone();
myModelCom[Current_ModelNum].Model_Image = model_roi.clone();
myModelCom[Current_ModelNum].Model_Rect = Model_Rect;
myModelCom[Current_ModelNum].Target_Point = Target_Point;
myModelCom[Current_ModelNum].Seartch_Image = Seartch_roi.clone();
myModelCom[Current_ModelNum].Seartch_Rect = Seartch_Rect;
myModelCom[Current_ModelNum].Flag = true;
}
//形状匹配
bool FindModel1(cv::Mat img,cv::Mat model,cv::Mat* dst,cv::Point* p)
{
try{
//模板匹配不带旋转
cv::Mat img_display,result;
//0:CV_TM_SQDIFF 1:CV_TM_SQDIFF_NORMED 2:CV_TM_CCORR 3:CV_TM_CCORR_NORMED 4:CV_TM_CCOEFF 5:CV_TM_CCOEFF_NORMED
int match_method = CV_TM_SQDIFF;
img.copyTo(img_display);
int result_cols = img.cols - model.cols+1;
int result_rows = img.rows - model.rows+1;
result.create(result_cols,result_rows,CV_32FC1);
//执行匹配与归一化处理
cv::matchTemplate(img,model,result,match_method);
cv::normalize(result,result,0,1,cv::NORM_MINMAX,-1,cv::Mat());
double minVal,maxVal;
cv::Point minLoc,maxLoc,matchLoc;
//寻找图中最大最小值的位置
cv::minMaxLoc(result,&minVal,&maxVal,&minLoc,&maxLoc,cv::Mat());
if(match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED)
{
matchLoc = minLoc;
}
else
{
matchLoc = maxLoc;
}
//绘制匹配结果范围
cv::rectangle(img_display,matchLoc,cv::Point(matchLoc.x+model.cols,matchLoc.y+model.rows),cv::Scalar::all(0),2,8,0);
//归一化结果
cv::rectangle(result,matchLoc,cv::Point(matchLoc.x+model.cols,matchLoc.y+model.rows),cv::Scalar::all(0),2,8,0);
//show
*dst = img_display.clone();
*p = cv::Point(matchLoc.x+model.cols/2,matchLoc.y+model.rows/2);
return true;
}catch(...){return false;}
}
3.标定算法
bool SetCalib(double ImageX[3],double Image_Y[3],double Robot_X[3],double Robot_Y[3])
{
try{
cv::Point2f srcTri[3];
cv::Point2f dstTri[3];
srcTri[0] = cv::Point2f(ImageX[0],Image_Y[0]);
srcTri[1] = cv::Point2f(ImageX[1],Image_Y[1]);
srcTri[2] = cv::Point2f(ImageX[2],Image_Y[2]);
dstTri[0] = cv::Point2f(Robot_X[0], Robot_Y[0]);
dstTri[1] = cv::Point2f(Robot_X[1], Robot_Y[1]);
dstTri[2] = cv::Point2f(Robot_X[2], Robot_Y[2]);
warp_mat = cv::getAffineTransform(srcTri, dstTri ); //获得旋转矩阵
return true;
}catch(...){return false;}
}
总结
本项目最终效果:
RobotVision_Opencv 模板匹配视屏