溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

發(fā)布時(shí)間:2021-04-01 09:58:12 來(lái)源:億速云 閱讀:406 作者:小新 欄目:開(kāi)發(fā)技術(shù)

小編給大家分享一下python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

程序目標(biāo)

單個(gè)數(shù)字模板:(這些單個(gè)模板是我自己直接從圖片上截取下來(lái)的)

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

要處理的圖片:

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

終端輸出:

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

文本輸出:

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

思路講解

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

代碼講解

首先定義兩個(gè)會(huì)用到的函數(shù)

第一個(gè)是顯示圖片的函數(shù),這樣的話(huà)在顯示圖片的時(shí)候就比較方便了

def cv_show(name, img):
 cv2.imshow(name, img)
 cv2.waitKey(0)
 cv2.destroyAllWindows()

第二個(gè)是圖片縮放的函數(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

先把這個(gè)代碼貼出來(lái),方便后面單個(gè)函數(shù)代碼的理解。

if __name__ == "__main__":
 # 存放數(shù)字模板列表
 digits = []
 # 當(dāng)前運(yùn)行目錄
 now_dir = os.getcwd()
 print("當(dāng)前運(yùn)行目錄:" + now_dir)
 numbers_address = now_dir + "\\numbers"
 load_digits()
 times = input("請(qǐng)輸入程序運(yùn)行次數(shù):")
 for i in range(1, int(times) + 1):
  demo(i)
 print("輸出成功,請(qǐng)檢查本地temp.txt文件")
 while True:
  if input("輸入小寫(xiě)‘q'并回車(chē)退出") == 'q':
   break

接下來(lái)是第一個(gè)主要函數(shù),功能是加載數(shù)字模板并進(jìn)行處理。

這個(gè)函數(shù)使用到了os模塊,所以需要在開(kāi)頭import os

def load_digits():
 # 加載數(shù)字模板
 path = numbers_address # 這個(gè)地方就是獲取當(dāng)前運(yùn)行目錄 獲取函數(shù)在主函數(shù)里面
 filename = os.listdir(path) # 獲取文件夾文件
 for file in filename:
  img = cv2.imread(numbers_address + "\\" + file) # 讀取圖片
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度處理
  # 自動(dòng)閾值二值化 把圖片處理成黑底白字
  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])
  # 將單個(gè)數(shù)字區(qū)域進(jìn)行縮放并存到列表中以備后面使用
  digit_roi = cv2.resize(img_temp[y:y+h, x:x+w], (57, 88))
  digits.append(digit_roi)

最后一個(gè)函數(shù)是程序的重點(diǎn),實(shí)現(xiàn)功能就是識(shí)別出數(shù)字并輸出。

不過(guò)這里把這個(gè)大函數(shù)分開(kāi)兩部分來(lái)講解。

第一部分是對(duì)圖片進(jìn)行處理,最終把圖片中的數(shù)字區(qū)域圈出來(lái)。

 # 這兩個(gè)都是核,參數(shù)可以改變
 rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
 sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
 # 這個(gè)就是讀取圖片的,可以暫時(shí)不理解
 target_path = now_dir + "\\" + "demo_" + str(index) + ".png"
 img_origin = cv2.imread(target_path)
 # 對(duì)圖片進(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)、
 # 自動(dòng)二值化處理,黑底白字
 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)
 # 自動(dòng)二值化
 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ù)字序列(包含逗號(hào))的輪廓,即過(guò)濾掉不需要的輪廓
 right_loc = []
 # 下面這個(gè)循環(huán)是對(duì)輪廓進(jìn)行篩選,只有長(zhǎng)寬比例大于2的才可以被添加到列表中
 # 這個(gè)比例可以根據(jù)具體情況來(lái)改變。除此之外,還可以通過(guò)輪廓周長(zhǎng)和輪廓面積等對(duì)輪廓進(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))

部分步驟的效果圖:

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

可以看到在進(jìn)行完最后一次閉操作后,一串?dāng)?shù)字全部變成白色區(qū)域,這樣再進(jìn)行輪廓檢測(cè)就可以框出每一行數(shù)字的大致范圍,這樣就可以縮小數(shù)字處理的范圍,可以在這些具體的區(qū)域內(nèi)部對(duì)單個(gè)數(shù)字進(jìn)行處理。

輪廓效果:

python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字

在這樣進(jìn)行以上步驟之后,就可以確定一行數(shù)字的范圍了,下面就進(jìn)行輪廓篩選把符合條件的輪廓存入列表。

注意:在代碼中使用了(cnts, boundingBoxes) = contours.sort_contours(cnts, "top-to-bottom")

這個(gè)函數(shù)的使用需要導(dǎo)入imutils,這個(gè)模塊具體使用方法可以瀏覽我的另一篇博客OpenCV學(xué)習(xí)筆記

函數(shù)的最后一部分就是對(duì)每個(gè)數(shù)字輪廓進(jìn)行分割,取出單個(gè)數(shù)字的區(qū)域然后進(jìn)行模板匹配。

for (gx, gy, gw, gh) in right_loc:
  # 用于存放識(shí)別到的數(shù)字
  digit_out = []
  # 下面兩個(gè)判斷主要是防止出現(xiàn)越界的情況發(fā)生,如果發(fā)生的話(huà)圖片讀取會(huì)出錯(cuò)
  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]
  # 尋找所有輪廓 找出每個(gè)數(shù)字的輪廓(包含逗號(hào)) 正確的話(huà)應(yīng)該有9個(gè)輪廓
  digitCnts = cv2.findContours(
   img_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
  # 從左到右排列輪廓
  # 這樣排列的好處是,正常情況下可以確定逗號(hào)的位置方便后面刪除逗號(hào)
  (cnts, boundingBoxes) = contours.sort_contours(digitCnts, "left-to-right")
  # cnts是元組,需要先轉(zhuǎn)換成列表,因?yàn)楹竺鏁?huì)對(duì)元素進(jìn)行刪除處理
  cnts = list(cnts)
  flag = 0
  # 判斷輪廓數(shù)量是否有9個(gè)
  if len(cnts) == 9:
   # 刪除逗號(hào)位置
   del cnts[1]
   del cnts[2]
   del cnts[3]
   del cnts[4]
   # 可以在轉(zhuǎn)成元組
   cnts = tuple(cnts)
   # 存放單個(gè)數(shù)字的矩形區(qū)域
   num_roi = []
   for c in cnts:
    x, y, w, h = cv2.boundingRect(c)
    num_roi.append((x, y, w, h))
   # 對(duì)數(shù)字區(qū)域進(jìn)行處理,把尺寸縮放到與數(shù)字模板相同
   # 對(duì)其進(jìn)行簡(jiǎ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)
    # 這個(gè)需要仔細(xì)理解 這個(gè)就是把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))))

這個(gè)是很重要的,列表source存放模板匹配的每個(gè)數(shù)字的匹配率,求出其中最大值的索引值,因?yàn)閿?shù)字模板是按照0-9排列的,索引source的匹配率也是按照0-9排列的,所以每個(gè)元素的索引值就與相匹配的數(shù)字相同。這樣的話(huà),取得最大值的索引值就相當(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ù)字序列(包含逗號(hào))的輪廓,即過(guò)濾掉不需要的輪廓
 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í)別到的數(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]
  # 尋找輪廓 找出每個(gè)數(shù)字的輪廓(包含逗號(hào)) 正確的話(huà)應(yīng)該有9個(gè)輪廓
  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)前運(yùn)行目錄
 now_dir = os.getcwd()
 print("當(dāng)前運(yùn)行目錄:" + now_dir)
 numbers_address = now_dir + "\\numbers"
 load_digits()
 times = input("請(qǐng)輸入程序運(yùn)行次數(shù):")
 for i in range(1, int(times) + 1):
  demo(i)
 print("輸出成功,請(qǐng)檢查本地temp.txt文件")
 cv2.waitKey(0)
 cv2.destroyAllWindows()
 while True:
  if input("輸入小寫(xiě)‘q'并回車(chē)退出") == 'q':
   break

整個(gè)文件下載地址:https://wwe.lanzous.com/iLSDunf850b

注意:如果想同時(shí)識(shí)別多個(gè)圖片話(huà),需要將圖片統(tǒng)一改名為“demo_ + 數(shù)字序號(hào).png” 例如:demo_1.png demo_2.png 同時(shí)在運(yùn)行代碼時(shí)輸入圖片個(gè)數(shù)即可。

以上是“python如何基于OpenCV模板匹配識(shí)別圖片中的數(shù)字”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI