在 OpenCV 中使用 ChArUco 棋盘

TLDR:目前在线的许多 Charuco 示例代码都已过时,并且会让你出错。如果 ChatGPT 或 Stack Exchange 建议你使用:cv2.aruco.CharucoBoard_create(length, width, ...)你会发现正确的语法是 cv2.aruco.CharucoBoard((length, width), ...)。希望下面的示例函数能有所帮助。

如果你之前使用过OpenCV,你可能熟悉我们用于校准相机的经典棋盘格。你可能也熟悉Aruco标记,它们用于机器人应用中的姿态估计。这些标记带有唯一标识符,类似于棋盘格,我们可以使用其角点来确定其坐标框架。Charuco棋盘是将它们结合到一个棋盘上的有用方式,如图所示。这使我们能够唯一地识别棋盘的每个角。识别唯一的角点有两个原因:
  • 棋盘的一部分被遮挡时无法检测到,但Charuco棋盘不受此影响。
  • 识别角点可以帮助我们更准确地描述棋盘的坐标框架。
自从一些更新的 OpenCV 版本(撰写本文时最新版本为 4.8.0)以来,Aruco 库发生了一些相当重大的变化 - 目前你在网上发现的大部分代码已经过时。希望本指南将提供更新的演练,并向你展示我如何使用 Charuco 棋盘进行图像校正和姿势估计(对于标准棋盘来说这是一项艰巨的任务)。确保你正在运行最新版本的Python,还有OpenCV的contrib版本。这一步非常重要,因为它为我们提供了正确的库:
pip install opencv-contrib-python

使用“contrib”版本来访问该库非常重要,并确保不要安装无界面版本(它会阻止 GUI 出现)。如果你在尝试运行任何 Aruco 或 ChAruco 库函数时遇到错误,则可能是你的安装出现错误。

接下来,你可以开始编写脚本。

创建Charuco棋盘

最好的起点是为自己创建一个 Charuco 棋盘。以下脚本将帮助你根据你的要求创建一个棋盘。该图像将显示两秒钟,然后保存到当前文件夹中。

import os
import numpy as np
import cv2

# ------------------------------
# ENTER YOUR PARAMETERS HERE:
ARUCO_DICT = cv2.aruco.DICT_6X6_250
SQUARES_VERTICALLY = 7
SQUARES_HORIZONTALLY = 5
SQUARE_LENGTH = 0.03
MARKER_LENGTH = 0.015
LENGTH_PX = 640   # total length of the page in pixels
MARGIN_PX = 20    # size of the margin in pixels
SAVE_NAME = 'ChArUco_Marker.png'
# ------------------------------

def create_and_save_new_board():
    dictionary = cv2.aruco.getPredefinedDictionary(ARUCO_DICT)
    board = cv2.aruco.CharucoBoard((SQUARES_VERTICALLY, SQUARES_HORIZONTALLY), SQUARE_LENGTH, MARKER_LENGTH, dictionary)
    size_ratio = SQUARES_HORIZONTALLY / SQUARES_VERTICALLY
    img = cv2.aruco.CharucoBoard.generateImage(board, (LENGTH_PX, int(LENGTH_PX*size_ratio)), marginSize=MARGIN_PX)
    cv2.imshow("img", img)
    cv2.waitKey(2000)
    cv2.imwrite(SAVE_NAME, img)

create_and_save_new_board()

我们可以指定棋盘中有多少行和列,以及方块和标记的大小(文档指定它应该以米为单位,尽管在这个示例中,我们实际上只关心两者的比例,因为我们指定了图像的大小)。在上面的函数中:

  • Dictionary表示所使用的 Aruco 标记的字典,
  • Board是 Charuco 对象,
  • img是棋盘的绘图(cv Image 对象)。

棋盘由上面的函数组成。

使用棋盘进行相机校准

对于此步骤,你将打印出刚刚保存的图像,并为其拍照(可能附着在某个表面上)。对于相机校准,你需要从多个角度获得至少 10 张不同的棋盘图像。这是因为校准是为了校正径向和切向镜头畸变,并将我们的图像转换为“理想针孔”相机模型。它通过解决一个优化问题来最小化图像点与预期世界点之间的最小二乘投影误差,因此更多的图像意味着更多的数据点用于优化。如果你将所有图像都放在一个文件夹中,你可以尝试使用下面的脚本对图像运行校准并保存结果:
# ------------------------------
# ENTER YOUR REQUIREMENTS HERE:
ARUCO_DICT = cv2.aruco.DICT_6X6_250
SQUARES_VERTICALLY = 7
SQUARES_HORIZONTALLY = 5
SQUARE_LENGTH = 0.03
MARKER_LENGTH = 0.015
# ...
PATH_TO_YOUR_IMAGES = '/Users/Ed/Downloads/Calibration_Images'
# ------------------------------

def calibrate_and_save_parameters():
    # Define the aruco dictionary and charuco board
    dictionary = cv2.aruco.getPredefinedDictionary(ARUCO_DICT)
    board = cv2.aruco.CharucoBoard((SQUARES_VERTICALLY, SQUARES_HORIZONTALLY), SQUARE_LENGTH, MARKER_LENGTH, dictionary)
    params = cv2.aruco.DetectorParameters()

    # Load PNG images from folder
    image_files = [os.path.join(PATH_TO_YOUR_IMAGES, f) for f in os.listdir(PATH_TO_YOUR_IMAGES) if f.endswith(".png")]
    image_files.sort()  # Ensure files are in order

    all_charuco_corners = []
    all_charuco_ids = []

    for image_file in image_files:
        image = cv2.imread(image_file)
        image_copy = image.copy()
        marker_corners, marker_ids, _ = cv2.aruco.detectMarkers(image, dictionary, parameters=params)

        # If at least one marker is detected
        if len(marker_ids) > 0:
            cv2.aruco.drawDetectedMarkers(image_copy, marker_corners, marker_ids)
            charuco_retval, charuco_corners, charuco_ids = cv2.aruco.interpolateCornersCharuco(marker_corners, marker_ids, image, board)
            if charuco_retval:
                all_charuco_corners.append(charuco_corners)
                all_charuco_ids.append(charuco_ids)

    # Calibrate camera
    retval, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.aruco.calibrateCameraCharuco(all_charuco_corners, all_charuco_ids, board, image.shape[:2], NoneNone)

    # Save calibration data
    np.save('camera_matrix.npy', camera_matrix)
    np.save('dist_coeffs.npy', dist_coeffs)

    # Iterate through displaying all the images
    for image_file in image_files:
        image = cv2.imread(image_file)
        undistorted_image = cv2.undistort(image, camera_matrix, dist_coeffs)
        cv2.imshow('Undistorted Image', undistorted_image)
        cv2.waitKey(0)

    cv2.destroyAllWindows()

calibrate_and_save_parameters()

这个函数的主要作用是相机校准,下面是函数的主要步骤:

  • 首先,你还是需要创建你的字典(用于 Aruco 标记)和棋盘(理想棋盘),同时创建一个 params 对象,它可以用于修改检测标记的方式(例如,你可以修改阈值处理等参数)。
  • 对于每张图像,检测标记,根据预期的标记和搜索参数来进行检测。这一步是为了找到图像中的Aruco标记。
  • 关键的步骤是 cv2.aruco.interpolateCornersCharuco,它用于找到棋盘格的角点,并将相应的Aruco标识与这些角点进行匹配。这一步是为了将图像中检测到的Aruco标记与棋盘格的角点进行关联。
  • 一旦找到并识别了角点,这些角点将被收集并加载到一个函数中,用于后续的校准过程。

你可以看到图像的角现在已经扭曲,以解决镜头畸变的问题。

使用棋盘获取姿态信息

这是Charuco棋盘的一个非常有用的部分 — 我们可以充分利用棋盘的校准能力和Aruco标记的姿态估计能力。当然,对于姿态估计,我们只需要一张图像(或者如果源是视频,则是一帧图像)。下面的detect_pose函数接受一张单独的图像,但我已经添加了一个主函数,用于迭代所有我们的校准图像。希望这段代码相对容易理解。
def detect_pose(image, camera_matrix, dist_coeffs):
    # Undistort the image
    undistorted_image = cv2.undistort(image, camera_matrix, dist_coeffs)

    # Define the aruco dictionary and charuco board
    dictionary = cv2.aruco.getPredefinedDictionary(ARUCO_DICT)
    board = cv2.aruco.CharucoBoard((SQUARES_VERTICALLY, SQUARES_HORIZONTALLY), SQUARE_LENGTH, MARKER_LENGTH, dictionary)
    params = cv2.aruco.DetectorParameters()

    # Detect markers in the undistorted image
    marker_corners, marker_ids, _ = cv2.aruco.detectMarkers(undistorted_image, dictionary, parameters=params)

    # If at least one marker is detected
    if len(marker_ids) > 0:
        # Interpolate CharUco corners
        charuco_retval, charuco_corners, charuco_ids = cv2.aruco.interpolateCornersCharuco(marker_corners, marker_ids, undistorted_image, board)

        # If enough corners are found, estimate the pose
        if charuco_retval:
            retval, rvec, tvec = cv2.aruco.estimatePoseCharucoBoard(charuco_corners, charuco_ids, board, camera_matrix, dist_coeffs, NoneNone)

            # If pose estimation is successful, draw the axis
            if retval:
                cv2.drawFrameAxes(undistorted_image, camera_matrix, dist_coeffs, rvec, tvec, length=0.1, thickness=15)
    return undistorted_image


def main():
    # Load calibration data
    camera_matrix = np.load['camera_matrix.npy']
    dist_coeffs = np.load['dist_coeffs.npy']

    # Iterate through PNG images in the folder
    image_files = [os.path.join(PATH_TO_YOUR_IMAGES, f) for f in os.listdir(PATH_TO_YOUR_IMAGES) if f.endswith(".png")]
    image_files.sort()  # Ensure files are in order

    for image_file in image_files:
        # Load an image
        image = cv2.imread(image_file)

        # Detect pose and draw axis
        pose_image = detect_pose(image, camera_matrix, dist_coeffs)

        # Show the image
        cv2.imshow('Pose Image', pose_image)
        cv2.waitKey(0)

main()

这应该允许你获取棋盘的位姿(你可以使用它来定位机器人、检测平面坐标系等)。

坐标系相对于原始图像的“左上角”。



☆ END ☆如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文扫描二维码添加小编↓

相关推荐

  • 在游戏开发中,如何平衡 GPT-3.5 和 GPT-4 的成本与性能?
  • 突发!OpenAI“宫斗”再反转:Altman不回归,确定加入微软
  • 又是一笔“5000亿”,高瓴再次出手
  • 突发,OpenAI政变再反转!阿尔特曼逼宫失败,董事会又换了新CEO
  • 美团到家面试,过了!
  • JavaScript 全栈解决方案比较:Angular、React、Vue.js 的对比
  • OpenAI “政变”48小时后:Altman 夺回主导权、回归在即!微软入局、董事会或重组
  • 用一款数据库解决 80%的问题,OceanBase 的底气在哪?
  • 是全部重做还是融合改造?揭秘京东云言犀升级全过程
  • 做了个小程序逆向的私活,赚了!
  • 参数量13B模型全方位碾压GPT-4?这背后有什么猫腻
  • ​EMNLP 2023 findings | 生成式框架下解决输入扰动槽填充任务
  • 大模型时代下的推荐系统变革
  • 报名 | 美团技术沙龙第79期:美团搜索/推荐/广告稀疏模型的端到端实践
  • 火山引擎 DataLeap 计算治理自动化解决方案实践和思考
  • 真正实现一步文生图,谷歌UFOGen极速采样,生成高质量图像
  • 谷歌Bard「破防」,用自然语言破解,提示注入引起数据泄漏风险
  • GPT-4V在自动驾驶上应用前景如何?面向真实场景的全面测评来了
  • OpenAI安全系统负责人长文梳理:大模型的对抗攻击与防御
  • 被开48小时后,「访客」Sam Altman重返OpenAI总部,马斯克呼吁披露真相