測試數據見文章頂部位置資源!!!
使用了OpenCV庫(cv2)和imutils庫。代碼的主要目的是處理圖像中的問題,如識別圖像中的文字,并對其進行分析和排序。
輔助答題卡判別
# -*- coding:utf-8 -*-
from imutils.perspective import four_point_transform
# 圖像處理函數,對OpenCV的簡化
from imutils import contours
# 支持大量的維度數組與矩陣運算
import numpy as np
# OpenCV庫(cv2)
import cv2 as cv
# https://github.com/qindongliang/answer_sheet_scan
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('test01.jpg')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 = 0for 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# 如果做對就加1if ANSWER_KEY_SCORE.get(q) == choice_num:fill_color = (0, 255, 0) # 正確 綠色correct_count = correct_count + 1else: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_SIMPLEXcv.putText(ox_sheet, text1 + " " + text2 + " " + text3, (10, 30), font, 0.5, (0, 0, 255), 2)cv.imshow("score", ox_sheet)breakcv.waitKey(0)
# -*- coding:utf-8 -*-
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import cv2 as cv# 加載原圖,可在項目imgs/example02目錄下找到
img = cv.imread("test01.jpg")# cv.resizeWindow("enhanced", 240, 280);
# 打印原圖
cv.imshow("orgin", img)# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 打印灰度圖
cv.imshow("gray", gray)# 高斯濾波,清除一些雜點
blur = cv.GaussianBlur(gray, (3, 3), 0)# 自適應二值化算法
thresh2 = cv.adaptiveThreshold(blur, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 131, 4)# 打印二值化后的圖
cv.imshow("thresh2", thresh2)# 尋找輪廓
image, cts, hierarchy = cv.findContours(thresh2, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)# 打印找到的輪廓
print("輪廓數:", len(cts))# 對拷貝的原圖進行輪廓標記
contour_flagged = cv.drawContours(img.copy(), cts, -1, (0, 0, 255), 3)
# 打印輪廓圖
cv.imshow("contours_flagged", contour_flagged)
# 按像素面積降序排序
list = sorted(cts, key=cv.contourArea, reverse=True)# 遍歷輪廓
for ct in list:# 周長,第1個參數是輪廓,第二個參數代表是否是閉環的圖形peri = 0.01 * cv.arcLength(ct, True)# 獲取多邊形的所有定點,如果是四個定點,就代表是矩形approx = cv.approxPolyDP(ct, peri, True)# 只考慮矩形if len(approx) == 4:# 從原圖中提取所需的矯正圖片ox = four_point_transform(img, approx.reshape(4, 2))# 從原圖中提取所需的矯正圖片tx = four_point_transform(gray, approx.reshape(4, 2))# 打印矯正后的灰度圖cv.imshow("tx", tx)# 對矯正圖進行高斯模糊blur = cv.GaussianBlur(tx, (3, 3), 0)# 對矯正圖做自適應二值化thresh2 = cv.adaptiveThreshold(blur, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 131, 4)# 打印矯正后的二值化圖cv.imshow("tx_thresh2", thresh2)# 獲取輪廓r_image, r_cts, r_hierarchy = cv.findContours(thresh2, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)# 打印得到輪廓數量print("第二層輪廓數:", len(r_cts))# 用于存儲答案的python list變量question_list = []for r_ct in r_cts:# 轉為矩形,分別獲取 x,y坐標,及矩形的寬和高x, y, w, h = cv.boundingRect(r_ct)# 過濾掉不符合答案坐標和長寬的選項if x > 2 and y > 2 and w > 20 and h > 20:# cv.drawContours(ox, r_ct, -1, (0, 0, 255), 1)question_list.append(r_ct)print("答案總數:", len(question_list))# 按坐標從上到下排序questionCnts = contours.sort_contours(question_list, 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]# 存儲一行題里面的每個答案ans_list = []for (j, cc) in enumerate(cnts):# 生成全黑畫布mask = np.zeros(thresh2.shape, dtype="uint8")# 將每一個答案按輪廓寫上去,并將填充顏色設置成白色tpp = cv.drawContours(mask, [cc], -1, 255, -1)# 兩個圖片做位運算mask = cv.bitwise_and(thresh2, thresh2, mask=mask)# 統計每個答案的像素total = cv.countNonZero(mask)# 添加到集合里面ans_list.append((total, j))# 按像素大小排序ans_list = sorted(ans_list, key=lambda x: x[0], reverse=True)max_ans_num = ans_list[0][1]max_ans_size = ans_list[0][0]print("答案序號:", max_ans_num, "列表:", ans_list)# 給選中答案,標記成紅色cv.drawContours(ox, cnts[max_ans_num], -1, (0, 0, 255), 2)cv.imshow("answer_flagged", ox)# 最大的輪廓就是我們想要的,之后的就可以結束循環了break# 阻塞等待窗體關閉
cv.waitKey(0)