基於Opencv圖像識別實現答題卡識別示例詳解
在觀看唐宇迪老師圖像處理的課程中,其中有一個答題卡識別的小項目,在此結合自己理解做一個簡單的總結。
1. 項目分析
首先在拿到項目時候,分析項目目的是什麼,要達到什麼樣的目標,有哪些需要註意的事項,同時構思實驗的大體流程。
圖1. 答題卡測試圖像
比如在答題卡識別的項目中,針對測試圖片如圖1 ,首先應當實現的功能是:
能夠捕獲答題卡中的每個填塗選項。
將獲取的填塗選項與正確選項做對比計算其答題正確率。
2.項目實驗
在對測試圖像進行形態學操作中,首先轉換為灰度圖像,其次是進行減噪的高斯濾波操作。
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) cv_show('blurred',blurred)
在得到高斯濾波結果後,對其進行邊緣檢測以及輪廓檢測,用以提取答題卡所有內容的邊界。
edged = cv2.Canny(blurred, 75, 200) cv_show('edged',edged) # 輪廓檢測 cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) cv_show('contours_img',contours_img) docCnt = None
圖2. 高斯濾波圖
圖3. 邊緣檢測圖
在得到邊緣檢測圖像後,進行外輪廓檢測以及進行透視變換。
# 輪廓檢測 cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) cv_show('contours_img',contours_img)
def four_point_transform(image, pts): # 獲取輸入坐標點 rect = order_points(pts) (tl, tr, br, bl) = 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
# 執行透視變換 warped = four_point_transform(gray, docCnt.reshape(4, 2)) cv_show('warped',warped)
在透視變換之後,需要再進行二值轉換,為瞭找到ROI圓圈輪廓,采用二次輪廓檢測執行遍歷循環以及 if 判斷找到所有符合篩選條件的圓圈輪廓。此處不使用霍夫變換的原因是在填塗答題卡的過程中,難免會有填塗超過圓圈區域的情況,使用霍夫變換的直線檢測方式會影響實驗結果的準確性。
# 找到每一個圓圈輪廓 cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3) cv_show('thresh_Contours',thresh_Contours) questionCnts = []
# 遍歷 for c in cnts: # 計算比例和大小 (x, y, w, h) = cv2.boundingRect(c) ar = w / float(h) # 根據實際情況指定標準 if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1: questionCnts.append(c) # 按照從上到下進行排序 questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0] correct = 0
圖4. 輪廓檢測圖
圖5. 透視變換圖
圖6. 二值轉換圖
圖7. 輪廓篩選圖
在得到每個圓圈輪廓後,需要將其進行排序,排序方式為從左到右,從上到下,以圖7為例,答題卡分佈為五行五列,在每一列中,每行A選項的橫坐標x值是相近的,而在每一行中,A、B、C、D、E的縱坐標y是相近的,因此利用這一特性來對所得到的圓圈輪廓進行排序,代碼如下:
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] (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse)) return cnts, boundingBoxes
在得到每一個具體輪廓後,便是判斷每道題所填塗的答案是否為正確答案,使用的方法為通過雙層循環遍歷每一個具體圓圈輪廓,通過mask圖像計算非零點數量來判斷答案是否正確。
# 每排有5個選項 for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)): # 排序 cnts = sort_contours(questionCnts[i:i + 5])[0] #從左到右排列,保持順序:A B C D E bubbled = None # 遍歷每一個結果 for (j, c) in enumerate(cnts): # 使用mask來判斷結果 mask = np.zeros(thresh.shape, dtype="uint8") cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充 cv_show('mask',mask) # 通過計算非零點數量來算是否選擇這個答案 mask = cv2.bitwise_and(thresh, thresh, mask=mask) total = cv2.countNonZero(mask) # 通過閾值判斷 if bubbled is None or total > bubbled[0]: bubbled = (total, j) # 對比正確答案 color = (0, 0, 255) k = ANSWER_KEY[q] # 判斷正確 if k == bubbled[1]: color = (0, 255, 0) correct += 1 # 繪圖 cv2.drawContours(warped, [cnts[k]], -1, color, 3)
圖8. 圓圈輪廓遍歷圖
3.項目結果
在實驗完成後,輸出實驗結果
score = (correct / 5.0) * 100 print("[INFO] score: {:.2f}%".format(score)) cv2.putText(warped, "{:.2f}%".format(score), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) cv2.imshow("Exam", warped) cv2.waitKey(0)
Connected to pydev debugger (build 201.6668.115) [INFO] score: 100.00% Process finished with exit code 0
圖9. 答題卡識別結果圖
總結
在處理答題卡識別小項目中,個人覺得重點有以下幾個方面:
- 圖像的形態學操作,處理的每一步都應該預先思考,選擇最合適的處理方式,如:未采用霍夫變換而使用瞭二次輪廓檢測。
- 利用mask圖像對比答案正確與否,通過判斷非零像素值的數量來進行抉擇。
- 巧妙利用雙層 for 循環以及 if 語句遍歷所有圓圈輪廓,排序之後進行答案比對。
以上就是基於Opencv圖像識別實現答題卡識別示例詳解的詳細內容,更多關於Opencv答題卡識別的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- python OpenCV實現答題卡識別判卷
- 使用 OpenCV-Python 識別答題卡判卷功能
- 基於python使用OpenCV進行物體輪廓排序
- Python OpenCV 基於圖像邊緣提取的輪廓發現函數
- opencv+python識別七段數碼顯示器的數字(數字識別)