目錄
- 前言
- 程序目標(biāo)
- 思路講解
- 代碼講解
- 完整代碼
- 總結(jié)
前言
本博客主要實現(xiàn)利用OpenCV的模板匹配識別圖像中的數(shù)字,然后把識別出來的數(shù)字輸出到txt文件中,如果識別失敗則輸出“讀取失敗”。
操作環(huán)境:
- OpenCV - 4.1.0
- Python 3.8.1
程序目標(biāo)
單個數(shù)字模板:(這些單個模板是我自己直接從圖片上截取下來的)
要處理的圖片:
終端輸出:
文本輸出:
思路講解
代碼講解
首先定義兩個會用到的函數(shù)
第一個是顯示圖片的函數(shù),這樣的話在顯示圖片的時候就比較方便了
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
第二個是圖片縮放的函數(shù)
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resized
先把這個代碼貼出來,方便后面單個函數(shù)代碼的理解。
if __name__ == "__main__":
# 存放數(shù)字模板列表
digits = []
# 當(dāng)前運行目錄
now_dir = os.getcwd()
print("當(dāng)前運行目錄:" + now_dir)
numbers_address = now_dir + "\\numbers"
load_digits()
times = input("請輸入程序運行次數(shù):")
for i in range(1, int(times) + 1):
demo(i)
print("輸出成功,請檢查本地temp.txt文件")
while True:
if input("輸入小寫‘q'并回車退出") == 'q':
break
接下來是第一個主要函數(shù),功能是加載數(shù)字模板并進(jìn)行處理。
這個函數(shù)使用到了os模塊,所以需要在開頭import os
def load_digits():
# 加載數(shù)字模板
path = numbers_address # 這個地方就是獲取當(dāng)前運行目錄 獲取函數(shù)在主函數(shù)里面
filename = os.listdir(path) # 獲取文件夾文件
for file in filename:
img = cv2.imread(numbers_address + "\\" + file) # 讀取圖片
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度處理
# 自動閾值二值化 把圖片處理成黑底白字
img_temp = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# 尋找數(shù)字輪廓
cnt = cv2.findContours(img_temp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
# 獲取數(shù)字矩形輪廓
x, y, w, h = cv2.boundingRect(cnt[0])
# 將單個數(shù)字區(qū)域進(jìn)行縮放并存到列表中以備后面使用
digit_roi = cv2.resize(img_temp[y:y+h, x:x+w], (57, 88))
digits.append(digit_roi)
最后一個函數(shù)是程序的重點,實現(xiàn)功能就是識別出數(shù)字并輸出。
不過這里把這個大函數(shù)分開兩部分來講解。
第一部分是對圖片進(jìn)行處理,最終把圖片中的數(shù)字區(qū)域圈出來。
# 這兩個都是核,參數(shù)可以改變
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 這個就是讀取圖片的,可以暫時不理解
target_path = now_dir + "\\" + "demo_" + str(index) + ".png"
img_origin = cv2.imread(target_path)
# 對圖片進(jìn)行縮放處理
img_origin = resize(img_origin, width=300)
# 灰度圖
img_gray = cv2.cvtColor(img_origin, cv2.COLOR_BGR2GRAY)
# 高斯濾波 參數(shù)可以改變,選擇效果最好的就可以
gaussian = cv2.GaussianBlur(img_gray, (5, 5), 1)、
# 自動二值化處理,黑底白字
img_temp = cv2.threshold(
gaussian, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# 頂帽操作
img_top = cv2.morphologyEx(img_temp, cv2.MORPH_TOPHAT, rectKernel)
# sobel操作
img_sobel_x = cv2.Sobel(img_top, cv2.CV_64F, 1, 0, ksize=7)
img_sobel_x = cv2.convertScaleAbs(img_sobel_x)
img_sobel_y = cv2.Sobel(img_top, cv2.CV_64F, 0, 1, ksize=7)
img_sobel_y = cv2.convertScaleAbs(img_sobel_y)
img_sobel_xy = cv2.addWeighted(img_sobel_x, 1, img_sobel_y, 1, 0)
# 閉操作
img_closed = cv2.morphologyEx(img_sobel_xy, cv2.MORPH_CLOSE, rectKernel)
# 自動二值化
thresh = cv2.threshold(
img_closed, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 閉操作
img_closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
# 尋找數(shù)字輪廓
cnts = cv2.findContours(
img_closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
# 輪廓排序
(cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")
# 存放正確數(shù)字序列(包含逗號)的輪廓,即過濾掉不需要的輪廓
right_loc = []
# 下面這個循環(huán)是對輪廓進(jìn)行篩選,只有長寬比例大于2的才可以被添加到列表中
# 這個比例可以根據(jù)具體情況來改變。除此之外,還可以通過輪廓周長和輪廓面積等對輪廓進(jìn)行篩選
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
ar = w/float(h)
if ar > 2:
right_loc.append((x, y, w, h))
部分步驟的效果圖:
可以看到在進(jìn)行完最后一次閉操作后,一串?dāng)?shù)字全部變成白色區(qū)域,這樣再進(jìn)行輪廓檢測就可以框出每一行數(shù)字的大致范圍,這樣就可以縮小數(shù)字處理的范圍,可以在這些具體的區(qū)域內(nèi)部對單個數(shù)字進(jìn)行處理。
輪廓效果:
在這樣進(jìn)行以上步驟之后,就可以確定一行數(shù)字的范圍了,下面就進(jìn)行輪廓篩選把符合條件的輪廓存入列表。
注意:在代碼中使用了(cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")
這個函數(shù)的使用需要導(dǎo)入imutils,這個模塊具體使用方法可以瀏覽我的另一篇博客OpenCV學(xué)習(xí)筆記
函數(shù)的最后一部分就是對每個數(shù)字輪廓進(jìn)行分割,取出單個數(shù)字的區(qū)域然后進(jìn)行模板匹配。
for (gx, gy, gw, gh) in right_loc:
# 用于存放識別到的數(shù)字
digit_out = []
# 下面兩個判斷主要是防止出現(xiàn)越界的情況發(fā)生,如果發(fā)生的話圖片讀取會出錯
if (gy-10 0):
now_gy = gy
else:
now_gy = gy-10
if (gx - 10 0):
now_gx = gx
else:
now_gx = gx-10
# 選擇圖片興趣區(qū)域
img_digit = gaussian[now_gy:gy+gh+10, now_gx:gx+gw+10]
# 二值化處理
img_thresh = cv2.threshold(
img_digit, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# 尋找所有輪廓 找出每個數(shù)字的輪廓(包含逗號) 正確的話應(yīng)該有9個輪廓
digitCnts = cv2.findContours(
img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
# 從左到右排列輪廓
# 這樣排列的好處是,正常情況下可以確定逗號的位置方便后面刪除逗號
(cnts, boundingBoxes) = contours.sort_contours(digitCnts, "left-to-right")
# cnts是元組,需要先轉(zhuǎn)換成列表,因為后面會對元素進(jìn)行刪除處理
cnts = list(cnts)
flag = 0
# 判斷輪廓數(shù)量是否有9個
if len(cnts) == 9:
# 刪除逗號位置
del cnts[1]
del cnts[2]
del cnts[3]
del cnts[4]
# 可以在轉(zhuǎn)成元組
cnts = tuple(cnts)
# 存放單個數(shù)字的矩形區(qū)域
num_roi = []
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
num_roi.append((x, y, w, h))
# 對數(shù)字區(qū)域進(jìn)行處理,把尺寸縮放到與數(shù)字模板相同
# 對其進(jìn)行簡單處理,方便與模板匹配,增加匹配率
for (rx, ry, rw, rh) in num_roi:
roi = img_digit[ry:ry+rh, rx:rx+rw]
roi = cv2.resize(roi, (57, 88))
# 高斯濾波
roi = cv2.GaussianBlur(roi, (5, 5), 1)
# 二值化
roi = cv2.threshold(
roi, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# 用于存放匹配率
source = []
# 遍歷數(shù)字模板
for digitROI in digits:
# 進(jìn)行模板匹配
res = cv2.matchTemplate(
roi, digitROI, cv2.TM_CCOEFF_NORMED)
max_val = cv2.minMaxLoc(res)[1]
source.append(max_val)
# 這個需要仔細(xì)理解 這個就是把0-9數(shù)字中匹配度最高的數(shù)字存放到列表中
digit_out.append(str(source.index(max(source))))
# 打印最終輸出值
print(digit_out)
else:
print("讀取失敗")
flag = 1
# 將數(shù)字輸出到txt文本中
t = ''
with open(now_dir + "\\temp.txt", 'a+') as q:
if flag == 0:
for content in digit_out:
t = t + str(content) + " "
q.write(t.strip(" "))
q.write('\n')
t = ''
else:
q.write("讀取失敗")
q.write('\n')
注意理解:digit_out.append(str(source.index(max(source))))
這個是很重要的,列表source存放模板匹配的每個數(shù)字的匹配率,求出其中最大值的索引值,因為數(shù)字模板是按照0-9排列的,索引source的匹配率也是按照0-9排列的,所以每個元素的索引值就與相匹配的數(shù)字相同。這樣的話,取得最大值的索引值就相當(dāng)于取到了匹配率最高的數(shù)字。
完整代碼
from imutils import contours
import cv2
import os
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resized
def load_digits():
# 加載數(shù)字模板
path = numbers_address
filename = os.listdir(path)
for file in filename:
# print(file)
img = cv2.imread(
numbers_address + "\\" + file)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_temp = cv2.threshold(
img_gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnt = cv2.findContours(img_temp, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_NONE)[0]
x, y, w, h = cv2.boundingRect(cnt[0])
digit_roi = cv2.resize(img_temp[y:y+h, x:x+w], (57, 88))
# 將數(shù)字模板存到列表中
digits.append(digit_roi)
def demo(index):
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
target_path = now_dir + "\\" + "demo_" + str(index) + ".png"
img_origin = cv2.imread(target_path)
img_origin = resize(img_origin, width=300)
img_gray = cv2.cvtColor(img_origin, cv2.COLOR_BGR2GRAY)
gaussian = cv2.GaussianBlur(img_gray, (5, 5), 1)
img_temp = cv2.threshold(
gaussian, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
img_top = cv2.morphologyEx(img_temp, cv2.MORPH_TOPHAT, rectKernel)
img_sobel_x = cv2.Sobel(img_top, cv2.CV_64F, 1, 0, ksize=7)
img_sobel_x = cv2.convertScaleAbs(img_sobel_x)
img_sobel_y = cv2.Sobel(img_top, cv2.CV_64F, 0, 1, ksize=7)
img_sobel_y = cv2.convertScaleAbs(img_sobel_y)
img_sobel_xy = cv2.addWeighted(img_sobel_x, 1, img_sobel_y, 1, 0)
img_closed = cv2.morphologyEx(img_sobel_xy, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(
img_closed, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
img_closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
cnts = cv2.findContours(
img_closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
(cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")
draw_img = img_origin.copy()
draw_img = cv2.drawContours(draw_img, cnts, -1, (0, 0, 255), 1)
cv_show("666", draw_img)
# 存放正確數(shù)字序列(包含逗號)的輪廓,即過濾掉不需要的輪廓
right_loc = []
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
ar = w/float(h)
if ar > 2:
right_loc.append((x, y, w, h))
for (gx, gy, gw, gh) in right_loc:
# 用于存放識別到的數(shù)字
digit_out = []
if (gy-10 0):
now_gy = gy
else:
now_gy = gy-10
if (gx - 10 0):
now_gx = gx
else:
now_gx = gx-10
img_digit = gaussian[now_gy:gy+gh+10, now_gx:gx+gw+10]
# 二值化處理
img_thresh = cv2.threshold(
img_digit, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# 尋找輪廓 找出每個數(shù)字的輪廓(包含逗號) 正確的話應(yīng)該有9個輪廓
digitCnts = cv2.findContours(
img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
# 從左到右排列
(cnts, boundingBoxes) = contours.sort_contours(digitCnts, "left-to-right")
cnts = list(cnts)
flag = 0
if len(cnts) == 9:
del cnts[1]
del cnts[2]
del cnts[3]
del cnts[4]
cnts = tuple(cnts)
num_roi = []
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
num_roi.append((x, y, w, h))
for (rx, ry, rw, rh) in num_roi:
roi = img_digit[ry:ry+rh, rx:rx+rw]
roi = cv2.resize(roi, (57, 88))
roi = cv2.GaussianBlur(roi, (5, 5), 1)
roi = cv2.threshold(
roi, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
source = []
for digitROI in digits:
res = cv2.matchTemplate(
roi, digitROI, cv2.TM_CCOEFF_NORMED)
max_val = cv2.minMaxLoc(res)[1]
source.append(max_val)
digit_out.append(str(source.index(max(source))))
cv2.rectangle(img_origin, (gx-5, gy-5),
(gx+gw+5, gy+gh+5), (0, 0, 255), 1)
print(digit_out)
else:
print("讀取失敗")
flag = 1
t = ''
with open(now_dir + "\\temp.txt", 'a+') as q:
if flag == 0:
for content in digit_out:
t = t + str(content) + " "
q.write(t.strip(" "))
q.write('\n')
t = ''
else:
q.write("讀取失敗")
q.write('\n')
if __name__ == "__main__":
# 存放數(shù)字模板列表
digits = []
# 當(dāng)前運行目錄
now_dir = os.getcwd()
print("當(dāng)前運行目錄:" + now_dir)
numbers_address = now_dir + "\\numbers"
load_digits()
times = input("請輸入程序運行次數(shù):")
for i in range(1, int(times) + 1):
demo(i)
print("輸出成功,請檢查本地temp.txt文件")
cv2.waitKey(0)
cv2.destroyAllWindows()
while True:
if input("輸入小寫‘q'并回車退出") == 'q':
break
整個文件下載地址:https://wwe.lanzous.com/iLSDunf850b
注意:如果想同時識別多個圖片話,需要將圖片統(tǒng)一改名為“demo_ + 數(shù)字序號.png” 例如:demo_1.png demo_2.png 同時在運行代碼時輸入圖片個數(shù)即可。
總結(jié)
這個程序代碼相對來說不算復(fù)雜,主要是對圖像的一些基礎(chǔ)處理需要注意。因為不同的圖像想要識別成功需要進(jìn)行不同程度的基礎(chǔ)處理,所以在做的時候可以多輸出幾張圖片檢查一下那一步效果不太好并及時進(jìn)行修改調(diào)整,這樣才能達(dá)到最終比較好的效果。
以上就是python基于OpenCV模板匹配識別圖片中的數(shù)字的詳細(xì)內(nèi)容,更多關(guān)于python 識別圖片中的數(shù)字的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- opencv-python圖像配準(zhǔn)(匹配和疊加)的實現(xiàn)
- Opencv Python實現(xiàn)兩幅圖像匹配
- Python使用Opencv實現(xiàn)圖像特征檢測與匹配的方法
- Python和OpenCV進(jìn)行多尺度模板匹配實現(xiàn)
- OpenCV-Python模板匹配人眼的實例
- OpenCV-Python實現(xiàn)多模板匹配
- Python開發(fā)之基于模板匹配的信用卡數(shù)字識別功能
- Python+Opencv實現(xiàn)圖像匹配功能(模板匹配)