答題卡自動識別案例

目錄

1.答題卡自動批閱整體實現思路

2.關鍵技術步驟與原理

答題卡區域提取

①輪廓檢測并排序

②執行透視變換

③找到每一個圓圈輪廓

④先對所有圓圈輪廓從上到下排序

⑤再通過循環每次只提取出五個輪廓再進行從左到右的排序

3.完整代碼


1.答題卡自動批閱整體實現思路

  • 首先,通過圖像處理技術將含答題卡的圖片中的答題區域單獨提取出來,使其變為一個標準的矩形。
  • 然后,針對處理后的答題卡區域進行分析,逐一識別每一道題目的各個選項(A, B, C, D, E)是否被學生正確涂黑。
  • 最終,根據判斷結果計算出學生的得分,并以百分比和具體答案串等形式展示。

2.關鍵技術步驟與原理

答題卡區域提取

該過程采用“輪廓查找”與“透視變換”。通過灰度化、二值化等預處理,利用cv2.findContours找到答題卡的最外層輪廓,隨后對輪廓進行近似得到四個角點,最后應用透視變換將答題卡區域整齊地裁剪出來。

image=cv2.imread(r'./images/test_04.png')
contours_img=image.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)
cv_show('edged',edged)

簡單的預處理操作

①輪廓檢測并排序
  • 首先通過輪廓的邊界框(bounding box)對所有選項輪廓進行從大到小的初步排序。
#輪廓檢測
cnts=cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show('contours_img',contours_img)
donCnt=None
#根據輪廓大小排序,準備透視變換
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:donCnt=approxbreak

approx的長度必須為四,得到答題卡區域輪廓的四個近似點坐標矩陣數組

②執行透視變換
#執行透視變換
warped_t=four_point_transform(image,donCnt.reshape(4,2))
warped_new=warped_t.copy()
cv_show('warped_t',warped_t)

二值化

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,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
warped_Contours=cv2.drawContours(warped_t,cnts,-1,(0,255,0),1)
cv_show('warped_Contours',warped_Contours)

對所有輪廓做外接矩形條件判斷,篩選出所有圓形答題輪廓共25個

questionCnts=[]
for c in cnts:(x,y,w,h)=cv2.boundingRect(c)ar=w/float(h)if w>=20 and h>=20 and 0.9<=ar<=1.1:questionCnts.append(c)
print(len(questionCnts))#25
④先對所有圓圈輪廓從上到下排序
questionCnts=sort_contours(questionCnts,method='top-to-bottom')[0]
correct=0
⑤再通過循環每次只提取出五個輪廓再進行從左到右的排序
#每排有五個選項
for (q,i) in enumerate(np.arange(0,len(questionCnts),5)):cnts=sort_contours(questionCnts[i:i+5])[0]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)進行與操作,只保留mask位置中的內容thresh_mask_and=cv2.bitwise_and(thresh,thresh,mask=mask)cv_show('thresh_mask_and',thresh_mask_and)total=cv2.countNonZero(thresh_mask_and)#統計灰度值不為零的像素數量if bubbled is None or total>bubbled[0]:bubbled=(total,j)#對比正確答案color=(0,0,255)answer=ANSWER_KRY[q]if answer==bubbled[1]:#判斷正確color=(0,255,0)correct+=1cv2.drawContours(warped_new,[cnts[answer]],-1,color,3)cv_show('warpeding',warped_new)

選項識別與答案分析

  • 使用逐個建立掩膜(mask)的方式,從原圖中精確摳取出每個選項的圖像。
  • 計算摳取出的每個選項區域內的白色像素點數量。
  • 通過比較不同選項的白色像素點數量,確定學生實際作答的選項,數量最多的即為作答的區域。

?標準答案比對與評分

  • 系統內部維護一個標準答案對照表(answer key),記錄每道題的正確選項。
    ANSWER_KRY={0:1,1:4,2:0,3:3,4:1}#正確答案
  • 將程序識別出的答案與標準答案進行比對。
  • 答案正確,則計為1分,并使用綠色邊框對正確選項進行標注;若答案錯誤,則不加分,并用紅色邊框標注正確選項。
  • 最終,系統將根據總分和題目數量計算出最終得分并在圖片上顯示。
score=(correct/5.0)*100
print('[INFO] score:{:.2f}%'.format(score))
cv2.putText(warped_new,'{:.2f}%'.format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv_show('Original',image)
cv_show('Exam',warped_new)#[INFO] score:20.00%

3.完整代碼

import cv2
import numpy as npdef order_points(pts):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 rect
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)#M是獲取到的轉換之間的關系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 a:a[1][i],reverse=reverse))return cnts,boundingBoxes
def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)ANSWER_KRY={0:1,1:4,2:0,3:3,4:1}#正確答案image=cv2.imread(r'./images/test_04.png')
contours_img=image.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)
cv_show('edged',edged)
#輪廓檢測
cnts=cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show('contours_img',contours_img)
donCnt=None
#根據輪廓大小排序,準備透視變換
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:donCnt=approxbreak
#執行透視變換
warped_t=four_point_transform(image,donCnt.reshape(4,2))
warped_new=warped_t.copy()
cv_show('warped_t',warped_t)
warped=cv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)
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,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
warped_Contours=cv2.drawContours(warped_t,cnts,-1,(0,255,0),1)
cv_show('warped_Contours',warped_Contours)questionCnts=[]
for c in cnts:(x,y,w,h)=cv2.boundingRect(c)ar=w/float(h)if w>=20 and h>=20 and 0.9<=ar<=1.1:questionCnts.append(c)
print(len(questionCnts))
#按照從上到下進行排序
questionCnts=sort_contours(questionCnts,method='top-to-bottom')[0]
correct=0
#每排有五個選項
for (q,i) in enumerate(np.arange(0,len(questionCnts),5)):cnts=sort_contours(questionCnts[i:i+5])[0]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)進行與操作,只保留mask位置中的內容thresh_mask_and=cv2.bitwise_and(thresh,thresh,mask=mask)cv_show('thresh_mask_and',thresh_mask_and)total=cv2.countNonZero(thresh_mask_and)#統計灰度值不為零的像素數量if bubbled is None or total>bubbled[0]:bubbled=(total,j)#對比正確答案color=(0,0,255)answer=ANSWER_KRY[q]if answer==bubbled[1]:#判斷正確color=(0,255,0)correct+=1cv2.drawContours(warped_new,[cnts[answer]],-1,color,3)cv_show('warpeding',warped_new)
score=(correct/5.0)*100
print('[INFO] score:{:.2f}%'.format(score))
cv2.putText(warped_new,'{:.2f}%'.format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv_show('Original',image)
cv_show('Exam',warped_new)

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

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

相關文章

C#實現通過POST實現讀取數據

C# POST請求與MySQL數據存儲實現下面是一個完整的C#解決方案&#xff0c;用于發送POST請求、接收響應數據&#xff0c;并將數據保存到MySQL數據庫中。完整代碼實現 using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.J…

Java 字符編碼問題,怎么優雅地解決?

網羅開發&#xff08;小紅書、快手、視頻號同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企業從事人工智能項目研發管理工作&#xff0c;平時熱衷于分享各種編程領域的軟硬技能知識以及前沿技術&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

STL之string類(C++)

1.string類核心定位std::string 本質是對 “字符序列” 的封裝&#xff0c;內部通過動態數組存儲字符&#xff0c;并自動管理內存&#xff08;分配、擴容、釋放&#xff09;&#xff0c;對外提供了簡潔的接口用于字符串的創建、修改、拼接、查找等操作。1.1 使用前提頭文件包含…

[Maven 基礎課程]第一個 Maven 項目

idea 新建一個項目&#xff1a; 來到 New Project 頁面&#xff1a; 這里我們有兩種方式創建 maven 項目&#xff0c;一種是自定義創建&#xff0c;另一種是使用 maven 模版項目創建。 自定義創建 maven 項目 基本配置 Name: first_maven_project 項目名稱&#xff0c;設為 …

uni小程序中使用Echarts圖表

前言 今天雞米花給大家帶來的是在uni里面使用echarts&#xff0c;能夠完美支持和PC端一樣的效果&#xff0c;我這邊的工程是uni轉為微信小程序&#xff0c;用的是vue3vite來寫的&#xff0c;然后實現了豎屏和橫屏的展示方式&#xff0c;好了獻上效果圖。 效果圖 一、引入插件 這…

從FOTA測試到汽車電子安全體系的啟蒙之旅

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 做到欲望極簡,了解自己的真實欲望,不受外在潮流的影響,不盲從,不跟風。把自己的精力全部用在自己。一是去掉多余,凡事找規律,基礎是誠信;二是…

stm32中 中斷和事件的區別

一、核心概念比喻想象一下工廠里的一個報警系統&#xff1a;?中斷 (Interrupt)??&#xff1a;就像火警警報器響了。它的目的是通知管理員&#xff08;CPU&#xff09;??&#xff1a;“著火了&#xff01;”。管理員聽到后&#xff0c;會停下手中的工作&#xff08;保存現場…

深入理解MySQL主從架構中的Seconds_Behind_Master指標

問題&#xff1a;主從延遲與寫后讀不一致 在典型的 MySQL 主從架構下&#xff0c;所有寫操作都會直接進入主庫&#xff0c;而讀操作大多分流到從庫&#xff0c;從而實現讀寫分離&#xff0c;緩解主庫壓力。 然而 MySQL 的復制機制是異步的&#xff1a;主庫先寫入 binlog&#…

MySQL安裝(linux版本)

MySQL安裝&#xff08;linux版本&#xff09; 課程地址 08. 進階-MySQL安裝(linux版本)_嗶哩嗶哩_bilibili 安裝過程中所有需要的程序都放在網盤里了 通過網盤分享的文件&#xff1a;虛擬機 鏈接: https://pan.baidu.com/s/1eLMD2iq1uEujNN7mWs2dIg?pwdckmh 提取碼: ckmh …

OpenCV 圖像雙三次BSpline插值

文章目錄 一、簡介 二、實現代碼 三、實現效果 參考資料 一、簡介 之前我們介紹過BSpline曲線,一條B樣條曲線可以被定義成 n + 1 n+1 n+1個控制點的集合 { Q i } i = 0 n {\{Q_i\}}^{n}_{i=0}

Prometheus+Grafana構建企業級監控方案

1.prometheus工作原理&#xff1a; Prometheus將指標收集并存儲為時間序列數據庫&#xff08;時序數據庫&#xff09;&#xff0c;即指標信息與記錄它的時間戳一起存儲&#xff0c;以及稱為標簽的可選鍵值對。 特性&#xff1a; 具有由指標名稱和鍵/值對識別的時間序列數據的…

第23課:行業解決方案設計

第23課:行業解決方案設計 課程目標 掌握金融、醫療、教育等行業應用 學習領域特定Agent設計 了解行業標準集成 實踐設計行業解決方案 課程內容 23.1 金融行業解決方案 金融Agent系統 class FinancialAgentSystem {constructor() {this.agents =

Go語言快速入門教程(JAVA轉go)——2 環境搭建與入門

安裝go Go官網下載地址&#xff1a;https://golang.org/dl/ 中國區官方鏡像站&#xff08;推薦&#xff09;&#xff1a;https://golang.google.cn/dl/ windows安裝 下載好后選擇安裝路徑即可&#xff0c;安裝完成后&#xff0c;winr 輸入cmd調出命令行窗口&#xff0c;輸入…

ffplay播放pcm

用 ffplay 播放 PCM 裸流時&#xff0c;必須手動告訴它“沒有封裝頭、采樣率、聲道數、采樣格式”四個關鍵點。命令模板如下&#xff1a; ffplay -f <采樣格式> -ar <采樣率> -ac <聲道數> -i <pcm文件>常用組合示例 48 kHz、16 bit、小端、雙聲道 ffp…

【LLM】大模型訓練中的穩定性問題

訓練穩定性問題 &#x1f4cb; 概述 本文檔詳細介紹了在項目中解決訓練穩定性問題的方法、原理分析以及實際應用。涵蓋了梯度裁剪、損失函數優化、數值穩定化處理和學習率調度等關鍵技術。&#x1f6a8; 問題描述 現象: 訓練過程中出現數值不穩定&#xff0c;損失函數波動劇烈 …

【linux系統】6. 基礎開發工具(一)

一. 軟件包管理器 1&#xff09;Linux下安裝軟件的常用方法 1. 源代碼安裝 下載程序的源代碼&#xff0c;本地編譯成二進制文件&#xff0c;拷貝到系統指定路徑下。 2. rpm包安裝 已經編譯好的安裝包&#xff0c;使用rpm對應的指令去安裝&#xff0c;也比較麻煩。 3. 包…

ffplay數據結構分析

struct VideoState 播放器封裝 typedef struct VideoState {SDL_Thread *read_tid; // 讀線程句柄AVInputFormat *iformat; // 指向demuxerint abort_request; // 1時請求退出播放int force_refresh; // 1時刷新畫面&#xff0c;請求立即刷新畫面的意思int paused; …

OpenCV:銀行卡號識別

目錄 一、項目原理與核心技術 二、環境準備與工具包導入 1. 環境依賴 2. 工具包導入 三、自定義工具類 myutils.py 實現 四、主程序核心流程&#xff08;銀行卡識別.py&#xff09; 1. 命令行參數設置 2. 銀行卡類型映射 3. 輔助函數&#xff1a;圖像展示 五、步驟 1…

基于spark的澳洲光伏發電站選址預測

基于spark的澳洲光伏發電站選址預測項目概況 [&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;] 點這里,查看所有項目 [&#x1f446;&#x1f446;&#x1f446;&#x1f446;&#x1f446;&#x1f446;&#x…

Kibana 雙棧網絡(Dual-Stack)支持能力評估

#作者&#xff1a;Unstopabler 文章目錄一&#xff0e;測試目標二&#xff0e;測試環境三&#xff0e;Kibana1、查詢 Kibana pod信息2、查詢Kibana service信息3、Kibana service 設置四&#xff0e;驗證測試1、Kibana 監聽參數設置2、Kibana節點IPv4狀態檢查3、Kibana節點IPv6…