样本采集
我使用的棋盘格标定板是自己用Matlab制作的,当时制作的程序找不到了,可以自己写一个,标定板图片长这个样子。
为了保证求解到的相机内参的精度,必须要保证有一定的样本数量,照片最好是10张以上。在采集中本文设置的使用方法是每在键盘上按以下字母‘r’便会采集到一张图片并自动命名,按字母‘q’退出采集。程序如下:
cap = cv2.VideoCapture(0)#此例为获取电脑摄像头的图像
while not cap.isOpened(): # 检查摄像头是否打开成功
time.sleep(50)
print('相机打开成功')
width = int(cap.get(3)) # 读取摄像头分辨率参数
height = int(cap.get(4))
frame = np.zeros((width,height,3),dtype=np.uint8) # 创建图像模板
ret, frame = cap.read()
Key_val = 0 # 保存键值
def Keybo_Moni(): # 按键测试函数
count = 0
while True:
global Key_val, frame, process_flag, cap
if Key_val == ord('r'):#键盘字母r为获取图像的一帧
Key_val= 0
cv2.imwrite('test' + str(count) + '.jpg', frame) # 保存图像
count += 1
print('Get new pic %d' % count)
try:
Keybo_Moni_Thread = threading.Thread(target=Keybo_Moni, name='Keyboard-Thread') # 创建键盘监控线程
Keybo_Moni_Thread.start() # 启动键盘监测线程
except:
print('Error:无法启动键盘!')
while True:
ret, frame = cap.read() # 读取视频帧
cv2.imshow('Video_Show', frame) # 显示图像
Key_val = cv2.waitKey(1) # 获取键值
if Key_val == ord('q'):
cv2.destroyAllWindows()
cap.release()
print('采样结束!')
break
cv2.destroyAllWindows()
cap.release()
求解相机内参
对采集到的样本图片进行一次的读取,并将棋盘格的坐标信息存储在变量imgpoints中,然后用calibrateamera函数进行求解相机内参。
需要解释的几点:
- 在制作棋盘格标定板的时候是有精度要求的,因为在整个求解过程中,物理坐标是通过棋盘格定义的,棋盘格的实际尺寸的精度一定程度上决定了整个参数求解的精度。
- 世界坐标系固定在棋盘格上,以棋盘格左上角的第一个内角点为(0,0),由于我设置的棋盘格的实际边长为3厘米,故往水平方向为(0,3),(0,6)……竖直方向也是一样的操作。注意:一定是内角点,因为寻找棋盘格角点的函数findChessboardCorners,只能找到四周都有其他连接点的角点,所以四周的外角点是找不到的。
- 像素坐标系和图像坐标系同上一篇文章张正友相机标定原理(一)所讲的一样,处于同一平面是那个,但原点不同,位于相机的成像平面。
- 相机坐标系就是以相机光心为原点建立的坐标系。
- 求解相机的内参就是将这个集相机的位置关系转换成数学关系进行求解。
代码如下
images = glob.glob('*.jpg')#读取文件夹中.jpg格式的文件
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (6,4), flags=3 ) # 寻找棋盘格角点
objpoints = [] # 存储世界坐标系中的3D点(实际上Zw在标定板上的值为0)
imgpoints = [] # 存储图像坐标系中的2D点
if ret == True:
objp = np.zeros((6*4,3),np.float32)# 设置标定板角点的规格
objp[:,:2] = np.mgrid[0:18:3,0:12:3].T.reshape(-1,2)# 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y,这里的间隔需与实际的标定板的单个棋盘格的尺寸保持一致。
objpoints.append(objp)
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)# 亚像素级焦点检测,基于提取的角点,进一步提高精确度
imgpoints.append(corners2)
img = cv2.drawChessboardCorners(img,(6,4),corners2,ret)#在图片中绘制棋盘格角点
cv2.imshow('img',img)
cv2.waitKey(50)
cv2.destroyAllWindows()
ret1, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
#删除样本采集时的获取的图像,主要是测试程序时方便,不会导致数据量过大
images = glob.glob('*.jpg')
for fname in images:
os.remove(fname)
重要函数解释:
寻找棋盘格角点的函数
ret,corners = (cv2.findChessboard(gray,(6,4),flags=3)
参数:
image:即上函数中的gray,8位灰度或者彩色图像;
patternSize:即上函数中的(6,4),棋盘格标定板的尺寸,注意,其为内角点的个数,如田字只有一个内角点;
flags:迭代准则
1.CALIB_CB_ADAPTIVE_THRESH :使用自适应阈值将灰度图像转化为二值图像,而不是固定的由图像的平均亮度计算出来的阈值
2.CALIB_CB_NORMALIZE_IMAGE :在利用固定阈值或者自适应的阈值进行二值化之前,先使用equalizeHist来均衡化图像gamma值。
3.CALIB_CB_FILTER_QUADS :使用其他的准则(如轮廓面积,周长,类似方形的形状)来去除在轮廓检测阶段检测到的错误方块。
4.CALIB_CB_FAST_CHECK 在图像上快速检测一下棋盘格角点,如果没有棋盘格焦点被发现,绕过其它费时的函数调用,这可以在图像没有棋盘格被观测以及恶劣情况下缩短整个函数执行时间。
返回值:
ret:是否检测出角点;
corners:角点的位置,即像素坐标。
寻找棋盘格角点的原理可参考此链接中的文章opencv棋盘格角点检测原理。
#亚像素提取
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_MAX_ITER,30,0.001)#设置停止寻找亚像素角点的参数,该例中的停止准则是最大循环次数30次或者最大误差达到0.001
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
参数:
image:输入图像,即上式中的gray,最好是8位灰度图,检测效率高;
corners:角点初始坐标,即在上个函数中找到的角点的坐标信息;
winsize:搜索窗口为 2*winsize+1;
zerozone:死区,不计算区域,避免自相关矩阵的奇异性。没有死区,参数为(-1,-1);
criteria:求角点的迭代终止条件。
返回值:
corner:角点位置。
寻找亚像素角点的原理(该部分为网上的资源,找不到原链接了,谢谢原作者给我们提供了这么好的学习资料,若有冒犯,我可以删掉这篇文章,再次感谢!):
#求解相机参数
ret,mtx,dist,rvecs,tvecs = cv2.cablibrateCamera(objpoints,imgpoints,gray,shape[::-1],None,None)
参数:
objectPoints:世界坐标系里的位置;
imagePoints: 像素坐标;
imageSize():图像的大小,即shape[::-1];
flags:标定采用的算法;
criteria:迭代终止条件设定。
返回值:
mtx:相机内参
dist:畸变参数
rvecs:旋转向量;
tvecs:位移向量;
ret:是否求解到相机的参数。
相机求解过程推导过程: