本来我以为会很简单的,然后实际做发现对我来说还是有点问题,我最初只是想着使用透视变换对不同角度拍照的纸牌首先进行变化,然后直接使用pytesseract库就行了,然后实际操作中发现并不能直接进行OCR变化,没有办法,最后使用模板匹配的方法进行,这次练习最大的收获是发现实操跟看视频差别很大。。。
最后附代码和图片的下载方式
先对纸牌进行规范命名:例如K-4,名字+花色,其中 1为红桃 2为方块 3 为梅花 4为黑桃,例图如下:
然后进行图片预处理,形态学操作,首先把图片经过透视变换转换为规整图像,如下图:
然后我把图片的中间部分给扣了出来填补为白色,同时只处理图片的上半部分,因为卡牌的左上角跟右下角有相同的花色和卡牌数字,只处理上半部分更方便些:
然后转换为灰度图,进行二值化处理,因为有的数字特征不太明显,可以进行写形态学操作,我这里使用了膨胀操作,迭代次数为5开始查找轮廓特征,分别框选出数字特征和花色特征,找到后进行规范命名并保存。我筛选的方式是通过轮廓矩形的面积:
通过这种方法进行13次对13张卡牌(剔除大小鬼牌了)分别操作,得到了卡牌与花色的图片,从1-17进行命名,方便生成模板:
然后通过Make_Template.py文件对每个小模板进行resize成相同大小,并且拼接为新模版,保存文件:
到这里,模板生成操作就结束了
首先查找模板特征,得到下面的结果:
然后,先按照面积进行排序,并且取前17个特征,这时候的特征是外面的方块,然后再次按照从左到右的顺序进行排序,这个时候排序的依据按照x的大小进行排序,保证特征的顺序正好从左到右是A到黑桃。
然后,遍历把上面的结果的每个特征保存起来,然后对于数字和花色分别进行模板匹配就行了,最后得到输出结果。
测试的输入图象:
测试的输出结果:
代码写的很烂。。。因为把一个main函数完成了很多功能,许多变量都重复使用了
main.py
import numpy as np
import cv2
import argparse
import pytesseract
import os
from PIL import Image
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image",required=True,
help="Path to image to be scanned")
ap.add_argument("-t", "--template", required=True,
help="path to template OCR-A image")
args = vars(ap.parse_args())
def cv_show(name,file):
cv2.imshow(name, file)
cv2.waitKey(0)
cv2.destroyAllWindows()
def sort_contours(cnts, method="left-to-right"):
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i], reverse=reverse))
return cnts, boundingBoxes
def resize(image,width=None,height = None,inter = cv2.INTER_AREA):
dim = None
(h,w) = image.shape[:2]
if width==None and height ==None:
return image
if width==None:
r = height / float(h)
dim = (int(w * r),height)
else:
r = width / float(w)
dim = (w,int(h * r))
resizes = cv2.resize(image,dim,interpolation=inter)
return resizes
def order_points(pts):
rect = np.zeros((4,2),dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts,axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
def four_point_transform(image,pts):
rect = order_points(pts)
(tl,tr,bl,br) = rect
# 计算输入的w和h的值
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] -tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2 ))
maxWidth = max(int(widthA),int(widthB))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# 变换后对应坐标位置
dst = np.array([
[0,0],
[maxWidth - 1,0],
[maxWidth - 1,maxHeight - 1],
[0,maxHeight - 1]],dtype = "float32")
M = cv2.getPerspectiveTransform(rect,dst)
warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))
return warped
image = cv2.imread(args["image"])
#cv_show("image",image)
ratio = image.shape[0] / 500
orig = image.copy()
image = resize(image.copy(),height=500)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(gray,75,200)
#cv_show("edged",edged)
# 查找轮廓,并且去除扑克牌中间的轮廓,只保留花色和数字
cnts = cv2.findContours(edged.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1]
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:10]
draw_img = image.copy()
for c in cnts:
# dra = cv2.drawContours(draw_img,c,-1,(0,0,255),2)
# cv_show("res",dra)
peri = cv2.arcLength(c,True)
approx = cv2.approxPolyDP(c,0.02*peri,True)
if len(approx) ==4:
screenCnt = approx
print(np.array(screenCnt.size))
print(np.array(screenCnt))
break
# 展示结果
#cv2.drawContours(image,[screenCnt],-1,(0,0,255),2)
#cv_show("outline",image)
wraped = four_point_transform(orig,screenCnt.reshape(4,2) * ratio)
#cv_show("wraped",wraped)
wraped = resize(wraped,height=500)
wraped = cv2.medianBlur(wraped,3)
cv_show("gray",gray)
#cv_show("img",wraped)
#gray = cv2.cvtColor(wraped,cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(gray,100,255,cv2.THRESH_BINARY)[1]
edged = cv2.Canny(ref,75,200)
#cv_show("ref",edged)
cnts = cv2.findContours(edged,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1]
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:10]
draw_img = wraped.copy()
peri = cv2.arcLength(cnts[0],True)
approx = cv2.approxPolyDP(cnts[0],0.02 * peri,True)
#cv2.drawContours(draw_img,[approx],-1,(0,0,255),2)
print("---------------------")
#print([approx])
#cv_show("Outline",draw_img)
print(draw_img.shape[:2])
print(approx) # 根据这个输出的结果确定mask
mask_img = draw_img
mask_img[96:412,64:218] = 255 # 这个范围是根据approx的结果得出来的大概值,把卡牌中间花的部位变为白色
mask_img = mask_img[:int(mask_img.shape[0]/2),:]
cv_show("1",mask_img)
mask_img = cv2.cvtColor(mask_img,cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
mask_img = clahe.apply(mask_img)
ref = cv2.threshold(mask_img,70,255,cv2.THRESH_BINARY)[1] # 大多数牌为75的时候就行,但是有些需要阈值设为100,或者125等。并且有的时候75 与 70得到的结果非常不相同
ref = cv2.medianBlur(ref,3)
cv_show("ref",ref)
#ref = cv2.Canny(ref,55,200)
kernel = np.ones((3,3),np.uint8)
ero = cv2.erode(ref,kernel,iterations = 4)
cv_show("ref",ero)
cnts = cv2.findContours(ero,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1]
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:5]
#cv2.drawContours(draw_img,cnts,-1,(0,0,255),2)
#cv_show("the page",draw_img)
draw_img = ref.copy()
save_img1 = []
save_img2 = []
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
area = w * h # 通过面积来寻找要求的轮廓 5917左右为数字 3366左右为花色信息
# if ar > 0.56 and ar < 0.63:
draw_img = cv2.rectangle(draw_img, (x, y), (x + w, y + h), (0, 255, 0), 3)
cv_show("draw_img", draw_img)
if area > 5600 and area < 7000:
draw_img = cv2.rectangle(draw_img,(x,y),(x+w,y+h),(0,255,0),2)
cv_show("draw_img",draw_img)
#print("draw_img",draw_img[y:y+h,x:x+w])
save_img1 =draw_img[y:y+h,x:x+w]
#cv2.imwrite("2.png",save_img1)
if area > 2700 and area < 4600:
draw_img = cv2.rectangle(draw_img, (x, y), (x + w, y + h), (0, 255, 0), 1)
cv_show("draw_img", draw_img)
save_img2 =draw_img[y:y+h,x:x+w]
#cv2.imwrite("2.png",save_img2)
'''
下面的代码是开始进行模板匹配了,上面的代码如果用来生成模板的话,取消cv2.imwrite的注释即可,然后注释掉下面的代码
模板匹配时,现在通过上面的代码已经得到了当前要检测的特征存放在save_img1中数字特征,save_img2中花色特征
'''
#cv_show("data",save_img1)
template = cv2.imread(args["template"])
ref = cv2.cvtColor(template,cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY)[1]
cv_show("ref",ref)
cnts = cv2.findContours(ref.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1]
cnts = sorted(cnts,key=cv2.contourArea,reverse=1)[:17] # 提取面积最大的17个特征,就是外框特征,剔除具体的数字和花色特征
cnts = sort_contours(cnts, method="left-to-right")[0]#排序,从左到右,从上到下 这次排序主要是看距离最左边点的距离来排序,这样就可以找出每个框的特征是什么了
cv2.drawContours(template,cnts,-1,(0,0,255),3)
cv_show('template',template)
draw_img = template.copy()
digits_and_type = {
} # 存放数字模板和花色模板
for (i,c) in enumerate(cnts):
x,y,w,h = cv2.boundingRect(c)
# cv2.rectangle(draw_img,(x,y),(x+w,y+h),(0,255,0),2)
# cv_show("draw_img",draw_img)
roi = ref[y:y+h,x:x+w]
digits_and_type[i] = roi
'''
第三步 进行模板匹配
'''
OutPut = []
scores = []
save_img2 = cv2.resize(save_img2,(60,90))
save_img1 = cv2.resize(save_img1,(60,90))
#save_img1 = cv2.threshold(save_img1,0,255,
# cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
#save_img2 = cv2.threshold(save_img2,0,255,
# cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
for (digit,digitROI) in digits_and_type.items():
# 模板匹配
result = cv2.matchTemplate(save_img1,digitROI,cv2.TM_CCOEFF)
(_,score,_,_) = cv2.minMaxLoc(result)
scores.append(score)
OutPut.append(np.argmax(scores)+1)
scores = [] # 清空
for (digit,digitROI) in digits_and_type.items():
# 模板匹配
result = cv2.matchTemplate(save_img2,digitROI,cv2.TM_CCOEFF)
(_,score,_,_) = cv2.minMaxLoc(result)
scores.append(score)
OutPut.append(np.argmax(scores)+1)
dict = {
1:"A",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"J",12:"Q",13:"K",14:"红桃",15:"方块",16:"梅花",17:"黑桃"}
print("the result:")
print("当前检测卡牌的数字为" + dict[OutPut[0]] +" 当前检测卡牌的花色为" + dict[OutPut[1]])
Make_Template.py
import numpy as np
import cv2
import matplotlib.pyplot as plt
from PIL import Image
def cv_show(name,file):
cv2.imshow(name,file)
cv2.waitKey(0)
cv2.destroyAllWindows()
for num in range(1,18):
#print(num)
file_name = r"Images/" + str(num) + ".png"
img = Image.open(file_name)
#img = np.array(img)
img = img.resize((60,90))
plt.imshow(img)
plt.show()
#print(img)
img.save(str(num) + ".png")
new_image = np.zeros([100,70 * 17,3],np.uint8) # 66为留出几个像素的小空,高度同理
for num in range(1,18):
file_name = str(num) + ".png"
img = cv2.imread(file_name)
new_image[5:95,(num-1) * 70+5:(num) * 70-5,:] = img
#print("OK")
cv_show("new",new_image)
cv2.imwrite("template.png",new_image)
CSDN下载地址
或者csdn私聊我百度网盘哈(实在是缺积分了最近。。就不直接给网盘咯)
Pycharm下python使用argparse报错: error: the following arguments are required: -i/–image