京公网安备 11010802034615号
经营许可证编号:京B2-20210330
VTK与Python实现机械臂三维模型可视化详解
三维可视化系统的建立依赖于三维图形平台, 如 OpenGL、VTK、OGRE、OSG等, 传统的方法多采用OpenGL进行底层编程,即对其特有的函数进行定量操作, 需要开发人员熟悉相关函数, 从而造成了开发难度大、 周期长等问题。VTK、 ORGE、OSG等平台使用封装更好的函数简化了开发过程。下面将使用Python与VTK进行机器人上位机监控界面的快速原型开发。
完整的上位机程序需要有三维显示模块、机器人信息监测模块(位置/角度/速度/电量/温度/错误信息...)、通信模块(串口/USB/WIFI/蓝牙...)、控制模块等功能模块。三维显示模块主要用于实时显示机器人的姿态(或位置)信息。比如机器人上肢手臂抬起,程序界面中的虚拟机器人也会同时进行同样的动作。三维显示模块也可以用于对机器人进行控制,实现良好的人机交互。比如在三维图像界面中可以点击拾取机器人某一关节,拖拽部件(肢体)控制真实的机器人完成同样的运动。Aldebaran Robotics的图形化编程软件Choregraphe可以完成上述的一些功能对NAO机器人进行控制。
对于简单的模型可以自己编写函数进行创建,但这种方法做出来的模型过于简单不够逼真。因此可以先在SolidWorks、Blender、3DMax、Maya、Rhino等三维设计软件中建立好模型,然后导出为通用的三维文件格式,再使用VTK将其读入并进行渲染。
在SolidWorks等三维设计软件中设计好机器人的大臂(upperarm)和小臂(forearm),然后创建装配体如下图所示。在将装配体导出为STL文件前需要注意几点:
1. 当从外界读入STL类型的模型时,其会按照它内部的坐标位置进行显示,因此它的位置和大小是确定的。为了以后的定位以及移动、旋转等操作的方便,需要先在SolidWorks中创建一个坐标系。如下图所示,坐标系建立在大臂关节中心点。
2. 如果将装配体整体输出为一个STL文件,则导入VTK后无法控制零部件进行相对运动。因此,需要将装配体各可动部件分别导出。
在SolidWorks的另存为STL对话框中,点开输出选项卡,如下图所示。注意之前提到的几点:如果勾选“在单一文件中保存装配体的所有零部件”则会将整个装配体导出为一个STL文件,否则就是分别命名的两个STL文件;输出坐标系下拉列表中选择之前创建的坐标系1,并勾选“不要转换STL输出数据到正的坐标空间”。
下面的Python代码简单实现了一个2自由度机械臂的三维仿真,可以拖动滑块或按键盘上的方向键控制肩关节或肘关节运动。当然程序还存在一些问题有待完善...
#!/usr/bin/env python
import vtk
import math
from vtk.util.colors import *
filenames = ["upperarm.stl","forearm.stl"]
dt = 1.0 # degree step in rotation
angle = [0, 0] # shoulder and elbow joint angle
renWin = vtk.vtkRenderWindow()
assembly = vtk.vtkAssembly()
slider_shoulder = vtk.vtkSliderRepresentation2D()
slider_elbow = vtk.vtkSliderRepresentation2D()
actor = list() # the list of links
# Customize vtkInteractorStyleTrackballCamera
class MyInteractor(vtk.vtkInteractorStyleTrackballCamera):
def __init__(self,parent=None):
self.AddObserver("CharEvent",self.OnCharEvent)
self.AddObserver("KeyPressEvent",self.OnKeyPressEvent)
# Override the default key operations which currently handle trackball or joystick styles is provided
# OnChar is triggered when an ASCII key is pressed. Some basic key presses are handled here
def OnCharEvent(self,obj,event):
pass
def OnKeyPressEvent(self,obj,event):
global angle
# Get the compound key strokes for the event
key = self.GetInteractor().GetKeySym()
# Output the key that was pressed
#print "Pressed: " , key
# Handle an arrow key
if(key == "Left"):
actor[1].RotateY(-dt)
if(key == "Right"):
actor[1].RotateY(dt)
if(key == "Up"):
assembly.RotateY(-dt)
angle[0] += dt
if angle[0] >= 360.0:
angle[0] -= 360.0
slider_shoulder.SetValue(angle[0])
if(key == "Down"):
assembly.RotateY(dt)
angle[0] -= dt
if angle[0] < 0.0:
angle[0] += 360.0
slider_shoulder.SetValue(angle[0])
# Ask each renderer owned by this RenderWindow to render its image and synchronize this process
renWin.Render()
return
def LoadSTL(filename):
reader = vtk.vtkSTLReader()
reader.SetFileName(filename)
mapper = vtk.vtkPolyDataMapper() # maps polygonal data to graphics primitives
mapper.SetInputConnection(reader.GetOutputPort())
actor = vtk.vtkLODActor()
actor.SetMapper(mapper)
return actor # represents an entity in a rendered scene
def CreateCoordinates():
# create coordinate axes in the render window
axes = vtk.vtkAxesActor()
axes.SetTotalLength(100, 100, 100) # Set the total length of the axes in 3 dimensions
# Set the type of the shaft to a cylinder:0, line:1, or user defined geometry.
axes.SetShaftType(0)
axes.SetCylinderRadius(0.02)
axes.GetXAxisCaptionActor2D().SetWidth(0.03)
axes.GetYAxisCaptionActor2D().SetWidth(0.03)
axes.GetZAxisCaptionActor2D().SetWidth(0.03)
#axes.SetAxisLabels(0) # Enable:1/disable:0 drawing the axis labels
#transform = vtk.vtkTransform()
#transform.Translate(0.0, 0.0, 0.0)
#axes.SetUserTransform(transform)
#axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(1,0,0)
#axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().BoldOff() # disable text bolding
return axes
def ShoulderSliderCallback(obj,event):
sliderRepres = obj.GetRepresentation()
pos = sliderRepres.GetValue()
assembly.SetOrientation(0,-pos,0)
renWin.Render()
def ElbowSliderCallback(obj,event):
sliderRepres = obj.GetRepresentation()
pos = sliderRepres.GetValue()
actor[1].SetOrientation(0,-pos,0)
renWin.Render()
def ConfigSlider(sliderRep, TitleText, Yaxes):
sliderRep.SetMinimumValue(0.0)
sliderRep.SetMaximumValue(360.0)
sliderRep.SetValue(0.0) # Specify the current value for the widget
sliderRep.SetTitleText(TitleText) # Specify the label text for this widget
sliderRep.GetSliderProperty().SetColor(1,0,0) # Change the color of the knob that slides
sliderRep.GetSelectedProperty().SetColor(0,0,1) # Change the color of the knob when the mouse is held on it
sliderRep.GetTubeProperty().SetColor(1,1,0) # Change the color of the bar
sliderRep.GetCapProperty().SetColor(0,1,1) # Change the color of the ends of the bar
#sliderRep.GetTitleProperty().SetColor(1,0,0) # Change the color of the text displaying the value
# Position the first end point of the slider
sliderRep.GetPoint1Coordinate().SetCoordinateSystemToDisplay()
sliderRep.GetPoint1Coordinate().SetValue(50, Yaxes)
# Position the second end point of the slider
sliderRep.GetPoint2Coordinate().SetCoordinateSystemToDisplay()
sliderRep.GetPoint2Coordinate().SetValue(400, Yaxes)
sliderRep.SetSliderLength(0.02) # Specify the length of the slider shape.The slider length by default is 0.05
sliderRep.SetSliderWidth(0.02) # Set the width of the slider in the directions orthogonal to the slider axis
sliderRep.SetTubeWidth(0.005)
sliderRep.SetEndCapWidth(0.03)
sliderRep.ShowSliderLabelOn() # display the slider text label
sliderRep.SetLabelFormat("%.1f")
sliderWidget = vtk.vtkSliderWidget()
sliderWidget.SetRepresentation(sliderRep)
sliderWidget.SetAnimationModeToAnimate()
return sliderWidget
def CreateGround():
# create plane source
plane = vtk.vtkPlaneSource()
plane.SetXResolution(50)
plane.SetYResolution(50)
plane.SetCenter(0,0,0)
plane.SetNormal(0,0,1)
# mapper
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(plane.GetOutputPort())
# actor
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetRepresentationToWireframe()
#actor.GetProperty().SetOpacity(0.4) # 1.0 is totally opaque and 0.0 is completely transparent
actor.GetProperty().SetColor(light_grey)
'''
# Load in the texture map. A texture is any unsigned char image.
bmpReader = vtk.vtkBMPReader()
bmpReader.SetFileName("ground_texture.bmp")
texture = vtk.vtkTexture()
texture.SetInputConnection(bmpReader.GetOutputPort())
texture.InterpolateOn()
actor.SetTexture(texture)
'''
transform = vtk.vtkTransform()
transform.Scale(2000,2000, 1)
actor.SetUserTransform(transform)
return actor
def CreateScene():
# Create a rendering window and renderer
ren = vtk.vtkRenderer()
#renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
# Create a renderwindowinteractor
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)
style = MyInteractor()
style.SetDefaultRenderer(ren)
iren.SetInteractorStyle(style)
for id, file in enumerate(filenames):
actor.append(LoadSTL(file))
#actor[id].GetProperty().SetColor(blue)
r = vtk.vtkMath.Random(.4, 1.0)
g = vtk.vtkMath.Random(.4, 1.0)
b = vtk.vtkMath.Random(.4, 1.0)
actor[id].GetProperty().SetDiffuseColor(r, g, b)
actor[id].GetProperty().SetDiffuse(.8)
actor[id].GetProperty().SetSpecular(.5)
actor[id].GetProperty().SetSpecularColor(1.0,1.0,1.0)
actor[id].GetProperty().SetSpecularPower(30.0)
assembly.AddPart(actor[id])
# Add the actors to the scene
#ren.AddActor(actor[id])
# Also set the origin, position and orientation of assembly in space.
assembly.SetOrigin(0, 0, 0) # This is the point about which all rotations take place
#assembly.AddPosition(0, 0, 0)
#assembly.RotateX(45)
actor[1].SetOrigin(274, 0, 0) # initial elbow joint position
ren.AddActor(assembly)
# Add coordinates
axes = CreateCoordinates()
ren.AddActor(axes)
# Add ground
ground = CreateGround()
ren.AddActor(ground)
# Add slider to control the robot
sliderWidget_shoulder = ConfigSlider(slider_shoulder,"Shoulder Joint", 80)
sliderWidget_shoulder.SetInteractor(iren)
sliderWidget_shoulder.EnabledOn()
sliderWidget_shoulder.AddObserver("InteractionEvent", ShoulderSliderCallback)
sliderWidget_elbow = ConfigSlider(slider_elbow,"Elbow Joint", 160)
sliderWidget_elbow.SetInteractor(iren)
sliderWidget_elbow.EnabledOn()
sliderWidget_elbow.AddObserver("InteractionEvent", ElbowSliderCallback)
# Set background color
ren.SetBackground(.2, .2, .2)
# Set window size
renWin.SetSize(600, 600)
# Set up the camera to get a particular view of the scene
camera = vtk.vtkCamera()
camera.SetFocalPoint(300, 0, 0)
camera.SetPosition(300, -400, 350)
camera.ComputeViewPlaneNormal()
camera.SetViewUp(0, 1, 0)
camera.Zoom(0.4)
ren.SetActiveCamera(camera)
# Enable user interface interactor
iren.Initialize()
iren.Start()
if __name__ == "__main__":
CreateScene()
下面是使用MFC搭建的机器
下面是使用MFC搭建的机器人上位机监控平台,可以实现上述的一些基本功能。这个GIF动画使用开源软件ScreenToGif生成,非常好用!
总结
以上就是本文关于VTK与Python实现机械臂三维模型可视化详解的全部内容,希望对大家有所帮助。
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
在Python开发中,HTTP请求是与外部服务交互的核心场景——调用第三方API、对接微服务、爬取数据等都离不开它。虽然requests库已 ...
2025-12-12在数据驱动决策中,“数据波动大不大”是高频问题——零售店长关心日销售额是否稳定,工厂管理者关注产品尺寸偏差是否可控,基金 ...
2025-12-12在CDA(Certified Data Analyst)数据分析师的能力矩阵中,数据查询语言(SQL)是贯穿工作全流程的“核心工具”。无论是从数据库 ...
2025-12-12很多小伙伴都在问CDA考试的问题,以下是结合 2025 年最新政策与行业动态更新的 CDA 数据分析师认证考试 Q&A,覆盖考试内容、报考 ...
2025-12-11在Excel数据可视化中,柱形图因直观展示数据差异的优势被广泛使用,而背景色设置绝非简单的“换颜色”——合理的背景色能突出核 ...
2025-12-11在科研实验、商业分析或医学研究中,我们常需要判断“两组数据的差异是真实存在,还是偶然波动”——比如“新降压药的效果是否优 ...
2025-12-11在CDA(Certified Data Analyst)数据分析师的工作体系中,数据库就像“数据仓库的核心骨架”——所有业务数据的存储、组织与提 ...
2025-12-11在神经网络模型搭建中,“最后一层是否添加激活函数”是新手常困惑的关键问题——有人照搬中间层的ReLU激活,导致回归任务输出异 ...
2025-12-05在机器学习落地过程中,“模型准确率高但不可解释”“面对数据噪声就失效”是两大核心痛点——金融风控模型若无法解释决策依据, ...
2025-12-05在CDA(Certified Data Analyst)数据分析师的能力模型中,“指标计算”是基础技能,而“指标体系搭建”则是区分新手与资深分析 ...
2025-12-05在回归分析的结果解读中,R方(决定系数)是衡量模型拟合效果的核心指标——它代表因变量的变异中能被自变量解释的比例,取值通 ...
2025-12-04在城市规划、物流配送、文旅分析等场景中,经纬度热力图是解读空间数据的核心工具——它能将零散的GPS坐标(如外卖订单地址、景 ...
2025-12-04在CDA(Certified Data Analyst)数据分析师的指标体系中,“通用指标”与“场景指标”并非相互割裂的两个部分,而是支撑业务分 ...
2025-12-04每到“双十一”,电商平台的销售额会迎来爆发式增长;每逢冬季,北方的天然气消耗量会显著上升;每月的10号左右,工资发放会带动 ...
2025-12-03随着数字化转型的深入,企业面临的数据量呈指数级增长——电商的用户行为日志、物联网的传感器数据、社交平台的图文视频等,这些 ...
2025-12-03在CDA(Certified Data Analyst)数据分析师的工作体系中,“指标”是贯穿始终的核心载体——从“销售额环比增长15%”的业务结论 ...
2025-12-03在神经网络训练中,损失函数的数值变化常被视为模型训练效果的“核心仪表盘”——初学者盯着屏幕上不断下降的损失值满心欢喜,却 ...
2025-12-02在CDA(Certified Data Analyst)数据分析师的日常工作中,“用部分数据推断整体情况”是高频需求——从10万条订单样本中判断全 ...
2025-12-02在数据预处理的纲量统一环节,标准化是消除量纲影响的核心手段——它将不同量级的特征(如“用户年龄”“消费金额”)转化为同一 ...
2025-12-02在数据驱动决策成为企业核心竞争力的今天,A/B测试已从“可选优化工具”升级为“必选验证体系”。它通过控制变量法构建“平行实 ...
2025-12-01