一个好的深度学习算法工程师,从来就是和opencv、pilow、Matplotlib一起混合用,由于这个代码不是我写的,我就不上传代码了。
这里我们使用python版opnecv,c++版本的基础算法也差不多。
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
ret, dst = cv2.threshold(src, thresh, maxval, type)
src: 输入图,只能输入单通道图像,通常来说为灰度图
thresh:一般取127和255
maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
type:二值化操作的类型,包含以下5种类型:
1.cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
2.cv2.THRESH_BINARY_INV THRESH_BINARY的反转
3.cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
4.cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
5.cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
常用:
THRESH_BINARY_INV
自动找到阙值:
cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
import cv2 import matplotlib.pyplot as plt img = cv2.imread('cat.jpg') img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret,img_bi = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY) ret,img_bi_inv = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY_INV) ret,img_tr = cv2.threshold(img_gray,127,255,cv2.THRESH_TRUNC) ret,img_zero = cv2.threshold(img_gray,127,255,cv2.THRESH_TOZERO) ret,img_zero_inv = cv2.threshold(img_gray,127,255,cv2.THRESH_TOZERO_INV) titles = ['Original','Binary','Binary_INV','TRUNC','ZERO','ZERO_INV'] images = [img,img_bi,img_bi_inv,img_tr,img_zero,img_zero_inv] for i in range(6): plt.subplot(2,3,i+1),plt.imshow(images[i],'gray'),plt.title(titles[i]) plt.xticks([]),plt.yticks([]) # 不显示坐标轴 plt.show(
contours,hierarchy=cv2.findContours(image, mode, method[, offset])
image:8位单通道图像。视为二值图像
contours:轮廓点。列表格式,每一个元素为一个3维数组(其形状为(n,1,2),其中n表示轮廓点个数,2表示像素点坐标),表示一个轮廓
hierarchy:轮廓间的层次关系,为三维数组,形状为(1,n,4),其中n表示轮廓总个数,4指的是用4个数表示各轮廓间的相互关系。第一个数表示同级轮廓的下一个轮廓编号,第二个数表示同级轮廓的上一个轮廓的编号,第三个数表示该轮廓下一级轮廓的编号,第四个数表示该轮廓的上一级轮廓的编号
mode:轮廓检索的方式
1.cv2.RETR_EXTERNAL:只检索外部轮廓
2.cv2.RETR_LIST: 检测所有轮廓且不建立层次结构
3.cv2.RETR_CCOMP: 检测所有轮廓,建立两级层次结构
4.cv2.RETR_TREE: 检测所有轮廓,建立完整的层次结构
method:轮廓近似的方法
1.cv2.CHAIN_APPROX_NONE:存储所有的轮廓点
2.cv2.CHAIN_APPROX_SIMPLE:压缩水平,垂直和对角线段,只留下端点。
常用:
cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
腐蚀与膨胀属于形态学操作,所谓的形态学,就是改变物体的形状,形象理解一些:腐蚀=变瘦(白色部分) 膨胀=变胖(白色部分)
主要是采用 cv2.erode() 和 cv2.dilate()
主要针对二值化图像的白色部分
腐蚀:是一种消除边界点,使边界向内部收缩的过程.到亮区(背景)变细,而黑色区域(字母)则变大了。
通俗讲法:在原图的每一个小区域里取最小值,由于是二值化图像,只要有一个点为0,则都为0,来达到瘦身的目的
算法:用 3x3 的 kernel,扫描图像的每一个像素;用 kernel 与其覆盖的二值图像做 “与” 操作;若都为1,则图像的该像素为1;否则为0. 最终结果:使二值图像减小一圈.
膨胀:是将与物体接触的所有背景点合并到该物体中,使边界向外部扩张的过程,可以用来填补物体中的空洞。背景(白色)膨胀,而黑色字母缩小了。
算法:用 3x3 的 kernel,扫描图像的每一个像素;用 kernel 与其覆盖的二值图像做 “与” 操作;若都为0,则图像的该像素为0;否则为1. 最终结果:使二值图像扩大一圈
1.先腐蚀后膨胀的过程称为 开运算。用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积
【cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)】
2.先膨胀后腐蚀的过程称为 闭运算。用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积.
【cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)】
3.膨胀 减去 腐蚀的过程称为 梯度运算。用来计算轮廓【cv2.morphologyEx(pie,cv2.MORPH_GRADIENT,kernel)】
4.顶帽:原始输入 减去 开运算结果 【cv2.morphologyEx(img,cv.MORPH_TOPHAT,kernel)】
5.黑帽:闭运算 减去 原始输入 【cv2.morphologyEx(img,cv.MORPH_BLACKHAT,kernel)】
cv2.Sobel(src, ddepth, dx, dy, ksize) 进行sobel算子计算
src表示当前图片,
ddepth表示图片深度,这里使用cv2.CV_64F使得结果可以是负值,
dx表示x轴方向,
dy表示y轴方向, ksize表示移动方框的大小
cv2.convertScalerAbs(src) 将像素点进行绝对值计算
src表示当前图片
import cv2 import matplotlib.pyplot as plt img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE) sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)# 1,0: 表示只算水平的,不算竖直的 sobelxx = cv2.convertScaleAbs(sobelx)# sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) sobelyy = cv2.convertScaleAbs(sobely) # 分别计算x和y,再求和,融合的较好 sobelxy_1 = cv2.addWeighted(sobelxx,0.5,sobelyy,0.5,0) # 不建议直接计算,融合的不好 sobelxy_2 = cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3) # cv_show('pie',img) cv2.imshow('sobelx',sobelx) cv2.imshow('sobelxx',sobelxx) cv2.imshow('sobely',sobely) cv2.imshow('sobelyy',sobelyy) cv2.imshow('sobelxy_1',sobelxy_1) cv2.imshow('sobelxy_2',sobelxy_2) cv2.waitKey(0) cv2.destroyAllWindows()
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后格每次计算的結果放入一个矩阵里,作为結果输出.假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
简单来说,模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域。
cv2.matchTemplate(image, templ, method, result=None, mask=None)
image:待搜索图像
templ:模板图像
method:计算匹配程度的方法
返回参数res:是一个结果矩阵,假设待匹配图像为 I,宽高为(W,H),模板图像为 T,宽高为(w,h)。那么result的大小就为(W-w+1, H-h+1)
其中method:
TM-SQDIFF:计算平方不同,计算出来的值越小,越相关
TM_CCORR:计算相关性,计算出来的值越大,越相关
TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
TM SQDIFF-NORMED: 计算归一化平方不同,计算出来的值越接近0,越相关
TM_CCORR-NORMED: 计t算归一化相关性,计算出来的值越接近1,越相关
TM-CCOEFF-NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
准备好识别模版,对它进行opencv操作,拆分为单个图0,1,2,3,4,5…并按从左到右排序。
对银行卡部分进行opencv的一些列操作,选取银行卡卡号的部分,进行拆分,并排序。
利用模版匹配,计算得分最高的,银行卡号进行识别。
ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", default='./image/credit_card_01.png',required=True, help="path to input image") ap.add_argument("-t", "--template",default='./image/ocr_a_reference.png', required=True, help="path to template OCR-A image") args = vars(ap.parse_args()) # 指定信用卡类型 FIRST_NUMBER = { "3": "American Express", "4": "Visa", "5": "MasterCard", "6": "Discover Card" } # 绘图展示 def cv_show(name,img): cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows() print(args["template"])
img=cv2.imread(args["template"]) cv_show("tem",img) #灰度图 ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) cv_show("GRAY",ref) #二值化 ref=cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1] cv_show("GRAY1",ref) refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # cv版本大于3.8的,只有两个返回值 cv2.drawContours(img,refCnts,-1,(0,0,255),3) cv_show("GRAY1",img) refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] digits = {} # 存模板的单个数字 # 2.遍历每一个轮廓,外接矩形 for (i, c) in enumerate(refCnts): # 计算外接矩形并且resize成合适大小 (x, y, w, h) = cv2.boundingRect(c) # 3.抠出模板 roi = ref[y:y + h, x:x + w] # 每个roi对应一个数字 roi = cv2.resize(roi, (57, 88)) # 太小,调大点 cv_show("waijie",roi) # 4.每一个数字对应每一个模板 digits[i] = roi
代码拆分:
img=cv2.imread(args["template"]) cv_show("tem",img)
#灰度图 ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) cv_show("GRAY",ref)
#二值化 ref=cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1] cv_show("GRAY1",ref) 123
refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # cv版本大于3.8的,只有两个返回值 cv2.drawContours(img,refCnts,-1,(0,0,255),3) cv_show("GRAY1",img) 12345
for (i, c) in enumerate(refCnts): # 计算外接矩形并且resize成合适大小 (x, y, w, h) = cv2.boundingRect(c) # 3.抠出模板 roi = ref[y:y + h, x:x + w] # 每个roi对应一个数字 roi = cv2.resize(roi, (57, 88)) # 太小,调大点 cv_show("waijie",roi) # 4.每一个数字对应每一个模板 digits[i] = roi 123456789
#初始化卷积核的大小 ################################################# # kernel = cv2.getStructuringElement(shape, ksize, anchor) # shape: 核的形状 # cv2.MORPH_RECT: 矩形 # cv2.MORPH_CROSS: 十字形(以矩形的锚点为中心的十字架) # cv2.MORPH_ELLIPSE: 椭圆(矩形的内切椭圆) # # ksize: 核的大小,矩形的宽,高格式为(width, height) # anchor: 核的锚点,默认值为(-1, -1), 即核的中心点 ################################################# #卷积核的大小根据项目设定 rectKernel =cv2.getStructuringElement(cv2.MORPH_RECT,(9,3)) sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 2.读取输入图像,预处理 image = cv2.imread(args["image"]) cv_show('Input_img',image) image = myutils.resize(image, width=300) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cv_show('Input_gray',gray) # 3.礼帽操作,突出更明亮的区域 # 形态学操作,礼帽+闭操作可以突出明亮区域,但并不是非得礼帽+闭操作 tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) cv_show('tophat',tophat) # 4.x方向的Sobel算子,实验表明,加y的效果的并不好 gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,ksize=-1) #ksize=-1相当于用3*3的 # sobelyy = cv2.convertScaleAbs(gradX) # cv_show('gradXt',sobelyy) # x方向取绝对值 -> 归一化 # 绝对值+归一化 # 归一化 x’ = (x-min)/(max-min gradX = np.absolute(gradX) # absolute: 计算绝对值 (minVal, maxVal) = (np.min(gradX), np.max(gradX)) gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) gradX = gradX.astype("uint8") print (np.array(gradX).shape) cv_show('Input_Sobel_gradX',gradX) #通过闭操作(先膨胀,再腐蚀)将数字连在一起.变亮 gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) cv_show('Input_CLOSE_gradX',gradX) # THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0 thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('Input_thresh',thresh) #再来一个闭操作,填补空洞 #两段代码雷同 # onekernel = np.ones((9,9), np.uint8) # thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,onekernel) thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) # 填补空洞 cv_show('Input_thresh_CLOSE',thresh) #计算轮廓 threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 1.遍历轮廓 locs = [] # 存符合条件的轮廓 for i,c in enumerate(threshCnts): # 计算矩形 x,y,w,h = cv2.boundingRect(c) ar = w / float(h) # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组 if ar > 2.5 and ar < 4.0: if (w > 40 and w < 55) and (h > 10 and h < 20): #符合的留下来 locs.append((x, y, w, h)) # 将符合的轮廓从左到右排序 #x:x[]字母可以随意修改,排序方式按照中括号[]里面的维度,[0]按照第一维,[1]按照第二维。 locs = sorted(locs,key=lambda x:x[0])
分解代码:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cv_show('Input_gray',gray)
# 3.礼帽操作,突出更明亮的区域 # 形态学操作,礼帽+闭操作可以突出明亮区域,但并不是非得礼帽+闭操作 tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) cv_show('tophat',tophat)
# 4.x方向的Sobel算子,实验表明,加y的效果的并不好 gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0,ksize=-1) #ksize=-1相当于用3*3的
# x方向取绝对值 -> 归一化 # 绝对值+归一化 # 归一化 x’ = (x-min)/(max-min gradX = np.absolute(gradX) # absolute: 计算绝对值 (minVal, maxVal) = (np.min(gradX), np.max(gradX)) gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) gradX = gradX.astype("uint8")
#通过闭操作(先膨胀,再腐蚀)将数字连在一起.变亮 gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) cv_show('Input_CLOSE_gradX',gradX)
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0 thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('Input_thresh',thresh)
遍历每一个轮廓中的数字 output = [] # 存正确的数字 for (i,(gx,gy,gw,gh)) in enumerate(locs): groupOutput = [] # 根据坐标提取每一个组(4个值) group = gray[gy-5:gy+gh+5, gx-5:gx+gw+5] # 往外扩一点 #cv_show('group_' + str(i), group) # 二值化预处理 group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 二值化的group #cv_show('group2_' + str(i), group) # 把每个轮廓分成小轮廓 digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #排序 digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0] for c in digitCnts: z = 0 (x, y, w, h) = cv2.boundingRect(c) # 外接矩形 roi = group[y:y + h, x:x + w] # 在原图中取出小轮廓覆盖区域,即数字 roi = cv2.resize(roi, (57, 88)) #cv_show('roi' , roi ) scores = [] #进行遍历模版匹配 for (digit, digitROI) in digits.items(): # 进行模板匹配, res是结果矩阵 res = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF) # 此时roi是X digitROI是0 依次是1,2.. 匹配10次,看模板最高得分多少 Max_score = cv2.minMaxLoc(res)[1] # 返回4个,取第二个最大值Maxscore scores.append(Max_score) # 10个最大值 print("scores:", scores) groupOutput.append(str(np.argmax(scores))) z = z + 1 # 2画出来 cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1) # 左上角,右下角 # putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细 cv2.putText(image, "".join(groupOutput), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2) # 得到结果 output.extend(groupOutput) print("groupOutput:", groupOutput) cv_show("result",image)
遍历每一个轮廓中的数字 output = [] # 存正确的数字 for (i,(gx,gy,gw,gh)) in enumerate(locs): groupOutput = [] # 根据坐标提取每一个组(4个值) group = gray[gy-5:gy+gh+5, gx-5:gx+gw+5] # 往外扩一点 #cv_show('group_' + str(i), group) # 二值化预处理 group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 二值化的group #cv_show('group2_' + str(i), group) # 把每个轮廓分成小轮廓 digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #排序 digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0] for c in digitCnts: z = 0 (x, y, w, h) = cv2.boundingRect(c) # 外接矩形 roi = group[y:y + h, x:x + w] # 在原图中取出小轮廓覆盖区域,即数字 roi = cv2.resize(roi, (57, 88)) #cv_show('roi' , roi ) scores = [] #进行遍历模版匹配 for (digit, digitROI) in digits.items(): # 进行模板匹配, res是结果矩阵 res = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF) # 此时roi是X digitROI是0 依次是1,2.. 匹配10次,看模板最高得分多少 Max_score = cv2.minMaxLoc(res)[1] # 返回4个,取第二个最大值Maxscore scores.append(Max_score) # 10个最大值 print("scores:", scores) groupOutput.append(str(np.argmax(scores))) z = z + 1 # 2画出来 cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 1) # 左上角,右下角 # putText参数:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细 cv2.putText(image, "".join(groupOutput), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2) # 得到结果 output.extend(groupOutput) print("groupOutput:", groupOutput) cv_show("result",image)
你学会了吗?如需获取系列教程加群:1136192749