一、理论基础

基于相关的模板匹配技术可直接用于在一幅图像中,寻找某种子图像模式。对于大小为MxN的图像f(x,y)和大小为JxK的子图像模式w(x,y),f与w的相关可表示为:


其种,x=0,1,2,…N-K,y=0,1,2,…M-J。此处
的目的是寻找匹配而不是对f(x,y)进行滤波操作,因此w的原点被设置在子图像的左上角,
并且式(11-3) 给出的形式也完全适用于J和K 为偶数的情况。

计算相关c(x,y)的过程就是在图像f(x,y)中逐点地移动子图像w(x,小),使w 的原点和点
(x,y)重合,然后计算w与f中被w覆盖的图像区域对应像素的乘积之和,以此计算结果作
为相关图像c(x,)在(x,y)点的响应。

相关可用于在图像f(x,y)中找到与子图像w(x,y)匹配的所有位置。实际上,当w 按照上
段中描述的过程移过整幅图像f之后,最大的响应点(xo,yo)即为最佳匹配的左上角点。我们
也可以设定一个阈值T,认为响应值大于该阈值的点均是可能的匹配位置。

相关的计算是通过将图像元素和子模式图像元素联系起来获得的,将相关元素相乘后累
加。我们完全可以将子图像w视为一个按行或按列存储的向量,将计算过程中被w覆盖的图像区域视为另一个按照同样的方式存储的向量.这样一来,相关计算就- 成了向量之间的点积运算。
两个向量的点积为:


其中,Θ 为向量、之间的夹角。显然,当和具有完全相同的方向(平行) 时,,从而式(11-4) 取得其最大值,这就意味着当图像的局部区域类似于子图像模式时,相关运算产生最大的响应。然而,式(11-4) 最终的取值还与向量、自身的模有关,这将导致按照式(11-4) 计算的相关响应存在着对f和w 的灰度幅值比较敏感的缺陷。这样一来,在f的高灰度区域,可能尽管其内容与子图像w的内容并不相近,但由于自身较大而同样产生一个很高的响应。可通过对向量以其模值来归一化解决这一问题,即通过来计算。

改进的用于匹配的相关计算公式如下:


二、代码部分

看了脑疼的理论,下面看看让人愉快的代码吧。
TemplateMatch.h

#pragma once
#include<opencv2\opencv.hpp>
using namespace cv;
typedef unsigned char BYTE;
void TemplateMatch(Mat * pTo, Mat * pTemplate,Mat * src);
1
2
3
4
5
6
TemplateMatch.cpp

 #include"TemplateMatch.h"

void TemplateMatch(Mat * pTo, Mat * pTemplate, Mat * src)
{

    //循环变量
    int i, j, m, n;

    double dSumT; //模板元素的平方和
    double dSumS; //图像子区域元素的平方和
    double dSumST; //图像子区域和模板的点积    

                   //响应值
    double R;

    //记录当前的最大响应
    double MaxR;

    //最大响应出现位置
    int nMaxX;
    int nMaxY;

    int nHeight = src->rows;
    int nWidth = src->cols;
    //模板的高、宽
    int nTplHeight = pTemplate->rows;
    int nTplWidth = pTemplate->cols;

    //计算 dSumT
    dSumT = 0;
    for (m = 0; m < nTplHeight; m++)
    {
        for (n = 0; n < nTplWidth; n++)
        {
            // 模板图像第m行,第n个象素的灰度值
            int nGray =*pTemplate->ptr(m, n);

            dSumT += (double)nGray*nGray;
        }
    }

    //找到图像中最大响应的出现位置
    MaxR = 0;
    for (i = 0; i < nHeight - nTplHeight + 1; i++)
    {
        for (j = 0; j < nWidth - nTplWidth + 1; j++)
        {
            dSumST = 0;
            dSumS = 0;

            for (m = 0; m < nTplHeight; m++)
            {
                for (n = 0; n < nTplWidth; n++)
                {
                    // 原图像第i+m行,第j+n列象素的灰度值
                    int nGraySrc = *src->ptr(i + m, j + n);

                    // 模板图像第m行,第n个象素的灰度值
                    int nGrayTpl = *pTemplate->ptr(m, n);

                    dSumS += (double)nGraySrc*nGraySrc;
                    dSumST += (double)nGraySrc*nGrayTpl;
                }
            }

            R = dSumST / (sqrt(dSumS)*sqrt(dSumT));//计算相关响应

            //与最大相似性比较
            if (R > MaxR)
            {
                MaxR = R;
                nMaxX = j;
                nMaxY = i;
            }
        }
    }

    //将找到的最佳匹配区域复制到目标图像
    for (m = 0; m < nTplHeight; m++)
    {
        for (n = 0; n < nTplWidth; n++)
        {
            int nGray = *src->ptr(nMaxY + m, nMaxX + n);
            //pTo->setTo(nMaxX + n, nMaxY + m, RGB(nGray, nGray, nGray));
            pTo->at<BYTE>(nMaxY + m, nMaxX + n) = nGray;
        }
    }

}   
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
测试代码

#include"TemplateMatch.h"
#include<iostream>

using namespace std;
int main()
{
    Mat src = imread("./src.jpg", 0);
    Mat Template = imread("./template.jpg", 0);
    Mat pt=src;
    pt.data = new BYTE[src.cols*src.rows];
    memset(pt.data, 255, src.cols*src.rows);
    TemplateMatch(&pt, &Template, &src);
    imshow("S", src);
    imshow("T", Template);
    imshow("P", pt);

    imwrite("S.jpg", src);
    imwrite("T.jpg", Template);
    imwrite("P.jpg", pt);
    waitKey(0);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
效果如下:


    下面给出这个工程的百度云连接:链接:http://pan.baidu.com/s/1jIuuT3w 密码:veto
1
写的不好的地方,还希望指正,O(∩_∩)O谢谢。
在这我要强烈推荐一本书:《数字图像处理与机器视觉 Visual C 与Matlab实现》,这本书写的很基础,适合初学者和研究人员
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/wu_lian_nan/article/details/77279108

C++编程模板匹配超详细的识别手写数字实现示例

C++编程模板匹配超详细的识别手写数字实现示例_C 语言_脚本之家

首先,本篇文章用到的方法是模板匹配,而不是基于神经网络的,还请各位注意了!(模板匹配还请自行了解,站上有很多介绍)我刚开始做实验的时候只有一点c++基础,对于文件和opencv我一点都不了解,所以导致了我刚开始迷茫了很久,直到后来才渐渐做起来。废话不多说,让我们开始吧!

过程很简单,如下:

匹配成功:存在一个最小距离(这些距离相等),且为一个数字;存在多个最小距离,且为同一个数字。

拒绝识别:存在多个最小距离,且为不同数字。

识别错误:存在一个最小距离,但与被测数字不是相同的数字。

也许乍一看看不明白,我在这里解释一下,明白的可以绕过。我们这里假设1,2,3(注意,他们的样本都有多个)为训练集,d为测试样本。匹配时匹配到d与1距离最小且只与1距离最小,(可能与多个1的样本距离最小或者只有一个)那么匹配成功;匹配时匹配到d与1和2的某个样本都有最小距离,那么拒绝匹配;匹配时匹配到d(假如d是1的样本)与2有最小距离,那么识别错误。

因为图片处理不是本文章的主要内容,我们跳过图像处理步骤(有兴趣的可以去看图像处理这门课),直接给处理好的图片。那么我们该如何构建训练库,又该如何让计算机能够识别我们的图片呢?接下来我们来看看如何实现构建训练库。

我的实验中有1000张训练样本(200张测试样本),既然要让计算机能够识别,那当然是把图片数字化。在图像处理的步骤里,我们得到的训练样本都是28*28像素点的图片,可以想到28*28是一个不小的数量,为了提高处理速度,我们把图片压缩成7*7大小的,这样即提高了处理速度,也方便我们写代码,因为7*7和4*4都是正方形。如下图:

压缩图片:我们纵向遍历7*7的方格,将里面像素大于127的小格子进行计数,当其数量超过6(有的同学会觉得应该是8,因为8是一半,但是8最终得到的正确率太低了,所以我找了一个合适的参数)我们就把大格子对应的7*7的二维数组的相应位置设置为1,反之为0;然后再将数组转换成字符串,这样下来我们就会得到一个长度为49的字符串,这个字符串就是我们计算机匹配的核心。

另外,我是先把训练集和测试集分别数据化,再依次取出来作比较。也可以采用一边遍历测试集和处理,一遍作比较,我没有输出文件名,因为我采用的方式比较笨,代码量也很多,主要是因为我之前写完之后有很多bug,导致我不能成功运行,所以我采用这种简单代码来避免错误,小伙伴们大可不必用这种方式!

值得注意的是,文件流的打开和关闭的时机也会很大程度上影响代码运行,这个问题困扰了我很久,希望大家引以为戒,代码中具体位置我已经标出来了。(标***的位置)另外,大家对于读文件写文件的文件流自己去了解,读文件是ofstream,写文件是ifstream,每次访问文件都要打开文件和关闭文件。getline函数每次依次取一行数据,所以我们在遍历完一个文档之前不会关闭文档,也就不会再打开文档。

最后我对我字符串比较做一个解释,我是采用了一个标志refused来标志当前字符串有没有被拒绝识别,当发现相似度(代码中用total表示的)小于49的就把它赋值给相似度,并且把拒绝识别设置为假,直到找到最小的,当找到最小的之后又找到了另一个相同相似度的,则判断两个样本数字是不是相同的,不是的话就把refused设置为真,即在后面直接输出拒绝识别。

我判断两个样本是否为同一个数字是通过范围比对,简单地来说就是训练样本的第0——99个对应测试样本的第0——19个,这是一个偷懒的办法,我没时间改代码了所以就这样代替了别人那种文本带文件名的。(带文件名比对时还需要去文件名)

其他的解释我放在代码里,有助于大家更直接的理解!

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

#include<iostream>

#include<fstream>

#include<opencv2/opencv.hpp>

#include<opencv2/highgui.hpp>

#include<opencv2/core.hpp>

#include<io.h>                          //api和结构体

#include<string.h>

#include<string>

using namespace std;

using namespace cv;

void ergodicTest(string filename, string name);    //遍历函数

string Image_Compression(string imgpath);          //压缩图片并返回字符串

int distance(string str1, string str2);            //对比函数不一样的位数

void compare();

int turn(char a);

void main()

{

    const char* filepath = "E:\\learn\\vsfile\\c++project\\pictureData\\train-images";    

    ergodicTest(filepath,"train_num.txt");         //处理训练集

    const char* test_path= "E:\\learn\\vsfile\\c++project\\pictureData\\test-images";

    ergodicTest(test_path, "test_num.txt");

    compare();  

}

  

void ergodicTest(string filename,string name)       //遍历并把路径存到files

{

    string firstfilename = filename + "\\*.bmp";

    struct _finddata_t fileinfo;

    intptr_t handle;            //不能用long,因为精度问题会导致访问冲突,longlong也可

    string rout = "E:\\learn\\vsfile\\c++project\\pictureData\\" + name;

    ofstream file;

    file.open(rout, ios::out);

    handle = _findfirst(firstfilename.c_str(), &fileinfo);

    if ( _findfirst(firstfilename.c_str(), &fileinfo) != -1)

    {

        do

        {

            file << fileinfo.name << ":" << Image_Compression(filename + "\\" + fileinfo.name) << endl;

        } while (!_findnext(handle, &fileinfo));

        file.close();

        _findclose(handle);

    }

}

string Image_Compression(string imgpath)   //输入图片地址返回图片二值像素字符

{

    Mat Image = imread(imgpath);               //输入的图片

    cvtColor(Image, Image, COLOR_BGR2GRAY);

    int Matrix[28][28];                        //将digitization转化为字符串类型

    for (int row = 0; row < Image.rows; row++)  //把图片的像素点传给数组

        for (int col = 0; col < Image.cols; col++)

        {

            Matrix[row][col] = Image.at<uchar>(row, col);

        }

    string img_str = "";                   //用来存储结果字符串

    int x = 0, y = 0;

    for (int k = 1; k < 50; k++)

    {

        int total = 0;

        for (int q = 0; q < 4; q++)

            for (int p = 0; p < 4; p++)

                if (Matrix[x + q][y + p] > 127) total += 1;

        y = (y + 4) % 28;

        if (total >= 6) img_str += '1';    //将28*28的图片转化为7*7即压缩

        else img_str += '0';

        if (k % 7 == 0)

        {

            x += 4;

            y = 0;

        }

    }

    return img_str;

}

  

int distance(string str1, string str2)  //比对两个字符串有多少个不一样

{

    int counts=0;

    for (int i = 0; i < str1.length(); i++)

    {

        if (str1[i] == str2[i]) continue;

        else counts++;

    }

    return counts;

}

int turn(char a)

{

    stringstream str;

    int f = 1;

    str << a;

    str >> f;

    str.clear();

    return f;

}

  

void compare()

{

    ifstream train_data;//建立读文件流

    ifstream test_data;

    string tmp1 = "";         //从train中取数据存在tmp1

    string tmp11 = "";

    string tmp2 = "";         //从test中取

    string tmp22 = "";

    bool refused = false; //拒绝识别标志

    int tr_num = 0;       //用来存储最小值的文件名(训练集)

    int num_refused = 0;   //拒绝识别个数

    int num_false = 0;     //识别错误个数

    int num_true = 0;      //正确识别个数

    test_data.open("E:\\learn\\vsfile\\c++project\\pictureData\\test_num.txt");

    for (int p = 0; p < 200; p++)

    {

        int total = 49;      //方便比大小,设置初值为49

        getline(test_data, tmp2);

        tmp22 = tmp2;    //在切割字符串之前保留,以便后面知晓该字符串是哪个数字的

        if(tmp2.length()==57) tmp2.erase(0,8); //erase函数是用来切割字符串的,这里是切割第0位的后面8位,存剩余的其他位

        else tmp2.erase(0,9);

        train_data.open("E:\\learn\\vsfile\\c++project\\pictureData\\train_num.txt");

        for (int j = 0; j < 1000; j++)         //一个测试样本和所有训练样本对比

        {

            getline(train_data, tmp1);

            tmp11 = tmp1;

            if (tmp1.length() == 57) tmp1.erase(0, 8);

            else tmp1.erase(0, 9);

            if (distance(tmp1, tmp2) < total)  //取最相近的

            {

                refused = false;   //拒绝识别被设置为否,即识别没有被拒绝

                total = distance(tmp1, tmp2);

                tr_num = turn(tmp11[0]);          //记录数字

            }

            else if(distance(tmp1, tmp2) == total && tr_num!= turn(tmp11[0]))  //发现相同相似度,且两者归属的数字不同

            {

                refused = true;   //拒绝识别

                continue;          //循环继续

            }  

        }

        train_data.close();

  

        if (!refused)

        {

            if (tr_num == turn(tmp22[0]))

            {

                //cout << tmp2[0] << endl;

                num_true++;

                cout << "识别为:" << tr_num << endl;

            }

            else

            {

                num_false++;

                cout << "识别错误!" << endl;

            }

        }

        else

        {

            num_refused++;

            cout << "拒绝识别!" << endl;

        }

    }

    test_data.close();

    double t = num_true / 200.0, f = num_false / 200.0, r = num_refused / 200.0;

    cout << "正确率为:" << t << endl;

    cout << "错误率为:" << f << endl;

    cout << "拒绝识别率为:" << r << endl;

}

代码中有很多//注释,都是我调试代码用的,不用管。

我把遍历文件夹的参考链接放在这里://www.jb51.net/article/225454.htm

另外,如有错误欢迎大家指正!

以上就是C++编程模板匹配超详细的识别手写数字实现示例的详细内容,更多关于C++编程模板匹配识别手写数字的资料请关注脚本之家其它相关文章!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐