Opencv之掩码实现答题卡辨认及精确率判断

打印 上一主题 下一主题

主题 952|帖子 952|积分 2856

掩码实现答题卡辨认及精确率判断


  
1 掩码


1.1 概念

掩码(Mask)是一种用于指定图像处置处罚操作区域的工具掩码通常是一个与图像尺寸相同的二值图像,其中像素值为0表现不处置处罚,像素值为255(或1)表现处置处罚。掩码可以用于多种操作,如图像滤波、图像合成、图像分割等。掩码的尺寸必须与图像的尺寸相同。掩码的像素值通常为0或255(或1),但也可以是其他值,详细取决于应用场景。通过使用掩码,可以更精确地控制图像处置处罚操作的范围,从而实现更复杂的结果。
1.2 创建掩码



  • mask = np.zeros((height, width), dtype=np.uint8),创建一个全黑的掩码

    • (height, width), 高宽
    • dtype=np.uint8 ,数据范例

  • cv2.rectangle(mask, (x1, y1), (x2, y2), 255, -1),在掩码上绘制矩形

    • (x1, y1), (x2, y2)起点和对角线坐标 ,
    • 255颜色, -1表全填充

  1. import cv2
  2. import numpy as np
  3. # 创建一个全黑的掩码
  4. mask = np.zeros((300, 300), dtype=np.uint8)
  5. # 在掩码上绘制一个白色矩形
  6. cv2.rectangle(mask, (50, 50), (100, 100), 255, -1)
  7. cv2.imshow('mask',mask)
  8. cv2.waitKey(0)
复制代码

2 答题卡辨认过程


2.1 显示、表面排序、透视变换函数

  1. # 显示图像的函数
  2. def cv_chow(name, img):
  3.     cv2.imshow(name, img)
  4.     cv2.waitKey(0)
  5. # 对四个点进行排序,确保顺序为:左上、右上、右下、左下
  6. def order_points(pts):
  7.     rect = np.zeros((4, 2), dtype="float32")
  8.     s = pts.sum(axis=1)  # 计算每个点的x+y值
  9.     rect[0] = pts[np.argmin(s)]  # 左上角点,x+y最小
  10.     rect[2] = pts[np.argmax(s)]  # 右下角点,x+y最大
  11.     diff = np.diff(pts, axis=1)  # 计算每个点的x-y值
  12.     rect[1] = pts[np.argmin(diff)]  # 右上角点,x-y最小
  13.     rect[3] = pts[np.argmax(diff)]  # 左下角点,x-y最大
  14.     return rect
  15. # 透视变换函数,将图像中的四边形区域变换为矩形
  16. def four_point_transform(img, pts):
  17.     rect = order_points(pts)  # 对四个点进行排序
  18.     (tl, tr, br, bl) = rect  # 分别获取左上、右上、右下、左下四个点
  19.     # 计算宽度和高度
  20.     widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
  21.     widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
  22.     max_width = max(int(widthA), int(widthB))  # 取最大宽度
  23.     heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
  24.     heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
  25.     max_height = max(int(heightA), int(heightB))  # 取最大高度
  26.     # 定义目标矩形的四个角点
  27.     dst = np.array([[0, 0], [max_width - 1, 0],
  28.                    [max_width - 1, max_height - 1], [0, max_height - 1]], dtype="float32")
  29.     # 计算透视变换矩阵并进行变换
  30.     m = cv2.getPerspectiveTransform(rect, dst)
  31.     warped = cv2.warpPerspective(img, m, (max_width, max_height))
  32.     return warped
  33. # 对轮廓进行排序,支持从左到右、从右到左、从上到下、从下到上四种排序方式
  34. def sort_contours(cons, method='left-to-right'):
  35.     reverse = False
  36.     i = 0
  37.     if method == 'right-to-left' or method == 'bottom-to-top':
  38.         reverse = True
  39.     if method == 'top-to-bottom' or method == 'bottom-to-top':
  40.         i = 1
  41.     boundingBoxes = [cv2.boundingRect(c) for c in cons]  # 获取每个轮廓的边界框
  42.     # 根据边界框的x或y坐标进行排序
  43.     (cons, boundingBoxes) = zip(*sorted(zip(cons, boundingBoxes),
  44.                                 key=lambda b: b[1][i], reverse=reverse))
  45.     return cons, boundingBoxes
复制代码
2.2 图像预处置处罚

通过灰度化高斯滤波Canny边沿检测,提取图像中的表面。
代码展示:
  1. img = cv2.imread('test_01.png')
  2. cont_img = img.copy()  # 复制图像用于绘制轮廓
  3. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转换为灰度图像
  4. blurred = cv2.GaussianBlur(gray, (5, 5), 0)  # 高斯模糊去噪
  5. edg = cv2.Canny(blurred, 75, 200)  # 边缘检测
  6. cv_chow('edg', edg)  # 显示边缘检测结果
  7. cnts = cv2.findContours(edg.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
  8. cv2.drawContours(cont_img, cnts, -1, (0, 0, 255), 3)  # 绘制轮廓
  9. cv_chow('cont_img', cont_img)  # 显示绘制轮廓后的图像
复制代码
运行结果:

2.3 透视变换

通过表面面积排序近似矩形,找到答题卡的外框,并进行透视变换,将答题卡区域变换为矩形。
代码展示:
  1. docCnt=None
  2. cnts = sorted(cnts,key=cv2.contourArea,reverse=True)
  3. for c in cnts:
  4.     p = cv2.arcLength(c,True)
  5.     approx = cv2.approxPolyDP(c,0.02*p,True)
  6.     if len(approx) == 4:
  7.         docCnt = approx
  8.         break
  9. warp_t = four_point_transform(img,docCnt.reshape(4,2))
  10. warp_new = warp_t.copy()
  11. cv_chow('warp',warp_t)
复制代码
2.4 二值化处置处罚

将图像转换为二值图像,便于后续处置处罚。
  1. warped = cv2.cvtColor(warp_t,cv2.COLOR_BGR2GRAY)
  2. thresh = cv2.threshold(warped,0,255,
  3.                        cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  4. cv_chow('thresh',thresh)
复制代码
运行结果:

2.5 表面检测与筛选

检测图像中的表面,并筛选出可能是选项的表面。
代码展示:
  1. thresh_cnt = thresh.copy()
  2. cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
  3. wraped_cont = cv2.drawContours(warp_t, cnts, -1, (0, 0, 255), 1)  # 绘制轮廓
  4. cv_chow('wraped_cont', wraped_cont)  # 显示绘制轮廓后的图像
  5. # 筛选出可能是选项的轮廓
  6. qus_cnts = []
  7. for c in cnts:
  8.     (x, y, w, h) = cv2.boundingRect(c)  # 获取轮廓的边界框
  9.     ar = w / float(h)  # 计算宽高比
  10.     if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:  # 过滤掉不符合条件的轮廓
  11.         qus_cnts.append(c)
  12. # 对选项轮廓进行排序(从上到下)
  13. qus_cnts = sort_contours(qus_cnts, method='top-to-bottom')[0]
复制代码
2.6 答案辨认和结果显示

通过掩膜和像素统计,辨认用户选择的答案,并与精确答案进行比对。在图像上标注精确答案,并计算和显示分数。
代码展示:
  1. # 计算正确答案的数量
  2. corret = 0
  3. for (q, i) in enumerate(np.arange(0, len(qus_cnts), 5)):  # 每5个轮廓为一组(一个问题的选项)
  4.     cnts = sort_contours(qus_cnts[i:i + 5])[0]  # 对每个问题的选项进行排序
  5.     bubbled = None
  6.     for (j, c) in enumerate(cnts):  # 遍历每个选项
  7.         mask = np.zeros(thresh.shape, dtype='uint8')  # 创建掩膜
  8.         cv2.drawContours(mask, [c], -1, 255, -1)  # 在掩膜上绘制轮廓
  9.         cv_chow('mask', mask)  # 显示掩膜
  10.         thresh_mask_and = cv2.bitwise_and(thresh, thresh, mask=mask)  # 应用掩膜
  11.         cv_chow('thresh_mask_and', thresh_mask_and)  # 显示掩膜应用后的图像
  12.         total = cv2.countNonZero(thresh_mask_and)  # 计算非零像素数量
  13.         if bubbled is None or total > bubbled[0]:  # 找到填充最多的选项
  14.             bubbled = (total, j)
  15.     color = (0, 0, 255)  # 默认颜色为红色
  16.     k = answer_key[q]  # 获取正确答案
  17.     if k == bubbled[1]:  # 如果选择的选项是正确答案
  18.         color = (0, 255, 0)  # 颜色设置为绿色
  19.         corret += 1  # 正确答案数量加1
  20.     cv2.drawContours(warp_new, [cnts[k]], -1, color, 3)  # 绘制正确答案的轮廓
  21.     cv_chow('warppeding', warp_new)  # 显示绘制后的图像
  22. # 计算并显示分数
  23. score = (corret / 5.0) * 100
  24. print('[INFO] SCORE: {:.2f}%'.format(score))
  25. cv2.putText(warp_new, ' {:.2f}%'.format(score), (10, 30),
  26.             cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 0.9, (0, 0, 255), 2)
  27. # 显示原始图像和处理后的图像
  28. cv2.imshow('IMG', img)
  29. cv2.imshow('exam', warp_new)
  30. cv2.waitKey(0)
复制代码
3 代码测试


代码展示:
  1. import numpy as npimport cv2# 显示图像的函数
  2. def cv_chow(name, img):
  3.     cv2.imshow(name, img)
  4.     cv2.waitKey(0)
  5. # 对四个点进行排序,确保顺序为:左上、右上、右下、左下
  6. def order_points(pts):
  7.     rect = np.zeros((4, 2), dtype="float32")
  8.     s = pts.sum(axis=1)  # 计算每个点的x+y值
  9.     rect[0] = pts[np.argmin(s)]  # 左上角点,x+y最小
  10.     rect[2] = pts[np.argmax(s)]  # 右下角点,x+y最大
  11.     diff = np.diff(pts, axis=1)  # 计算每个点的x-y值
  12.     rect[1] = pts[np.argmin(diff)]  # 右上角点,x-y最小
  13.     rect[3] = pts[np.argmax(diff)]  # 左下角点,x-y最大
  14.     return rect
  15. # 透视变换函数,将图像中的四边形区域变换为矩形
  16. def four_point_transform(img, pts):
  17.     rect = order_points(pts)  # 对四个点进行排序
  18.     (tl, tr, br, bl) = rect  # 分别获取左上、右上、右下、左下四个点
  19.     # 计算宽度和高度
  20.     widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
  21.     widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
  22.     max_width = max(int(widthA), int(widthB))  # 取最大宽度
  23.     heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
  24.     heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
  25.     max_height = max(int(heightA), int(heightB))  # 取最大高度
  26.     # 定义目标矩形的四个角点
  27.     dst = np.array([[0, 0], [max_width - 1, 0],
  28.                    [max_width - 1, max_height - 1], [0, max_height - 1]], dtype="float32")
  29.     # 计算透视变换矩阵并进行变换
  30.     m = cv2.getPerspectiveTransform(rect, dst)
  31.     warped = cv2.warpPerspective(img, m, (max_width, max_height))
  32.     return warped
  33. # 对轮廓进行排序,支持从左到右、从右到左、从上到下、从下到上四种排序方式
  34. def sort_contours(cons, method='left-to-right'):
  35.     reverse = False
  36.     i = 0
  37.     if method == 'right-to-left' or method == 'bottom-to-top':
  38.         reverse = True
  39.     if method == 'top-to-bottom' or method == 'bottom-to-top':
  40.         i = 1
  41.     boundingBoxes = [cv2.boundingRect(c) for c in cons]  # 获取每个轮廓的边界框
  42.     # 根据边界框的x或y坐标进行排序
  43.     (cons, boundingBoxes) = zip(*sorted(zip(cons, boundingBoxes),
  44.                                 key=lambda b: b[1][i], reverse=reverse))
  45.     return cons, boundingBoxes
  46. # 定义答案键,表现每个问题的精确答案answer_key = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}# 读取图像img = cv2.imread('test_01.png')cont_img = img.copy()  # 复制图像用于绘制表面gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转换为灰度图像blurred = cv2.GaussianBlur(gray, (5, 5), 0)  # 高斯模糊去噪edg = cv2.Canny(blurred, 75, 200)  # 边沿检测cv_chow('edg', edg)  # 显示边沿检测结果# 查找表面cnts = cv2.findContours(edg.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]cv2.drawContours(cont_img, cnts, -1, (0, 0, 255), 3)  # 绘制表面cv_chow('cont_img', cont_img)  # 显示绘制表面后的图像# 找到最大的四边形表面(假设为答题卡的外框)docCnt = Nonecnts = sorted(cnts, key=cv2.contourArea, reverse=True)  # 按表面面积从大到小排序for c in cnts:    p = cv2.arcLength(c, True)  # 计算表面周长    approx = cv2.approxPolyDP(c, 0.02 * p, True)  # 多边形逼近    if len(approx) == 4:  # 假如是四边形        docCnt = approx        break# 对图像进行透视变换,将答题卡区域变换为矩形warp_t = four_point_transform(img, docCnt.reshape(4, 2))warp_new = warp_t.copy()  # 复制变换后的图像cv_chow('warp', warp_t)  # 显示透视变换后的图像# 对变换后的图像进行二值化处置处罚warped = cv2.cvtColor(warp_t, cv2.COLOR_BGR2GRAY)thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]cv_chow('thresh', thresh)  # 显示二值化图像# 查找二值化图像中的表面thresh_cnt = thresh.copy()
  47. cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
  48. wraped_cont = cv2.drawContours(warp_t, cnts, -1, (0, 0, 255), 1)  # 绘制轮廓
  49. cv_chow('wraped_cont', wraped_cont)  # 显示绘制轮廓后的图像
  50. # 筛选出可能是选项的轮廓
  51. qus_cnts = []
  52. for c in cnts:
  53.     (x, y, w, h) = cv2.boundingRect(c)  # 获取轮廓的边界框
  54.     ar = w / float(h)  # 计算宽高比
  55.     if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:  # 过滤掉不符合条件的轮廓
  56.         qus_cnts.append(c)
  57. # 对选项轮廓进行排序(从上到下)
  58. qus_cnts = sort_contours(qus_cnts, method='top-to-bottom')[0]
  59. # 计算精确答案的数目corret = 0for (q, i) in enumerate(np.arange(0, len(qus_cnts), 5)):  # 每5个表面为一组(一个问题的选项)    cnts = sort_contours(qus_cnts[i:i + 5])[0]  # 对每个问题的选项进行排序    bubbled = None    for (j, c) in enumerate(cnts):  # 遍历每个选项        mask = np.zeros(thresh.shape, dtype='uint8')  # 创建掩膜        cv2.drawContours(mask, [c], -1, 255, -1)  # 在掩膜上绘制表面        cv_chow('mask', mask)  # 显示掩膜        thresh_mask_and = cv2.bitwise_and(thresh, thresh, mask=mask)  # 应用掩膜        cv_chow('thresh_mask_and
复制代码
运行结果:



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

光之使者

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表