书名:计算机视觉40例从入门到深度学习:OpenCV-Python
作者:李立宗
出版社:电子工业出版社
出版时间:2022-07-01
ISBN:9787121436857
- 答题卡处理2
对答题卡进行倾斜校正、裁剪掉扫描的边缘
第9章 答题卡识别
9.2 整张答题卡识别原理
9.2.2 答题卡处理
1、问题2:如何对答题卡进行倾斜校正、裁剪掉扫描的边缘
- 通常情况下,通过扫描等方式得到的答题卡可能存在较大的黑边及较大程度的倾斜,需要对其进行校正。该操作通常通过透视变换实现。
2、透视变换
- 透视变换可以将矩形映射为任意四边形
在OpenCV中可通过函数warpPerspective实现- dst = cv2.warpPerspective(src, M,dsize)
其中:
● dst表示透视处理后的输出图像。
● src表示要透视的图像。
● M表示一个3×3的变换矩阵。
● dsize表示输出图像的尺寸。
- dst = cv2.warpPerspective(src, M,dsize)
- 由此可知,函数warpPerspective通过变换矩阵将原始图像src转换为目标图像dst。因此,在通过透视变换对图像进行倾斜校正时,需要构造一个变换矩阵。
- OpenCV提供的函数getPerspectiveTransform能够构造从原始图像到目标图像(矩阵)之间的变换矩阵M,其语法格式如下:
- M = cv2.getPerspectiveTransform(src,dst)
其中,src是原始图像的四个顶点,
dst是目标图像的四个顶点。
- M = cv2.getPerspectiveTransform(src,dst)
3、倾斜校正
综上所述,使用透视变换将扫描得到的不规则四边形的答题卡映射为矩形的具体步骤如下。
- 第一步:找到原始图像(待校正的不规则四边形)的四个顶点src及目标图像(规则形)的四个顶点dst。
- 第二步:根据src和dst,使用函数getPerspectiveTransform构造原始图像到目标图像的变换矩阵。
- 第三步:根据变换矩阵,使用函数warpPerspective实现从原始图像到目标图像的变换,完成倾斜校正。
需要注意的一个关键问题是,用于构造变换矩阵使用的原始图像的四个顶点和目标图像的四个顶点的位置必须是匹配的。也就是说,要将左上、右上、左下、右下四个顶点按照相同的顺序排列。
通过轮廓查找,确定轮廓的逼近多边形,找到答题卡(待校正的不规则四边形)的四个顶点。由于并不知道这四个顶点分别是左上、右上、左下、右下四个顶点中的哪个顶点,因此需要在函数内先确定好这四个顶点分别对应左上、右上、左下、右下四个顶点中的哪个顶点。然后将这四个顶点和目标图像的四个顶点,按照一致的排列方式传递给函数getPerspectiveTransform获取变换矩阵。最后根据变换矩阵,使用函数warpPerspective完成倾斜校正。
根据逼近多边形中各个点的坐标,进一步确定了每一个顶点分别对应左上、右上、左下、右下四个顶点中的哪个顶点。
4、程序
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from scipy.spatial import distance as dist # 用于计算距离
# 自定义透视函数
# step 1: 参数 pts 是要进行倾斜校正的轮廓的逼近多边形(本例中的答题卡)的四个顶点
def myWarpPerspective(image,pts):
#确定四个顶点分别对应左上、右上、右下、左下四个顶点中的哪个顶点
# step 1.1:根据轴坐标值对四个顶点进行排序
xSorted = pts[np.argsort(pts[:,0]), :]
# step 1.2:将四个顶点划分为左侧两个、右侧两个
left = xSorted[:2, :]
right = xSorted[2:, :]
# step 1.3:在左侧寻找左上顶点、左下顶点
# 根据 y 轴坐标值排序
left = left[np.argsort(left[:, 1]),:]
# 排在前面的是左上角顶点 (tl:top-left)、排在后面的是左下角顶点 (bl:bottom-left)
(tl,bl) = left
#step 1.4: 根据右侧两个顶点与左上角顶点的距离判断右侧两个顶点的位置
# 计算右侧两个顶点距离左上角顶点的距离
D = dist.cdist(tl[np.newaxis], right,"euclidean")[0]
# 形状大致如下
# 左上角顶点(t1) 右上角顶点(tr)
# 页面中心
# 左下角顶点(bl) 右下角顶点(br)
# 右侧两个顶点中,距离左上角顶点远的点是右下角顶点 (br),近的点是右上角顶点(tr)
# br:bottom-right/tr:top-right
(br, tr) = right[np.argsort(D)[::-1], :]
# step 1.5:确定 pts 的四个顶点分别属于左上、左下、右上、右下顶点中的哪个
# src 是根据左上、左下、右上、右下顶点对 pts 的四个顶点进行排序的结果
src = np.array([tl, tr, br, bl], dtype="float32")
#========以下5行是测试语句,显示计算的顶点是否正确=========
srcx = np.array([tl, tr, br, bl], dtype="int32")
print("看看各个顶点在哪: \n",src) # 测试语句,查看顶点
test=image.copy() # 复制 image 图像,处理用
cv2.polylines(test,[srcx],True,(255,0,0),8) # 在 test 内绘制得到的点
cv2.imshow("Test",test) #显示绘制线条结果
# ======= Step2:根据 pts的四个顶点,计算校正后图像的宽度和高度==
# 校正后图像的大小计算比较随意,根据需要选用合适值即可
# 这里选用较长的宽度和高度作为最终宽度和高度
# 计算方式:由于图像是倾斜的,所以将算得的X轴方向、Y轴方向的差值的平方根作为实际长度
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 对应的四个顶点
dst = np.array([[0,0], [maxWidth - 1,0], [maxWidth - 1,maxHeight - 1], [0,maxHeight - 1]], dtype="float32")
# print("看看目标如何: n,dst) # 测试语句
# 构造从 src到dst的变换矩阵
M = cv2.getPerspectiveTransform(src, dst)
# 完成从 src到dst的透视变换
warped = cv2.warpPerspective(image, M, (maxWidth,maxHeight)) # 返回透视变换的结果
return warped
# ======主程序======
# ====读取原始图像====
img = cv2.imread('d:\\OpenCVpic\\TestPaper.jpg')
cv2.imshow('img', img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 高斯滤波
gaussian = cv2.GaussianBlur(gray,(5,5), 0)
edged =cv2.Canny(gaussian, 50, 200)
cts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# cv2.drawContours(img,cts,-1,(0,0,255),3)
list=sorted(cts,key=cv2.contourArea,reverse=True)
print("寻找轮廓的个数:",len(cts))
# cv2.imshow("draw contours",img)
rightSum = 0
# 可能只能找到一个轮廓,该轮廓就是答题卡的轮廓
# 由于噪声等影响,也可能找到很多轮廓
# 使用 for 循环,遍历每一个轮廓,找到答题卡的轮廓
# 对答题卡进行倾斜校正处理
for c in list:
peri=0.01*cv2.arcLength(c,True)
approx=cv2.approxPolyDP(c,peri,True)
print("顶点个数:",len(approx))
# 四个顶点的轮廓是矩形(或者是扫描造成的矩形失真为梯形)
if len(approx)==4:
# 对外轮廓进行倾斜校正,将其构造成一个矩形
# 处理后,只保留答题卡部分,答题卡外面的边界被删除#原始图像的倾斜校正用于后续标注
# print(approx)
print(approx.reshape(4,2))
paper = myWarpPerspective(img,approx.reshape(4,2))
cv2.imshow("result",paper) # 打印识别结果
cv2.waitKey()
cv2.destroyAllWindows()
运行结果
输出数据
5、分析
从结果可以看到,该程序在对左侧图像进行了倾斜校正、裁边后得到了右侧的处理结果。
在实际对答题卡进行识别时,要得到两个倾斜校正结果,分别如下:
- 原始图像的倾斜校正结果图像A,用于显示最终结果,包括最终正确率、考生作答选项的标识(在考生选择正确的选项上用绿色轮廓包围,在考生选择错误的选项上用红色轮廓包围)等要以彩色形式显示。
图像A只有在色彩空间中才能正常显示彩色辅助信息。 - 原始图像对应的灰度图像倾斜校正结果图像B,用于后续计算。
后续的轮廓计算等操作都基于灰度图像的。
网友评论