python图像处理笔记-九-简单标定、姿态估计与增强现实
一种简单的标定方法:
在上一个笔记中我们学习了张正友标定法,并且挖了一个用张正友标定法标定相机的坑,但是这个坑目前还填补上。这一节里面我们要学的是用一种非常简单的方法进行标定,方法如下:
需要准备的东西
- 一个矩形(一本书就可以了)
- 一个卷尺
- 一个平面
操作步骤
数据测量
- 测量你选的矩形的边长:\(dX,dY\)。
- 将照相机和标定物体放置在平面上,使得照相机的背面和标定物体平行,同时物体位于照相机图像视图的中心,你可能需要调整照相机或物体来获得良好的对齐效果。
- 测量标定物体到照相机的距离\(dZ\)
- 拍摄一幅图像来测试是否正确(被摄物体的竖边和图像竖边平行,横边也一样)
- 使用像素数来测量标定物体图像的宽和高:\(dx,dy\)
计算
- 焦距:
- \(f_x = \frac{dx}{dX}dZ\)
- \(f_y = \frac{dy}{dY}dZ\)
实操
我选用了我的iapd进行标定,因为我的ipad有个支架,他非常适合进行固定和拍摄。我利用几本书将ipad固定在地上,然后再将另外一本书靠在墙上进行拍摄。我刚拍摄回来,接着写:
在第一次实拍过程中我是这样布局的:
拍摄出来的效果如下:
可以看出来拍摄的结果是存在一定问题的,问题主要在于:拍出来的书的边是斜的,这主要是由于:
书的最后一页有张开
ipad有点斜
于是我进行了一些调整,调整后的如下:
这里用牛奶盒子避免了书翻页的问题,结果如下:
接下来进行测量:
盒子宽:168mm
盒子长:282mm
盒子到相机:556mm
接下来我们写个程序来获取物体在图片中的高和宽:
1 | import pylab as pl |
在图片上我们的盒子的尺寸是:1496.883116883117*801.4285714285716
图像的尺寸是:2448 *3264
经过计算我们知道:
\[ f_x = 2951,f_y = 2652 \]
与书中相比,我这个误差还是比较大的。
我们给出一个根据已经标定好的内参生成内参矩阵的方法:
1 | def myCaliBration(sz): |
这个方法和张正友比起来已经非常精简了,但是精简的代价是结果比较扣脚。
姿态估计
这里主要就是前面的知识的应用了,直接贴代码:
1 | from PCV.geometry import homography |
最后的结果之一如下:
增强现实
从照相机矩阵到OpenGL格式
这个用的也都是之前的知识,openGL和以往的用法有所不同,在代码中我都有所标注,大家直接越读代码即可: 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
glutInit()
import pygame, pygame.image
from pygame.locals import *
import numpy as np
import pylab as pl
import pickle
from scipy import linalg
import sys
# 设置图片大小
width,height = 1000,747
def setProjectionFromCamera(K):
"""
从照相机标定矩阵获得视图的大小等信息,并且创建相应视图
"""
# 将当前的工作区域设置为GL_PROJECTION,即操作该矩阵
glMatrixMode(GL_PROJECTION)
# 将GL_PROJECTION归为单位矩阵
glLoadIdentity()
fx = K[0][0] # 获取横轴焦距
fy = K[1][1] # 获取纵轴焦距
# 这里的具体含义是:视角的大小,即眼睛睁开的角度
# 如果设置为0,那么就相当于闭上了眼睛
# 如果设置为180,就可以认为你的视野开阔
fovy = 2 * np.arctan(0.5 * height / fy ) * 180 / np.pi
# 就是窗口的的纵横比x/y
aspect = (width * fy) / (height * fx)
# 定义近的和远的剪裁平面
near = 0.1
far = 100.0
# 设定透视
# 这个函数有四个参数,他们分别代表:
# 1. fovy , 已经在上面介绍过了
# 2. aspect, 这个是窗口的纵横比,也介绍了
# 3. zNear, 近处的截面离眼睛的距离
# 4. zFar, 远处的截面离眼睛的距离
gluPerspective(fovy, aspect, near, far)
# 指定了窗口的可见区域,其中前两个值代表的是窗口起点
# 后两个参数代表窗口的宽度与高度
glViewport(0, 0, width, height)
# 从相机姿态中获得模拟视图矩阵
def setModelviewFromCamera(Rt):
# 这里我们将工作区切换到GL_MODELVIEW矩阵
glMatrixMode(GL_MODELVIEW)
# 将GL_MODELVIEW归为单位矩阵
glLoadIdentity()
# 创建一个90度旋转矩阵
Rx = np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
# 获得旋转的最佳逼近
R = Rt[:, :3]
# 矩阵撕裂
U, S, V = linalg.svd(R)
R = np.dot(U, V)
R[0, :] = - R[0, :] # 改变x轴符号
# 获得平移量
t = Rt[: ,3]
# 获得4*4的模拟视图矩阵
M = np.eye(4)
M[:3, :3] = np.dot(R, Rx)
M[:3, 3] = t
# 转置并压平以获取序列数值
M = M.T
m = M.flatten()
# 将模拟视图矩阵替换为新的矩阵
glLoadMatrixf(m)
def drawBackground(imname):
"""
使用四边形绘制背景图像
"""
# 载入背景图像
bgImage = pygame.image.load(imname).convert()
bgData = pygame.image.tostring(bgImage, "RGBX", 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# 绑定纹理
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, glGenTextures(1))
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bgData)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
# 创建四方形填充整个窗口
glBegin(GL_QUADS)
glTexCoord2f(0.0, 0.0)
glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(1.0, 0.0)
glVertex3f( 1.0, -1.0, -1.0)
glTexCoord2f(1.0, 1.0)
glVertex3f( 1.0, 1.0, -1.0)
glTexCoord2f(0.0, 1.0)
glVertex3f(-1.0, 1.0, -1.0)
glEnd()
# 清除纹理
glDeleteTextures(1)
def drawTeapot(size):
"""
在原点处绘制红色茶壶
"""
# glEnable 可以开启OpenGL的一些特性
# 这里,开启了一些OPENGL的特性这里激活了灯光效果
glEnable(GL_LIGHTING)
# 这里激活了一盏灯
glEnable(GL_LIGHT0)
# 这里打开了深度测试,使得物体按照其深度表示出来(远处的物体不能绘制在近处物体的前面)
glEnable(GL_DEPTH_TEST)
# 清理深度缓存
glClear(GL_DEPTH_BUFFER_BIT)
# 绘制红色茶壶
# 制定物体的物质特性,比如镜面反射颜色等
glMaterialfv(GL_FRONT, GL_AMBIENT, [0, 0, 0, 0])
glMaterialfv(GL_FRONT, GL_DIFFUSE, [0.5, 0.0, 0.0, 0.0])
glMaterialfv(GL_FRONT, GL_SPECULAR, [0.7, 0.6, 0.6, 0.0])
glMaterialf(GL_FRONT, GL_SHININESS, 0.25 * 128.0)
# 将制定的物质特性加入到茶壶上
glutWireTeapot(size)
# 集成
def setUp():
"""
设置窗口和pygmae环境
"""
pygame.init()
pygame.display.set_mode((width, height), OPENGL | DOUBLEBUF)
pygame.display.set_caption('OpenGL AR demo')
# 载入照相机数据
with open(r'/home/wangsy/Codes/PCVLearning/myCodes/Carma/ar_camera.pkl','rb') as f:
K = pickle.load(f)
Rt = pickle.load(f)
setUp()
drawBackground(r'/home/wangsy/Codes/PCVLearning/myCodes/Carma/book_perspective.bmp')
setProjectionFromCamera(K)
setModelviewFromCamera(Rt)
drawTeapot(0.051)
while True:
event = pygame.event.poll()
if(event.type in (QUIT, KEYDOWN)):
break
pygame.display.flip()glutSolidTeapot(size)
这种方法在我的环境中会报错:
freeglut ERROR: Function
called without first calling 'glutInit'.
发现该错误后,我在程序的头部加上了glutInit()
,但是仍然报错:
Current thread 0x00007f9484371700 (most recent call first): File "AR.py", line 139 in drawTeapot File "AR.py", line 161 in
zsh: abort (core dumped) python AR.py
上网搜了很多方法(几乎都用了),都没有解决,最终发现,将glutSolidTeapot(size)
方法更改为glutWireTeapot(size)
就可以运行无误了,最终结果如下: