Opencv學習筆記(3)---紙牌數字識別練習實踐專案
Opencv學習筆記(3)—紙牌數字識別練習
本來我以為會很簡單的,然後實際做發現對我來說還是有點問題,我最初只是想著使用透視變換對不同角度拍照的紙牌首先進行變化,然後直接使用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私聊我百度網盤哈(實在是缺積分了最近。。就不直接給網盤咯)
總結
- 我在測試過程中不太明白為啥不能用庫直接進行識別數字。。然後學到的很關鍵的一點就是圖片很受光照的影響;剛開始我拍照是直接在桌子上拍的卡牌,然後發現有的圖片對於卡牌的最外輪廓識別不出來,然後就直接換為了在一個黑皮筆記本上拍照,這個時候解決了最外輪廓的問題。
- 中間遇到一個非常頭疼的問題就是有的數字特徵,經過二值化後,可能會消失,因為圖片在拍照的時候光線有的很弱,然後二值化的不同閾值就會造成影響,今天上午找到了一個很好的解決辦法:自適應直方圖均衡化處理,然後就改善了結果(因為沒有再特殊在暗光或者什麼地方測試過,不過我自己又拍的都成功了)
- 待改進方面: 這個是一個已知的BUG,應該有的地方還是會遇到,就是在選取數字和花色特徵的面積的範圍那裡,有的圖片可能特徵面積更大或者更小,就導致不在選取範圍內出現BUG,這個時候可以Debug一下,檢視這個特徵的面積多大,然後修改一下閾值,但是這個還算BUG,因為不能保證每次都要修改,所以可以改進的地方是在進行圖片讀取後,都resize一下,或者圖片進行透視變換後,resize一下規範化,這樣就可以保證不同的輸入圖片都不會出現這個問題,同時在把卡牌中間部位變為白色時的結果也更好
- 程式碼有點亂,僅供參考。我也是初學者,這個帖子主要記錄下思路,並且提供一個想出來的小練手專案和資料,想了解更多程式碼的思路可以私聊哈:D
相關文章
- Spark學習筆記——手寫數字識別Spark筆記
- 學習筆記專案實踐(python)筆記Python
- OpenCV學習筆記(3)——Scalar資料型別理解OpenCV筆記資料型別
- 《手寫數字識別》神經網路 學習筆記神經網路筆記
- opencv 學習之 基於K近鄰的數字識別OpenCV
- opencv學習筆記(一)OpenCV筆記
- Vue 學習筆記 (三) -- VueCli 3 專案配置Vue筆記
- ThreadLoop實踐學習筆記threadOOP筆記
- opencv學習實踐(3) cv::waitKey()的使用OpenCVAI
- 深度學習實驗:Softmax實現手寫數字識別深度學習
- 數字遊戲策劃學習筆記遊戲筆記
- Python學習筆記 - 字串,數字Python筆記字串
- 機器學習框架ML.NET學習筆記【4】多元分類之手寫數字識別機器學習框架筆記
- 《Golang學習筆記》error最佳實踐Golang筆記Error
- 怎樣通過業餘練手專案學習與實踐
- python opencv識別藍牌車牌號 之 取出車牌號 (1/3)PythonOpenCV
- iOS學習筆記06 手勢識別iOS筆記
- Vue 學習筆記 (一) -- 初識 VueCli 3Vue筆記
- 【Get】用深度學習識別手寫數字深度學習
- 數理統計筆記[牛客專項練習]筆記
- OpenCV學習筆記(4)——mixChannels函式OpenCV筆記函式
- OpenCV學習筆記(5)——normalize函式OpenCV筆記ORM函式
- OpenCV 名稱空間學習筆記OpenCV筆記
- javascript學習筆記--元字元使用練習JavaScript筆記字元
- Java學習筆記——陣列練習(七)Java筆記陣列
- 【學習筆記】數學筆記
- python基礎學習筆記(紙質)Python筆記
- 基金訓練營學習筆記3-股票基金筆記
- 數論學習筆記 (3):因數與倍數筆記
- 強化學習-學習筆記3 | 策略學習強化學習筆記
- mnist手寫數字識別——深度學習入門專案(tensorflow+keras+Sequential模型)深度學習Keras模型
- Python學習筆記——Python Number(數字)Python筆記
- 學習 第3章:專項練習之一
- C++基礎知識學習筆記(3)C++筆記
- 利用OpenCV和深度學習來實現人類活動識別OpenCV深度學習
- OpenCV學習筆記(六)——對XML和YAML檔案實現I/O操作OpenCV筆記XMLYAML
- Vue學習筆記3Vue筆記
- Thymeleaf 3學習筆記筆記