python学习 - 使用OpenCV库(cv2)和imutils库实现辅助答题卡判别

测试数据见文章顶部位置资源!!!

使用了OpenCV库(cv2)和imutils库。代码的主要目的是处理图像中的问题,如识别图像中的文字,并对其进行分析和排序。
辅助答题卡判别

# -*- coding:utf-8 -*-
from imutils.perspective import four_point_transform
# 图像处理函数,对OpenCV的简化
from imutils import contours
# 支持大量的维度数组与矩阵运算
import numpy as np
# OpenCV库(cv2)
import cv2 as cv
# https://github.com/qindongliang/answer_sheet_scan
ANSWER_KEY_SCORE = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}

ANSWER_KEY = {0: "A", 1: "B", 2: "C", 3: "D", 4: "E"}

# 加载一个图片到opencv中
img = cv.imread('test01.jpg')

cv.imshow("orgin", img)

# 转化成灰度图片
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

cv.imshow("gray", gray)

gaussian_bulr = cv.GaussianBlur(gray, (5, 5), 0)  # 高斯模糊

cv.imshow("gaussian", gaussian_bulr)

edged = cv.Canny(gaussian_bulr, 75, 200)  # 边缘检测,灰度值小于2参这个值的会被丢弃,大于3参这个值会被当成边缘,在中间的部分,自动检测

cv.imshow("edged", edged)

# 寻找轮廓
image, cts, hierarchy = cv.findContours(edged.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

# 给轮廓加标记,便于我们在原图里面观察,注意必须是原图才能画出红色,灰度图是没有颜色的
# cv.drawContours(img, cts, -1, (0,0,255), 3)

# 按面积大小对所有的轮廓排序
list = sorted(cts, key=cv.contourArea, reverse=True)

print("寻找轮廓的个数:", len(cts))
cv.imshow("draw_contours", img)

# 正确题的个数
correct_count = 0

for c in list:
    # 周长,第1个参数是轮廓,第二个参数代表是否是闭环的图形
    peri = 0.01 * cv.arcLength(c, True)
    # 获取多边形的所有定点,如果是四个定点,就代表是矩形
    approx = cv.approxPolyDP(c, peri, True)
    # 打印定点个数
    print("顶点个数:", len(approx))

    if len(approx) == 4:  # 矩形
        # 透视变换提取原图内容部分
        ox_sheet = four_point_transform(img, approx.reshape(4, 2))
        # 透视变换提取灰度图内容部分
        tx_sheet = four_point_transform(gray, approx.reshape(4, 2))

        cv.imshow("ox", ox_sheet)
        cv.imshow("tx", tx_sheet)
        # 使用ostu二值化算法对灰度图做一个二值化处理
        ret, thresh2 = cv.threshold(tx_sheet, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
        cv.imshow("ostu", thresh2)

        # 继续寻找轮廓
        r_image, r_cnt, r_hierarchy = cv.findContours(thresh2.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

        print("找到轮廓个数:", len(r_cnt))

        # 使用红色标记所有的轮廓
        # cv.drawContours(ox_sheet,r_cnt,-1,(0,0,255),2)

        # 把所有找到的轮廓,给标记出来

        questionCnts = []
        for cxx in r_cnt:
            # 通过矩形,标记每一个指定的轮廓
            x, y, w, h = cv.boundingRect(cxx)
            ar = w / float(h)

            if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
                # 使用红色标记,满足指定条件的图形
                # cv.rectangle(ox_sheet, (x, y), (x + w, y + h), (0, 0, 255), 2)
                # 把每个选项,保存下来
                questionCnts.append(cxx)

        cv.imshow("ox_1", ox_sheet)
        # 按坐标从上到下排序
        questionCnts = contours.sort_contours(questionCnts, method="top-to-bottom")[0]

        # 使用np函数,按5个元素,生成一个集合
        for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):

            # 获取按从左到右的排序后的5个元素
            cnts = contours.sort_contours(questionCnts[i:i + 5])[0]

            bubble_rows = []

            # 遍历每一个选项
            for (j, c) in enumerate(cnts):
                # 生成一个大小与透视图一样的全黑背景图布
                mask = np.zeros(tx_sheet.shape, dtype="uint8")
                # 将指定的轮廓+白色的填充写到画板上,255代表亮度值,亮度=255的时候,颜色是白色,等于0的时候是黑色
                cv.drawContours(mask, [c], -1, 255, -1)
                # 做两个图片做位运算,把每个选项独自显示到画布上,为了统计非0像素值使用,这部分像素最大的其实就是答案
                mask = cv.bitwise_and(thresh2, thresh2, mask=mask)
                # cv.imshow("c" + str(i), mask)
                # 获取每个答案的像素值
                total = cv.countNonZero(mask)
                # 存到一个数组里面,tuple里面的参数分别是,像素大小和答案的序号值
                # print(total,j)
                bubble_rows.append((total, j))

            bubble_rows = sorted(bubble_rows, key=lambda x: x[0], reverse=True)
            # 选择的答案序号
            choice_num = bubble_rows[0][1]
            print("答案:{} 数据: {}".format(ANSWER_KEY.get(choice_num), bubble_rows))

            fill_color = None

            # 如果做对就加1
            if ANSWER_KEY_SCORE.get(q) == choice_num:
                fill_color = (0, 255, 0)  # 正确 绿色
                correct_count = correct_count + 1
            else:
                fill_color = (0, 0, 255)  # 错误 红色

            cv.drawContours(ox_sheet, cnts[choice_num], -1, fill_color, 2)

        cv.imshow("answer_flagged", ox_sheet)

        text1 = "total: " + str(len(ANSWER_KEY)) + ""

        text2 = "right: " + str(correct_count)

        text3 = "score: " + str(correct_count * 1.0 / len(ANSWER_KEY) * 100) + ""

        font = cv.FONT_HERSHEY_SIMPLEX
        cv.putText(ox_sheet, text1 + "  " + text2 + "  " + text3, (10, 30), font, 0.5, (0, 0, 255), 2)

        cv.imshow("score", ox_sheet)

        break

cv.waitKey(0)

# -*- coding:utf-8 -*-
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import cv2 as cv

# 加载原图,可在项目imgs/example02目录下找到
img = cv.imread("test01.jpg")

# cv.resizeWindow("enhanced", 240, 280);
# 打印原图
cv.imshow("orgin", img)

# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 打印灰度图
cv.imshow("gray", gray)

# 高斯滤波,清除一些杂点
blur = cv.GaussianBlur(gray, (3, 3), 0)

# 自适应二值化算法
thresh2 = cv.adaptiveThreshold(blur, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 131, 4)

# 打印二值化后的图
cv.imshow("thresh2", thresh2)

# 寻找轮廓
image, cts, hierarchy = cv.findContours(thresh2, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

# 打印找到的轮廓
print("轮廓数:", len(cts))

# 对拷贝的原图进行轮廓标记
contour_flagged = cv.drawContours(img.copy(), cts, -1, (0, 0, 255), 3)
# 打印轮廓图
cv.imshow("contours_flagged", contour_flagged)
# 按像素面积降序排序
list = sorted(cts, key=cv.contourArea, reverse=True)

# 遍历轮廓
for ct in list:
    # 周长,第1个参数是轮廓,第二个参数代表是否是闭环的图形
    peri = 0.01 * cv.arcLength(ct, True)
    # 获取多边形的所有定点,如果是四个定点,就代表是矩形
    approx = cv.approxPolyDP(ct, peri, True)
    # 只考虑矩形
    if len(approx) == 4:

        # 从原图中提取所需的矫正图片
        ox = four_point_transform(img, approx.reshape(4, 2))
        # 从原图中提取所需的矫正图片
        tx = four_point_transform(gray, approx.reshape(4, 2))

        # 打印矫正后的灰度图
        cv.imshow("tx", tx)

        # 对矫正图进行高斯模糊
        blur = cv.GaussianBlur(tx, (3, 3), 0)

        # 对矫正图做自适应二值化
        thresh2 = cv.adaptiveThreshold(blur, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 131, 4)

        # 打印矫正后的二值化图
        cv.imshow("tx_thresh2", thresh2)

        # 获取轮廓
        r_image, r_cts, r_hierarchy = cv.findContours(thresh2, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

        # 打印得到轮廓数量
        print("第二层轮廓数:", len(r_cts))

        # 用于存储答案的python list变量
        question_list = []
        for r_ct in r_cts:
            # 转为矩形,分别获取 x,y坐标,及矩形的宽和高
            x, y, w, h = cv.boundingRect(r_ct)

            # 过滤掉不符合答案坐标和长宽的选项
            if x > 2 and y > 2 and w > 20 and h > 20:
                # cv.drawContours(ox, r_ct, -1, (0, 0, 255), 1)
                question_list.append(r_ct)

        print("答案总数:", len(question_list))

        # 按坐标从上到下排序
        questionCnts = contours.sort_contours(question_list, method="top-to-bottom")[0]

        #  使用np函数,按5个元素,生成一个集合
        for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):

            # 每一个行5个答案,从左到右排序
            cnts = contours.sort_contours(questionCnts[i:i + 5])[0]

            # 存储一行题里面的每个答案
            ans_list = []
            for (j, cc) in enumerate(cnts):
                # 生成全黑画布
                mask = np.zeros(thresh2.shape, dtype="uint8")
                # 将每一个答案按轮廓写上去,并将填充颜色设置成白色
                tpp = cv.drawContours(mask, [cc], -1, 255, -1)
                # 两个图片做位运算
                mask = cv.bitwise_and(thresh2, thresh2, mask=mask)
                # 统计每个答案的像素
                total = cv.countNonZero(mask)

                # 添加到集合里面
                ans_list.append((total, j))

            # 按像素大小排序
            ans_list = sorted(ans_list, key=lambda x: x[0], reverse=True)

            max_ans_num = ans_list[0][1]
            max_ans_size = ans_list[0][0]
            print("答案序号:", max_ans_num, "列表:", ans_list)

            # 给选中答案,标记成红色
            cv.drawContours(ox, cnts[max_ans_num], -1, (0, 0, 255), 2)

        cv.imshow("answer_flagged", ox)

        # 最大的轮廓就是我们想要的,之后的就可以结束循环了
        break

# 阻塞等待窗体关闭
cv.waitKey(0)

相关推荐

  1. opencv-python cv2.imread() 读取图片

    2024-06-07 10:34:01       16 阅读
  2. opencv-python cv2边界填充&resize图片

    2024-06-07 10:34:01       12 阅读
  3. opencv-python cv2图像二值化详解

    2024-06-07 10:34:01       14 阅读
  4. opencv-python cv2逐帧读取视频cv2.VideoCapture()

    2024-06-07 10:34:01       14 阅读
  5. Python-第三方cv2

    2024-06-07 10:34:01       13 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-07 10:34:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-07 10:34:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-07 10:34:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-07 10:34:01       18 阅读

热门阅读

  1. shell脚本对编码和行尾符敏感吗

    2024-06-07 10:34:01       9 阅读
  2. 2-链表-61-相交节点-LeetCode160

    2024-06-07 10:34:01       7 阅读
  3. GaussDB 数据库的事务管理

    2024-06-07 10:34:01       8 阅读
  4. Python语言回归:深入探索与实战应用

    2024-06-07 10:34:01       9 阅读
  5. 8086 汇编笔记(十一):内中断

    2024-06-07 10:34:01       9 阅读
  6. OC和Swift的区别,发送消息和执行方法的区别

    2024-06-07 10:34:01       6 阅读
  7. AWS Load Balancer Controller 实践

    2024-06-07 10:34:01       7 阅读
  8. iOS查看、分离、合并库framework的架构

    2024-06-07 10:34:01       7 阅读
  9. 图论第5天

    2024-06-07 10:34:01       8 阅读