OpenCV 学习:图像拼接与答题卡识别的实现

文章目录

    • 一、基于特征匹配的图像拼接技术
      • [1.1 核心功能函数](#1.1 核心功能函数)
      • [1.2 特征检测与描述](#1.2 特征检测与描述)
      • [1.3 特征点匹配](#1.3 特征点匹配)
      • [1.4 透视变换与图像融合](#1.4 透视变换与图像融合)
    • 二、答题卡识别系统
      • [2.1 坐标点排序与透视变换](#2.1 坐标点排序与透视变换)
      • [2.2 图像预处理与轮廓检测](#2.2 图像预处理与轮廓检测)
      • [2.3 答题区域识别与答案判断](#2.3 答题区域识别与答案判断)

一、基于特征匹配的图像拼接技术

图像拼接是将多张有重叠区域的图片合成一张全景图的过程,核心技术包括特征点检测、匹配和透视变换。

1.1 核心功能函数

python 复制代码
def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)

功能分析

  • 这是一个图像显示辅助函数
  • cv2.imshow() 创建指定名称的窗口显示图像
  • cv2.waitKey(0) 等待键盘输入,参数0表示无限等待
  • 按任意键后窗口关闭,程序继续执行

1.2 特征检测与描述

python 复制代码
def detectAndDescribe(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 将彩色图片转换成灰度图
    sift = cv2.SIFT_create()  # 建立SIFT生成器
    # 检测SIFT特征点,并计算描述符,第二个参数为掩膜
    (kps, des) = sift.detectAndCompute(gray, None)
    # 将结果转换成NumPy数组
    kps_float = np.float32([kpc.pt for kpc in kps])
    # kpc 包含两个值,分别是关键点在图像中的 x 和 y 坐标。这些坐标通常是浮点数,可以精确地描述关键点在图像中的位置。
    return (kps, kps_float, des)  # 返回特征点集,及对应的描述特征

参数与方法解析

  • cv2.cvtColor(image, cv2.COLOR_BGR2GRAY):颜色空间转换

    • 将BGR彩色图像转为灰度图像,减少计算复杂度
    • 多数特征检测算法在灰度图上工作效果更好
  • cv2.SIFT_create():创建SIFT检测器

    • SIFT(尺度不变特征变换)对旋转、尺度缩放、亮度变化保持不变性
    • 返回的检测器对象可用于关键点检测和描述符计算
  • sift.detectAndCompute(gray, None):检测关键点并计算描述符

    • 第一个参数:输入图像(灰度图)
    • 第二个参数:掩膜,指定图像中哪些区域需要检测(None表示全图检测)
    • 返回值:关键点列表和对应的描述符矩阵

1.3 特征点匹配

python 复制代码
'''建立匹配器BFMatcher,在匹配大图训练集合时使用FlannBasedMatcher速度更快。'''
matcher = cv2.BFMatcher_create()
rawMatches = matcher.knnMatch(desB, desA, k=2)
good = []
matches = []

for m in rawMatches:
    # 当最近距离跟次近距离的比值小于0.65时,保留此匹配对
    if len(m) == 2 and m[0].distance < 0.65 * m[1].distance:
        good.append(m)
        # 存储两个点在featuresA、featuresB中的索引值
        matches.append((m[0].queryIdx, m[0].trainIdx))

匹配策略分析

  • cv2.BFMatcher_create():创建暴力匹配器

    • 计算查询图像(desB)中每个描述符与训练图像(desA)中所有描述符的距离
    • 返回最匹配的k个结果(此处k=2)
  • 比率测试(Ratio Test):

    • 比较最佳匹配距离与次佳匹配距离的比值
    • 比值小于0.65时认为是可靠匹配
    • 有效过滤错误匹配,提高匹配质量

1.4 透视变换与图像融合

python 复制代码
if len(matches) > 4:  # 当筛选后的匹配对大于4时,计算视角变换矩阵。
    # 获取匹配对的点坐标
    ptsB = np.float32([kps_floatB[i] for (i, _) in matches])  # matches是通过阈值筛选之后的特征点对象
    ptsA = np.float32([kps_floatA[i] for (_, i) in matches])  # kps_floatA是图片A中的全部特征点坐标

    # 计算透视变换矩阵
    # findHomography(srcPoints, dstPoints, method=None, ransacReprojThreshold=None, mask=None, maxIters=None, confidence=None)
    # 计算视角变换矩阵,透视变换函数,与cv2.getPerspectiveTransform()的区别在与可多个数据点变换
    # 参数srcPoints:图片A的匹配点坐标
    # 参数dstPoints:图片B的匹配点坐标
    # 参数method:计算变换矩阵的方法。
    # 0 - 使用所有的点,最小二乘
    # RANSAC - 基于随机样本一致性
    # LMEDS - 最小中值
    # RHO - 基于渐近样本一致性
    # ransacReprojThreshold: 最大允许重投影错误阈值。该参数只有在method参数为RANSAC与RHO的时启用,默认为3
    # 返回值:中值为变换矩阵,mask是掩模标志,指示哪些点对应内点,哪些是外点。
    (H, mask) = cv2.findHomography(ptsB, ptsA, cv2.RANSAC, 10)

透视变换关键参数

  • cv2.findHomography(srcPoints, dstPoints, method, ransacReprojThreshold)
    • srcPoints:源图像中的点坐标(图像B)
    • dstPoints:目标图像中的点坐标(图像A)
    • method=cv2.RANSAC :使用RANSAC算法估计单应性矩阵
      • RANSAC对异常值(错误匹配)具有鲁棒性
      • 随机选择最小样本集,找到使内点数量最多的模型
    • ransacReprojThreshold=10 :重投影误差阈值
      • 将源点投影到目标图像,计算实际点与投影点的距离
      • 距离小于10的被认为是内点,否则是外点
python 复制代码
# 应用透视变换
result = cv2.warpPerspective(imageB, H, (imageB.shape[1] + imageA.shape[1], imageB.shape[0]))
cv_show('resultB', result)

# 将图片A放入结果图像中
result[0:imageA.shape[0], 0:imageA.shape[1]] = imageA
cv_show('result', result)
cv2.imwrite('pingjie.jpg', result)



图像融合过程

  1. cv2.warpPerspective() 将图像B变换到图像A的视角
  2. 创建足够大的画布容纳两张图像
  3. 将原始图像A复制到结果图像的相应位置
  4. 由于透视变换,图像B会与图像A自然融合

二、答题卡识别系统

答题卡识别系统通过计算机视觉技术自动批改选择题,流程包括图像预处理、透视矫正、轮廓检测和答案判断。

2.1 坐标点排序与透视变换

python 复制代码
def order_points(pts):
    # 一共4个坐标点
    rect = np.zeros((4, 2), dtype="float32")  # 用来存储排序之后的坐标位置
    # 按顺序找到对应坐标0123分别是:左上、右上、右下、左下
    s = pts.sum(axis=1)  # 对pts矩阵的每一行进行求和操作。(x+y)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts, axis=1)  # 对pts矩阵的每一行进行求差操作。(y-x)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

坐标排序逻辑

  • 左上角:x+y值最小(最靠近原点)
  • 右下角:x+y值最大(离原点最远)
  • 右上角:y-x值最小(x相对较大,y相对较小)
  • 左下角:y-x值最大(y相对较大,x相对较小)
python 复制代码
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

透视矫正步骤

  1. 计算文档的实际宽度和高度
  2. 定义变换后的标准矩形坐标
  3. 使用 cv2.getPerspectiveTransform() 计算变换矩阵
  4. 应用变换得到矫正后的图像

2.2 图像预处理与轮廓检测

python 复制代码
# 预处理
image = cv2.imread(r'./images/test_01.png')
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, ksize=(5, 5), sigmaX=0)
cv_show('blurred', blurred)
edges = cv2.Canny(blurred, threshold1=75, threshold2=200)  # 修正参数名
cv_show('edges', edges)

预处理参数分析

  • cv2.GaussianBlur(gray, ksize=(5, 5), sigmaX=0):高斯模糊

    • ksize=(5,5):高斯核大小,必须是正奇数
    • sigmaX=0:X方向标准差,0表示根据核大小自动计算
    • 目的:减少噪声,平滑图像
  • cv2.Canny(blurred, threshold1=75, threshold2=200):边缘检测

    • threshold1=75:低阈值,强度低于75的边被丢弃
    • threshold2=200:高阈值,强度高于200的边被保留为强边缘
    • 介于两者之间的边根据连通性判断
python 复制代码
# 轮廓检测
_, cnts, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)  # 修正:在原图上绘制轮廓
cv_show('contours_img', contours_img)
docCnt = None

轮廓检测参数

  • cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    • RETR_EXTERNAL:只检测最外层轮廓
    • CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,只保留端点

2.3 答题区域识别与答案判断

python 复制代码
# 找到每一个圆轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
warped_Contours = cv2.drawContours(warped_t.copy(), cnts, -1, (0, 255, 0), 1)  # 创建副本
cv_show('warped_Contours', warped_Contours)

questionCnts = []
for c in cnts:  # 遍历轮廓并计算比例和大小
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)
    # 根据实际情况指定标准
    if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:
        questionCnts.append(c)
print(len(questionCnts))

气泡筛选标准

  • 宽度≥20且高度≥20:过滤小噪声点
  • 宽高比0.9-1.1:筛选近似圆形的轮廓
  • 确保只识别答题气泡,排除其他干扰
python 复制代码
# 每排有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)进行"与"操作,只保留mask位置中的内容
        thresh_mask_and = cv2.bitwise_and(thresh, thresh, mask=mask)
        cv_show('thresh_mask_and', thresh_mask_and)
        total = cv2.countNonZero(thresh_mask_and)  # 统计灰度值不为0的像素数

        # 通过阈值判断,保存灰度值最大的序号
        if bubbled is None or total > bubbled[0]:
            bubbled = (total, j)

答案判断逻辑

  1. 为每个气泡创建掩膜
  2. 使用 cv2.bitwise_and() 提取气泡区域
  3. 统计区域内非零像素数量(涂黑部分)
  4. 选择非零像素最多的选项作为学生答案
python 复制代码
# 对比正确答案
color = (0, 0, 255)
k = ANSWER_KEY[q]

if k == bubbled[1]:  # 判断正确
    color = (0, 255, 0)
    correct += 1

cv2.drawContours(warped_new, [cnts[k]], -1, color, 3)  # 绘图
cv_show('warpeding', warped_new)


可视化反馈

  • 红色轮廓:正确答案位置(如果学生答错)
  • 绿色轮廓:正确答案位置(如果学生答对)
  • 直观展示批改结果
相关推荐
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers][base]platform
linux·笔记·学习
bjxiaxueliang2 小时前
一文掌握Python Flask:HTTP微服务开发从入门到部署
python·http·flask
SunnyRivers2 小时前
Python 中的 HTTP 客户端:Requests、HTTPX 与 AIOHTTP 对比
python·httpx·requests·aiohttp·区别
Σίσυφος19003 小时前
霍夫变换vs LS vs RANSAC 拟合直线 MATLAB实现
算法·计算机视觉·matlab
u0109272713 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
lixin5565563 小时前
基于迁移学习的图像风格增强器
java·人工智能·pytorch·python·深度学习·语言模型
阡陌..3 小时前
浅谈SAR图像处理---形态学滤波
图像处理·人工智能·python
W_a_i_T3 小时前
【Coding日记】菜鸟编程C语言100例——第一题
c语言·学习·编程思维·菜鸟编程
测试人社区-浩辰4 小时前
AI与区块链结合的测试验证方法
大数据·人工智能·分布式·后端·opencv·自动化·区块链