├── README.md
├── Resources
├── 算法总览.jpg
└── 算法总览.vsdx
├── __pycache__
├── defectComparison.cpython-36.pyc
├── getBestMatching.cpython-36.pyc
├── getParts.cpython-36.pyc
└── utils.cpython-36.pyc
├── defectComparison.py
├── feature
└── feature1.jpg
├── getBestMatching.py
├── getParts.py
├── init.py
├── out
├── 1.jpg
├── 2.jpg
└── 3.jpg
├── testp
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
└── 4.png
└── utils.py
/README.md:
--------------------------------------------------------------------------------
1 | # PartsTemplateMatching
2 | 用于电路板的零件识别,所用方法为Opencv的模板识别
3 | * Author : FreeA7
4 | * Date : Jan 28, 2019
5 | * Version : 1.0
6 |
7 | ## 注意
8 | * code 由 [5300 零件匹配](https://github.com/FreeA7/5300)修改过来,所以现有所有函数都可直接用于 5300
9 | * 使用之前一定要看此文档,可以帮助你快速入手
10 | * testp 和 feature 中的图片都是 5300 的图片
11 | * 最好使用类似 Photoshop 等软件进行描点,达到最好的匹配效率
12 | * 请预先模仿 GetBestMatching 类中生成二值图片的方法生成好二值 feature ,参数都是我多次测试模拟效果最好的,一般不要改动,如果效果很差可自行调整。
13 |
14 | ## 算法总览
15 |
16 |
17 | ## 各文件描述
18 | ### init.py
19 | * 这是执行文件,可在这里执行各种方法
20 | * 方法包括对单独图片或者对整个文件夹的,注意注释即可使用
21 | * 有的方法可以保存图片结果,注意注释即可
22 | * 由于目前手头没有故障文件,所以用 nparray 进行测试替代
23 | * 不同零件的阈值可能有较大区别,注意测试,如使用空图或者完全随机的图片
24 |
25 | ### utils.py
26 | * 这是需要使用者自行修改的文件,包括零件位置,输出结构等
27 | * 其中的函数不要修改,只是供 init 使用,绝大多数时候不用改
28 | * 三个子类需要根据零件具体情况进行重写,具体请看注释
29 |
30 | ### getBestMatching.py
31 | * 这是进行模板匹配并返回最佳匹配的父类
32 | * 一般来说流程是固定的,不需要修改
33 | * 需要调整的 resize 函数已经继承在 util.py 中,重写即可
34 |
35 | ### getParts.py
36 | * 这是获取零件具体位置的父类
37 | * 一般来说流程是固定的,不需要修改
38 | * 需要调整的 getTarget 函数已经继承在 util.py 中,重写即可
39 |
40 | ### defectComparison.py
41 | * 这是进行故障匹配并输出结果的父类
42 | * 一般来说流程是固定的,不需要修改
43 | * 需要调整的三个函数已经继承在 util.py 中,重写即可
44 |
45 | ### feature & testp & out
46 | * 这是我在测试时使用的存放feature、图片和输出结果的文件夹
47 | * 可删可改可随意调整,不固定,在 init 中写好位置就行
48 |
--------------------------------------------------------------------------------
/Resources/算法总览.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/Resources/算法总览.jpg
--------------------------------------------------------------------------------
/Resources/算法总览.vsdx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/Resources/算法总览.vsdx
--------------------------------------------------------------------------------
/__pycache__/defectComparison.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/__pycache__/defectComparison.cpython-36.pyc
--------------------------------------------------------------------------------
/__pycache__/getBestMatching.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/__pycache__/getBestMatching.cpython-36.pyc
--------------------------------------------------------------------------------
/__pycache__/getParts.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/__pycache__/getParts.cpython-36.pyc
--------------------------------------------------------------------------------
/__pycache__/utils.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/__pycache__/utils.cpython-36.pyc
--------------------------------------------------------------------------------
/defectComparison.py:
--------------------------------------------------------------------------------
1 | import cv2 as cv
2 | import numpy as np
3 |
4 |
5 | class DefectComparison(object):
6 | """docstring for DefectComparison"""
7 |
8 | def __init__(self, ptdic, shape, picpath, resizedef):
9 | super(DefectComparison, self).__init__()
10 | self.ptdic = ptdic
11 | self.shape = shape
12 | self.pic = cv.imread(picpath, 0)
13 | self.resizedef = resizedef
14 |
15 | def getone(self, n, hov):
16 | # 防止Main匹配时切图切片值比图片像素大而返回空图
17 | if n < 0:
18 | return 0
19 | else:
20 | if not hov:
21 | if n > self.shape[1]:
22 | return self.shape[1]
23 | else:
24 | return n
25 | else:
26 | if n > self.shape[0]:
27 | return self.shape[0]
28 | else:
29 | return n
30 |
31 | def getOverlapping(self, ptss, target):
32 | # 零件的匹配,矩阵相加进行匹配
33 | im1 = np.zeros(self.shape[:2], dtype=np.uint8)
34 | for pts in ptss:
35 | im1 = cv.fillConvexPoly(im1, pts, 1)
36 |
37 | if not isinstance(target, np.ndarray): # target 为缺陷图片路径
38 | target = self.pic // 255
39 | target = self.resizedef(target)
40 | else:
41 | im2 = np.zeros(self.shape[:2], dtype=np.uint8) # target 为缺陷轮廓点
42 | target = cv.fillConvexPoly(im2, target, 1)
43 |
44 | img = im1 + target
45 |
46 | if (img > 1).any():
47 | return 1
48 | else:
49 | return 0
50 |
51 | def getMOverlapping(self, m, target):
52 | # 进行piex个数的匹配,将图片一个piex一个piex的切开,故障同理然后匹配,快
53 | sum_m = 0
54 |
55 | im1 = np.ones(self.shape[:2], dtype=np.uint8)
56 |
57 | if not isinstance(target, np.ndarray):
58 | target = self.pic // 255
59 | target = self.resizedef(target)
60 | else:
61 | im2 = np.zeros(self.shape[:2], dtype=np.uint8)
62 | target = cv.fillConvexPoly(im2, target, 1)
63 |
64 | for p in m:
65 | v1 = self.getone(p[0][1], 1)
66 | v2 = self.getone(p[2][1], 1)
67 | if v1 > v2:
68 | v1, v2 = v2, v1 # 镜像方向会变
69 | h1 = self.getone(p[0][0], 0)
70 | h2 = self.getone(p[2][0], 0)
71 | if h1 > h2:
72 | h1, h2 = h2, h1
73 |
74 | im = im1[v1:v2, h1:h2]
75 |
76 | tr = target[v1:v2, h1:h2]
77 |
78 | get = im + tr
79 |
80 | if (get > 1).any():
81 | sum_m += 1
82 |
83 | return sum_m
84 |
85 | def getReturn(self, m1, m2, m):
86 | # 返回最终要求的result,自行定义
87 | result = {}
88 | result['AFFECTEDPIXELNUM'] = m
89 | if not m1:
90 | result['ISM1OPEN'] = False
91 | result['M1M1'] = False
92 | else:
93 | result['ISM1OPEN'] = True
94 | if m1 > 1:
95 | result['M1M1'] = True
96 | else:
97 | result['M1M1'] = False
98 | if not m2:
99 | result['ISM2OPEN'] = False
100 | result['M2M2'] = False
101 | else:
102 | result['ISM2OPEN'] = True
103 | if m2 > 1:
104 | result['M2M2'] = True
105 | else:
106 | result['M2M2'] = False
107 | if m1 > 0 and m2 > 0:
108 | result['M1M2'] = True
109 | else:
110 | result['M1M2'] = False
111 | return result
112 |
113 | def getQOut(self, target=0):
114 | '''
115 | 模板匹配结果和缺陷分割结果对比获取结果入口
116 | :param target: 缺陷分割图像或者缺陷分割点
117 | :return: 对比的最终结果
118 | '''
119 | # 与m1相加个数,m2相交个数,影响了几个piex
120 | sum_m1 = 0
121 | sum_m2 = 0
122 | sum_m = 0
123 |
124 | m1 = []
125 | # 将一组零件放入一个list
126 | for key in ['M1-1', 'M1-2', 'M1-3']:
127 | m1.extend(self.ptdic[key])
128 | # 匹配故障
129 | if self.getOverlapping(m1, target):
130 | sum_m1 += 1
131 |
132 | m2 = []
133 | for key in ['M2-1', 'M2-2', 'M2-3']:
134 | m2.extend(self.ptdic[key])
135 | if self.getOverlapping(m2, target):
136 | sum_m2 += 1
137 |
138 | # 计算piex的思路不同,是把所有main放入一个list,然后切块匹配
139 | sum_m = self.getMOverlapping(self.ptdic['Main'], target)
140 |
141 | return self.getReturn(sum_m1, sum_m2, sum_m)
142 |
--------------------------------------------------------------------------------
/feature/feature1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/feature/feature1.jpg
--------------------------------------------------------------------------------
/getBestMatching.py:
--------------------------------------------------------------------------------
1 | import cv2 as cv
2 |
3 |
4 | class GetBestMatching(object):
5 | """docstring for GetBestMatching"""
6 |
7 | def __init__(self, modelPath, threshold):
8 | super(GetBestMatching, self).__init__()
9 | self.m = self.getModel(modelPath)
10 | self.threshold = threshold
11 |
12 | def gaussianThreshold(self, img, showimg=0):
13 | # 图片进行二值化
14 | gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
15 | binary = cv.adaptiveThreshold(
16 | gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 15, 10)
17 | if showimg:
18 | cv.namedWindow('GAUSSIAN', cv.WINDOW_AUTOSIZE)
19 | cv.imshow('GAUSSIAN', binary)
20 | return binary
21 |
22 | def getModel(self, path):
23 | # 获取模板
24 | m = cv.imread(path, cv.IMREAD_GRAYSCALE)
25 | return m
26 |
27 | def getMore(self, img, m):
28 | # 对目标图片进行模板的匹配,返回最大值与位置
29 | res0 = cv.matchTemplate(img, m, cv.TM_CCOEFF_NORMED)
30 | min_val0, max_val0, min_loc0, max_loc0 = cv.minMaxLoc(res0)
31 | return max_val0, max_loc0
32 |
33 | def resizePic(self, img):
34 | ''' resize图片和返回最大匹配
35 | 如果需要改变图片分辨率需要继承并重写此函数,如:
36 | if img.shape[:2] == (432, 576):
37 | img = cv.resize(img, (558, 421), cv.INTER_CUBIC)
38 | elif img.shape[:2] == (768, 1024):
39 | img = np.rot90(img)
40 | img = np.rot90(img)
41 | img = np.rot90(img)
42 | img = cv.resize(img, (634, 846), cv.INTER_CUBIC)
43 | '''
44 | return img
45 |
46 | def getBest(self, img, m):
47 | # 输出最终模板匹配值的结果并返回
48 | img = self.resizePic(img)
49 | max_val, max_loc = self.getMore(img, m)
50 | print(' END:%f' % (max_val))
51 | if max_val >= self.threshold:
52 | return max_val, max_loc, img
53 | else:
54 | return 0, 0, img
55 |
56 | def getWhich(self, img, m):
57 | # 获取目标图片的最好匹配val和位置loc
58 | max_val, max_loc, img = self.getBest(img, m)
59 | if max_val == 0:
60 | return 0, 0, img
61 | return max_val, max_loc, img
62 |
63 | def getWhere(self, img, showimg=0):
64 | '''
65 |
66 | :param img: 输入图片路径
67 | :param showimg: 是否展示图片(调试时需要设置为1)
68 | :return: 返回最佳匹配点,以及图片(resize后的)或者 0 和 原图img
69 | '''
70 | # 1、保存原始未处理图像便于画图
71 | img = cv.imread(img)
72 | c_img = img.copy()
73 |
74 | # 2、或者模板并对图像进行二值化处理
75 | c_img = self.gaussianThreshold(c_img)
76 | print('进行匹配:')
77 |
78 | # 3、获取最好匹配位置
79 | max_val, max_loc, c_img = self.getWhich(c_img, self.m)
80 | if max_val == 0: # return 没有一个大于0.75的匹配
81 | print('Error')
82 | if showimg:
83 | cv.namedWindow("match", cv.WINDOW_AUTOSIZE)
84 | cv.imshow("match", img)
85 | cv.waitKey(0)
86 | cv.destroyAllWindows()
87 | return [0, img]
88 |
89 | # 4、如果二值化的图片有过拉伸处理这里对要进行画图的原图也进行同样的处理
90 | img = self.resizePic(img)
91 |
92 | # 5、获取最佳位置以及模板大小,并把最佳匹配在图中画出来
93 | th, tw = self.m.shape[:2]
94 | tl = max_loc
95 | br = (tl[0] + tw, tl[1] + th)
96 | cv.rectangle(img, tl, br, (0, 0, 255), 1)
97 |
98 | # 6、返回模板位置以及resize后的图片便于展示
99 | if showimg:
100 | cv.namedWindow("match", cv.WINDOW_AUTOSIZE)
101 | cv.imshow("match", img)
102 | cv.waitKey(0)
103 | cv.destroyAllWindows()
104 | return [tl, img]
105 |
--------------------------------------------------------------------------------
/getParts.py:
--------------------------------------------------------------------------------
1 | import cv2 as cv
2 | import numpy as np
3 |
4 |
5 | class GetParts(object):
6 | """docstring for GetParts"""
7 |
8 | def __init__(self, arg):
9 | super(GetParts, self).__init__()
10 | self.tl = arg[0]
11 | self.img = arg[1]
12 |
13 | def getUDMirror(self, ptsx, tl, loc):
14 | # 将列表中的多边形统一以一个水平轴进行对称
15 | for key in ptsx.keys():
16 | ptsx[key] = np.array(
17 | [[j[0], ((tl[1] + loc) + ((tl[1] + loc) - j[1]))] for j in ptsx[key]])
18 | return ptsx
19 |
20 | def getLRMirror(self, ptsx, tl, loc):
21 | # 将列表中的多边形统一以一个垂直轴进行对称
22 | for key in ptsx.keys():
23 | ptsx[key] = np.array(
24 | [[((tl[0] + loc) + ((tl[0] + loc) - j[0])), j[1]] for j in ptsx[key]])
25 | return ptsx
26 |
27 | def getZeroFlag(self, pts, img):
28 | # 判断这个多边形是否有任何一个顶点是在图片的像素范围之内
29 | flag = 0
30 | h = img.shape[0]
31 | w = img.shape[1]
32 | for i in pts:
33 | if i[0] > 0 and i[1] > 0 and i[0] < w and i[1] < h:
34 | flag = 1
35 | break
36 | return flag
37 |
38 | def getMove(self, pts, gap, hov):
39 | # 将列表中的多边形统一向水平或者垂直方向移动gap
40 | if hov:
41 | return np.array([[i[0], i[1] + gap] for i in pts])
42 | else:
43 | return np.array([[i[0] + gap, i[1]] for i in pts])
44 |
45 | def getAllTarget(self, ptsx, img, gap, hov, ptdic, offset=0):
46 | ''' 获取一个多边形list的一个方向(横向或者纵向)上的所有有点的拷贝
47 | --------------------------- ---------------------------
48 | | | | |
49 | | | | |
50 | | | | |
51 | | |-| | --> ||-||-||-||-||-||-||-||-| |
52 | | | | |
53 | | | | |
54 | | | | |
55 | | | | |
56 | --------------------------- ---------------------------
57 | '''
58 | # 获取ptsx原始拷贝
59 | ptss = ptsx.copy()
60 | # 画出初始图像
61 | for key in ptsx.keys():
62 | pts = ptsx[key]
63 | ptdic[key].append(pts.copy())
64 | pts = pts.reshape(-1, 1, 2)
65 | img = cv.polylines(img, [pts], True, (0, 255, 0))
66 |
67 | # 是否改变方向,0为没有1为有
68 | changeFlag = 0
69 | # 偏移修正系数
70 | if not offset:
71 | offset_num = 0
72 | else:
73 | offset_num = 1
74 | while 1:
75 | # 整体移动ptsx
76 | for key in ptsx.keys():
77 | # print(gap - offset_num // (offset + 1))
78 | ptsx[key] = self.getMove(
79 | ptsx[key], gap - offset_num // (offset + 1), hov)
80 | if offset_num > 0:
81 | offset_num += 1
82 | elif offset_num < 0:
83 | offset_num -= 1
84 | # 定义是否还有多边形存在点,0为没有1为有
85 | zeroFlag = 0
86 | # 逐个多边形进行判断并绘点
87 | for key in ptsx.keys():
88 | # 判断这个多边形是否有点
89 | if self.getZeroFlag(ptsx[key], img):
90 | pts = ptsx[key]
91 | pts = pts.reshape(-1, 1, 2)
92 | img = cv.polylines(img, [pts], True, (255, 0, 0))
93 | # 只要有一个多边形有点就可以继续移动
94 | zeroFlag = 1
95 | # 绘点结束,判断是否要进行下一次移动,如果任何多边形有点则继续移动
96 | if zeroFlag:
97 | for key in ptsx.keys():
98 | ptdic[key].append(ptsx[key].copy())
99 | continue
100 | # 所有多边形没点,并且还未改变过移动方向
101 | elif not changeFlag:
102 | # 改变移动方向,ptss回归原始拷贝
103 | changeFlag = 1
104 | if offset:
105 | offset_num = -1
106 | gap = (-1) * gap
107 | ptsx = ptss
108 | continue
109 | # 所有多边形没点并且改变过一次移动方向
110 | else:
111 | break
112 | return img
113 |
114 | def get1LR(self, ptsx, img, hgap, vgap, ptdic, offset=0):
115 | ''' 获取一个多边形list左右一定gap的上下所有多边形
116 | --------------------------- ---------------------------
117 | | | | |-| |-| |
118 | | | | |-| |-| |
119 | | | | |-| |-| |
120 | | |-| | --> | |-| hgap |-| hgap |-| |
121 | | | | |-| |-| |
122 | | | | |-| |-| |
123 | | | | |-| |-| |
124 | | | | |-| |-| |
125 | --------------------------- ---------------------------
126 | '''
127 | ptss = ptsx.copy()
128 | for key in ptsx.keys():
129 | ptsx[key] = self.getMove(ptsx[key], hgap, 0)
130 | img = self.getAllTarget(ptsx, img, vgap, 1, ptdic, offset)
131 | ptsx = ptss
132 | for key in ptsx.keys():
133 | ptsx[key] = self.getMove(ptsx[key], (-1) * hgap, 0)
134 | img = self.getAllTarget(ptsx, img, vgap, 1, ptdic, offset)
135 | return img
136 |
137 | def get1UD(self, ptsx, img, hgap, vgap, ptdic, offset=0):
138 | ''' 获取一个多边形list上下一定gap的左右所有多边形
139 | -------------------------- --------------------------
140 | | | | |
141 | | | ||-||-||-||-||-||-||-||-||
142 | | | | vgap |
143 | | |-| | --> | |-| |
144 | | | | vgap |
145 | | | ||-||-||-||-||-||-||-||-||
146 | | | | |
147 | | | | |
148 | -------------------------- --------------------------
149 | '''
150 | ptss = ptsx.copy()
151 | for key in ptsx.keys():
152 | ptsx[key] = self.getMove(ptsx[key], vgap, 1)
153 | img = self.getAllTarget(ptsx, img, hgap, 0, ptdic, offset)
154 | ptsx = ptss
155 | for key in ptsx.keys():
156 | ptsx[key] = self.getMove(ptsx[key], (-1) * vgap, 1)
157 | img = self.getAllTarget(ptsx, img, hgap, 0, ptdic, offset)
158 | return img
159 |
160 | def getTarget(self, img, tl):
161 | # 必须重写此函数
162 | ptdic = {}
163 | return img, ptdic
164 |
165 | def getAllParts(self, showimg=0, getimg=0):
166 | '''
167 | 类的调用入口
168 | :param showimg: 是否展示图片,调试时设置为1
169 | :param getimg: 是否需要返回图片
170 | :return:
171 | '''
172 | self.img, ptdic = self.getTarget(self.img, self.tl)
173 | if showimg:
174 | cv.namedWindow("match", cv.WINDOW_AUTOSIZE)
175 | cv.imshow("match", self.img)
176 | cv.waitKey(0)
177 | cv.destroyAllWindows()
178 | if not getimg:
179 | # 返回多边形dic、图片形状
180 | return ptdic
181 | else:
182 | return self.img
183 |
--------------------------------------------------------------------------------
/init.py:
--------------------------------------------------------------------------------
1 | from utils import onepictureout, onepictureparts
2 | from utils import pathout, pathparts
3 |
4 | import numpy as np
5 |
6 |
7 | def main():
8 | # 模板所在位置
9 | modelPath = './feature/feature1.jpg'
10 | # 单个图片所在位置
11 | picPath = './testp/4.jpg'
12 | # 保存零件图片位置
13 | outpath = './out/'
14 | # 图片路径
15 | testpath = './testp/'
16 | # 测试所用故障,没有图片用array也行,不用array可删了import numpy
17 | #target = np.array([[0, 0], [0, 1000], [1000, 1000], [1000, 0]])
18 | target = './testp/4.png'
19 | # 模板匹配的阈值
20 | threshold = 0.5
21 | # 一张图片获得零件位置并展示
22 | #onepictureparts(modelPath, picPath, threshold)
23 | # 一张图片获得零件位置,展示并输出结果
24 | onepictureout(modelPath, picPath, target, threshold)
25 | # 路径下的图片获得零件位置并展示
26 | #pathparts(testpath, modelPath, threshold)
27 | # 路径下的图片获得零件位置保存结果,不展示
28 | #pathparts(testpath, modelPath, threshold, writepath=outpath)
29 | # 路径下的图片获得最终结果不展示
30 | #pathout(testpath, modelPath, target, threshold)
31 |
32 |
33 | if __name__ == '__main__':
34 | main()
35 |
--------------------------------------------------------------------------------
/out/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/out/1.jpg
--------------------------------------------------------------------------------
/out/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/out/2.jpg
--------------------------------------------------------------------------------
/out/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/out/3.jpg
--------------------------------------------------------------------------------
/testp/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/testp/1.jpg
--------------------------------------------------------------------------------
/testp/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/testp/2.jpg
--------------------------------------------------------------------------------
/testp/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/testp/3.jpg
--------------------------------------------------------------------------------
/testp/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/testp/4.jpg
--------------------------------------------------------------------------------
/testp/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liqb-a/templateMatchForImage/8d8293f2756cf1cb00cdfe0574de008a61226e6d/testp/4.png
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import cv2 as cv
2 | import numpy as np
3 | import os
4 | import datetime
5 | from getBestMatching import GetBestMatching
6 | from getParts import GetParts
7 | from defectComparison import DefectComparison
8 |
9 |
10 | def getJPG(path, li=0):
11 | # 返回一个文件夹下所有jpg文件名,li==1代表将地址和文件名拆开返回
12 | list_name = []
13 | for file in os.listdir(path):
14 | file_path = os.path.join(path, file)
15 | if file[-3:].lower() == 'jpg' and not os.path.isdir(file_path):
16 | if not li:
17 | list_name.append(file_path)
18 | else:
19 | list_name.append([path, file])
20 | if os.path.isdir(file_path):
21 | list_name += getJPG(file_path, li)
22 | return list_name
23 |
24 |
25 | class GetBestMatchinger(GetBestMatching):
26 | """
27 | docstring for GetBestMatchinger
28 | 这是一个用于获取模板所对应的最佳位置的类
29 | 需要重写的函数有resizePic:用于多种分辨率下的图形变化使图片一致
30 | 如果不需要变换删掉底下的重写就行,原本类是默认不变的
31 | 这里的重写适用于零件5300
32 | """
33 |
34 | def __init__(self, modelPath, threshold):
35 | super(GetBestMatchinger, self).__init__(modelPath, threshold)
36 |
37 | def resizePic(self, img):
38 | ''' resize图片和返回最大匹配
39 | 如果需要改变图片分辨率需要继承并重写此函数,如:
40 | '''
41 | if img.shape[:2] == (432, 576):
42 | img = cv.resize(img, (558, 421), cv.INTER_CUBIC)
43 | elif img.shape[:2] == (768, 1024):
44 | img = np.rot90(img)
45 | img = np.rot90(img)
46 | img = np.rot90(img)
47 | img = cv.resize(img, (634, 846), cv.INTER_CUBIC)
48 | return img
49 |
50 |
51 | class GetPartser(GetParts):
52 | """
53 | docstring for GetPartser
54 | 这是一个用于获得每一个零件位置的类
55 | 需要修改的只有getTarget:用于描点的函数
56 | 具体定义请看下方,在这里重写,这里原本的适用于5300
57 | """
58 |
59 | def __init__(self, arg):
60 | super(GetPartser, self).__init__(arg)
61 |
62 | def getTarget(self, img, tl):
63 | '''
64 | 此函数一共分为三个功能:
65 | 1、定义piex长宽
66 | 1、从模板匹配的最优点找到piex的起始点
67 | 2、标记各个零件的坐标画多边形
68 | 3、从一组零件变化画出其他所有零件
69 | '''
70 |
71 | '''
72 | 1、ptsx 为所画组零件的list
73 | ptdic 用于放所有的零件
74 | hgap 为piex的左右宽度
75 | vgap 为piex的上下高度
76 | offset为是否进行偏移回归,0为不回归
77 | 否则为每 (offset+1) 个piex回归1个像素
78 | '''
79 | ptsx = {}
80 | ptdic = {}
81 | hgap = 454
82 | vgap = 152
83 | offset = 0
84 |
85 | # 2、从模板匹配的最优点找到piex的起始点
86 | tl = [tl[0] + 37, tl[1] + 23]
87 |
88 | # 3、标记各个零件的坐标画多边形
89 | # M1-1
90 | ptdic['M1-1'] = []
91 | ptsx['M1-1'] = np.array([[tl[0] + 19, tl[1] + 174], [tl[0] + 19, tl[1] + 140],
92 | [tl[0] + 27, tl[1] + 131], [tl[0] + 40, tl[1] + 131],
93 | [tl[0] + 47, tl[1] + 140], [tl[0] + 47, tl[1] + 154],
94 | [tl[0] + 458, tl[1] +
95 | 154], [tl[0] + 458, tl[1] + 164],
96 | [tl[0] + 47, tl[1] + 164], [tl[0] + 47, tl[1] + 174]])
97 |
98 | # M1-2
99 | ptdic['M1-2'] = []
100 | ptsx['M1-2'] = np.array([[tl[0] - 23, tl[1] + 78], [tl[0] - 23, tl[1] + 85],
101 | [tl[0] + 36, tl[1] + 85], [tl[0] + 36, tl[1] + 78]])
102 |
103 | # M1-3
104 | ptdic['M1-3'] = []
105 | ptsx['M1-3'] = np.array([[tl[0] - 3, tl[1] + 110], [tl[0] - 3, tl[1] + 117],
106 | [tl[0] + 19, tl[1] + 117], [tl[0] + 19, tl[1] + 110]])
107 |
108 | # M2-1
109 | ptdic['M2-1'] = []
110 | ptsx['M2-1'] = np.array([[tl[0] + 28, tl[1] + 137], [tl[0] + 23, tl[1] + 143],
111 | [tl[0] + 22, tl[1] + 138], [tl[0] + 15, tl[1] + 138],
112 | [tl[0] + 15, tl[1] + 153], [tl[0] + 1, tl[1] + 153],
113 | [tl[0] + 1, tl[1] + 17], [tl[0] + 15, tl[1] + 17],
114 | [tl[0] + 15, tl[1] + 130], [tl[0] + 24, tl[1] + 130]])
115 |
116 | # M2-2
117 | ptdic['M2-2'] = []
118 | ptsx['M2-2'] = np.array([[tl[0] + 23, tl[1] + 167], [tl[0] + 28, tl[1] + 167],
119 | [tl[0] + 28, tl[1] + 144], [tl[0] + 31, tl[1] + 141],
120 | [tl[0] + 34, tl[1] + 141], [tl[0] + 38, tl[1] + 144],
121 | [tl[0] + 38, tl[1] + 167], [tl[0] + 43, tl[1] + 167],
122 | [tl[0] + 43, tl[1] + 143], [tl[0] + 38, tl[1] + 137],
123 | [tl[0] + 28, tl[1] + 137], [tl[0] + 23, tl[1] + 143]])
124 |
125 | # M2-3
126 | ptdic['M2-3'] = []
127 | ptsx['M2-3'] = np.array([[tl[0] + 31, tl[1] + 144], [tl[0] + 35, tl[1] + 144],
128 | [tl[0] + 35, tl[1] + 179], [tl[0] + 39, tl[1] + 179],
129 | [tl[0] + 39, tl[1] + 193], [tl[0] + 24, tl[1] + 193],
130 | [tl[0] + 24, tl[1] + 179], [tl[0] + 31, tl[1] + 179]])
131 |
132 | # Main
133 | ptdic['Main'] = []
134 | ptsx['Main'] = np.array([[tl[0], tl[1]], [tl[0], tl[1] + 152],
135 | [tl[0] + 454, tl[1] + 152], [tl[0] + 454, tl[1]]])
136 |
137 | # add
138 | ptdic['add'] = []
139 | ptsx['add'] = np.array([[tl[0] - 29, tl[1] + 83], [tl[0] - 29, tl[1] + 155],
140 | [tl[0] + 53, tl[1] + 155], [tl[0] + 53, tl[1] + 83]])
141 |
142 | # 4、从一组零件变化画出其他所有零件
143 | ptss = ptsx.copy()
144 | img = self.getAllTarget(ptsx, img, hgap, 0, ptdic, offset)
145 |
146 | ptsx = ptss.copy()
147 | img = self.get1UD(ptsx, img, hgap, 2 * vgap, ptdic, offset)
148 |
149 | ptsx = ptss.copy()
150 | img = self.get1UD(ptsx, img, hgap, 4 * vgap, ptdic, offset)
151 |
152 | ptsx = ptss.copy()
153 | img = self.get1UD(ptsx, img, hgap, 6 * vgap, ptdic, offset)
154 |
155 | ptsx = ptss.copy()
156 | img = self.get1UD(ptsx, img, hgap, 8 * vgap, ptdic, offset)
157 |
158 | ptsx = self.getLRMirror(ptss, tl, 234)
159 | ptss = ptsx.copy()
160 |
161 | img = self.get1UD(ptsx, img, hgap, vgap, ptdic, offset)
162 |
163 | ptsx = ptss.copy()
164 | img = self.get1UD(ptsx, img, hgap, 3 * vgap, ptdic, offset)
165 |
166 | ptsx = ptss.copy()
167 | img = self.get1UD(ptsx, img, hgap, 5 * vgap, ptdic, offset)
168 |
169 | ptsx = ptss.copy()
170 | img = self.get1UD(ptsx, img, hgap, 7 * vgap, ptdic, offset)
171 |
172 | return img, ptdic
173 |
174 |
175 | class DefectComparisoner(DefectComparison):
176 | """
177 | docstring for DefectComparisoner:
178 | 这是一个用于故障对比的类
179 | 这个类可能需要修改的部分是:
180 | getReturn:不同的零件要求输出结果不同
181 | getQOut:其中的M1,M2是list需要改变
182 | 可以直接在这里重写这两个函数就可以直接使用,
183 | 这里原本的这两个函数适用于零件5300
184 | """
185 |
186 | def __init__(self, ptdic, shape, picpath, resizedef):
187 | super(DefectComparisoner, self).__init__(
188 | ptdic, shape, picpath, resizedef)
189 |
190 |
191 | def onepictureparts(modelPath, picPath, threshold):
192 | # 获取一张图片的所有零件匹配情况并画图
193 | start = datetime.datetime.now()
194 | matcher = GetBestMatchinger(modelPath, threshold)
195 | res = matcher.getWhere(picPath, showimg=0)
196 | end = datetime.datetime.now()
197 | print(' 得到最佳匹配所花时间%fs:' %
198 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
199 | if res[0] == 0:
200 | print('GG')
201 | return
202 | getter = GetPartser(res)
203 | getter.getAllParts(showimg=1)
204 |
205 |
206 | def onepictureout(modelPath, picPath, target, threshold):
207 | # 获取一张图片的所有零件匹配情况并画图,并与预制的故障图片或者np多边形进行比对输出结果
208 | start = datetime.datetime.now()
209 | matcher = GetBestMatchinger(modelPath, threshold)
210 | res = matcher.getWhere(picPath, showimg=0)
211 | end = datetime.datetime.now()
212 | print(' 得到最佳匹配所花时间%fs:' %
213 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
214 | if res[0] == 0:
215 | print('GG')
216 | return
217 | getter = GetPartser(res)
218 | ptdic = getter.getAllParts(showimg=1)
219 | start = datetime.datetime.now()
220 | if isinstance(target, str):
221 | comparisoner = DefectComparisoner(
222 | ptdic, res[1].shape, target, matcher.resizePic)
223 | output = comparisoner.getQOut()
224 | elif isinstance(target, np.ndarray):
225 | comparisoner = DefectComparisoner(
226 | ptdic, res[1].shape, '', matcher.resizePic)
227 | output = comparisoner.getQOut(target)
228 | end = datetime.datetime.now()
229 | print(' 进行故障比对所花时间%fs:' %
230 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
231 | print(output)
232 |
233 |
234 | def pathparts(path, modelPath, threshold, writepath=0):
235 | # 获取目标位置下的所有图片的所有零件匹配情况并画图,可选择只是看或者还是输出
236 | start_ = datetime.datetime.now()
237 | s = 0
238 | for i in getJPG(path, li=1):
239 | s += 1
240 | start = datetime.datetime.now()
241 | matcher = GetBestMatchinger(modelPath, threshold)
242 | res = matcher.getWhere(os.path.join(i[0], i[1]), showimg=0)
243 | end = datetime.datetime.now()
244 | print(' 得到最佳匹配所花时间%fs:' %
245 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
246 | if res[0] == 0:
247 | print('GG')
248 | continue
249 | start = datetime.datetime.now()
250 | getter = GetPartser(res)
251 | if not writepath:
252 | getter.getAllParts(showimg=1)
253 | end = datetime.datetime.now()
254 | else:
255 | img = getter.getAllParts(showimg=0, getimg=1)
256 | end = datetime.datetime.now()
257 | print(' 获取所有零件位置所花时间%fs:' %
258 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
259 | name = '%s%s' % (writepath, i[1])
260 | cv.imwrite(name, img)
261 | end_ = datetime.datetime.now()
262 | if writepath:
263 | print(' 平均获取每件零件位置所花时间%fs:' %
264 | (((end_ - start_).seconds + (((end_ - start_).microseconds) / 1e6)) / s))
265 |
266 |
267 | def pathout(path, modelPath, target, threshold):
268 | # 获取目标位置下的所有图片的所有零件匹配情况并画图,并与预制的故障图片或者np多边形进行比对输出结果
269 | start_ = datetime.datetime.now()
270 | s = 0
271 | for i in getJPG(path):
272 | s += 1
273 | start = datetime.datetime.now()
274 | matcher = GetBestMatchinger(modelPath, threshold)
275 | res = matcher.getWhere(i, showimg=0)
276 | end = datetime.datetime.now()
277 | print(' 得到最佳匹配所花时间%fs:' %
278 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
279 | if res[0] == 0:
280 | print('GG')
281 | continue
282 | start = datetime.datetime.now()
283 | getter = GetPartser(res)
284 | ptdic = getter.getAllParts(showimg=0)
285 | end = datetime.datetime.now()
286 | print(' 获取所有零件位置所花时间%fs:' %
287 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
288 | start = datetime.datetime.now()
289 | if isinstance(target, str):
290 | comparisoner = DefectComparisoner(
291 | ptdic, res[1].shape, target, matcher.resizePic)
292 | output = comparisoner.getQOut()
293 | elif isinstance(target, np.ndarray):
294 | comparisoner = DefectComparisoner(
295 | ptdic, res[1].shape, '', matcher.resizePic)
296 | output = comparisoner.getQOut(target)
297 | end = datetime.datetime.now()
298 | print(' 进行故障比对所花时间%fs:' %
299 | ((end - start).seconds + (((end - start).microseconds) / 1e6)))
300 | print(output)
301 | end_ = datetime.datetime.now()
302 | print(' 平均获取每件零件位置并进行故障对比所花时间%fs:' %
303 | (((end_ - start_).seconds + (((end_ - start_).microseconds) / 1e6)) / s))
304 |
--------------------------------------------------------------------------------