



























明白了,你的场景更加聚焦:识别试题模板中的题号 + 识别手写的数字答案。这是一个相对明确且可落地的任务,我为你提供一个精简实用的方案。
| 子任务 | 推荐方案 | 理由 |
|---|---|---|
如果你的试卷模板固定(同一份试卷多次使用,只是学生手写答案不同),这是最高效的方式:
# 手动标注一次模板中各题号的坐标区域
template_regions = {
1: {"bbox": [x1, y1, x2, y2], "type": "handwritten"},
2: {"bbox": [x1, y1, x2, y2], "type": "handwritten"},
3: {"bbox": [x1, y1, x2, y2], "type": "handwritten"},
# 题号本身的位置(如果需要识别题号是否匹配)
"question_numbers": {
1: [x1, y1, x2, y2],
2: [x1, y1, x2, y2],
}
}
import cv2
import pytesseract
def recognize_question_number(img, bbox):
x1, y1, x2, y2 = bbox
roi = img[y1:y2, x1:x2]
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
# 二值化增强
_, thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)
# OCR识别题号
text = pytesseract.image_to_string(thresh, config='--psm 7 -c tessedit_char_whitelist=0123456789')
return text.strip()
训练一个针对手写数字的CNN分类器(使用MNIST或自有数据):
import tensorflow as tf
from tensorflow import keras
# 构建简单CNN模型
model = keras.Sequential([
keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28,28,1)),
keras.layers.MaxPooling2D(2,2),
keras.layers.Conv2D(64, (3,3), activation='relu'),
keras.layers.MaxPooling2D(2,2),
keras.layers.Flatten(),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax') # 0-9十个类别
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 使用MNIST数据集训练
mnist = keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train.reshape(-1,28,28,1)
x_test = x_test.reshape(-1,28,28,1)
model.fit(x_train, y_train, epochs=5)
model.save('handwritten_digit_model.h5')
def process_answer_sheet(image_path, template_regions):
img = cv2.imread(image_path)
results = {}
# 1. 可选:验证题号是否匹配(确保试卷对齐)
for q_id, bbox in template_regions["question_numbers"].items():
recognized_num = recognize_question_number(img, bbox)
if recognized_num != str(q_id):
print(f"警告:题号{q_id}识别为{recognized_num},可能存在对齐偏差")
# 2. 识别手写答案区域
for q_id, region in template_regions.items():
if q_id == "question_numbers":
continue
x1, y1, x2, y2 = region["bbox"]
roi = img[y1:y2, x1:x2]
# 预处理手写区域
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 提取单个数字(如有多个数字)
digits = extract_digits(binary) # 通过轮廓检测切分数字
# 识别每个数字
answer = ""
for digit_img in digits:
digit_img = cv2.resize(digit_img, (28,28))
digit_img = digit_img.reshape(1,28,28,1) / 255.0
pred = model.predict(digit_img, verbose=0)
answer += str(np.argmax(pred))
results[q_id] = answer
return results
def extract_digits(binary_img):
"""通过轮廓检测切分单个数字"""
contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
digits = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
if w * h > 100: # 过滤噪声
digit = binary_img[y:y+h, x:x+w]
digits.append(digit)
# 按x坐标排序
digits.sort(key=lambda d: cv2.boundingRect(d)[0])
return digits
如果不想训练CNN模型,PaddleOCR本身对数字识别效果也很好:
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=False, lang='en') # 英文模式,数字识别更好
def recognize_digit_region(roi):
result = ocr.ocr(roi, cls=False)
if result and result[0]:
text = result[0][0][1][0] # 提取识别的文本
# 只保留数字
digits = ''.join([c for c in text if c.isdigit()])
return digits
return ""
优点:无需训练,开箱即用
缺点:对于潦草手写数字,准确率可能不如定制CNN
| 问题 | 优化方法 |
|---|---|
如果选择训练自己的CNN模型,建议收集真实场景的手写数字:
答题卡图像
↓
透视变换校正(可选)
↓
模板匹配/坐标映射
↓
┌─────────────────┐
│ 题号区域OCR验证 │ → 确认试卷对齐
└─────────────────┘
↓
┌─────────────────┐
│ 手写答案区域 │
│ - 预处理二值化 │
│ - 轮廓检测切分 │
│ - CNN逐数字识别 │
└─────────────────┘
↓
结果拼接与校验
↓
输出 {题号: 答案}
你的试卷模板是完全固定的(每次布局一样),还是会有变化?如果是完全固定的,用模板坐标映射是最省事的,我可以帮你写一个完整的识别脚本。如果是多变的,可能需要加入YOLO做区域检测,但工作量会大一些。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。