基于Opencv圖像識(shí)別實(shí)現(xiàn)答題卡識(shí)別示例詳解
在觀看唐宇迪老師圖像處理的課程中,其中有一個(gè)答題卡識(shí)別的小項(xiàng)目,在此結(jié)合自己理解做一個(gè)簡單的總結(jié)。
1. 項(xiàng)目分析
首先在拿到項(xiàng)目時(shí)候,分析項(xiàng)目目的是什么,要達(dá)到什么樣的目標(biāo),有哪些需要注意的事項(xiàng),同時(shí)構(gòu)思實(shí)驗(yàn)的大體流程。

圖1. 答題卡測(cè)試圖像
比如在答題卡識(shí)別的項(xiàng)目中,針對(duì)測(cè)試圖片如圖1 ,首先應(yīng)當(dāng)實(shí)現(xiàn)的功能是:
能夠捕獲答題卡中的每個(gè)填涂選項(xiàng)。
將獲取的填涂選項(xiàng)與正確選項(xiàng)做對(duì)比計(jì)算其答題正確率。
2.項(xiàng)目實(shí)驗(yàn)
在對(duì)測(cè)試圖像進(jìn)行形態(tài)學(xué)操作中,首先轉(zhuǎn)換為灰度圖像,其次是進(jìn)行減噪的高斯濾波操作。
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
cv_show('blurred',blurred)
在得到高斯濾波結(jié)果后,對(duì)其進(jìn)行邊緣檢測(cè)以及輪廓檢測(cè),用以提取答題卡所有內(nèi)容的邊界。
edged = cv2.Canny(blurred, 75, 200)
cv_show('edged',edged)
# 輪廓檢測(cè)
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. 邊緣檢測(cè)圖
在得到邊緣檢測(cè)圖像后,進(jìn)行外輪廓檢測(cè)以及進(jìn)行透視變換。
# 輪廓檢測(cè)
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): # 獲取輸入坐標(biāo)點(diǎn) rect = order_points(pts) (tl, tr, br, bl) = rect # 計(jì)算輸入的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)) # 變換后對(duì)應(yīng)坐標(biāo)位置 dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype = "float32") # 計(jì)算變換矩陣 M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) # 返回變換后結(jié)果 return warped
# 執(zhí)行透視變換
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped',warped)
在透視變換之后,需要再進(jìn)行二值轉(zhuǎn)換,為了找到ROI圓圈輪廓,采用二次輪廓檢測(cè)執(zhí)行遍歷循環(huán)以及 if 判斷找到所有符合篩選條件的圓圈輪廓。此處不使用霍夫變換的原因是在填涂答題卡的過程中,難免會(huì)有填涂超過圓圈區(qū)域的情況,使用霍夫變換的直線檢測(cè)方式會(huì)影響實(shí)驗(yàn)結(jié)果的準(zhǔn)確性。
# 找到每一個(gè)圓圈輪廓
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: # 計(jì)算比例和大小 (x, y, w, h) = cv2.boundingRect(c) ar = w / float(h) # 根據(jù)實(shí)際情況指定標(biāo)準(zhǔn) if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1: questionCnts.append(c) # 按照從上到下進(jìn)行排序 questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0] correct = 0

圖4. 輪廓檢測(cè)圖

圖5. 透視變換圖

圖6. 二值轉(zhuǎn)換圖

圖7. 輪廓篩選圖
在得到每個(gè)圓圈輪廓后,需要將其進(jìn)行排序,排序方式為從左到右,從上到下,以圖7為例,答題卡分布為五行五列,在每一列中,每行A選項(xiàng)的橫坐標(biāo)x值是相近的,而在每一行中,A、B、C、D、E的縱坐標(biāo)y是相近的,因此利用這一特性來對(duì)所得到的圓圈輪廓進(jìn)行排序,代碼如下:
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
在得到每一個(gè)具體輪廓后,便是判斷每道題所填涂的答案是否為正確答案,使用的方法為通過雙層循環(huán)遍歷每一個(gè)具體圓圈輪廓,通過mask圖像計(jì)算非零點(diǎn)數(shù)量來判斷答案是否正確。
# 每排有5個(gè)選項(xiàng)
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
# 遍歷每一個(gè)結(jié)果
for (j, c) in enumerate(cnts):
# 使用mask來判斷結(jié)果
mask = np.zeros(thresh.shape, dtype="uint8")
cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充
cv_show('mask',mask)
# 通過計(jì)算非零點(diǎn)數(shù)量來算是否選擇這個(gè)答案
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
# 通過閾值判斷
if bubbled is None or total > bubbled[0]:
bubbled = (total, j)
# 對(duì)比正確答案
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.項(xiàng)目結(jié)果
在實(shí)驗(yàn)完成后,輸出實(shí)驗(yàn)結(jié)果
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. 答題卡識(shí)別結(jié)果圖
總結(jié)
在處理答題卡識(shí)別小項(xiàng)目中,個(gè)人覺得重點(diǎn)有以下幾個(gè)方面:
- 圖像的形態(tài)學(xué)操作,處理的每一步都應(yīng)該預(yù)先思考,選擇最合適的處理方式,如:未采用霍夫變換而使用了二次輪廓檢測(cè)。
- 利用mask圖像對(duì)比答案正確與否,通過判斷非零像素值的數(shù)量來進(jìn)行抉擇。
- 巧妙利用雙層 for 循環(huán)以及 if 語句遍歷所有圓圈輪廓,排序之后進(jìn)行答案比對(duì)。?
以上就是基于Opencv圖像識(shí)別實(shí)現(xiàn)答題卡識(shí)別示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Opencv答題卡識(shí)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python實(shí)現(xiàn)一個(gè)簡單的ping工具方法
今天小編就為大家分享一篇python實(shí)現(xiàn)一個(gè)簡單的ping工具方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-01-01
Python的輸出格式化和進(jìn)制轉(zhuǎn)換介紹
大家好,本篇文章主要講的是Python的輸出格式化和進(jìn)制轉(zhuǎn)換介紹,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
Deepsort + Yolo 實(shí)現(xiàn)行人檢測(cè)和軌跡追蹤的方法
這篇文章主要介紹了Deepsort + Yolo 實(shí)現(xiàn)行人檢測(cè)和軌跡追蹤,本項(xiàng)目通過采用深度學(xué)習(xí)方法實(shí)現(xiàn)YOLO算法行人檢測(cè)和deepsort算法對(duì)人員定位的和軌跡跟蹤,需要的朋友可以參考下2021-09-09
使用Django xadmin 實(shí)現(xiàn)修改時(shí)間選擇器為不可輸入狀態(tài)
這篇文章主要介紹了使用Django xadmin 實(shí)現(xiàn)修改時(shí)間選擇器為不可輸入狀態(tài),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03
python高并發(fā)異步服務(wù)器核心庫forkcore使用方法
這篇文章主要介紹了python高并發(fā)異步服務(wù)器核心庫forkcore的使用方法,大家參考使用吧2013-11-11
python如何實(shí)現(xiàn)word批量轉(zhuǎn)HTML
這篇文章主要介紹了python如何實(shí)現(xiàn)word批量轉(zhuǎn)HTML,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下2020-09-09

