├── 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 | --------------------------------------------------------------------------------