ArUco标记

首先什么是aruco标记呢?

aruco标记是可用于摄像机姿态估计的二进制方形基准标记。它的主要优点是检测简单、快速,并且具有很强的鲁棒性。ArUco 标记是由宽黑色边框和确定其标识符(id)的内部二进制矩阵组成的正方形标记。aruco标记的黑色边框有助于其在图像中的快速检测,内部二进制编码用于识别标记和提供错误检测和纠正。aruco标记尺寸的大小决定内部矩阵的大小,例如尺寸为 4x4 的标记由 16 位二进制数组成。

通俗地说,aruco标记其实就是一种编码,就和我们日常生活中的二维码是相似的,只不过由于编码方式的不同,导致它们存储信息的方式、容量等等有所差异,所以在应用层次上也会有所不同。由于单个aruco标记就可以提供足够的对应关系,例如有四个明显的角点及内部的二进制编码,所以aruco标记被广泛用来增加从二维世界映射到三维世界时的信息量,便于发现二维世界与三维世界之间的投影关系,从而实现姿态估计、相机矫正等等应用。

OpenCV中的ArUco模块包括了对aruco标记的创建和检测,以及将aruco标记用于姿势估计和相机矫正等应用的相关API,同时还提供了标记板等等。本次笔记中主要先整理aruco标记的创建与检测。

首先我们创建aruco标记时,需要先指定一个字典,这个字典表示的是创建出来的aruco标记具有怎样的尺寸、怎样的编码等等内容,我们使用APIgetPredefinedDictionary()来声明我们使用的字典。在OpenCV中,提供了多种预定义字典,我们可以通过PREDEFINED_DICTIONARY_NAME来查看有哪些预定义字典。而且字典名称表示了该字典的aruco标记数量和尺寸,例如DICT_7X7_50表示一个包含了50种7x7位标记的字典。


ArUco标记生成器

在线aruco标记生成器:http://aruco.dgut.top/

(备用):https://chev.me/arucogen/

在OpenCV中生成ArUco标记

opencv-python生成aruco标记

确定好我们需要的字典后,就可以通过APIdrawMarker()来绘制出aruco标记,其参数含义如下:

1
2
3
4
5
6
7
8
9
10
import cv2
import numpy as np
# 生成aruco标记
# 加载预定义的字典
dictionary = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250)

# 生成标记
markerImage = np.zeros((200, 200), dtype=np.uint8)
markerImage = cv2.aruco.drawMarker(dictionary, 22, 200, markerImage, 1)
cv2.imwrite("marker22.png", markerImage)

opencv的aruco模块共有25个预定义的标记词典。每个词典中所有的Aruco标记均包含相同数量的块或位(例如4×4、5×5、6×6或7×7),且每个词典中Aruco标记的数量固定(例如50、100、250或1000)。

cv2.aruco.Dictionary_get()函数会加载cv2.aruco.DICT_6X6_250包含250个标记的字典,其中每个标记都是6×6位二进制模式

cv2.aruco.drawMarker(dictionary, 22, 200, markerImage, 1)中的第二个参数22是aruco的标记id(0~249),第三个参数决定生成的标记的大小,在上面的示例中,它将生成200×200像素的图像,第四个参数表示将要存储aruco标记的对象(上面的markerImage),最后,第五个参数是边界宽度参数,它决定应将多少位(块)作为边界添加到生成的二进制图案中。

执行后将会生成这样的标记:标记id分别是22

展开所支持的标记字典

展开查看的内容;
DICT_4X4_50 
Python: cv.aruco.DICT_4X4_50
DICT_4X4_100 
Python: cv.aruco.DICT_4X4_100
DICT_4X4_250 
Python: cv.aruco.DICT_4X4_250
DICT_4X4_1000 
Python: cv.aruco.DICT_4X4_1000
DICT_5X5_50 
Python: cv.aruco.DICT_5X5_50
DICT_5X5_100 
Python: cv.aruco.DICT_5X5_100
DICT_5X5_250 
Python: cv.aruco.DICT_5X5_250
DICT_5X5_1000 
Python: cv.aruco.DICT_5X5_1000
DICT_6X6_50 
Python: cv.aruco.DICT_6X6_50
DICT_6X6_100 
Python: cv.aruco.DICT_6X6_100
DICT_6X6_250 
Python: cv.aruco.DICT_6X6_250
DICT_6X6_1000 
Python: cv.aruco.DICT_6X6_1000
DICT_7X7_50 
Python: cv.aruco.DICT_7X7_50
DICT_7X7_100 
Python: cv.aruco.DICT_7X7_100
DICT_7X7_250 
Python: cv.aruco.DICT_7X7_250
DICT_7X7_1000 
Python: cv.aruco.DICT_7X7_1000
DICT_ARUCO_ORIGINAL 
Python: cv.aruco.DICT_ARUCO_ORIGINAL
DICT_APRILTAG_16h5 
Python: cv.aruco.DICT_APRILTAG_16h5
4x4 bits, minimum hamming distance between any two codes = 5, 30 codes
-----

批量生成aruco标记

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
# 生成aruco标记
# 加载预定义的字典
dictionary = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250)

# 生成标记
markerImage = np.zeros((200, 200), dtype=np.uint8)
for i in range(30):
markerImage = cv2.aruco.drawMarker(dictionary, i, 200, markerImage, 1);

firename='armark/'+str(i)+'.png'
cv2.imwrite(firename, markerImage);

在armark文件夹下会生成一系列的6*6 aruco标记


Aruco标记的检测和定位

静态检测

在环境中图像检测Aruco标记,环境中有7个标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
import time
import cv2
import cv2.aruco as aruco
#读取图片
frame=cv2.imread('IMG_3739.jpg')
#调整图片大小
frame=cv2.resize(frame,None,fx=0.2,fy=0.2,interpolation=cv2.INTER_CUBIC)
#灰度话
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#设置预定义的字典
aruco_dict = aruco.Dictionary_get(aruco.DICT_6X6_250)
#使用默认值初始化检测器参数
parameters = aruco.DetectorParameters_create()
#使用aruco.detectMarkers()函数可以检测到marker,返回ID和标志板的4个角点坐标
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray,aruco_dict,parameters=parameters)
#画出标志位置
aruco.drawDetectedMarkers(frame, corners,ids)


cv2.imshow("frame",frame)
cv2.waitKey(0)
cv2.destroyAllWindows()

对于每次成功检测到标记,将按从左上,右上,右下和左下的顺序检测标记的四个角点。在C ++中,将这4个检测到的角点存储为点矢量,并将图像中的多个标记一起存储在点矢量容器中。在Python中,它们存储为Numpy 数组。

detectMarkers()函数用于检测和确定标记角点的位置。

  • 第一个参数image是带有标记的场景图像。
  • 第二个参数dictionary是用于生成标记的字典。成功检测到的标记将存储在markerCorners中,其ID存储在markerIds中。先前初始化的DetectorParameters对象作为传递参数。
  • 第三个参数parametersDetectionParameters 类的对象,该对象包括在检测过程中可以自定义的所有参数;
  • 返回参数corners:检测到的aruco标记的角点列表,对于每个标记,其四个角点均按其原始顺序返回(从右上角开始顺时针旋转),第一个角是右上角,然后是右下角,左下角和左上角。
  • 返回ids:检测到的每个标记的 id,需要注意的是第三个参数和第四个参数具有相同的大小;
  • 返回参数rejectedImgPoints:抛弃的候选标记列表,即检测到的、但未提供有效编码的正方形。每个候选标记也由其四个角定义,其格式与第三个参数相同,该参数若无特殊要求可以省略。
1
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray,aruco_dict,parameters=parameters)

当我们检测到aruco标签之后,为了方便观察,我们需要进行可视化操作,把标签标记出来:使用drawDetectedMarkers()这个API来绘制检测到的aruco标记,其参数含义如下:

  • 参数image: 是将绘制标记的输入 / 输出图像(通常就是检测到标记的图像)
  • 参数corners:检测到的aruco标记的角点列表
  • 参数ids:检测到的每个标记对应到其所属字典中的id,可选(如果未提供)不会绘制ID。
  • 参数borderColor:绘制标记外框的颜色,其余颜色(文本颜色和第一个角颜色)将基于该颜色进行计算,以提高可视化效果。
  • 无返回值
1
aruco.drawDetectedMarkers(image, corners,ids,borderColor)

效果演示:



动态检测

利用摄像头进行一个实时动态监测aruco标记并且估计姿势,摄像头的内参需要提前标定,如何标定请看我另一篇文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import numpy as np
import time
import cv2
import cv2.aruco as aruco



# mtx = np.array([
# [2946.48, 0, 1980.53],
# [ 0, 2945.41, 1129.25],
# [ 0, 0, 1],
# ])
# #我的手机拍棋盘的时候图片大小是 4000 x 2250
# #ip摄像头拍视频的时候设置的是 1920 x 1080,长宽比是一样的,
# #ip摄像头设置分辨率的时候注意一下
#
#
# dist = np.array( [0.226317, -1.21478, 0.00170689, -0.000334551, 1.9892] )


#相机纠正参数

# dist=np.array(([[-0.51328742, 0.33232725 , 0.01683581 ,-0.00078608, -0.1159959]]))
#
# mtx=np.array([[464.73554153, 0.00000000e+00 ,323.989155],
# [ 0., 476.72971528 ,210.92028],
# [ 0., 0., 1. ]])
dist=np.array(([[-0.58650416 , 0.59103816, -0.00443272 , 0.00357844 ,-0.27203275]]))
newcameramtx=np.array([[189.076828 , 0. , 361.20126638]
,[ 0 ,2.01627296e+04 ,4.52759577e+02]
,[0, 0, 1]])
mtx=np.array([[398.12724231 , 0. , 304.35638757],
[ 0. , 345.38259888, 282.49861858],
[ 0., 0., 1. ]])



cap = cv2.VideoCapture(0)


font = cv2.FONT_HERSHEY_SIMPLEX #font for displaying text (below)

#num = 0
while True:
ret, frame = cap.read()
h1, w1 = frame.shape[:2]
# 读取摄像头画面
# 纠正畸变
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (h1, w1), 0, (h1, w1))
dst1 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
x, y, w1, h1 = roi
dst1 = dst1[y:y + h1, x:x + w1]
frame=dst1


gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
aruco_dict = aruco.Dictionary_get(aruco.DICT_6X6_250)
parameters = aruco.DetectorParameters_create()
dst1 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
'''
detectMarkers(...)
detectMarkers(image, dictionary[, corners[, ids[, parameters[, rejectedI
mgPoints]]]]) -> corners, ids, rejectedImgPoints
'''

#使用aruco.detectMarkers()函数可以检测到marker,返回ID和标志板的4个角点坐标
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray,aruco_dict,parameters=parameters)

# 如果找不打id
if ids is not None:

rvec, tvec, _ = aruco.estimatePoseSingleMarkers(corners, 0.05, mtx, dist)
# 估计每个标记的姿态并返回值rvet和tvec ---不同
# from camera coeficcients
(rvec-tvec).any() # get rid of that nasty numpy value array error

# aruco.drawAxis(frame, mtx, dist, rvec, tvec, 0.1) #绘制轴
# aruco.drawDetectedMarkers(frame, corners) #在标记周围画一个正方形

for i in range(rvec.shape[0]):
aruco.drawAxis(frame, mtx, dist, rvec[i, :, :], tvec[i, :, :], 0.03)
aruco.drawDetectedMarkers(frame, corners)
###### DRAW ID #####
cv2.putText(frame, "Id: " + str(ids), (0,64), font, 1, (0,255,0),2,cv2.LINE_AA)


else:
##### DRAW "NO IDS" #####
cv2.putText(frame, "No Ids", (0,64), font, 1, (0,255,0),2,cv2.LINE_AA)


# 显示结果框架
cv2.imshow("frame",frame)

key = cv2.waitKey(1)

if key == 27: # 按esc键退出
print('esc break...')
cap.release()
cv2.destroyAllWindows()
break

if key == ord(' '): # 按空格键保存
# num = num + 1
# filename = "frames_%s.jpg" % num # 保存一张图像
filename = str(time.time())[:10] + ".jpg"
cv2.imwrite(filename, frame)

效果

附件

相机标定,并且写入文件保存标定文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import cv2
import numpy as np
import glob
import matplotlib.pyplot as plt
import matplotlib.patches as patches


# 找棋盘格角点标定并且写入文件

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # 阈值
#棋盘格模板规格
w = 9 # 10 - 1
h = 6 # 7 - 1
# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((w*h,3), np.float32)
objp[:,:2] = np.mgrid[0:w,0:h].T.reshape(-1,2)
objp = objp*18.1 # 18.1 mm

# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点

images = glob.glob('./pic/*.jpg') # 拍摄的十几张棋盘图片所在目录

i = 1
for fname in images:

img = cv2.imread(fname)
# 获取画面中心点

h1, w1 = img.shape[0], img.shape[1]
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
u, v = img.shape[:2]
# 找到棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, (w,h),None)
# 如果找到足够点对,将其存储起来
if ret == True:
print("i:", i)
i = i+1

cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
objpoints.append(objp)
imgpoints.append(corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w,h), corners, ret)
cv2.namedWindow('findCorners', cv2.WINDOW_NORMAL)
cv2.resizeWindow('findCorners', 640, 480)
cv2.imshow('findCorners',img)
cv2.waitKey(200)
cv2.destroyAllWindows()
#%% 标定
print('正在计算')
ret, mtx, dist, rvecs, tvecs = \
cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
cv_file=cv2.FileStorage("camera.yaml",cv2.FILE_STORAGE_WRITE)
cv_file.write("camera_matrix",mtx)
cv_file.write("dist_coeff",dist)
# 请注意,*释放*不会关闭()FileStorage对象

cv_file.release()

print("ret:",ret )
print("mtx:\n",mtx) # 内参数矩阵
print("dist畸变值:\n",dist ) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print("rvecs旋转(向量)外参:\n",rvecs) # 旋转向量 # 外参数
print("tvecs平移(向量)外参:\n",tvecs ) # 平移向量 # 外参数
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (u, v), 0, (u, v))
print('newcameramtx外参',newcameramtx)
camera=cv2.VideoCapture(0)

# dist=np.array(([[-0.3918239532375715, 0.1553689004591761, 0.001069066277469635, 2.175204930902934e-06, -0.02850420360197434]]))
# # newcameramtx=np.array([[1.85389837e+04 ,0.00000000e+00, 5.48743017e+02]
# # ,[ 0 ,2.01627296e+04 ,4.52759577e+02]
# # ,[0, 0, 1]])
# mtx=np.array([[379.1368428730273, 0, 312.1210537268028],
# [ 0, 381.6396537294123, 242.492484246843],
# [ 0., 0., 1. ]])



while True:
(grabbed,frame)=camera.read()
h1, w1 = frame.shape[:2]
#打开标定文件
cv_file = cv2.FileStorage("camera.yaml", cv2.FILE_STORAGE_READ)
camera_matrix = cv_file.getNode("camera_matrix").mat()
dist_matrix = cv_file.getNode("dist_coeff").mat()
cv_file.release()

newcameramtx, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_matrix, (u, v), 0, (u, v))
# 纠正畸变
dst1 = cv2.undistort(frame, camera_matrix, dist_matrix, None, newcameramtx)
#dst2 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
mapx,mapy=cv2.initUndistortRectifyMap(camera_matrix,dist_matrix,None,newcameramtx,(w1,h1),5)
dst2=cv2.remap(frame,mapx,mapy,cv2.INTER_LINEAR)


# 裁剪图像,输出纠正畸变以后的图片
x, y, w1, h1 = roi
dst1 = dst1[y:y + h1, x:x + w1]


cv2.imshow('dst1',dst1)
#cv2.imshow('dst2', dst2)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按q保存一张图片
cv2.imwrite("../u4/frame.jpg", dst1)
break

camera.release()
cv2.destroyAllWindows()

利用标定文件检测aruco标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import numpy as np
import time
import cv2
import cv2.aruco as aruco


#加载相机纠正参数
cv_file = cv2.FileStorage("yuyan.yaml", cv2.FILE_STORAGE_READ)
camera_matrix = cv_file.getNode("camera_matrix").mat()
dist_matrix = cv_file.getNode("dist_coeff").mat()
cv_file.release()

# dist=np.array(([[-0.51328742, 0.33232725 , 0.01683581 ,-0.00078608, -0.1159959]]))
#
# mtx=np.array([[464.73554153, 0.00000000e+00 ,323.989155],
# [ 0., 476.72971528 ,210.92028],
# [ 0., 0., 1. ]])

# dist=np.array(([[-0.58650416 , 0.59103816, -0.00443272 , 0.00357844 ,-0.27203275]]))
# newcameramtx=np.array([[189.076828 , 0. , 361.20126638]
# ,[ 0 ,2.01627296e+04 ,4.52759577e+02]
# ,[0, 0, 1]])
# mtx=np.array([[398.12724231 , 0. , 304.35638757],
# [ 0. , 345.38259888, 282.49861858],
# [ 0., 0., 1. ]])



cap = cv2.VideoCapture(0)


font = cv2.FONT_HERSHEY_SIMPLEX #font for displaying text (below)

#num = 0
while True:
ret, frame = cap.read()
h1, w1 = frame.shape[:2]
# 读取摄像头画面
# 纠正畸变
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_matrix, (h1, w1), 0, (h1, w1))
dst1 = cv2.undistort(frame, camera_matrix, dist_matrix, None, newcameramtx)
x, y, w1, h1 = roi
dst1 = dst1[y:y + h1, x:x + w1]
frame=dst1


gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
aruco_dict = aruco.Dictionary_get(aruco.DICT_6X6_250)
parameters = aruco.DetectorParameters_create()
#dst1 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
'''
detectMarkers(...)
detectMarkers(image, dictionary[, corners[, ids[, parameters[, rejectedI
mgPoints]]]]) -> corners, ids, rejectedImgPoints
'''

#使用aruco.detectMarkers()函数可以检测到marker,返回ID和标志板的4个角点坐标
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray,aruco_dict,parameters=parameters)

# 如果找不打id
if ids is not None:

rvec, tvec, _ = aruco.estimatePoseSingleMarkers(corners, 0.05, camera_matrix, dist_matrix)
# 估计每个标记的姿态并返回值rvet和tvec ---不同
# from camera coeficcients
(rvec-tvec).any() # get rid of that nasty numpy value array error

# aruco.drawAxis(frame, mtx, dist, rvec, tvec, 0.1) #绘制轴
# aruco.drawDetectedMarkers(frame, corners) #在标记周围画一个正方形

for i in range(rvec.shape[0]):
aruco.drawAxis(frame, camera_matrix, dist_matrix, rvec[i, :, :], tvec[i, :, :], 0.03)
aruco.drawDetectedMarkers(frame, corners,ids)
###### DRAW ID #####
cv2.putText(frame, "Id: " + str(ids), (0,64), font, 1, (0,255,0),2,cv2.LINE_AA)


else:
##### DRAW "NO IDS" #####
cv2.putText(frame, "No Ids", (0,64), font, 1, (0,255,0),2,cv2.LINE_AA)


# 显示结果框架
cv2.imshow("frame",frame)

key = cv2.waitKey(1)

if key == 27: # 按esc键退出
print('esc break...')
cap.release()
cv2.destroyAllWindows()
break

if key == ord(' '): # 按空格键保存
# num = num + 1
# filename = "frames_%s.jpg" % num # 保存一张图像
filename = str(time.time())[:10] + ".jpg"
cv2.imwrite(filename, frame)

其中yuyan.yaml为保存的标定文件,利用cv2.FileStorage(“yuyan.yaml”, cv2.FILE_STORAGE_READ)及cv_file.getNode(“camera_matrix”).mat()加载

本文参考:

1.https://blog.csdn.net/sinat_17456165/article/details/105649131

2.https://www.learnopencv.com/augmented-reality-using-aruco-markers-in-opencv-c-python/