1. 程式人生 > >使用python3+opencv3實現的識別答題卡的例子(01)

使用python3+opencv3實現的識別答題卡的例子(01)

answer_sheet_scan

使用python3+opencv3實現的一些識別答題卡的例子

由於工作需要,最近在研究關於如何通過程式識別答題卡的客觀題的答案,之前雖然接觸過python,但對於計算機視覺這一塊卻完全是一個陌生的領域,經過各種調研,發現網上大多數的例子都是採用的OpenCV這個開源庫來做的,OpenCV是計算機視覺領域的處理的一個非常優秀的開源庫,原生由C++編寫,也提供了各個主流程式語言的介面支援,這裡選擇python完全是因為python在電腦科學領域有著壓倒性的優勢和生態系統,所以使用它毫無疑問,最快上手的方式莫過於直接閱讀網上已有的例子或者輪子了,通過閱讀原始碼以問題驅動的方式來學習和研究某一項技術是比較高效的一種方式。

識別例子01

例子01是參考:pyimagesearch網站上一個識別例子,參考作者的原始碼,先在本地執行成功之後,然後加上自己的理解,給大多數核心程式碼加上了詳細的中文註釋,並在每一個關鍵階段都會彈出具體的窗體展示識別流程,這樣便於大家更能詳細的看到核心部分的細節,感興趣的同學,可以自己在再嘗試加一些更細部分的debug彈窗。

例子01的在我本地PyCharm執行後一些截圖:

(1)原圖

image

(2)灰度+高斯模糊後的圖

image

(3)使用邊緣檢測後的圖

image

(4)透視變換後提取指定答題區域的灰度圖

image

(5)使用ostu的二值化後的圖

image

(6)識別答案成功後的圖

image

(7)標記出做對和做錯的圖並計算得分

image

程式碼如下:

# -*- coding:utf-8 -*-
from imutils.perspective import four_point_transform from imutils import contours import numpy as np import imutils import cv2 as cv ANSWER_KEY_SCORE = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1} ANSWER_KEY = {0: "A", 1: "B", 2: "C", 3: "D", 4: "E"} # 載入一個圖片到opencv img = cv.imread('E:\\tmp\\t1.png') cv.imshow("orgin"
,img) #轉化成灰度圖片 gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY) cv.imshow("gray",gray) gaussian_bulr = cv.GaussianBlur(gray, (5, 5), 0) # 高斯模糊 cv.imshow("gaussian",gaussian_bulr) edged=cv.Canny(gaussian_bulr,75,200) # 邊緣檢測,灰度值小於2參這個值的會被丟棄,大於3參這個值會被當成邊緣,在中間的部分,自動檢測 cv.imshow("edged",edged) # 尋找輪廓 image, cts, hierarchy = cv.findContours( edged.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) # 給輪廓加標記,便於我們在原圖裡面觀察,注意必須是原圖才能畫出紅色,灰度圖是沒有顏色的 # cv.drawContours(img, cts, -1, (0,0,255), 3) # 按面積大小對所有的輪廓排序 list=sorted(cts,key=cv.contourArea,reverse=True) print("尋找輪廓的個數:",len(cts)) cv.imshow("draw_contours",img) # 正確題的個數 correct_count=0 for c in list: # 周長,第1個引數是輪廓,第二個引數代表是否是閉環的圖形 peri=0.01*cv.arcLength(c,True) # 獲取多邊形的所有定點,如果是四個定點,就代表是矩形 approx=cv.approxPolyDP(c,peri,True) # 列印定點個數 print("頂點個數:",len(approx)) if len(approx)==4: #矩形 # 透視變換提取原圖內容部分 ox_sheet = four_point_transform(img, approx.reshape(4, 2)) # 透視變換提取灰度圖內容部分 tx_sheet = four_point_transform(gray, approx.reshape(4, 2)) cv.imshow("ox", ox_sheet) cv.imshow("tx", tx_sheet) # 使用ostu二值化演算法對灰度圖做一個二值化處理 ret,thresh2 = cv.threshold(tx_sheet, 0, 255,cv.THRESH_BINARY_INV | cv.THRESH_OTSU) cv.imshow("ostu", thresh2) # 繼續尋找輪廓 r_image, r_cnt, r_hierarchy = cv.findContours(thresh2.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) print("找到輪廓個數:",len(r_cnt)) # 使用紅色標記所有的輪廓 # cv.drawContours(ox_sheet,r_cnt,-1,(0,0,255),2) # 把所有找到的輪廓,給標記出來 questionCnts = [] for cxx in r_cnt: # 通過矩形,標記每一個指定的輪廓 x, y, w, h = cv.boundingRect(cxx) ar = w / float(h) if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1: # 使用紅色標記,滿足指定條件的圖形 # cv.rectangle(ox_sheet, (x, y), (x + w, y + h), (0, 0, 255), 2) # 把每個選項,儲存下來 questionCnts.append(cxx) cv.imshow("ox_1", ox_sheet) # 按座標從上到下排序 questionCnts = contours.sort_contours(questionCnts, method="top-to-bottom")[0] # 使用np函式,按5個元素,生成一個集合 for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)): # 獲取按從左到右的排序後的5個元素 cnts = contours.sort_contours(questionCnts[i:i + 5])[0] bubble_rows=[] # 遍歷每一個選項 for (j, c) in enumerate(cnts): # 生成一個大小與透檢視一樣的全黑背景圖布 mask = np.zeros(tx_sheet.shape, dtype="uint8") # 將指定的輪廓+白色的填充寫到畫板上,255代表亮度值,亮度=255的時候,顏色是白色,等於0的時候是黑色 cv.drawContours(mask, [c], -1, 255, -1) # 做兩個圖片做位運算,把每個選項獨自顯示到畫布上,為了統計非0畫素值使用,這部分畫素最大的其實就是答案 mask = cv.bitwise_and(thresh2, thresh2, mask=mask) # cv.imshow("c" + str(i), mask) # 獲取每個答案的畫素值 total = cv.countNonZero(mask) # 存到一個數組裡面,tuple裡面的引數分別是,畫素大小和答案的序號值 # print(total,j) bubble_rows.append((total,j)) bubble_rows=sorted(bubble_rows,key=lambda x: x[0],reverse=True) # 選擇的答案序號 choice_num=bubble_rows[0][1] print("答案:{} 資料: {}".format(ANSWER_KEY.get(choice_num),bubble_rows)) fill_color=None # 如果做對就加1 if ANSWER_KEY_SCORE.get(q) == choice_num: fill_color = (0, 255, 0) #正確 綠色 correct_count = correct_count+1 else: fill_color = (0, 0, 255) #錯誤 紅色 cv.drawContours(ox_sheet, cnts[choice_num], -1, fill_color, 2) cv.imshow("answer_flagged", ox_sheet) text1 = "total: " + str(len(ANSWER_KEY)) + "" text2 = "right: " + str(correct_count) text3 = "score: " + str(correct_count*1.0/len(ANSWER_KEY)*100)+"" font = cv.FONT_HERSHEY_SIMPLEX cv.putText(ox_sheet, text1 + " " + text2+" "+text3, (10, 30), font, 0.5, (0, 0, 255), 2) cv.imshow("score", ox_sheet) break cv.waitKey(0)

原始碼已經上傳我的github上,歡迎大家fork學習.