基于OpenCV和图像金字塔的模板匹配——火花塞间隙尺寸测量

前言

本项目是机器视觉课程的大作业,写的并不是很明晰,如有问题欢迎提出。

介绍

本项目为基于OpenCV的火花塞间隙尺寸测量,主要是运用模板匹配技术定位火花塞间隙尺寸,然后寻找火花塞间隙并测量其尺寸。

算法说明

在这里插入图片描述

程序主要分成四个部分:

预处理:

在此部分中,将完成模板的创建和待处理图片的路径读取。程序将从文件夹中读取model.png图片作为模板。如果文件夹中没有该图片,则将从第一张待处理图片中截取所需区域作为模板。

匹配:

此函数主要调用OpenCV库中的matchTemplate函数以及相关函数寻找各种角度的图片中与模板的最佳匹配点,并且根据该点找寻区域的中心位置。模板匹配算法根据需要决定,本项目使用平均方差。

旋转图像

为了匹配旋转的图像,所以在匹配时要将图像进行旋转。
旋转使用的是仿射变换,仿射矩阵可用getRotationMatrix2D()得到M1。
由于在旋转图像后,图像的大小也会改变,所以不能直接使用M1进行变换,而是要用getAffineTransform()函数再获得一个大小变换矩阵M2
最后将M1的旋转部分和M2的大小变换部分相结合变成一个新的变换矩阵M,使用warpAffine()函数就能完成图像的仿射变换。
由于代码需要,还需要写坐标变换的函数,公式如下
在这里插入图片描述
M就是一个3*2的矩阵

图像金字塔

如果单纯使用模板匹配,时间复杂度将爆炸,但我们可以使用图像金字塔来进行优化

  1. 建立n层图像金字塔,对于每一层金字塔,将图像向下采样,每 2 n 2^n 2n 2 n 2^n 2n列取一个像素。由此获得 1 2 n \frac{1}{2^n} 2n1倍的图像和模板。采样完后的图像可用高斯滤波器滤波(本项目没有)

  2. 从高层开始将该层模板对该层图像进行模板匹配,所有匹配度高于某一阈值的点都将用一个矩形框住,并记录下旋转角度变换范围。

  3. 下一层在上一层截取的区域和角度中进行模板匹配,依次类推逐渐获得一个比较小的匹配区域和角度范围。

  4. 对n-1层图像金字塔依次进行匹配后,在源图像中找到得到的区域,并在相应角度范围中进行最终的模板匹配,获得中心点的位置。

尺寸计算:

当找到匹配区域的中心点位置后,垂直向下寻找火花塞间隙。将图片变换成灰度图之后,就可以发现向下直线上的两处灰度值突变的点便是间隙边缘上的点,计算两点间距离就是火花塞间隙的开度。

绘制输出

测量处相关函数之后就可以在图片上绘制测量区域,并且显示开度的数值

代码实现

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
其他函数///
void Min(int &a,int b)
{
    if(a>b)a = b;
}
void Max(int &a,int b)
{
    if(a<b)a = b;
}
Point GetCenter(int x,int y,int w,int h)
{
    return Point(x+w/2,y+h/2);
}
/*
获取仿射矩阵
*/
Mat GetMatrix(int cols,int rows,int newCols,int newRows,int degree)
{ 
	Point2f center(cols / 2, rows/2);//中心
	Mat M1 = getRotationMatrix2D(center, degree, 1);//计算旋转的仿射变换矩阵 
	Point2f srcPoints1[3];
	Point2f dstPoints1[3];

	srcPoints1[0] = Point2i(0, 0);
	srcPoints1[1] = Point2i(0, rows);
	srcPoints1[2] = Point2i(cols, 0);

	dstPoints1[0] = Point2i((newCols - cols)/2 , (newRows - rows)/2);
	dstPoints1[1] = Point2i((newCols - cols)/2 , (newRows + rows)/2);
	dstPoints1[2] = Point2i((newCols + cols)/2, (newRows - rows)/2);

	Mat M2 = getAffineTransform(srcPoints1, dstPoints1);
	M1.at<double>(0, 2) = M1.at<double>(0, 2) + M2.at<double>(0, 2);
	M1.at<double>(1, 2) = M1.at<double>(1, 2) + M2.at<double>(1, 2);
	return M1;
}
//旋转图像内容不变,尺寸相应变大  
Mat Rotate(Mat src, int degree)    
{    
    double angle = degree  * CV_PI / 180.;   
    double a = sin(angle), b = cos(angle);   
    int rows=src.rows;
    int cols=src.cols;  
    //旋转后的新图尺寸  
    int width_rotate= int(rows * fabs(a) + cols * fabs(b));    
    int height_rotate=int(cols * fabs(a) + rows * fabs(b));    

    Mat M = GetMatrix(cols,rows,width_rotate,height_rotate,degree);
    Mat dst= Mat::zeros(width_rotate, height_rotate, src.type());
	warpAffine(src, dst, M, Size(width_rotate, height_rotate));//仿射变换
    return dst;
}  
/*
    坐标变换
*/
void TransCoor(Point &point,int degree,int rows,int cols,bool dir = 1)
{
    double angle = degree  * CV_PI / 180.;   
    double a = sin(angle), b = cos(angle);  
    //旋转后的新图尺寸  
    int width_rotate= ceil(rows * fabs(a) + cols * fabs(b));    
    int height_rotate=ceil(cols * fabs(a) + rows * fabs(b));    

    Mat M;
    if(dir == 0)//顺时针
    {
         M = GetMatrix(cols,rows,width_rotate,height_rotate,degree);
    }
    else //逆时针
    {
        M = GetMatrix(width_rotate,height_rotate,cols,rows,-degree);
    }
    point = Point(point.x*M.at<double>(0, 0)+point.y*M.at<double>(0,1)+M.at<double>(0,2),
                  point.x*M.at<double>(1, 0)+point.y*M.at<double>(1, 1)+M.at<double>(1,2));
} 
int level = 3;//金字塔层级
int degreeMin,degreeMax;//旋转范围
int degree;//最终的旋转角度
/*
    在ROI图像中匹配模板model
*/
Point Match(Mat src,Mat model)
{
    Point temLoc;//最佳匹配点
    double Min = 1;
    for(int d = degreeMin;d<=degreeMax;d++)//旋转角度
    {
        Mat img = Rotate(src,d);
        int row = img.rows;
        int col = img.cols;
        Mat result(col, row, CV_32FC1);
       
        if(row < model.rows || col < model.cols)continue;
        matchTemplate(img, model, result, TM_SQDIFF_NORMED);//匹配模板
        
        //normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());//归一化 
        Point minLoc;
        Point maxLoc;
        double min, max;
        //找到最匹配点(函数说明:在一个数组中找到全局最小值和全局最大值)
        minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());
        if(min<Min)
        {
            temLoc = minLoc;//TM_SQDIFF_NORMED是最小值
            degree = d;//最佳匹配角度
            Min = min;
        }
    }
    return GetCenter(temLoc.x, temLoc.y, model.cols, model.rows);
}
/*
    获得第level层金字塔图像
*/
Mat GetPyramid(Mat src,int level)
{
    int sample = 1<<level;//采样率为2^level
    Mat dst = Mat::zeros(Size(src.cols/sample,src.rows/sample),src.type());
    for(int i=0;i<dst.rows;i++)
        for(int j=0;j<dst.cols;j++)
        {
            dst.at<Vec3b>(i,j) = src.at<Vec3b>(i*sample,j*sample);
        }
        
    return dst;
}
/*
    缩小寻找范围
*/
void FindROI(Mat src,Rect &ROI,int level,Mat model)
{
    Mat pyramidSrc = GetPyramid(src,level);
    Mat pyramidModel = GetPyramid(model,level);

    Point topLeftP = Point(pyramidSrc.cols,pyramidSrc.rows);
    Point ButtonRightP = Point(0,0);
    bool isMatch = false;
    int dmin = 359,dmax = 0;
    for(int d = degreeMin;d<=degreeMax;d+=10*level)//旋转角度
    {
        Mat img = Rotate(pyramidSrc,d);
        int row = img.rows;
        int col = img.cols;
        if(row < pyramidModel.rows || col < pyramidModel.cols)continue;
        Mat result(col, row, CV_32FC1);
        matchTemplate(img, pyramidModel, result, TM_SQDIFF_NORMED);//匹配模板

        Point minLoc;
        Point maxLoc;
        double min, max;
        //找到最匹配点(函数说明:在一个数组中找到全局最小值和全局最大值)
        minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());
        if(min<0.1*level)
        {
            isMatch = true;
            //匹配到的四个点
            Point point[4] = {
                minLoc,
                Point(minLoc.x,minLoc.y+pyramidModel.rows),
                Point(minLoc.x+pyramidModel.cols,minLoc.y),
                Point(minLoc.x+pyramidModel.cols,minLoc.y+pyramidModel.rows)
            };
            //寻找左上角和右下角
            for(int i = 0;i<4;i++)
            {
                TransCoor(point[i],d,pyramidSrc.rows,pyramidSrc.cols);
                Min(topLeftP.x,point[i].x);
                Min(topLeftP.y,point[i].y);
                Max(ButtonRightP.x,point[i].x);
                Max(ButtonRightP.y,point[i].y);
            }
            Min(dmin,d);
            Max(dmax,d);
        }
    }
    if(isMatch == false)return ;//如果没匹配到则返回
    //更新感兴趣区域
    topLeftP*=1<<level;
    ButtonRightP*=1<<level;
    Max(ROI.x,topLeftP.x+ROI.x);
    Max(ROI.y,topLeftP.y+ROI.y);
    Min(ROI.width,ButtonRightP.x-topLeftP.x);
    Min(ROI.height,ButtonRightP.y-topLeftP.y);
    Max(ROI.x,0);
    Max(ROI.y,0);
    Min(ROI.width,src.cols);
    Min(ROI.height,src.rows);
    degreeMax = dmax;
    degreeMin = dmin;
}
Point FindTemplate(Mat src,Mat model)
{
    Rect ROI = Rect(0,0,src.cols,src.rows);
    degreeMin = 0,degreeMax = 359;
    for(int i=level;i>=1;i--)
    {
        FindROI(src(ROI),ROI,i,model);
    }
    cout<<degreeMax<<" "<<degreeMin<<endl;
    //原始图像中匹配
    Point center = Match(src(ROI),model);
    //还原中心坐标
    TransCoor(center,degree,ROI.height,ROI.width);
    center.x+=ROI.x;
    center.y+=ROI.y;

    TransCoor(center,degree,src.rows,src.cols,false);
    return center;
}

/*
    寻找直线端点
*/
void FindLinePoint(Mat src,Point startP,Point &firstP,Point &secondP,int &dis)
{

    int y = startP.y;
    Mat gray;
    cvtColor(src,gray,COLOR_RGB2GRAY);
    while(y<src.rows)
    {
        int deri = (int)gray.at<uchar>(y,startP.x)-
                   (int)gray.at<uchar>(y+1,startP.x);
        if( deri < -65)//像素变大且跨度大于阈值则为第一个点
        {
            firstP = Point(startP.x,y+1);
        }
        if( deri > 65)//像素变小且跨度大于阈值则为第二个点
        {
            secondP = Point(startP.x,y+1);
            break;
        }
        y++;
    }
    dis = abs(firstP.y-secondP.y);//计算尺寸
    
    return ;
}
void Deal(Mat src,Mat model)
{
    //寻找匹配到的区域的中心点
    Point center = FindTemplate(src,model);
    //寻找火花塞间隙的两个端点,并计算尺寸
    Point firstP,secondP;
    int dis;
    FindLinePoint(Rotate(src,degree),center,firstP,secondP,dis);
    // //标记并输出图像
     Mat dst;
     src.copyTo(dst);

    // //将坐标变换为原图
    TransCoor(center,degree,src.rows,src.cols);
    TransCoor(firstP,degree,src.rows,src.cols);
    TransCoor(secondP,degree,src.rows,src.cols);

     circle(dst,center,3,Scalar(0,0,255));
    line(dst,firstP,secondP,Scalar(0,0,255));
    putText(dst,"d:"+to_string(dis),(secondP+firstP)/2+Point(10,0),
            FONT_HERSHEY_COMPLEX,0.5,Scalar(0,0,255));
     imshow("dst",dst);  
}
int main()
{
    string pattern_jpg;
    vector<String> image_files;
    pattern_jpg = ".\\img\\*.png";
    glob(pattern_jpg, image_files);//读取图片路径

    //创建模板
    string mode_path = ".\\img\\model.png";
    Mat model = imread(mode_path);//模板
   if(model.empty())//读取不到图片
   {
       Mat img=imread(image_files[0]);
       model = img(Rect(250,150,120,80));
   }
   //处理图片
    for(int i=0;i<image_files.size();i++)
    {
        cout << image_files[i] << endl;
        if(image_files[i]==".\\img\\model.png")continue;
   		Mat img=imread(image_files[i]);
        Deal(img,model);
        waitKey();
    }
    return  0;
}

实验结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结论

在这个项目中我们学到了如何基于opencv库中的模板匹配实现待识别器件的定位,以及根据位置信息测量所需的参数。该技术可用于简单环境下的零件尺寸测量。由于需要旋转角度匹配时可以匹配旋转的图片,所以速度非常的慢。但使用图像金字塔进行优化后,速度提升非常之大。

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
可以使用OpenCV中的模板匹配函数进行图像多模板匹配。该函数需要输入待匹配图像和模板图像,并返回匹配结果的坐标信息。以下是一个基于C语言和OpenCV的图像多模板匹配的示例代码: #include <opencv2/highgui/highgui_c.h> #include <opencv2/imgproc/imgproc_c.h> void multi_template_match(IplImage* src, IplImage* tem[], int n_template) { // Define match method and threshold int match_method = CV_TM_CCOEFF_NORMED; double threshold = 0.8; // Loop through all templates and perform matching for (int i = 0; i < n_template; i++) { IplImage* result = cvCreateImage(cvSize(src->width - tem[i]->width + 1, src->height - tem[i]->height + 1), IPL_DEPTH_32F, 1); cvMatchTemplate(src, tem[i], result, match_method); // Find best match CvPoint max_loc; double max_val; cvMinMaxLoc(result, 0, &max_val, 0, &max_loc, 0); if (max_val >= threshold) { // Draw rectangle around matched area cvRectangle(src, cvPoint(max_loc.x, max_loc.y), cvPoint(max_loc.x + tem[i]->width, max_loc.y + tem[i]->height), CV_RGB(255, 0, 0), 2); } cvReleaseImage(&result); } } int main() { // Load source image and templates IplImage* src = cvLoadImage("source.jpg"); IplImage* tem[2]; tem[0] = cvLoadImage("template1.jpg"); tem[1] = cvLoadImage("template2.jpg"); // Perform multi-template matching multi_template_match(src, tem, 2); // Show result image cvShowImage("Result", src); cvWaitKey(0); // Release resources cvReleaseImage(&src); cvReleaseImage(&tem[0]); cvReleaseImage(&tem[1]); return 0; }
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值