├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── __init__.py
├── apps
├── Cal_norm.py
├── generate-dataset-canny.py
├── kinect2grasp.py
├── pointclouds.py
├── read_file_sdf.py
├── read_grasps_from_file.py
├── show_pcd.py
├── voxelgrid.py
└── ycb_cloud_generate.py
├── data
├── 1v_500_2class_ourway2sample.model
├── 1v_750_2class_ourway2sample.model
├── 1v_750_2class_ourway2sample0.model
├── grasp_pipeline.svg
├── image-20210311111731964.png
├── image-20210311162402868.png
├── 在线检测时的夹爪各项参数定义.png
└── 在线检测时的夹爪数学模型各点以及夹爪坐标系定义.png
├── main_1v.py
├── main_1v_gpd.py
├── main_1v_mc.py
├── main_fullv.py
├── main_fullv_gpd.py
├── main_fullv_mc.py
├── main_test.py
├── model
├── __init__.py
├── dataset.py
├── gpd.py
└── pointnet.py
├── requirements.txt
└── requirements2.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | assets/learned_models/
3 | assets/log/
4 | dataset/ycb_grasp/
5 | dataset/ycb_meshes_google/
6 | dataset/ycb_rgbd/
7 | PointNetGPD/OLD/
8 | PointNetGPD/chann_12/
9 | PointNetGPDchann_3/
10 | PointNetGPD/fullpc/
11 | PointNetGPD/1view/
12 | exception.txt
13 | *.npy
14 | # Created by https://www.gitignore.io/api/vim,python,pycharm,jupyternotebook
15 |
16 | ### JupyterNotebook ###
17 | .ipynb_checkpoints
18 | */.ipynb_checkpoints/*
19 |
20 | # Remove previous ipynb_checkpoints
21 | # git rm -r .ipynb_checkpoints/
22 | #
23 | ### PyCharm ###
24 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
25 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
26 |
27 | # User-specific stuff:
28 | .idea/**/workspace.xml
29 | .idea/**/tasks.xml
30 | .idea/dictionaries
31 |
32 | # Sensitive or high-churn files:
33 | .idea/**/dataSources/
34 | .idea/**/dataSources.ids
35 | .idea/**/dataSources.xml
36 | .idea/**/dataSources.local.xml
37 | .idea/**/sqlDataSources.xml
38 | .idea/**/dynamic.xml
39 | .idea/**/uiDesigner.xml
40 |
41 | # Gradle:
42 | .idea/**/gradle.xml
43 | .idea/**/libraries
44 |
45 | # CMake
46 | cmake-build-debug/
47 |
48 | # Mongo Explorer plugin:
49 | .idea/**/mongoSettings.xml
50 |
51 | ## File-based project format:
52 | *.iws
53 |
54 | ## Plugin-specific files:
55 |
56 | # IntelliJ
57 | /out/
58 |
59 | # mpeltonen/sbt-idea plugin
60 | .idea_modules/
61 |
62 | # JIRA plugin
63 | atlassian-ide-plugin.xml
64 |
65 | # Cursive Clojure plugin
66 | .idea/replstate.xml
67 |
68 | # Ruby plugin and RubyMine
69 | /.rakeTasks
70 |
71 | # Crashlytics plugin (for Android Studio and IntelliJ)
72 | com_crashlytics_export_strings.xml
73 | crashlytics.properties
74 | crashlytics-build.properties
75 | fabric.properties
76 |
77 | ### PyCharm Patch ###
78 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
79 |
80 | # *.iml
81 | # modules.xml
82 | # .idea/misc.xml
83 | # *.ipr
84 |
85 | # Sonarlint plugin
86 | .idea/sonarlint
87 |
88 | ### Python ###
89 | # Byte-compiled / optimized / DLL files
90 | __pycache__/
91 | *.py[cod]
92 | *$py.class
93 |
94 | # C extensions
95 | *.so
96 |
97 | # Distribution / packaging
98 | .Python
99 | build/
100 | develop-eggs/
101 | dist/
102 | downloads/
103 | eggs/
104 | .eggs/
105 | lib/
106 | lib64/
107 | parts/
108 | sdist/
109 | var/
110 | wheels/
111 | *.egg-info/
112 | .installed.cfg
113 | *.egg
114 |
115 | # PyInstaller
116 | # Usually these files are written by a python script from a template
117 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
118 | *.manifest
119 | *.spec
120 |
121 | # Installer logs
122 | pip-log.txt
123 | pip-delete-this-directory.txt
124 |
125 | # Unit test / coverage reports
126 | htmlcov/
127 | .tox/
128 | .coverage
129 | .coverage.*
130 | .cache
131 | .pytest_cache/
132 | nosetests.xml
133 | coverage.xml
134 | *.cover
135 | .hypothesis/
136 |
137 | # Translations
138 | *.mo
139 | *.pot
140 |
141 | # Flask stuff:
142 | instance/
143 | .webassets-cache
144 |
145 | # Scrapy stuff:
146 | .scrapy
147 |
148 | # Sphinx documentation
149 | docs/_build/
150 |
151 | # PyBuilder
152 | target/
153 |
154 | # Jupyter Notebook
155 |
156 | # pyenv
157 | .python-version
158 |
159 | # celery beat schedule file
160 | celerybeat-schedule.*
161 |
162 | # SageMath parsed files
163 | *.sage.py
164 |
165 | # Environments
166 | .env
167 | .venv
168 | env/
169 | venv/
170 | ENV/
171 | env.bak/
172 | venv.bak/
173 |
174 | # Spyder project settings
175 | .spyderproject
176 | .spyproject
177 |
178 | # Rope project settings
179 | .ropeproject
180 |
181 | # mkdocs documentation
182 | /site
183 |
184 | # mypy
185 | .mypy_cache/
186 |
187 | ### Vim ###
188 | # swap
189 | .sw[a-p]
190 | .*.sw[a-p]
191 | # session
192 | Session.vim
193 | # temporary
194 | .netrwhist
195 | *~
196 | # auto-generated tag files
197 | tags
198 |
199 |
200 | # End of https://www.gitignore.io/api/vim,python,pycharm,jupyternotebook
201 |
202 | # ignore pickle file
203 | *.pickle
204 |
205 | # for latex files
206 | *.aux
207 | *.log
208 | *.nav
209 | *.out
210 | *.snm
211 | *.toc
212 | *.synctex.gz
213 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "离线训练",
9 | "type": "python",
10 | "request": "launch",
11 | "program": "${workspaceFolder}/main_1v.py",
12 | "console": "integratedTerminal",
13 | "args": [
14 | "--epoch",
15 | "200",
16 | "--mode",
17 | "train",
18 | "--batch-size",
19 | "32"
20 | ]
21 | },
22 | {
23 | "name": "Python: 当前文件",
24 | "type": "python",
25 | "request": "launch",
26 | "program": "${file}",
27 | "console": "integratedTerminal"
28 | },
29 | {
30 | "name": "在线GPD",
31 | "type": "python",
32 | "request": "launch",
33 | "program": "${workspaceFolder}/apps/kinect2grasp.py",
34 | "console": "integratedTerminal",
35 | "args": [
36 | "--cuda",
37 | "--gpu",
38 | "0",
39 | "--load-model",
40 | "./data/1v_500_2class_ourway2sample.model",
41 | "--using_mp",
42 | "--model_type",
43 | "750"
44 | ]
45 | }
46 | ]
47 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "/home/wgk/anaconda3/bin/python"
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Hongzhuo Liang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | typora-root-url: ./
3 | ---
4 |
5 | # PointNetGPD: Detecting Grasp Configurations from Point Set
6 |
7 | 感谢原作者开源:https://github.com/lianghongzhuo/PointNetGPD
8 |
9 | PointNetGPD (ICRA 2019, [arXiv](https://arxiv.org/abs/1809.06267)) is an end-to-end grasp evaluation model to address the challenging problem of localizing robot grasp configurations directly from the point cloud.
10 |
11 | **一个题外话:** 关于抓取采样的问题,很多人希望在混杂场景中也能够进行6-Dof候选抓取的采样,以便于用于一些regression-base算法;目前这种采样方式考虑到标注成本,一般是在虚拟场景中采样,然后用机理法标注;我个人也开源了一个[“半成品的”](https://github.com/Hymwgk/simulate_dataset_generation)仓库,有时间有精力的同学可以看看,也许有些代码片段可以帮助到你; 由于是自己手写的,很多是从小白开始写的,希望也许会有一些借鉴的价值吧。
12 |
13 |
14 |
15 | 简单介绍一下PointNet的代码流程:
16 | - 离线阶段
17 |
18 | 1. **候选抓取采样与打分:** 针对[YCB objects Dataset](http://ycb-benchmarks.s3-website-us-east-1.amazonaws.com/) 中的CAD模型,利用传统采样方法(默认Antipods采样)对进行候选抓取姿态(夹爪6D姿态)采样,并通过Force Closure结合 GWS 方法对所检测出的姿态进行打分,生成候选抓取数据集存起来备用;
19 | 2. **点云原始数据生成:** [YCB objects Dataset](http://ycb-benchmarks.s3-website-us-east-1.amazonaws.com/) 提供了一些物体的CAD模型,以及某些视角下这些物体的激光深度图;但是我们在训练网络时候,使用的是点云数据,因此,这里提前要把某些视角下的深度图转换为点云数据,以备后续使用;
20 | 3. **PointNet模型训练:** 利用生成的某候选夹爪姿态(步骤1.i),结合CAD模型的多视角点云(步骤1.ii),提取出来该候选抓取姿态下夹爪闭合区域内的点云;将夹爪闭合区域内的点云以及该抓取姿态的分数送入PointNet中进行训练;
21 |
22 | - 在线阶段
23 | 1. **抓取采样:** [从点云中直接采样候选抓取姿态](https://www.researchgate.net/publication/318107041_Grasp_Pose_Detection_in_Point_Clouds) ,并剔除掉与桌面碰撞、与场景点云碰撞的非法抓取;
24 | 2. **打分:** 提取剩余的抓取姿态夹爪内部的点,进一步剔除掉不合理的数据之后,将点集送入训练好的PointNet网络中打分;
25 | 3. **排序:** 将候选的抓取按照分数从高到低排序,输出分数最高的抓取。
26 |
27 |
28 |
29 | ## Video
30 | - 作者的实验视频
31 | [](https://www.youtube.com/watch?v=RBFFCLiWhRw)
32 | - 本人的复现实验视频(未加速)
33 |
34 | [](https://www.youtube.com/watch?v=OfvJ-HpKjI4)
35 | 在实验中发现,gpd效果还是很不错的;但是夹爪经常撞到目标物体上,这是受到了手眼标定的精度以及panda夹爪构型的影响(panda夹爪的深度比较浅,最大张开距离也比较小)
36 | ## 关于数据集
37 |
38 | 代码中数据集的生成部分比较混乱,这里解释一下具体数据集的生成逻辑:
39 |
40 | YCB数据集主要分为两个部分,一部分是数据集物体的CAD模型,一部分是各个视角下物体的rgbd深度图,因此在前期的准备过程中,主要分为两个独立的部分:
41 |
42 | - 对YCB数据集的CAD模型进行候选抓取姿态采样,获取候选grasp数据集;
43 | - 对YCB数据集中各个视角的深度图像进行计算,生成该视角下的点云数据集;
44 |
45 | 之后两部分的计算结果将会被放在同一个文件夹中,Dataloader将会对该文件夹进行处理,并计算出样本数据送入PointNet中进行训练。
46 |
47 |
48 |
49 | 以下是稍微具体一些的解释图:
50 |
51 |
52 |
53 | 为了防止硬盘不够大,本代码将最终Dataloader用到的数据集,单独放在了以下目录,可以挂载到一个单独的硬盘里,详细见后文的设置
54 |
55 | ```bash
56 | mkdir -p $HOME/dataset/PointNetGPD
57 | ```
58 |
59 |
60 |
61 | ## 本代码的修改
62 |
63 | 原代码中
64 |
65 | 离线阶段:使用Antipod算法对YCB 的CAD模型进行候选grasp pose采样,并结合夹爪数学模型和模型视角点云来生成夹爪内部点云,进而输入PointNet中训练;
66 |
67 | 在线阶段:使用基于点云的gpg算法,对场景点云进行候选grasp pose采样,并结合夹爪数学模型和场景点云来生成夹爪内部点云,进而输入PointNet中进行预测。
68 |
69 | 然而,实际代码中两个阶段使用的夹爪数学模型不一致,个人感觉统一模型会好一些,于是本代码中统一dataloader中相同的数学模型;
70 |
71 | ## Before Install
72 |
73 | 在使用前,clone的代码文件夹需要放在如下的code文件夹中:
74 | ```bash
75 | mkdir -p $HOME/code/
76 | cd $HOME/code/
77 | ```
78 |
79 |
80 | ## Install all the requirements (Using a virtual environment is recommended)
81 |
82 | 1. Make sure in your Python environment do not have same package named ```meshpy``` or ```dexnet```.
83 |
84 | 2. 确保已经安装了ROS以及相机预处理相关的[程序包](https://github.com/Hymwgk/point_cloud_process)
85 |
86 | 3. Clone this repository:
87 | ```bash
88 | cd $HOME/code
89 | git clone https://github.com/hymwgk/PointNetGPD.git
90 | ```
91 |
92 | 4. Install our requirements in `requirements.txt` (在python2以及python3环境中都需要安装)
93 | ```bash
94 | cd $HOME/code/PointNetGPD
95 | pip install -r requirements.txt #python3环境中
96 | pip install -r requirements2.txt #python2环境中
97 | ```
98 |
99 | 5. Install our modified meshpy (Modify from [Berkeley Automation Lab: meshpy](https://github.com/BerkeleyAutomation/meshpy))
100 | ```bash
101 | cd $HOME/code
102 | git clone https://github.com/Hymwgk/meshpy.git
103 | cd meshpy
104 | #分别在python2和python3环境下执行一遍
105 | python setup.py develop
106 | ```
107 |
108 | 6. Install our modified dex-net (Modify from [Berkeley Automation Lab: dex-net](https://github.com/BerkeleyAutomation/dex-net))
109 | ```bash
110 | cd $HOME/code
111 | git clone https://github.com/Hymwgk/dex-net.git
112 | cd dex-net
113 | # 分别在python2和python3环境下执行一遍
114 | python setup.py develop
115 | ```
116 |
117 | 7. 设置夹爪数学模型,之后离线以及在线节点的候选夹爪姿态计算都会依据此模型来生成;你可以直接在以下文件中根据自己的实际夹爪尺寸来修改对应参数。
118 |
119 | ```bash
120 | vim $HOME/code/dex-net/data/grippers/robotiq_85/params.json
121 | ```
122 | 以下是`params.json`中,原作者离线阶段用到的夹爪数学模型各个参数(本代码中已经将其废弃)
123 |
124 | ```bash
125 | "min_width": 夹爪的最小闭合角度
126 | "force_limit": 抓取力度限制
127 | "max_width": 夹爪最大张开距离
128 | "finger_radius": 用于软体手,指定软体手的弯曲角度(弧度制),一般用不到,补上去就行了
129 | "max_depth": 夹爪的最大深度,竖向的距离
130 | ```
131 | 以下本代码中离线训练和在线检测统一使用的夹爪参数定义:
132 | ```bash
133 | "finger_width": 夹持器的两个夹爪的“厚度”
134 | "real_finger_width": 也是两个夹爪的厚度,和上面写一样就行(仅仅用于显示,影响不大,不用于姿态检测)
135 | "hand_height": 夹爪的另一侧厚度,一会儿看图
136 | "hand_height_two_finger_side": 没有用到,代码中没有找到调用,所以不用管
137 | "hand_outer_diameter": 夹爪最大的可以张开的距离,从最外侧量(包括两个爪子的厚度)
138 | "hand_depth": 夹爪的竖向深度
139 | "real_hand_depth": 和hand_depth保持一致,代码中两者是相同的
140 | "init_bite": 这个是用于在线抓取检测时,定义的一个后撤距离,主要是避免由于点云误差之类的,导致夹爪和物体碰撞,以米为单位,一般设置1cm就行了
141 | ```
142 |
143 | `params.json`参数的具体定义示意图,修改后的本代码,离线的夹爪参数仅作为候选抓取姿态的采样,而不涉及到夹爪内部点云的提取。
144 |
145 |
146 |
147 |
148 |
149 |
150 | ## 对YCB数据集的CAD模型进行候选抓取姿态采样
151 |
152 | 1. 下载 YCB object set from [YCB Dataset](http://ycb-benchmarks.s3-website-us-east-1.amazonaws.com/),该数据集提供了物体的CAD模型和一定角度下的深度图;
153 | 2. 原代码中,将YCB的数据集放在了如下位置:
154 | ```bash
155 | cd $HOME/dataset/PointNetGPD/ycb_meshes_google/objects
156 | ```
157 |
158 | 如果你的Home盘分区不够大,希望换一个位置,可以:
159 | >```bash
160 | >cd $HOME/code/PointNetGPD/apps
161 | >vim generate-dataset-canny.py
162 | >```
163 | >修改YCB dataset 路径
164 | >```python
165 | >219 #存放CAD模型的文件夹
166 | >220 file_dir = home_dir + "/dataset/ycb_meshes_google/objects" #获取模型的路径
167 | >```
168 | >也可以修改计算结果文件的存放位置
169 | >```python
170 | >61 #将gpg得到的候选抓取文件存放起来
171 | >62 good_grasp_file_name = "./generated_grasps/{}_{}_{}".format(filename_prefix, str(object_name), str(len(good_grasp)))
172 | >```
173 |
174 | 每个物体的文件夹结构都应该如下所示:
175 | ```bash
176 | ├002_master_chef_can
177 | |└── google_512k
178 | | ├── kinbody.xml (no use)
179 | | ├── nontextured.obj
180 | | ├── nontextured.ply
181 | | ├── nontextured.sdf (generated by SDFGen)
182 | | ├── nontextured.stl
183 | | ├── textured.dae (no use)
184 | | ├── textured.mtl (no use)
185 | | ├── textured.obj (no use)
186 | | ├── textured.sdf (no use)
187 | | └── texture_map.png (no use)
188 | ├003_cracker_box
189 | └004_sugar_box
190 | ...
191 | ```
192 | 3. Install SDFGen from [GitHub](https://github.com/jeffmahler/SDFGen.git):
193 | ```bash
194 | cd code
195 | git clone https://github.com/jeffmahler/SDFGen.git
196 | cd SDFGen
197 | sudo sh install.sh
198 | ```
199 |
200 |
201 | 4. 安装python pcl library [python-pcl](https://github.com/strawlab/python-pcl),python pcl在离线训练(python3)和在线pgd(python2)时均有使用,以下要求Ubuntu18.04,PCL1.8.1(源安装在系统路径下):
202 | ```bash
203 | git clone https://github.com/lianghongzhuo/python-pcl.git
204 | pip install --upgrade pip
205 | pip install cython==0.25.2 #python2
206 | pip install cython #python3
207 | pip install numpy
208 | cd python-pcl
209 | python setup.py build_ext -i #python2和3环境中都要执行
210 | python setup.py develop #python2和3环境中都要执行
211 | ```
212 | 5. 为默认路径`$HOME/dataset/PointNetGPD/ycb_meshes_google/objects/`下的.ply文件生成.sdf 文件(放在同一文件夹下):
213 | ```bash
214 | cd $HOME/code/PointNetGPD/apps
215 | python read_file_sdf.py #anaconda3环境下python3
216 | ```
217 |
218 |
219 | 6. 为默认路径`$HOME/dataset/PointNetGPD/ycb_meshes_google/objects/`下的CAD模型使用Antipod进行候选抓取姿态采样,以及利用ForceClosure&GWS对生成抓取姿态进行打分,这部分的执行时间极长,主要花费时间在抓取采样之上:
220 | ```bash
221 | cd $HOME/code/PointNetGPD/apps
222 | python generate-dataset-canny.py [prefix] #anaconda3环境下python3
223 | ```
224 |
225 | 计算结束后将会把结果以`.npy`文件形式保存在默认的`$HOME/dataset/PointNetGPD/ycb_grasp/`路径下;这里的`[prefix]`可以根据自己的夹爪类型,添加一个标签,也可以选择不加,那么就会自动被替换成为`default`
226 |
227 | 7. 作者还给出了一个根据roboticq85夹爪模型采样好的候选grasp pose [结果文件](https://tams.informatik.uni-hamburg.de/research/datasets/PointNetGPD_grasps_dataset.zip)
228 |
229 |
230 | ## 对YCB数据集中各个视角的深度图像生成点云
231 |
232 | 1. 将下载的YCB数据集文件夹`ycb_rgbd`拷贝至如下路径
233 |
234 | ```bash
235 | cp .../ycb_rgbd $HOME/dataset/PointNetGPD/
236 | ```
237 |
238 |
239 | 2. 将默认路径`$HOME/dataset/PointNetGPD/ycb_rgbd/*/`下的深度图转换为点云数据,并放在`$HOME/dataset/PointNetGPD/ycb_rgbd/*/clouds`文件夹中。
240 |
241 | ```bash
242 | cd $HOME/code/PointNetGPD/apps/
243 | python ycb_cloud_generate.py #anaconda3 python3
244 | ```
245 |
246 |
247 | ## 准备Dataloader需要的数据文件夹
248 |
249 | 此该文件夹将会提供给PointNet的Dataloader,该Dataloader将会在训练时结合候选grasp pose&点云 提取“夹爪内部的点云”(详细解释见作者论文)
250 |
251 | 1. 进入Dataloader需要的文件夹:
252 |
253 | ```bash
254 | cd $HOME/dataset/PointNetGPD/
255 | ```
256 | 确保该文件夹下有如下文件
257 | ```
258 | ├── google2cloud.csv (Transform from google_ycb model to ycb_rgbd model)
259 | ├── google2cloud.pkl (Transform from google_ycb model to ycb_rgbd model)
260 | ├── ycb_grasp (里面就是离线Antipod采样到的候选grasp pose)
261 | ├── ycb_meshes_google (YCB dataset)
262 | └── ycb_rgbd (上面已经生成了各模型各视角点云)
263 | ```
264 |
265 | 其中,`ycb_grasp`文件夹需要手动创建为如下结构,对之前生成的候选抓取样本分成两部分一部分训练一部分验证(作者好像没说咋分),每个文件夹中都是之前`generate-dataset-canny.py`采样到的grasp pose(`.npy`)
266 |
267 | ```bash
268 | ├── ycb_grasp
269 | │ ├── test #
270 | │ └── train #(训练)
271 | ```
272 |
273 |
274 | ## 训练模型
275 |
276 | 1. Run the experiments:
277 |
278 | ```bash
279 | cd PointNetGPD
280 | ```
281 |
282 |
283 | Launch a tensorboard for monitoring
284 | ```bash
285 | tensorboard --log-dir ./assets/log --port 8080
286 | ```
287 |
288 | and run an experiment for 200 epoch
289 | ```bash
290 | python main_1v.py --epoch 200 --mode train --batch-size x (x>1) #anaconda3 python3
291 | ```
292 |
293 | File name and corresponding experiment:
294 | ```bash
295 | main_1v.py --- 1-viewed point cloud, 2 class
296 | main_1v_mc.py --- 1-viewed point cloud, 3 class
297 | main_1v_gpd.py --- 1-viewed point cloud, GPD
298 | main_fullv.py --- Full point cloud, 2 class
299 | main_fullv_mc.py --- Full point cloud, 3 class
300 | main_fullv_gpd.py --- Full point cloud, GPD
301 | ```
302 | For GPD experiments, you may change the input channel number by modifying `input_chann` in the experiment scripts(only 3 and 12 channels are available)
303 |
304 | ## 使用模型,执行抓取
305 |
306 | 需要注意的是:
307 |
308 | - 为了能够脱离机械臂实物,仅仅进行GPD的实验,同时还能够相对容易地剔除掉桌面点云;代码中选择将场景点云变换到桌面标签(ar_marker_6)坐标系中,该部分的变换处理[参看此处](https://github.com/Hymwgk/point_cloud_process)
309 | - 如果使用机械臂实物(以panda为例)所有的指令运行的窗口都需要运行`source panda_client.sh`指令确保本机ROS_MASTER指向远程工控机,如果仅仅进行gpd不实际进行抓取则不需要这样做。[参看此处](https://github.com/Hymwgk/panda_moveit_config)
310 |
311 | 1. (不实际抓取不需要)手眼标定并发布“手眼”变换关系,关于Panda手眼标定和发布步骤[参看此处](https://github.com/Hymwgk/panda_hand_eye_calibrate)
312 |
313 | ```bash
314 | roslaunch panda_hand_eye_calibrate publish_panda_eob.launch
315 | ```
316 |
317 | 2. 安装预处理包
318 |
319 | ```bash
320 | cd ~/catkin_ws/src
321 | git clone https://github.com/Hymwgk/point_cloud_process.git
322 | catkin build
323 | ```
324 |
325 | 3. 启动点云采集与预处理
326 | ```bash
327 | roslaunch kinect2_bridge kinect2_bridge.launch publish_tf:=true #启动相机
328 | roslaunch point_cloud_process get_table_top_points.launch #启动标签追踪以及点云预处理
329 | ```
330 |
331 | 4. 运行感知节点
332 |
333 | 这部分就是实际使用PointNetGPD的部分,读取预处理后桌面上的目标区域点云,基于点云进行gpg,之后将夹爪内部的点云送入pointNet中打分,并以ROS消息的形式输出good grasp
334 |
335 | ```bash
336 | cd $HOME/code/PointNetGPD/apps
337 | python kinect2grasp.py #anaconda2 python2 需要在conda2环境中也安装pytorch
338 | ```
339 | 以下是用到的参数
340 | ```bash
341 | arguments:
342 | -h, --help show this help message and exit
343 | --cuda 使用CUDA进行计算
344 | --gpu GPU 指定使用的GPU编号
345 | --load-model LOAD_MODEL 设置使用了哪个训练好的网络 (这个参数其实没有效果,被后面的model_type MODEL_TYPE覆盖了)
346 | --show_final_grasp 设置是否显示最终抓取(修改了多线程不显示的问题)
347 | --tray_grasp not finished grasp type(还没搞好)
348 | --using_mp 是否使用多线程去进行抓取采样
349 | --model_type MODEL_TYPE 从三种模型中选择使用哪个模型
350 | ```
351 | 举个栗子:
352 | ```bash
353 | python kinect2grasp.py --cuda --gpu 0 --load-model ../data/1v_500_2class_ourway2sample.model --using_mp --model_type 750
354 | ```
355 |
356 |
357 |
358 | ------
359 |
360 | **以下是使用机械臂进行真正的抓取执行需要的步骤,可以不做**
361 |
362 | 5. 安装ROS抓取消息包
363 |
364 | ```bash
365 | cd ~/catkin_ws/src
366 | git clone https://github.com/TAMS-Group/gpd_grasp_msgs.git
367 | cd ..
368 | catkin build
369 | ```
370 |
371 | 6. 获取机械臂当前状态,并准备接收生成的抓取(以franka panda为例)需提前安装[panda_go_grasp包](https://github.com/Hymwgk/panda_go_grasp),向ROS参数服务器发布一个参数,指明机械臂的当前是在移动状态还是已经返回home状态,机械臂在移动时,将暂时禁止gpd。
372 |
373 | ```bash
374 | roslaunch panda_go_grasp state_checker.launch #anaconda2 python2
375 | ```
376 |
377 | 7. 执行抓取
378 |
379 | ```bash
380 | roslaunch panda_go_grasp go_grasp.launch #anaconda2 python2
381 | ```
382 |
383 |
384 |
385 |
386 |
387 | ## 辅助工具:查看之前采样的候选grasp pose(可跳过)
388 | - Visualization grasps
389 | ```bash
390 | cd $HOME/code/PointNetGPD/apps
391 | python read_grasps_from_file.py #anaconda3 python3
392 | ```
393 |
394 | Note:
395 |
396 | - This file will visualize the grasps in `$HOME/code/PointNetGPD/PointNetGPD/data/ycb_grasp/` folder
397 |
398 | - Visualization object normals
399 | ```bash
400 | cd $HOME/code/PointNetGPD/apps
401 | python Cal_norm.py #anaconda3 python3
402 | ```
403 |
404 | This code will check the norm calculated by meshpy and pcl library.
405 |
406 |
407 | ## Citation
408 | If you found PointNetGPD useful in your research, please consider citing:
409 |
410 | ```plain
411 | @inproceedings{liang2019pointnetgpd,
412 | title={{PointNetGPD}: Detecting Grasp Configurations from Point Sets},
413 | author={Liang, Hongzhuo and Ma, Xiaojian and Li, Shuang and G{\"o}rner, Michael and Tang, Song and Fang, Bin and Sun, Fuchun and Zhang, Jianwei},
414 | booktitle={IEEE International Conference on Robotics and Automation (ICRA)},
415 | year={2019}
416 | }
417 | ```
418 |
419 | ## Acknowledgement
420 | - [gpg](https://github.com/atenpas/gpg)
421 | - [gpd](https://github.com/atenpas/gpd)
422 | - [dex-net](https://github.com/BerkeleyAutomation/dex-net)
423 | - [meshpy](https://github.com/BerkeleyAutomation/meshpy)
424 | - [SDFGen](https://github.com/christopherbatty/SDFGen)
425 | - [pyntcloud](https://github.com/daavoo/pyntcloud)
426 | - [metu-ros-pkg](https://github.com/kadiru/metu-ros-pkg)
427 | - [mayavi](https://github.com/enthought/mayavi)
428 |
429 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/__init__.py
--------------------------------------------------------------------------------
/apps/Cal_norm.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # Author : Hongzhuo Liang
4 | # E-mail : liang@informatik.uni-hamburg.de
5 | # Description:
6 | # Date : 09/06/2018 7:47 PM
7 | # File Name : Cal_norm.py
8 | import os
9 | from meshpy.obj_file import ObjFile
10 | from meshpy.sdf_file import SdfFile
11 | from dexnet.grasping import GraspableObject3D
12 | import matplotlib.pyplot as plt
13 | import numpy as np
14 | import pcl
15 | import multiprocessing
16 | import time
17 | from mayavi import mlab
18 |
19 |
20 | def show_obj(surface_points_, color='b'):
21 | if color == 'b':
22 | color_f = (0, 0, 1)
23 | elif color == 'r':
24 | color_f = (1, 0, 0)
25 | elif color == 'g':
26 | color_f = (0, 1, 0)
27 | else:
28 | color_f = (1, 1, 1)
29 | points = surface_points_
30 | mlab.points3d(points[:, 0], points[:, 1], points[:, 2], color=color_f, scale_factor=.0007)
31 |
32 |
33 | def get_file_name(file_dir_):
34 | file_list = []
35 | for root, dirs, files in os.walk(file_dir_):
36 | # print(root) # current path
37 | if root.count('/') == file_dir_.count('/')+1:
38 | file_list.append(root)
39 | # print(dirs) # all the directories in current path
40 | # print(files) # all the files in current path
41 | file_list.sort()
42 | return file_list
43 |
44 |
45 | def show_grasp_norm(grasp_bottom_center, grasp_axis):
46 | un1 = grasp_bottom_center - 0.25 * grasp_axis * 0.25
47 | un2 = grasp_bottom_center # - 0.25 * grasp_axis * 0.05
48 | mlab.plot3d([un1[0], un2[0]], [un1[1], un2[1]], [un1[2], un2[2]], color=(0, 1, 0), tube_radius=0.0005)
49 |
50 |
51 | def show_pcl_norm(grasp_bottom_center, normal_, color='r', clear=False):
52 | if clear:
53 | plt.figure()
54 | plt.clf()
55 | plt.gcf()
56 | plt.ion()
57 |
58 | ax = plt.gca(projection='3d')
59 | un1 = grasp_bottom_center + 0.5 * normal_ * 0.05
60 | ax.scatter(un1[0], un1[1], un1[2], marker='x', c=color)
61 | un2 = grasp_bottom_center
62 | ax.scatter(un2[0], un2[1], un2[2], marker='^', c='g')
63 | ax.plot([un1[0], un2[0]], [un1[1], un2[1]], [un1[2], un2[2]], 'b-', linewidth=1) # bi normal
64 |
65 |
66 | def do_job(job_i):
67 | ii = np.random.choice(all_p.shape[0])
68 | show_grasp_norm(all_p[ii], surface_normal[ii])
69 | print("done job", job_i, ii)
70 |
71 |
72 | if __name__ == '__main__':
73 | home_dir = os.environ['HOME']
74 | file_dir = home_dir + "/dataset/ycb_meshes_google/objects"
75 | mlab.figure(bgcolor=(1.0, 1.0, 1.0), fgcolor=(0.7, 0.7, 0.7))
76 | file_list_all = get_file_name(file_dir)
77 | object_numbers = file_list_all.__len__()
78 | i = 0 # index of objects to define which object to show
79 | if os.path.exists(str(file_list_all[i]) + "/google_512k/nontextured.obj"):
80 | of = ObjFile(str(file_list_all[i]) + "/google_512k/nontextured.obj")
81 | sf = SdfFile(str(file_list_all[i]) + "/google_512k/nontextured.sdf")
82 |
83 | else:
84 | print("can't find any obj or sdf file!")
85 | raise NameError("can't find any obj or sdf file!")
86 | mesh = of.read()
87 | sdf = sf.read()
88 | graspable = GraspableObject3D(sdf, mesh)
89 | print("Log: opened object")
90 | begin_time = time.time()
91 | surface_points, _ = graspable.sdf.surface_points(grid_basis=False)
92 | all_p = surface_points
93 | method = "voxel"
94 | if method == "random":
95 | surface_points = surface_points[np.random.choice(surface_points.shape[0], 1000, replace=False)]
96 | surface_normal = []
97 | elif method == "voxel":
98 | surface_points = surface_points.astype(np.float32)
99 | p = pcl.PointCloud(surface_points)
100 | voxel = p.make_voxel_grid_filter()
101 | voxel.set_leaf_size(*([graspable.sdf.resolution * 5] * 3))
102 | surface_points = voxel.filter().to_array()
103 |
104 | # cal normal with pcl
105 | use_voxel = False # use voxelized point to get norm is not accurate
106 | if use_voxel:
107 | norm = voxel.filter().make_NormalEstimation()
108 | else:
109 | norm = p.make_NormalEstimation()
110 |
111 | norm.set_KSearch(10)
112 | normals = norm.compute()
113 | surface_normal = normals.to_array()
114 | surface_normal = surface_normal[:, 0:3]
115 | surface_gg = norm.compute().to_array()[:, 3]
116 | # from IPython import embed;embed()
117 | use_pcl = True
118 | if use_pcl:
119 | # for ii in range(surface_normal[0]):
120 | show_grasp_norm(all_p[0], surface_normal[0])
121 | # FIXME: multi processing is not working, Why?
122 | # mayavi do not support
123 | # cores = multiprocessing.cpu_count()
124 | # pool = multiprocessing.Pool(processes=cores)
125 | # pool.map(multiprocessing_jobs.do_job, range(2000))
126 | sample_points = 500
127 | for _ in range(sample_points):
128 | do_job(_)
129 | mlab.pipeline.surface(mlab.pipeline.open(str(file_list_all[i]) + "/google_512k/nontextured.ply")
130 | , opacity=1)
131 | mlab.show()
132 | print(time.time() - begin_time)
133 | # show_obj(all_p)
134 | else:
135 | raise ValueError("No such method", method)
136 |
137 | use_meshpy = False
138 | if use_meshpy:
139 | normal = []
140 | # show norm
141 | surface_points = surface_points[:100]
142 | for ind in range(len(surface_points)):
143 | # use meshpy cal norm:
144 | p_grid = graspable.sdf.transform_pt_obj_to_grid(surface_points[ind])
145 | normal_tmp = graspable.sdf.surface_normal(p_grid)
146 | # use py pcl cal norm, Wrong.
147 | # normal_tmp = surface_normal[ind]
148 | if normal_tmp is not None:
149 | normal.append(normal_tmp)
150 | show_grasp_norm(surface_points[ind], normal_tmp)
151 | else:
152 | print(len(normal))
153 | mlab.pipeline.surface(mlab.pipeline.open(str(file_list_all[i]) + "/google_512k/nontextured.ply"))
154 | mlab.show()
155 |
--------------------------------------------------------------------------------
/apps/generate-dataset-canny.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # python3
4 | # Author : Hongzhuo Liang
5 | # E-mail : liang@informatik.uni-hamburg.de
6 | # Description:
7 | # Date : 20/05/2018 2:45 PM
8 | # File Name : generate-dataset-canny.py
9 |
10 | import numpy as np
11 | import sys
12 | import pickle
13 | from dexnet.grasping.quality import PointGraspMetrics3D
14 | from dexnet.grasping import GaussianGraspSampler, AntipodalGraspSampler, UniformGraspSampler, GpgGraspSampler
15 | from dexnet.grasping import RobotGripper, GraspableObject3D, GraspQualityConfigFactory, PointGraspSampler
16 | import dexnet
17 | from autolab_core import YamlConfig
18 | from meshpy.obj_file import ObjFile
19 | from meshpy.sdf_file import SdfFile
20 | import os
21 |
22 | import multiprocessing
23 | import matplotlib.pyplot as plt
24 | plt.switch_backend('agg') # for the convenient of run on remote computer
25 |
26 | #sys.path()
27 |
28 | #输入文件夹地址,返回一个列表,其中保存的是文件夹中的文件名称
29 | def get_file_name(file_dir_):
30 | file_list = []
31 | for root, dirs, files in os.walk(file_dir_):
32 | #将下一层子文件夹的地址保存到 file_list 中
33 | if root.count('/') == file_dir_.count('/') + 1:
34 | file_list.append(root)
35 | #排序
36 | file_list.sort()
37 | return file_list
38 | """
39 | \brief: 创建多线程,多线程调用worker函数,处理指定的模型,
40 | """
41 | def do_job(i): #处理函数 处理第i个模型
42 | #根据id号,截取对应的目标模型名称
43 | object_name = file_list_all[i].split('/')[-1]
44 |
45 | """
46 | good_grasp = multiprocessing.Manager().list() #列表
47 |
48 | # grasp_amount per friction: 20*40 共开50个子线程做这件事
49 | p_set = [multiprocessing.Process(target=worker, args=(i, 100, 20, good_grasp)) for _ in
50 | range(50)]
51 |
52 | #开始多线程,并等待结束
53 | [p.start() for p in p_set]
54 | [p.join() for p in p_set]
55 | good_grasp = list(good_grasp)
56 | """
57 | #创建空列表
58 | good_grasp=[]
59 | #执行worker,将采样的抓取结果放到good_grasp中
60 | worker(i, 100, 20, good_grasp)
61 |
62 | #对CAD模型进行Antipod采样并筛选得到的候选抓取
63 | good_grasp_file_name = os.environ['HOME']+"/dataset/PointNetGPD/ycb_grasp/{}_{}_{}".format(filename_prefix, str(object_name), str(len(good_grasp)))
64 |
65 | #创建一个pickle文件,将good_grasp保存起来
66 | with open(good_grasp_file_name + '.pickle', 'wb') as f:
67 | pickle.dump(good_grasp, f)
68 |
69 | tmp = []
70 | for grasp in good_grasp:
71 | grasp_config = grasp[0].configuration
72 | score_friction = grasp[1]
73 | score_canny = grasp[2]
74 | tmp.append(np.concatenate([grasp_config, [score_friction, score_canny]]))
75 | np.save(good_grasp_file_name + '.npy', np.array(tmp))
76 | print("finished job ", object_name)
77 |
78 | def do_jobs(i):
79 | print("tesk id", i)
80 |
81 | def worker(i, sample_nums, grasp_amount, good_grasp): #主要是抓取采样器以及打分 100 20
82 | """
83 | brief: 对制定的模型,利用随机采样算法,进行抓取姿态的检测和打分
84 | param [in] i 处理第i个mesh模型
85 | param [in] sample_nums 每个对象模型返回的目标抓取数量
86 | param [in] grasp_amount
87 | """
88 |
89 | #截取目标对象名称
90 | object_name = file_list_all[i][len(home_dir) + 35:]
91 | print('a worker of task {} start'.format(object_name))
92 |
93 | #读取初始配置文件,读取并在内存中复制了一份
94 | yaml_config = YamlConfig(home_dir + "/code/dex-net/test/config.yaml")
95 | #设置夹名称
96 | gripper_name = 'panda'
97 | #根据设置的夹爪名称加载夹爪配置
98 | gripper = RobotGripper.load(gripper_name, home_dir + "/code/dex-net/data/grippers")
99 | #设置抓取采样的方法
100 | grasp_sample_method = "antipodal"
101 | if grasp_sample_method == "uniform":
102 | ags = UniformGraspSampler(gripper, yaml_config)
103 | elif grasp_sample_method == "gaussian":
104 | ags = GaussianGraspSampler(gripper, yaml_config)
105 | elif grasp_sample_method == "antipodal":
106 | #使用对映点抓取,输入夹爪与配置文件
107 | ags = AntipodalGraspSampler(gripper, yaml_config)
108 | elif grasp_sample_method == "gpg":
109 | ags = GpgGraspSampler(gripper, yaml_config)
110 | elif grasp_sample_method == "point":
111 | ags = PointGraspSampler(gripper, yaml_config)
112 | else:
113 | raise NameError("Can't support this sampler")
114 | print("Log: do job", i)
115 | #设置obj模型文件与sdf文件路径
116 | if os.path.exists(str(file_list_all[i]) + "/google_512k/nontextured.obj") and os.path.exists(str(file_list_all[i]) + "/google_512k/nontextured.sdf"):
117 | of = ObjFile(str(file_list_all[i]) + "/google_512k/nontextured.obj")
118 | sf = SdfFile(str(file_list_all[i]) + "/google_512k/nontextured.sdf")
119 | else:
120 | print("can't find any obj or sdf file!")
121 | raise NameError("can't find any obj or sdf file!")
122 |
123 | #根据路径读取模型与sdf文件
124 | mesh = of.read()
125 | sdf = sf.read()
126 | #构建抓取模型类
127 | obj = GraspableObject3D(sdf, mesh)
128 | print("Log: opened object", i + 1, object_name)
129 |
130 | #########################################
131 | #设置
132 | force_closure_quality_config = {} #设置力闭合 字典
133 | canny_quality_config = {}
134 | #生成一个起点是2.0终点是0.75 步长为-0.4 (递减)的等距数列fc_list_sub1 (2.0, 0.75, -0.4)
135 | fc_list_sub1 = np.arange(2.0, 0.75, -0.3)
136 | #生成一个起点是0.5终点是0.36 步长为-0.05的等距数列fc_list_sub2 (0.5, 0.36, -0.05)
137 | fc_list_sub2 = np.arange(0.5, 0.36, -0.1)
138 |
139 | #将上面两个向量接起来,变成一个长条向量,使用不同的步长,目的是为了在更小摩擦力的时候,有更多的分辨率
140 | fc_list = np.concatenate([fc_list_sub1, fc_list_sub2])
141 | print("判断摩擦系数")
142 | print(fc_list)
143 | for value_fc in fc_list:
144 | #对value_fc保留2位小数,四舍五入
145 | value_fc = round(value_fc, 2)
146 | #更改内存中配置中的摩擦系数,而没有修改硬盘中的yaml文件
147 | yaml_config['metrics']['force_closure']['friction_coef'] = value_fc
148 | yaml_config['metrics']['robust_ferrari_canny']['friction_coef'] = value_fc
149 | #把每个摩擦力值当成键,
150 | force_closure_quality_config[value_fc] = GraspQualityConfigFactory.create_config(
151 | yaml_config['metrics']['force_closure'])
152 | canny_quality_config[value_fc] = GraspQualityConfigFactory.create_config(
153 | yaml_config['metrics']['robust_ferrari_canny'])
154 |
155 | #####################准备开始采样############################
156 | #填充一个与摩擦数量相同的数组,每个对应的元素都是0
157 | good_count_perfect = np.zeros(len(fc_list))
158 | count = 0
159 | #设置每个摩擦值需要计算的最少抓取数量 (根据指定输入值20)
160 | minimum_grasp_per_fc = grasp_amount
161 | #如果每个摩擦系数下,有效的抓取(满足力闭合或者其他判断标准)小于要求值,就一直循环查找,直到所有摩擦系数条件下至少都存在20个有效抓取
162 | while np.sum(good_count_perfect < minimum_grasp_per_fc) != 0:
163 | #开始使用antipodes sample获得对映随机抓取,此时并不判断是否满足力闭合,只是先采集满足夹爪条件的抓取
164 | #如果一轮多次随机采样之后,发现无法获得指定数量的随机抓取,就会重复迭代计算3次,之后放弃,并把已经找到的抓取返回来
165 | grasps = ags.generate_grasps(obj, target_num_grasps=sample_nums, grasp_gen_mult=10,max_iter=10,
166 | vis=False, random_approach_angle=True)
167 | count += len(grasps)
168 | #循环对每个采样抓取进行判断
169 | for j in grasps:
170 | tmp, is_force_closure = False, False
171 | #循环对某个采样抓取应用不同的抓取摩擦系数,判断是否是力闭合
172 | for ind_, value_fc in enumerate(fc_list):
173 | value_fc = round(value_fc, 2)
174 | tmp = is_force_closure
175 | #判断在当前给定的摩擦系数下,抓取是否是力闭合的
176 | is_force_closure = PointGraspMetrics3D.grasp_quality(j, obj,
177 | force_closure_quality_config[value_fc], vis=False)
178 | #假设当前,1号摩擦力为1.6 抓取不是力闭合的,但是上一个0号摩擦系数2.0 条件下抓取是力闭合的
179 | if tmp and not is_force_closure:
180 | #当0号2.0摩擦系数条件下采样的good抓取数量还不足指定的最低数量20
181 | if good_count_perfect[ind_ - 1] < minimum_grasp_per_fc:
182 | #以0号摩擦系数作为边界
183 | canny_quality = PointGraspMetrics3D.grasp_quality(j, obj,
184 | canny_quality_config[
185 | round(fc_list[ind_ - 1], 2)],
186 | vis=False)
187 | good_grasp.append((j, round(fc_list[ind_ - 1], 2), canny_quality))
188 | #在0号系数的good抓取下计数加1
189 | good_count_perfect[ind_ - 1] += 1
190 | #当前抓取j的边界摩擦系数找到了,退出摩擦循环,判断下一个抓取
191 | break
192 | #如果当前1号摩擦系数1.6条件下,该抓取j本身就是力闭合的,且摩擦系数是列表中的最后一个(所有的摩擦系数都判断完了)
193 | elif is_force_closure and value_fc == fc_list[-1]:
194 | if good_count_perfect[ind_] < minimum_grasp_per_fc:
195 | #以当前摩擦系数作为边界
196 | canny_quality = PointGraspMetrics3D.grasp_quality(j, obj,
197 | canny_quality_config[value_fc], vis=False)
198 | good_grasp.append((j, value_fc, canny_quality))
199 | good_count_perfect[ind_] += 1
200 | #当前抓取j关于当前摩擦系数1.6判断完毕,而且满足所有的摩擦系数,就换到下一个摩擦系数
201 | break
202 | print('Object:{} GoodGrasp:{}'.format(object_name, good_count_perfect)) #判断
203 |
204 | object_name_len = len(object_name)
205 | object_name_ = str(object_name) + " " * (25 - object_name_len)
206 | if count == 0:
207 | good_grasp_rate = 0
208 | else:
209 | good_grasp_rate = len(good_grasp) / count
210 | print('Gripper:{} Object:{} Rate:{:.4f} {}/{}'.
211 | format(gripper_name, object_name_, good_grasp_rate, len(good_grasp), count))
212 |
213 |
214 | if __name__ == '__main__':
215 | if len(sys.argv) > 1:
216 | filename_prefix = sys.argv[1]
217 | else:
218 | filename_prefix = "default"
219 | home_dir = os.environ['HOME']
220 | #存放CAD模型的文件夹
221 | file_dir = home_dir + "/dataset/PointNetGPD/ycb_meshes_google/objects" #获取模型的路径
222 | file_list_all = get_file_name(file_dir) #返回一个列表,包含物体
223 | object_numbers = file_list_all.__len__() #获取文件夹中物体数量
224 |
225 | job_list = np.arange(object_numbers) #返回一个长度为object_numbers的元组 0 1 2 3 ...
226 | job_list = list(job_list) #转换为列表
227 | #设置同时对几个模型进行采样
228 | pool_size = 40
229 | assert (pool_size <= len(job_list))
230 | # Initialize pool
231 | pool = [] #创建列表
232 |
233 | for _ in range(pool_size): #想多线程处理多个模型,但是实际上本代码每次只处理一个
234 | job_i = job_list.pop(0) #删除掉第0号值,并把job_i赋予0号值
235 | pool.append(multiprocessing.Process(target=do_job, args=(job_i,))) #在pool末尾添加元素
236 | [p.start() for p in pool] #启动多线程
237 | # refill
238 |
239 | while len(job_list) > 0: #如果有些没处理完
240 | for ind, p in enumerate(pool):
241 | if not p.is_alive():
242 | pool.pop(ind)
243 | job_i = job_list.pop(0)
244 | p = multiprocessing.Process(target=do_job, args=(job_i,))
245 | p.start()
246 | pool.append(p)
247 | break
248 | print('All job done.')
249 |
--------------------------------------------------------------------------------
/apps/kinect2grasp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # Author : Hongzhuo Liang
4 | # E-mail : liang@informatik.uni-hamburg.de
5 | # Description:
6 | # Date : 05/08/2018 6:04 PM
7 | # File Name : kinect2grasp.py
8 |
9 | import torch
10 |
11 | import rospy
12 | from sensor_msgs.msg import PointCloud2
13 | from visualization_msgs.msg import MarkerArray
14 | from visualization_msgs.msg import Marker
15 | import tf
16 | import moveit_commander
17 | import numpy as np
18 | #自定义pointcloud包
19 | import pointclouds
20 | #from pcl import PointCloud
21 | #自定义
22 | import voxelgrid
23 |
24 |
25 | import pcl
26 | #容易报错无法导入ruamel.yaml,需要使用命令 conda install ruamel.yaml 来安装,不能使用pip安装
27 | from autolab_core import YamlConfig
28 | from dexnet.grasping import RobotGripper
29 | from dexnet.grasping import GpgGraspSamplerPcl
30 | import os
31 | from pyquaternion import Quaternion
32 | import sys
33 | from os import path
34 | import time
35 | from scipy.stats import mode
36 | import multiprocessing as mp
37 | try:
38 | from gpd_grasp_msgs.msg import GraspConfig
39 | from gpd_grasp_msgs.msg import GraspConfigList
40 | except ImportError:
41 | print("Please install grasp msgs from https://github.com/TAMS-Group/gpd_grasp_msgs in your ROS workspace")
42 | exit()
43 |
44 | try:
45 | from mayavi import mlab
46 | except ImportError:
47 | print("Can not import mayavi")
48 | mlab = None
49 | #sys.path.append(path.dirname(path.dirname(path.dirname(path.abspath("__file__")))))
50 | sys.path.append(os.environ['HOME'] + "/code/PointNetGPD")
51 |
52 | #把读取网络模型,外部命令参数的指令写在了main_test中
53 | from main_test import test_network, model, args
54 |
55 | # global config:全局的配置文件
56 | yaml_config = YamlConfig(os.environ['HOME'] + "/code/dex-net/test/config.yaml")
57 | gripper_name = 'panda'
58 | #加载夹爪
59 | gripper = RobotGripper.load(gripper_name, os.environ['HOME'] + "/code/dex-net/data/grippers")
60 | ags = GpgGraspSamplerPcl(gripper, yaml_config)
61 |
62 | value_fc = 0.4 # no use, set a random number
63 | num_grasps_single_worker = 20
64 | #如果使用多线程,每个线程采样num_grasps_p_worker个抓取
65 | num_grasps_p_worker=6
66 | #如果使用多线程,将一共使用num_workers个线程
67 | num_workers = 20
68 | max_num_samples = 150
69 | n_voxel = 500
70 |
71 | #输入pointnet的最小点数
72 | minimal_points_send_to_point_net = 20
73 | marker_life_time = 8
74 |
75 | show_bad_grasp = False
76 | save_grasp_related_file = False
77 |
78 | using_mp = args.using_mp
79 | show_final_grasp = args.show_final_grasp
80 |
81 |
82 | tray_grasp = args.tray_grasp
83 | single_obj_testing = False # if True, it will wait for input before get pointcloud
84 |
85 | #指定输入点云的点数 number of points put into neural network
86 | if args.model_type == "500": # minimal points send for training
87 | input_points_num = 500
88 | elif args.model_type == "750":
89 | input_points_num = 750
90 | elif args.model_type == "3class":
91 | input_points_num = 500
92 | else:
93 | input_points_num = 0
94 |
95 | #去除支撑桌面点
96 | def remove_table_points(points_voxel_, vis=False):
97 | xy_unique = np.unique(points_voxel_[:, 0:2], axis=0)
98 | new_points_voxel_ = points_voxel_
99 | pre_del = np.zeros([1])
100 | for i in range(len(xy_unique)):
101 | tmp = []
102 | for j in range(len(points_voxel_)):
103 | if np.array_equal(points_voxel_[j, 0:2], xy_unique[i]):
104 | tmp.append(j)
105 | print(len(tmp))
106 | if len(tmp) < 3:
107 | tmp = np.array(tmp)
108 | pre_del = np.hstack([pre_del, tmp])
109 | if len(pre_del) != 1:
110 | pre_del = pre_del[1:]
111 | new_points_voxel_ = np.delete(points_voxel_, pre_del, 0)
112 | print("Success delete [[ {} ]] points from the table!".format(len(points_voxel_) - len(new_points_voxel_)))
113 |
114 | if vis:
115 | p = points_voxel_
116 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.002, color=(1, 0, 0))
117 | p = new_points_voxel_
118 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.002, color=(0, 0, 1))
119 | mlab.points3d(0, 0, 0, scale_factor=0.01, color=(0, 1, 0)) # plot 0 point
120 | mlab.show()
121 | return new_points_voxel_
122 |
123 |
124 | def remove_white_pixel(msg, points_, vis=False):
125 | points_with_c_ = pointclouds.pointcloud2_to_array(msg)
126 | points_with_c_ = pointclouds.split_rgb_field(points_with_c_)
127 | r = np.asarray(points_with_c_['r'], dtype=np.uint32)
128 | g = np.asarray(points_with_c_['g'], dtype=np.uint32)
129 | b = np.asarray(points_with_c_['b'], dtype=np.uint32)
130 | rgb_colors = np.vstack([r, g, b]).T
131 | # rgb = rgb_colors.astype(np.float) / 255
132 | ind_good_points_ = np.sum(rgb_colors[:] < 210, axis=-1) == 3
133 | ind_good_points_ = np.where(ind_good_points_ == 1)[0]
134 | new_points_ = points_[ind_good_points_]
135 | if vis:
136 | p = points_
137 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.002, color=(1, 0, 0))
138 | p = new_points_
139 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.002, color=(0, 0, 1))
140 | mlab.points3d(0, 0, 0, scale_factor=0.01, color=(0, 1, 0)) # plot 0 point
141 | mlab.show()
142 | return new_points_
143 |
144 |
145 | def get_voxel_fun(points_, n):
146 | get_voxel = voxelgrid.VoxelGrid(points_, n_x=n, n_y=n, n_z=n)
147 | get_voxel.compute()
148 | points_voxel_ = get_voxel.voxel_centers[get_voxel.voxel_n]
149 | points_voxel_ = np.unique(points_voxel_, axis=0)
150 | return points_voxel_
151 |
152 |
153 | def cal_grasp(msg, cam_pos_):
154 | """根据在线采集的点云计算候选的抓取姿态
155 | """
156 | #把pointcloud2类型的消息点云,转换为ndarray points_
157 | points_ = pointclouds.pointcloud2_to_xyz_array(msg)
158 | #复制一份points_ ndarray对象,并将所有的点坐标转换为float32类型
159 | points_ = points_.astype(np.float32)
160 |
161 | remove_white = False
162 | if remove_white:
163 | points_ = remove_white_pixel(msg, points_, vis=True)
164 | # begin voxel points
165 | n = n_voxel # parameter related to voxel method
166 | # gpg improvements, highlights: flexible n parameter for voxelizing.
167 | #这一句话执行的时候,不能打开虚拟机,否则容易卡住
168 | points_voxel_ = get_voxel_fun(points_, n)
169 |
170 | #当点云点数小于2000时
171 | if len(points_) < 2000: # should be a parameter
172 | while len(points_voxel_) < len(points_)-15:
173 | points_voxel_ = get_voxel_fun(points_, n)
174 | n = n + 100
175 | rospy.loginfo("the voxel has {} points, we want get {} points".format(len(points_voxel_), len(points_)))
176 |
177 | rospy.loginfo("the voxel has {} points, we want get {} points".format(len(points_voxel_), len(points_)))
178 | #
179 | points_ = points_voxel_
180 | remove_points = False
181 | #是否剔除支撑平面
182 | if remove_points:
183 | points_ = remove_table_points(points_, vis=True)
184 | #重新构造经过“降采样”的点云
185 | point_cloud = pcl.PointCloud(points_)
186 |
187 | print(len(points_))
188 | #构造法向量估计对象
189 | norm = point_cloud.make_NormalEstimation()
190 | tree=point_cloud.make_kdtree()
191 | norm.set_SearchMethod(tree)
192 | #以周边30个点作为法向量计算点
193 | norm.set_KSearch(10) # critical parameter when calculating the norms
194 | normals = norm.compute()
195 |
196 |
197 | #将点云法向量转换为ndarry类型
198 | surface_normal = normals.to_array()
199 |
200 | surface_normal = surface_normal[:, 0:3]
201 |
202 | #每个点到 相机位置(无姿态)的向量 但是,感觉是相机到点的向量
203 | vector_p2cam = cam_pos_ - points_
204 | #print(vector_p2cam)
205 | #print(cam_pos_)
206 |
207 | """
208 | np.linalg.norm(vector_p2cam, axis=1) 默认求2范数,axis=1 代表按行向量处理,求多个行向量的2范数(求模)
209 | np.linalg.norm(vector_p2cam, axis=1).reshape(-1, 1) 将其调整为m行 1列
210 |
211 | 整句话的含义是,将vector_p2cam归一化,单位化
212 | """
213 | vector_p2cam = vector_p2cam / np.linalg.norm(vector_p2cam, axis=1).reshape(-1, 1)
214 |
215 |
216 |
217 | #将表面法相与表面法相(都是单位向量)点乘,以备后面计算向量夹角
218 | tmp = np.dot(vector_p2cam, surface_normal.T).diagonal()
219 | #print(vector_p2cam)
220 | #print(surface_normal.T)
221 | #print(tmp)
222 |
223 | """
224 | np.clip(tmp, -1.0, 1.0) 截取函数,将tmp中的值,都限制在-1.0到1.0之间,大于1的变成1,小于-1的记为-1
225 | np.arccos() 求解反余弦,求夹角
226 | """
227 | angel = np.arccos(np.clip(tmp, -1.0, 1.0))
228 | #print(angel)
229 |
230 | #找到与视角向量夹角大于90度的角(认为法向量计算错误)
231 | wrong_dir_norm = np.where(angel > np.pi * 0.5)[0]
232 | #print(np.where(angel > np.pi * 0.5))
233 | #print(wrong_dir_norm)
234 | #print(len(wrong_dir_norm))
235 |
236 | #创建一个len(angel)行,3列的ndarry对象
237 | tmp = np.ones([len(angel), 3])
238 | #将法向量错误的行的元素都改写为-1
239 | tmp[wrong_dir_norm, :] = -1
240 | #与表面法相元素对元素相乘,作用是将"错误的"法向量的方向 扭转过来
241 | surface_normal = surface_normal * tmp
242 | #选取桌子以上2cm处的点作为检测点
243 | select_point_above_table = 0.020
244 | #modify of gpg: make it as a parameter. avoid select points near the table.
245 | #查看每个点的z方向,如果它们的点z轴方向的值大于select_point_above_table,就把他们抽出来
246 | points_for_sample = points_[np.where(points_[:, 2] > select_point_above_table)[0] ]
247 | print(len(points_for_sample))
248 | if len(points_for_sample) == 0:
249 | rospy.loginfo("Can not seltect point, maybe the point cloud is too low?")
250 | return [], points_, surface_normal
251 | yaml_config['metrics']['robust_ferrari_canny']['friction_coef'] = value_fc
252 |
253 | grasps_together_ = []
254 |
255 | if rospy.get_param("/robot_at_home") == "false":
256 | robot_at_home = False
257 | else:
258 | robot_at_home = True
259 |
260 | if not robot_at_home:
261 | rospy.loginfo("Robot is moving, waiting the robot go home.")
262 | elif not using_mp:
263 | rospy.loginfo("Begin cal grasps using single thread, slow!")
264 | grasps_together_ = ags.sample_grasps(point_cloud, points_for_sample, surface_normal, num_grasps_single_worker,
265 | max_num_samples=max_num_samples, show_final_grasp=show_final_grasp)
266 | else:
267 | # begin parallel grasp:
268 | rospy.loginfo("Begin cal grasps using parallel!")
269 |
270 | def grasp_task(num_grasps_, ags_, queue_):
271 | ret = ags_.sample_grasps(point_cloud, points_for_sample, surface_normal, num_grasps_,
272 | max_num_samples=max_num_samples, show_final_grasp=False)
273 | queue_.put(ret)
274 |
275 | queue = mp.Queue()
276 |
277 | #num_grasps_p_worker = int(num_grasps/num_workers)
278 | workers = [mp.Process(target=grasp_task, args=(num_grasps_p_worker, ags, queue)) for _ in range(num_workers)]
279 | [i.start() for i in workers]
280 |
281 |
282 | grasps_together_ = []
283 | for i in range(num_workers):
284 | grasps_together_ = grasps_together_ + queue.get()
285 | rospy.loginfo("Finish mp processing!")
286 |
287 | if show_final_grasp and using_mp:
288 | ags.show_all_grasps(points_, grasps_together_)
289 | ags.show_points(points_, scale_factor=0.002)
290 | mlab.show()
291 |
292 |
293 | rospy.loginfo("Grasp sampler finish, generated {} grasps.".format(len(grasps_together_)))
294 | #返回抓取 场景的点 以及点云的表面法向量
295 | return grasps_together_, points_, surface_normal
296 |
297 |
298 | #检查碰撞
299 | def check_collision_square(grasp_bottom_center, approach_normal, binormal,
300 | minor_pc, points_, p, way="p_open"):
301 | #抓取坐标系 轴单位化
302 | approach_normal = approach_normal.reshape(1, 3)
303 | approach_normal = approach_normal / np.linalg.norm(approach_normal)
304 | binormal = binormal.reshape(1, 3)
305 | binormal = binormal / np.linalg.norm(binormal)
306 | minor_pc = minor_pc.reshape(1, 3)
307 | minor_pc = minor_pc / np.linalg.norm(minor_pc)
308 | #构建旋转矩阵
309 | matrix_ = np.hstack([approach_normal.T, binormal.T, minor_pc.T])
310 | #正交矩阵的逆,就是它的转置
311 | grasp_matrix = matrix_.T
312 | #center=grasp_bottom_center+approach_normal*ags.gripper.hand_depth
313 | points_ = points_ - grasp_bottom_center.reshape(1, 3)
314 | #points_ = points_ - center.reshape(1, 3)
315 |
316 | tmp = np.dot(grasp_matrix, points_.T)
317 | points_g = tmp.T
318 |
319 | #选择是否使用与数据集采样相同的方式来采集点云
320 | use_dataset_py = False
321 | if not use_dataset_py:
322 | if way == "p_open":
323 | s1, s2, s4, s8 = p[1], p[2], p[4], p[8]
324 | elif way == "p_left":
325 | s1, s2, s4, s8 = p[9], p[1], p[10], p[12]
326 | elif way == "p_right":
327 | s1, s2, s4, s8 = p[2], p[13], p[3], p[7]
328 | elif way == "p_bottom":
329 | s1, s2, s4, s8 = p[11], p[15], p[12], p[20]
330 | else:
331 | raise ValueError('No way!')
332 | a1 = s1[1] < points_g[:, 1]
333 | a2 = s2[1] > points_g[:, 1]
334 | a3 = s1[2] > points_g[:, 2]
335 | a4 = s4[2] < points_g[:, 2]
336 | a5 = s4[0] > points_g[:, 0]
337 | a6 = s8[0] < points_g[:, 0]
338 |
339 | a = np.vstack([a1, a2, a3, a4, a5, a6])
340 | points_in_area = np.where(np.sum(a, axis=0) == len(a))[0]
341 | if len(points_in_area) == 0:
342 | has_p = False
343 | else:
344 | has_p = True
345 | # for the way of pointGPD/dataset.py:
346 | else:
347 | width = ags.gripper.hand_outer_diameter - 2 * ags.gripper.finger_width
348 | x_limit = ags.gripper.hand_depth
349 | z_limit = width / 4
350 | y_limit = width / 2
351 | x1 = points_g[:, 0] > 0
352 | x2 = points_g[:, 0] < x_limit
353 | y1 = points_g[:, 1] > -y_limit
354 | y2 = points_g[:, 1] < y_limit
355 | z1 = points_g[:, 2] > -z_limit
356 | z2 = points_g[:, 2] < z_limit
357 | a = np.vstack([x1, x2, y1, y2, z1, z2])
358 | points_in_area = np.where(np.sum(a, axis=0) == len(a))[0]
359 | if len(points_in_area) == 0:
360 | has_p = False
361 | else:
362 | has_p = True
363 |
364 | vis = False
365 | if vis:
366 | p = points_g
367 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.002, color=(0, 0, 1))
368 | p = points_g[points_in_area]
369 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.002, color=(1, 0, 0))
370 | p = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
371 | mlab.points3d(p[:, 0], p[:, 1], p[:, 2], scale_factor=0.005, color=(0, 1, 0))
372 | mlab.show()
373 |
374 | return has_p, points_in_area, points_g
375 |
376 |
377 | def collect_pc(grasp_, pc):
378 | """
379 | grasp_bottom_center, normal, major_pc, minor_pc
380 | grasp_是一个list形式的数据
381 | """
382 | #获取抓取的数量
383 | grasp_num = len(grasp_)
384 | #将抓取转换为ndarry的形式,其实本身也就是
385 | grasp_ = np.array(grasp_)
386 | #print(grasp_)
387 |
388 | #变成n个,5行3列的 矩阵, 每个矩阵块,都是一个候选抓取坐标系
389 | grasp_ = grasp_.reshape(-1, 5, 3) # prevent to have grasp that only have number 1
390 | #print(grasp_)
391 |
392 | #使用每个块的第五行数据,作为抓取中心点,因为,第5个是修正后的夹爪姿态中心
393 | grasp_bottom_center = grasp_[:,4, :]
394 | approach_normal = grasp_[:, 1,:]
395 | binormal = grasp_[:, 2,:]
396 | minor_pc = grasp_[:, 3,:]
397 |
398 | in_ind_ = []
399 | in_ind_points_ = []
400 | #获取夹爪角点
401 | p = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
402 |
403 | for i_ in range(grasp_num):
404 | #通过实验发现,这个函数并不是只返回了夹爪open区域内部的点,而是"扩大"了一些,
405 | # 估计是为了返回足够多的点
406 | #但是,问题是这需要与训练时候的样本点相对应啊,如果训练的时候,就没有"扩大",那网络的打分会高么?
407 | #所以需要去看训练部分的采样部分两点
408 | # 1.生成的样本点云是否是以固定夹爪为参考系的?
409 | # 2.训练时,采样点的提取部分,是否进行了"扩大"?
410 | has_p, in_ind_tmp, points_g = check_collision_square(grasp_bottom_center[i_], approach_normal[i_],
411 | binormal[i_], minor_pc[i_], pc, p)
412 | #把索引添加进list中保存
413 | in_ind_.append(in_ind_tmp)
414 | #从下面这句话可以看出
415 | # 这些返回的夹爪内部点,是已经旋转过后的点,而不是原始的点,为什么?
416 | #是因为,网络输入的点要求是以固定夹爪为参考系的么?
417 | #猜测是的,为什么?因为,这样子的话,就可以在传入网络点云的同时,
418 | # 相当于也传入了夹爪的姿态
419 |
420 |
421 |
422 | #这一点,他想把grasp_g中的点坐标也都保存下来,in_ind_points_是一个list
423 | in_ind_points_.append(points_g[in_ind_tmp])
424 |
425 |
426 | #显示出截取的夹爪内部区域的点云(红色)&夹爪&整体点云
427 | if 0:
428 | #p = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
429 | ags.show_grasp_3d(p)
430 | ags.show_points(points_g)
431 | ags.show_points(in_ind_points_[i_],color='r',scale_factor=0.005)
432 | #table_points = np.array([[-1, 1, 0], [1, 1, 0], [1, -1, 0], [-1, -1, 0]]) * 0.5
433 | #triangles = [(1, 2, 3), (0, 1, 3)]
434 | #mlab.triangular_mesh(table_points[:, 0], table_points[:, 1], table_points[:, 2],
435 | #triangles, color=(0.8, 0.8, 0.8), opacity=0.5)
436 | mlab.show()
437 |
438 | return in_ind_, in_ind_points_
439 |
440 |
441 | def show_marker(marker_array_, pos_, ori_, scale_, color_, lifetime_):
442 | """显示标注物体
443 | """
444 | marker_ = Marker()
445 | marker_.header.frame_id = "/ar_marker_6"
446 | # marker_.header.stamp = rospy.Time.now()
447 | marker_.type = marker_.CUBE
448 | marker_.action = marker_.ADD
449 |
450 | marker_.pose.position.x = pos_[0]
451 | marker_.pose.position.y = pos_[1]
452 | marker_.pose.position.z = pos_[2]
453 | marker_.pose.orientation.x = ori_[1]
454 | marker_.pose.orientation.y = ori_[2]
455 | marker_.pose.orientation.z = ori_[3]
456 | marker_.pose.orientation.w = ori_[0]
457 |
458 | marker_.lifetime = rospy.Duration.from_sec(lifetime_)
459 | marker_.scale.x = scale_[0]
460 | marker_.scale.y = scale_[1]
461 | marker_.scale.z = scale_[2]
462 | marker_.color.a = 0.5
463 | red_, green_, blue_ = color_
464 | marker_.color.r = red_
465 | marker_.color.g = green_
466 | marker_.color.b = blue_
467 | marker_array_.markers.append(marker_)
468 |
469 |
470 | def show_grasp_marker(marker_array_, real_grasp_, gripper_, color_, lifetime_):
471 | """
472 | show grasp using marker使用marker来显示抓取
473 | :param marker_array_: marker array
474 | :param real_grasp_: [0] position, [1] approach [2] binormal [3] minor pc
475 | :param gripper_: gripper parameter of a grasp
476 | :param color_: color of the gripper 显示夹爪的颜色
477 | :param lifetime_: time for showing the maker marker的显示时间长短
478 | :return: return add makers to the maker array
479 |
480 | """
481 | hh = gripper_.hand_height
482 | fw = gripper_.real_finger_width
483 | hod = gripper_.hand_outer_diameter
484 | hd = gripper_.real_hand_depth
485 | open_w = hod - fw * 2
486 |
487 | approach = real_grasp_[1]
488 | binormal = real_grasp_[2]
489 | minor_pc = real_grasp_[3]
490 | grasp_bottom_center = real_grasp_[4] - approach * (gripper_.real_hand_depth - gripper_.hand_depth)
491 |
492 | rotation = np.vstack([approach, binormal, minor_pc]).T
493 | qua = Quaternion(matrix=rotation)
494 |
495 | marker_bottom_pos = grasp_bottom_center - approach * hh * 0.5
496 | marker_left_pos = grasp_bottom_center - binormal * (open_w * 0.5 + fw * 0.5) + hd * 0.5 * approach
497 | marker_right_pos = grasp_bottom_center + binormal * (open_w * 0.5 + fw * 0.5) + hd * 0.5 * approach
498 | show_marker(marker_array_, marker_bottom_pos, qua, np.array([hh, hod, hh]), color_, lifetime_)
499 | show_marker(marker_array_, marker_left_pos, qua, np.array([hd, fw, hh]), color_, lifetime_)
500 | show_marker(marker_array_, marker_right_pos, qua, np.array([hd, fw, hh]), color_, lifetime_)
501 |
502 |
503 | def check_hand_points_fun(real_grasp_):
504 | """该函数是计算处于夹爪内部有多少个点(只求数量)
505 | """
506 | ind_points_num = []
507 | for i in range(len(real_grasp_)):
508 | #修正后的抓取中心点坐标(位于第5个)
509 | grasp_bottom_center = real_grasp_[i][4]
510 | approach_normal = real_grasp_[i][1]
511 | binormal = real_grasp_[i][2]
512 | minor_pc = real_grasp_[i][3]
513 | #固定了手,而去变换点云,原因是,这样更容易计算碰撞,可以查看ags.check_collision_square函数内部
514 | local_hand_points = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
515 | #检查open区域内的点数量,返回点的索引list
516 | has_points_tmp, ind_points_tmp = ags.check_collision_square(grasp_bottom_center, approach_normal,
517 | binormal, minor_pc, points,
518 | local_hand_points, "p_open")
519 | #打印list中的点数
520 | ind_points_num.append(len(ind_points_tmp))
521 | print(ind_points_num)
522 | #这是啥意思?
523 | file_name = "./generated_grasps/real_points/" + str(np.random.randint(300)) + str(len(real_grasp_)) + ".npy"
524 | np.save(file_name, np.array(ind_points_num))
525 |
526 |
527 | def get_grasp_msg(real_good_grasp_, score_value_):
528 | """
529 | 创建一个ROS官方的抓取配置消息
530 | 里面涵盖了抓取坐标系的位置与姿态定义
531 | """
532 | grasp_bottom_center_modify = real_good_grasp_[4]
533 | approach = real_good_grasp_[1]
534 | binormal = real_good_grasp_[2]
535 | minor_pc = real_good_grasp_[3]
536 |
537 | grasp_config_ = GraspConfig()
538 | #
539 | top_p_ = grasp_bottom_center_modify + approach * ags.gripper.hand_depth
540 | grasp_config_.bottom.x = grasp_bottom_center_modify[0]
541 | grasp_config_.bottom.y = grasp_bottom_center_modify[1]
542 | grasp_config_.bottom.z = grasp_bottom_center_modify[2]
543 | grasp_config_.top.x = top_p_[0]
544 | grasp_config_.top.y = top_p_[1]
545 | grasp_config_.top.z = top_p_[2]
546 | #抓取的三个坐标轴向量
547 | grasp_config_.approach.x = approach[0]
548 | grasp_config_.approach.y = approach[1]
549 | grasp_config_.approach.z = approach[2]
550 | grasp_config_.binormal.x = binormal[0]
551 | grasp_config_.binormal.y = binormal[1]
552 | grasp_config_.binormal.z = binormal[2]
553 | grasp_config_.axis.x = minor_pc[0]
554 | grasp_config_.axis.y = minor_pc[1]
555 | grasp_config_.axis.z = minor_pc[2]
556 | #该抓取的分数
557 | grasp_config_.score.data = score_value_
558 |
559 | return grasp_config_
560 |
561 |
562 | def remove_grasp_outside_tray(grasps_, points_):
563 | x_min = points_[:, 0].min()
564 | x_max = points_[:, 0].max()
565 | y_min = points_[:, 1].min()
566 | y_max = points_[:, 1].max()
567 | valid_grasp_ind_ = []
568 | for i in range(len(grasps_)):
569 | grasp_bottom_center = grasps_[i][4]
570 | approach_normal = grasps_[i][1]
571 | major_pc = grasps_[i][2]
572 | hand_points_ = ags.get_hand_points(grasp_bottom_center, approach_normal, major_pc)
573 | finger_points_ = hand_points_[[1, 2, 3, 4, 9, 10, 13, 14], :]
574 | # aa = points_[:, :2] - finger_points_[0][:2] # todo: work of remove outside grasp not finished.
575 |
576 | # from IPython import embed;embed()
577 | a = finger_points_[:, 0] < x_min
578 | b = finger_points_[:, 0] > x_max
579 | c = finger_points_[:, 1] < y_min
580 | d = finger_points_[:, 1] > y_max
581 | if np.sum(a) + np.sum(b) + np.sum(c) + np.sum(d) == 0:
582 | valid_grasp_ind_.append(i)
583 | grasps_inside_ = [grasps_[i] for i in valid_grasp_ind_]
584 | rospy.loginfo("gpg got {} grasps, after remove grasp outside tray, {} grasps left".format(len(grasps_),
585 | len(grasps_inside_)))
586 | return grasps_inside_
587 |
588 |
589 | if __name__ == '__main__':
590 | """
591 | definition of gotten grasps:
592 |
593 | grasp_bottom_center = grasp_[0]
594 | approach_normal = grasp_[1]
595 | binormal = grasp_[2]
596 | """
597 | #初始化节点,把节点名称写为grasp_tf_broadcaster 抓取发布器,anonymous参数在
598 | # 为True的时候会在原本节点名字的后面加一串随机数,来保证可以同时开启多个同样的
599 | # 节点,如果为false的话就只能开一个
600 | rospy.init_node('grasp_tf_broadcaster', anonymous=True)
601 | #创建发布器,用于显示抓取
602 | pub1 = rospy.Publisher('gripper_vis', MarkerArray, queue_size=1)
603 | #发布检测到的抓取,用于抓取
604 | pub2 = rospy.Publisher('/detect_grasps/clustered_grasps', GraspConfigList, queue_size=1)
605 |
606 | #
607 | rate = rospy.Rate(10)
608 | #在ros参数服务器上,设置一个参数
609 | rospy.set_param("/robot_at_home", "true") # only use when in simulation test.
610 | rospy.loginfo("getting transform from kinect2 to table top")
611 |
612 | #cam_pos列表
613 | cam_pos = []
614 | #创建TF监听器
615 | listener = tf.TransformListener()
616 | #是否得到变换的标志位
617 | get_transform = False
618 | #等待tf中查找到'/table_top'与'/kinect2_ir_optical_frame'
619 | # 两个坐标系之间的变换关系
620 | while not get_transform:
621 | try:
622 | #查看kinect2相机与桌子之间的关系,确保相机已经能够看到标签ar_marker_6
623 | #cam_pos代表的是相机的trans,不是rot
624 | cam_pos, _ = listener.lookupTransform('/ar_marker_6', '/kinect2_rgb_optical_frame', rospy.Time(0))
625 | print(cam_pos)
626 | print(_)
627 | get_transform = True
628 | rospy.loginfo("got transform complete")
629 | except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
630 | continue
631 |
632 |
633 | while not rospy.is_shutdown():
634 | #检测机械臂是否处于home状态
635 | if rospy.get_param("/robot_at_home") == "false":
636 | robot_at_home = False
637 | else:
638 | robot_at_home = True
639 | #如果机械臂状态不在home(移动中)
640 | if not robot_at_home:
641 | rospy.loginfo("Robot is moving, waiting the robot go home.")
642 | #就跳过后面一直循环等待移动完毕
643 | continue
644 | else:
645 | #在home状态,可采集点云
646 | rospy.loginfo("Robot is at home, safely catching point cloud data.")
647 | if single_obj_testing:
648 | input("Pleas put object on table and press any number to continue!")
649 |
650 | rospy.loginfo("rospy is waiting for message: /table_top_points")
651 |
652 | """点云数据的名称是/table_top_points
653 | 对象是kinect_data 类形是 PointCloud2类型
654 | """
655 | #kinect_data = rospy.wait_for_message("/table_top_points", PointCloud2)
656 | kinect_data_ = rospy.wait_for_message("/table_top_points", PointCloud2)
657 | kinect_data = rospy.wait_for_message("/table_top_points_subsampled", PointCloud2)
658 |
659 | real_good_grasp = []
660 | real_bad_grasp = []
661 | real_score_value = []
662 |
663 |
664 | #设置每个候选抓取的夹爪内部的点
665 | # 需要送入pointnet中训练几次
666 | repeat = 3 # speed up this try 10 time is too time consuming
667 | ###########################开始抓取姿态检测###############################
668 | # begin of grasp detection 开始抓取检测
669 | # if there is no point cloud on table, waiting for point cloud.
670 | if kinect_data.data == '':
671 | rospy.loginfo("There is no points on the table, waiting...")
672 | continue
673 |
674 |
675 | #获取当前文件所在目录(文件夹)的绝对路径
676 | path=os.path.dirname(os.path.abspath(__file__))
677 | #更改(确保)当前所在目录是工作目录
678 | os.chdir(path)
679 |
680 | """根据Kinect读取到的场景点云,使用gpd检测候选的抓取?
681 | 输入:
682 | kinect_data读取的点云数据
683 | cam_pos Kinect与桌子标签之间的距离
684 | grasp_sampled 生成的所有候选抓取(不与桌子以及场景物体碰撞的抓取)
685 | points 函数中处理后,用于计算抓取的点云,ndarry形式
686 | normals_cal points对应的所有法相量 ndarry形式
687 | """
688 | grasp_sampled, points, normals_cal = cal_grasp(kinect_data, cam_pos)
689 | #检查生成的抓取数量是否为0
690 | if len(grasp_sampled)==0:
691 | rospy.loginfo("Nice try!")
692 | continue
693 |
694 |
695 | #托盘,如果有托盘?
696 | if tray_grasp:
697 | #grasp_sampled 去除外部托盘导致的抓取;
698 | grasp_sampled = remove_grasp_outside_tray(grasp_sampled, points)
699 | #估计一个抓取中的点数
700 | check_grasp_points_num = False # evaluate the number of points in a grasp
701 |
702 | """
703 | 等效于
704 | if check_grasp_points_num:
705 | check_hand_points_fun(grasp_sampled)
706 | else:
707 | 0
708 | """
709 | check_hand_points_fun(grasp_sampled) if check_grasp_points_num else 0
710 |
711 | #计算,每个抓取,夹爪内部的点云
712 | in_ind, in_ind_points= collect_pc(grasp_sampled, points)
713 |
714 |
715 | #保存在线抓取抓取相关的文件
716 | if save_grasp_related_file:
717 | np.save("./generated_grasps/points.npy", points)
718 | np.save("./generated_grasps/in_ind.npy", in_ind)
719 | np.save("./generated_grasps/grasp_sampled.npy", grasp_sampled)
720 | np.save("./generated_grasps/cal_norm.npy", normals_cal)
721 | #打分
722 | score = [] # should be 0 or 1
723 | score_value = [] # should be float [0, 1]
724 |
725 | ind_good_grasp = []
726 | ind_bad_grasp = []
727 | ############################把检测到的抓取送到pointnet中打分####################################
728 | rospy.loginfo("Begin send grasp into pointnet, cal grasp score")
729 |
730 | #in_ind_points 是一个 list
731 | for ii in range(len(in_ind_points)):
732 | """
733 | 首先需要保证机械臂处于home状态
734 | """
735 | if rospy.get_param("/robot_at_home") == "false":
736 | robot_at_home = False
737 | else:
738 | robot_at_home = True
739 | if not robot_at_home:
740 | rospy.loginfo("robot is not at home, stop calculating the grasp score")
741 | break
742 | """
743 | 判断,夹爪内部的点数量,是否满足最小点数(20),如果小于20的话就别扩充啥的了
744 | 太少了,可能就只是噪点抓取而已
745 | """
746 | if in_ind_points[ii].shape[0] < minimal_points_send_to_point_net:
747 | rospy.loginfo("Mark as bad grasp! Only {} points, should be at least {} points.".format(
748 | in_ind_points[ii].shape[0], minimal_points_send_to_point_net))
749 | #第一个分数记为0
750 | score.append(0)
751 | score_value.append(0.0)
752 | #如果需要记下来,哪个是bad抓取的话,就把哪个抓取给记下来
753 | if show_bad_grasp:
754 | ind_bad_grasp.append(ii)
755 |
756 | #内部点数满足要求
757 | else:
758 | predict = []
759 | grasp_score = []
760 | """for _ in range() 其中_代表临时变量,说明只关心完成循环而已,不关心循环到第几个
761 | 给定一个抓取,找到夹爪内部的点,然后重复repeat次输入pointnet中,进行多次打分
762 | 因为下面的采集点具有部分随机性,所以需要重复输入几次,但是稍微比较费时
763 | """
764 | for _ in range(repeat):
765 | #如果在线采集的夹爪内部点云点数量是大于 指定的点数量
766 | if len(in_ind_points[ii]) >= input_points_num:
767 | #随机抽选其中的一些点,保证和要求的点数量是一致的
768 | points_modify = in_ind_points[ii][np.random.choice(len(in_ind_points[ii]),
769 | input_points_num, replace=False)]
770 | #如果在线采集的夹爪内部点云点数量是小于 指定的点数量
771 | else:
772 | #就是补上去一些没有用的点,但是怎么补的,没看清楚??????????????????
773 | points_modify = in_ind_points[ii][np.random.choice(len(in_ind_points[ii]),
774 | input_points_num, replace=True)]
775 | if 0:
776 | p = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
777 | ags.show_grasp_3d(p)
778 | print("原始点数{},修改后点数{}".format(len(in_ind_points[ii]),len(points_modify)))
779 | ags.show_points(points_modify,color='b',scale_factor=0.005)
780 | ags.show_points(in_ind_points[ii],color='r',scale_factor=0.003)
781 | #table_points = np.array([[-1, 1, 0], [1, 1, 0], [1, -1, 0], [-1, -1, 0]]) * 0.5
782 | #triangles = [(1, 2, 3), (0, 1, 3)]
783 | #mlab.triangular_mesh(table_points[:, 0], table_points[:, 1], table_points[:, 2],
784 | #triangles, color=(0.8, 0.8, 0.8), opacity=0.5)
785 | mlab.show()
786 |
787 |
788 |
789 | """在这里输入网络打分!
790 | 返回的是?,grasp打分
791 | """
792 | if_good_grasp, grasp_score_tmp = test_network(model.eval(), points_modify)
793 |
794 |
795 | predict.append(if_good_grasp.item())
796 | #保存分数,添加到list中(这个list中都是同一个抓取的分数,重复了几次)
797 | grasp_score.append(grasp_score_tmp)
798 |
799 | predict_vote = mode(predict)[0][0] # vote from all the "repeat" results.
800 | #把list转换为np.array
801 | grasp_score = np.array(grasp_score)
802 | #如果是3class的???????????????
803 | if args.model_type == "3class": # the best in 3 class classification is the last column, third column
804 | which_one_is_best = 2 # should set as 2
805 | #两分类是第二个
806 | else: # for two class classification best is the second column (also the last column)
807 | which_one_is_best = 1 # should set as 1
808 |
809 | """np.mean()函数功能:求取均值numpy.mean(a, axis, dtype, out,keepdims )
810 | 经常操作的参数为axis,以m * n矩阵举例:
811 | axis 不设置值,对 m*n 个数求均值,返回一个实数
812 | axis = 0:压缩行,对各列求均值,返回 1* n 矩阵
813 | axis =1 :压缩列,对各行求均值,返回 m *1 矩阵
814 | """
815 | score_vote = np.mean(grasp_score[np.where(predict == predict_vote)][:, 0, which_one_is_best])
816 | score.append(predict_vote)
817 | score_value.append(score_vote)
818 |
819 | if score[ii] == which_one_is_best:
820 | ind_good_grasp.append(ii)
821 | else:
822 | if show_bad_grasp:
823 | #存放“坏抓取”的抓取编号
824 | ind_bad_grasp.append(ii)
825 | #记录好的抓取的数量,以及坏的抓取数量
826 | print("Got {} good grasps, and {} bad grasps".format(len(ind_good_grasp),
827 | len(in_ind_points) - len(ind_good_grasp)))
828 |
829 |
830 | if len(ind_good_grasp) != 0:
831 | real_good_grasp = [grasp_sampled[i] for i in ind_good_grasp]
832 | real_score_value = [score_value[i] for i in ind_good_grasp]
833 | if show_bad_grasp:
834 | real_bad_grasp = [grasp_sampled[i] for i in ind_bad_grasp]
835 | # end of grasp detection抓取检测部分结束
836 |
837 |
838 | #real_bad_grasp = [grasp_sampled[i] for i in ind_bad_grasp]
839 |
840 |
841 | # get sorted ind by the score values对检测到的抓取通过打分进行排序
842 | sorted_value_ind = list(index for index, item in sorted(enumerate(real_score_value),
843 | key=lambda item: item[1],
844 | reverse=True))
845 | # sort grasps using the ind
846 | sorted_real_good_grasp = [real_good_grasp[i] for i in sorted_value_ind]
847 | real_good_grasp = sorted_real_good_grasp
848 | # get the sorted score value, from high to low
849 | real_score_value = sorted(real_score_value, reverse=True)
850 |
851 |
852 |
853 | marker_array = MarkerArray()
854 | marker_array_single = MarkerArray()
855 |
856 |
857 | grasp_msg_list = GraspConfigList()
858 |
859 | #按照顺序将所有的好的抓取添加显示,应该是第0个是最优的抓取
860 | for i in range(len(real_good_grasp)):
861 | grasp_msg = get_grasp_msg(real_good_grasp[i], real_score_value[i])
862 | grasp_msg_list.grasps.append(grasp_msg)
863 | #把所有的抓取都显示成为绿色的,都添加进入那个显示序列中
864 | for i in range(len(real_good_grasp)):
865 | show_grasp_marker(marker_array, real_good_grasp[i], gripper, (0, 1, 0), marker_life_time)
866 |
867 | if show_bad_grasp:
868 | for i in range(len(real_bad_grasp)):
869 | show_grasp_marker(marker_array, real_bad_grasp[i], gripper, (1, 0, 0), marker_life_time)
870 |
871 | id_ = 0
872 | for m in marker_array.markers:
873 | m.id = id_
874 | id_ += 1
875 |
876 | grasp_msg_list.header.stamp = rospy.Time.now()
877 | grasp_msg_list.header.frame_id = "/ar_marker_6"
878 |
879 | # from IPython import embed;embed()
880 | if len(real_good_grasp) != 0:
881 | #if 0:
882 | i = 0
883 | #第0个抓取得分是最高的,以红色显示出来
884 | single_grasp_list_pub = GraspConfigList()
885 | single_grasp_list_pub.header.stamp = rospy.Time.now()
886 | single_grasp_list_pub.header.frame_id = "/ar_marker_6"
887 | grasp_msg = get_grasp_msg(real_good_grasp[i], real_score_value[i])
888 | single_grasp_list_pub.grasps.append(grasp_msg)
889 | show_grasp_marker(marker_array_single, real_good_grasp[i], gripper, (1, 0, 0), marker_life_time+5)
890 |
891 | for m in marker_array_single.markers:
892 | m.id = id_
893 | id_ += 1
894 | pub1.publish(marker_array)
895 | rospy.sleep(4)
896 | pub2.publish(single_grasp_list_pub)
897 | pub1.publish(marker_array_single)
898 | print(grasp_msg.approach)
899 | print(grasp_msg.binormal)
900 | print(grasp_msg.axis)
901 |
902 | # pub2.publish(grasp_msg_list)
903 | rospy.loginfo(" Publishing grasp pose to rviz using marker array and good grasp pose")
904 |
905 | rate.sleep()
906 |
--------------------------------------------------------------------------------
/apps/pointclouds.py:
--------------------------------------------------------------------------------
1 | # Software License Agreement (BSD License)
2 | #
3 | # Copyright (c) 2008, Willow Garage, Inc.
4 | # All rights reserved.
5 | #
6 | # Redistribution and use in source and binary forms, with or without
7 | # modification, are permitted provided that the following conditions
8 | # are met:
9 | #
10 | # * Redistributions of source code must retain the above copyright
11 | # notice, this list of conditions and the following disclaimer.
12 | # * Redistributions in binary form must reproduce the above
13 | # copyright notice, this list of conditions and the following
14 | # disclaimer in the documentation and/or other materials provided
15 | # with the distribution.
16 | # * Neither the name of Willow Garage, Inc. nor the names of its
17 | # contributors may be used to endorse or promote products derived
18 | # from this software without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 | # POSSIBILITY OF SUCH DAMAGE.
32 | #
33 | # Author: Jon Binney
34 | # Original author: Jon Binney
35 | # Modify by: Hongzhuo Liang
36 | # Time: Aug 6 2018
37 | # Download from https://github.com/kfu/metu-ros-pkg.git
38 | """
39 | Functions for working with PointCloud2.
40 | Tested on kinect2 and works well.
41 | Python2 is needed. Do not support python3
42 | """
43 |
44 | __docformat__ = "restructuredtext en"
45 |
46 |
47 | import numpy as np
48 | from sensor_msgs.msg import PointCloud2, PointField
49 |
50 | # prefix to the names of dummy fields we add to get byte alignment correct. this needs to not
51 | # clash with any actual field names
52 | DUMMY_FIELD_PREFIX = '__'
53 |
54 | # mappings between PointField types and numpy types
55 | type_mappings = [(PointField.INT8, np.dtype('int8')), (PointField.UINT8, np.dtype('uint8')),
56 | (PointField.INT16, np.dtype('int16')),
57 | (PointField.UINT16, np.dtype('uint16')), (PointField.INT32, np.dtype('int32')),
58 | (PointField.UINT32, np.dtype('uint32')),
59 | (PointField.FLOAT32, np.dtype('float32')), (PointField.FLOAT64, np.dtype('float64'))]
60 | pftype_to_nptype = dict(type_mappings)
61 | nptype_to_pftype = dict((nptype, pftype) for pftype, nptype in type_mappings)
62 |
63 | # sizes (in bytes) of PointField types
64 | pftype_sizes = {PointField.INT8: 1, PointField.UINT8: 1, PointField.INT16: 2, PointField.UINT16: 2,
65 | PointField.INT32: 4, PointField.UINT32: 4, PointField.FLOAT32: 4, PointField.FLOAT64: 8}
66 |
67 |
68 | def pointcloud2_to_dtype(cloud_msg):
69 | """Convert a list of PointFields to a numpy record datatype.
70 | """
71 | offset = 0
72 | np_dtype_list = []
73 | for f in cloud_msg.fields:
74 | while offset < f.offset:
75 | # might be extra padding between fields
76 | np_dtype_list.append(('%s%d' % (DUMMY_FIELD_PREFIX, offset), np.uint8))
77 | offset += 1
78 | np_dtype_list.append((f.name, pftype_to_nptype[f.datatype]))
79 | offset += pftype_sizes[f.datatype]
80 |
81 | # might be extra padding between points
82 | while offset < cloud_msg.point_step:
83 | np_dtype_list.append(('%s%d' % (DUMMY_FIELD_PREFIX, offset), np.uint8))
84 | offset += 1
85 |
86 | return np_dtype_list
87 |
88 |
89 | def arr_to_fields(cloud_arr):
90 | """Convert a numpy record datatype into a list of PointFields.
91 | """
92 | fields = []
93 | for field_name in cloud_arr.dtype.names:
94 | np_field_type, field_offset = cloud_arr.dtype.fields[field_name]
95 | pf = PointField()
96 | pf.name = field_name
97 | pf.datatype = nptype_to_pftype[np_field_type]
98 | pf.offset = field_offset
99 | pf.count = 1 # is this ever more than one?
100 | fields.append(pf)
101 | return fields
102 |
103 |
104 | def pointcloud2_to_array(cloud_msg, split_rgb=False):
105 | """
106 | Converts a rospy PointCloud2 message to a numpy recordarray
107 |
108 | Reshapes the returned array to have shape (height, width), even if the height is 1.
109 |
110 | The reason for using np.fromstring rather than struct.unpack is speed... especially
111 | for large point clouds, this will be faster.
112 | """
113 | # construct a numpy record type equivalent to the point type of this cloud
114 | dtype_list = pointcloud2_to_dtype(cloud_msg)
115 |
116 | # parse the cloud into an array
117 | cloud_arr = np.fromstring(cloud_msg.data, dtype_list)
118 |
119 | # remove the dummy fields that were added
120 | cloud_arr = cloud_arr[
121 | [fname for fname, _type in dtype_list if not (fname[:len(DUMMY_FIELD_PREFIX)] == DUMMY_FIELD_PREFIX)]]
122 |
123 | if split_rgb:
124 | cloud_arr = split_rgb_field(cloud_arr)
125 |
126 | return np.reshape(cloud_arr, (cloud_msg.height, cloud_msg.width))
127 |
128 |
129 | def array_to_pointcloud2(cloud_arr, stamp=None, frame_id=None, merge_rgb=False):
130 | """Converts a numpy record array to a sensor_msgs.msg.PointCloud2.
131 | """
132 | if merge_rgb:
133 | cloud_arr = merge_rgb_fields(cloud_arr)
134 |
135 | # make it 2d (even if height will be 1)
136 | cloud_arr = np.atleast_2d(cloud_arr)
137 |
138 | cloud_msg = PointCloud2()
139 |
140 | if stamp is not None:
141 | cloud_msg.header.stamp = stamp
142 | if frame_id is not None:
143 | cloud_msg.header.frame_id = frame_id
144 | cloud_msg.height = cloud_arr.shape[0]
145 | cloud_msg.width = cloud_arr.shape[1]
146 | cloud_msg.fields = arr_to_fields(cloud_arr)
147 | cloud_msg.is_bigendian = False # assumption
148 | cloud_msg.point_step = cloud_arr.dtype.itemsize
149 | cloud_msg.row_step = cloud_msg.point_step*cloud_arr.shape[1]
150 | cloud_msg.is_dense = all([np.isfinite(cloud_arr[fname]).all() for fname in cloud_arr.dtype.names])
151 | cloud_msg.data = cloud_arr.tostring()
152 | return cloud_msg
153 |
154 |
155 | def merge_rgb_fields(cloud_arr):
156 | """Takes an array with named np.uint8 fields 'r', 'g', and 'b', and returns an array in
157 | which they have been merged into a single np.float32 'rgb' field. The first byte of this
158 | field is the 'r' uint8, the second is the 'g', uint8, and the third is the 'b' uint8.
159 |
160 | This is the way that pcl likes to handle RGB colors for some reason.
161 | """
162 | r = np.asarray(cloud_arr['r'], dtype=np.uint32)
163 | g = np.asarray(cloud_arr['g'], dtype=np.uint32)
164 | b = np.asarray(cloud_arr['b'], dtype=np.uint32)
165 | rgb_arr = np.array((r << 16) | (g << 8) | (b << 0), dtype=np.uint32)
166 |
167 | # not sure if there is a better way to do this. i'm changing the type of the array
168 | # from uint32 to float32, but i don't want any conversion to take place -jdb
169 | rgb_arr.dtype = np.float32
170 |
171 | # create a new array, without r, g, and b, but with rgb float32 field
172 | new_dtype = []
173 | for field_name in cloud_arr.dtype.names:
174 | field_type, field_offset = cloud_arr.dtype.fields[field_name]
175 | if field_name not in ('r', 'g', 'b'):
176 | new_dtype.append((field_name, field_type))
177 | new_dtype.append(('rgb', np.float32))
178 | new_cloud_arr = np.zeros(cloud_arr.shape, new_dtype)
179 |
180 | # fill in the new array
181 | for field_name in new_cloud_arr.dtype.names:
182 | if field_name == 'rgb':
183 | new_cloud_arr[field_name] = rgb_arr
184 | else:
185 | new_cloud_arr[field_name] = cloud_arr[field_name]
186 |
187 | return new_cloud_arr
188 |
189 |
190 | def split_rgb_field(cloud_arr):
191 | """Takes an array with a named 'rgb' float32 field, and returns an array in which
192 | this has been split into 3 uint 8 fields: 'r', 'g', and 'b'.
193 |
194 | (pcl stores rgb in packed 32 bit floats)
195 | """
196 | rgb_arr = cloud_arr['rgb'].copy()
197 | rgb_arr.dtype = np.uint32
198 | r = np.asarray((rgb_arr >> 16) & 255, dtype=np.uint8)
199 | g = np.asarray((rgb_arr >> 8) & 255, dtype=np.uint8)
200 | b = np.asarray(rgb_arr & 255, dtype=np.uint8)
201 |
202 | # create a new array, without rgb, but with r, g, and b fields
203 | new_dtype = []
204 | for field_name in cloud_arr.dtype.names:
205 | field_type, field_offset = cloud_arr.dtype.fields[field_name]
206 | if not field_name == 'rgb':
207 | new_dtype.append((field_name, field_type))
208 | new_dtype.append(('r', np.uint8))
209 | new_dtype.append(('g', np.uint8))
210 | new_dtype.append(('b', np.uint8))
211 | new_cloud_arr = np.zeros(cloud_arr.shape, new_dtype)
212 |
213 | # fill in the new array
214 | for field_name in new_cloud_arr.dtype.names:
215 | if field_name == 'r':
216 | new_cloud_arr[field_name] = r
217 | elif field_name == 'g':
218 | new_cloud_arr[field_name] = g
219 | elif field_name == 'b':
220 | new_cloud_arr[field_name] = b
221 | else:
222 | new_cloud_arr[field_name] = cloud_arr[field_name]
223 | return new_cloud_arr
224 |
225 |
226 | def get_xyz_points(cloud_array, remove_nans=True, dtype=np.float):
227 | """Pulls out x, y, and z columns from the cloud recordarray, and returns
228 | a 3xN matrix.
229 | """
230 | # remove crap points
231 | if remove_nans:
232 | mask = np.isfinite(cloud_array['x']) & np.isfinite(cloud_array['y']) & np.isfinite(cloud_array['z'])
233 | cloud_array = cloud_array[mask]
234 |
235 | # pull out x, y, and z values
236 | points = np.zeros(list(cloud_array.shape) + [3], dtype=dtype)
237 | points[..., 0] = cloud_array['x']
238 | points[..., 1] = cloud_array['y']
239 | points[..., 2] = cloud_array['z']
240 |
241 | return points
242 |
243 |
244 | def pointcloud2_to_xyz_array(cloud_msg, remove_nans=True):
245 | return get_xyz_points(pointcloud2_to_array(cloud_msg), remove_nans=remove_nans)
246 |
--------------------------------------------------------------------------------
/apps/read_file_sdf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # Author : Hongzhuo Liang
4 | # E-mail : liang@informatik.uni-hamburg.de
5 | # Description:
6 | # Date : 22/05/2018 12:37 AM
7 | # File Name : read_file_sdf.py
8 | import os
9 | import multiprocessing
10 | import subprocess
11 |
12 | """
13 | This file convert obj file to sdf file automatically and multiprocessingly.
14 | All the cores of a computer can do the job parallel.
15 | """
16 |
17 |
18 | def get_file_name(file_dir_):
19 | file_list = []
20 | for root, dirs, files in os.walk(file_dir_):
21 | # print(root) # current path
22 | if root.count('/') == file_dir_.count('/')+1:
23 | file_list.append(root)
24 | # print(dirs) # all the directories in current path
25 | # print(files) # all the files in current path
26 | file_list.sort()
27 | return file_list
28 |
29 |
30 | def generate_sdf(path_to_sdfgen, obj_filename, dim, padding):
31 | """ Converts mesh to an sdf object """
32 |
33 | # create the SDF using binary tools
34 | sdfgen_cmd = '%s \"%s\" %d %d' % (path_to_sdfgen, obj_filename, dim, padding)
35 | os.system(sdfgen_cmd)
36 | # print('SDF Command: %s' % sdfgen_cmd)
37 | return
38 |
39 |
40 | def do_job_convert_obj_to_sdf(x):
41 | # file_list_all = get_file_name(file_dir)
42 | if os.path.exists(str(file_list_all[x])+"/google_512k/nontextured.sdf"):
43 | print(str(file_list_all[x])+"/google_512k/nontextured.sdf","pass")
44 | else:
45 | generate_sdf(path_sdfgen, str(file_list_all[x])+"/google_512k/nontextured.obj", 100, 5) # for google scanner
46 | print("Done job number", x)
47 |
48 |
49 | def generate_obj_from_ply(file_name_):
50 | base = file_name_.split(".")[0]
51 | p = subprocess.Popen(["pcl_ply2obj", base + ".ply", base + ".obj"])
52 | p.wait()
53 |
54 |
55 | if __name__ == '__main__':
56 | home_dir = os.environ['HOME']
57 | #设置obj ply文件的路径
58 | file_dir = home_dir + "/dataset/PointNetGPD/ycb_meshes_google/objects" # for google ycb
59 | # file_dir = home_dir + "/dataset/ycb_meshes" # for low quality ycb
60 | path_sdfgen = home_dir + "/code/SDFGen/bin/SDFGen"
61 | file_list_all = get_file_name(file_dir)
62 | object_numbers = file_list_all.__len__()
63 |
64 | # generate obj from ply file
65 | for i in file_list_all:
66 | if os.path.exists(i+"/google_512k/nontextured.ply"):
67 | print(i+"/google_512k/nontextured.ply","pass")
68 | else:
69 | generate_obj_from_ply(i+"/google_512k/nontextured.ply")
70 | print("finish", i)
71 | # The operation for the multi core
72 | cores = multiprocessing.cpu_count()
73 | pool = multiprocessing.Pool(processes=cores)
74 | pool.map(do_job_convert_obj_to_sdf, range(object_numbers))
75 |
--------------------------------------------------------------------------------
/apps/read_grasps_from_file.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # Author : Hongzhuo Liang
4 | # E-mail : liang@informatik.uni-hamburg.de
5 | # Description:
6 | # Date : 30/05/2018 9:57 AM
7 | # File Name : read_grasps_from_file.py
8 | # 使用python3
9 | import os
10 | import sys
11 | import re
12 | from meshpy.obj_file import ObjFile
13 | from meshpy.sdf_file import SdfFile
14 | from dexnet.grasping import GraspableObject3D
15 | import numpy as np
16 | from dexnet.visualization.visualizer3d import DexNetVisualizer3D as Vis
17 | from dexnet.grasping import RobotGripper
18 | from autolab_core import YamlConfig
19 | from mayavi import mlab
20 | from dexnet.grasping import GpgGraspSampler # temporary way for show 3D gripper using mayavi
21 | import glob
22 |
23 | # global configurations:
24 | home_dir = os.environ["HOME"] + "/code/PointNetGPD"
25 | yaml_config = YamlConfig(home_dir + "/dex-net/test/config.yaml")
26 | gripper_name = "robotiq_85"
27 | gripper = RobotGripper.load(gripper_name, home_dir + "/dex-net/data/grippers")
28 | ags = GpgGraspSampler(gripper, yaml_config)
29 |
30 | save_fig = False # save fig as png file
31 | show_fig = True # show the mayavi figure
32 | generate_new_file = False # whether generate new file for collision free grasps
33 | check_pcd_grasp_points = False
34 |
35 |
36 | def open_npy_and_obj(name_to_open_):
37 | """从路径指定的npy文件中读取并返回
38 | npy_m_: 抓取序列(只是针对某单个物体的抓取序列)
39 | obj_: 待抓取物体的模型mesh(单个模型)
40 | ply_name_: 待抓取物体的路径
41 | object_name_: 待抓取物体的名称
42 | """
43 | npy_m_ = np.load(name_to_open_)
44 | file_dir = home_dir + "/PointNetGPD/data/ycb_meshes_google/"
45 | object_name_ = name_to_open_.split("/")[-1][:-4]
46 | #object_name_=object_name_.split("_")[-1][:-3]
47 | print(object_name_)
48 | ply_name_ = file_dir + object_name_ + "/google_512k/nontextured.ply"
49 | if not check_pcd_grasp_points:
50 | of = ObjFile(file_dir + object_name_ + "/google_512k/nontextured.obj")
51 | sf = SdfFile(file_dir + object_name_ + "/google_512k/nontextured.sdf")
52 | mesh = of.read()
53 | sdf = sf.read()
54 | obj_ = GraspableObject3D(sdf, mesh)
55 | else:
56 | cloud_path = home_dir + "/dataset/ycb_rgbd/" + object_name_ + "/clouds/"
57 | pcd_files = glob.glob(cloud_path + "*.pcd")
58 | obj_ = pcd_files
59 | obj_.sort()
60 | return npy_m_, obj_, ply_name_, object_name_
61 |
62 |
63 | def display_object(obj_):
64 | """display object only using mayavi"""
65 | Vis.figure(bgcolor=(1, 1, 1), size=(1000, 1000))
66 | Vis.mesh(obj_.mesh.trimesh, color=(0.5, 0.5, 0.5), style="surface")
67 | Vis.show()
68 |
69 |
70 | def display_gripper_on_object(obj_, grasp_):
71 | """display both object and gripper using mayavi"""
72 | # transfer wrong was fixed by the previews comment of meshpy modification.
73 | # gripper_name = "robotiq_85"
74 | # gripper = RobotGripper.load(gripper_name, home_dir + "/dex-net/data/grippers")
75 | # stable_pose = self.dataset.stable_pose(object.key, "pose_1")
76 | # T_obj_world = RigidTransform(from_frame="obj", to_frame="world")
77 | t_obj_gripper = grasp_.gripper_pose(gripper)
78 |
79 | stable_pose = t_obj_gripper
80 | grasp_ = grasp_.perpendicular_table(stable_pose)
81 |
82 | Vis.figure(bgcolor=(1, 1, 1), size=(1000, 1000))
83 | Vis.gripper_on_object(gripper, grasp_, obj_,
84 | gripper_color=(0.25, 0.25, 0.25),
85 | # stable_pose=stable_pose, # .T_obj_world,
86 | plot_table=False)
87 | Vis.show()
88 |
89 |
90 | def display_grasps(grasp, graspable, color):
91 | """
92 | 显示给定的抓取,做一下碰撞检测,和指定颜色
93 | grasp:单个抓取
94 | graspable:目标物体(仅做碰撞)
95 | color:夹爪的颜色
96 | """
97 | center_point = grasp[0:3] #夹爪中心(指尖中心)
98 | major_pc = grasp[3:6] # binormal
99 | width = grasp[6]
100 | angle = grasp[7]
101 | level_score, refine_score = grasp[-2:]
102 | # cal approach
103 | cos_t = np.cos(angle)
104 | sin_t = np.sin(angle)
105 | R1 = np.c_[[cos_t, 0, sin_t],[0, 1, 0],[-sin_t, 0, cos_t]]
106 |
107 | axis_y = major_pc
108 | axis_x = np.array([axis_y[1], -axis_y[0], 0])
109 | if np.linalg.norm(axis_x) == 0:
110 | axis_x = np.array([1, 0, 0])
111 |
112 | axis_x = axis_x / np.linalg.norm(axis_x)
113 | axis_y = axis_y / np.linalg.norm(axis_y)
114 |
115 | axis_z = np.cross(axis_x, axis_y)
116 |
117 | R2 = np.c_[axis_x, np.c_[axis_y, axis_z]]
118 | approach_normal = R2.dot(R1)[:, 0]
119 | approach_normal = approach_normal / np.linalg.norm(approach_normal)
120 | minor_pc = np.cross(major_pc, approach_normal)
121 |
122 | #计算夹爪bottom_center
123 | grasp_bottom_center = -ags.gripper.hand_depth * approach_normal + center_point
124 | #以世界坐标系为参考系时
125 | hand_points = ags.get_hand_points(grasp_bottom_center, approach_normal, major_pc)
126 | #固定夹爪作为参考系时
127 | local_hand_points = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
128 | #检测一下夹爪的碰撞(这里使用原始函数,不检测与桌面的碰撞)
129 | if_collide = ags.check_collide(grasp_bottom_center, approach_normal,
130 | major_pc, minor_pc, graspable, local_hand_points)
131 |
132 | #只显示夹爪,不显示点云
133 | if not if_collide and (show_fig or save_fig):
134 | ags.show_grasp_3d(hand_points, color=color)
135 | return True
136 | elif not if_collide:
137 | return True
138 | else:
139 | return False
140 |
141 |
142 | def show_selected_grasps_with_color(m, ply_name_, title, obj_):
143 | """显示选择出的抓取,将抓取序列中good的抓取和bad的抓取区分开来,并分别显示在两个窗口中
144 | m: 针对被抓物体的带有打分的grasp序列
145 | ply_name_ : 被抓物体的路径
146 | title:被抓物体的名称
147 | obj_:被抓物体的mesh文件
148 | """
149 | m_good = m[m[:, -2] <= 0.4]
150 | m_good = m_good[np.random.choice(len(m_good), size=25, replace=True)]
151 |
152 | m_bad = m[m[:, -2] >= 1.8]
153 | m_bad = m_bad[np.random.choice(len(m_bad), size=25, replace=True)]
154 |
155 | collision_grasp_num = 0
156 | if save_fig or show_fig:
157 | # fig 1: good grasps
158 | mlab.figure(bgcolor=(1, 1, 1), fgcolor=(0.7, 0.7, 0.7), size=(1000, 1000))
159 | mlab.pipeline.surface(mlab.pipeline.open(ply_name_))
160 | for a in m_good:
161 | # display_gripper_on_object(obj, a) # real gripper
162 | collision_free = display_grasps(a, obj_, color="d") # simulated gripper
163 | if not collision_free:
164 | collision_grasp_num += 1
165 |
166 | if save_fig:
167 | mlab.savefig("good_"+title+".png")
168 | mlab.close()
169 | elif show_fig:
170 | mlab.title(title, size=0.5)
171 |
172 | # fig 2: bad grasps
173 | mlab.figure(bgcolor=(1, 1, 1), fgcolor=(0.7, 0.7, 0.7), size=(1000, 1000))
174 | mlab.pipeline.surface(mlab.pipeline.open(ply_name_))
175 |
176 | for a in m_bad:
177 | # display_gripper_on_object(obj, a) # real gripper
178 | collision_free = display_grasps(a, obj_, color=(1, 0, 0))
179 | if not collision_free:
180 | collision_grasp_num += 1
181 |
182 | if save_fig:
183 | mlab.savefig("bad_"+title+".png")
184 | mlab.close()
185 | elif show_fig:
186 | mlab.title(title, size=0.5)
187 | mlab.show()
188 | elif generate_new_file:
189 | # only to calculate collision:
190 | collision_grasp_num = 0
191 | ind_good_grasp_ = []
192 | for i_ in range(len(m)):
193 | collision_free = display_grasps(m[i_][0], obj_, color=(1, 0, 0))
194 | if not collision_free:
195 | collision_grasp_num += 1
196 | else:
197 | ind_good_grasp_.append(i_)
198 | collision_grasp_num = str(collision_grasp_num)
199 | collision_grasp_num = (4-len(collision_grasp_num))*" " + collision_grasp_num
200 | print("collision_grasp_num =", collision_grasp_num, "| object name:", title)
201 | return ind_good_grasp_
202 |
203 |
204 | def get_grasp_points_num(m, obj_):
205 | """获取夹爪内部点云的数量
206 | """
207 | has_points_ = []
208 | ind_points_ = []
209 | for i_ in range(len(m)):
210 | grasps = m[i_][0]
211 | approach_normal = grasps.rotated_full_axis[:, 0]
212 | approach_normal = approach_normal / np.linalg.norm(approach_normal)
213 | major_pc = grasps.configuration[3:6]
214 | major_pc = major_pc / np.linalg.norm(major_pc)
215 | minor_pc = np.cross(approach_normal, major_pc)
216 | center_point = grasps.center
217 |
218 | #计算bottom_center
219 | grasp_bottom_center = -ags.gripper.hand_depth * approach_normal + center_point
220 |
221 | # hand_points = ags.get_hand_points(grasp_bottom_center, approach_normal, major_pc)
222 | local_hand_points = ags.get_hand_points(np.array([0, 0, 0]), np.array([1, 0, 0]), np.array([0, 1, 0]))
223 |
224 | has_points_tmp, ind_points_tmp = ags.check_collision_square(grasp_bottom_center, approach_normal,
225 | major_pc, minor_pc, obj_, local_hand_points,
226 | "p_open")
227 | ind_points_tmp = len(ind_points_tmp) # here we only want to know the number of in grasp points.
228 | has_points_.append(has_points_tmp)
229 | ind_points_.append(ind_points_tmp)
230 | return has_points_, ind_points_
231 |
232 |
233 | if __name__ == "__main__":
234 | #获取npy的名称
235 | npy_names = glob.glob(home_dir + "/PointNetGPD/data/ycb_grasp/train/*.npy")
236 | npy_names.sort()
237 | for i in range(len(npy_names)):
238 | #把夹爪姿态和打分,先从npy中读取出来
239 | grasps_with_score, obj, ply_name, obj_name = open_npy_and_obj(npy_names[i])
240 | print("load file {}".format(npy_names[i]))
241 | #显示抓取
242 | ind_good_grasp = show_selected_grasps_with_color(grasps_with_score, ply_name, obj_name, obj)
243 |
244 |
--------------------------------------------------------------------------------
/apps/show_pcd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # Author : Hongzhuo Liang
4 | # E-mail : liang@informatik.uni-hamburg.de
5 | # Description:
6 | # Date : 21/08/2018 9:08 PM
7 | # File Name : show_pcd.py
8 |
9 | import numpy as np
10 | from mayavi import mlab
11 | import glob
12 | import os
13 | import pcl
14 |
15 |
16 | #需要提前安装pyside
17 | def show_obj(surface_points_, title, color='b'):
18 | mlab.figure(bgcolor=(0, 0, 0), fgcolor=(0.7, 0.7, 0.7), size=(1000, 1000))
19 | if color == 'b':
20 | color_f = (0, 0, 1)
21 | elif color == 'r':
22 | color_f = (1, 0, 0)
23 | elif color == 'g':
24 | color_f = (0, 1, 0)
25 | else:
26 | color_f = (1, 1, 1)
27 | points_ = surface_points_
28 | mlab.points3d(points_[:, 0], points_[:, 1], points_[:, 2], color=color_f, scale_factor=.0007)
29 | mlab.title(title, size=0.5)
30 |
31 |
32 | path = os.environ['HOME'] + "/win10/data/ycb_rgbd/003_cracker_box/clouds"
33 | pcd_files = glob.glob(os.path.join(path, '*.pcd'))
34 | pcd_files.sort()
35 | for i in range(450, len(pcd_files)):
36 | pcd = pcd_files[i]
37 | file_name = pcd.split('/')[-1]
38 | print(file_name)
39 | points_c = pcl.load(pcd)
40 | points = points_c.to_array()
41 | show_obj(points, file_name)
42 | mlab.show()
43 |
--------------------------------------------------------------------------------
/apps/voxelgrid.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # Author : Hongzhuo Liang
4 | # E-mail : liang@informatik.uni-hamburg.de
5 | # Description:
6 | # Date : 05/08/2018 6:04 PM
7 | # File Name : kinect2grasp_python2.py
8 | # Note: this file is inspired by PyntCloud
9 | # Reference web: https://github.com/daavoo/pyntcloud
10 |
11 | import numpy as np
12 | from scipy.spatial import cKDTree
13 | from numba import jit
14 |
15 | is_numba_avaliable = True
16 |
17 |
18 | @jit
19 | def groupby_count(xyz, indices, out):
20 | for i in range(xyz.shape[0]):
21 | out[indices[i]] += 1
22 | return out
23 |
24 |
25 | @jit
26 | def groupby_sum(xyz, indices, N, out):
27 | for i in range(xyz.shape[0]):
28 | out[indices[i]] += xyz[i][N]
29 | return out
30 |
31 |
32 | @jit
33 | def groupby_max(xyz, indices, N, out):
34 | for i in range(xyz.shape[0]):
35 | if xyz[i][N] > out[indices[i]]:
36 | out[indices[i]] = xyz[i][N]
37 | return out
38 |
39 |
40 | def cartesian(arrays, out=None):
41 | """Generate a cartesian product of input arrays.
42 |
43 | Parameters
44 | ----------
45 | arrays : list of array-like
46 | 1-D arrays to form the cartesian product of.
47 | out : ndarray
48 | Array to place the cartesian product in.
49 |
50 | Returns
51 | -------
52 | out : ndarray
53 | 2-D array of shape (M, len(arrays)) containing cartesian products
54 | formed of input arrays.
55 |
56 | Examples
57 | --------
58 | >>> cartesian(([1, 2, 3], [4, 5], [6, 7]))
59 | array([[1, 4, 6],
60 | [1, 4, 7],
61 | [1, 5, 6],
62 | [1, 5, 7],
63 | [2, 4, 6],
64 | [2, 4, 7],
65 | [2, 5, 6],
66 | [2, 5, 7],
67 | [3, 4, 6],
68 | [3, 4, 7],
69 | [3, 5, 6],
70 | [3, 5, 7]])
71 |
72 | """
73 | arrays = [np.asarray(x) for x in arrays]
74 | shape = (len(x) for x in arrays)
75 | dtype = arrays[0].dtype
76 |
77 | ix = np.indices(shape)
78 | ix = ix.reshape(len(arrays), -1).T
79 |
80 | if out is None:
81 | out = np.empty_like(ix, dtype=dtype)
82 |
83 | for n, arr in enumerate(arrays):
84 | out[:, n] = arrays[n][ix[:, n]]
85 |
86 | return out
87 |
88 |
89 | class VoxelGrid:
90 |
91 | def __init__(self, points, n_x=1, n_y=1, n_z=1, size_x=None, size_y=None, size_z=None, regular_bounding_box=True):
92 | """Grid of voxels with support for different build methods.
93 |
94 | Parameters
95 | ----------
96 | points: (N, 3) numpy.array
97 | n_x, n_y, n_z : int, optional
98 | Default: 1
99 | The number of segments in which each axis will be divided.
100 | Ignored if corresponding size_x, size_y or size_z is not None.
101 | size_x, size_y, size_z : float, optional
102 | Default: None
103 | The desired voxel size along each axis.
104 | If not None, the corresponding n_x, n_y or n_z will be ignored.
105 | regular_bounding_box : bool, optional
106 | Default: True
107 | If True, the bounding box of the point cloud will be adjusted
108 | in order to have all the dimensions of equal length.
109 | """
110 | self._points = points
111 | self.x_y_z = [n_x, n_y, n_z]
112 | self.sizes = [size_x, size_y, size_z]
113 | self.regular_bounding_box = regular_bounding_box
114 |
115 | def compute(self):
116 | xyzmin = self._points.min(0)
117 | xyzmax = self._points.max(0)
118 |
119 | if self.regular_bounding_box:
120 | #: adjust to obtain a minimum bounding box with all sides of equal length
121 | margin = max(xyzmax - xyzmin) - (xyzmax - xyzmin)
122 | xyzmin = xyzmin - margin / 2
123 | xyzmax = xyzmax + margin / 2
124 |
125 | for n, size in enumerate(self.sizes):
126 | if size is None:
127 | continue
128 | margin = (((self._points.ptp(0)[n] // size) + 1) * size) - self._points.ptp(0)[n]
129 | xyzmin[n] -= margin / 2
130 | xyzmax[n] += margin / 2
131 | self.x_y_z[n] = ((xyzmax[n] - xyzmin[n]) / size).astype(int)
132 |
133 | self.xyzmin = xyzmin
134 | self.xyzmax = xyzmax
135 |
136 | segments = []
137 | shape = []
138 | for i in range(3):
139 | # note the +1 in num
140 | s, step = np.linspace(xyzmin[i], xyzmax[i], num=(self.x_y_z[i] + 1), retstep=True)
141 | segments.append(s)
142 | shape.append(step)
143 |
144 | self.segments = segments
145 | self.shape = shape
146 |
147 | self.n_voxels = self.x_y_z[0] * self.x_y_z[1] * self.x_y_z[2]
148 |
149 | self.id = "V({},{},{})".format(self.x_y_z, self.sizes, self.regular_bounding_box)
150 |
151 | # find where each point lies in corresponding segmented axis
152 | # -1 so index are 0-based; clip for edge cases
153 | self.voxel_x = np.clip(np.searchsorted(self.segments[0], self._points[:, 0]) - 1, 0, self.x_y_z[0])
154 | self.voxel_y = np.clip(np.searchsorted(self.segments[1], self._points[:, 1]) - 1, 0, self.x_y_z[1])
155 | self.voxel_z = np.clip(np.searchsorted(self.segments[2], self._points[:, 2]) - 1, 0, self.x_y_z[2])
156 | self.voxel_n = np.ravel_multi_index([self.voxel_x, self.voxel_y, self.voxel_z], self.x_y_z)
157 |
158 | # compute center of each voxel
159 | midsegments = [(self.segments[i][1:] + self.segments[i][:-1]) / 2 for i in range(3)]
160 | self.voxel_centers = cartesian(midsegments).astype(np.float32)
161 |
162 | def query(self, points):
163 | """ABC API. Query structure.
164 |
165 | TODO Make query_voxelgrid an independent function, and add a light
166 | save mode where only segments and x_y_z are saved.
167 | """
168 | voxel_x = np.clip(np.searchsorted(
169 | self.segments[0], points[:, 0]) - 1, 0, self.x_y_z[0])
170 | voxel_y = np.clip(np.searchsorted(
171 | self.segments[1], points[:, 1]) - 1, 0, self.x_y_z[1])
172 | voxel_z = np.clip(np.searchsorted(
173 | self.segments[2], points[:, 2]) - 1, 0, self.x_y_z[2])
174 | voxel_n = np.ravel_multi_index([voxel_x, voxel_y, voxel_z], self.x_y_z)
175 |
176 | return voxel_n
177 |
178 | def get_feature_vector(self, mode="binary"):
179 | """Return a vector of size self.n_voxels. See mode options below.
180 |
181 | Parameters
182 | ----------
183 | mode: str in available modes. See Notes
184 | Default "binary"
185 |
186 | Returns
187 | -------
188 | feature_vector: [n_x, n_y, n_z] ndarray
189 | See Notes.
190 |
191 | Notes
192 | -----
193 | Available modes are:
194 |
195 | binary
196 | 0 for empty voxels, 1 for occupied.
197 |
198 | density
199 | number of points inside voxel / total number of points.
200 |
201 | TDF
202 | Truncated Distance Function. Value between 0 and 1 indicating the distance
203 | between the voxel's center and the closest point. 1 on the surface,
204 | 0 on voxels further than 2 * voxel side.
205 |
206 | x_max, y_max, z_max
207 | Maximum coordinate value of points inside each voxel.
208 |
209 | x_mean, y_mean, z_mean
210 | Mean coordinate value of points inside each voxel.
211 | """
212 | vector = np.zeros(self.n_voxels)
213 |
214 | if mode == "binary":
215 | vector[np.unique(self.voxel_n)] = 1
216 |
217 | elif mode == "density":
218 | count = np.bincount(self.voxel_n)
219 | vector[:len(count)] = count
220 | vector /= len(self.voxel_n)
221 |
222 | elif mode == "TDF":
223 | # truncation = np.linalg.norm(self.shape)
224 | kdt = cKDTree(self._points)
225 | vector, i = kdt.query(self.voxel_centers, n_jobs=-1)
226 |
227 | elif mode.endswith("_max"):
228 | if not is_numba_avaliable:
229 | raise ImportError("numba is required to compute {}".format(mode))
230 | axis = {"x_max": 0, "y_max": 1, "z_max": 2}
231 | vector = groupby_max(self._points, self.voxel_n, axis[mode], vector)
232 |
233 | elif mode.endswith("_mean"):
234 | if not is_numba_avaliable:
235 | raise ImportError("numba is required to compute {}".format(mode))
236 | axis = {"x_mean": 0, "y_mean": 1, "z_mean": 2}
237 | voxel_sum = groupby_sum(self._points, self.voxel_n, axis[mode], np.zeros(self.n_voxels))
238 | voxel_count = groupby_count(self._points, self.voxel_n, np.zeros(self.n_voxels))
239 | vector = np.nan_to_num(voxel_sum / voxel_count)
240 |
241 | else:
242 | raise NotImplementedError("{} is not a supported feature vector mode".format(mode))
243 |
244 | return vector.reshape(self.x_y_z)
245 |
246 | def get_voxel_neighbors(self, voxel):
247 | """Get valid, non-empty 26 neighbors of voxel.
248 |
249 | Parameters
250 | ----------
251 | voxel: int in self.set_voxel_n
252 |
253 | Returns
254 | -------
255 | neighbors: list of int
256 | Indices of the valid, non-empty 26 neighborhood around voxel.
257 | """
258 |
259 | x, y, z = np.unravel_index(voxel, self.x_y_z)
260 |
261 | valid_x = []
262 | valid_y = []
263 | valid_z = []
264 | if x - 1 >= 0:
265 | valid_x.append(x - 1)
266 | if y - 1 >= 0:
267 | valid_y.append(y - 1)
268 | if z - 1 >= 0:
269 | valid_z.append(z - 1)
270 |
271 | valid_x.append(x)
272 | valid_y.append(y)
273 | valid_z.append(z)
274 |
275 | if x + 1 < self.x_y_z[0]:
276 | valid_x.append(x + 1)
277 | if y + 1 < self.x_y_z[1]:
278 | valid_y.append(y + 1)
279 | if z + 1 < self.x_y_z[2]:
280 | valid_z.append(z + 1)
281 |
282 | valid_neighbor_indices = cartesian((valid_x, valid_y, valid_z))
283 |
284 | ravel_indices = np.ravel_multi_index((valid_neighbor_indices[:, 0],
285 | valid_neighbor_indices[:, 1],
286 | valid_neighbor_indices[:, 2]), self.x_y_z)
287 |
288 | return [x for x in ravel_indices if x in np.unique(self.voxel_n)]
289 |
290 |
--------------------------------------------------------------------------------
/apps/ycb_cloud_generate.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | import h5py as h5
4 | from scipy.misc import imread
5 | from struct import pack, unpack
6 | import math
7 | import multiprocessing as mp
8 | import glob
9 |
10 |
11 | #将rgbd图像转换为点云图,并转换到目标物体坐标系系统
12 | # extract pointcloud from rgb-d, then convert it into obj coordinate system(tsdf/poisson reconstructed object)
13 |
14 | def im2col(im, psize):
15 | n_channels = 1 if len(im.shape) == 2 else im.shape[0]
16 | (n_channels, rows, cols) = (1,) * (3 - len(im.shape)) + im.shape
17 |
18 | im_pad = np.zeros((n_channels,
19 | int(math.ceil(1.0 * rows / psize) * psize),
20 | int(math.ceil(1.0 * cols / psize) * psize)))
21 | im_pad[:, 0:rows, 0:cols] = im
22 |
23 | final = np.zeros((im_pad.shape[1], im_pad.shape[2], n_channels,
24 | psize, psize))
25 | for c in range(n_channels):
26 | for x in range(psize):
27 | for y in range(psize):
28 | im_shift = np.vstack(
29 | (im_pad[c, x:], im_pad[c, :x]))
30 | im_shift = np.column_stack(
31 | (im_shift[:, y:], im_shift[:, :y]))
32 | final[x::psize, y::psize, c] = np.swapaxes(
33 | im_shift.reshape(int(im_pad.shape[1] / psize), psize,
34 | int(im_pad.shape[2] / psize), psize), 1, 2)
35 |
36 | return np.squeeze(final[0:rows - psize + 1, 0:cols - psize + 1])
37 |
38 | #滤波器,率除掉断续的图像?
39 | def filterDiscontinuities(depthMap):
40 | filt_size = 7
41 | thresh = 1000
42 |
43 | # Ensure that filter sizes are okay
44 | assert filt_size % 2 == 1, "Can only use odd filter sizes."
45 |
46 | # Compute discontinuities
47 | offset = int((filt_size - 1) / 2)
48 | patches = 1.0 * im2col(depthMap, filt_size)
49 | mids = patches[:, :, offset, offset]
50 | mins = np.min(patches, axis=(2, 3))
51 | maxes = np.max(patches, axis=(2, 3))
52 |
53 | discont = np.maximum(np.abs(mins - mids),
54 | np.abs(maxes - mids))
55 | mark = discont > thresh
56 |
57 | # Account for offsets
58 | final_mark = np.zeros((480, 640), dtype=np.uint16)
59 | final_mark[offset:offset + mark.shape[0],
60 | offset:offset + mark.shape[1]] = mark
61 |
62 | return depthMap * (1 - final_mark)
63 |
64 | #把深度图和rgb图像相互配准
65 | def registerDepthMap(unregisteredDepthMap,
66 | rgbImage,
67 | depthK,
68 | rgbK,
69 | H_RGBFromDepth):
70 |
71 |
72 | unregisteredHeight = unregisteredDepthMap.shape[0]
73 | unregisteredWidth = unregisteredDepthMap.shape[1]
74 |
75 | registeredHeight = rgbImage.shape[0]
76 | registeredWidth = rgbImage.shape[1]
77 |
78 | registeredDepthMap = np.zeros((registeredHeight, registeredWidth))
79 |
80 | xyzDepth = np.empty((4,1))
81 | xyzRGB = np.empty((4,1))
82 |
83 | # Ensure that the last value is 1 (homogeneous coordinates)
84 | xyzDepth[3] = 1
85 |
86 | invDepthFx = 1.0 / depthK[0,0]
87 | invDepthFy = 1.0 / depthK[1,1]
88 | depthCx = depthK[0,2]
89 | depthCy = depthK[1,2]
90 |
91 | rgbFx = rgbK[0,0]
92 | rgbFy = rgbK[1,1]
93 | rgbCx = rgbK[0,2]
94 | rgbCy = rgbK[1,2]
95 |
96 | undistorted = np.empty(2)
97 | for v in range(unregisteredHeight):
98 | for u in range(unregisteredWidth):
99 |
100 | depth = unregisteredDepthMap[v,u]
101 | if depth == 0:
102 | continue
103 |
104 | xyzDepth[0] = ((u - depthCx) * depth) * invDepthFx
105 | xyzDepth[1] = ((v - depthCy) * depth) * invDepthFy
106 | xyzDepth[2] = depth
107 |
108 | xyzRGB[0] = (H_RGBFromDepth[0,0] * xyzDepth[0] +
109 | H_RGBFromDepth[0,1] * xyzDepth[1] +
110 | H_RGBFromDepth[0,2] * xyzDepth[2] +
111 | H_RGBFromDepth[0,3])
112 | xyzRGB[1] = (H_RGBFromDepth[1,0] * xyzDepth[0] +
113 | H_RGBFromDepth[1,1] * xyzDepth[1] +
114 | H_RGBFromDepth[1,2] * xyzDepth[2] +
115 | H_RGBFromDepth[1,3])
116 | xyzRGB[2] = (H_RGBFromDepth[2,0] * xyzDepth[0] +
117 | H_RGBFromDepth[2,1] * xyzDepth[1] +
118 | H_RGBFromDepth[2,2] * xyzDepth[2] +
119 | H_RGBFromDepth[2,3])
120 |
121 | invRGB_Z = 1.0 / xyzRGB[2]
122 | undistorted[0] = (rgbFx * xyzRGB[0]) * invRGB_Z + rgbCx
123 | undistorted[1] = (rgbFy * xyzRGB[1]) * invRGB_Z + rgbCy
124 |
125 | uRGB = int(undistorted[0] + 0.5)
126 | vRGB = int(undistorted[1] + 0.5)
127 |
128 | if (uRGB < 0 or uRGB >= registeredWidth) or (vRGB < 0 or vRGB >= registeredHeight):
129 | continue
130 |
131 | registeredDepth = xyzRGB[2]
132 | if registeredDepth > registeredDepthMap[vRGB,uRGB]:
133 | registeredDepthMap[vRGB,uRGB] = registeredDepth
134 |
135 | return registeredDepthMap
136 |
137 |
138 | #把rgb配准后的深度图转换为点云
139 | def registeredDepthMapToPointCloud(depthMap, rgbImage, rgbK, refFromRGB, objFromref, organized=False):
140 | rgbCx = rgbK[0,2]
141 | rgbCy = rgbK[1,2]
142 | invRGBFx = 1.0/rgbK[0,0]
143 | invRGBFy = 1.0/rgbK[1,1]
144 |
145 | height = depthMap.shape[0]
146 | width = depthMap.shape[1]
147 |
148 | if organized:
149 | cloud = np.empty((height, width, 6), dtype=np.float)
150 | else:
151 | cloud = np.empty((1, height*width, 6), dtype=np.float)
152 |
153 | goodPointsCount = 0
154 | for v in range(height):
155 | for u in range(width):
156 |
157 | depth = depthMap[v,u]
158 |
159 | if organized:
160 | row = v
161 | col = u
162 | else:
163 | row = 0
164 | col = goodPointsCount
165 |
166 | if depth <= 0:
167 | if organized:
168 | if depth <= 0:
169 | cloud[row,col,0] = float('nan')
170 | cloud[row,col,1] = float('nan')
171 | cloud[row,col,2] = float('nan')
172 | cloud[row,col,3] = 0
173 | cloud[row,col,4] = 0
174 | cloud[row,col,5] = 0
175 | continue
176 |
177 | x = (u - rgbCx) * depth * invRGBFx
178 | y = (v - rgbCy) * depth * invRGBFy
179 | z = depth
180 |
181 | #refFromRGB
182 | x1 = (refFromRGB[0,0] * x +
183 | refFromRGB[0,1] * y +
184 | refFromRGB[0,2] * z +
185 | refFromRGB[0,3])
186 | y1 = (refFromRGB[1,0] * x +
187 | refFromRGB[1,1] * y +
188 | refFromRGB[1,2] * z +
189 | refFromRGB[1,3])
190 | z1 = (refFromRGB[2,0] * x +
191 | refFromRGB[2,1] * y +
192 | refFromRGB[2,2] * z +
193 | refFromRGB[2,3])
194 |
195 | x, y, z = x1, y1, z1
196 |
197 | # obj from ref
198 | cloud[row,col,0] = (objFromref[0,0] * x +
199 | objFromref[0,1] * y +
200 | objFromref[0,2] * z +
201 | objFromref[0,3])
202 | cloud[row,col,1] = (objFromref[1,0] * x +
203 | objFromref[1,1] * y +
204 | objFromref[1,2] * z +
205 | objFromref[1,3])
206 | cloud[row,col,2] = (objFromref[2,0] * x +
207 | objFromref[2,1] * y +
208 | objFromref[2,2] * z +
209 | objFromref[2,3])
210 |
211 | cloud[row,col,3] = rgbImage[v,u,0]
212 | cloud[row,col,4] = rgbImage[v,u,1]
213 | cloud[row,col,5] = rgbImage[v,u,2]
214 | if not organized:
215 | goodPointsCount += 1
216 |
217 | if not organized:
218 | cloud = cloud[:,:goodPointsCount,:]
219 |
220 | return cloud
221 |
222 | #把点云写到ply文件中
223 | def writePLY(filename, cloud, faces=[]):
224 | if len(cloud.shape) != 3:
225 | print("Expected pointCloud to have 3 dimensions. Got %d instead" % len(cloud.shape))
226 | return
227 |
228 | color = True if cloud.shape[2] == 6 else False
229 | num_points = cloud.shape[0]*cloud.shape[1]
230 |
231 | header_lines = [
232 | 'ply',
233 | 'format ascii 1.0',
234 | 'element vertex %d' % num_points,
235 | 'property float x',
236 | 'property float y',
237 | 'property float z',
238 | ]
239 | if color:
240 | header_lines.extend([
241 | 'property uchar diffuse_red',
242 | 'property uchar diffuse_green',
243 | 'property uchar diffuse_blue',
244 | ])
245 | if faces != None:
246 | header_lines.extend([
247 | 'element face %d' % len(faces),
248 | 'property list uchar int vertex_indices'
249 | ])
250 |
251 | header_lines.extend([
252 | 'end_header',
253 | ])
254 |
255 | f = open(filename, 'w+')
256 | f.write('\n'.join(header_lines))
257 | f.write('\n')
258 |
259 | lines = []
260 | for i in range(cloud.shape[0]):
261 | for j in range(cloud.shape[1]):
262 | if color:
263 | lines.append('%s %s %s %d %d %d' % tuple(cloud[i, j, :].tolist()))
264 | else:
265 | lines.append('%s %s %s' % tuple(cloud[i, j, :].tolist()))
266 |
267 | for face in faces:
268 | lines.append(('%d' + ' %d'*len(face)) % tuple([len(face)] + list(face)))
269 |
270 | f.write('\n'.join(lines) + '\n')
271 | f.close()
272 |
273 |
274 | #把点云写到pcd文件中
275 | def writePCD(filename, pointCloud, ascii=True):
276 | if len(pointCloud.shape) != 3:
277 | print("Expected pointCloud to have 3 dimensions. Got %d instead" % len(pointCloud.shape))
278 | return
279 | with open(filename, 'w') as f:
280 | height = pointCloud.shape[0]
281 | width = pointCloud.shape[1]
282 | f.write("# .PCD v.7 - Point Cloud Data file format\n")
283 | f.write("VERSION .7\n")
284 | if pointCloud.shape[2] == 3:
285 | f.write("FIELDS x y z\n")
286 | f.write("SIZE 4 4 4\n")
287 | f.write("TYPE F F F\n")
288 | f.write("COUNT 1 1 1\n")
289 | else:
290 | f.write("FIELDS x y z rgb\n")
291 | f.write("SIZE 4 4 4 4\n")
292 | f.write("TYPE F F F F\n")
293 | f.write("COUNT 1 1 1 1\n")
294 | f.write("WIDTH %d\n" % width)
295 | f.write("HEIGHT %d\n" % height)
296 | f.write("VIEWPOINT 0 0 0 1 0 0 0\n")
297 | f.write("POINTS %d\n" % (height * width))
298 | if ascii:
299 | f.write("DATA ascii\n")
300 | for row in range(height):
301 | for col in range(width):
302 | if pointCloud.shape[2] == 3:
303 | f.write("%f %f %f\n" % tuple(pointCloud[row, col, :]))
304 | else:
305 | f.write("%f %f %f" % tuple(pointCloud[row, col, :3]))
306 | r = int(pointCloud[row, col, 3])
307 | g = int(pointCloud[row, col, 4])
308 | b = int(pointCloud[row, col, 5])
309 | rgb_int = (r << 16) | (g << 8) | b
310 | packed = pack('i', rgb_int)
311 | rgb = unpack('f', packed)[0]
312 | f.write(" %.12e\n" % rgb)
313 | else:
314 | f.write("DATA binary\n")
315 | if pointCloud.shape[2] == 6:
316 | # These are written as bgr because rgb is interpreted as a single
317 | # little-endian float.
318 | dt = np.dtype([('x', np.float32),
319 | ('y', np.float32),
320 | ('z', np.float32),
321 | ('b', np.uint8),
322 | ('g', np.uint8),
323 | ('r', np.uint8),
324 | ('I', np.uint8)])
325 | pointCloud_tmp = np.zeros((height*width, 1), dtype=dt)
326 | for i, k in enumerate(['x', 'y', 'z', 'r', 'g', 'b']):
327 | pointCloud_tmp[k] = pointCloud[:, :, i].reshape((height*width, 1))
328 | pointCloud_tmp.tofile(f)
329 | else:
330 | dt = np.dtype([('x', np.float32),
331 | ('y', np.float32),
332 | ('z', np.float32),
333 | ('I', np.uint8)])
334 | pointCloud_tmp = np.zeros((height*width, 1), dtype=dt)
335 | for i, k in enumerate(['x', 'y', 'z']):
336 | pointCloud_tmp[k] = pointCloud[:, :, i].reshape((height*width, 1))
337 | pointCloud_tmp.tofile(f)
338 |
339 | def getRGBFromDepthTransform(calibration, camera, referenceCamera):
340 | irKey = "H_{0}_ir_from_{1}".format(camera, referenceCamera)
341 | rgbKey = "H_{0}_from_{1}".format(camera, referenceCamera)
342 |
343 | rgbFromRef = calibration[rgbKey][:]
344 | irFromRef = calibration[irKey][:]
345 |
346 | return np.dot(rgbFromRef, np.linalg.inv(irFromRef)), np.linalg.inv(rgbFromRef)
347 |
348 |
349 | def generate(path):
350 | path = path.split('/')
351 | # Parameters
352 | ycb_data_folder = os.path.join('/',*path[:-2]) # Folder that contains the ycb data.
353 | target_object = path[-2] # Full name of the target object.
354 | viewpoint_camera = path[-1].split('_')[0] # Camera which the viewpoint will be generated.
355 | viewpoint_angle = path[-1].split('_')[1].split('.')[0] # Relative angle of the object w.r.t the camera (angle of the turntable).
356 |
357 | referenceCamera = "NP5" # can only be NP5
358 |
359 | ply_fname = os.path.join(ycb_data_folder, target_object, "clouds", "pc_"+viewpoint_camera+"_"+referenceCamera+"_"+viewpoint_angle+".ply")
360 | pcd_fname = os.path.join(ycb_data_folder, target_object, "clouds", "pc_"+viewpoint_camera+"_"+referenceCamera+"_"+viewpoint_angle+".pcd")
361 | npy_fname = os.path.join(ycb_data_folder, target_object, "clouds", "pc_"+viewpoint_camera+"_"+referenceCamera+"_"+viewpoint_angle+".npy")
362 |
363 | if os.path.exists(ply_fname) and os.path.exists(pcd_fname):
364 | print(ycb_data_folder, target_object, viewpoint_camera, viewpoint_angle, 'pass')
365 | return
366 | else:
367 | print(ycb_data_folder, target_object, viewpoint_camera, viewpoint_angle)
368 |
369 | try:
370 | if not os.path.exists(os.path.join(ycb_data_folder, target_object, "clouds")):
371 | os.makedirs(os.path.join(ycb_data_folder, target_object, "clouds"))
372 |
373 | basename = "{0}_{1}".format(viewpoint_camera, viewpoint_angle)
374 | depthFilename = os.path.join(ycb_data_folder, target_object, basename + ".h5")
375 | rgbFilename = os.path.join(ycb_data_folder, target_object, basename + ".jpg")
376 | pbmFilename = os.path.join(ycb_data_folder, target_object, 'masks', basename + "_mask.pbm")
377 |
378 | calibrationFilename = os.path.join(ycb_data_folder, target_object, "calibration.h5")
379 | objFromrefFilename = os.path.join(ycb_data_folder, target_object, 'poses', '{0}_{1}_pose.h5'.format(referenceCamera, viewpoint_angle))
380 | calibration = h5.File(calibrationFilename, 'r')
381 | objFromref = h5.File(objFromrefFilename, 'r')['H_table_from_reference_camera'][:]
382 |
383 | if not os.path.isfile(rgbFilename):
384 | print("The rgbd data is not available for the target object \"%s\". Please download the data first." % target_object)
385 | exit(1)
386 | rgbImage = imread(rgbFilename)
387 | pbmImage = imread(pbmFilename)
388 | depthK = calibration["{0}_depth_K".format(viewpoint_camera)][:] # use depth instead of ir
389 | rgbK = calibration["{0}_rgb_K".format(viewpoint_camera)][:]
390 | depthScale = np.array(calibration["{0}_ir_depth_scale".format(viewpoint_camera)]) * .0001 # 100um to meters
391 | H_RGBFromDepth, refFromRGB = getRGBFromDepthTransform(calibration, viewpoint_camera, referenceCamera)
392 |
393 | unregisteredDepthMap = h5.File(depthFilename, 'r')["depth"][:]
394 | unregisteredDepthMap = filterDiscontinuities(unregisteredDepthMap) * depthScale
395 |
396 | registeredDepthMap = registerDepthMap(unregisteredDepthMap,
397 | rgbImage,
398 | depthK,
399 | rgbK,
400 | H_RGBFromDepth)
401 | # apply mask
402 | registeredDepthMap[pbmImage == 255] = 0
403 |
404 | pointCloud = registeredDepthMapToPointCloud(registeredDepthMap, rgbImage, rgbK, refFromRGB, objFromref)
405 |
406 | writePLY(ply_fname, pointCloud)
407 | writePCD(pcd_fname, pointCloud)
408 | np.save(npy_fname, pointCloud[: ,:, :3].reshape(-1, 3))
409 | except:
410 | f = open('exception.txt', 'a')
411 | f.write('/'.join(path) + '\n')
412 | f.close()
413 | print(ycb_data_folder, target_object, viewpoint_camera, viewpoint_angle, 'failed')
414 | return
415 |
416 |
417 | def main():
418 | #获取当前文件所在目录的绝对路径
419 | path=os.path.dirname(os.path.abspath(__file__))
420 | #更改(确保)当前所在目录是工作目录
421 | os.chdir(path)
422 | print(os.getcwd())
423 | home_dir = os.environ['HOME']
424 | file_dir = home_dir + "/dataset/PointNetGPD/ycb_rgbd/0*/*.jpg" #获取模型的路径
425 | #遍历(相对路径,相对于工作目录的路径)data/ycb_rgbd中,所有的以0开头的文件夹,并再次遍历该文件夹中的jpg图片
426 | fl = np.array(glob.glob(file_dir))
427 | print(fl[0])
428 | np.random.shuffle(fl)
429 | #获取cpu核心数
430 | cores = mp.cpu_count()
431 | #构建cpu的线程数
432 | pool = mp.Pool(processes=1)
433 | pool.map(generate, fl)
434 |
435 | if __name__ == "__main__":
436 | main()
437 |
--------------------------------------------------------------------------------
/data/1v_500_2class_ourway2sample.model:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/1v_500_2class_ourway2sample.model
--------------------------------------------------------------------------------
/data/1v_750_2class_ourway2sample.model:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/1v_750_2class_ourway2sample.model
--------------------------------------------------------------------------------
/data/1v_750_2class_ourway2sample0.model:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/1v_750_2class_ourway2sample0.model
--------------------------------------------------------------------------------
/data/image-20210311111731964.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/image-20210311111731964.png
--------------------------------------------------------------------------------
/data/image-20210311162402868.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/image-20210311162402868.png
--------------------------------------------------------------------------------
/data/在线检测时的夹爪各项参数定义.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/在线检测时的夹爪各项参数定义.png
--------------------------------------------------------------------------------
/data/在线检测时的夹爪数学模型各点以及夹爪坐标系定义.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/data/在线检测时的夹爪数学模型各点以及夹爪坐标系定义.png
--------------------------------------------------------------------------------
/main_1v.py:
--------------------------------------------------------------------------------
1 | #在python3中训练
2 | import argparse
3 | import os
4 | import time
5 | import pickle
6 | import sys
7 |
8 | print(sys.path)
9 | #sys.path.append('/home/wgk/code/PointNetGPD/model')
10 |
11 |
12 | import torch
13 | import torch.utils.data
14 | import torch.nn as nn
15 | import torch.nn.functional as F
16 | import torch.optim as optim
17 | import numpy as np
18 | #from tensorboardX import SummaryWriter
19 | #现在pytorch已经集成了tensorboard了
20 | from torch.utils.tensorboard import SummaryWriter
21 | from torch.optim.lr_scheduler import StepLR
22 |
23 | ##################/home/wgk/code/PointNetGPD/PointNetGPD/model 包###########################
24 |
25 | #需要安装pcl
26 | from model.dataset import *
27 | #pointnet的具体网络结构
28 | from model.pointnet import PointNetCls, DualPointNetCls
29 |
30 |
31 | import argparse
32 |
33 | #创建一个argparse模块中的ArgumentParser类对象
34 | # description只是解释一下,这个类对象是做什么的
35 | parser = argparse.ArgumentParser(description='pointnetGPD')
36 | #添加一个参数 tag 这个可以自定义,主要用于区分指定训练结果的存放文件夹等
37 | parser.add_argument('--tag', type=str, default='default')
38 | #默认epoch训练的轮数,大小为200
39 | parser.add_argument('--epoch', type=int, default=200)
40 | #添加模式参数,指定从train或者test中选择,且是必选项
41 | parser.add_argument('--mode', choices=['train', 'test'], required=True)
42 | #设定batch-size,默认是1
43 | parser.add_argument('--batch-size', type=int, default=1)
44 | #意思是,当命令中出现 --cuda的时候,就把 object.cuda的值设置为真
45 | parser.add_argument('--cuda', action='store_true')
46 | #添加参数 选择使用的gpu编号,默认使用0号gpu
47 | parser.add_argument('--gpu', type=int, default=0)
48 | parser.add_argument('--lr', type=float, default=0.005)
49 | parser.add_argument('--load-model', type=str, default='')
50 |
51 |
52 | parser.add_argument('--load-epoch', type=int, default=-1)
53 | parser.add_argument('--model-path', type=str, default='./assets/learned_models',
54 | help='pre-trained model path')
55 | parser.add_argument('--data-path', type=str, default=os.environ['HOME']+'/dataset/PointNetGPD', help='data path')
56 | #设置每隔多少次迭代,就打印出来一次loss还有训练进度
57 | parser.add_argument('--log-interval', type=int, default=10)
58 | #保存间隔,设置每隔几个epoch就保存一次模型,比如epoch=10就是训练10轮之后就保存当前的训练模型
59 | parser.add_argument('--save-interval', type=int, default=1)
60 |
61 |
62 | #此时,使用对象parser中的解析函数.parse_args(),读取命令行给出的参数,集合成namspace 返回给args
63 | #例子: python main_1v.py --epoch 200 --mode train --batch-size 3
64 | args = parser.parse_args()
65 |
66 | #查看是否安装好了cuda
67 | args.cuda = args.cuda if torch.cuda.is_available else False
68 | #为当前的gpu设置随机种子;这样在以后运行该程序的时候,随机数都是相同的,不会每次运行都变化一次
69 | #作用主要是为了固定随机初始化的权重值,这样就可以在每次重新从头训练网络的时候,权重值虽然是随机的
70 | #但是都是固定的,不会每次都在变化
71 | #其中的seed值,可以随便写
72 | """
73 | https://blog.csdn.net/weixin_43002433/article/details/104706950?utm_
74 | medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1
75 | .add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-Blog
76 | CommendFromMachineLearnPai2-1.add_param_isCf
77 | """
78 | if args.cuda:
79 | torch.cuda.manual_seed(1)
80 |
81 | #获取当前文件所在目录(文件夹)的绝对路径
82 | path=os.path.dirname(os.path.abspath(__file__))
83 | #更改(确保)当前所在目录是工作目录
84 | os.chdir(path)
85 | print(os.getcwd())
86 | #获取当前的时间,年月日 时间
87 | current_time = time.strftime("%Y-%m-%dT%H:%M", time.localtime())
88 | logger = SummaryWriter(os.path.join('./assets/log/', current_time))
89 |
90 | #设置numpy的随机数种子,但是注意,这里的随机数种子是随着系统时间变化的,因此每次运行出现的随机数都是不同的
91 | np.random.seed(int(time.time()))
92 |
93 | """
94 | 使用单线程进行数据集导入时候,有时候比较慢,会阻碍到计算的过程,于是考虑用多个线程进行数据的导入
95 | 如果参数num_workers设置的大于1,就是指定了多个线程,于是考虑使用多个线程导入数据,防止影响计算
96 | 每个线程叫一个"worker",这个 worker_init_fn就是定义每个线程worker初始化的时候,需要执行哪些操作
97 | 其中,pid就是子线程的线程号,直接写成这样就行
98 | """
99 | def worker_init_fn(pid):
100 | """
101 | 为后台工作进程设置唯一但具有确定性的随机种子,只需要对numpy设置种子就行了,
102 | pytorch和python的随机数生成器会自己管理自己的子线程?
103 | 设置torch.initial_seed() % (2**31-1)取余数,是因为numpy的种子范围是0到2**31-1
104 | """
105 | np.random.seed(torch.initial_seed() % (2**31-1))
106 |
107 |
108 | """
109 | 将样本采样器返回的list中对应的样构建成一个minibatch的tensor,自定义的tensor的话
110 | 对于map型的数据集,且有多个读取进程的情况,采样器会先将样本集分成一个个batch,返回每个batch的样本的索引的 list,
111 | 再根据my_collate函数,说明如何把这些索引指定的样本整合成一个data Tensor和lable Tensor
112 | """
113 | def my_collate(batch):
114 | batch = list(filter(lambda x:x is not None, batch))
115 | return torch.utils.data.dataloader.default_collate(batch)
116 |
117 | #
118 | grasp_points_num=750
119 | thresh_good=0.6
120 | thresh_bad=0.6
121 | #设置点是只有xyz ?
122 | point_channel=3
123 |
124 |
125 | """
126 | 数据加载器,主要实现数据加载到网络中的相关作用,核心类是torch.utils.data.DataLoader
127 | DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
128 | batch_sampler=None, num_workers=0, collate_fn=None,
129 | pin_memory=False, drop_last=False, timeout=0,
130 | worker_init_fn=None)
131 |
132 | dataset:加载的数据集,继承自torch.utils.data.Dataset类的自定义类的对象,或者就是torch.utils.data.Dataset类的对象
133 | batch_size:batch size,就是batchs ize
134 | shuffle::是否将数据打乱
135 | sampler: 样本抽样,后续会详细介绍
136 | batch_sampler,从注释可以看出,其和batch_size、shuffle等参数是互斥的,一般采用默认
137 | num_workers:使用多进程加载的进程数,0代表不使用多进程
138 | collate_fn: 如何将多个样本数据拼接成一个batch,一般使用默认的拼接方式即可
139 | pin_memory:是否将数据保存在pin memory区,pin memory中的数据转到GPU会快一些
140 | drop_last:dataset中的数据个数可能不是batch_size的整数倍,drop_last为True会将多出来不足一个batch的数据丢弃
141 |
142 | """
143 | train_loader = torch.utils.data.DataLoader(
144 | #设置dataset,传入的是继承了torch.utils.data.Dataset类的子类的实例对象 class PointGraspOneViewDataset(torch.utils.data.Dataset):
145 | PointGraspOneViewDataset(
146 | #设置夹爪内部的点数量最少要有多少个
147 | grasp_points_num=grasp_points_num,
148 | path=args.data_path,
149 | prefix='panda',
150 | tag='train',
151 | grasp_amount_per_file=16800, #每个物体已经生成的抓取点云个数,140×120 (单物体的生成140个不同抓取姿态×单物体共有120个不同视角点云)
152 | thresh_good=thresh_good,
153 | thresh_bad=thresh_bad,
154 | ),
155 | #设置batch_size
156 | batch_size=args.batch_size,
157 | #设置使用多少个子线程来导入数据,如果设置为0,那就是直接把数据导入到主线程中
158 | num_workers=32,
159 | #如果设置为True,就使得Tensor数据最开始存放于内存的锁页内存中,这样将内存Tensor转移到GPU显存就会快一些
160 | # 当计算机内存充足的时候,选择True,如果不充足,可能使用到虚拟内存的时候,就写为False
161 | pin_memory=True,
162 | #如果设置为True,会默认构建一个乱序采样器(为False时,构建顺序采样器);每次epoch之后都会把数据集打乱
163 | shuffle=True,
164 |
165 | #
166 | worker_init_fn=worker_init_fn,
167 | #collate_fn函数,将sampler返回的数据list合并成为一个整体的tensor,作为一个mini-batch
168 | collate_fn=my_collate,
169 | #设置,如何处理训练到最后,数据集长度不足一个batch_size时的数据,True就抛弃,否则保留
170 | drop_last=True,
171 | )
172 | """
173 | 测试时候用的数据集加载器;这里的test数据集是用来测试训练好的网络的准确率
174 | """
175 | test_loader = torch.utils.data.DataLoader(
176 | PointGraspOneViewDataset(
177 | grasp_points_num=grasp_points_num,
178 | path=args.data_path,
179 | prefix='panda',
180 | #设置标签为test
181 | tag='test',
182 | grasp_amount_per_file=500, #
183 | thresh_good=thresh_good,
184 | thresh_bad=thresh_bad,
185 | with_obj=True,
186 | ),
187 | batch_size=args.batch_size,
188 | num_workers=32,
189 | pin_memory=True,
190 | shuffle=True,
191 | worker_init_fn=worker_init_fn,
192 | collate_fn=my_collate,
193 | )
194 |
195 | is_resume = 0
196 | if args.load_model and args.load_epoch != -1:
197 | is_resume = 1
198 | #如果是测试模式
199 | if is_resume or args.mode == 'test':
200 | #加载网络结构和参数,加载到命令行 指定的gpu中
201 | model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
202 |
203 | model.device_ids = [args.gpu]
204 | print('load model {}'.format(args.load_model))
205 | #如果是训练模式,就加载模型
206 | else:
207 | model = PointNetCls(num_points=grasp_points_num, input_chann=point_channel, k=2)
208 |
209 |
210 |
211 | #如果命令行出现了cuda字眼(args.cuda将会自动设置为True)
212 | if args.cuda:
213 | if args.gpu != -1:
214 | #设置为指定编号的gpu,使用指定编号的gpu进行训练
215 | torch.cuda.set_device(args.gpu)
216 | #将网络模型的所有参数等都转移到gpu中(刚指定的那个)
217 | model = model.cuda()
218 | else:
219 | #如果args.gpu=-1
220 | device_id = [0]
221 | #在这里选择使用哪个gpu
222 | torch.cuda.set_device(device_id[0])
223 | #这句话,使得原有的model(网络),重构成为一个新的model,这个模型能够调用多个gpu同时运行
224 | model = nn.DataParallel(model,device_id).cuda()
225 | #选择adam优化器
226 | optimizer = optim.Adam(model.parameters(), lr=args.lr)
227 | """
228 | from torch.optim.lr_scheduler import StepLR
229 | StepLR类主要用来调整学习率,lr=learning rate,让学习率随着epoch变化
230 | 构造函数 输入数据optimizer选择优化器;选择step_size=30, gamma=0.5
231 | 成员函数StepLR.step()是用来
232 | """
233 | scheduler = StepLR(optimizer, step_size=30, gamma=0.5)
234 |
235 |
236 | #训练函数,仅仅是一个epoch,其中包含了很多batch
237 | def train(model, loader, epoch):
238 | #每一次epoch之前,都更新一下学习率
239 | scheduler.step()
240 | #调用train函数训练;这个model是提前设置好的pointnet
241 | model.train()
242 |
243 | torch.set_grad_enabled(True)
244 | correct = 0
245 | dataset_size = 0
246 | """
247 | 注意,如果使用了多线程导入数据集的情况下,在当调用enumerate时,将会在此时在后台创建多线程导入数据
248 | loader是一个可迭代对象,索引是batch_idx,对象是(data, target)
249 | 这里实现的效果是:一个一个batch的训练网络,直到一个epoch训练完毕
250 | """
251 | for batch_idx, (data, target) in enumerate(loader):
252 | #print(len(data),"data len is")
253 | """
254 | 在实际的实验过程中发现,当某一个batch(比如剩下来的某一个batch)中间只含有一个sample
255 | 那么很有可能会报错,这里判断一下本次的batch中是不是只有一个样本,如果只有一个样本
256 | 那就跳过这个batch
257 | """
258 | if len(data) <=1:
259 | continue
260 |
261 | dataset_size += data.shape[0]
262 | data, target = data.float(), target.long().squeeze()
263 | #如果使用cuda的话
264 | if args.cuda:
265 | data, target = data.cuda(), target.cuda()
266 | optimizer.zero_grad()
267 | #这里进行前向传播,并输出结果 但是这个output是什么
268 | output, _ = model(data)
269 | #计算loss
270 | loss = F.nll_loss(output, target)
271 | #反向传播,更新权重
272 | loss.backward()
273 | #进行学习率的优化
274 | optimizer.step()
275 |
276 | pred = output.data.max(1, keepdim=True)[1]
277 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
278 | #设置每隔多少次迭代(batch)打印一次loss,这里默认是10次迭代
279 | if batch_idx % args.log_interval == 0:
280 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
281 | #当前第几个epoch, 当前训练到该epoch中的第几个batch,数据集总共有多大
282 | epoch, batch_idx * args.batch_size, len(loader.dataset),
283 | #计算当前训练百分比
284 | 100. * batch_idx * args.batch_size / len(loader.dataset), loss.item(), args.tag))
285 |
286 | """在tensorboard上面绘制折线图
287 | def add_scalar(self, tag, scalar_value, global_step=None, walltime=None):
288 | 参数含义:
289 | tag (string): 显示什么名称
290 | scalar_value (float or string/blobname): 需要打印出来(同时会保存下来)的变量
291 | global_step (int): 就是坐标系横轴的下标显示什么变量
292 | walltime (float): Optional override default walltime (time.time())
293 | with seconds after epoch of event
294 | """
295 | logger.add_scalar('train_loss',
296 | loss.cpu().item(), #这里的loss.cpu是不是利用到了cpu? 做了什么计算?
297 | batch_idx + epoch * len(loader))#横坐标下标是迭代次数,训练了几次epoch
298 | """len(loader)和len(loader.dataset)区别
299 | len(loader.dataset) 返回的是数据集中的sample的数量
300 | len(loader) 返回的是 len(loader.dataset)/batch_size向上取整 就是一个epoch中包含有多少个batch(一个epoch迭代多少次)
301 | """
302 | return float(correct)/float(dataset_size)
303 |
304 | #测试训练好的网络
305 | def test(model, loader):
306 | model.eval()
307 | torch.set_grad_enabled(False)
308 | test_loss = 0
309 | correct = 0
310 | dataset_size = 0
311 | da = {}
312 | db = {}
313 | res = []
314 | for data, target, obj_name in loader:
315 | dataset_size += data.shape[0]
316 | data, target = data.float(), target.long().squeeze()
317 | if args.cuda:
318 | data, target = data.cuda(), target.cuda()
319 | output, _ = model(data) # N*C
320 | test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
321 | pred = output.data.max(1, keepdim=True)[1]
322 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
323 | for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
324 | res.append((i, j[0], k))
325 |
326 | test_loss /= len(loader.dataset)
327 | acc = float(correct)/float(dataset_size)
328 | return acc, test_loss
329 |
330 |
331 | def main():
332 | #
333 | if args.mode == 'train':
334 | for epoch_i in range(is_resume*args.load_epoch, args.epoch):
335 | #训练一个epoch,把网络模型、数据集、当前是第几轮epoch的编号,都写进去
336 | acc_train = train(model, train_loader, epoch_i)
337 | #训练完毕i,精度等于多少
338 | print('Train done, acc={}'.format(acc_train))
339 | #使用测试数据集进行测试
340 | acc, loss = test(model, test_loader)
341 | #打印测试出的精度和loss
342 | print('Test done, acc={}, loss={}'.format(acc, loss))
343 | #然后把训练的结果测试的结果,存放在logger中,之后使用折线图打印出来
344 | logger.add_scalar('train_acc', acc_train, epoch_i)
345 | logger.add_scalar('test_acc', acc, epoch_i)
346 | logger.add_scalar('test_loss', loss, epoch_i)
347 | if epoch_i % args.save_interval == 0:
348 | path = os.path.join(args.model_path, current_time + '_{}.model'.format(epoch_i))
349 | #如果满足要求了,就保存下来,注意这里,需要选定_use_new_zipfile_serialization=False
350 | torch.save(model, path,_use_new_zipfile_serialization=False)
351 | print('Save model @ {}'.format(path))
352 | else:
353 | print('testing...')
354 | acc, loss = test(model, test_loader)
355 | print('Test done, acc={}, loss={}'.format(acc, loss))
356 |
357 | if __name__ == "__main__":
358 | main()
359 |
--------------------------------------------------------------------------------
/main_1v_gpd.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 | import time
4 | import pickle
5 |
6 | import torch
7 | import torch.utils.data
8 | import torch.nn as nn
9 | import torch.nn.functional as F
10 | import torch.optim as optim
11 | import numpy as np
12 | from tensorboardX import SummaryWriter
13 | from torch.optim.lr_scheduler import StepLR
14 |
15 | from model.dataset import *
16 | from model.gpd import *
17 |
18 | parser = argparse.ArgumentParser(description='pointnetGPD, gpd baseline')
19 | parser.add_argument('--tag', type=str, default='default')
20 | parser.add_argument('--epoch', type=int, default=200)
21 | parser.add_argument('--mode', choices=['train', 'test'], required=True)
22 | parser.add_argument('--batch-size', type=int, default=1)
23 | parser.add_argument('--cuda', action='store_true')
24 | parser.add_argument('--gpu', type=int, default=0)
25 | parser.add_argument('--lr', type=float, default=0.001)
26 | parser.add_argument('--load-model', type=str, default='')
27 | parser.add_argument('--load-epoch', type=int, default=-1)
28 | parser.add_argument('--model-path', type=str, default='./assets/learned_models',
29 | help='pre-trained model path')
30 | parser.add_argument('--data-path', type=str, default='./data', help='data path')
31 | parser.add_argument('--log-interval', type=int, default=10)
32 | parser.add_argument('--save-interval', type=int, default=1)
33 |
34 | args = parser.parse_args()
35 |
36 | args.cuda = args.cuda if torch.cuda.is_available else False
37 |
38 | if args.cuda:
39 | torch.cuda.manual_seed(1)
40 |
41 | logger = SummaryWriter(os.path.join('./assets/log/', 'GPD_{}'.format(args.tag)))
42 | np.random.seed(int(time.time()))
43 |
44 | def worker_init_fn(pid):
45 | np.random.seed(torch.initial_seed() % (2**31-1))
46 |
47 | def my_collate(batch):
48 | batch = list(filter(lambda x:x is not None, batch))
49 | return torch.utils.data.dataloader.default_collate(batch)
50 |
51 | grasp_points_num=750
52 | thresh_good=0.6
53 | thresh_bad=0.6
54 |
55 | input_size=60
56 | input_chann=3 # 3 # 12
57 |
58 | train_loader = torch.utils.data.DataLoader(
59 | PointGraspOneViewDataset(
60 | grasp_points_num=grasp_points_num,
61 | path=args.data_path,
62 | tag='train',
63 | grasp_amount_per_file=6500,
64 | thresh_good=thresh_good,
65 | thresh_bad=thresh_bad,
66 | projection=True,
67 | project_chann=input_chann,
68 | project_size=input_size,
69 | ),
70 | batch_size=args.batch_size,
71 | num_workers=32,
72 | pin_memory=True,
73 | shuffle=True,
74 | worker_init_fn=worker_init_fn,
75 | collate_fn=my_collate,
76 | )
77 |
78 | test_loader = torch.utils.data.DataLoader(
79 | PointGraspOneViewDataset(
80 | grasp_points_num=grasp_points_num,
81 | path=args.data_path,
82 | tag='test',
83 | grasp_amount_per_file=500,
84 | thresh_good=thresh_good,
85 | thresh_bad=thresh_bad,
86 | with_obj=True,
87 | projection=True,
88 | project_chann=input_chann,
89 | project_size=input_size,
90 | ),
91 | batch_size=args.batch_size,
92 | num_workers=32,
93 | pin_memory=True,
94 | shuffle=True,
95 | worker_init_fn=worker_init_fn,
96 | collate_fn=my_collate,
97 | )
98 |
99 | is_resume = 0
100 | if args.load_model and args.load_epoch != -1:
101 | is_resume = 1
102 |
103 | if is_resume or args.mode == 'test':
104 | model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
105 | model.device_ids = [args.gpu]
106 | print('load model {}'.format(args.load_model))
107 | else:
108 | model = GPDClassifier(input_chann)
109 | if args.cuda:
110 | if args.gpu != -1:
111 | torch.cuda.set_device(args.gpu)
112 | model = model.cuda()
113 | else:
114 | device_id = [0,1]
115 | torch.cuda.set_device(device_id[0])
116 | model = nn.DataParallel(model, device_ids=device_id).cuda()
117 | optimizer = optim.Adam(model.parameters(), lr=args.lr)
118 | scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
119 |
120 | def train(model, loader, epoch):
121 | scheduler.step()
122 | model.train()
123 | torch.set_grad_enabled(True)
124 | correct = 0
125 | dataset_size = 0
126 | for batch_idx, (data, target) in enumerate(loader):
127 | dataset_size += data.shape[0]
128 | data, target = data.float(), target.long().squeeze()
129 | if args.cuda:
130 | data, target = data.cuda(), target.cuda()
131 | optimizer.zero_grad()
132 | output = model(data)
133 | loss = F.nll_loss(output, target)
134 | loss.backward()
135 | optimizer.step()
136 | pred = output.data.max(1, keepdim=True)[1]
137 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
138 | if batch_idx % args.log_interval == 0:
139 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
140 | epoch, batch_idx * len(data), len(loader.dataset),
141 | 100. * batch_idx * len(data) / len(loader.dataset), loss.item(), args.tag))
142 | logger.add_scalar('train_loss', loss.cpu().item(),
143 | batch_idx + epoch * len(loader))
144 | return float(correct)/float(dataset_size)
145 |
146 |
147 | def test(model, loader):
148 | model.eval()
149 | torch.set_grad_enabled(False)
150 | test_loss = 0
151 | correct = 0
152 | dataset_size = 0
153 | da = {}
154 | db = {}
155 | res = []
156 | for batch_idx, (data, target, obj_name) in enumerate(loader):
157 | dataset_size += data.shape[0]
158 | data, target = data.float(), target.long().squeeze()
159 | if args.cuda:
160 | data, target = data.cuda(), target.cuda()
161 | output = model(data) # N*C
162 | test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
163 | pred = output.data.max(1, keepdim=True)[1]
164 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
165 | for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
166 | res.append((i, j[0], k))
167 |
168 | test_loss /= len(loader.dataset)
169 | acc = float(correct)/float(dataset_size)
170 | return acc, test_loss
171 |
172 |
173 | def main():
174 | if args.mode == 'train':
175 | for epoch in range(is_resume*args.load_epoch, args.epoch):
176 | acc_train = train(model, train_loader, epoch)
177 | print('Train done, acc={}'.format(acc_train))
178 | acc, loss = test(model, test_loader)
179 | print('Test done, acc={}, loss={}'.format(acc, loss))
180 | logger.add_scalar('train_acc', acc_train, epoch)
181 | logger.add_scalar('test_acc', acc, epoch)
182 | logger.add_scalar('test_loss', loss, epoch)
183 | if epoch % args.save_interval == 0:
184 | path = os.path.join(args.model_path, args.tag + '_{}.model'.format(epoch))
185 | torch.save(model, path)
186 | print('Save model @ {}'.format(path))
187 | else:
188 | print('testing...')
189 | acc, loss = test(model, test_loader)
190 | print('Test done, acc={}, loss={}'.format(acc, loss))
191 |
192 | if __name__ == "__main__":
193 | main()
194 |
--------------------------------------------------------------------------------
/main_1v_mc.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 | import time
4 | import pickle
5 |
6 | import torch
7 | import torch.utils.data
8 | import torch.nn as nn
9 | import torch.nn.functional as F
10 | import torch.optim as optim
11 | import numpy as np
12 | from tensorboardX import SummaryWriter
13 | from torch.optim.lr_scheduler import StepLR
14 |
15 | from model.dataset import *
16 | from model.pointnet import PointNetCls, DualPointNetCls
17 |
18 | parser = argparse.ArgumentParser(description='pointnetGPD')
19 | parser.add_argument('--tag', type=str, default='default')
20 | parser.add_argument('--epoch', type=int, default=200)
21 | parser.add_argument('--mode', choices=['train', 'test'], required=True)
22 | parser.add_argument('--batch-size', type=int, default=1)
23 | parser.add_argument('--cuda', action='store_true')
24 | parser.add_argument('--gpu', type=int, default=0)
25 | parser.add_argument('--lr', type=float, default=0.005)
26 | parser.add_argument('--load-model', type=str, default='')
27 | parser.add_argument('--load-epoch', type=int, default=-1)
28 | parser.add_argument('--model-path', type=str, default='./assets/learned_models',
29 | help='pre-trained model path')
30 | parser.add_argument('--data-path', type=str, default='./data', help='data path')
31 | parser.add_argument('--log-interval', type=int, default=10)
32 | parser.add_argument('--save-interval', type=int, default=1)
33 |
34 | args = parser.parse_args()
35 |
36 | args.cuda = args.cuda if torch.cuda.is_available else False
37 |
38 | if args.cuda:
39 | torch.cuda.manual_seed(1)
40 |
41 | logger = SummaryWriter(os.path.join('./assets/log/', args.tag))
42 | np.random.seed(int(time.time()))
43 |
44 | def worker_init_fn(pid):
45 | np.random.seed(torch.initial_seed() % (2**31-1))
46 |
47 | def my_collate(batch):
48 | batch = list(filter(lambda x:x is not None, batch))
49 | return torch.utils.data.dataloader.default_collate(batch)
50 |
51 | grasp_points_num=750
52 | point_channel=3
53 | # FCs for dataset
54 | # fc_list_sub1 = np.arange(2.0, 0.75, -0.4)
55 | # fc_list_sub2 = np.arange(0.5, 0.36, -0.05)
56 | # ->[0.4, 0.5] class 1 (0.4, 0.45, 0.5)
57 | # ->(0.5, 1.2] class 2 (0.8, 1.2)
58 | # ->(1.2, 2.0] class 3 (1.6, 2.0)
59 | thresh_bad=1.2
60 | thresh_good=0.5
61 |
62 | train_loader = torch.utils.data.DataLoader(
63 | PointGraspOneViewMultiClassDataset(
64 | grasp_points_num=grasp_points_num,
65 | path=args.data_path,
66 | tag='train',
67 | grasp_amount_per_file=6500,
68 | thresh_good=thresh_good,
69 | thresh_bad=thresh_bad,
70 | ),
71 | batch_size=args.batch_size,
72 | num_workers=32,
73 | pin_memory=True,
74 | shuffle=True,
75 | worker_init_fn=worker_init_fn,
76 | collate_fn=my_collate,
77 | )
78 |
79 | test_loader = torch.utils.data.DataLoader(
80 | PointGraspOneViewMultiClassDataset(
81 | grasp_points_num=grasp_points_num,
82 | path=args.data_path,
83 | tag='test',
84 | grasp_amount_per_file=500,
85 | thresh_good=thresh_good,
86 | thresh_bad=thresh_bad,
87 | with_obj=True,
88 | ),
89 | batch_size=args.batch_size,
90 | num_workers=32,
91 | pin_memory=True,
92 | shuffle=True,
93 | worker_init_fn=worker_init_fn,
94 | collate_fn=my_collate,
95 | )
96 |
97 | is_resume = 0
98 | if args.load_model and args.load_epoch != -1:
99 | is_resume = 1
100 |
101 | if is_resume or args.mode == 'test':
102 | model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
103 | model.device_ids = [args.gpu]
104 | print('load model {}'.format(args.load_model))
105 | else:
106 | model = PointNetCls(num_points=grasp_points_num, input_chann=point_channel, k=3)
107 | if args.cuda:
108 | if args.gpu != -1:
109 | torch.cuda.set_device(args.gpu)
110 | model = model.cuda()
111 | else:
112 | device_id = [0,1,2,3]
113 | torch.cuda.set_device(device_id[0])
114 | model = nn.DataParallel(model, device_ids=device_id).cuda()
115 | optimizer = optim.Adam(model.parameters(), lr=args.lr)
116 | scheduler = StepLR(optimizer, step_size=30, gamma=0.5)
117 |
118 | def train(model, loader, epoch):
119 | scheduler.step()
120 | model.train()
121 | torch.set_grad_enabled(True)
122 | correct = 0
123 | dataset_size = 0
124 | for batch_idx, (data, target) in enumerate(loader):
125 | dataset_size += data.shape[0]
126 | data, target = data.float(), target.long().squeeze()
127 | if args.cuda:
128 | data, target = data.cuda(), target.cuda()
129 | optimizer.zero_grad()
130 | output, _ = model(data)
131 | loss = F.nll_loss(output, target)
132 | loss.backward()
133 | optimizer.step()
134 | pred = output.data.max(1, keepdim=True)[1]
135 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
136 | if batch_idx % args.log_interval == 0:
137 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
138 | epoch, batch_idx * args.batch_size, len(loader.dataset),
139 | 100. * batch_idx * args.batch_size / len(loader.dataset), loss.item(), args.tag))
140 | logger.add_scalar('train_loss', loss.cpu().item(),
141 | batch_idx + epoch * len(loader))
142 | return float(correct)/float(dataset_size)
143 |
144 |
145 | def test(model, loader):
146 | model.eval()
147 | torch.set_grad_enabled(False)
148 | test_loss = 0
149 | correct = 0
150 | dataset_size = 0
151 | da = {}
152 | db = {}
153 | res = []
154 | for data, target, obj_name in loader:
155 | dataset_size += data.shape[0]
156 | data, target = data.float(), target.long().squeeze()
157 | if args.cuda:
158 | data, target = data.cuda(), target.cuda()
159 | output, _ = model(data) # N*C
160 | test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
161 | pred = output.data.max(1, keepdim=True)[1]
162 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
163 | for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
164 | res.append((i, j[0], k))
165 |
166 | test_loss /= len(loader.dataset)
167 | acc = float(correct)/float(dataset_size)
168 | return acc, test_loss
169 |
170 |
171 | def main():
172 | if args.mode == 'train':
173 | for epoch in range(is_resume*args.load_epoch, args.epoch):
174 | acc_train = train(model, train_loader, epoch)
175 | print('Train done, acc={}'.format(acc_train))
176 | acc, loss = test(model, test_loader)
177 | print('Test done, acc={}, loss={}'.format(acc, loss))
178 | logger.add_scalar('train_acc', acc_train, epoch)
179 | logger.add_scalar('test_acc', acc, epoch)
180 | logger.add_scalar('test_loss', loss, epoch)
181 | if epoch % args.save_interval == 0:
182 | path = os.path.join(args.model_path, args.tag + '_{}.model'.format(epoch))
183 | torch.save(model, path)
184 | print('Save model @ {}'.format(path))
185 | else:
186 | print('testing...')
187 | acc, loss = test(model, test_loader)
188 | print('Test done, acc={}, loss={}'.format(acc, loss))
189 |
190 | if __name__ == "__main__":
191 | main()
192 |
--------------------------------------------------------------------------------
/main_fullv.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 | import time
4 | import pickle
5 |
6 | import torch
7 | import torch.utils.data
8 | import torch.nn as nn
9 | import torch.nn.functional as F
10 | import torch.optim as optim
11 | import numpy as np
12 | from tensorboardX import SummaryWriter
13 | from torch.optim.lr_scheduler import StepLR
14 |
15 | from model.dataset import PointGraspDataset, PointGraspMultiViewDataset
16 | from model.pointnet import PointNetCls, DualPointNetCls
17 |
18 | parser = argparse.ArgumentParser(description='pointnetGPD')
19 | parser.add_argument('--tag', type=str, default='default')
20 | parser.add_argument('--epoch', type=int, default=200)
21 | parser.add_argument('--mode', choices=['train', 'test'], required=True)
22 | parser.add_argument('--batch-size', type=int, default=1)
23 | parser.add_argument('--cuda', action='store_true')
24 | parser.add_argument('--gpu', type=int, default=0)
25 | parser.add_argument('--lr', type=float, default=0.005)
26 | parser.add_argument('--load-model', type=str, default='')
27 | parser.add_argument('--load-epoch', type=int, default=-1)
28 | parser.add_argument('--model-path', type=str, default='./assets/learned_models',
29 | help='pre-trained model path')
30 | parser.add_argument('--data-path', type=str, default='./data', help='data path')
31 | parser.add_argument('--log-interval', type=int, default=10)
32 | parser.add_argument('--save-interval', type=int, default=1)
33 |
34 | args = parser.parse_args()
35 |
36 | args.cuda = args.cuda if torch.cuda.is_available else False
37 |
38 | if args.cuda:
39 | torch.cuda.manual_seed(1)
40 |
41 | logger = SummaryWriter(os.path.join('./assets/log/', args.tag))
42 | np.random.seed(int(time.time()))
43 |
44 | def worker_init_fn(pid):
45 | np.random.seed(torch.initial_seed() % (2**31-1))
46 |
47 | def my_collate(batch):
48 | batch = list(filter(lambda x:x is not None, batch))
49 | return torch.utils.data.dataloader.default_collate(batch)
50 |
51 | grasp_points_num=1000
52 | obj_points_num=50000
53 | pc_file_used_num=20
54 | thresh_good=0.6
55 | thresh_bad=0.6
56 | point_channel=3
57 |
58 | train_loader = torch.utils.data.DataLoader(
59 | PointGraspDataset(
60 | obj_points_num=obj_points_num,
61 | grasp_points_num=grasp_points_num,
62 | pc_file_used_num=pc_file_used_num,
63 | path=args.data_path,
64 | tag='train',
65 | grasp_amount_per_file=6500,
66 | thresh_good=thresh_good,
67 | thresh_bad=thresh_bad,
68 | ),
69 | batch_size=args.batch_size,
70 | num_workers=32,
71 | pin_memory=True,
72 | shuffle=True,
73 | worker_init_fn=worker_init_fn,
74 | collate_fn=my_collate,
75 | )
76 |
77 | test_loader = torch.utils.data.DataLoader(
78 | PointGraspDataset(
79 | obj_points_num=obj_points_num,
80 | grasp_points_num=grasp_points_num,
81 | pc_file_used_num=pc_file_used_num,
82 | path=args.data_path,
83 | tag='test',
84 | grasp_amount_per_file=500,
85 | thresh_good=thresh_good,
86 | thresh_bad=thresh_bad,
87 | with_obj=True,
88 | ),
89 | batch_size=args.batch_size,
90 | num_workers=32,
91 | pin_memory=True,
92 | shuffle=True,
93 | worker_init_fn=worker_init_fn,
94 | collate_fn=my_collate,
95 | )
96 |
97 | is_resume = 0
98 | if args.load_model and args.load_epoch != -1:
99 | is_resume = 1
100 |
101 | if is_resume or args.mode == 'test':
102 | model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
103 | model.device_ids = [args.gpu]
104 | print('load model {}'.format(args.load_model))
105 | else:
106 | model = PointNetCls(num_points=grasp_points_num, input_chann=point_channel, k=2)
107 | if args.cuda:
108 | if args.gpu != -1:
109 | torch.cuda.set_device(args.gpu)
110 | model = model.cuda()
111 | else:
112 | device_id = [0,1,2,3]
113 | torch.cuda.set_device(device_id[0])
114 | model = nn.DataParallel(model, device_ids=device_id).cuda()
115 | optimizer = optim.Adam(model.parameters(), lr=args.lr)
116 | scheduler = StepLR(optimizer, step_size=30, gamma=0.5)
117 |
118 | def train(model, loader, epoch):
119 | scheduler.step()
120 | model.train()
121 | torch.set_grad_enabled(True)
122 | correct = 0
123 | dataset_size = 0
124 | for batch_idx, (data, target) in enumerate(loader):
125 | dataset_size += data.shape[0]
126 | data, target = data.float(), target.long().squeeze()
127 | if args.cuda:
128 | data, target = data.cuda(), target.cuda()
129 | optimizer.zero_grad()
130 | output, _ = model(data)
131 | loss = F.nll_loss(output, target)
132 | loss.backward()
133 | optimizer.step()
134 | pred = output.data.max(1, keepdim=True)[1]
135 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
136 | if batch_idx % args.log_interval == 0:
137 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
138 | epoch, batch_idx * args.batch_size, len(loader.dataset),
139 | 100. * batch_idx * args.batch_size / len(loader.dataset), loss.item(), args.tag))
140 | logger.add_scalar('train_loss', loss.cpu().item(),
141 | batch_idx + epoch * len(loader))
142 | return float(correct)/float(dataset_size)
143 |
144 |
145 | def test(model, loader):
146 | model.eval()
147 | torch.set_grad_enabled(False)
148 | test_loss = 0
149 | correct = 0
150 | dataset_size = 0
151 | da = {}
152 | db = {}
153 | res = []
154 | for data, target, obj_name in loader:
155 | dataset_size += data.shape[0]
156 | data, target = data.float(), target.long().squeeze()
157 | if args.cuda:
158 | data, target = data.cuda(), target.cuda()
159 | output, _ = model(data) # N*C
160 | test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
161 | pred = output.data.max(1, keepdim=True)[1]
162 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
163 | for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
164 | res.append((i, j[0], k))
165 |
166 | test_loss /= len(loader.dataset)
167 | acc = float(correct)/float(dataset_size)
168 | return acc, test_loss
169 |
170 |
171 | def main():
172 | if args.mode == 'train':
173 | for epoch in range(is_resume*args.load_epoch, args.epoch):
174 | acc_train = train(model, train_loader, epoch)
175 | print('Train done, acc={}'.format(acc_train))
176 | acc, loss = test(model, test_loader)
177 | print('Test done, acc={}, loss={}'.format(acc, loss))
178 | logger.add_scalar('train_acc', acc_train, epoch)
179 | logger.add_scalar('test_acc', acc, epoch)
180 | logger.add_scalar('test_loss', loss, epoch)
181 | if epoch % args.save_interval == 0:
182 | path = os.path.join(args.model_path, args.tag + '_{}.model'.format(epoch))
183 | torch.save(model, path)
184 | print('Save model @ {}'.format(path))
185 | else:
186 | print('testing...')
187 | acc, loss = test(model, test_loader)
188 | print('Test done, acc={}, loss={}'.format(acc, loss))
189 |
190 | if __name__ == "__main__":
191 | main()
192 |
--------------------------------------------------------------------------------
/main_fullv_gpd.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 | import time
4 | import pickle
5 |
6 | import torch
7 | import torch.utils.data
8 | import torch.nn as nn
9 | import torch.nn.functional as F
10 | import torch.optim as optim
11 | import numpy as np
12 | from tensorboardX import SummaryWriter
13 | from torch.optim.lr_scheduler import StepLR
14 |
15 | from model.dataset import PointGraspDataset
16 | from model.gpd import *
17 |
18 | parser = argparse.ArgumentParser(description='pointnetGPD, gpd baseline')
19 | parser.add_argument('--tag', type=str, default='default')
20 | parser.add_argument('--epoch', type=int, default=200)
21 | parser.add_argument('--mode', choices=['train', 'test'], required=True)
22 | parser.add_argument('--batch-size', type=int, default=1)
23 | parser.add_argument('--cuda', action='store_true')
24 | parser.add_argument('--gpu', type=int, default=0)
25 | parser.add_argument('--lr', type=float, default=0.001)
26 | parser.add_argument('--load-model', type=str, default='')
27 | parser.add_argument('--load-epoch', type=int, default=-1)
28 | parser.add_argument('--model-path', type=str, default='./assets/learned_models',
29 | help='pre-trained model path')
30 | parser.add_argument('--data-path', type=str, default='./data', help='data path')
31 | parser.add_argument('--log-interval', type=int, default=10)
32 | parser.add_argument('--save-interval', type=int, default=1)
33 |
34 | args = parser.parse_args()
35 |
36 | args.cuda = args.cuda if torch.cuda.is_available else False
37 |
38 | if args.cuda:
39 | torch.cuda.manual_seed(1)
40 |
41 | logger = SummaryWriter(os.path.join('./assets/log/', 'GPD_{}'.format(args.tag)))
42 | np.random.seed(int(time.time()))
43 |
44 | def worker_init_fn(pid):
45 | np.random.seed(torch.initial_seed() % (2**31-1))
46 |
47 | def my_collate(batch):
48 | batch = list(filter(lambda x:x is not None, batch))
49 | return torch.utils.data.dataloader.default_collate(batch)
50 |
51 | grasp_points_num=1000
52 | obj_points_num=50000
53 | pc_file_used_num=20
54 | thresh_good=0.6
55 | thresh_bad=0.6
56 |
57 | input_size=60
58 | input_chann=3 # 3 # 12
59 |
60 | train_loader = torch.utils.data.DataLoader(
61 | PointGraspDataset(
62 | obj_points_num=obj_points_num,
63 | grasp_points_num=grasp_points_num,
64 | pc_file_used_num=pc_file_used_num,
65 | path=args.data_path,
66 | tag='train',
67 | grasp_amount_per_file=6500,
68 | thresh_good=thresh_good,
69 | thresh_bad=thresh_bad,
70 | projection=True,
71 | project_chann=input_chann,
72 | project_size=input_size,
73 | ),
74 | batch_size=args.batch_size,
75 | num_workers=32,
76 | pin_memory=True,
77 | shuffle=True,
78 | worker_init_fn=worker_init_fn,
79 | collate_fn=my_collate,
80 | )
81 |
82 | test_loader = torch.utils.data.DataLoader(
83 | PointGraspDataset(
84 | obj_points_num=obj_points_num,
85 | grasp_points_num=grasp_points_num,
86 | pc_file_used_num=pc_file_used_num,
87 | path=args.data_path,
88 | tag='test',
89 | grasp_amount_per_file=500,
90 | thresh_good=thresh_good,
91 | thresh_bad=thresh_bad,
92 | with_obj=True,
93 | projection=True,
94 | project_chann=input_chann,
95 | project_size=input_size,
96 | ),
97 | batch_size=args.batch_size,
98 | num_workers=32,
99 | pin_memory=True,
100 | shuffle=True,
101 | worker_init_fn=worker_init_fn,
102 | collate_fn=my_collate,
103 | )
104 |
105 | is_resume = 0
106 | if args.load_model and args.load_epoch != -1:
107 | is_resume = 1
108 |
109 | if is_resume or args.mode == 'test':
110 | model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
111 | model.device_ids = [args.gpu]
112 | print('load model {}'.format(args.load_model))
113 | else:
114 | model = GPDClassifier(input_chann)
115 | if args.cuda:
116 | if args.gpu != -1:
117 | torch.cuda.set_device(args.gpu)
118 | model = model.cuda()
119 | else:
120 | device_id = [0,1]
121 | torch.cuda.set_device(device_id[0])
122 | model = nn.DataParallel(model, device_ids=device_id).cuda()
123 | optimizer = optim.Adam(model.parameters(), lr=args.lr)
124 | scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
125 |
126 | def train(model, loader, epoch):
127 | scheduler.step()
128 | model.train()
129 | torch.set_grad_enabled(True)
130 | correct = 0
131 | dataset_size = 0
132 | for batch_idx, (data, target) in enumerate(loader):
133 | dataset_size += data.shape[0]
134 | data, target = data.float(), target.long().squeeze()
135 | if args.cuda:
136 | data, target = data.cuda(), target.cuda()
137 | optimizer.zero_grad()
138 | output = model(data)
139 | loss = F.nll_loss(output, target)
140 | loss.backward()
141 | optimizer.step()
142 | pred = output.data.max(1, keepdim=True)[1]
143 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
144 | if batch_idx % args.log_interval == 0:
145 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
146 | epoch, batch_idx * len(data), len(loader.dataset),
147 | 100. * batch_idx * len(data) / len(loader.dataset), loss.item(), args.tag))
148 | logger.add_scalar('train_loss', loss.cpu().item(),
149 | batch_idx + epoch * len(loader))
150 | return float(correct)/float(dataset_size)
151 |
152 |
153 | def test(model, loader):
154 | model.eval()
155 | torch.set_grad_enabled(False)
156 | test_loss = 0
157 | correct = 0
158 | dataset_size = 0
159 | da = {}
160 | db = {}
161 | res = []
162 | for batch_idx, (data, target, obj_name) in enumerate(loader):
163 | dataset_size += data.shape[0]
164 | data, target = data.float(), target.long().squeeze()
165 | if args.cuda:
166 | data, target = data.cuda(), target.cuda()
167 | output = model(data) # N*C
168 | test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
169 | pred = output.data.max(1, keepdim=True)[1]
170 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
171 | for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
172 | res.append((i, j[0], k))
173 |
174 | test_loss /= len(loader.dataset)
175 | acc = float(correct)/float(dataset_size)
176 | return acc, test_loss
177 |
178 |
179 | def main():
180 | if args.mode == 'train':
181 | for epoch in range(is_resume*args.load_epoch, args.epoch):
182 | acc_train = train(model, train_loader, epoch)
183 | print('Train done, acc={}'.format(acc_train))
184 | acc, loss = test(model, test_loader)
185 | print('Test done, acc={}, loss={}'.format(acc, loss))
186 | logger.add_scalar('train_acc', acc_train, epoch)
187 | logger.add_scalar('test_acc', acc, epoch)
188 | logger.add_scalar('test_loss', loss, epoch)
189 | if epoch % args.save_interval == 0:
190 | path = os.path.join(args.model_path, args.tag + '_{}.model'.format(epoch))
191 | torch.save(model, path)
192 | print('Save model @ {}'.format(path))
193 | else:
194 | print('testing...')
195 | acc, loss = test(model, test_loader)
196 | print('Test done, acc={}, loss={}'.format(acc, loss))
197 |
198 | if __name__ == "__main__":
199 | main()
200 |
--------------------------------------------------------------------------------
/main_fullv_mc.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 | import time
4 | import pickle
5 |
6 | import torch
7 | import torch.utils.data
8 | import torch.nn as nn
9 | import torch.nn.functional as F
10 | import torch.optim as optim
11 | import numpy as np
12 | from tensorboardX import SummaryWriter
13 | from torch.optim.lr_scheduler import StepLR
14 |
15 | from model.dataset import PointGraspDataset, PointGraspMultiClassDataset
16 | from model.pointnet import PointNetCls, DualPointNetCls
17 |
18 | parser = argparse.ArgumentParser(description='pointnetGPD')
19 | parser.add_argument('--tag', type=str, default='default')
20 | parser.add_argument('--epoch', type=int, default=200)
21 | parser.add_argument('--mode', choices=['train', 'test'], required=True)
22 | parser.add_argument('--batch-size', type=int, default=1)
23 | parser.add_argument('--cuda', action='store_true')
24 | parser.add_argument('--gpu', type=int, default=0)
25 | parser.add_argument('--lr', type=float, default=0.005)
26 | parser.add_argument('--load-model', type=str, default='')
27 | parser.add_argument('--load-epoch', type=int, default=-1)
28 | parser.add_argument('--model-path', type=str, default='./assets/learned_models',
29 | help='pre-trained model path')
30 | parser.add_argument('--data-path', type=str, default='./data', help='data path')
31 | parser.add_argument('--log-interval', type=int, default=10)
32 | parser.add_argument('--save-interval', type=int, default=1)
33 |
34 | args = parser.parse_args()
35 |
36 | args.cuda = args.cuda if torch.cuda.is_available else False
37 |
38 | if args.cuda:
39 | torch.cuda.manual_seed(1)
40 |
41 | logger = SummaryWriter(os.path.join('./assets/log/', args.tag))
42 | np.random.seed(int(time.time()))
43 |
44 | def worker_init_fn(pid):
45 | np.random.seed(torch.initial_seed() % (2**31-1))
46 |
47 | def my_collate(batch):
48 | batch = list(filter(lambda x:x is not None, batch))
49 | return torch.utils.data.dataloader.default_collate(batch)
50 |
51 | grasp_points_num=1000
52 | obj_points_num=50000
53 | pc_file_used_num=20
54 | point_channel=3
55 | # FCs for dataset
56 | # fc_list_sub1 = np.arange(2.0, 0.75, -0.4)
57 | # fc_list_sub2 = np.arange(0.5, 0.36, -0.05)
58 | # ->[0.4, 0.5] class 1 (0.4, 0.45, 0.5)
59 | # ->(0.5, 1.2] class 2 (0.8, 1.2)
60 | # ->(1.2, 2.0] class 3 (1.6, 2.0)
61 | thresh_bad=1.2
62 | thresh_good=0.5
63 |
64 | train_loader = torch.utils.data.DataLoader(
65 | PointGraspMultiClassDataset(
66 | obj_points_num=obj_points_num,
67 | grasp_points_num=grasp_points_num,
68 | pc_file_used_num=pc_file_used_num,
69 | path=args.data_path,
70 | tag='train',
71 | grasp_amount_per_file=6500,
72 | thresh_good=thresh_good,
73 | thresh_bad=thresh_bad,
74 | ),
75 | batch_size=args.batch_size,
76 | num_workers=32,
77 | pin_memory=True,
78 | shuffle=True,
79 | worker_init_fn=worker_init_fn,
80 | collate_fn=my_collate,
81 | )
82 |
83 | test_loader = torch.utils.data.DataLoader(
84 | PointGraspMultiClassDataset(
85 | obj_points_num=obj_points_num,
86 | grasp_points_num=grasp_points_num,
87 | pc_file_used_num=pc_file_used_num,
88 | path=args.data_path,
89 | tag='test',
90 | grasp_amount_per_file=500,
91 | thresh_good=thresh_good,
92 | thresh_bad=thresh_bad,
93 | with_obj=True,
94 | ),
95 | batch_size=args.batch_size,
96 | num_workers=32,
97 | pin_memory=True,
98 | shuffle=True,
99 | worker_init_fn=worker_init_fn,
100 | collate_fn=my_collate,
101 | )
102 |
103 | is_resume = 0
104 | if args.load_model and args.load_epoch != -1:
105 | is_resume = 1
106 |
107 | if is_resume or args.mode == 'test':
108 | model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
109 | model.device_ids = [args.gpu]
110 | print('load model {}'.format(args.load_model))
111 | else:
112 | model = PointNetCls(num_points=grasp_points_num, input_chann=point_channel, k=3)
113 | if args.cuda:
114 | if args.gpu != -1:
115 | torch.cuda.set_device(args.gpu)
116 | model = model.cuda()
117 | else:
118 | device_id = [0,1,2,3]
119 | torch.cuda.set_device(device_id[0])
120 | model = nn.DataParallel(model, device_ids=device_id).cuda()
121 | optimizer = optim.Adam(model.parameters(), lr=args.lr)
122 | scheduler = StepLR(optimizer, step_size=30, gamma=0.5)
123 |
124 | def train(model, loader, epoch):
125 | scheduler.step()
126 | model.train()
127 | torch.set_grad_enabled(True)
128 | correct = 0
129 | dataset_size = 0
130 | for batch_idx, (data, target) in enumerate(loader):
131 | dataset_size += data.shape[0]
132 | data, target = data.float(), target.long().squeeze()
133 | if args.cuda:
134 | data, target = data.cuda(), target.cuda()
135 | optimizer.zero_grad()
136 | output, _ = model(data)
137 | loss = F.nll_loss(output, target)
138 | loss.backward()
139 | optimizer.step()
140 | pred = output.data.max(1, keepdim=True)[1]
141 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
142 | if batch_idx % args.log_interval == 0:
143 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
144 | epoch, batch_idx * args.batch_size, len(loader.dataset),
145 | 100. * batch_idx * args.batch_size / len(loader.dataset), loss.item(), args.tag))
146 | logger.add_scalar('train_loss', loss.cpu().item(),
147 | batch_idx + epoch * len(loader))
148 | return float(correct)/float(dataset_size)
149 |
150 |
151 | def test(model, loader):
152 | model.eval()
153 | torch.set_grad_enabled(False)
154 | test_loss = 0
155 | correct = 0
156 | dataset_size = 0
157 | da = {}
158 | db = {}
159 | res = []
160 | for data, target, obj_name in loader:
161 | dataset_size += data.shape[0]
162 | data, target = data.float(), target.long().squeeze()
163 | if args.cuda:
164 | data, target = data.cuda(), target.cuda()
165 | output, _ = model(data) # N*C
166 | test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
167 | pred = output.data.max(1, keepdim=True)[1]
168 | correct += pred.eq(target.view_as(pred)).long().cpu().sum()
169 | for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
170 | res.append((i, j[0], k))
171 |
172 | test_loss /= len(loader.dataset)
173 | acc = float(correct)/float(dataset_size)
174 | return acc, test_loss
175 |
176 |
177 | def main():
178 | if args.mode == 'train':
179 | for epoch in range(is_resume*args.load_epoch, args.epoch):
180 | acc_train = train(model, train_loader, epoch)
181 | print('Train done, acc={}'.format(acc_train))
182 | acc, loss = test(model, test_loader)
183 | print('Test done, acc={}, loss={}'.format(acc, loss))
184 | logger.add_scalar('train_acc', acc_train, epoch)
185 | logger.add_scalar('test_acc', acc, epoch)
186 | logger.add_scalar('test_loss', loss, epoch)
187 | if epoch % args.save_interval == 0:
188 | path = os.path.join(args.model_path, args.tag + '_{}.model'.format(epoch))
189 | torch.save(model, path)
190 | print('Save model @ {}'.format(path))
191 | else:
192 | print('testing...')
193 | acc, loss = test(model, test_loader)
194 | print('Test done, acc={}, loss={}'.format(acc, loss))
195 |
196 | if __name__ == "__main__":
197 | main()
198 |
--------------------------------------------------------------------------------
/main_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | from __future__ import print_function
4 | import argparse
5 | import os
6 | import time
7 | import torch
8 | import torch.utils.data
9 | import torch.nn as nn
10 | import numpy as np
11 | from scipy.stats import mode
12 | import sys
13 | from os import path
14 |
15 | sys.path.append(path.dirname(path.dirname(path.abspath("__file__"))))
16 | parser = argparse.ArgumentParser(description="pointnetGPD")
17 | #使用本py文件的路径作为基准
18 | os.chdir(os.path.dirname(os.path.abspath(__file__)))
19 | print(os.getcwd())
20 | #是否使用cuda
21 | parser.add_argument("--cuda", action="store_true", default=False)
22 | #指定使用的gpu编号
23 | parser.add_argument("--gpu", type=int, default=0)
24 |
25 | #加载训练好的pointnet
26 | parser.add_argument("--load-model", type=str,
27 | default="../data/pointgpd_3class.model")
28 | #是否显示最终的抓取结果
29 | parser.add_argument("--show_final_grasp", action="store_true", default=False)
30 | #是否是托盘抓取?就是待抓取物品有没有托盘
31 | parser.add_argument("--tray_grasp", action="store_true", default=False)
32 | #
33 | parser.add_argument("--using_mp", action="store_true", default=False)
34 |
35 | parser.add_argument("--model_type", type=str)
36 |
37 | args = parser.parse_args()
38 |
39 | args.cuda = args.cuda if torch.cuda.is_available else False
40 |
41 | if args.cuda:
42 | torch.cuda.manual_seed(1)
43 |
44 | np.random.seed(int(time.time()))
45 |
46 |
47 |
48 | if args.model_type == "500":
49 | args.load_model = "./data/1v_500_2class_ourway2sample.model"
50 | elif args.model_type == "750":
51 | args.load_model = "./data/1v_750_2class_ourway2sample.model"
52 | elif args.model_type == "3class": # input points number is 500
53 | args.load_model = "./data/pointgpd_3class.model"
54 | else:
55 | print("Using default model file")
56 | model = torch.load(args.load_model, map_location="cpu")
57 | model.device_ids = [args.gpu]
58 | print("load model {}".format(args.load_model))
59 |
60 | if args.cuda:
61 | model = torch.load(args.load_model, map_location="cuda:{}".format(args.gpu))
62 | if args.gpu != -1:
63 | torch.cuda.set_device(args.gpu)
64 | model = model.cuda()
65 | else:
66 | device_id = [0, 1]
67 | torch.cuda.set_device(device_id[0])
68 | model = nn.DataParallel(model, device_ids=device_id).cuda()
69 | if isinstance(model, torch.nn.DataParallel):
70 | model = model.module
71 |
72 |
73 | def test_network(model_, local_pc):
74 | local_pc = local_pc.T
75 | local_pc = local_pc[np.newaxis, ...]
76 | local_pc = torch.FloatTensor(local_pc)
77 | if args.cuda:
78 | local_pc = local_pc.cuda()
79 | output, _ = model_(local_pc) # N*C
80 | output = output.softmax(1)
81 | pred = output.data.max(1, keepdim=True)[1]
82 | output = output.cpu()
83 | return pred[0], output.data.numpy()
84 |
85 |
86 | def main():
87 | repeat = 10
88 | num_point = 500
89 | model.eval()
90 | torch.set_grad_enabled(False)
91 |
92 | # load pc(should be in local gripper coordinate)
93 | # local_pc: (N, 3)
94 | # local_pc = np.load("test.npy")
95 | local_pc = np.random.random([500, 3]) # test only
96 | predict = []
97 | for _ in range(repeat):
98 | if len(local_pc) >= num_point:
99 | local_pc = local_pc[np.random.choice(len(local_pc), num_point, replace=False)]
100 | else:
101 | local_pc = local_pc[np.random.choice(len(local_pc), num_point, replace=True)]
102 |
103 | # run model
104 | predict.append(test_network(model, local_pc)[0])
105 | print("voting: ", predict)
106 | predict = mode(predict).mode[0]
107 |
108 | # output
109 | print("Test result:", predict)
110 |
111 |
112 | if __name__ == "__main__":
113 | main()
114 |
--------------------------------------------------------------------------------
/model/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hymwgk/PointNetGPD/e33b37f8da0b6326976bb339faa976f724980d82/model/__init__.py
--------------------------------------------------------------------------------
/model/gpd.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 | class GPDClassifier(nn.Module):
6 | """
7 | Input: (batch_size, input_chann, 60, 60)
8 | """
9 | def __init__(self, input_chann, dropout=False):
10 | super(GPDClassifier, self).__init__()
11 | self.conv1 = nn.Conv2d(input_chann, 20, 5)
12 | self.pool1 = nn.MaxPool2d(2, stride=2)
13 | self.conv2 = nn.Conv2d(20, 50, 5)
14 | self.pool2 = nn.MaxPool2d(2, stride=2)
15 | self.fc1 = nn.Linear(12*12*50, 500)
16 | self.dp = nn.Dropout2d(p=0.5, inplace=False)
17 | self.relu = nn.ReLU()
18 | self.fc2 = nn.Linear(500, 2)
19 | self.if_dropout = dropout
20 |
21 | def forward(self, x):
22 | x = self.pool1(self.conv1(x))
23 | x = self.pool2(self.conv2(x))
24 | x = x.view(-1, 7200)
25 | x = self.relu(self.fc1(x))
26 | if self.if_dropout:
27 | x = self.dp(x)
28 | x = self.fc2(x)
29 | x = F.log_softmax(x, dim=-1)
30 |
31 | return x
32 |
--------------------------------------------------------------------------------
/model/pointnet.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import torch
3 | import torch.nn as nn
4 | from torch.autograd import Variable
5 | import torch.nn.functional as F
6 |
7 |
8 | class STN3d(nn.Module):
9 | def __init__(self, num_points = 2500, input_chann = 3):
10 | super(STN3d, self).__init__()
11 | self.num_points = num_points
12 | self.conv1 = torch.nn.Conv1d(input_chann, 64, 1)
13 | self.conv2 = torch.nn.Conv1d(64, 128, 1)
14 | self.conv3 = torch.nn.Conv1d(128, 1024, 1)
15 | self.mp1 = torch.nn.MaxPool1d(num_points)
16 | self.fc1 = nn.Linear(1024, 512)
17 | self.fc2 = nn.Linear(512, 256)
18 | self.fc3 = nn.Linear(256, 9)
19 | self.relu = nn.ReLU()
20 |
21 | self.bn1 = nn.BatchNorm1d(64)
22 | self.bn2 = nn.BatchNorm1d(128)
23 | self.bn3 = nn.BatchNorm1d(1024)
24 | self.bn4 = nn.BatchNorm1d(512)
25 | self.bn5 = nn.BatchNorm1d(256)
26 |
27 |
28 | def forward(self, x):
29 | batchsize = x.size()[0]
30 | x = F.relu(self.bn1(self.conv1(x)))
31 | x = F.relu(self.bn2(self.conv2(x)))
32 | x = F.relu(self.bn3(self.conv3(x)))
33 | x = self.mp1(x)
34 | x = x.view(-1, 1024)
35 |
36 | x = F.relu(self.bn4(self.fc1(x)))
37 | x = F.relu(self.bn5(self.fc2(x)))
38 | x = self.fc3(x)
39 |
40 | iden = Variable(torch.from_numpy(np.array([1,0,0,0,1,0,0,0,1]).astype(np.float32))).view(1,9).repeat(batchsize,1)
41 | if x.is_cuda:
42 | iden = iden.cuda()
43 | x = x + iden
44 | x = x.view(-1, 3, 3)
45 | return x
46 |
47 |
48 | class SimpleSTN3d(nn.Module):
49 | def __init__(self, num_points = 2500, input_chann = 3):
50 | super(SimpleSTN3d, self).__init__()
51 | self.num_points = num_points
52 | self.conv1 = torch.nn.Conv1d(input_chann, 64, 1)
53 | self.conv2 = torch.nn.Conv1d(64, 128, 1)
54 | self.conv3 = torch.nn.Conv1d(128, 256, 1)
55 | self.mp1 = torch.nn.MaxPool1d(num_points)
56 | self.fc1 = nn.Linear(256, 128)
57 | self.fc2 = nn.Linear(128, 64)
58 | self.fc3 = nn.Linear(64, 9)
59 | self.relu = nn.ReLU()
60 |
61 | self.bn1 = nn.BatchNorm1d(64)
62 | self.bn2 = nn.BatchNorm1d(128)
63 | self.bn3 = nn.BatchNorm1d(256)
64 | self.bn4 = nn.BatchNorm1d(128)
65 | self.bn5 = nn.BatchNorm1d(64)
66 |
67 |
68 | def forward(self, x):
69 | batchsize = x.size()[0]
70 | x = F.relu(self.bn1(self.conv1(x)))
71 | x = F.relu(self.bn2(self.conv2(x)))
72 | x = F.relu(self.bn3(self.conv3(x)))
73 | x = self.mp1(x)
74 | x = x.view(-1, 256)
75 |
76 | x = F.relu(self.bn4(self.fc1(x)))
77 | x = F.relu(self.bn5(self.fc2(x)))
78 | x = self.fc3(x)
79 |
80 | iden = Variable(torch.from_numpy(np.array([1,0,0,0,1,0,0,0,1]).astype(np.float32))).view(1,9).repeat(batchsize,1)
81 | if x.is_cuda:
82 | iden = iden.cuda()
83 | x = x + iden
84 | x = x.view(-1, 3, 3)
85 | return x
86 |
87 |
88 | class DualPointNetfeat(nn.Module):
89 | def __init__(self, num_points = 2500, input_chann = 6, global_feat = True):
90 | super(DualPointNetfeat, self).__init__()
91 | self.stn1 = SimpleSTN3d(num_points = num_points, input_chann = input_chann//2)
92 | self.stn2 = SimpleSTN3d(num_points = num_points, input_chann = input_chann//2)
93 | self.conv1 = torch.nn.Conv1d(input_chann, 64, 1)
94 | self.conv2 = torch.nn.Conv1d(64, 128, 1)
95 | self.conv3 = torch.nn.Conv1d(128, 1024, 1)
96 | self.bn1 = nn.BatchNorm1d(64)
97 | self.bn2 = nn.BatchNorm1d(128)
98 | self.bn3 = nn.BatchNorm1d(1024)
99 | self.mp1 = torch.nn.MaxPool1d(num_points)
100 | self.num_points = num_points
101 | self.global_feat = global_feat
102 | def forward(self, x):
103 | batchsize = x.size()[0]
104 | trans1 = self.stn1(x[:, 0:3, :])
105 | trans2 = self.stn2(x[:, 3:6, :])
106 | x = x.transpose(2,1)
107 | x = torch.cat([torch.bmm(x[..., 0:3], trans1), torch.bmm(x[..., 3:6], trans2)], dim=-1)
108 | x = x.transpose(2,1)
109 | x = F.relu(self.bn1(self.conv1(x)))
110 | pointfeat = x
111 | x = F.relu(self.bn2(self.conv2(x)))
112 | x = self.bn3(self.conv3(x))
113 | x = self.mp1(x)
114 | x = x.view(-1, 1024)
115 | if self.global_feat:
116 | return x, trans1 + trans2
117 | else:
118 | x = x.view(-1, 1024, 1).repeat(1, 1, self.num_points)
119 | return torch.cat([x, pointfeat], 1), trans1 + trans2
120 |
121 |
122 | class PointNetfeat(nn.Module):
123 | def __init__(self, num_points = 2500, input_chann = 3, global_feat = True):
124 | super(PointNetfeat, self).__init__()
125 | self.stn = STN3d(num_points = num_points, input_chann = input_chann)
126 | self.conv1 = torch.nn.Conv1d(input_chann, 64, 1)
127 | self.conv2 = torch.nn.Conv1d(64, 128, 1)
128 | self.conv3 = torch.nn.Conv1d(128, 1024, 1)
129 | self.bn1 = nn.BatchNorm1d(64)
130 | self.bn2 = nn.BatchNorm1d(128)
131 | self.bn3 = nn.BatchNorm1d(1024)
132 | self.mp1 = torch.nn.MaxPool1d(num_points)
133 | self.num_points = num_points
134 | self.global_feat = global_feat
135 | def forward(self, x):
136 | batchsize = x.size()[0]
137 | trans = self.stn(x)
138 | x = x.transpose(2,1)
139 | x = torch.bmm(x, trans)
140 | # x = torch.cat([torch.bmm(x[..., 0:3], trans), torch.bmm(x[..., 3:6], trans)], dim=-1)
141 | x = x.transpose(2,1)
142 | x = F.relu(self.bn1(self.conv1(x)))
143 | pointfeat = x
144 | x = F.relu(self.bn2(self.conv2(x)))
145 | x = self.bn3(self.conv3(x))
146 | x = self.mp1(x)
147 | x = x.view(-1, 1024)
148 | if self.global_feat:
149 | return x, trans
150 | else:
151 | x = x.view(-1, 1024, 1).repeat(1, 1, self.num_points)
152 | return torch.cat([x, pointfeat], 1), trans
153 |
154 |
155 | class DualPointNetCls(nn.Module):
156 | def __init__(self, num_points = 2500, input_chann = 3, k = 2):
157 | super(DualPointNetCls, self).__init__()
158 | self.num_points = num_points
159 | self.feat = DualPointNetfeat(num_points, input_chann=input_chann, global_feat=True)
160 | self.fc1 = nn.Linear(1024, 512)
161 | self.fc2 = nn.Linear(512, 256)
162 | self.fc3 = nn.Linear(256, k)
163 | self.bn1 = nn.BatchNorm1d(512)
164 | self.bn2 = nn.BatchNorm1d(256)
165 | self.relu = nn.ReLU()
166 | def forward(self, x):
167 | x, trans = self.feat(x)
168 | x = F.relu(self.bn1(self.fc1(x)))
169 | x = F.relu(self.bn2(self.fc2(x)))
170 | x = self.fc3(x)
171 | return F.log_softmax(x, dim=-1), trans
172 |
173 |
174 | class PointNetCls(nn.Module):
175 | def __init__(self, num_points = 2500, input_chann = 3, k = 2):
176 | super(PointNetCls, self).__init__()
177 | self.num_points = num_points
178 | self.feat = PointNetfeat(num_points, input_chann=input_chann, global_feat=True)
179 | self.fc1 = nn.Linear(1024, 512)
180 | self.fc2 = nn.Linear(512, 256)
181 | self.fc3 = nn.Linear(256, k)
182 | self.bn1 = nn.BatchNorm1d(512)
183 | self.bn2 = nn.BatchNorm1d(256)
184 | self.relu = nn.ReLU()
185 | def forward(self, x):
186 | x, trans = self.feat(x)
187 | x = F.relu(self.bn1(self.fc1(x)))
188 | x = F.relu(self.bn2(self.fc2(x)))
189 | x = self.fc3(x)
190 | return F.log_softmax(x, dim=-1), trans
191 |
192 | class PointNetDenseCls(nn.Module):
193 | def __init__(self, num_points = 2500, input_chann = 3, k = 2):
194 | super(PointNetDenseCls, self).__init__()
195 | self.num_points = num_points
196 | self.k = k
197 | self.feat = PointNetfeat(num_points, input_chann=input_chann, global_feat=False)
198 | self.conv1 = torch.nn.Conv1d(1088, 512, 1)
199 | self.conv2 = torch.nn.Conv1d(512, 256, 1)
200 | self.conv3 = torch.nn.Conv1d(256, 128, 1)
201 | self.conv4 = torch.nn.Conv1d(128, self.k, 1)
202 | self.bn1 = nn.BatchNorm1d(512)
203 | self.bn2 = nn.BatchNorm1d(256)
204 | self.bn3 = nn.BatchNorm1d(128)
205 |
206 | def forward(self, x):
207 | batchsize = x.size()[0]
208 | x, trans = self.feat(x)
209 | x = F.relu(self.bn1(self.conv1(x)))
210 | x = F.relu(self.bn2(self.conv2(x)))
211 | x = F.relu(self.bn3(self.conv3(x)))
212 | x = self.conv4(x)
213 | x = x.transpose(2,1).contiguous()
214 | x = F.log_softmax(x.view(-1,self.k), dim=-1)
215 | x = x.view(batchsize, self.num_points, self.k)
216 | return x, trans
217 |
218 |
219 | if __name__ == '__main__':
220 | pass
221 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | networkx==2.2
2 | scipy
3 | h5py
4 | numpy
5 | NearPy
6 | Pillow
7 | python-dateutil
8 | PyYAML
9 | scikit-image
10 | scikit-learn
11 | six
12 | trimesh
13 | pyhull
14 | cvxopt
15 | pandas
16 | visualization
17 | pyquaternion
18 | numba
19 | autolab-core
20 | autolab-perception
21 | shapely
22 |
23 |
--------------------------------------------------------------------------------
/requirements2.txt:
--------------------------------------------------------------------------------
1 | opencv-python==4.2.0.32
2 | trimesh
3 | ffmpeg-python==0.1.5
4 | networkx==2.2
5 | scipy
6 | h5py
7 | numpy
8 | NearPy
9 | Pillow
10 | python-dateutil
11 | PyYAML
12 | scikit-image
13 | scikit-learn
14 | six
15 | trimesh
16 | pyhull
17 | cvxopt
18 | pandas
19 | pyquaternion
20 | numba
21 | autolab-core
22 | autolab-perception
23 | shapely
24 |
--------------------------------------------------------------------------------