基于opencv-python的相机标定

1.简述

利用aruco进行动态检测时,需要先矫正摄像机带来的图形畸变。为了找到这些纠正参数,我们必须要提供一些包含明显图案模式的样本图片(比如说棋盘)。我们可以在上面找到一些特殊点(如棋盘的四个角点)。我们找到这些特殊点在图片中的位置以及它们的真实位置。有了这些信息,我们就可以使用数学方法求解畸变系数。

2.准备:

将棋盘图像固定到一个平面上,使用相机从不同角度,不同位置拍摄10-20张标定图。

''' python 调用opencv相机拍照代码'''
import cv2
camera=cv2.VideoCapture(0)
i = 0
while 1:
    (grabbed, img) = camera.read()
    cv2.imshow('img',img)
    if cv2.waitKey(1) & 0xFF == ord('j'):  # 按j保存一张图片
        i += 1
        u = str(i)
        firename=str('./img'+u+'.jpg')
        cv2.imwrite(firename, img)
        print('写入:',firename)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

3. 寻找棋盘并且标定

3.1 利用opencv寻找棋盘

  1. api
  • 使用函数 cv2.findChessboardCorners(),找到棋盘图案。我们还需要传入图案的类型,比如说 8x8 的格子或 5x5 的格子等。它会返回角点,如果得到图像的话返回值类型(Retval)就会是 True。这些角点会按顺序排列(从左到右,从上到下)

注:棋盘的w,和h,是在图中棋盘行和列的小格数减去一得到的结果

  • cv2.cornerSubPix():增强检测到角点的准确度(在原角点的基础上寻找亚像素点)

1.第一个参数是输入图像,
2.第二个参数是 cv2.findChessboardCorners() 检测到的角点,即是输入也是输出。(该函数无返回值,会直接在上一步的输出结果 corners 上进行修改)
3.第三个参数是计算亚像素角点时考虑的区域的大小,大小为NXN; N=(winSize*2+1)。
4.第四个参数作用类似于winSize,但是总是具有较小的范围,通常忽略即 (-1, -1)
5.第五个参数用于表示计算亚像素时停止迭代的标准,可选的值有cv2.TermCriteria_MAX_ITER 、cv.TermCriteria_EPS(可以是两者其一,或两者均选),前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。具体形式见代码。
————————————————
原文链接:https://blog.csdn.net/qq_28584889/article/details/103356855

  • cv2.drawChessboardCorners():绘制图案
  1. 代码:
# 检测棋盘格角点
ret , coners = cv2.findChessboardCorners(gray,(w,h),None)
'''[output]:
True
[[[213.42604 143.87164]]
 [[234.48198 142.49297]]
...
 [[394.42282 302.6433 ]]
 [[416.57324 301.3984 ]]
 [[437.30234 299.3177 ]]]'''

# 寻找亚像素点
# critria 为寻找亚像素点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)

# 将角点在图像上表示出来
#(w,h)
cv2.drawChessboardCorners(img, (11,8),corners,True)
cv2.namedWindow('findCorners',cv2.WINDOW_NORMAL)
cv2.resizeWindow('findCorners',640,480)
cv2.imshow('findCorners',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

3.2 标定

再得到对象点和图像点之后,就可以进行摄像机标定。

  1. api:
  • cv2.calibrateCamera(): 返回值为摄像机矩阵,畸变系数,旋转和变换向量等。
  1. 代码:
# 创建世界坐标系中的棋盘格点,使z坐标为0
w = 11 
h = 8
objp = np.zeros((w*h,3),np.float32)
objp[:,:2] = np.mgrid[0:w,0:h].T.reshape(-1,2)
# 世界坐标要乘上棋盘每个小格子的尺寸 例如本例采用20mm
objp = objp*20
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
objpoints.append(objp)
imgpoints.append(corners)
#### 计算标定
ret, mtx ,dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
u, v = img.shape[:2]
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)
'''[output]:
ret: 0.30404563059120104
mtx:
 [[1.14680270e+03 0.00000000e+00 3.04927127e+02]
 [0.00000000e+00 1.14497089e+03 2.18227227e+02]
 [0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dist畸变值:
 [[-5.90292637e+00  1.28873057e+02  2.09194579e-02  5.44015360e-02
  -2.83669301e+03]]
rvecs旋转(向量)外参:
 [array([[-0.06720353],
       [ 0.0355854 ],
       [ 0.00079517]])]
tvecs平移(向量)外参:
 [array([[-563.20786475],
       [-456.5025334 ],
       [6513.25636228]])]
newcameramtx外参 [[1.14441345e+03 0.00000000e+00 3.04291847e+02]
 [0.00000000e+00 1.14318188e+03 2.17886252e+02]
 [0.00000000e+00 0.00000000e+00 1.00000000e+00]]
'''

3.3 相机矩阵优化

得到相机的相关参数后,就可以对一幅图像进行矫正了。不过在那之前我们可以使用从函数 cv2.getOptimalNewCameraMatrix() 得到的自由缩放系数对摄像机矩阵进行优化。如果缩放系数 alpha = 0,返回的非畸变图像会带有最少量的不想要的像素。它甚至有可能在图像角点去除一些像素。如果 alpha = 1,所有的像素都会被返回,还有一些黑图像。它还会返回一个 ROI 图像,我们可以用来对结果进行裁剪。

  1. api:函数:cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),0,(w,h))
  • 第一个参数为:内参数矩阵
  • 第二个参数为:畸变系数
  • 注意: 第四个参数要设置为0
  1. 代码:
# 返回一个优化后的内参数矩阵
# (u,v) 为初始棋盘图像的img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (u, v), 0, (u, v))

3.4 畸变到非畸变

opencv中提供了两种畸变矫正的方法,以下将依次叙述:

  1. cv2.undistort(): 使用该函数和上边得到的ROI对结果进行裁剪。
  2. 代码:
 # 纠正畸变
 # frame 需要处理的图像
    dst1 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
    x,y,w1,h1 = roi
    dst1 = dst1[y:y+h1,x:x+w1]

3.5 反向投影误差

  • 我们可以利用反向投影误差对我们找到的参数的准确性进行估计。得到的结果越接近 0 越好。有了内部参数,畸变参数和旋转变换矩阵,我们就可以使用 cv2.projectPoints() 将对象点转换到图像点。然后就可以计算变换得到图像与角点检测算法的绝对差了。然后我们计算所有标定图像的误差平均值。(但是本文不需要,所以没有将其写入)

4. 主要代码

import cv2
import numpy as np
import glob

# 找棋盘格角点
# 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
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 = [] # 在图像平面的二维点
#加载pic文件夹下所有的jpg图像
images = glob.glob('./*.jpg')  #   拍摄的十几张棋盘图片所在目录

i=0
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)


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)
while True:
    (grabbed,frame)=camera.read()
    h1, w1 = frame.shape[:2]
    newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (u, v), 0, (u, v))
    # 纠正畸变
    dst1 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
    #dst2 = cv2.undistort(frame, mtx, dist, None, newcameramtx)
    mapx,mapy=cv2.initUndistortRectifyMap(mtx,dist,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('frame',dst2)
    #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()

声明:内容大部分来自 https://blog.dgut.top/2020/07/20/opencv-biaoding/,本文是对其中知识的一些扩展总结和重新整理。

你可能感兴趣的:(opencv,计算机视觉)