import cv2
import matplotlib.pyplot as plt
import numpy as np
import myutils
import argparse
import imutils.contours
# 正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 读取输入
image = cv2.imread("D:\\images\\text.jpg")
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)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, \
cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
cv_show('contours_img', contours_img)
def order_points(pts):
# 一共四个坐标点
rect = np.zeros((4, 2), dtype="float32")
# 按照顺序找到对应坐标0123分别是 左上,右上,右下,左下
# 计算左上,右下
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# 计算右上,左下
d = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(d)]
rect[3] = pts[np.argmax(d)]
return rect
def four_point_transform(img, pts):
# 获取输入坐标
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算输入的w和h值
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(img, M, (maxWidth, maxHeight))
# 反回变换后的结果
return 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
cnt = cnts
dotCnt = None
if len(cnt) > 0:
cnt = sorted(cnt, key=cv2.contourArea, reverse=True)
for c in cnt:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
dotCnt = approx
warp = four_point_transform(gray, dotCnt.reshape(4, 2))
# otsu's 阈值处理
thresh = cv2.threshold(warp, 0, 255, cv2.THRESH_BINARY_INV |
cv2.THRESH_OTSU)[1]
thresh_contours = thresh.copy()
# 找到每一个圆圈轮廓
cnt = cv2.findContours(thresh_contours, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(thresh_contours, cnt, -1, (0, 255, 0), 2)
questionCnts = []
cv_show('cnt', thresh_contours)
# 遍历
for c in cnt:
# 计算比例和大小
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# 根据实际情况指定标准
if w >= 20 and h >= 20 and ar > 0.9 and ar < 1.1:
questionCnts.append(c)
# 按照从上到下进行排序
questionCnts = sort_contours(questionCnts, method='top-to-bottom')[0]
# cv2.drawContours(warp,questionCnts,1,(0,255,255),2)
# 每排有5个选项
correct = 0
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
# 排序
cnts = sort_contours(questionCnts[i:i + 5])[0]
bubbled = None
# 遍历每一个结果
for (j, c) in enumerate(cnts):
# 使用mask来判断结果
mask = np.zeros(thresh.shape, dtype='uint8')
cv2.drawContours(mask, [c], -1, 255, -1)
cv_show('mask', mask)
# 通过计算非零点数量来算是否选择这个答案
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
# 通过阈值判断
if bubbled is None or total > bubbled[0]:
bubbled = (total, j)
# 对比正确答案
color = (0, 0, 255)
k = ANSWER_KEY[q]
# 判断正确
if k == bubbled[1]:
color = (0, 255, 0)
correct += 1
# 绘图
cv2.drawContours(warp, cnts[k], -1, color, 2)
cv2.imshow("warp", warp)
# print(correct)
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warp, "{:.2f}%".format(score), (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Exam", warp)
cv2.waitKey(0)
cv2.waitKey()
cv2.destroyAllWindows()
步骤:
1:轮廓检测
# 读取输入
image = cv2.imread("D:\\images\\text.jpg")
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)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, \
cv2.CHAIN_APPROX_SIMPLE)[0]
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
cv_show('contours_img', contours_img)
先进行灰度图-->平滑操作(高斯滤波去噪音)-->边缘检测-->轮廓检测(只检测最外层轮廓)
cv2.findContours函数中mode参数选择cv2.RETR_EXTERNAL(表示只检测外轮廓)
2:透视变换
def order_points(pts):
# 一共四个坐标点
rect = np.zeros((4, 2), dtype="float32")
# 按照顺序找到对应坐标0123分别是 左上,右上,右下,左下
# 计算左上,右下
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# 计算右上,左下
d = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(d)]
rect[3] = pts[np.argmax(d)]
return rect
def four_point_transform(img, pts):
# 获取输入坐标
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算输入的w和h值
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(img, M, (maxWidth, maxHeight))
# 反回变换后的结果
return warped
先获得图像的几个顶点坐标-->再计算图像的宽和高-->重新定义图像的坐标
np.zeros函数的作用 返回来一个给定形状和类型的用0填充的数组; zeros(shape, dtype=float, order=‘C’) shape:形状 dtype:数据类型,可选参数,默认numpy.float64 order:可选参数,c代表与c语言类似,行优先;F代表列优先
s = pts.sum(axis=1)?
每行像素值进行相加,如果axis=0,则表示每列像素值相加
dotCnt = None
if len(cnt) > 0:
cnt = sorted(cnt, key=cv2.contourArea, reverse=True)
for c in cnt:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
dotCnt = approx
warp = four_point_transform(gray, dotCnt.reshape(4, 2))
cv2.arcLength:函数用于计算封闭轮廓的周长或曲线的长度
arcLength(InputArray curve, bool closed);
参数
curve,输入的二维点集(轮廓顶点),可以是 vector 或 Mat 类型。
closed,用于指示曲线是否封闭。
cv2.approxPolyDP: 使用 Douglas-Peucker 算法求得一条顶点较少的多折线/多边形,以指定的精度近似输入的曲线或多边形
参数:
curve:输入点集,二维点向量的集合 approxCurve:输出点集,表示拟合曲线或多边形,数据与输入参数 curve 一致 epsilon:指定的近似精度,原始曲线与近似曲线之间的最大距离 close: 闭合标志,True 表示闭合多边形,False 表示多边形不闭合
判断答案
# 每排有5个选项
correct = 0
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
# 排序
cnts = sort_contours(questionCnts[i:i + 5])[0]
bubbled = None
# 遍历每一个结果
for (j, c) in enumerate(cnts):
# 使用mask来判断结果
mask = np.zeros(thresh.shape, dtype='uint8')
cv2.drawContours(mask, [c], -1, 255, -1)
cv_show('mask', mask)
# 通过计算非零点数量来算是否选择这个答案
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
# 通过阈值判断
if bubbled is None or total > bubbled[0]:
bubbled = (total, j)
# 对比正确答案
color = (0, 0, 255)
k = ANSWER_KEY[q]
# 判断正确
if k == bubbled[1]:
color = (0, 255, 0)
correct += 1
# 绘图
cv2.drawContours(warp, cnts[k], -1, color, 2)
选定掩膜:
mask = np.zeros(thresh.shape, dtype='uint8')
cv2.drawContours(mask, [c], -1, 255, -1)
cv_show('mask', mask)
# 通过计算非零点数量来算是否选择这个答案
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
np.zeros(thresh.shape, dtype='uint8')把图像(像素)置为0变为全黑,
cv2.drawContours(mask, [c], -1, 255, -1)对掩膜操作得到一个个选项的位置效果为
mask = cv2.bitwise_and(thresh, thresh, mask=mask)
total = cv2.countNonZero(mask)
?cv2.bitwise_and(thresh, thresh, mask=mask)
给定指定的掩膜,得到选项
countNonZero():返回灰度值不为0的像素数,可用来判断图像是否全黑
最终效果:
?
|