任务描述
本关任务:理解相机坐标转换并使用 OpenCV 识别出图像角点,记录世界物体坐标和图像坐标。
相关知识
为了完成本关任务,你需要掌握:
- 理解相机标定的基本概念;
- 理解相机标定的坐标系转换;
- 使用 OpenCV 识别出图像角点,记录世界物体坐标和图像坐标。
相机标定
相机标定是我们相机拍摄的物体都处于三维世界坐标系中,而相机拍摄成像时把三维相机坐标系向二维图像坐标系转换。不同镜头成像时的转换矩阵不同可能引入失真,标定的作用是近似地估算出转换矩阵和失真系数。为了估算,需要知道若干点的三维世界坐标系中的坐标和二维图像坐标系中的坐标。
传统的照相机标定方法是通过世界坐标集(Xi,Yi,Zi),以及它们在图像平面上的投影坐标集(ui,vi),计算相机投影矩阵 M 中的 11 个未知参数(内参有五个,分别是:摄像头拍摄到的物体和实际物体在 x,y 轴上的映射关系(两个参数);摄像头中心和图像中心的偏移关系(两个参数);摄像头和镜头安装非完全垂直,存在一个角度的偏差(一个参数)。外参有六个,分别是 x,y,z 方向上的平移和旋转),需要三个两两互相垂直的平面来做标定(条件较为严格,一般情况难以实现)。
而棋盘标定只需要两个平面,只需要黑白格子相交的角点来标记,会比原始标定容易许多。总而言之,棋盘标定的意义就在于克服了传统标定法需要的高精度标定物的缺点,而仅需使用一个打印出来的棋盘格就可以。
相机标定的坐标系
世界坐标系 (world coordinate system) :用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而被引入,单位为 m 。
相机坐标系 (camera coordinate system) :在相机上建立的坐标系,为了从相机的角度描述物体位置而定义,作为沟通世界坐标系和图像/像素坐标系的中间一环,单位为 m。
图像坐标系 (image coordinate system) :为了描述成像过程中,物体从相机坐标系到图像坐标系的投影透射关系而引入,方便进一步得到像素坐标系下的坐标, 单位为 m。
像素坐标系 (pixel coordinate system) :为了描述物体成像后的像点在数字图像上(相片)的坐标而引入,是我们真正从相机内读取到的信息所在的坐标系,单位为个(像素数目)。
坐标系变换
在视觉测量中,需要进行的一个重要预备工作是定义四个坐标系的意义,即摄像机坐标系 、 图像物理坐标系、图像像素坐标系和世界坐标系(参考坐标系)。
一、图像坐标系 (x,y) 至像素坐标系(u,v)
图 1 图像坐标系 (x,y) 至像素坐标系(u,v)
如图 1 所示,u0,v0 是图像平面中心,将该坐标系o−xy下的p(x,y)转换为Ouv−uv坐标系下新的坐标(u,v)。此时坐标系没有旋转变换,但是坐标原点位置不一致,大小不一致,则设计伸缩变换及平移变换。
其中dx,dy 表示感光芯片上像素的实际大小,得出这个公式后,我们可以运用线性代数的知识把方程用矩阵形式表示:
其中:
二、相机坐标系 (Xc,Yc,Zc) 至图像坐标系 (x,y)
图 2 相机坐标系 (Xc,Yc,Zc) 至图像坐标系 (x,y)
摄像机坐标系为O−XcYcZc,图像坐标系为o−xy。假设P在相机坐标系下的坐标为[Xc,Yc,Zc],p 为[x,y],焦距为f。根据小孔成像原理,图像坐标系应在相机坐标系的另一边,为倒立反向成像,但为方便理解和计算,故投影至同侧。根据三角形相似性原理,在相机模型中我们可以得到以下公式:
三、世界坐标系 (Xw,Yw,Zw) 至相机坐标系 (Xc,Yc,Zc)
图 3 世界坐标系 (Xw,Yw,Zw) 至相机坐标系 (Xc,Yc,Zc)
这两个坐标系之间的关系,我们可以旋转矩阵 R 和平移矩阵 T 来得到以下关系:
四、合并公式
图 4 各个坐标系之间的变换
可以将以上公式综合一下就可以得到:
其中,Zc叫做尺度因子,矩阵
被称为相机的内参矩阵,矩阵
被称为相机的透视摄影矩阵,即外参矩阵。更多的理论推导部分请参考 相机模型公式推导。
角点检测
张正友的相机标定中,世界坐标的原点和 xy 平面是在标定板上的,z 轴垂直于标定板,所以可以自己设置世界坐标,随便在标定板上选定一个参考点作为原点,设置互相垂直的 x 轴和 y 轴,就可以得到标定板上特征点的世界坐标了。
FindChessboardCorners()
是 OpenCV 的一个函数,可以用来寻找棋盘图的内角点位置。函数形式为:
retval, corners = cv2.findChessboardCorners(image, patternSize[, corners[, flags]])
参数:
image
:棋盘图像,8 位灰度或彩色图像;patternSize
:棋盘的尺寸(注意应为内角点个数,内角点是和其他格子连着的点,不是有几个方格,比如说“田”字只有一个内角点);corners
:存放角点的位置;flags
:迭代的准则。 返回值:retvalgray
:是否检测出角点;corners
:角点的位置。
一般情况下仅使用前两个参数,其他参数设置为None
。以一个 8×8 的黑白图像gray
进行标记为例:
ret, corners = cv2.findChessboardCorners(gray,(7,7),None)
把检测的角点在原图像标记,打印显示图像,结果如图 5 所示。
图 5 角点检测
找到角点后,我们可以使用cv2.cornerSubPix()
可以得到更为准确的角点像素坐标。函数用于寻找亚像素角点位置,不是整数型位置,而是更精确的浮点型位置。相比于角点检测的位置,亚像素级的检测在点的结果上更加精确。
corners = cv2.cornerSubPix(image, corners, winSize, zeroZone, criteria)
它返回检查出的角点corners
,输入参数如下:
image
: 输入图像的像素矩阵,最好是 8 位灰度图像,检测效率更高;corners
: 初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据;winSize
: 大小为搜索窗口的一半;zeroZone
: 死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)时表示没有死区;criteria
: 定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合。
编程要求
图 6 给定的原始图像
根据提示,在右侧编辑器补充task1()
函数的 Begin-End 区间代码,实现对给定图片(如图 6 所示)实现角点检测的任务。具体要求如下:
- 使用
cv2.findChessboardCorners()
函数寻找角点,存入corners
,ret
是找到角点的flag
; cv2.cornerSubPix()
执行亚像素级角点检测,初始角点坐标为上一步中找到的corners
,winSize
为(11,11)
,没有死区,终止条件使用程序中定义的criteria
,存入变量corners2
中。
测试说明
平台会对你补全的代码进行测试,如果实际输出与预期输出匹配则通关。
测试输入:无 ; 预期输出: corners detection success
参考代码:
import cv2
import numpy as np
def task1():
fname = '/data/workspace/myshixun/task1/5test1.jpg'
# criteria:角点精准化迭代过程的终止条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((7*7,3), np.float32)
'''
设定世界坐标下点的坐标值,因为用的是棋盘可以直接按网格取;
假定棋盘正好在x-y平面上,这样z值直接取0,简化初始化步骤。
mgrid把列向量[0:cbraw]复制了cbcol列,把行向量[0:cbcol]复制了cbraw行。
转置reshape后,每行都是4×6网格中的某个点的坐标。
'''
objp[:,:2] = np.mgrid[0:7,0:7].T.reshape(-1,2)
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
# 识别出角点,记录世界物体坐标和图像坐标
img = cv2.imread(fname) #source image
cv2.imwrite('/data/workspace/myshixun/task1/out/5test1.png', img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转灰度
# 寻找角点,存入corners,ret是找到角点的flag
########## Begin ##########
ret, corners = cv2.findChessboardCorners(gray, (7, 7), None)
########## End ##########
if ret == True:
objpoints.append(objp)
# 执行亚像素级角点检测
########## Begin ##########
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
########## End ##########
imgpoints.append(corners2)
# 在棋盘上绘制角点
img = cv2.drawChessboardCorners(img,(7,7),corners2,ret)
# 保存图像
filepath = '/data/workspace/myshixun/task1/'
cv2.imwrite(filepath + 'out/img.png', img)