2401_88941799 2025-04-24 08:53 采纳率: 100%
浏览 6
已结题

python-opencv实战问题解决

对于opencv答题卡识别实战项目的代码问题
出现诸多错误请问怎么解决

img

import numpy as np
import imutils
import cv2

image_path = r"F:\pythonnotebook\123.jpg"
image = cv2.imread(image_path)

# 正确答案
ANSWER_KEY = {0: 1, 1: 2, 2: 0, 3: 2, 4: 4}


def order_points(pts):
    # 一共4个坐标点
    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)]

    # 计算右上和左下
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    return rect


def four_point_transform(image, 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(image, 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


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 读取命令行参数指定的图片
image = cv2.imread(r"F:\pythonnotebook\123.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, 45, 200)
cv2.imshow('edged', edged)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 锐化处理
kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
sharpened = cv2.filter2D(gray, -1, kernel)
# 轮廓检测
contours, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = contours


# 打印轮廓数据类型和长度
print(type(cnts))
print(len(cnts))

# 根据轮廓大小进行排序
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

docCnt = None

# 遍历每一个轮廓
for c in cnts:
    # 近似
    peri = cv2.arcLength(c, closed=True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, closed=True)

    # 准备做透视变换
    if len(approx) == 4:
        docCnt = approx
        break

# 执行透视变换
if docCnt is not None:
    warped = four_point_transform(gray, docCnt.reshape(4, 2))
    cv_show('warped', warped)
    # Otsu's 阈值处理
    thresh = cv2.threshold(warped, 0, 255,
                           cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    cv_show('thresh', thresh)
    thresh_contours = thresh.copy()

    # 找到每一个圆圈轮廓
    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)[1]
    if len(cnts) == 0:
        print("未检测到圆圈轮廓,请检查图片或调整阈值处理参数!")
        import sys

        sys.exit(1)

    cv2.drawContours(contours_img, contours, -1, (0, 255, 0), 2)
    cv_show('thresh_contours', thresh_contours)

    questionCnts = []
    for c in cnts:
        try:
            # 强制类型转换 + 维度检查
            c = np.array(c, dtype=np.int32).reshape(-1, 1, 2)
            if len(c) < 1:  # 完全空的情况
                continue

            (x, y, w, h) = cv2.boundingRect(c)
            ar = w / float(h)

            # 你的筛选条件
            if w >= 0.5 and h >= 0.5 and 0.3 <= ar <= 1.5:
                questionCnts.append(c)

        except Exception as e:
            print(f"处理轮廓时出错: {e}")
            continue
    if len(questionCnts) == 0:
        print("警告:未检测到任何有效气泡轮廓!")
        print("请检查:")
        print("1. 阈值处理是否合适(当前阈值:Otsu自动阈值)")
        print("2. 气泡大小参数")
        print("3. 原始图像是否包含清晰的圆形气泡区域")
        cv2.imshow("Threshold Image", thresh)  # 显示二值化图像供调试
        cv2.waitKey(0)
        exit()  # 或改为跳过评分部分
    else:
        # 只有检测到气泡时才继续处理
        questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0]


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

    # 每排有5个选项
    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)  # -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

        # 绘图

        if k < len(cnts):
            cv2.drawContours(warped, [cnts[k]], -1, color, 3)
        else:
            print(f"警告:索引 {k} 超出轮廓列表长度 {len(cnts)}")

    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, 0, 255), 2)
    cv2.imshow("Original", image)
    cv2.imshow("Exam", warped)
    cv2.waitKey(0)
else:
    print("未检测到答题卡轮廓,请检查图片或调整边缘检测参数!")


展开全部

  • 写回答

4条回答 默认 最新

  • 宝码香车 JavaScript领域优质创作者 2025-04-24 09:23
    关注

    让【宝码香车】来帮你解答,本回答参考DeepSeek编写,并整理提供。
    如果还有疑问可以点击头像关注私信或评论。如果答案让您满意,请采纳、点赞、关注,非常感谢!

    OpenCV答题卡识别项目问题分析与解决方案

    根据您提供的代码,我发现了几个潜在的问题点并提供解决方案:

    主要问题分析

    1. 图像路径问题:代码中两次定义了image_path,可能导致混淆
    2. 轮廓处理问题:在检测气泡轮廓时可能存在类型转换和维度问题
    3. 重复排序操作questionCnts被排序了两次
    4. 索引越界风险:在绘制正确答案时没有充分检查索引范围

    解决方案代码

    import numpy as np
    import cv2
    
    # 正确答案
    ANSWER_KEY = {0: 1, 1: 2, 2: 0, 3: 2, 4: 4}
    
    def order_points(pts):
        rect = np.zeros((4, 2), dtype="float32")
        s = pts.sum(axis=1)
        rect[0] = pts[np.argmin(s)]
        rect[2] = pts[np.argmax(s)]
        diff = np.diff(pts, axis=1)
        rect[1] = pts[np.argmin(diff)]
        rect[3] = pts[np.argmax(diff)]
        return rect
    
    def four_point_transform(image, pts):
        rect = order_points(pts)
        (tl, tr, br, bl) = rect
    
        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(image, 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
    
    def cv_show(name, img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    
    # 读取图片
    image_path = r"F:\pythonnotebook\123.jpg"  # 确保路径正确
    image = cv2.imread(image_path)
    if image is None:
        print(f"无法读取图片,请检查路径: {image_path}")
        exit()
    
    # 预处理
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edged = cv2.Canny(blurred, 45, 200)
    
    # 轮廓检测
    contours = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = imutils.grab_contours(contours)
    cnts = sorted(contours, key=cv2.contourArea, reverse=True)
    
    docCnt = None
    for c in cnts:
        peri = cv2.arcLength(c, True)
        approx = cv2.approxPolyDP(c, 0.02 * peri, True)
        if len(approx) == 4:
            docCnt = approx
            break
    
    if docCnt is None:
        print("未检测到答题卡轮廓,请检查图片或调整边缘检测参数!")
        exit()
    
    # 透视变换
    warped = four_point_transform(gray, docCnt.reshape(4, 2))
    thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    
    # 检测气泡轮廓
    cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    questionCnts = []
    
    for c in cnts:
        (x, y, w, h) = cv2.boundingRect(c)
        ar = w / float(h)
        if w >= 20 and h >= 20 and 0.7 <= ar <= 1.3:  # 调整参数以适应实际气泡大小
            questionCnts.append(c)
    
    if len(questionCnts) == 0:
        print("未检测到任何气泡,请调整参数或检查图片质量")
        exit()
    
    # 排序气泡轮廓
    questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0]
    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 = np.zeros(thresh.shape, dtype="uint8")
            cv2.drawContours(mask, [c], -1, 255, -1)
            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.get(q, -1)
        
        if k != -1 and k < len(cnts):
            if k == bubbled[1]:
                color = (0, 255, 0)
                correct += 1
            cv2.drawContours(warped, [cnts[k]], -1, color, 3)
    
    score = (correct / 5.0) * 100
    print(f"得分: {score:.2f}%")
    cv2.putText(warped, f"{score:.2f}%", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
    cv2.imshow("Original", image)
    cv2.imshow("Exam", warped)
    cv2.waitKey(0)
    

    关键改进点

    1. 图像路径检查:添加了图像读取成功检查
    2. 轮廓处理优化
      • 使用imutils.grab_contours确保轮廓格式一致
      • 调整了气泡检测的参数(w和h的阈值)
    3. 错误处理增强
      • 添加了对ANSWER_KEY中索引的检查
      • 更完善的轮廓存在性检查
    4. 性能优化
      • 移除了重复的排序操作
      • 简化了部分逻辑

    调试建议

    1. 在关键步骤后添加图像显示,检查中间结果
    2. 调整Canny边缘检测的阈值(45, 200)以适应不同图片
    3. 根据实际气泡大小调整检测参数(w >= 20, h >= 20)
    4. 确保答题卡图片质量足够高,光照均匀

    如果问题仍然存在,建议提供具体的错误信息和中间处理结果的图像,以便更精准地定位问题。

    展开全部

    本回答被专家选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(3条)
编辑
预览

报告相同问题?

问题事件

  • 系统已结题 5月5日
  • 专家已采纳回答 4月27日
  • 创建了问题 4月24日
手机看
程序员都在用的中文IT技术交流社区

程序员都在用的中文IT技术交流社区

专业的中文 IT 技术社区,与千万技术人共成长

专业的中文 IT 技术社区,与千万技术人共成长

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回
顶部