【从小项目学图片处理】#1 答题卡识别

2023-11-19

说明:项目皆参考于网上,代码也有大部分参考原文。仅用于学习和练习图像处理操作。
项目原文: Bubble sheet multiple choice scanner and test grader using OMR, Python and OpenCV

问题描述:
给定下面一张答题卡,识别并依次输出被涂黑的字母 BEACB。
在这里插入图片描述
解决思路/步骤:

  • 1# 识别图像中答题卡部分,通过透视变换,将图像摆正。
  • 2# 识别并提取答题卡中圆形部分。
  • 3# 判断被涂黑的部分,输出被涂黑的字母。

代码实现:

  • 用c++实现
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

bool cmp1(Rect a, Rect b){
	return (a.y<=b.y);
}
bool cmp2(Rect a, Rect b){
	return (a.x<=b.x);
}

int main(int argc, char** argv){
	if(argc==1) {
		cout << "Usage: ProgramName PicturnFile" << endl;
		return -1;
	}
	Mat imgSrc = imread(argv[1]);	
	if(imgSrc.empty()) {
		cout << "Failed to load image!" << endl;
		return -1;
	}

	Mat img = Mat::zeros(imgSrc.size(),imgSrc.type());
	cvtColor(imgSrc, img, COLOR_BGR2GRAY);
	Mat gray = img.clone();
	GaussianBlur(img, img, Size(5,5), 0);
	Canny(img, img, 75, 200);

	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(img, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

//	Mat test = Mat::zeros(img.size(), img.type());
//	drawContours(test, contours, -1, Scalar(255), 2);
	int k = 0;
	float area = 0;
	for(int i=0; i<contours.size(); i++){
		float Area = contourArea(contours[i]);
		if(area <= Area) {
			area = Area;
			k = i;
		}
	}
	
	vector<Point2f> p;
	double len;
	len = arcLength(contours[k], true);
	approxPolyDP(contours[k], p, len * 0.02, true);
	
	int height = max((int)sqrt((p[0].y-p[1].y)*(p[0].y-p[1].y)),(int)sqrt((p[2].y-p[3].y)*(p[2].y-p[3].y)));
	int width = max((int)sqrt((p[0].x-p[3].x)*(p[0].x-p[3].x)),(int)sqrt((p[2].x-p[1].x)*(p[2].x-p[1].x)));
	
	vector<Point2f> pdst = {Point2f(0,0), Point2f(0,width-1), Point2f(height-1,width-1), Point2f(height-1,0)};

	Mat m(3, 3, CV_32F);
	m = getPerspectiveTransform(p, pdst);
	warpPerspective(gray, img, m, Size(height, width));

	threshold(img, img, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	contours.clear();
	findContours(img, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	vector<Rect> rec;
	for(int i=0; i<contours.size(); i++){
		Rect rect = boundingRect(contours[i]);
		float ar = rect.width/(float)rect.height;
		if(rect.width >= 20 && rect.height >= 20 ){
			rec.push_back(rect);
		}
	}

	sort(rec.begin(), rec.end(), cmp1);
	for(vector<Rect>::iterator it=rec.begin(); it!=rec.end(); it+=5){
		sort(it, it+5, cmp2);
	}
	
	vector<int> ans(5,0);
	for(int i=0; i<5; i++){
		int maxcount = 0;
		for(int j=0; j<5; j++){
			Mat mask = Mat::zeros(img.size(),img.type());
			rectangle(mask, rec[i*5+j], Scalar(255), -1);
			bitwise_and(mask, img, mask);
			int count = countNonZero(mask);
			if(count >= maxcount){
				maxcount = count;
				ans[i] = j;	
			}
		}
	}
	imshow("img",img);
	waitKey(0);
	for(int i=0; i<ans.size(); i++){
		char out= 'A'+ans[i];
		cout << out;
	}

	return 0;
}
  • 用python实现(源项目代码)
# import the necessary packages
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
	help="path to the input image")
args = vars(ap.parse_args())
# define the answer key which maps the question number
# to the correct answer
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
# load the image, convert it to grayscale, blur it
# slightly, then find edges
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)
# find contours in the edge map, then initialize
# the contour that corresponds to the document
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
docCnt = None
# ensure that at least one contour was found
if len(cnts) > 0:
	# sort the contours according to their size in
	# descending order
	cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
	# loop over the sorted contours
	for c in cnts:
		# approximate the contour
		peri = cv2.arcLength(c, True)
		approx = cv2.approxPolyDP(c, 0.02 * peri, True)
		# if our approximated contour has four points,
		# then we can assume we have found the paper
		if len(approx) == 4:
			docCnt = approx
			break
# apply a four point perspective transform to both the
# original image and grayscale image to obtain a top-down
# birds eye view of the paper
paper = four_point_transform(image, docCnt.reshape(4, 2))
warped = four_point_transform(gray, docCnt.reshape(4, 2))
# apply Otsu's thresholding method to binarize the warped
# piece of paper
thresh = cv2.threshold(warped, 0, 255,
	cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
# find contours in the thresholded image, then initialize
# the list of contours that correspond to questions
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
questionCnts = []
# loop over the contours
for c in cnts:
	# compute the bounding box of the contour, then use the
	# bounding box to derive the aspect ratio
	(x, y, w, h) = cv2.boundingRect(c)
	ar = w / float(h)
	# in order to label the contour as a question, region
	# should be sufficiently wide, sufficiently tall, and
	# have an aspect ratio approximately equal to 1
	if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
		questionCnts.append(c)
# sort the question contours top-to-bottom, then initialize
# the total number of correct answers
questionCnts = contours.sort_contours(questionCnts,
	method="top-to-bottom")[0]
correct = 0
# each question has 5 possible answers, to loop over the
# question in batches of 5
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
	# sort the contours for the current question from
	# left to right, then initialize the index of the
	# bubbled answer
	cnts = contours.sort_contours(questionCnts[i:i + 5])[0]
	bubbled = None
	# loop over the sorted contours
	for (j, c) in enumerate(cnts):
		# construct a mask that reveals only the current
		# "bubble" for the question
		mask = np.zeros(thresh.shape, dtype="uint8")
		cv2.drawContours(mask, [c], -1, 255, -1)
		# apply the mask to the thresholded image, then
		# count the number of non-zero pixels in the
		# bubble area
		mask = cv2.bitwise_and(thresh, thresh, mask=mask)
		total = cv2.countNonZero(mask)
		# if the current total has a larger number of total
		# non-zero pixels, then we are examining the currently
		# bubbled-in answer
		if bubbled is None or total > bubbled[0]:
			bubbled = (total, j)
	# initialize the contour color and the index of the
	# *correct* answer
	color = (0, 0, 255)
	k = ANSWER_KEY[q]
	# check to see if the bubbled answer is correct
	if k == bubbled[1]:
		color = (0, 255, 0)
		correct += 1
	# draw the outline of the correct answer on the test
	cv2.drawContours(paper, [cnts[k]], -1, color, 3)
# grab the test taker
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(paper, "{:.2f}%".format(score), (10, 30),
	cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", paper)
cv2.waitKey(0)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【从小项目学图片处理】#1 答题卡识别 的相关文章

  • 当玩家触摸屏幕一侧时,如何让 pygame 发出警告?

    我使用 pygame 创建了一个游戏 当玩家触摸屏幕一侧时 我想让 pygame 给出类似 你不能触摸屏幕两侧 的错误 我尝试在互联网上搜索 但没有找到任何好的结果 我想过在屏幕外添加一个方块 当玩家触摸该方块时 它会发出警告 但这花了很长
  • 表达式中的 Python 'in' 关键字与 for 循环中的比较 [重复]

    这个问题在这里已经有答案了 我明白什么是in运算符在此代码中执行的操作 some list 1 2 3 4 5 print 2 in some list 我也明白i将采用此代码中列表的每个值 for i in 1 2 3 4 5 print
  • 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

    我在 C VS2012 NET 4 5 中的文本加密和解密方面遇到问题 具体来说 当我加密并随后解密字符串时 输出与输入不同 然而 奇怪的是 如果我复制加密的输出并将其硬编码为字符串文字 解密就会起作用 以下代码示例说明了该问题 我究竟做错
  • Python:尝试检查有效的电话号码

    我正在尝试编写一个接受以下格式的电话号码的程序XXX XXX XXXX并将条目中的任何字母翻译为其相应的数字 现在我有了这个 如果启动不正确 它将允许您重新输入正确的数字 然后它会翻译输入的原始数字 我该如何解决 def main phon
  • x:将 ViewModel 方法绑定到 DataTemplate 内的事件

    我基本上问同样的问题这个人 https stackoverflow com questions 10752448 binding to viewmodels property from a template 但在较新的背景下x Bind V
  • Python - 按月对日期进行分组

    这是一个简单的问题 起初我认为很简单而忽略了它 一个小时过去了 我不太确定 所以 我有一个Python列表datetime对象 我想用图表来表示它们 x 值是年份和月份 y 值是此列表中本月发生的日期对象的数量 也许一个例子可以更好地证明这
  • Python - 在窗口最小化或隐藏时使用 pywinauto 控制窗口

    我正在尝试做的事情 我正在尝试使用 pywinauto 在 python 中创建一个脚本 以在后台自动安装 notepad 隐藏或最小化 notepad 只是一个示例 因为我将编辑它以与其他软件一起使用 Problem 问题是我想在安装程序
  • 两个类可以使用 C++ 互相查看吗?

    所以我有一个 A 类 我想在其中调用一些 B 类函数 所以我包括 b h 但是 在 B 类中 我想调用 A 类函数 如果我包含 a h 它最终会陷入无限循环 对吗 我能做什么呢 仅将成员函数声明放在头文件 h 中 并将成员函数定义放在实现文
  • 从 pygame 获取 numpy 数组

    我想通过 python 访问我的网络摄像头 不幸的是 由于网络摄像头的原因 openCV 无法工作 Pygame camera 使用以下代码就像魅力一样 from pygame import camera display camera in
  • 为什么 isnormal() 说一个值是正常的,而实际上不是?

    include
  • 如何在 Linq to SQL 中使用distinct 和 group by

    我正在尝试将以下 sql 转换为 Linq 2 SQL select groupId count distinct userId from processroundissueinstance group by groupId 这是我的代码
  • 如何从没有结尾的管道中读取 python 中的 stdin

    当管道来自 打开 时 不知道正确的名称 我无法从 python 中的标准输入或管道读取数据 文件 我有作为例子管道测试 py import sys import time k 0 try for line in sys stdin k k
  • 在 Pandas DataFrame Python 中添加新列[重复]

    这个问题在这里已经有答案了 例如 我在 Pandas 中有数据框 Col1 Col2 A 1 B 2 C 3 现在 如果我想再添加一个名为 Col3 的列 并且该值基于 Col2 式中 如果Col2 gt 1 则Col3为0 否则为1 所以
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • 在python中,如何仅搜索所选子字符串之前的一个单词

    给定文本文件中的长行列表 我只想返回紧邻其前面的子字符串 例如单词狗 描述狗的单词 例如 假设有这些行包含狗 hotdog big dog is dogged dog spy with my dog brown dogs 在这种情况下 期望
  • 在 Python 类中动态定义实例字段

    我是 Python 新手 主要从事 Java 编程 我目前正在思考Python中的类是如何实例化的 我明白那个 init 就像Java中的构造函数 然而 有时 python 类没有 init 方法 在这种情况下我假设有一个默认构造函数 就像
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • C# 中最小化字符串长度

    我想减少字符串的长度 喜欢 这串 string foo Lorem ipsum dolor sit amet consectetur adipiscing elit Aenean in vehicula nulla Phasellus li
  • 协方差矩阵的对角元素不是 1 pandas/numpy

    我有以下数据框 A B 0 1 5 1 2 6 2 3 7 3 4 8 我想计算协方差 a df iloc 0 values b df iloc 1 values 使用 numpy 作为 cov numpy cov a b I get ar
  • Python - 字典和列表相交

    给定以下数据结构 找出这两种数据结构共有的交集键的最有效方法是什么 dict1 2A 3A 4B list1 2A 4B Expected output 2A 4B 如果这也能产生更快的输出 我可以将列表 不是 dict1 组织到任何其他数

随机推荐

  • ESP32学习笔记05-串口事件方式读取数据

    串口中断方式处理数据 事件机构体 typedef struct uart event type t type lt UART event type size t size lt UART data size for UART DATA ev
  • leetcode 142.环形链表2

    leetcode 142 环形链表2 给定一个链表的头节点 head 返回链表开始入环的第一个节点 如果链表无环 则返回 null 如果链表中有某个节点 可以通过连续跟踪 next 指针再次到达 则链表中存在环 为了表示给定链表中的环 评测
  • VASP - Bader Charge Analysis

    Bader电荷分析 用于分析原子周围的电荷密度 从而得到原子价电子数 下载 Bader Charge Analysis 工具包 下载地址为 http theory cm utexas edu henkelman code bader 下载
  • C++中的friend详细解析

    https blog csdn net u012861978 article details 52095607
  • 奇偶数排序

    题目描述 给定一个整数数组 请调整数组的顺序 使得所有奇数位于数组前半部分 所有偶数位于数组后半部分 要求时间复杂度为O n 分析与解法 最容易想到的办法是从头到尾扫描这个数组 每遇到一个偶数 就把他单独取出来 然后把该偶数后面的所有数往前
  • vulnhub之vegeta

    目录 一 主机发现 二 端口扫描 四 信息收集 五 cyberchef解密 1 两道base64解密 2 save保存 3 qrcode module 4 二维码提取工具zbaring 六 大字典跑目录 七 bulma 八 音频分析软件au
  • spark-2.2.2-bin-hadoop2.7 安装

    1 上传spark 2 2 2 bin hadoop2 7 tgz 2 解压文件 tar zxvf spark 2 2 2 bin hadoop2 7 tgz C usr local 3 进入conf 下把spark env sh temp
  • 『NLP经典项目集』05:新年到,飞桨带你对对联

    基于seq2seq的对联生成 对联 是汉族传统文化之一 是写在纸 布上或刻在竹子 木头 柱子上的对偶语句 对联对仗工整 平仄协调 是一字一音的汉语独特的艺术形式 是中国传统文化瑰宝 这里 我们将根据上联 自动写下联 这是一个典型的序列到序列
  • Mysql 检查表是否存在并创建表,检查列是否存在并新增、修改、删除列

    判断表是否存在 不存在就可新增 CREATE TABLE IF NOT EXISTS example ENGINE InnoDB AUTO INCREMENT 1 DEFAULT CHARSET utf8 判断表字段是否存在 不存在就可新增
  • springboot好在哪

    springboot好在哪 欢迎观看 第一篇 欢迎观看 你好 这是本人第一次使用 新手发文 多多包涵 第一篇 以往的ssm框架整合通常有两种形式 一种是xml形式 一种是注解形式 不管是xml还是注解 基本都会有一大堆xml标签配置 其中有
  • C++学习之list的实现

    在了解学习list实现之前我们首先了解一下关于迭代器的分类 按功能分类 正向迭代器 反向迭代器 const正向迭代器 const反向迭代器 按性质分类 单向迭代器 只能 例如单链表 双向迭代器 可 也可 例如双链表 map和set 随机迭代
  • Unity3D 监控面板显示数据(Inspector)

    using System Collections using System Collections Generic using UnityEngine using UnityEngine Serialization AddComponent
  • 机器学习十大算法之四:SVM(支持向量机)

    SVM 支持向量机 支持向量机 Support Vector Machine 是一种十分常见的分类器 曾经火爆十余年 分类能力强于NN 整体实力比肩LR与RF 核心思路是通过构造分割面将数据进行分离 寻找到一个超平面使样本分成两类 并且间隔
  • IC卡和ID卡

    定义 最常用的感应卡分为ID卡和IC卡两种 IC卡全称集成电路卡 Integrated Circuit Card 又称智能卡 Smart Card 可读写 容量大 有加密功能 数据记录可靠 使用更方便 如一卡通系统 消费系统等 ID卡全称身
  • java.sql.SQLException: Statement.executeQuery() cannot issue statements that do not produce result

    java sql SQLException Statement executeQuery cannot issue statements that do not produce result sets 解决 看看自己的java代码里的 sq
  • Linux内核内存检测工具KASAN

    KASAN k z n KASAN 是 Kernel Address Sanitizer 的缩写 它是一个动态检测内存错误的工具 主要功能是检查内存越界访问和使用已释放的内存等问题 KASAN 集成在 Linux 内核中 随 Linux 内
  • 项目管理 附下载地址

    本书对现代项目管理的基本管理过程 知识模块 工具和方法等进行了全面的介绍 全书共有十二章 每部分都力求深入浅出 站在项目经理的角度 考虑其责任大于权力的现实 并结合时代特征 学习项目管理的软技能和硬技能 通过实际案例 高效的工具和模板 使读
  • 作为字典数据获取枚举值

    RequestMapping value getAmmeterType method RequestMethod GET ResponseBody ApiOperation notes 获取电表类型 value 获取电表类型 public
  • Python程序异常处理

    一 什么是异常 异常就是程序运行时发生错误的信号 在程序由于某些原因出现错误的时候 若程序没有处理它 则会抛出异常 程序也的运行也会随之终止 程序异常带来的问题 1 程序终止 无法运行下去 2 如果程序是面向客户 那么会使客户的体验感很差
  • 【从小项目学图片处理】#1 答题卡识别

    说明 项目皆参考于网上 代码也有大部分参考原文 仅用于学习和练习图像处理操作 项目原文 Bubble sheet multiple choice scanner and test grader using OMR Python and Op