最近正在做点云分割相关的课题,数据集采集有点麻烦,想通过Pybullet先制作一批仿真合成数据集出来。虽然思路挺清晰,由RGB-D图像生成点云,但是中间有很多地方会卡住,所以写篇blog记录一下。
图像的拍摄挺简单的,直接用Pybullet现成的函数就可以获取RGB图像和深度图像,就是先要对物体还有相机的位置朝向等做个设置。
import pybullet as p
import pybullet_data
import numpy as np
import cv2
import PIL.Image as Image
import open3d as o3d
# 连接引擎
_ = p.connect(p.GUI)
# 不展示GUI的套件
p.configureDebugVisualizer(p.COV_ENABLE_GUI, 1)
# 添加资源路径
p.setAdditionalSearchPath(pybullet_data.getDataPath())
planeUid = p.loadURDF("plane.urdf", useMaximalCoordinates=True) # 加载一个地面
trayUid = p.loadURDF("tray/traybox.urdf", basePosition=[0, 0, 0]) # 加载一个箱子,设置初始位置为(0,0,0)
p.setGravity(0,0,-10)
width = 1080 # 图像宽度
height = 720 # 图像高度
fov = 50 # 相机视角
aspect = width / height # 宽高比
near = 0.01 # 最近拍摄距离
far = 20 # 最远拍摄距离
cameraPos = [0,0,1] # 相机位置
targetPos = [0,0,0] # 目标位置,与相机位置之间的向量构成相机朝向
cameraupPos = [1,0,0] # 相机顶端朝向
viewMatrix = p.computeViewMatrix(
cameraEyePosition=cameraPos,
cameraTargetPosition=targetPos,
cameraUpVector=cameraupPos,
physicsClientId=0
) # 计算视角矩阵
projection_matrix = p.computeProjectionMatrixFOV(fov, aspect, near, far) # 计算投影矩阵
w, h, rgb, depth, seg = p.getCameraImage(width, height, viewMatrix, projection_matrix, renderer=p.ER_BULLET_HARDWARE_OPENGL)
# 由此便可以得到RGB图像、深度图像、分割标签图像
接下来就是将图像存储下来了,这里有挺多坑的。
彩色图像的存储还是比较简单的,我这里直接用OpenCV存,就是需要将RGB通道转换一下,OpenCV存储时的通道顺序有点不一样。
rgbImg = cv2.cvtColor(images[2], cv2.COLOR_BGR2RGB)
cv2.imwrite('image/rgb.jpg',rgbImg)
坑1:深度图像一般的存储单位都是毫米,而Pybullet里的单位是米,所以需要做个换算。
坑2:需要对深度图像的深度值做一个透视变换,原理参考下文:《【3D数学】——透视变换与相机偏手性》,主要就是如下公式(符号意义看链接中的文章):
m
3
+
m
4
z
=
z
′
⇒
z
=
m
4
z
′
−
m
3
m_3+\frac{m_4}{z}=z'\Rightarrow z= \frac{m_4}{z'-m_3}
m3+zm4=z′⇒z=z′−m3m4
坑3:由于深度数值会超过255,因此深度图像需要存储成为int16格式,jpg文件只能存储int8的数据,因此需要存储成为png文件。
存储代码如下(做了个按键交互):
i=1
# 开始渲染
while True:
p.stepSimulation()
images = p.getCameraImage(width, height, viewMatrix, projection_matrix, renderer=p.ER_BULLET_HARDWARE_OPENGL)
keys = p.getKeyboardEvents()
if ord("z") in keys and keys[ord("z")] & p.KEY_WAS_RELEASED:
rgbImg = cv2.cvtColor(images[2], cv2.COLOR_BGR2RGB)
cv2.imwrite('image/rgb'+str(i)+'.jpg',rgbImg)
depImg = far * near / (far - (far - near) * images[3])
depImg = np.asanyarray(depImg).astype(np.float32) * 1000.
depImg = (depImg.astype(np.uint16))
print(type(depImg[0,0]))
depImg = Image.fromarray(depImg)
depImg.save('image/depth'+str(i)+'.png')
cv2.imwrite('image/seg'+str(i)+'.jpg',images[4])
i=i+1
这个图像的像素值就是0,1,…,代表每个物体的标签,重构点云暂时用不到。
如果要通过RGB-D图像重建点云,那么必然需要相机的内参,但是Pybullet中的相机貌似只能计算得到投影矩阵,无法直接获取内参,那么就需要进行一定的计算获取内参参数。
这里还是先稍微介绍一下相关概念。
图源:https://zhuanlan.zhihu.com/p/473172644
世界坐标系world:顾名思义。
相机坐标系camera:以相机的光心为原点,前为Z轴,右为X轴,下为Y轴。
图像坐标系xy:成像平面与光轴的交点为原点。真实情况下,图像平面是在光心Oc后面,所产生的图像是一个颠倒的画面。为了讨论方便,这里是将图像平面放到了Oc前面,因此此时图像平面的图像没有颠倒,而xy轴的方向与相机坐标系的方向相反,单位为mm。
像素坐标系:图像平面中图像左上角为原点,单位为pixel。
焦距:光心与成像平面之间的距离。
我们将世界到相机的变换称为相机的外参矩阵R,t。
我们将相机投影到图像的变换称为相机的内参矩阵K。
外参:将世界坐标系转换成相机坐标系。
内参:将相机坐标系转换成图像坐标系。

图源:https://zhuanlan.zhihu.com/p/473172644
fx = f / dx
fy = f / dy
f : 焦距长度,物理单位mm
dx、dy : x、y方向上一个像素分别代表多少mm
u0、v0 : 图像的中心像素坐标和图像原点像素坐标之间相差的横向和纵向像素数。理论值应该是图像宽度、高度的一半,但实际是有偏差的,一般摄像头越好越接近理论值。有些地方写作cx,cy。
大概算是介绍完了一些基础概念,然后开始从Pybullet中的投影矩阵得到内参。投影矩阵就是上文代码中的projection_matrix,输出为16个元素的行向量数组,需要提取一下变成4X4矩阵,Pybullet采用的是OpenGL中的投影矩阵,形式如下:
[
2
f
x
r
−
l
0
r
+
l
r
−
l
0
0
2
f
y
t
−
b
t
+
b
t
−
b
0
0
0
−
(
f
+
n
)
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
]
\begin{bmatrix} \frac {2f_x} {r-l} & 0 & \frac {r+l} {r-l} & 0\\ 0 & \frac {2f_y} {t-b} & \frac {t+b} {t-b} &0 \\ 0 & 0 & \frac {-(f+n)} {f-n} & \frac {-2fn} {f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \
r−l2fx0000t−b2fy00r−lr+lt−bt+bf−n−(f+n)−100f−n−2fn0
其中:
{
l
=
−
c
x
r
=
W
−
c
x
b
=
−
c
y
t
=
H
−
c
y
\begin{cases} l=-c_x \\ r=W-c_x\\ b=-c_y \\ t=H-c_y \\ \end{cases}
⎩
⎨
⎧l=−cxr=W−cxb=−cyt=H−cy
H、W分别为图像的高和宽,单位为pixel。
f为最远拍摄距离,n为最近拍摄距离。
那么投影矩阵又可以表示为
[
2
f
x
W
0
W
−
2
c
x
W
0
0
2
f
y
H
H
−
2
c
y
H
0
0
0
−
(
f
+
n
)
f
−
n
−
2
f
n
f
−
n
0
0
−
1
0
]
\begin{bmatrix} \frac {2f_x} {W} & 0 & \frac {W-2c_x} {W} & 0\\ 0 & \frac {2f_y} {H} & \frac {H-2c_y} {H} &0 \\ 0 & 0 & \frac {-(f+n)} {f-n} & \frac {-2fn} {f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \
W2fx0000H2fy00WW−2cxHH−2cyf−n−(f+n)−100f−n−2fn0
Pybullet得到的投影矩阵和上面的投影矩阵属于转置关系,其计算公式为:(pybullet已经帮你算好了)

因此做个一一对应就可以计算得到相机内参了,假设投影矩阵为P:
{
c
x
=
W
/
2
c
y
=
H
/
2
f
x
=
P
[
0
,
0
]
∗
W
/
2
f
y
=
P
[
1
,
1
]
∗
H
/
2
\begin{cases} c_x=W/2 \\ c_y=H/2 \\ f_x = P[0,0]*W/2 \\ f_y = P[1,1]*H/2 \end{cases}
⎩
⎨
⎧cx=W/2cy=H/2fx=P[0,0]∗W/2fy=P[1,1]∗H/2
OK,总算是把内参算好了,畸变这里就不考虑了。
接下来就简单了,Open3D读取然后重建一下就好了。
import open3d as o3d
color_image = o3d.io.read_image('image/rgb1.jpg')
depth_image = o3d.io.read_image('image/depth1.png')
rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(color_image, depth_image,convert_rgb_to_intensity=False)
intrinsic = o3d.camera.PinholeCameraIntrinsic(
o3d.camera.PinholeCameraIntrinsicParameters.Kinect2DepthCameraDefault )
intrinsic.set_intrinsics(width=1080, height=720, fx=2.14450693*1080/2, fy=2.14450693*720/2, cx=1080/2, cy=720/2)
point_cloud = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, intrinsic)
o3d.visualization.draw_geometries([point_cloud])
大致流程就是这样了,全部代码都在文章里面了,搞懂这一切还是挺花时间的。
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司
状态:我正在构建一个应用程序,其中需要一个可供用户选择颜色的字段,该字段将包含RGB颜色代码字符串。我已经测试了一个看起来很漂亮但效果不佳的。它是“挑剔的颜色”,并托管在此存储库中:https://github.com/Astorsoft/picky-color.在这里我打开一个关于它的一些问题的问题。问题:请建议我在Rails3应用程序中使用一些颜色选择器。 最佳答案 也许页面上的列表jQueryUIDevelopment:ColorPicker为您提供开箱即用的产品。原因是jQuery现在包含在Rails3应用程序中,因此使用基
如何在Ruby中获取BasicObject实例的类名?例如,假设我有这个:classMyObjectSystem我怎样才能使这段代码成功?编辑:我发现Object的实例方法class被定义为returnrb_class_real(CLASS_OF(obj));。有什么方法可以从Ruby中使用它? 最佳答案 我花了一些时间研究irb并想出了这个:classBasicObjectdefclassklass=class这将为任何从BasicObject继承的对象提供一个#class您可以调用的方法。编辑评论中要求的进一步解释:假设你有对象
是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在