当前位置:   article > 正文

基于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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291

实验结果

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

结论

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/545404
推荐阅读
相关标签
  

闽ICP备14008679号