四、模擬英語四六級答題卡識別閱卷評分

一、思路分析

首先拿到答題卡照片的時候,需要對照片進行一系列預處理操作,通過透視變換將圖像擺正方便后續的操作。每一道題五個選項,有五道題,通過字典存放準確答案。沒有依次對答題卡進行輪廓檢測,這里采用的是正方形,寬高比是1:1,當然也可以是矩形,也可以通過指定其他的篩選進行進行過濾篩選。最后通過掩膜操作,因為用戶所選擇的答案都是被涂過的,也就是通過判斷黑色和白色來進行區分是否是用戶選擇的答案。一行一行的存儲,因為一道題是五個選項,每一行是一道題,這里采用從上到下從左到右分別依次存放1-5題的A-E選項。通過與標準答案字典所存放的索引進行對比,從而給出用戶得分。

二、導包及其相關函數

#導入工具包
import numpy as np
import argparse
import imutils
import cv2
# 設置參數
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,help="path to the input image")
args = vars(ap.parse_args())# 正確答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}#存放正確答案BEADB
def order_points(pts):# 一共4個坐標點rect = np.zeros((4, 2), dtype = "float32")# 按順序找到對應坐標0123分別是 左上,右上,右下,左下# 計算左上,右下s = pts.sum(axis = 1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 計算右上和左下diff = np.diff(pts, axis = 1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef 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")#看個人需求而定,這里將圖像左上角規定為(0,0)位置# 計算變換矩陣M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回變換后結果return warped
def sort_contours(cnts, method="left-to-right"):#從上到下進行排序,因為題目就是一行一行的reverse = Falsei = 0if method == "right-to-left" or method == "bottom-to-top":reverse = Trueif method == "top-to-bottom" or method == "bottom-to-top":i = 1boundingBoxes = [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
def cv_show(name,img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()  

三、對答題卡進行預處理及透視變換擺正

# 預處理
image = cv2.imread(args["image"])#讀取圖像
contours_img = image.copy()#為了不改動原始圖像,copy一下圖像,因為后續需要進行一系列輪廓檢測
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰度圖
blurred = cv2.GaussianBlur(gray, (5, 5), 0)#高斯濾波,去除一些噪音點
cv_show('blurred',blurred)
edged = cv2.Canny(blurred, 75, 200)#Canny邊緣檢測
cv_show('edged',edged)
# 輪廓檢測
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#輪廓檢測完之后會得到三個返回值,這里的[1]存放的是輪廓信息
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) #將圖像通過透視變換進行擺正,繪制出答題卡的大致輪廓,拿到的答題卡圖像也不一定是正兒八經的矩形
cv_show('contours_img',contours_img)
docCnt = None
# 確保檢測到了
if len(cnts) > 0:#因為可能會檢測到其他干擾影響,但是答題卡的輪廓是最大的# 根據輪廓大小進行排序cnts = sorted(cnts, key=cv2.contourArea, reverse=True)#把檢測到的所有輪廓按面積進行排序# 遍歷每一個輪廓for c in cnts:# 近似peri = cv2.arcLength(c, True)#計算一下輪廓周長approx = cv2.approxPolyDP(c, 0.02 * peri, True)#對輪廓進行近似# 準備做透視變換if len(approx) == 4:#多邊形頂點有四個也就是矩形,這個就是我們的答題卡輪廓docCnt = approxbreak
# 執行透視變換
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped',warped)

四、對每一道題均進行輪廓檢測,遍歷篩選

# 自適應閾值處理
thresh = cv2.threshold(warped, 0, 255,	cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_Contours = thresh.copy()
# 找到每一道題輪廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#這里再次進行輪廓檢測,之所以不用霍夫圓檢測是因為有可能答題卡會被全部涂滿甚至越界
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)#因為是圓形,這里是寬高比,外接矩形差不多寬高比是1:1# 根據實際情況指定標準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

五、對比答案,評分

# 每排有5個選項
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):#因為每一題都有五個選項,q為第幾行# 排序cnts = sort_contours(questionCnts[i:i + 5])[0]#第i題的五個結果bubbled = None# 遍歷每一個結果for (j, c) in enumerate(cnts):#j為第i道題的第j個選項# 使用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]#第q道題的答案# 判斷正確if k == bubbled[1]:color = (0, 255, 0)correct += 1# 繪圖cv2.drawContours(warped, [cnts[k]], -1, color, 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("Original", image)
cv2.imshow("Exam", warped)
cv2.waitKey(0)

六、Pycharm參數設定

設置參數指定圖像路徑
在這里插入圖片描述
找到Edit Configurations
將image參數改成自己測試圖像路徑--image images\test11.png,其中images\test11.png為答題卡路徑
在這里插入圖片描述

七、完整代碼

#導入工具包
import numpy as np
import argparse
import imutils
import cv2# 設置參數
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,help="path to the input image")
args = vars(ap.parse_args())# 正確答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}#存放正確答案BEADBdef order_points(pts):# 一共4個坐標點rect = np.zeros((4, 2), dtype = "float32")# 按順序找到對應坐標0123分別是 左上,右上,右下,左下# 計算左上,右下s = pts.sum(axis = 1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 計算右上和左下diff = np.diff(pts, axis = 1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef 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")#看個人需求而定,這里將圖像左上角規定為(0,0)位置# 計算變換矩陣M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回變換后結果return warped
def sort_contours(cnts, method="left-to-right"):#從上到下進行排序,因為題目就是一行一行的reverse = Falsei = 0if method == "right-to-left" or method == "bottom-to-top":reverse = Trueif method == "top-to-bottom" or method == "bottom-to-top":i = 1boundingBoxes = [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
def cv_show(name,img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()  # 預處理
image = cv2.imread(args["image"])#讀取圖像
contours_img = image.copy()#為了不改動原始圖像,copy一下圖像,因為后續需要進行一系列輪廓檢測
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰度圖
blurred = cv2.GaussianBlur(gray, (5, 5), 0)#高斯濾波,去除一些噪音點
cv_show('blurred',blurred)
edged = cv2.Canny(blurred, 75, 200)#Canny邊緣檢測
cv_show('edged',edged)# 輪廓檢測
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#輪廓檢測完之后會得到三個返回值,這里的[1]存放的是輪廓信息
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) #將圖像通過透視變換進行擺正,繪制出答題卡的大致輪廓,拿到的答題卡圖像也不一定是正兒八經的矩形
cv_show('contours_img',contours_img)
docCnt = None# 確保檢測到了
if len(cnts) > 0:#因為可能會檢測到其他干擾影響,但是答題卡的輪廓是最大的# 根據輪廓大小進行排序cnts = sorted(cnts, key=cv2.contourArea, reverse=True)#把檢測到的所有輪廓按面積進行排序# 遍歷每一個輪廓for c in cnts:# 近似peri = cv2.arcLength(c, True)#計算一下輪廓周長approx = cv2.approxPolyDP(c, 0.02 * peri, True)#對輪廓進行近似# 準備做透視變換if len(approx) == 4:#多邊形頂點有四個也就是矩形,這個就是我們的答題卡輪廓docCnt = approxbreak# 執行透視變換
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped',warped)# 自適應閾值處理
thresh = cv2.threshold(warped, 0, 255,	cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_Contours = thresh.copy()# 找到每一道題輪廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#這里再次進行輪廓檢測,之所以不用霍夫圓檢測是因為有可能答題卡會被全部涂滿甚至越界
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)#因為是圓形,這里是寬高比,外接矩形差不多寬高比是1:1# 根據實際情況指定標準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# 每排有5個選項
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):#因為每一題都有五個選項,q為第幾行# 排序cnts = sort_contours(questionCnts[i:i + 5])[0]#第i題的五個結果bubbled = None# 遍歷每一個結果for (j, c) in enumerate(cnts):#j為第i道題的第j個選項# 使用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]#第q道題的答案# 判斷正確if k == bubbled[1]:color = (0, 255, 0)correct += 1# 繪圖cv2.drawContours(warped, [cnts[k]], -1, color, 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("Original", image)
cv2.imshow("Exam", warped)
cv2.waitKey(0)

答題卡原題:
在這里插入圖片描述
這里展示的太大,我就截取了其中一小部分進行展示
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
接下來就是依次對每道題進行遍歷找到掩膜,一共25次,這里就不一一展示了
最后根據設定的字典里面的正確答案給出評分
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/377868.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/377868.shtml
英文地址,請注明出處:http://en.pswp.cn/news/377868.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

leetcode 17. 電話號碼的字母組合 思考分析

題目 給定一個僅包含數字 2-9 的字符串&#xff0c;返回所有它能表示的字母組合。 給出數字到字母的映射如下&#xff08;與電話按鍵相同&#xff09;。注意 1 不對應任何字母。 思考與遞歸程序 解空間樹的寬度是輸入數字對應的字符的個數&#xff0c;深度是輸入的數字的個數…

Blockquotes,引用,html里面,經常用到的一個!

blockquote元素的使用已經非常多樣化&#xff0c;但語義上它只適用于一件事–標記了一段你的網頁被引用從另一來源。這意味著&#xff0c;如果你想讓那些花俏的引文&#xff0c;<blockquote>是不是你應該使用元素。讓我們看一看如何你應該使用此元素&#xff1a; <art…

仔細分析了下這7行,貌似時間復雜度,空間復雜度都不大,為嘛就是執行效率這么低?...

for(Girl girl Girls.first(); !myGirlFriend.like(me); girl Girls.next()){if(!girl.hasBoyFriend(now) && i.like(girl)) { GirlFriend myGirlFriend (GirlFriend)girl; }} 轉載于:https://www.cnblogs.com/naran/archive/2011/12/28/2305467.html…

BHMS的完整形式是什么?

BHMS&#xff1a;順勢療法醫學和外科學士 (BHMS: Bachelor of Homeopathic Medicine and Surgery) BHMS is an abbreviation of Bachelor of Homeopathic Medicine and Surgery. It is a medical degree program for under graduation in Homeopathy; an alternative move towa…

c++編程思想2 --友元存儲控制

友元friend在c中的應用 我們知道在c的類訪問權限中,private和 protected在類外面進行訪問的時候 會因為權限而不能訪問 &#xff0c;友元就解決了這個問題 。 可以這樣理解&#xff0c;他為外部的 函數 或者類 進行了 訪問授權,其實這已經超出OOP的范疇,但是對于C而言是以實用…

WordPress Event Easy Calendar插件多個跨站請求偽造漏洞

漏洞名稱&#xff1a;WordPress Event Easy Calendar插件多個跨站請求偽造漏洞CNNVD編號&#xff1a;CNNVD-201309-083發布時間&#xff1a;2013-09-11更新時間&#xff1a;2013-09-11危害等級&#xff1a; 漏洞類型&#xff1a;跨站請求偽造威脅類型&#xff1a;遠程CVE編號&…

XML轉txt格式腳本

一、東北大學老師收集的鋼材缺陷數據集是XML格式的&#xff0c;但是YOLOv5只允許使用txt文件標簽 例如其中一種缺陷圖片所對應的標簽&#xff1a;crazing_1.xml <annotation><folder>cr</folder><filename>crazing_1.jpg</filename><source&…

python程序生成exe_使用Python程序生成QR代碼的Python程序

python程序生成exeQR code is a short form of the quick response code. It is a type of matrix barcode that contains some information like some specific link, important message, email-id, etc. In Python, the qrcode module is used to generate the QR code of so…

leetcode 242. 有效的字母異位詞 思考分析

題目 給定兩個字符串 s 和 t &#xff0c;編寫一個函數來判斷 t 是否是 s 的字母異位詞。 我們先考慮低階版本&#xff0c;認為字符只有26種可能&#xff0c;然后將a ~ z的字符映射到數組的索引0 ~ 25&#xff0c;數組中存放的則是該索引出現的頻次。 記錄下s的頻次和t的頻次…

總結一下ERP .NET程序員必須掌握的.NET技術,掌握了這些技術工作起來才得心應手...

從畢業做.NET到現在&#xff0c;有好幾年了&#xff0c;自認為只能是達到熟練的水平&#xff0c;談不上精通。所以&#xff0c;總結一下&#xff0c;自己到底熟練掌握了哪些.NET方面的開發技術&#xff0c;以此對照&#xff0c;看看還有哪些不足&#xff0c;歡迎補充。 1 .NET …

js \n直接顯示字符串_顯示N個字符的最短時間

js \n直接顯示字符串Problem statement: 問題陳述&#xff1a; You need to display N similar characters on a screen. You are allowed to do three types of operation each time. 您需要在屏幕上顯示N個相似的字符。 每次允許您執行三種類型的操作。 You can insert a c…

示例 Demo 工程和 API 參考鏈接

Camera Explorer&#xff1a;有關 Windows Phone8 中有關增強 Camera API 的使用。文章鏈接 Filter Effects&#xff1a;對拍攝的照片或者圖片庫中的照片應用 Nokia Imaging SDK 中的濾鏡。文章鏈接 Filter Explorer&#xff1a;演示了對新拍攝圖片或者現有圖片的編輯功能&…

三、標簽準備

所有操作均在anaconda中的自己配置的環境下進行 一、安裝labelimg 因為YOLO模型所需要的樣本標簽必須是txt類型&#xff0c;本人使用labelimg軟件進行對圖像進行打標簽操作。 pip install pycocotools-windows pip install pyqt5 pip install labelimg 通過labelimg命令打…

ubuntu 8.04安裝應用軟件Can't find X includes錯誤解決辦法

系統很小。應用軟件都的自己裝。 首先把 APT’s database is not updated. # apt-get update    # apt-get upgrade 再裝其它軟件。 make xconfigure 無法運行時&#xff1a; apt-get install qt3-dev-tools 編譯QVFB  是出現&#xff1a; 出現&#xff1a;C preproces…

leetcode 39. 組合總和 思考分析

目錄1、題目2、思考分析3、未經優化代碼4、剪枝優化1、題目 給定一個無重復元素的數組 candidates 和一個目標數 target &#xff0c;找出 candidates 中所有可以使數字和為 target 的組合。 candidates 中的數字可以無限制重復被選取。 2、思考分析 解空間樹寬度部分即數…

java uuid靜態方法_Java UUID equals()方法與示例

java uuid靜態方法UUID類equals()方法 (UUID Class equals() method) equals() method is available in java.util package. equals()方法在java.util包中可用。 equals() method is used to check whether this object equals to the given object or not. equals()方法用于檢…

一、機器學習概念

一、何為機器學習(Mechine Learning)&#xff1f; 答&#xff1a;利用已有數據(經驗)&#xff0c;來訓練某種模型&#xff0c;利用此模型來預測未來。機器學習是人工智能的核心Mechine Learning。 例如&#xff1a;你和狗蛋兒7點在老槐樹下集合&#xff0c;如何一塊約去開黑&a…

Java線程新特征——Java并發庫

一、線程池 Sun在Java5中&#xff0c;對Java線程的類庫做了大量的擴展&#xff0c;其中線程池就是Java5的新特征之一&#xff0c;除了線程池之外&#xff0c;還有很多多線程相關的內容&#xff0c;為多線程的編程帶來了極大便利。為了編寫高效穩定可靠的多線程程序&#xff0c;…

第一篇博文

剛剛申請博客&#xff0c;開通了&#xff0c;很高興。但是由于這幾天考試比較多&#xff0c;等考完之后&#xff0c;再開始正式寫博客&#xff0c;與諸君共進步&#xff01; 2012/1/1 18:20 轉載于:https://www.cnblogs.com/zhenglichina/archive/2012/01/01/2309561.html

leetcode 40. 組合總和 II 思考分析

題目 給定一個數組 candidates 和一個目標數 target &#xff0c;找出 candidates 中所有可以使數字和為 target 的組合。 candidates 中的每個數字在每個組合中只能使用一次。 思考以及代碼 如果我們直接套用39題的思路&#xff0c;那么就會出現重復的組合。 重復組合的…