如果你之前使用过OpenCV,你可能熟悉我们用于校准相机的经典棋盘格。你可能也熟悉Aruco标记,它们用于机器人应用中的姿态估计。这些标记带有唯一标识符,类似于棋盘格,我们可以使用其角点来确定其坐标框架。Charuco棋盘是将它们结合到一个棋盘上的有用方式,如图所示。这使我们能够唯一地识别棋盘的每个角。识别唯一的角点有两个原因:TLDR:目前在线的许多 Charuco 示例代码都已过时,并且会让你出错。如果 ChatGPT 或 Stack Exchange 建议你使用:
cv2.aruco.CharucoBoard_create(length, width, ...)
你会发现正确的语法是cv2.aruco.CharucoBoard((length, width), ...)
。希望下面的示例函数能有所帮助。
pip install opencv-contrib-python
使用“contrib”版本来访问该库非常重要,并确保不要安装无界面版本(它会阻止 GUI 出现)。如果你在尝试运行任何 Aruco 或 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 对象)。棋盘由上面的函数组成。
# ------------------------------
# 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()
这个函数的主要作用是相机校准,下面是函数的主要步骤:
你可以看到图像的角现在已经扭曲,以解决镜头畸变的问题。
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()
这应该允许你获取棋盘的位姿(你可以使用它来定位机器人、检测平面坐标系等)。
坐标系相对于原始图像的“左上角”。