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], None, None)
# 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, None, None)
# 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()
这应该允许你获取棋盘的位姿(你可以使用它来定位机器人、检测平面坐标系等)。
坐标系相对于原始图像的“左上角”。
|