基于OpenCV的答題卡自動識別與評分系統

引言

在教育考試場景中,手動批改答題卡效率低下且容易出錯。本文將介紹如何使用Python和OpenCV實現一個答題卡自動識別與評分系統,通過計算機視覺技術完成答題卡的透視校正、選項識別和得分計算。該系統可廣泛應用于學校考試、培訓測評等場景,大幅提升批改效率。


環境準備

  • Python 3.7+
  • OpenCVpip install opencv-python
  • NumPypip install numpy

核心功能模塊解析

整個系統分為以下核心步驟:
圖像預處理 → 輪廓檢測 → 透視變換 → 選項定位 → 答案匹配 → 結果輸出

1. 導入依賴庫

import numpy as np
import cv2
  • numpy:用于數值計算和數組操作。
  • cv2:OpenCV庫,提供圖像處理和計算機視覺算法。

2. 輔助函數定義

2.1 坐標點排序函數 order_points

答題卡的四個角點需要按左上→右上→右下→左下的順序排列,否則透視變換會出錯。
該函數通過計算坐標的和與差實現排序:

def order_points(pts):rect = np.zeros((4, 2), dtype="float32")s = pts.sum(axis=1)  # 計算(x+y),左上角點(x+y)最小,右下角點最大rect[0] = pts[np.argmin(s)]  # 左上rect[2] = pts[np.argmax(s)]  # 右下diff = np.diff(pts, axis=1)  # 計算(y-x),右上角點(y-x)最小(接近0),左下角點最大rect[1] = pts[np.argmin(diff)]  # 右上rect[3] = pts[np.argmax(diff)]  # 左下return rect
2.2 透視變換函數 four_point_transform

通過透視變換將傾斜的答題卡校正為正視圖。
關鍵步驟:

  1. 計算變換前的四個角點(rect)。
  2. 確定變換后的目標尺寸(maxWidthmaxHeight)。
  3. 生成透視變換矩陣(M)并應用變換。
def four_point_transform(image, pts):rect = order_points(pts)(tl, tr, br, bl) = rect  # 解包四個角點# 計算變換后的寬度和高度(取兩組對邊的最大值)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
2.3 輪廓排序函數 sort_contours

檢測到的選項輪廓需要按從上到下、從左到右排序,以便逐題匹配答案。
支持多種排序方式(左→右、右→左、上→下、下→上):

def sort_contours(cnts, method="left-to-right"):reverse = Falsei = 0  # 排序依據:0為x軸(左右),1為y軸(上下)if method in ["right-to-left", "bottom-to-top"]:reverse = Trueif method in ["top-to-bottom", "bottom-to-top"]:i = 1# 計算每個輪廓的包圍盒(x,y,w,h)boundingBoxes = [cv2.boundingRect(c) for c in cnts]# 按包圍盒的指定維度排序(x或y坐標)(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))return cnts, boundingBoxes
2.4 圖像顯示函數 cv_show

調試時用于顯示中間結果:

def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()

3. 圖像預處理與輪廓檢測

3.1 讀取圖像并灰度化
image = cv2.imread("images/test_01.png")  # 替換為你的答題卡路徑
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 灰度化減少計算量
3.2 高斯模糊去噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)  # 5x5核,σ=0
cv_show('blurred', blurred)  # 調試:觀察模糊效果

高斯模糊可消除圖像中的高頻噪聲(如紙張紋理、光照不均),避免后續邊緣檢測出現偽影。

3.3 Canny邊緣檢測
edged = cv2.Canny(blurred, 75, 200)  # 閾值75和200
cv_show('edged', edged)  # 調試:觀察邊緣輪廓

Canny算法通過梯度計算提取圖像邊緣,參數75200分別為低閾值和高閾值,用于區分強邊緣和弱邊緣。

3.4 輪廓檢測與篩選
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]  # 獲取外層輪廓
# 按面積降序排序(最大的輪廓通常是答題卡)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
# 尋找近似四邊形(答題卡的四個角點)
docCnt = None
for c in cnts:peri = cv2.arcLength(c, True)  # 計算輪廓周長approx = cv2.approxPolyDP(c, 0.02 * peri, True)  # 多邊形近似(精度0.02倍周長)if len(approx) == 4:  # 篩選四邊形docCnt = approxbreak
  • cv2.findContours:檢測圖像中的輪廓,RETR_EXTERNAL表示只檢測外層輪廓。
  • approxPolyDP:通過道格拉斯-普克算法簡化輪廓,保留關鍵頂點(答題卡的四個角點)。

4. 透視變換校正答題卡

warped_t = four_point_transform(image, docCnt.reshape(4, 2))  # 應用透視變換
cv_show('warped', warped_t)  # 調試:觀察校正后的答題卡

通過four_point_transform函數,傾斜的答題卡被校正為正視圖,便于后續選項定位。


5. 選項區域定位與識別

5.1 二值化處理
warped = cv2.cvtColor(warped_t, cv2.COLOR_BGR2GRAY)  # 轉為灰度圖
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]  # Otsu自適應閾值
cv_show('thresh', thresh)  # 調試:觀察二值化結果
  • THRESH_BINARY_INV:反轉二值化結果(選項填涂區域為白色,背景為黑色)。
  • THRESH_OTSU:自動計算最佳閾值,適應不同光照條件。
5.2 篩選選項輪廓
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]  # 檢測選項輪廓
questionCnts = []
for c in cnts:(x, y, w, h) = cv2.boundingRect(c)ar = w / float(h)  # 計算寬高比# 篩選條件:尺寸足夠大且接近正方形(0.9≤寬高比≤1.1)if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:questionCnts.append(c)
print(f"檢測到{len(questionCnts)}個選項輪廓")

通過寬高比(接近1)和最小尺寸(避免噪聲)篩選出有效選項輪廓。

5.3 按題目分組排序
questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0]  # 按從上到下排序

假設每道題有5個選項,按行分組后逐題處理。


6. 答案匹配與評分

6.1 定義正確答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}  # 鍵為題號,值為正確選項索引(0~4)

根據實際題目修改ANSWER_KEY,例如第0題正確選項是第1個(索引從0開始)。

6.2 逐題識別答案
correct = 0
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):  # 每5個選項為一題cnts = sort_contours(questionCnts[i:i+5])[0]  # 當前題的5個選項(按左→右排序)bubbled = None  # 記錄當前題填涂最深的選項for (j, c) in enumerate(cnts):  # 遍歷每個選項# 創建掩膜(僅保留當前選項區域)mask = np.zeros(thresh.shape, dtype="uint8")cv2.drawContours(mask, [c], -1, 255, -1)  # -1表示填充輪廓內部# 計算掩膜區域的非零像素數(填涂程度)thresh_mask_and = cv2.bitwise_and(thresh, thresh, mask=mask)total = cv2.countNonZero(thresh_mask_and)# 更新填涂最深的選項if bubbled is None or total > bubbled[0]:bubbled = (total, j)# 匹配正確答案k = ANSWER_KEY[q]  # 當前題的正確選項索引if k == bubbled[1]:  # 填涂選項與正確答案一致color = (0, 255, 0)  # 綠色標記正確correct += 1else:color = (0, 0, 255)  # 紅色標記錯誤# 在校正后的圖像上繪制結果cv2.drawContours(warped_t, [cnts[k]], -1, color, 3)
  • 掩膜技術:通過mask僅保留當前選項的區域,統計該區域的白色像素數(填涂程度),像素數最多的選項即為填涂答案。
  • 結果可視化:正確選項用綠色框標記,錯誤選項用紅色框標記。
6.3 計算得分并輸出
score = (correct / len(ANSWER_KEY)) * 100  # 總題數為ANSWER_KEY的長度
print(f"[INFO] 得分: {score:.2f}%")
# 在圖像上顯示得分
cv2.putText(warped_t, f"{score:.2f}%", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Result", warped_t)
cv2.waitKey(0)

運行結果示例

假設測試圖像test_01.png是一張5題的答題卡,其中3題正確,2題錯誤,則輸出:

檢測到25個選項輪廓
[INFO] 得分: 60.00%

最終圖像會顯示校正后的答題卡,正確選項為綠色框,錯誤選項為紅色框,并標注得分。


注意事項與改進方向

  1. 圖像質量:確保答題卡光照均勻、無遮擋,否則可能導致輪廓檢測失敗。
  2. 答案鍵配置:需根據實際題目修改ANSWER_KEY字典。
  3. 魯棒性優化:可添加輪廓面積過濾、傾斜角度校正等功能,適應更復雜的拍攝場景。
  4. 多題型支持:當前僅支持單選題,可擴展支持多選題(通過統計多個最高像素數的選項)。

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

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

相關文章

LLaMA-MoE v2:基于后訓練混合專家模型的稀疏性探索與技術突破

重新定義大型語言模型的效率邊界在人工智能飛速發展的今天&#xff0c;大型語言模型&#xff08;LLMs&#xff09;已成為推動技術進步的核心力量。然而&#xff0c;模型規模的不斷擴大帶來了驚人的計算成本和高昂的部署門檻&#xff0c;使得眾多研究機構和中小型企業難以承擔。…

R geo 然后讀取數據的時候 make.names(vnames, unique = TRUE): invalid multibyte string 9

setwd("K:/download/geo") # 替換為實際工作目錄 # 修改get_geo_data_local函數中的讀取部分 #file_path <- "K:/download/geo/raw_data/GEO/GSE32967_series_matrix_fixed.txt" file_path <- "K:/download/geo/data/GSE32967_series_matrix.t…

深入理解 Spring @Async 注解:原理、實現與實踐

在現代 Java 應用開發中&#xff0c;異步編程是提升系統吞吐量和響應速度的關鍵技術之一。Spring 框架提供的Async注解極大簡化了異步方法的實現&#xff0c;讓開發者無需手動管理線程即可輕松實現異步操作。本文將從底層原理到實際應用&#xff0c;全面解析Async注解的工作機制…

linux C 語言開發 (七) 文件 IO 和標準 IO

文章的目的為了記錄使用C語言進行linux 開發學習的經歷。開發流程和要點有些記憶模糊&#xff0c;趕緊記錄&#xff0c;防止忘記。 相關鏈接&#xff1a; linux C 語言開發 (一) Window下用gcc編譯和gdb調試 linux C 語言開發 (二) VsCode遠程開發 linux linux C 語言開發 (…

maven , mvn 運行 項目

提示&#xff1a;環境搭建 文章目錄前言一、使用步驟1. 以構建含有 pom.xml 的項目2.mvn 運行具體項目3.mvn 指定模塊>運行具體項目前言 提示&#xff1a;版本 spirngboot 3.2 jdk 21 mvn 3.9 提示&#xff1a;以下是本篇文章正文內容&#xff0c;下面案例可供參考 一、使…

JVM垃圾回收的時機是什么時候(深入理解 JVM 垃圾回收時機:什么時候會觸發 GC?)

深入理解 JVM 垃圾回收時機&#xff1a;什么時候會觸發 GC&#xff1f;在 Java 開發中&#xff0c;我們常聽說 “JVM 會自動進行垃圾回收”&#xff0c;但很少有人能說清&#xff1a;GC 究竟在什么情況下會被觸發&#xff1f;是到固定時間就執行&#xff1f;還是內存滿了才會啟…

在Vue項目中Axios發起請求時的小知識

在Vue項目中Axios發起請求時的小知識 在Vue項目開發中&#xff0c;Axios作為基于Promise的HTTP客戶端&#xff0c;憑借其簡潔的API設計和強大的功能&#xff08;如請求/響應攔截、自動JSON轉換、取消請求等&#xff09;&#xff0c;已成為前端與后端通信的主流選擇。本文將深入…

GeoHash分級索引技術

GeoHash分級索引技術是一種將二維地理坐標轉換為一維字符串的空間索引方法,其核心是通過分級網格劃分和前綴編碼實現高效的空間數據檢索。以下從技術原理、實現細節到工程優化展開詳細解析: 一、編碼原理與分級結構 1. 經緯度二進制化 GeoHash通過遞歸二分地球表面生成網格…

HTML HTML基礎(4)

1.列表 (1).有序列表 概念&#xff1a;有順序或側重順序的列表。 <h2>要把大象放冰箱總共分幾步</h2> <ol> <li>把冰箱門打開</li> <li>把大象放進去</li> <li>把冰箱門關上</li> </ol> (2).無序列表 概念&a…

MySQL中的回表操作

在數據庫查詢&#xff08;尤其是基于 B樹索引 的關系型數據庫&#xff0c;如MySQL、PostgreSQL&#xff09;中&#xff0c;“回表”是一個核心且高頻出現的概念&#xff0c;直接影響查詢性能。要理解回表&#xff0c;需先理清索引結構與數據存儲的關聯&#xff0c;再拆解其發生…

QT子線程與GUI線程安全交互

在Qt應用程序開發中&#xff0c;涉及到多線程處理時&#xff0c;如何安全地從子線程更新UI界面是一個常見的問題。Qt的UI界面并不是線程安全的&#xff0c;意味著你不能直接在子線程中操作UI組件&#xff08;比如按鈕、標簽等&#xff09;。如果不遵循線程安全的規則&#xff0…

RL【10-2】:Actor - Critic

系列文章目錄 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

開源大模型天花板?DeepSeek-V3 6710億參數MoE架構深度拆解

文章目錄認知解構&#xff1a;DeepSeek的定位與核心價值模型概述與發展歷程創立初期與技術奠基&#xff08;2023年7月-2024年11月&#xff09;里程碑一&#xff1a;MoE架構規模化突破&#xff08;2024年12月&#xff09;里程碑二&#xff1a;推理成本革命性優化&#xff08;202…

10 訓練中的一些問題

&#x1f31f; 大背景&#xff1a;訓練神經網絡 下山尋寶 訓練神經網絡就像你蒙著眼在一座大山里&#xff0c;想找最低點&#xff08;最小損失&#xff09;。你只能靠腳下的坡度&#xff08;梯度&#xff09;來決定往哪兒走。 你的位置 模型參數&#xff08;權重 www&#xf…

synchronized鎖升級的過程(從無鎖到偏向鎖,再到輕量級鎖,最后到重量級鎖的一個過程)

鎖升級是 Java 中 synchronized 鎖 的核心優化機制&#xff08;基于 JVM 的 對象頭 Mark Word 實現&#xff09;&#xff0c;指鎖的狀態從 無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖 逐步升級的過程。其目的是通過 “按需升級”&#xff0c;在不同并發場景下選擇最優的鎖實現&am…

HOT100--Day25--84. 柱狀圖中最大的矩形,215. 數組中的第K個最大元素,347. 前 K 個高頻元素

HOT100–Day25–84. 柱狀圖中最大的矩形&#xff0c;215. 數組中的第K個最大元素&#xff0c;347. 前 K 個高頻元素 每日刷題系列。今天的題目是《力扣HOT100》題單。 題目類型&#xff1a;棧&#xff0c;堆。 84. 柱狀圖中最大的矩形 思路&#xff1a; class Solution {publ…

基于 Apache Doris 的用戶畫像數據模型設計方案

一、 需求分析與設計目標數據源&#xff1a;用戶基本信息&#xff1a;用戶ID、性別、出生日期、注冊時間、常駐地域&#xff08;省、市、區&#xff09;、職業等。用戶體檢報告&#xff1a;每次體檢的報告ID、體檢時間、各項指標&#xff08;如血壓、血糖、血脂、BMI等&#xf…

Python的深度學習

深入理解Python高級特性掌握Python的高級特性是進階的關鍵&#xff0c;包括裝飾器、生成器、上下文管理器、元類等。這些特性能夠提升代碼的靈活性和效率。例如&#xff0c;裝飾器可以用于實現AOP&#xff08;面向切面編程&#xff09;&#xff0c;生成器可以處理大數據流而無需…

數據庫范式(Normalization)

一個設計混亂的數據庫就像一個雜亂的房間&#xff0c;用起來非常不方便&#xff1a;東西到處亂放&#xff08;數據冗余&#xff09;&#xff0c;找件東西要翻遍所有角落&#xff08;查詢困難&#xff09;&#xff0c;扔掉一把舊椅子時&#xff0c;可能會把搭在上面的唯一一件外…

數據結構---循環隊列

基于循環數組實現的循環隊列解決了順序隊列中的假溢出導致的空間浪費問題操作&#xff1a;&#xff08;1&#xff09;初始化//循環隊列 typedef struct {int *data;//指針模擬聲明數組int head,tail;//隊頭&#xff0c;隊尾 }Queue; //初始化 Queue *InitQueue() {Queue *q (Q…