OpenCV计算机视觉实战- 答题卡识别判卷【项目实战】(附完整代码)

#################################################################
纸上得来终觉浅,绝知此事要躬行
B站视频
新课件:https://pan.baidu.com/s/1frWHqCVGR2VTn5QBtW4lPA 提取码:xh02
老课件:https://pan.baidu.com/s/1Wi31FxSPBqWiuJX9quX-jA 提取码:bbfg
################################################################

答题卡识别效果

在这里插入图片描述
大致思路】:先进行仿射变换去除背景(只留试卷部分),二值化,圆形轮廓检测,遍历每一行选项,统计非零像素,记录填充选项(即非零像素最多的轮廓区域),与正确答案进行比对,正确则correct数+1,得到总成绩

1-4 基础操作+透视变换

1-4是基础操作, 3是做近似变换, 取最大的那个轮廓,最有可能是图像最大外围的轮廓

3的近似变换 和 4的透视变换原理 可以参考我的 OCR文档扫描实战博客

# 1.预处理
image = cv2.imread(args["image"])
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)

# 2.轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show(‘contours_img’,contours_img)
docCnt = None

# 3.确保检测到了
if len(cnts) > 0:
# 根据轮廓大小进行排序
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

<span class="token comment"># 遍历每一个轮廓</span>
<span class="token keyword">for</span> c <span class="token keyword">in</span> cnts<span class="token punctuation">:</span>
	<span class="token comment"># 近似</span>
	peri <span class="token operator">=</span> cv2<span class="token punctuation">.</span>arcLength<span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token boolean">True</span><span class="token punctuation">)</span>
	approx <span class="token operator">=</span> cv2<span class="token punctuation">.</span>approxPolyDP<span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token number">0.02</span> <span class="token operator">*</span> peri<span class="token punctuation">,</span> <span class="token boolean">True</span><span class="token punctuation">)</span>

	<span class="token comment"># 准备做透视变换</span>
	<span class="token keyword">if</span> <span class="token builtin">len</span><span class="token punctuation">(</span>approx<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">4</span><span class="token punctuation">:</span>
		docCnt <span class="token operator">=</span> approx
		<span class="token keyword">break</span>

# 4.执行透视变换
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show(‘warped’,warped)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

在这里插入图片描述

5-6 阈值处理+轮廓检测

5.Otsu’s 阈值处理
THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
在我的信用卡数字识别案例中出现也有应用(第三、五部分)

6.然后怎么区分涂和没涂的圆?
这里不用霍夫变换,因为有些涂完后 会突出边界,如下
在这里插入图片描述

# 5.Otsu's 阈值处理
thresh = cv2.threshold(warped, 0, 255,
	cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] 
cv_show('thresh',thresh)

thresh_Contours = thresh.copy()
# 6.找到每一个圆圈轮廓
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 = []

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

7-8 筛选答题圈

7.遍历所有圆圈轮廓(包括干扰项) 筛选出答题区域的圆,其轮廓存于questionCnts
无论是圆形还是矩形的答题卡,都是规则的形状,比例相同.
所以这里要人工设定圆圈外接矩形的长宽比例
参考信用卡数字识别 (第四部分)

8.按照从上到下(从左到右)进行排序
参考信用卡数字识别 (第二部分)

# 7.遍历所有圆圈轮廓(包括干扰项) 筛选出答题区域的圆
for c in cnts:
	# 计算比例和大小
	(x, y, w, h) = cv2.boundingRect(c)
	ar = w / float(h)
<span class="token comment"># 根据实际情况指定标准	-- 过滤操作</span>
<span class="token keyword">if</span> w <span class="token operator">&gt;=</span> <span class="token number">20</span> <span class="token operator">and</span> h <span class="token operator">&gt;=</span> <span class="token number">20</span> <span class="token operator">and</span> ar <span class="token operator">&gt;=</span> <span class="token number">0.9</span> <span class="token operator">and</span> ar <span class="token operator">&lt;=</span> <span class="token number">1.1</span><span class="token punctuation">:</span>
	questionCnts<span class="token punctuation">.</span>append<span class="token punctuation">(</span>c<span class="token punctuation">)</span>

# 8.按照从上到下进行排序
questionCnts = sort_contours(questionCnts,
method=“top-to-bottom”)[0]
correct = 0

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

9.每行的5个选项 分别比对正确答案

答题圈的轮廓questionCnts长度应为25, 间隔5, 可以遍历5次, 则
q 取0 1 2 3 4,共5行;
i 表示从第几个轮廓开始:0,5,10,15,20. 即每行的第一个轮廓

# 9.每行的5个选项 分别比对正确答案
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
	# 9.1排序
	cnts = sort_contours(questionCnts[i:i + 5])[0]
	bubbled = None
<span class="token comment"># 9.2 遍历每一个结果</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>j<span class="token punctuation">,</span> c<span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token builtin">enumerate</span><span class="token punctuation">(</span>cnts<span class="token punctuation">)</span><span class="token punctuation">:</span>
	<span class="token comment"># 9.2.1 使用mask来判断结果</span>
	mask <span class="token operator">=</span> np<span class="token punctuation">.</span>zeros<span class="token punctuation">(</span>thresh<span class="token punctuation">.</span>shape<span class="token punctuation">,</span> dtype<span class="token operator">=</span><span class="token string">"uint8"</span><span class="token punctuation">)</span>
	cv2<span class="token punctuation">.</span>drawContours<span class="token punctuation">(</span>mask<span class="token punctuation">,</span> <span class="token punctuation">[</span>c<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">#-1表示填充</span>
	cv_show<span class="token punctuation">(</span><span class="token string">'mask'</span><span class="token punctuation">,</span>mask<span class="token punctuation">)</span>
	<span class="token comment"># 9.2.2 通过计算非零点数量来算是否选择这个答案</span>
	mask <span class="token operator">=</span> cv2<span class="token punctuation">.</span>bitwise_and<span class="token punctuation">(</span>thresh<span class="token punctuation">,</span> thresh<span class="token punctuation">,</span> mask<span class="token operator">=</span>mask<span class="token punctuation">)</span>
	total <span class="token operator">=</span> cv2<span class="token punctuation">.</span>countNonZero<span class="token punctuation">(</span>mask<span class="token punctuation">)</span>

	<span class="token comment"># 9.2.3 通过阈值判断</span>
	<span class="token keyword">if</span> bubbled <span class="token keyword">is</span> <span class="token boolean">None</span> <span class="token operator">or</span> total <span class="token operator">&gt;</span> bubbled<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
		bubbled <span class="token operator">=</span> <span class="token punctuation">(</span>total<span class="token punctuation">,</span> j<span class="token punctuation">)</span>

<span class="token comment"># 9.3 获取正确答案</span>
color <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">)</span>
k <span class="token operator">=</span> ANSWER_KEY<span class="token punctuation">[</span>q<span class="token punctuation">]</span>

<span class="token comment"># 9.4 对比答案 并 判断正确</span>
<span class="token keyword">if</span> k <span class="token operator">==</span> bubbled<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
	color <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
	correct <span class="token operator">+=</span> <span class="token number">1</span>

<span class="token comment"># 9.5 绘图</span>
cv2<span class="token punctuation">.</span>drawContours<span class="token punctuation">(</span>warped<span class="token punctuation">,</span> <span class="token punctuation">[</span>cnts<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> color<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

9.1 确保每一行的顺序为A B C D E

9.2 同样这么一行里,这5个框有什么不同.

j 取每个选项0 1 2 3 4

9.2.1 使用mask来判断结果

初始化一个 跟透视变换后的图 一样大小的mask(全黑)
然后在mask上, 画出当前遍历的这个(圆圈)轮廓c, 画成白色

  • cv2.drawContours (传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度)
    参考OpenCV基本操作
    补充一点:线条厚度 为负值或CV_FILLED 表示填充轮廓内部

一行遍历5个选项,5行一共25个选项,这展示前两行的遍历结果,后三行同理…
在这里插入图片描述

9.2.2 与操作

一张图片 跟 一张相同大小的黑白图片 进行与操作,则只保留图片的白色区域

  • cv2.bitwise_and(src1, src2, dst=None, mask=None)
    对图像(灰度图像或彩色图像均可)每个像素值进行二进制“与”操作,
    1&1=1,1&0=0,0&1=0,0&0=0

    函数返回值: 调用时若无mask参数 则返回src1 & src2,若存在mask参数,则返回src1 & src2 & mask

    src1:输入原图1
    src2:输入原图2, src1与src2可以相同也可以不相同,可以是灰度图像也可以是彩色图像
    dst:输出矩阵,和输入矩阵一样的尺寸和类型 若存在参数时:src1 & src2 或者 src1 & src2 & mask
    mask:可以是单通道8bit灰度图像,也可以是矩阵,一般为二值化后的图像,指定要更改的输出数组的元素

  • cv2.countNonZero统计非零像素点个数

9.2.3 依次判断5个选项的哪个非零值最大(即哪个被填充上了)

total > bubbled[0] 比它大的才保留到bubbled
bubbled 保留最大的选项( 即填充上的选项 ) j

9.3 k = ANSWER_KEY[q] 是第几题(行)的正确答案

9.4 若k = bubbled[1],判断正确,correct+=1

.

10.打印正确率

# 10.打印正确率
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, 255, 0), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", warped)
cv2.waitKey(0)

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述
.
.

* 测试其他答题卡效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

结束语

虽然代码有点长,但大部分内容都是前面几个项目有用到的知识。可见遇到不同场景的举一反三能力多重要啦

扩展

有兴趣的同学们 可以尝试一下矩形答题卡的识别

完整代码

一个py文件,3个函数

# 导入工具包
import numpy as np
import argparse
import cv2
def order_points(pts):
	# 一共4个坐标点
	rect = np.zeros((4, 2), dtype = "float32")
<span class="token comment"># 按顺序找到对应坐标0123分别是 左上,右上,右下,左下</span>
<span class="token comment"># 计算左上,右下</span>
s <span class="token operator">=</span> pts<span class="token punctuation">.</span><span class="token builtin">sum</span><span class="token punctuation">(</span>axis <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">)</span>
rect<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> pts<span class="token punctuation">[</span>np<span class="token punctuation">.</span>argmin<span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">]</span>
rect<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">=</span> pts<span class="token punctuation">[</span>np<span class="token punctuation">.</span>argmax<span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">]</span>

<span class="token comment"># 计算右上和左下</span>
diff <span class="token operator">=</span> np<span class="token punctuation">.</span>diff<span class="token punctuation">(</span>pts<span class="token punctuation">,</span> axis <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">)</span>
rect<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> pts<span class="token punctuation">[</span>np<span class="token punctuation">.</span>argmin<span class="token punctuation">(</span>diff<span class="token punctuation">)</span><span class="token punctuation">]</span>
rect<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">=</span> pts<span class="token punctuation">[</span>np<span class="token punctuation">.</span>argmax<span class="token punctuation">(</span>diff<span class="token punctuation">)</span><span class="token punctuation">]</span>

<span class="token keyword">return</span> rect

def four_point_transform(image, pts):
# 获取输入坐标点
rect = order_points(pts)
(tl, tr, br, bl) = rect

<span class="token comment"># 计算输入的w和h值</span>
widthA <span class="token operator">=</span> np<span class="token punctuation">.</span>sqrt<span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">(</span>br<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">-</span> bl<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>br<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">-</span> bl<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
widthB <span class="token operator">=</span> np<span class="token punctuation">.</span>sqrt<span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">(</span>tr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">-</span> tl<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tr<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">-</span> tl<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
maxWidth <span class="token operator">=</span> <span class="token builtin">max</span><span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">(</span>widthA<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">int</span><span class="token punctuation">(</span>widthB<span class="token punctuation">)</span><span class="token punctuation">)</span>

heightA <span class="token operator">=</span> np<span class="token punctuation">.</span>sqrt<span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">(</span>tr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">-</span> br<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tr<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">-</span> br<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
heightB <span class="token operator">=</span> np<span class="token punctuation">.</span>sqrt<span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">(</span>tl<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">-</span> bl<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tl<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">-</span> bl<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
maxHeight <span class="token operator">=</span> <span class="token builtin">max</span><span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">(</span>heightA<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">int</span><span class="token punctuation">(</span>heightB<span class="token punctuation">)</span><span class="token punctuation">)</span>

<span class="token comment"># 变换后对应坐标位置</span>
dst <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span>
	<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
	<span class="token punctuation">[</span>maxWidth <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
	<span class="token punctuation">[</span>maxWidth <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> maxHeight <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
	<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> maxHeight <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span> dtype <span class="token operator">=</span> <span class="token string">"float32"</span><span class="token punctuation">)</span>

<span class="token comment"># 计算变换矩阵</span>
M <span class="token operator">=</span> cv2<span class="token punctuation">.</span>getPerspectiveTransform<span class="token punctuation">(</span>rect<span class="token punctuation">,</span> dst<span class="token punctuation">)</span>
warped <span class="token operator">=</span> cv2<span class="token punctuation">.</span>warpPerspective<span class="token punctuation">(</span>image<span class="token punctuation">,</span> M<span class="token punctuation">,</span> <span class="token punctuation">(</span>maxWidth<span class="token punctuation">,</span> maxHeight<span class="token punctuation">)</span><span class="token punctuation">)</span>

<span class="token comment"># 返回变换后结果</span>
<span class="token keyword">return</span> warped

def sort_contours(cnts, method=“left-to-right”):
reverse = False
i = 0
if method “right-to-left” or method “bottom-to-top”:
reverse = True
if method “top-to-bottom” or method “bottom-to-top”:
i = 1
boundingBoxes = [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()

‘’‘下面为主函数’’’
if name == main:
# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", “–image”, required=True,
help=“path to the input image”)
args = vars(ap.parse_args())

<span class="token comment"># 正确答案</span>
ANSWER_KEY <span class="token operator">=</span> <span class="token punctuation">{<!-- --></span><span class="token number">0</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">}</span>

<span class="token comment"># 1.预处理</span>
image <span class="token operator">=</span> cv2<span class="token punctuation">.</span>imread<span class="token punctuation">(</span>args<span class="token punctuation">[</span><span class="token string">"image"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
contours_img <span class="token operator">=</span> image<span class="token punctuation">.</span>copy<span class="token punctuation">(</span><span class="token punctuation">)</span>
gray <span class="token operator">=</span> cv2<span class="token punctuation">.</span>cvtColor<span class="token punctuation">(</span>image<span class="token punctuation">,</span> cv2<span class="token punctuation">.</span>COLOR_BGR2GRAY<span class="token punctuation">)</span>
blurred <span class="token operator">=</span> cv2<span class="token punctuation">.</span>GaussianBlur<span class="token punctuation">(</span>gray<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
cv_show<span class="token punctuation">(</span><span class="token string">'blurred'</span><span class="token punctuation">,</span>blurred<span class="token punctuation">)</span>
edged <span class="token operator">=</span> cv2<span class="token punctuation">.</span>Canny<span class="token punctuation">(</span>blurred<span class="token punctuation">,</span> <span class="token number">75</span><span class="token punctuation">,</span> <span class="token number">200</span><span class="token punctuation">)</span>
cv_show<span class="token punctuation">(</span><span class="token string">'edged'</span><span class="token punctuation">,</span>edged<span class="token punctuation">)</span>

<span class="token comment"># 2.轮廓检测</span>
cnts <span class="token operator">=</span> cv2<span class="token punctuation">.</span>findContours<span class="token punctuation">(</span>edged<span class="token punctuation">.</span>copy<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cv2<span class="token punctuation">.</span>RETR_EXTERNAL<span class="token punctuation">,</span>
	cv2<span class="token punctuation">.</span>CHAIN_APPROX_SIMPLE<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>
cv2<span class="token punctuation">.</span>drawContours<span class="token punctuation">(</span>contours_img<span class="token punctuation">,</span>cnts<span class="token punctuation">,</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">255</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">)</span> 
cv_show<span class="token punctuation">(</span><span class="token string">'contours_img'</span><span class="token punctuation">,</span>contours_img<span class="token punctuation">)</span>
docCnt <span class="token operator">=</span> <span class="token boolean">None</span>

<span class="token comment"># 3.确保检测到了</span>
<span class="token keyword">if</span> <span class="token builtin">len</span><span class="token punctuation">(</span>cnts<span class="token punctuation">)</span> <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">:</span>
	<span class="token comment"># 根据轮廓大小进行排序</span>
	cnts <span class="token operator">=</span> <span class="token builtin">sorted</span><span class="token punctuation">(</span>cnts<span class="token punctuation">,</span> key<span class="token operator">=</span>cv2<span class="token punctuation">.</span>contourArea<span class="token punctuation">,</span> reverse<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>

	<span class="token comment"># 遍历每一个轮廓</span>
	<span class="token keyword">for</span> c <span class="token keyword">in</span> cnts<span class="token punctuation">:</span>
		<span class="token comment"># 近似</span>
		peri <span class="token operator">=</span> cv2<span class="token punctuation">.</span>arcLength<span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token boolean">True</span><span class="token punctuation">)</span>
		approx <span class="token operator">=</span> cv2<span class="token punctuation">.</span>approxPolyDP<span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token number">0.02</span> <span class="token operator">*</span> peri<span class="token punctuation">,</span> <span class="token boolean">True</span><span class="token punctuation">)</span>

		<span class="token comment"># 准备做透视变换</span>
		<span class="token keyword">if</span> <span class="token builtin">len</span><span class="token punctuation">(</span>approx<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">4</span><span class="token punctuation">:</span>
			docCnt <span class="token operator">=</span> approx
			<span class="token keyword">break</span>

<span class="token comment"># 4.执行透视变换</span>
warped <span class="token operator">=</span> four_point_transform<span class="token punctuation">(</span>gray<span class="token punctuation">,</span> docCnt<span class="token punctuation">.</span>reshape<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
cv_show<span class="token punctuation">(</span><span class="token string">'warped'</span><span class="token punctuation">,</span>warped<span class="token punctuation">)</span>
<span class="token comment"># 5.Otsu's 阈值处理</span>
thresh <span class="token operator">=</span> cv2<span class="token punctuation">.</span>threshold<span class="token punctuation">(</span>warped<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span>
	cv2<span class="token punctuation">.</span>THRESH_BINARY_INV <span class="token operator">|</span> cv2<span class="token punctuation">.</span>THRESH_OTSU<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> 
cv_show<span class="token punctuation">(</span><span class="token string">'thresh'</span><span class="token punctuation">,</span>thresh<span class="token punctuation">)</span>

thresh_Contours <span class="token operator">=</span> thresh<span class="token punctuation">.</span>copy<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment"># 6.找到所有轮廓</span>
cnts <span class="token operator">=</span> cv2<span class="token punctuation">.</span>findContours<span class="token punctuation">(</span>thresh<span class="token punctuation">.</span>copy<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cv2<span class="token punctuation">.</span>RETR_EXTERNAL<span class="token punctuation">,</span>
	cv2<span class="token punctuation">.</span>CHAIN_APPROX_SIMPLE<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>
cv2<span class="token punctuation">.</span>drawContours<span class="token punctuation">(</span>thresh_Contours<span class="token punctuation">,</span>cnts<span class="token punctuation">,</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">255</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">)</span> 
cv_show<span class="token punctuation">(</span><span class="token string">'thresh_Contours'</span><span class="token punctuation">,</span>thresh_Contours<span class="token punctuation">)</span>
questionCnts <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>

<span class="token comment"># 7.遍历所有圆圈轮廓(包括干扰项) 筛选出答题区域的圆</span>
<span class="token keyword">for</span> c <span class="token keyword">in</span> cnts<span class="token punctuation">:</span>
	<span class="token comment"># 计算比例和大小</span>
	<span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">,</span> w<span class="token punctuation">,</span> h<span class="token punctuation">)</span> <span class="token operator">=</span> cv2<span class="token punctuation">.</span>boundingRect<span class="token punctuation">(</span>c<span class="token punctuation">)</span>
	ar <span class="token operator">=</span> w <span class="token operator">/</span> <span class="token builtin">float</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span>

	<span class="token comment"># 根据实际情况指定标准	-- 过滤操作</span>
	<span class="token keyword">if</span> w <span class="token operator">&gt;=</span> <span class="token number">20</span> <span class="token operator">and</span> h <span class="token operator">&gt;=</span> <span class="token number">20</span> <span class="token operator">and</span> ar <span class="token operator">&gt;=</span> <span class="token number">0.9</span> <span class="token operator">and</span> ar <span class="token operator">&lt;=</span> <span class="token number">1.1</span><span class="token punctuation">:</span>
		questionCnts<span class="token punctuation">.</span>append<span class="token punctuation">(</span>c<span class="token punctuation">)</span>

<span class="token comment"># 8.按照从上到下进行排序</span>
questionCnts <span class="token operator">=</span> sort_contours<span class="token punctuation">(</span>questionCnts<span class="token punctuation">,</span>
	method<span class="token operator">=</span><span class="token string">"top-to-bottom"</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>
correct <span class="token operator">=</span> <span class="token number">0</span>

<span class="token comment"># 9.每行的5个选项 分别比对正确答案</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>q<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token builtin">enumerate</span><span class="token punctuation">(</span>np<span class="token punctuation">.</span>arange<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token builtin">len</span><span class="token punctuation">(</span>questionCnts<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
	<span class="token comment"># 9.1排序</span>
	cnts <span class="token operator">=</span> sort_contours<span class="token punctuation">(</span>questionCnts<span class="token punctuation">[</span>i<span class="token punctuation">:</span>i <span class="token operator">+</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>
	bubbled <span class="token operator">=</span> <span class="token boolean">None</span>

	<span class="token comment"># 9.2 遍历每一个结果</span>
	<span class="token keyword">for</span> <span class="token punctuation">(</span>j<span class="token punctuation">,</span> c<span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token builtin">enumerate</span><span class="token punctuation">(</span>cnts<span class="token punctuation">)</span><span class="token punctuation">:</span>
		<span class="token comment"># 9.2.1 使用mask来判断结果</span>
		mask <span class="token operator">=</span> np<span class="token punctuation">.</span>zeros<span class="token punctuation">(</span>thresh<span class="token punctuation">.</span>shape<span class="token punctuation">,</span> dtype<span class="token operator">=</span><span class="token string">"uint8"</span><span class="token punctuation">)</span>
		cv2<span class="token punctuation">.</span>drawContours<span class="token punctuation">(</span>mask<span class="token punctuation">,</span> <span class="token punctuation">[</span>c<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">#-1表示填充</span>
		<span class="token comment"># cv_show('mask',mask)</span>
		<span class="token comment"># 9.2.2 通过计算非零点数量来算是否选择这个答案</span>
		mask <span class="token operator">=</span> cv2<span class="token punctuation">.</span>bitwise_and<span class="token punctuation">(</span>thresh<span class="token punctuation">,</span> thresh<span class="token punctuation">,</span> mask<span class="token operator">=</span>mask<span class="token punctuation">)</span>
		total <span class="token operator">=</span> cv2<span class="token punctuation">.</span>countNonZero<span class="token punctuation">(</span>mask<span class="token punctuation">)</span>

		<span class="token comment"># 9.2.3 通过阈值判断</span>
		<span class="token keyword">if</span> bubbled <span class="token keyword">is</span> <span class="token boolean">None</span> <span class="token operator">or</span> total <span class="token operator">&gt;</span> bubbled<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
			bubbled <span class="token operator">=</span> <span class="token punctuation">(</span>total<span class="token punctuation">,</span> j<span class="token punctuation">)</span>

	<span class="token comment"># 9.3 获取正确答案</span>
	color <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">)</span>
	k <span class="token operator">=</span> ANSWER_KEY<span class="token punctuation">[</span>q<span class="token punctuation">]</span>

	<span class="token comment"># 9.4 对比答案 并 判断正确</span>
	<span class="token keyword">if</span> k <span class="token operator">==</span> bubbled<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
		color <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
		correct <span class="token operator">+=</span> <span class="token number">1</span>

	<span class="token comment"># 9.5 绘图</span>
	cv2<span class="token punctuation">.</span>drawContours<span class="token punctuation">(</span>warped<span class="token punctuation">,</span> <span class="token punctuation">[</span>cnts<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> color<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span>

<span class="token comment"># 10.打印正确率</span>
score <span class="token operator">=</span> <span class="token punctuation">(</span>correct <span class="token operator">/</span> <span class="token number">5.0</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span>
<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"[INFO] score: {:.2f}%"</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>score<span class="token punctuation">)</span><span class="token punctuation">)</span>
cv2<span class="token punctuation">.</span>putText<span class="token punctuation">(</span>warped<span class="token punctuation">,</span> <span class="token string">"{:.2f}%"</span><span class="token punctuation">.</span><span class="token builtin">format</span><span class="token punctuation">(</span>score<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
	cv2<span class="token punctuation">.</span>FONT_HERSHEY_SIMPLEX<span class="token punctuation">,</span> <span class="token number">0.9</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">255</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>
cv2<span class="token punctuation">.</span>imshow<span class="token punctuation">(</span><span class="token string">"Original"</span><span class="token punctuation">,</span> image<span class="token punctuation">)</span>
cv2<span class="token punctuation">.</span>imshow<span class="token punctuation">(</span><span class="token string">"Exam"</span><span class="token punctuation">,</span> warped<span class="token punctuation">)</span>
cv2<span class="token punctuation">.</span>waitKey<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值