├── .gitignore ├── LICENSE ├── README.md ├── RTL ├── dataloader.py ├── floor │ ├── carpet │ │ ├── carpet.jpg │ │ ├── carpet.mtl │ │ └── carpet.obj │ ├── drum │ │ ├── drum.jpg │ │ ├── drum.mtl │ │ └── drum.obj │ ├── grass │ │ ├── data.json │ │ ├── grass.jpg │ │ ├── grass.mtl │ │ └── grass.obj │ ├── mousemat │ │ ├── mousemat.jpg │ │ ├── mousemat.mtl │ │ └── mousemat.obj │ └── table │ │ ├── table.jpg │ │ ├── table.mtl │ │ └── table.obj ├── main.py ├── recon.py ├── run_camera.py ├── scene.py └── templates │ ├── qrcode.png │ └── test_flask.html ├── figs ├── algo_comparison.png ├── book_cover.png ├── comp_others.png ├── dance.gif ├── gpu.png ├── icon.png ├── livecap_comparison.png ├── lossless.png ├── ohem.png ├── robustness.png ├── rtl.jpg ├── shift.gif ├── suppl_results1.png ├── suppl_results2.png ├── twoside.png └── zeng.gif ├── monoport ├── __init__.py └── lib │ ├── common │ ├── config.py │ ├── logger.py │ └── trainer.py │ ├── dataset │ ├── ppl_dynamic.py │ ├── ppl_static.py │ └── utils.py │ ├── mesh_util.py │ ├── modeling │ ├── MonoPortNet.py │ ├── __init__.py │ ├── backbones │ │ ├── HGFilters.py │ │ ├── HRNetFilters.py │ │ ├── ResBlkFilters.py │ │ ├── Yolov4Filters.py │ │ └── __init__.py │ ├── geometry.py │ ├── heads │ │ ├── SurfaceClassifier.py │ │ └── __init__.py │ └── normalizers │ │ ├── DepthNormalizer.py │ │ └── __init__.py │ └── render │ ├── BaseCamera.py │ ├── CameraPose.py │ ├── PespectiveCamera.py │ ├── __init__.py │ └── gl │ ├── AlbedoRender.py │ ├── GLObject.py │ ├── NormalRender.py │ ├── PrtRender.py │ ├── Render.py │ ├── ShRender.py │ ├── Shader.py │ ├── __init__.py │ ├── glcontext.py │ └── shaders │ ├── albedo.fs │ ├── albedo.vs │ ├── color.fs │ ├── color.vs │ ├── normal.fs │ ├── normal.vs │ ├── prt.fs │ ├── prt.vs │ ├── prt_uv.fs │ ├── prt_uv.vs │ ├── quad.fs │ ├── quad.vs │ ├── sh.fs │ ├── sh.vs │ ├── simple.fs │ └── simple.vs ├── requirements.txt ├── scripts └── download_model.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | # lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | data/ 107 | 108 | .DS_Store 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is Copyright © 2021 Ruilong Li, The University of Southern California. All Rights Reserved. 2 | 3 | Permission to use, copy, modify, and distribute this software and its documentation for educational, research and non-profit purposes, without fee, and without a written agreement is hereby granted, provided that the above copyright notice, this paragraph and the following three paragraphs appear in all copies. 4 | 5 | Permission to make commercial use of this software may be obtained by contacting: 6 | USC Stevens Center for Innovation 7 | University of Southern California 8 | 1150 S. Olive Street, Suite 2300 9 | Los Angeles, CA 90115, USA 10 | 11 | This software program and documentation are copyrighted by The University of Southern California. The software program and documentation are supplied "as is", without any accompanying services from USC. USC does not warrant that the operation of the program will be uninterrupted or error-free. The end-user understands that the program was developed for research purposes and is advised not to rely exclusively on the program for any reason. 12 | 13 | IN NO EVENT SHALL THE UNIVERSITY OF SOUTHERN CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF SOUTHERN CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF SOUTHERN CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF SOUTHERN CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Monoport: Monocular Volumetric Human Teleportation (SIGGRAPH 2020 Real-Time Live)](http://xiuyuliang.cn/monoport/) 2 | 3 | ### Time: Tuesday, 25 August 2020 (Pacific Time Zone) 4 | [![report](https://img.shields.io/badge/Paper-Arxiv-red)](https://arxiv.org/abs/2007.13988) [![homepage](https://img.shields.io/badge/Project-Homepage-green)](https://project-splinter.github.io/) [![report](https://img.shields.io/badge/Demo-Youtube-yellow)](https://youtu.be/fQDsYVE7GtQ) 5 | 6 |

7 | 8 |

9 | 10 | Our volumetric capture system captures a completely clothed human body (including the back) using **a single RGB webcam** and in real time. 11 | 12 | ## Requirements 13 | - Python 3.7 14 | - PyOpenGL 3.1.5 (need X server in Ubuntu) 15 | - [PyTorch](https://pytorch.org/) tested on 1.4.0 16 | - [ImplicitSegCUDA](https://github.com/Project-Splinter/ImplicitSegCUDA) 17 | - [human_inst_seg](https://github.com/Project-Splinter/human_inst_seg) 18 | - [streamer_pytorch](https://github.com/Project-Splinter/streamer_pytorch) 19 | - [human_det](https://github.com/Project-Splinter/human_det) 20 | 21 | We run the demo with 2 GeForce RTX 2080Ti GPUs, the memory usage is as follows (~3.4GB at GPU1, ~9.7GB at GPU2): 22 | 23 |

24 | 25 |

26 | 27 | **Note**: The last four dependencies are also developed by our team, and are all in active maintenance. If you meet any installation problems specifically regarding those tools, we recommend you to file the issue in the corresponding repo. (You don't need to install them manually here as they are included in the requirements.txt) 28 | 29 | ## How to run our Siggraph RTL Demo 30 | 31 | #### 1. Setup the repo 32 | First you need to download the model: 33 | ``` 34 | sh scripts/download_model.sh 35 | ``` 36 | 37 | Then install all the dependencies: 38 | ``` 39 | pip install -r requirements.txt 40 | ``` 41 | 42 | #### 2. Start the main process as a server. 43 | ``` 44 | # if you want to use the input from a webcam: 45 | python RTL/main.py --use_server --ip --port 5555 --camera -- netG.ckpt_path ./data/PIFu/net_G netC.ckpt_path ./data/PIFu/net_C 46 | 47 | # or if you want to use the input from a image folder: 48 | python RTL/main.py --use_server --ip --port 5555 --image_folder -- netG.ckpt_path ./data/PIFu/net_G netC.ckpt_path ./data/PIFu/net_C 49 | 50 | # or if you want to use the input from a video: 51 | python RTL/main.py --use_server --ip --port 5555 --videos -- netG.ckpt_path ./data/PIFu/net_G netC.ckpt_path ./data/PIFu/net_C 52 | ``` 53 | 54 | If everything goes well, you should be able to see those logs after waiting for a few seconds: 55 | 56 | loading networkG from ./data/PIFu/net_G ... 57 | loading networkC from ./data/PIFu/net_C ... 58 | initialize data streamer ... 59 | Using cache found in /home/rui/.cache/torch/hub/NVIDIA_DeepLearningExamples_torchhub 60 | Using cache found in /home/rui/.cache/torch/hub/NVIDIA_DeepLearningExamples_torchhub 61 | * Serving Flask app "main" (lazy loading) 62 | * Environment: production 63 | WARNING: This is a development server. Do not use it in a production deployment. 64 | Use a production WSGI server instead. 65 | * Debug mode: on 66 | * Running on http://:5555/ (Press CTRL+C to quit) 67 | 68 | #### 2. Access the server to start. 69 | Open the page `http://:5555/` on a web browser from any device (Desktop/IPad/IPhone), You should be able to see the **MonoPort VR Demo** page on that device, and at the same time you should be able to see the a screen poping up on your desktop, showing the reconstructed normal and texture image. 70 | 71 |

72 | 73 |

74 | 75 | ## Contributors 76 | 77 | MonoPort is based on [Monocular Real-Time Volumetric Performance Capture(ECCV'20)](https://project-splinter.github.io/), authored by Ruilong Li*([@liruilong940607](https://github.com/liruilong940607)), Yuliang Xiu*([@yuliangxiu](https://github.com/YuliangXiu)), Shunsuke Saito([@shunsukesaito](https://github.com/shunsukesaito)), Zeng Huang([@ImaginationZ](https://github.com/ImaginationZ)) and Kyle Olszewski([@kyleolsz](https://github.com/kyleolsz)), [Hao Li](https://www.hao-li.com/) is the corresponding author. 78 | 79 | 80 | ## Citation 81 | 82 | ``` 83 | @inproceedings{li2020monoport, 84 | title={Monocular Real-Time Volumetric Performance Capture}, 85 | author={Li, Ruilong and Xiu, Yuliang and Saito, Shunsuke and Huang, Zeng and Olszewski, Kyle and Li, Hao}, 86 | booktitle={European Conference on Computer Vision}, 87 | pages={49--67}, 88 | year={2020}, 89 | organization={Springer} 90 | } 91 | 92 | @incollection{li2020monoportRTL, 93 | title={Volumetric human teleportation}, 94 | author={Li, Ruilong and Olszewski, Kyle and Xiu, Yuliang and Saito, Shunsuke and Huang, Zeng and Li, Hao}, 95 | booktitle={ACM SIGGRAPH 2020 Real-Time Live}, 96 | pages={1--1}, 97 | year={2020} 98 | } 99 | ``` 100 | 101 | ## Relevant Works 102 | 103 | **[PIFu: Pixel-Aligned Implicit Function for High-Resolution Clothed Human Digitization (ICCV 2019)](https://shunsukesaito.github.io/PIFu/)** 104 | *Shunsuke Saito\*, Zeng Huang\*, Ryota Natsume\*, Shigeo Morishima, Angjoo Kanazawa, Hao Li* 105 | 106 | The original work of Pixel-Aligned Implicit Function for geometry and texture reconstruction, unifying sigle-view and multi-view methods. 107 | 108 | **[PIFuHD: Multi-Level Pixel-Aligned Implicit Function for High-Resolution 3D Human Digitization (CVPR 2020)](https://shunsukesaito.github.io/PIFuHD/)** 109 | *Shunsuke Saito, Tomas Simon, Jason Saragih, Hanbyul Joo* 110 | 111 | They further improve the quality of reconstruction by leveraging multi-level approach! 112 | 113 | **[ARCH: Animatable Reconstruction of Clothed Humans (CVPR 2020)](https://arxiv.org/pdf/2004.04572.pdf)** 114 | *Zeng Huang, Yuanlu Xu, Christoph Lassner, Hao Li, Tony Tung* 115 | 116 | Learning PIFu in canonical space for animatable avatar generation! 117 | 118 | **[Robust 3D Self-portraits in Seconds (CVPR 2020)](http://www.liuyebin.com/portrait/portrait.html)** 119 | *Zhe Li, Tao Yu, Chuanyu Pan, Zerong Zheng, Yebin Liu* 120 | 121 | They extend PIFu to RGBD + introduce "PIFusion" utilizing PIFu reconstruction for non-rigid fusion. 122 | 123 | ## Relavant applications 124 | 125 | **[Real-time VR PhD Defense](https://www.youtube.com/watch?v=RhWTqjVekVU&feature=youtu.be)** 126 | Dr. [Zeng Huang](https://zeng.science/) defensed his PhD virtually using our system. [(Media in Chinese)](https://mp.weixin.qq.com/s/Bl0HohrSVzaVPF0EHzuIWw) 127 | 128 |

129 | 130 |

131 | -------------------------------------------------------------------------------- /RTL/floor/carpet/carpet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/RTL/floor/carpet/carpet.jpg -------------------------------------------------------------------------------- /RTL/floor/carpet/carpet.mtl: -------------------------------------------------------------------------------- 1 | # 2 | # Wavefront material file 3 | # Converted by Meshlab Group 4 | # 5 | 6 | newmtl material_0 7 | Ka 0.200000 0.200000 0.200000 8 | Kd 0.584314 0.584314 0.584314 9 | Ks 1.000000 1.000000 1.000000 10 | Tr 0.000000 11 | illum 2 12 | Ns 0.000000 13 | map_Kd carpet.jpg 14 | 15 | -------------------------------------------------------------------------------- /RTL/floor/carpet/carpet.obj: -------------------------------------------------------------------------------- 1 | #### 2 | # 3 | # OBJ File Generated by Meshlab 4 | # 5 | #### 6 | # Object carpet.obj 7 | # 8 | # Vertices: 24 9 | # Faces: 34 10 | # 11 | #### 12 | mtllib ./carpet.mtl 13 | 14 | vn 2.133503 -2.133503 3.706217 15 | v 22.185629 -22.185631 1.354754 0.749020 0.749020 0.749020 16 | vn 2.133502 2.133502 3.706217 17 | v 22.185629 22.185631 1.354756 0.749020 0.749020 0.749020 18 | vn -2.133502 2.133502 3.706217 19 | v -22.185629 22.185631 1.354756 0.749020 0.749020 0.749020 20 | vn -2.133503 -2.133503 3.706217 21 | v -22.185629 -22.185631 1.354754 0.749020 0.749020 0.749020 22 | vn -2.328125 1.376172 2.073882 23 | v -26.004105 20.084265 1.354756 0.749020 0.749020 0.749020 24 | vn -2.328123 -1.376173 2.073881 25 | v -26.004105 -20.084265 1.354754 0.749020 0.749020 0.749020 26 | vn 1.376173 2.328126 2.073882 27 | v 20.084263 26.004107 1.354756 0.749020 0.749020 0.749020 28 | vn -1.376173 2.328122 2.073881 29 | v -20.084263 26.004107 1.354756 0.749020 0.749020 0.749020 30 | vn 2.328126 -1.376173 2.073882 31 | v 26.004105 -20.084265 1.354754 0.749020 0.749020 0.749020 32 | vn 2.328123 1.376173 2.073881 33 | v 26.004105 20.084265 1.354756 0.749020 0.749020 0.749020 34 | vn -1.376173 -2.328126 2.073882 35 | v -20.084263 -26.004107 1.354754 0.749020 0.749020 0.749020 36 | vn 1.376173 -2.328123 2.073881 37 | v 20.084263 -26.004107 1.354754 0.749020 0.749020 0.749020 38 | vn 2.133503 -2.133503 0.000000 39 | v 22.185629 -22.185631 0.959159 0.749020 0.749020 0.749020 40 | vn 1.376173 -2.328126 0.000000 41 | v 20.084263 -26.004107 0.959159 0.749020 0.749020 0.749020 42 | vn 2.328123 -1.376173 0.000000 43 | v 26.004105 -20.084265 0.959159 0.749020 0.749020 0.749020 44 | vn -1.376173 -2.328123 0.000000 45 | v -20.084263 -26.004107 0.959159 0.749020 0.749020 0.749020 46 | vn 2.328126 1.376173 0.000000 47 | v 26.004105 20.084265 0.959161 0.749020 0.749020 0.749020 48 | vn -1.376173 2.328126 0.000000 49 | v -20.084263 26.004107 0.959161 0.749020 0.749020 0.749020 50 | vn 1.376173 2.328123 0.000000 51 | v 20.084263 26.004107 0.959161 0.749020 0.749020 0.749020 52 | vn -2.328126 -1.376173 0.000000 53 | v -26.004105 -20.084265 0.959159 0.749020 0.749020 0.749020 54 | vn -2.328123 1.376173 0.000000 55 | v -26.004105 20.084265 0.959161 0.749020 0.749020 0.749020 56 | vn -2.133502 2.133502 0.000000 57 | v -22.185629 22.185631 0.959161 0.749020 0.749020 0.749020 58 | vn 2.133503 2.133503 0.000000 59 | v 22.185629 22.185631 0.959161 0.749020 0.749020 0.749020 60 | vn -2.133503 -2.133503 0.000000 61 | v -22.185629 -22.185631 0.959159 0.749020 0.749020 0.749020 62 | # 24 vertices, 0 vertices normals 63 | 64 | 65 | usemtl material_0 66 | vt 0.877100 0.116100 67 | vt 0.877100 0.883900 68 | vt 0.122900 0.883900 69 | f 1/1/1 2/2/2 3/3/3 70 | vt 0.122900 0.116100 71 | f 1/1/1 3/3/3 4/4/4 72 | vt 0.058000 0.847600 73 | vt 0.058000 0.152400 74 | f 3/3/3 5/5/5 6/6/6 75 | f 3/3/3 6/6/6 4/4/4 76 | vt 0.841400 0.950000 77 | vt 0.158600 0.950000 78 | f 2/2/2 7/7/7 8/8/8 79 | f 2/2/2 8/8/8 3/3/3 80 | vt 0.942000 0.152400 81 | vt 0.942000 0.847600 82 | f 1/1/1 9/9/9 10/10/10 83 | f 1/1/1 10/10/10 2/2/2 84 | vt 0.158600 0.050000 85 | vt 0.841400 0.050000 86 | f 4/4/4 11/11/11 12/12/12 87 | f 4/4/4 12/12/12 1/1/1 88 | vt 0.162000 0.054500 89 | vt 0.838000 0.054500 90 | vt 0.817500 0.084300 91 | f 16/13/16 14/14/14 12/15/12 92 | vt 0.188100 0.084300 93 | f 16/13/16 12/15/12 11/16/11 94 | vt 0.873400 0.119900 95 | vt 0.852900 0.155300 96 | f 14/14/14 13/17/13 1/18/1 97 | f 14/14/14 1/18/1 12/15/12 98 | vt 0.937600 0.155900 99 | vt 0.911500 0.174500 100 | f 13/17/13 15/19/15 9/20/9 101 | f 13/17/13 9/20/9 1/18/1 102 | vt 0.937600 0.844100 103 | vt 0.911500 0.810600 104 | f 15/19/15 17/21/17 10/22/10 105 | f 15/19/15 10/22/10 9/20/9 106 | vt 0.873400 0.880100 107 | vt 0.849100 0.848400 108 | f 17/21/17 23/23/23 2/24/2 109 | f 17/21/17 2/24/2 10/22/10 110 | vt 0.838000 0.945500 111 | vt 0.821200 0.910100 112 | f 23/23/23 19/25/19 7/26/7 113 | f 23/23/23 7/26/7 2/24/2 114 | vt 0.162000 0.945500 115 | vt 0.175000 0.910100 116 | f 19/25/19 18/27/18 8/28/8 117 | f 19/25/19 8/28/8 7/26/7 118 | vt 0.113500 0.885700 119 | vt 0.145200 0.855900 120 | f 18/27/18 22/29/22 3/30/3 121 | f 18/27/18 3/30/3 8/28/8 122 | vt 0.049300 0.849700 123 | vt 0.089400 0.829200 124 | f 22/29/22 21/31/21 5/32/5 125 | f 22/29/22 5/32/5 3/30/3 126 | vt 0.053000 0.155900 127 | vt 0.089400 0.187600 128 | f 21/31/21 20/33/20 6/34/6 129 | f 21/31/21 6/34/6 5/32/5 130 | vt 0.126600 0.119900 131 | vt 0.156400 0.144100 132 | f 20/33/20 24/35/24 4/36/4 133 | f 20/33/20 4/36/4 6/34/6 134 | f 24/35/24 16/13/16 11/16/11 135 | f 24/35/24 11/16/11 4/36/4 136 | # 34 faces, 36 coords texture 137 | 138 | # End of File -------------------------------------------------------------------------------- /RTL/floor/drum/drum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/RTL/floor/drum/drum.jpg -------------------------------------------------------------------------------- /RTL/floor/drum/drum.mtl: -------------------------------------------------------------------------------- 1 | # 2 | # Wavefront material file 3 | # Converted by Meshlab Group 4 | # 5 | 6 | newmtl material_0 7 | Ka 0.200000 0.200000 0.200000 8 | Kd 0.639216 0.639216 0.639216 9 | Ks 1.000000 1.000000 1.000000 10 | Tr 1.000000 11 | illum 2 12 | Ns 0.000000 13 | map_Kd drum.jpg 14 | 15 | -------------------------------------------------------------------------------- /RTL/floor/grass/grass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/RTL/floor/grass/grass.jpg -------------------------------------------------------------------------------- /RTL/floor/grass/grass.mtl: -------------------------------------------------------------------------------- 1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware 2 | # File Created: 25.04.2011 16:05:12 3 | 4 | newmtl 10438_Circular_Grass_Patch_v1 5 | Ns 10.0000 6 | Ni 1.5000 7 | d 1.0000 8 | Tr 0.0000 9 | Tf 1.0000 1.0000 1.0000 10 | illum 2 11 | Ka 0.5882 0.5882 0.5882 12 | Kd 0.5882 0.5882 0.5882 13 | Ks 0.0450 0.0450 0.0450 14 | Ke 0.0000 0.0000 0.0000 15 | map_Ka grass.jpg 16 | map_Kd grass.jpg 17 | -------------------------------------------------------------------------------- /RTL/floor/mousemat/mousemat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/RTL/floor/mousemat/mousemat.jpg -------------------------------------------------------------------------------- /RTL/floor/mousemat/mousemat.mtl: -------------------------------------------------------------------------------- 1 | # 2 | # Wavefront material file 3 | # Converted by Meshlab Group 4 | # 5 | 6 | newmtl material_0 7 | Ns 10.0000 8 | Ni 1.5000 9 | d 1.0000 10 | Tr 0.0000 11 | Tf 1.0000 1.0000 1.0000 12 | illum 2 13 | Ka 0.5882 0.5882 0.5882 14 | Kd 0.5882 0.5882 0.5882 15 | Ks 0.0450 0.0450 0.0450 16 | Ke 0.0000 0.0000 0.0000 17 | map_Kd mousemat.jpg 18 | map_Ka mousemat.jpg 19 | 20 | -------------------------------------------------------------------------------- /RTL/floor/table/table.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/RTL/floor/table/table.jpg -------------------------------------------------------------------------------- /RTL/floor/table/table.mtl: -------------------------------------------------------------------------------- 1 | # 2 | # Wavefront material file 3 | # Converted by Meshlab Group 4 | # 5 | 6 | newmtl material_0 7 | Ka 0.200000 0.200000 0.200000 8 | Kd 0.600000 0.600000 0.894118 9 | Ks 1.000000 1.000000 1.000000 10 | Tr 0.000000 11 | illum 2 12 | Ns 0.000000 13 | map_Kd table.jpg 14 | 15 | -------------------------------------------------------------------------------- /RTL/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import argparse 4 | import glob 5 | import tqdm 6 | import cv2 7 | import math 8 | import numpy as np 9 | from base64 import b64encode 10 | from sys import getsizeof 11 | 12 | from flask import Response 13 | from flask import Flask 14 | from flask import render_template 15 | 16 | import torch 17 | import torch.nn.functional as F 18 | 19 | from monoport.lib.common.config import get_cfg_defaults 20 | from monoport.lib.modeling.MonoPortNet import MonoPortNet 21 | from monoport.lib.modeling.MonoPortNet import PIFuNetG, PIFuNetC 22 | from monoport.lib.modeling.geometry import orthogonal, perspective 23 | from monoport.lib.render.gl.glcontext import create_opengl_context 24 | from monoport.lib.render.gl.AlbedoRender import AlbedoRender 25 | 26 | import streamer_pytorch as streamer 27 | import human_inst_seg 28 | from implicit_seg.functional import Seg3dTopk, Seg3dLossless 29 | from implicit_seg.functional.utils import plot_mask3D 30 | 31 | from dataloader import DataLoader 32 | from scene import MonoPortScene, make_rotate 33 | from recon import pifu_calib, forward_vertices 34 | 35 | 36 | ######################################## 37 | ## Global Control 38 | ######################################## 39 | DESKTOP_MODE = 'TEXTURE_NORM' 40 | # assert DESKTOP_MODE in ['SEGM', 'NORM', 'TEXTURE', 'TEXTURE_NORM'] 41 | 42 | SERVER_MODE = 'TEXTURE' 43 | # assert SERVER_MODE in ['NORM', 'TEXTURE'] 44 | 45 | VIEW_MODE = 'AUTO' 46 | # assert VIEW_MODE in ['FRONT', 'BACK', 'LEFT', 'RIGHT', 'AUTO', 'LOAD'] 47 | 48 | ######################################## 49 | ## load configs 50 | ######################################## 51 | parser = argparse.ArgumentParser() 52 | parser.add_argument( 53 | '-cfg', '--config_file', default=None, type=str, 54 | help='path of the yaml config file') 55 | parser.add_argument( 56 | '--camera', action="store_true") 57 | parser.add_argument( 58 | '--images', default="", nargs="*") 59 | parser.add_argument( 60 | '--image_folder', default=None) 61 | parser.add_argument( 62 | '--videos', default="", nargs="*") 63 | parser.add_argument( 64 | '--loop', action="store_true") 65 | parser.add_argument( 66 | '--use_server', action="store_true") 67 | parser.add_argument( 68 | '--ip', type=str, default="192.168.1.53") 69 | parser.add_argument( 70 | '--port', type=str, default="5555") 71 | 72 | 73 | argv = sys.argv[1:sys.argv.index('--')] 74 | args = parser.parse_args(argv) 75 | opts = sys.argv[sys.argv.index('--') + 1:] 76 | 77 | cfg = get_cfg_defaults() 78 | if args.config_file is not None: 79 | cfg.merge_from_file(args.config_file) 80 | cfg.merge_from_list(opts) 81 | cfg.freeze() 82 | 83 | 84 | ######################################## 85 | ## access avaiable GPUs 86 | ######################################## 87 | device_count = torch.cuda.device_count() 88 | if device_count == 1: 89 | cuda_backbone_G='cuda:0' 90 | cuda_backbone_C='cuda:0' 91 | cuda_recon='cuda:0' 92 | cuda_color='cuda:0' 93 | elif device_count == 2: 94 | cuda_backbone_G='cuda:1' 95 | cuda_backbone_C='cuda:1' 96 | cuda_recon='cuda:0' 97 | cuda_color='cuda:1' 98 | else: 99 | raise NotImplementedError 100 | 101 | 102 | ######################################## 103 | ## load networks 104 | ######################################## 105 | print (f'loading networkG from {cfg.netG.ckpt_path} ...') 106 | netG = MonoPortNet(cfg.netG) 107 | assert os.path.exists(cfg.netG.ckpt_path), 'we need a ckpt to run RTL demo.' 108 | if 'checkpoints' in cfg.netG.ckpt_path: 109 | ckpt = torch.load(cfg.netG.ckpt_path, map_location="cpu") 110 | netG.load_state_dict(ckpt['net']) 111 | else: 112 | netG.load_legacy_pifu(cfg.netG.ckpt_path) 113 | 114 | netG.image_filter = netG.image_filter.to(cuda_backbone_G) 115 | netG.surface_classifier = netG.surface_classifier.to(cuda_recon) 116 | netG.eval() 117 | 118 | if os.path.exists(cfg.netC.ckpt_path): 119 | print (f'loading networkC from {cfg.netC.ckpt_path} ...') 120 | netC = MonoPortNet(cfg.netC) 121 | netC.load_legacy_pifu(cfg.netC.ckpt_path) 122 | 123 | netC.image_filter = netC.image_filter.to(cuda_backbone_C) 124 | netC.surface_classifier = netC.surface_classifier.to(cuda_color) 125 | netC.eval() 126 | else: 127 | netC = None 128 | print (f'we are not loading netC ...') 129 | 130 | 131 | ######################################## 132 | ## initialize data streamer 133 | ######################################## 134 | print (f'initialize data streamer ...') 135 | if args.camera: 136 | data_stream = streamer.CaptureStreamer(pad=False) 137 | elif len(args.videos) > 0: 138 | data_stream = streamer.VideoListStreamer( 139 | args.videos * (10 if args.loop else 1)) 140 | elif len(args.images) > 0: 141 | data_stream = streamer.ImageListStreamer( 142 | args.images * (10000 if args.loop else 1)) 143 | elif args.image_folder is not None: 144 | images = sorted(glob.glob(args.image_folder+'/*.jpg')) 145 | images += sorted(glob.glob(args.image_folder+'/*.png')) 146 | data_stream = streamer.ImageListStreamer( 147 | images * (10 if args.loop else 1)) 148 | 149 | 150 | ######################################## 151 | ## human segmentation model 152 | ######################################## 153 | seg_engine = human_inst_seg.Segmentation( 154 | device=cuda_backbone_G, verbose=False) 155 | seg_engine.eval() 156 | 157 | 158 | ######################################## 159 | ## pre-loaded scene for rendering 160 | ######################################## 161 | scene = MonoPortScene(size=(256, 256)) 162 | 163 | 164 | ######################################## 165 | ## variables for hierachy occupancy reconstruction 166 | ######################################## 167 | calib_tensor = torch.eye(4).unsqueeze(0).to(cuda_recon) 168 | @torch.no_grad() 169 | def query_func(points, im_feat_list, calib_tensor): 170 | ''' 171 | - points: size of (bz, N, 3) 172 | - proj_matrix: size of (bz, 4, 4) 173 | return: size of (bz, 1, N) 174 | ''' 175 | assert len(points) == 1 176 | samples = points.repeat(1, 1, 1) 177 | samples = samples.permute(0, 2, 1) # [bz, 3, N] 178 | 179 | preds = netG.query( 180 | im_feat_list, 181 | points=samples, 182 | calibs=calib_tensor)[0] 183 | return preds 184 | 185 | b_min = torch.tensor([-1.0, -1.0, -1.0]).float() 186 | b_max = torch.tensor([ 1.0, 1.0, 1.0]).float() 187 | resolutions = [16+1, 32+1, 64+1, 128+1, 256+1] 188 | reconEngine = Seg3dLossless( 189 | query_func=query_func, 190 | b_min=b_min.unsqueeze(0).numpy(), 191 | b_max=b_max.unsqueeze(0).numpy(), 192 | resolutions=resolutions, 193 | balance_value=0.5, 194 | use_cuda_impl=False, 195 | faster=True).to(cuda_recon) 196 | 197 | 198 | ######################################## 199 | ## variables for color inference 200 | ######################################## 201 | canvas = torch.ones( 202 | (resolutions[-1], resolutions[-1], 3), dtype=torch.float32 203 | ).to(cuda_color) 204 | mat = torch.eye(4, dtype=torch.float32) 205 | length = b_max - b_min 206 | mat[0, 0] = length[0] / resolutions[-1] 207 | mat[1, 1] = length[1] / resolutions[-1] 208 | mat[2, 2] = length[2] / resolutions[-1] 209 | mat[0:3, 3] = b_min 210 | mat_color = mat.to(cuda_color) 211 | 212 | @torch.no_grad() 213 | def colorization(netC, feat_tensor_C, X, Y, Z, calib_tensor, norm=None): 214 | if X is None: 215 | return None 216 | 217 | device = calib_tensor.device 218 | global canvas 219 | # use normal as color 220 | if norm is not None: 221 | color = (norm + 1) / 2 222 | color = color.clamp(0, 1) 223 | image = canvas.clone() 224 | image[X, Y, :] = color 225 | return image 226 | 227 | # use netC to predict color 228 | else: 229 | feat_tensor_C = [ 230 | [feat.to(device) for feat in feats] for feats in feat_tensor_C] 231 | verts = torch.stack([ 232 | X.float(), Y.float(), resolutions[-1]-Z.float() # TODO 233 | ], dim=1) 234 | 235 | samples = verts.unsqueeze(0).repeat(1, 1, 1) 236 | samples = samples.permute(0, 2, 1) # [bz, 3, N] 237 | samples = orthogonal(samples, mat_color.unsqueeze(0)) 238 | 239 | preds = netC.query( 240 | feat_tensor_C, 241 | points=samples, 242 | calibs=calib_tensor)[0] 243 | 244 | color = preds[0] * 0.5 + 0.5 # FIXME 245 | color = color.t() # [N, 3] 246 | 247 | image = canvas.clone() 248 | image[X, Y, :] = color 249 | return image 250 | 251 | 252 | @torch.no_grad() 253 | def visulization(render_norm, render_tex=None): 254 | if render_norm is None and render_tex is None: 255 | return None, None, None 256 | 257 | render_size = 256 258 | 259 | if render_norm is not None: 260 | render_norm = render_norm.detach() * 255.0 261 | render_norm = torch.rot90(render_norm, 1, [0, 1]).permute(2, 0, 1).unsqueeze(0) 262 | render_norm = F.interpolate(render_norm, size=(render_size, render_size)) 263 | render_norm = render_norm[0].cpu().numpy().transpose(1, 2, 0) 264 | reference = render_norm 265 | 266 | if render_tex is not None: 267 | render_tex = render_tex.detach() * 255.0 268 | render_tex = torch.rot90(render_tex, 1, [0, 1]).permute(2, 0, 1).unsqueeze(0) 269 | render_tex = F.interpolate(render_tex, size=(render_size, render_size)) 270 | render_tex = render_tex[0].cpu().numpy().transpose(1, 2, 0) 271 | reference = render_tex 272 | 273 | bg = np.logical_and( 274 | np.logical_and( 275 | reference[:, :, 0] == 255, 276 | reference[:, :, 1] == 255), 277 | reference[:, :, 2] == 255, 278 | ).reshape(render_size, render_size, 1) 279 | mask = ~bg 280 | 281 | return render_norm, render_tex, mask 282 | 283 | 284 | 285 | ######################################## 286 | ## define async processors 287 | ######################################## 288 | mean = torch.tensor(cfg.netG.mean).to(cuda_backbone_G).view(1, 3, 1, 1) 289 | std = torch.tensor(cfg.netG.std).to(cuda_backbone_G).view(1, 3, 1, 1) 290 | scaled_boxes = [torch.Tensor([[ 50.0, 0.0, 450.0, 500.0]]).to(cuda_backbone_G)] 291 | 292 | def update_camera(): 293 | extrinsic = np.array([ 294 | [1.0, 0.0, 0.0, 0.0], 295 | [0.0, 1.0, 0.0, 0.0], 296 | [0.0, 0.0, 1.0, -2.0], 297 | [0.0, 0.0, 0.0, 1.0], 298 | ], dtype=np.float32) 299 | 300 | if VIEW_MODE == 'FRONT': 301 | yaw, pitch = 20, 0 302 | elif VIEW_MODE == 'BACK': 303 | yaw, pitch = 20, 180 304 | elif VIEW_MODE == 'LEFT': 305 | yaw, pitch = 20, 90 306 | elif VIEW_MODE == 'RIGHT': 307 | yaw, pitch = 20, 270 308 | elif VIEW_MODE == 'AUTO': 309 | extrinsic, intrinsic = scene.update_camera(load=False) 310 | return extrinsic, intrinsic 311 | elif VIEW_MODE == 'LOAD': 312 | extrinsic, intrinsic = scene.update_camera(load=True) 313 | return extrinsic, intrinsic 314 | else: 315 | raise NotImplementedError 316 | 317 | intrinsic = scene.intrinsic 318 | R = np.matmul( 319 | make_rotate(math.radians(yaw), 0, 0), 320 | make_rotate(0, math.radians(pitch), 0) 321 | ) 322 | extrinsic[0:3, 0:3] = R 323 | return extrinsic, intrinsic 324 | 325 | 326 | processors=[ 327 | lambda data: {"input": data.to(cuda_backbone_G, non_blocking=True)}, 328 | 329 | # scene camera updating 330 | lambda data_dict: { 331 | **data_dict, 332 | **dict(zip( 333 | ["extrinsic", "intrinsic"], 334 | update_camera(), 335 | ))}, 336 | 337 | # calculate calib tensor 338 | lambda data_dict: { 339 | **data_dict, 340 | "calib_tensor": pifu_calib( 341 | data_dict["extrinsic"], data_dict["intrinsic"], device=cuda_recon) 342 | }, 343 | 344 | # instance segmentation: 345 | lambda data_dict: { 346 | **data_dict, 347 | **dict(zip( 348 | ["segm", "bboxes", "probs"], 349 | seg_engine(data_dict["input"], scaled_boxes) 350 | ))}, 351 | 352 | # update input by removing bg 353 | lambda data_dict: { 354 | **data_dict, 355 | "input_netG": ( 356 | ((data_dict["segm"][:, 0:3] * 0.5 + 0.5) - mean) / std 357 | )*data_dict["segm"][:, 3:4] 358 | }, 359 | 360 | # update input by removing bg 361 | lambda data_dict: { 362 | **data_dict, 363 | "input_netC": data_dict["segm"][:, 0:3] * data_dict["segm"][:, 3:4] 364 | }, 365 | 366 | # pifu netG feature extraction 367 | lambda data_dict: { 368 | **data_dict, 369 | "feat_tensor_G": netG.filter(data_dict["input_netG"]) 370 | }, 371 | 372 | # pifu netC feature extraction 373 | lambda data_dict: { 374 | **data_dict, 375 | "feat_tensor_C": netC.filter( 376 | data_dict["input_netC"].to(cuda_backbone_C, non_blocking=True), 377 | feat_prior=data_dict["feat_tensor_G"][-1][-1]) \ 378 | if (netC is not None) and ('TEXTURE' in DESKTOP_MODE or 'TEXTURE' in SERVER_MODE) else None 379 | }, 380 | 381 | # move feature to cuda_recon device 382 | lambda data_dict: { 383 | **data_dict, 384 | "feat_tensor_G": [ 385 | [feat.to(cuda_recon) for feat in feats] 386 | for feats in data_dict["feat_tensor_G"]] 387 | }, 388 | 389 | # pifu sdf space recon 390 | lambda data_dict: { 391 | **data_dict, 392 | "sdf": reconEngine( 393 | im_feat_list=data_dict["feat_tensor_G"], 394 | calib_tensor=data_dict["calib_tensor"]) 395 | }, 396 | 397 | # lambda data_dict: plot_mask3D( 398 | # data_dict["sdf"].to("cpu"), title="sdf"), 399 | 400 | # pifu visible vertices 401 | lambda data_dict: { 402 | **data_dict, 403 | **dict(zip( 404 | ["X", "Y", "Z", "norm"], 405 | forward_vertices(data_dict["sdf"], direction="front") 406 | ))}, 407 | 408 | lambda data_dict: { 409 | **data_dict, 410 | "X": data_dict['X'].to(cuda_color) if data_dict['X'] is not None else None, 411 | "Y": data_dict['Y'].to(cuda_color) if data_dict['X'] is not None else None, 412 | "Z": data_dict['Z'].to(cuda_color) if data_dict['X'] is not None else None, 413 | "norm": data_dict['norm'].to(cuda_color) if data_dict['X'] is not None else None, 414 | "calib_tensor": data_dict['calib_tensor'].to(cuda_color) if data_dict['X'] is not None else None, 415 | }, 416 | 417 | # pifu render normal 418 | lambda data_dict: { 419 | **data_dict, 420 | "render_norm": colorization( 421 | netC, 422 | None, 423 | data_dict["X"], 424 | data_dict["Y"], 425 | data_dict["Z"], 426 | data_dict["calib_tensor"], 427 | data_dict["norm"]) if ('NORM' in DESKTOP_MODE or 'NORM' in SERVER_MODE) else None 428 | }, 429 | 430 | # pifu render texture 431 | lambda data_dict: { 432 | **data_dict, 433 | "render_tex": colorization( 434 | netC, 435 | data_dict["feat_tensor_C"], 436 | data_dict["X"], 437 | data_dict["Y"], 438 | data_dict["Z"], 439 | data_dict["calib_tensor"], 440 | None) if data_dict["feat_tensor_C"] else None 441 | }, 442 | 443 | # visualization 444 | lambda data_dict: { 445 | **data_dict, 446 | **dict(zip( 447 | ["render_norm", "render_tex", "mask"], 448 | visulization( 449 | data_dict["render_norm"], 450 | data_dict["render_tex"]) 451 | ))}, 452 | ] 453 | 454 | 455 | ######################################## 456 | ## build async processor 457 | ######################################## 458 | loader = DataLoader( 459 | data_stream, 460 | batch_size=1, 461 | num_workers=1, 462 | pin_memory=True, 463 | processors=processors, 464 | ) 465 | 466 | 467 | def main_loop(): 468 | global DESKTOP_MODE, SERVER_MODE, VIEW_MODE 469 | 470 | window_server = np.ones((256, 256, 3), dtype=np.uint8) * 255 471 | window_desktop = np.ones((512, 1024, 3), dtype=np.uint8) * 255 472 | 473 | create_opengl_context(256, 256) 474 | renderer = AlbedoRender(width=256, height=256, multi_sample_rate=1) 475 | renderer.set_attrib(0, scene.vert_data) 476 | renderer.set_attrib(1, scene.uv_data) 477 | renderer.set_texture('TargetTexture', scene.texture_image) 478 | 479 | def render(extrinsic, intrinsic): 480 | uniform_dict = {'ModelMat': extrinsic, 'PerspMat': intrinsic} 481 | renderer.draw(uniform_dict) 482 | color = (renderer.get_color() * 255).astype(np.uint8) 483 | background = cv2.cvtColor(color, cv2.COLOR_RGB2BGR) 484 | background = cv2.resize(background, (256, 256)) 485 | return background 486 | 487 | for data_dict in tqdm.tqdm(loader): 488 | render_norm = data_dict["render_norm"] # [256, 256, 3] RGB 489 | render_tex = data_dict["render_tex"] # [256, 256, 3] RGB 490 | mask = data_dict["mask"] 491 | extrinsic = data_dict["extrinsic"] 492 | intrinsic = data_dict["intrinsic"] 493 | 494 | if DESKTOP_MODE is not None: 495 | input4c = data_dict["segm"].cpu().numpy()[0].transpose(1, 2, 0) # [512, 512, 4] 496 | input = (input4c[:, :, 0:3] * 0.5) + 0.5 497 | if DESKTOP_MODE == 'SEGM': 498 | segmentation = (input4c[:, :, 0:3] * input4c[:, :, 3:4] * 0.5) + 0.5 499 | window_desktop = np.uint8(np.hstack([ 500 | input * 255, 501 | segmentation * 255 502 | ])) # RGB 503 | elif DESKTOP_MODE == 'NORM': 504 | if render_norm is None: 505 | render_norm = np.ones((256, 256, 3), dtype=np.float32) * 255 506 | window_desktop = np.uint8(np.hstack([ 507 | input * 255, 508 | cv2.resize(render_norm, (512, 512)) 509 | ])) # RGB 510 | elif DESKTOP_MODE == 'TEXTURE': 511 | if render_tex is None: 512 | render_tex = np.ones((256, 256, 3), dtype=np.float32) * 255 513 | window_desktop = np.uint8(np.hstack([ 514 | input * 255, 515 | cv2.resize(render_tex, (512, 512)) 516 | ])) # RGB 517 | elif DESKTOP_MODE == 'TEXTURE_NORM': 518 | if render_tex is None: 519 | render_tex = np.ones((256, 256, 3), dtype=np.float32) * 255 520 | if render_norm is None: 521 | render_norm = np.ones((256, 256, 3), dtype=np.float32) * 255 522 | window_desktop = np.uint8(np.vstack([ 523 | render_tex, 524 | render_norm 525 | ])) # RGB 526 | else: 527 | window_desktop = None 528 | 529 | if DESKTOP_MODE is not None: 530 | # window_desktop = cv2.resize(window_desktop, (2400, 1200)) 531 | cv2.namedWindow('window_desktop', cv2.WINDOW_NORMAL) 532 | # cv2.setWindowProperty('window_desktop', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) 533 | cv2.imshow('window_desktop', window_desktop[:, :, ::-1]) 534 | 535 | if args.use_server: 536 | if SERVER_MODE == 'NORM': 537 | background = render(extrinsic, intrinsic) 538 | if mask is None: 539 | window_server = background 540 | else: 541 | window_server = np.uint8(mask * render_norm + (1 - mask) * background) 542 | elif SERVER_MODE == 'TEXTURE': 543 | background = render(extrinsic, intrinsic) 544 | if mask is None: 545 | window_server = background 546 | else: 547 | window_server = np.uint8(mask * render_tex + (1 - mask) * background) 548 | else: 549 | if render_norm is not None: 550 | window_server = np.uint8(render_norm) 551 | 552 | # yield window_desktop, window_server 553 | (flag, encodedImage) = cv2.imencode(".jpg", window_server[:, :, ::-1]) 554 | if not flag: 555 | continue 556 | yield(b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + 557 | bytearray(encodedImage) + b'\r\n') 558 | 559 | key = cv2.waitKey(1) 560 | if key == ord('q'): 561 | DESKTOP_MODE = 'SEGM' 562 | elif key == ord('w'): 563 | DESKTOP_MODE = 'NORM' 564 | elif key == ord('e'): 565 | DESKTOP_MODE = 'TEXTURE' 566 | elif key == ord('r'): 567 | DESKTOP_MODE = 'TEXTURE_NORM' 568 | 569 | # elif key == ord('a'): 570 | # SERVER_MODE = 'SEGM' 571 | elif key == ord('s'): 572 | SERVER_MODE = 'NORM' 573 | elif key == ord('d'): 574 | SERVER_MODE = 'TEXTURE' 575 | elif key == ord('f'): 576 | SERVER_MODE = None 577 | 578 | elif key == ord('z'): 579 | VIEW_MODE = 'FRONT' 580 | elif key == ord('x'): 581 | VIEW_MODE = 'BACK' 582 | elif key == ord('c'): 583 | VIEW_MODE = 'LEFT' 584 | elif key == ord('v'): 585 | VIEW_MODE = 'RIGHT' 586 | elif key == ord('b'): 587 | VIEW_MODE = 'AUTO' 588 | elif key == ord('n'): 589 | VIEW_MODE = 'LOAD' 590 | 591 | elif key == ord('g'): 592 | scene.shift_floor() 593 | 594 | 595 | 596 | 597 | if __name__ == '__main__': 598 | if args.use_server: 599 | ######################################## 600 | ## Flask related 601 | ######################################## 602 | app = Flask(__name__) 603 | 604 | @app.route("/") 605 | def index(): 606 | return render_template("test_flask.html") 607 | 608 | @app.route("/video_feed") 609 | def video_feed(): 610 | return Response(main_loop(), 611 | mimetype = "multipart/x-mixed-replace; boundary=frame") 612 | 613 | # start the flask app 614 | app.run(host=args.ip, port=args.port, debug=True, 615 | threaded=True, use_reloader=False) 616 | else: 617 | print('start main_loop.') 618 | for _ in main_loop(): 619 | pass 620 | -------------------------------------------------------------------------------- /RTL/recon.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | 4 | @torch.no_grad() 5 | def pifu_calib(extrinsic, intrinsic, device="cuda:0"): 6 | pifu_matrix = np.array([ 7 | [1.0, 0.0, 0.0, 0.0], 8 | [0.0, -1.0, 0.0, 0.0], 9 | [0.0, 0.0, 1.0, 0.0], 10 | [0.0, 0.0, 0.0, 1.0], 11 | ]) 12 | 13 | # orthognal 14 | intrinsic = intrinsic.copy() 15 | intrinsic[2, 2] = intrinsic[0, 0] 16 | intrinsic[2, 3] = 0 17 | extrinsic = extrinsic.copy() 18 | extrinsic[2, 3] = 0 19 | 20 | calib = np.linalg.inv( 21 | np.matmul(np.matmul(intrinsic, extrinsic), pifu_matrix)) 22 | 23 | calib_tensor = torch.from_numpy(calib).unsqueeze(0).float() 24 | calib_tensor = calib_tensor.to(device) 25 | return calib_tensor 26 | 27 | @torch.no_grad() 28 | def forward_vertices(sdf, direction="front"): 29 | ''' 30 | - direction: "front" | "back" | "left" | "right" 31 | ''' 32 | if sdf is None: 33 | return None, None, None, None 34 | else: 35 | sdf = sdf[0, 0] 36 | 37 | device = sdf.device 38 | resolution = sdf.size(2) 39 | if direction == "front": 40 | pass 41 | elif direction == "left": 42 | sdf = sdf.permute(2, 1, 0) 43 | elif direction == "back": 44 | inv_idx = torch.arange(sdf.size(2)-1, -1, -1).long() 45 | sdf = sdf[inv_idx, :, :] 46 | elif direction == "right": 47 | inv_idx = torch.arange(sdf.size(2)-1, -1, -1).long() 48 | sdf = sdf[inv_idx, :, :] 49 | sdf = sdf.permute(2, 1, 0) 50 | 51 | inv_idx = torch.arange(sdf.size(2)-1, -1, -1).long() 52 | sdf = sdf[inv_idx, :, :] 53 | sdf_all = sdf.permute(2, 1, 0) 54 | 55 | # shadow 56 | grad_v = (sdf_all>0.5) * torch.linspace(resolution, 1, steps=resolution).to(device) 57 | grad_c = torch.ones_like(sdf_all) * torch.linspace(0, resolution-1, steps=resolution).to(device) 58 | max_v, max_c = grad_v.max(dim=2) 59 | shadow = grad_c > max_c.view(resolution, resolution, 1) 60 | keep = (sdf_all>0.5) & (~shadow) 61 | 62 | p1 = keep.nonzero().t() #[3, N] 63 | p2 = p1.clone() # z 64 | p2[2, :] = (p2[2, :]-2).clamp(0, resolution) 65 | p3 = p1.clone() # y 66 | p3[1, :] = (p3[1, :]-2).clamp(0, resolution) 67 | p4 = p1.clone() # x 68 | p4[0, :] = (p4[0, :]-2).clamp(0, resolution) 69 | 70 | v1 = sdf_all[p1[0, :], p1[1, :], p1[2, :]] 71 | v2 = sdf_all[p2[0, :], p2[1, :], p2[2, :]] 72 | v3 = sdf_all[p3[0, :], p3[1, :], p3[2, :]] 73 | v4 = sdf_all[p4[0, :], p4[1, :], p4[2, :]] 74 | 75 | X = p1[0, :].long() #[N,] 76 | Y = p1[1, :].long() #[N,] 77 | Z = p2[2, :].float() * (0.5 - v1) / (v2 - v1) + p1[2, :].float() * (v2 - 0.5) / (v2 - v1) #[N,] 78 | Z = Z.clamp(0, resolution) 79 | 80 | # normal 81 | norm_z = v2 - v1 82 | norm_y = v3 - v1 83 | norm_x = v4 - v1 84 | # print (v2.min(dim=0)[0], v2.max(dim=0)[0], v3.min(dim=0)[0], v3.max(dim=0)[0]) 85 | 86 | norm = torch.stack([norm_x, norm_y, norm_z], dim=1) 87 | norm = norm / torch.norm(norm, p=2, dim=1, keepdim=True) 88 | 89 | return X, Y, Z, norm 90 | -------------------------------------------------------------------------------- /RTL/run_camera.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import torch 4 | import tqdm 5 | import numpy as np 6 | import cv2 7 | 8 | import streamer_pytorch as streamer 9 | import human_inst_seg 10 | 11 | from dataloader import DataLoader 12 | from scene import MonoPortScene 13 | from recon import pifu_calib, forward_vertices 14 | 15 | 16 | ######################################## 17 | ## load configs 18 | ######################################## 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument( 21 | '--camera', action="store_true") 22 | parser.add_argument( 23 | '--images', default="", nargs="*") 24 | parser.add_argument( 25 | '--image_folder', default=None) 26 | parser.add_argument( 27 | '--videos', default="", nargs="*") 28 | parser.add_argument( 29 | '--loop', action="store_true") 30 | parser.add_argument( 31 | '--vis', action="store_true") 32 | parser.add_argument( 33 | '--use_VRweb', action="store_true") 34 | args = parser.parse_args() 35 | device = 'cuda:0' 36 | scaled_boxes = [torch.Tensor([[ 50.0, 0.0, 450.0, 500.0]]).to(device)] 37 | 38 | 39 | ######################################## 40 | ## initialize data streamer 41 | ######################################## 42 | print (f'initialize data streamer ...') 43 | if args.camera: 44 | data_stream = streamer.CaptureStreamer(pad=False) 45 | elif len(args.videos) > 0: 46 | data_stream = streamer.VideoListStreamer( 47 | args.videos * (10 if args.loop else 1)) 48 | elif len(args.images) > 0: 49 | data_stream = streamer.ImageListStreamer( 50 | args.images * (10000 if args.loop else 1)) 51 | elif args.image_folder is not None: 52 | images = sorted(glob.glob(args.image_folder+'/*.jpg')) 53 | images += sorted(glob.glob(args.image_folder+'/*.png')) 54 | data_stream = streamer.ImageListStreamer( 55 | images * (10 if args.loop else 1)) 56 | 57 | 58 | ######################################## 59 | ## human segmentation model 60 | ######################################## 61 | seg_engine = human_inst_seg.Segmentation( 62 | device=device, verbose=False) 63 | seg_engine.eval() 64 | 65 | 66 | processors=[ 67 | lambda data: {"input": data.to(device, non_blocking=True)}, 68 | 69 | # instance segmentation: 70 | lambda data_dict: { 71 | **data_dict, 72 | **dict(zip( 73 | ["segm", "bboxes", "probs"], 74 | seg_engine(data_dict["input"], scaled_boxes) 75 | ))}, 76 | ] 77 | 78 | 79 | ######################################## 80 | ## build async processor 81 | ######################################## 82 | loader = DataLoader( 83 | data_stream, 84 | batch_size=1, 85 | num_workers=1, 86 | pin_memory=True, 87 | processors=processors, 88 | ) 89 | 90 | 91 | @torch.no_grad() 92 | def main_loop(): 93 | for data_dict in tqdm.tqdm(loader): 94 | segm = data_dict["segm"].cpu().numpy()[0].transpose(1, 2, 0) # [512, 512, 4] 95 | input = (segm[:, :, 0:3] * 0.5) + 0.5 96 | output = (segm[:, :, 0:3] * segm[:, :, 3:4] * 0.5) + 0.5 97 | x1, y1, x2, y2 = scaled_boxes[0].cpu().numpy()[0] 98 | 99 | window = np.hstack([input, output]).astype(np.float32) 100 | window = np.uint8(window[:, :, ::-1] * 255) # To BGR 101 | window = cv2.rectangle(cv2.UMat(window), (x1, y1), (x2, y2), (255, 0, 0), thickness=3) 102 | 103 | window = cv2.resize(window, (0, 0), fx=3, fy=3) 104 | cv2.imshow('segmenation', window) 105 | cv2.waitKey(1) 106 | 107 | 108 | if __name__ == '__main__': 109 | try: 110 | main_loop() 111 | except Exception as e: 112 | print (e) 113 | del data_stream -------------------------------------------------------------------------------- /RTL/scene.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import math 4 | import os 5 | import json 6 | 7 | from monoport.lib.render.gl.glcontext import create_opengl_context 8 | from monoport.lib.render.gl.AlbedoRender import AlbedoRender 9 | from monoport.lib.render.BaseCamera import BaseCamera 10 | from monoport.lib.render.PespectiveCamera import PersPectiveCamera 11 | from monoport.lib.render.CameraPose import CameraPose 12 | 13 | from monoport.lib.mesh_util import load_obj_mesh 14 | 15 | 16 | _RTL_DATA_FOLDER = os.path.join( 17 | os.path.dirname(__file__)) 18 | 19 | 20 | def _load_floor(floor_type="grass", floor_size=3.0, floor_center=np.array([0, -0.9, 0])): 21 | mesh_file = os.path.join( 22 | _RTL_DATA_FOLDER, 23 | f'floor/{floor_type}/{floor_type}.obj') 24 | text_file = os.path.join( 25 | _RTL_DATA_FOLDER, 26 | f'floor/{floor_type}/{floor_type}.jpg') 27 | vertices, faces, normals, faces_normals, textures, face_textures = load_obj_mesh( 28 | mesh_file, with_normal=True, with_texture=True) 29 | vertices = vertices[:, [0, 2, 1]] 30 | 31 | # change cm to meter 32 | vertices = vertices / 150 * floor_size 33 | vertices = vertices - vertices.mean(axis=0) 34 | vertices += floor_center 35 | 36 | texture_image = cv2.imread(text_file) 37 | # texture_image = cv2.cvtColor(texture_image, cv2.COLOR_BGR2RGB) 38 | 39 | # Here we pack the vertex data needed for the render 40 | vert_data = vertices[faces.reshape([-1])] 41 | uv_data = textures[face_textures.reshape([-1])] 42 | return vert_data, uv_data, texture_image 43 | 44 | 45 | def _load_intrinsic(near=0.0, far=10.0, scale=2.0): 46 | intrinsic_cam = BaseCamera() 47 | intrinsic_cam.near = near 48 | intrinsic_cam.far = far 49 | intrinsic_cam.set_parameters(scale, scale) 50 | return intrinsic_cam.get_projection_mat() 51 | 52 | 53 | def _load_extrinsic(): 54 | path = os.path.join( 55 | _RTL_DATA_FOLDER, 'webxr/modelview.json') 56 | with open(path, 'r') as f: 57 | extrinsic = json.load(f)['data'] 58 | extrinsic = np.array(extrinsic).reshape(4, 4).T 59 | return extrinsic 60 | 61 | 62 | def make_rotate(rx, ry, rz): 63 | sinX = np.sin(rx) 64 | sinY = np.sin(ry) 65 | sinZ = np.sin(rz) 66 | 67 | cosX = np.cos(rx) 68 | cosY = np.cos(ry) 69 | cosZ = np.cos(rz) 70 | 71 | Rx = np.zeros((3,3)) 72 | Rx[0, 0] = 1.0 73 | Rx[1, 1] = cosX 74 | Rx[1, 2] = -sinX 75 | Rx[2, 1] = sinX 76 | Rx[2, 2] = cosX 77 | 78 | Ry = np.zeros((3,3)) 79 | Ry[0, 0] = cosY 80 | Ry[0, 2] = sinY 81 | Ry[1, 1] = 1.0 82 | Ry[2, 0] = -sinY 83 | Ry[2, 2] = cosY 84 | 85 | Rz = np.zeros((3,3)) 86 | Rz[0, 0] = cosZ 87 | Rz[0, 1] = -sinZ 88 | Rz[1, 0] = sinZ 89 | Rz[1, 1] = cosZ 90 | Rz[2, 2] = 1.0 91 | 92 | R = np.matmul(np.matmul(Rz,Ry),Rx) 93 | return R 94 | 95 | 96 | class MonoPortScene: 97 | def __init__(self, size=(512, 512), debug=False): 98 | self.floors = ["carpet", "drum", "grass", "mousemat", "table"] 99 | self.vert_data, self.uv_data, self.texture_image = _load_floor() 100 | self.intrinsic = _load_intrinsic() 101 | 102 | create_opengl_context(size[0], size[1]) 103 | self.renderer = AlbedoRender(width=size[0], height=size[1], multi_sample_rate=1) 104 | self.renderer.set_attrib(0, self.vert_data) 105 | self.renderer.set_attrib(1, self.uv_data) 106 | self.renderer.set_texture('TargetTexture', self.texture_image) 107 | 108 | self.extrinsic = np.array([ 109 | [1.0, 0.0, 0.0, 0.0], 110 | [0.0, 1.0, 0.0, 0.0], 111 | [0.0, 0.0, 1.0, -2.0], 112 | [0.0, 0.0, 0.0, 1.0], 113 | ], dtype=np.float32) 114 | self.step = 0 115 | 116 | def shift_floor(self): 117 | self.vert_data, self.uv_data, self.texture_image = _load_floor(floor_type=np.random.choice(self.floors)) 118 | self.renderer.set_attrib(0, self.vert_data) 119 | self.renderer.set_attrib(1, self.uv_data) 120 | self.renderer.set_texture('TargetTexture', self.texture_image) 121 | 122 | def update_camera(self, load=False): 123 | if load == False: 124 | if self.step < 3600000: 125 | yaw = 20 126 | pitch = self.step 127 | else: 128 | yaw = self.step % 180 129 | pitch = 0 130 | 131 | R = np.matmul( 132 | make_rotate(math.radians(yaw), 0, 0), 133 | make_rotate(0, math.radians(pitch), 0) 134 | ) 135 | self.extrinsic[0:3, 0:3] = R 136 | self.step += 3 137 | extrinsic = self.extrinsic 138 | else: 139 | while True: 140 | try: 141 | extrinsic = _load_extrinsic() 142 | break 143 | except Exception as e: 144 | print (e) 145 | 146 | return extrinsic, self.intrinsic 147 | 148 | def render(self, extrinsic, intrinsic): 149 | uniform_dict = { 150 | 'ModelMat': extrinsic, 151 | 'PerspMat': intrinsic, 152 | } 153 | self.renderer.draw( 154 | uniform_dict 155 | ) 156 | 157 | color = (self.renderer.get_color() * 255).astype(np.uint8) 158 | color = cv2.cvtColor(color, cv2.COLOR_RGB2BGR) 159 | return color 160 | 161 | 162 | if __name__ == '__main__': 163 | import tqdm 164 | scene = MonoPortScene(debug=True) 165 | 166 | for _ in tqdm.tqdm(range(10000)): 167 | extrinsic, intrinsic = scene.update_camera() 168 | background = scene.render(extrinsic, intrinsic) 169 | # print (extrinsic) 170 | cv2.imshow('scene', background) 171 | cv2.waitKey(15) -------------------------------------------------------------------------------- /RTL/templates/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/RTL/templates/qrcode.png -------------------------------------------------------------------------------- /RTL/templates/test_flask.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MonoPort (SIGGRAPH Real-Time Live 2020) 5 | 6 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 |
57 | 58 |

MonoPort VR Demo

59 |

SIGGRAPH Real-Time Live 2020

60 | 61 |
62 |
63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /figs/algo_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/algo_comparison.png -------------------------------------------------------------------------------- /figs/book_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/book_cover.png -------------------------------------------------------------------------------- /figs/comp_others.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/comp_others.png -------------------------------------------------------------------------------- /figs/dance.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/dance.gif -------------------------------------------------------------------------------- /figs/gpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/gpu.png -------------------------------------------------------------------------------- /figs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/icon.png -------------------------------------------------------------------------------- /figs/livecap_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/livecap_comparison.png -------------------------------------------------------------------------------- /figs/lossless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/lossless.png -------------------------------------------------------------------------------- /figs/ohem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/ohem.png -------------------------------------------------------------------------------- /figs/robustness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/robustness.png -------------------------------------------------------------------------------- /figs/rtl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/rtl.jpg -------------------------------------------------------------------------------- /figs/shift.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/shift.gif -------------------------------------------------------------------------------- /figs/suppl_results1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/suppl_results1.png -------------------------------------------------------------------------------- /figs/suppl_results2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/suppl_results2.png -------------------------------------------------------------------------------- /figs/twoside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/twoside.png -------------------------------------------------------------------------------- /figs/zeng.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/figs/zeng.gif -------------------------------------------------------------------------------- /monoport/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/monoport/__init__.py -------------------------------------------------------------------------------- /monoport/lib/common/config.py: -------------------------------------------------------------------------------- 1 | from yacs.config import CfgNode as CN 2 | 3 | 4 | _C = CN() 5 | 6 | # needed by trainer 7 | _C.name = 'default' 8 | _C.checkpoints_path = './data/checkpoints/' 9 | _C.results_path = './data/results/' 10 | _C.learning_rate = 1e-3 11 | _C.weight_decay = 0.0 12 | _C.momentum = 0.0 13 | _C.optim = 'RMSprop' 14 | _C.schedule = [15, 20] 15 | _C.gamma = 0.1 16 | _C.resume = False 17 | 18 | # needed by train() 19 | _C.batch_size = 4 20 | _C.num_threads = 4 21 | _C.num_epoch = 100 22 | _C.freq_plot = 10 23 | _C.freq_save = 100 24 | _C.freq_eval = 100 25 | _C.freq_vis = 100 26 | 27 | 28 | # --- netG options --- 29 | _C.netG = CN() 30 | _C.netG.mean = (0.5, 0.5, 0.5) 31 | _C.netG.std = (0.5, 0.5, 0.5) 32 | _C.netG.ckpt_path = '' 33 | _C.netG.projection = 'orthogonal' 34 | 35 | # --- netG:backbone options --- 36 | _C.netG.backbone = CN() 37 | _C.netG.backbone.IMF = 'PIFuHGFilters' 38 | 39 | # --- netG:normalizer options --- 40 | _C.netG.normalizer = CN() 41 | _C.netG.normalizer.IMF = 'PIFuNomalizer' 42 | _C.netG.normalizer.soft_onehot = False 43 | _C.netG.normalizer.soft_dim = 64 44 | 45 | # --- netG:head options --- 46 | _C.netG.head = CN() 47 | _C.netG.head.IMF = 'PIFuNetGMLP' 48 | 49 | # --- netG:loss options --- 50 | _C.netG.loss = CN() 51 | _C.netG.loss.IMF = 'MSE' 52 | 53 | 54 | # --- netC options --- 55 | _C.netC = CN() 56 | _C.netC.mean = (0.5, 0.5, 0.5) 57 | _C.netC.std = (0.5, 0.5, 0.5) 58 | _C.netC.ckpt_path = '' 59 | _C.netC.projection = 'orthogonal' 60 | 61 | # --- netC:backbone options --- 62 | _C.netC.backbone = CN() 63 | _C.netC.backbone.IMF = 'PIFuResBlkFilters' 64 | 65 | # --- netC:normalizer options --- 66 | _C.netC.normalizer = CN() 67 | _C.netC.normalizer.IMF = 'PIFuNomalizer' 68 | _C.netC.normalizer.soft_onehot = False 69 | _C.netC.normalizer.soft_dim = 64 70 | 71 | # --- netC:head options --- 72 | _C.netC.head = CN() 73 | _C.netC.head.IMF = 'PIFuNetCMLP' 74 | 75 | # --- netC:loss options --- 76 | _C.netC.loss = CN() 77 | _C.netC.loss.IMF = 'L1' 78 | 79 | 80 | # --- dataset options --- 81 | _C.dataset = CN() 82 | _C.dataset.aug_bri = 0.4 83 | _C.dataset.aug_con = 0.4 84 | _C.dataset.aug_sat = 0.4 85 | _C.dataset.aug_hue = 0.0 86 | _C.dataset.blur = 1.0 87 | _C.dataset.num_sample_geo = 5000 88 | _C.dataset.num_sample_color = 0 89 | _C.dataset.sigma_geo = 0.05 90 | _C.dataset.sigma_color = 0.001 91 | _C.dataset.pre_load = False 92 | _C.dataset.align_hip = False 93 | _C.dataset.score_filter = 0.0 94 | _C.dataset.scale_uniform = False 95 | 96 | def get_cfg_defaults(): 97 | """Get a yacs CfgNode object with default values for my_project.""" 98 | # Return a clone so that the defaults will not be altered 99 | # This is for the "local variable" use pattern 100 | return _C.clone() 101 | 102 | # Alternatively, provide a way to import the defaults as 103 | # a global singleton: 104 | # cfg = _C # users can `from config import cfg` 105 | 106 | # cfg = get_cfg_defaults() 107 | # cfg.merge_from_file('../configs/example.yaml') 108 | 109 | # # Now override from a list (opts could come from the command line) 110 | # opts = ['dataset.root', '../data/XXXX', 'learning_rate', '1e-2'] 111 | # cfg.merge_from_list(opts) 112 | 113 | -------------------------------------------------------------------------------- /monoport/lib/common/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | __all__ = ["colorlogger"] 5 | 6 | OK = '\033[92m' 7 | WARNING = '\033[93m' 8 | FAIL = '\033[91m' 9 | END = '\033[0m' 10 | 11 | PINK = '\033[95m' 12 | BLUE = '\033[94m' 13 | GREEN = OK 14 | RED = FAIL 15 | WHITE = END 16 | YELLOW = WARNING 17 | 18 | class colorlogger(): 19 | def __init__(self, logdir, name='train_logs.txt'): 20 | # set log 21 | self._logger = logging.getLogger(name) 22 | self._logger.setLevel(logging.INFO) 23 | log_file = os.path.join(logdir, name) 24 | if not os.path.exists(logdir): 25 | os.makedirs(logdir) 26 | file_log = logging.FileHandler(log_file, mode='a') 27 | file_log.setLevel(logging.INFO) 28 | console_log = logging.StreamHandler() 29 | console_log.setLevel(logging.INFO) 30 | formatter = logging.Formatter( 31 | "{}%(asctime)s{} %(message)s".format(GREEN, END), 32 | "%m-%d %H:%M:%S") 33 | file_log.setFormatter(formatter) 34 | console_log.setFormatter(formatter) 35 | self._logger.addHandler(file_log) 36 | self._logger.addHandler(console_log) 37 | 38 | def debug(self, msg): 39 | self._logger.debug(str(msg)) 40 | 41 | def info(self, msg): 42 | self._logger.info(str(msg)) 43 | 44 | def warning(self, msg): 45 | self._logger.warning(WARNING + 'WRN: ' + str(msg) + END) 46 | 47 | def critical(self, msg): 48 | self._logger.critical(RED + 'CRI: ' + str(msg) + END) 49 | 50 | def error(self, msg): 51 | self._logger.error(RED + 'ERR: ' + str(msg) + END) -------------------------------------------------------------------------------- /monoport/lib/common/trainer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from easydict import EasyDict as edict 4 | 5 | import torch 6 | import torch.nn as nn 7 | 8 | from tensorboardX import SummaryWriter 9 | from .logger import colorlogger 10 | 11 | 12 | class Trainer(): 13 | def __init__(self, net, opt=None, use_tb=True): 14 | self.opt = opt if opt is not None else Trainer.get_default_opt() 15 | self.net = nn.DataParallel(net) 16 | self.net.train() 17 | 18 | # set cache path 19 | self.checkpoints_path = os.path.join(opt.checkpoints_path, opt.name) 20 | os.makedirs(self.checkpoints_path, exist_ok=True) 21 | self.results_path = os.path.join(opt.results_path, opt.name) 22 | os.makedirs(self.results_path, exist_ok=True) 23 | 24 | # set logger 25 | self.logger = colorlogger(logdir=self.results_path) 26 | self.logger.info(self.opt) 27 | 28 | # set tensorboard 29 | if use_tb: 30 | self.tb_writer = SummaryWriter(logdir=self.results_path) 31 | 32 | # set optimizer 33 | learning_rate = opt.learning_rate 34 | weight_decay = opt.weight_decay 35 | momentum = opt.momentum 36 | if opt.optim == "Adadelta": 37 | self.optimizer = torch.optim.Adadelta( 38 | self.net.parameters(), lr=learning_rate, 39 | weight_decay=weight_decay) 40 | elif opt.optim == "SGD": 41 | self.optimizer = torch.optim.SGD( 42 | self.net.parameters(), lr=learning_rate, 43 | momentum=momentum, weight_decay=weight_decay) 44 | elif opt.optim == "Adam": 45 | self.optimizer = torch.optim.Adam( 46 | self.net.parameters(), lr=learning_rate) 47 | elif opt.optim == "RMSprop": 48 | self.optimizer = torch.optim.RMSprop( 49 | self.net.parameters(), lr=learning_rate, 50 | weight_decay=weight_decay, momentum=momentum) 51 | else: 52 | raise NotImplementedError 53 | 54 | # set scheduler 55 | self.scheduler = torch.optim.lr_scheduler.MultiStepLR( 56 | self.optimizer, milestones=opt.schedule, gamma=opt.gamma) 57 | 58 | self.epoch = 0 59 | self.iteration = 0 60 | 61 | def update_ckpt(self, filename, epoch, iteration, **kwargs): 62 | # `kwargs` can be used to store loss, accuracy, epoch, iteration and so on. 63 | ckpt_path = os.path.join(self.checkpoints_path, filename) 64 | saved_dict = { 65 | "opt": self.opt, 66 | "net": self.net.module.state_dict(), 67 | "optimizer": self.optimizer.state_dict(), 68 | "scheduler": self.scheduler.state_dict(), 69 | "epoch": epoch, 70 | "iteration": iteration, 71 | } 72 | for k, v in kwargs.items(): 73 | saved_dict[k] = v 74 | torch.save(saved_dict, ckpt_path) 75 | self.logger.info(f'save ckpt to {ckpt_path}') 76 | 77 | def load_ckpt(self, ckpt_path): 78 | self.logger.info(f'load ckpt from {ckpt_path}') 79 | ckpt = torch.load(ckpt_path, map_location="cpu") 80 | self.net.module.load_state_dict(ckpt["net"]) 81 | 82 | if self.opt.resume: 83 | self.logger.info('loading for optimizer & scheduler ...') 84 | self.optimizer.load_state_dict(ckpt["optimizer"]) 85 | self.scheduler.load_state_dict(ckpt["scheduler"]) 86 | 87 | self.epoch = ckpt["epoch"] 88 | self.logger.info(f'loading for start epoch ... {self.epoch}') 89 | self.iteration = ckpt["iteration"] 90 | self.logger.info(f'loading for start iteration ... {self.iteration}') 91 | 92 | @classmethod 93 | def get_default_opt(cls): 94 | opt = edict() 95 | 96 | opt.name = 'example' 97 | opt.checkpoints_path = '../data/checkpoints/' 98 | opt.results_path = '../data/results/' 99 | opt.learning_rate = 1e-3 100 | opt.weight_decay = 0.0 101 | opt.momentum = 0.0 102 | opt.optim = 'RMSprop' 103 | opt.schedule = [40, 60] 104 | opt.gamma = 0.1 105 | opt.resume = False 106 | return opt -------------------------------------------------------------------------------- /monoport/lib/dataset/ppl_dynamic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import glob 4 | import torch 5 | import random 6 | import tqdm 7 | import tinyobjloader 8 | 9 | from .utils import load_image, projection 10 | 11 | 12 | def load_calib(calib_path, render_size=512): 13 | calib_data = np.loadtxt(calib_path, dtype=float) 14 | extrinsic = calib_data[:4, :4] 15 | intrinsic = calib_data[4:8, :4] 16 | calib_mat = np.matmul(intrinsic, extrinsic) 17 | calib = torch.from_numpy(calib_mat) 18 | return calib 19 | 20 | 21 | def load_obj_verts(mesh_path): 22 | # Create reader. 23 | reader = tinyobjloader.ObjReader() 24 | 25 | # Load .obj(and .mtl) using default configuration 26 | ret = reader.ParseFromFile(mesh_path) 27 | 28 | if ret == False: 29 | print("Failed to load : ", mesh_path) 30 | return None 31 | 32 | # note here for wavefront obj, #v might not equal to #vt, same as #vn. 33 | attrib = reader.GetAttrib() 34 | verts = np.array(attrib.vertices).reshape(-1, 3) 35 | return verts 36 | 37 | 38 | class PPLDynamicDataset(): 39 | def __init__(self, 40 | cfg, 41 | mean=(0.5, 0.5, 0.5), 42 | std=(0.5, 0.5, 0.5), 43 | training=True, 44 | split='train', 45 | shared_dict=None): 46 | self.root = '/home/rui/local/projects/MonoPortDataset/data/' 47 | self.root_render = '/media/linux_data/data/pifu_orth_v1/' 48 | self.cfg = cfg 49 | self.mean = mean 50 | self.std = std 51 | self.training = training if split == 'train' else False 52 | self.split = split 53 | self.shared_dict = shared_dict 54 | self.rotations = range(0, 360, 1) 55 | self.motion_list = self.get_motion_list() 56 | self.santity_check() 57 | 58 | def __len__(self): 59 | return len(self.motion_list) * len(self.rotations) 60 | 61 | def __getitem__(self, index): 62 | try: 63 | return self.get_item(index) 64 | except Exception as e: 65 | print(e) 66 | return self.get_item(index=random.randint(0, self.__len__() - 1)) 67 | 68 | def get_item(self, index): 69 | rid = index % len(self.rotations) 70 | mid = index // len(self.rotations) 71 | 72 | rotation = self.rotations[rid] 73 | motion = self.motion_list[mid] 74 | 75 | calib_path = self.get_calib_path(motion, rotation) 76 | calib = load_calib(calib_path) 77 | 78 | # align 79 | if self.cfg.align_hip: 80 | skel_path = self.get_skeleton_path(motion) 81 | center = np.loadtxt(skel_path, usecols=[1, 2, 3])[1, :] / 100 82 | center_proj = projection(center.reshape(1, 3), calib).reshape(3,) 83 | calib[2, 3] -= center_proj[2] 84 | else: 85 | center = np.loadtxt(self.get_center_path(motion)).reshape(1, 3) 86 | center_proj = projection(center, calib).reshape(3,) 87 | calib[2, 3] -= center_proj[2] 88 | 89 | # scale to uniform size 90 | if self.cfg.scale_uniform: 91 | scale_base = 1.8 / np.loadtxt(self.get_scale_path(motion))[1] 92 | else: 93 | scale_base = 1 94 | 95 | # load image 96 | image_path = self.get_image_path(motion, rotation) 97 | if self.training: 98 | scale = random.uniform(0.9, 1.1) * scale_base 99 | calib[0:3] *= scale 100 | image, mask = load_image( 101 | image_path, None, 102 | crop_size=int(512/scale), 103 | input_size=512, 104 | mean=self.mean, 105 | std=self.std, 106 | blur=self.cfg.blur, 107 | brightness=self.cfg.aug_bri, 108 | contrast=self.cfg.aug_con, 109 | saturation=self.cfg.aug_sat, 110 | hue=self.cfg.aug_hue) 111 | else: 112 | scale = scale_base 113 | calib[0:3] *= scale 114 | image, mask = load_image( 115 | image_path, None, 116 | crop_size=int(512/scale), 117 | input_size=512, 118 | mean=self.mean, 119 | std=self.std) 120 | 121 | # left-right flip aug 122 | if self.training and random.random() < 0.5: 123 | calib[0, :] *= -1 124 | image = image.flip(dims=[2]) 125 | mask = mask.flip(dims=[2]) 126 | 127 | # return data dict 128 | data_dict = { 129 | 'motion': str(motion), 130 | 'rotation': rotation, 131 | 'image': image.float(), 132 | 'mask': mask.float(), 133 | 'calib': calib.float(), 134 | 'mesh_path': self.get_mesh_path(motion), 135 | } 136 | 137 | # sampling 138 | if self.cfg.num_sample_geo: 139 | samples_geo, labels_geo = self.get_sampling_geo(motion) 140 | data_dict.update({ 141 | 'samples_geo': samples_geo.float(), 142 | 'labels_geo': labels_geo.float()}) 143 | 144 | if self.cfg.num_sample_color: 145 | raise NotImplementedError 146 | 147 | return data_dict 148 | 149 | def get_motion_list(self): 150 | # val motions 151 | val_motions = [] 152 | val_subjects = np.loadtxt(os.path.join(self.root, 'renderppl', 'val.txt'), dtype=str) 153 | 154 | if self.cfg.score_filter > 0: 155 | tags = np.loadtxt( 156 | './data/dynamic_chamfer.txt', dtype=str, usecols=[0, 1, 2] 157 | )[::4] 158 | scores = np.loadtxt( 159 | './data/dynamic_chamfer.txt', dtype=float, usecols=[4] 160 | ).reshape(-1, 4).mean(axis=1) 161 | tags = tags[scores < self.cfg.score_filter] 162 | train_motions = [ 163 | [subject, action, int(frame)] for (subject, action, frame) in tags] 164 | else: 165 | # scan all motions 166 | paths = sorted(glob.glob(os.path.join(self.root_render, '*/*/*/render'))) 167 | train_motions = [] 168 | for path in paths: 169 | splits = path.split('/') 170 | subject, action, frame = [splits[-4], splits[-3], int(splits[-2])] 171 | if subject in val_subjects: 172 | val_motions.append([subject, action, frame]) 173 | else: 174 | train_motions.append([subject, action, frame]) 175 | 176 | if self.split == 'train': 177 | return train_motions 178 | else: 179 | return val_motions 180 | 181 | def santity_check(self): 182 | print (f'santity check of the dataset ... before: {len(self.motion_list)} motions.') 183 | motion_list_valid = [] 184 | for motion in tqdm.tqdm(self.motion_list): 185 | rotation = self.rotations[-1] 186 | subject, action, frame = motion 187 | if not os.path.exists(self.get_texture_path(motion, rotation)): 188 | continue 189 | if not os.path.exists(self.get_image_path(motion, rotation)): 190 | continue 191 | if not os.path.exists(self.get_mesh_path(motion)): 192 | continue 193 | if not os.path.exists(self.get_calib_path(motion, rotation)): 194 | continue 195 | if not os.path.exists(self.get_sample_path(motion)): 196 | continue 197 | if not os.path.exists(self.get_skeleton_path(motion)): 198 | continue 199 | if not os.path.exists(self.get_center_path(motion)): 200 | continue 201 | skel_path = self.get_skeleton_path(motion) 202 | skel = np.loadtxt(skel_path, usecols=[1, 2, 3]) / 100 203 | if skel[6, 1] < skel[1, 1]: # y(head) < y(hip) 204 | continue 205 | calib_path = self.get_calib_path(motion, rotation) 206 | calib = load_calib(calib_path) 207 | skel_proj = projection(skel, calib) 208 | if skel_proj.min() < -1.0 or skel_proj.max() > 1.0: 209 | continue 210 | motion_list_valid.append(motion) 211 | self.motion_list = motion_list_valid 212 | print (f'santity check of the dataset ... after: {len(self.motion_list)} motions.') 213 | 214 | def get_texture_path(self, motion, rotation): 215 | subject, action, frame = motion 216 | return os.path.join( 217 | self.root_render, subject, action, f'{frame:06d}', 218 | 'uv_render', f'{rotation:03d}.jpg') # be careful! 219 | 220 | def get_image_path(self, motion, rotation): 221 | subject, action, frame = motion 222 | return os.path.join( 223 | self.root_render, subject, action, f'{frame:06d}', 224 | 'render', f'{rotation:03d}.png') 225 | 226 | def get_mesh_path(self, motion): 227 | subject, action, frame = motion 228 | return os.path.join( 229 | self.root_render, subject, action, f'{frame:06d}', 230 | 'mesh_poisson.obj') 231 | 232 | def get_calib_path(self, motion, rotation): 233 | subject, action, frame = motion 234 | return os.path.join( 235 | self.root_render, subject, action, f'{frame:06d}', 236 | 'calib', f'{rotation:03d}.txt') 237 | 238 | def get_skeleton_path(self, motion): 239 | subject, action, frame = motion 240 | return os.path.join( 241 | self.root_render, subject, action, f'{frame:06d}', 242 | 'skeleton.txt') 243 | 244 | def get_center_path(self, motion): 245 | subject, action, frame = motion 246 | return os.path.join( 247 | self.root_render, subject, action, f'{frame:06d}', 248 | 'center.txt') 249 | 250 | def get_scale_path(self, motion): 251 | subject, action, frame = motion 252 | return os.path.join( 253 | self.root_render, subject, action, f'{frame:06d}', 254 | 'scale.txt') 255 | 256 | def get_sample_path(self, motion): 257 | subject, action, frame = motion 258 | return os.path.join( 259 | self.root_render, subject, action, f'{frame:06d}', 260 | f'samples_{self.cfg.sigma_geo:.2f}_v3') 261 | 262 | def get_sampling_geo(self, motion): 263 | num_sample = self.cfg.num_sample_geo 264 | samples_path = self.get_sample_path(motion) 265 | 266 | samples_surface = np.load( 267 | os.path.join(samples_path, f'surface_{random.randint(0, 99)}.npy')) 268 | samples_surface = samples_surface[np.random.choice( 269 | samples_surface.shape[0], 4 * num_sample, replace=False)] 270 | 271 | samples_uniform = np.load( 272 | os.path.join(samples_path, f'uniform_{random.randint(0, 99)}.npy')) 273 | samples_uniform = samples_uniform[np.random.choice( 274 | samples_uniform.shape[0], num_sample // 4, replace=False)] 275 | 276 | samples = np.concatenate([samples_surface, samples_uniform]) 277 | np.random.shuffle(samples) 278 | inside = samples[:, 3] 279 | samples = samples[:, 0:3] 280 | 281 | # balance in and out 282 | inside_samples = samples[inside > 0.5] 283 | outside_samples = samples[inside <= 0.5] 284 | 285 | nin = inside_samples.shape[0] 286 | if nin > num_sample // 2: 287 | inside_samples = inside_samples[:num_sample // 2] 288 | outside_samples = outside_samples[:num_sample // 2] 289 | else: 290 | outside_samples = outside_samples[:(num_sample - nin)] 291 | 292 | samples = np.concatenate([inside_samples, outside_samples], 0) 293 | labels = np.concatenate([ 294 | np.ones(inside_samples.shape[0]), np.zeros(outside_samples.shape[0])]) 295 | 296 | samples = torch.from_numpy(samples) 297 | labels = torch.from_numpy(labels) 298 | return samples, labels 299 | 300 | -------------------------------------------------------------------------------- /monoport/lib/dataset/ppl_static.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import glob 4 | import torch 5 | import random 6 | 7 | from .utils import load_image 8 | 9 | 10 | def load_calib(calib_path, render_size=512): 11 | # loading calibration data 12 | param = np.load(calib_path, allow_pickle=True) 13 | # pixel unit / world unit 14 | ortho_ratio = param.item().get('ortho_ratio') 15 | # world unit / model unit 16 | scale = param.item().get('scale') 17 | # camera center world coordinate 18 | center = param.item().get('center') 19 | # model rotation 20 | R = param.item().get('R') 21 | 22 | translate = -np.matmul(R, center).reshape(3, 1) 23 | extrinsic = np.concatenate([R, translate], axis=1) 24 | extrinsic = np.concatenate([extrinsic, np.array([0, 0, 0, 1]).reshape(1, 4)], 0) 25 | # Match camera space to image pixel space 26 | scale_intrinsic = np.identity(4) 27 | scale_intrinsic[0, 0] = scale / ortho_ratio 28 | scale_intrinsic[1, 1] = -scale / ortho_ratio 29 | scale_intrinsic[2, 2] = scale / ortho_ratio 30 | # Match image pixel space to image uv space 31 | uv_intrinsic = np.identity(4) 32 | uv_intrinsic[0, 0] = 1.0 / float(render_size // 2) 33 | uv_intrinsic[1, 1] = 1.0 / float(render_size // 2) 34 | uv_intrinsic[2, 2] = 1.0 / float(render_size // 2) 35 | 36 | intrinsic = np.matmul(uv_intrinsic, scale_intrinsic) 37 | calib_mat = np.matmul(intrinsic, extrinsic) 38 | calib = torch.from_numpy(calib_mat) 39 | return calib 40 | 41 | 42 | class PPLStaticDataset(): 43 | def __init__(self, 44 | cfg, 45 | mean=(0.5, 0.5, 0.5), 46 | std=(0.5, 0.5, 0.5), 47 | training=True, 48 | split='train', 49 | shared_dict=None): 50 | self.root = '/home/rui/local/projects/release/PIFu/data/static/' 51 | self.cfg = cfg 52 | self.mean = mean 53 | self.std = std 54 | self.training = training if split == 'train' else False 55 | self.split = split 56 | self.shared_dict = shared_dict 57 | self.rotations = range(0, 360, 1) 58 | self.motion_list = self.get_motion_list() 59 | 60 | def __len__(self): 61 | return len(self.motion_list) * len(self.rotations) 62 | 63 | def __getitem__(self, index): 64 | try: 65 | return self.get_item(index) 66 | except Exception as e: 67 | print(e) 68 | return self.get_item(index=random.randint(0, self.__len__() - 1)) 69 | 70 | def get_item(self, index): 71 | rid = index % len(self.rotations) 72 | mid = index // len(self.rotations) 73 | 74 | rotation = self.rotations[rid] 75 | motion = self.motion_list[mid] 76 | 77 | calib_path = self.get_calib_path(motion, rotation) 78 | calib = load_calib(calib_path) 79 | 80 | image_path = self.get_image_path(motion, rotation) 81 | mask_path = self.get_mask_path(motion, rotation) 82 | if self.training: 83 | scale = random.uniform(0.9, 1.1) 84 | calib[0:3] *= scale 85 | image, mask = load_image( 86 | image_path, mask_path, 87 | crop_size=int(512/scale), 88 | input_size=512, 89 | mean=self.mean, 90 | std=self.std, 91 | blur=self.cfg.blur, 92 | brightness=self.cfg.aug_bri, 93 | contrast=self.cfg.aug_con, 94 | saturation=self.cfg.aug_sat, 95 | hue=self.cfg.aug_hue) 96 | else: 97 | image, mask = load_image( 98 | image_path, mask_path, 99 | mean=self.mean, 100 | std=self.std) 101 | 102 | # left-right flip aug 103 | if self.training and random.random() < 0.5: 104 | calib[0, :] *= -1 105 | image = image.flip(dims=[2]) 106 | mask = mask.flip(dims=[2]) 107 | 108 | # return data dict 109 | data_dict = { 110 | 'motion': str(motion), 111 | 'rotation': rotation, 112 | 'image': image.float(), 113 | 'mask': mask.float(), 114 | 'calib': calib.float(), 115 | 'mesh_path': self.get_mesh_path(motion), 116 | } 117 | 118 | # sampling 119 | if self.cfg.num_sample_geo: 120 | samples_geo, labels_geo = self.get_sampling_geo(motion) 121 | data_dict.update({ 122 | 'samples_geo': samples_geo.float(), 123 | 'labels_geo': labels_geo.float()}) 124 | 125 | if self.cfg.num_sample_color: 126 | raise NotImplementedError 127 | 128 | return data_dict 129 | 130 | def get_motion_list(self): 131 | all_subjects = os.listdir(os.path.join(self.root, 'RENDER')) 132 | val_subjects = np.loadtxt(os.path.join(self.root, 'val.txt'), dtype=str) 133 | if len(val_subjects) == 0: 134 | return sorted(all_subjects) 135 | if self.split == 'train': 136 | return sorted(list(set(all_subjects) - set(val_subjects))) 137 | else: 138 | return sorted(list(val_subjects)) 139 | 140 | def get_texture_path(self, motion, rotation): 141 | return os.path.join(self.root, 'UV_RENDER', motion, f'{rotation}_0_00.jpg') 142 | 143 | def get_image_path(self, motion, rotation): 144 | return os.path.join(self.root, 'RENDER', motion, f'{rotation}_0_00.jpg') 145 | 146 | def get_mask_path(self, motion, rotation): 147 | return os.path.join(self.root, 'MASK', motion, f'{rotation}_0_00.png') 148 | 149 | def get_mesh_path(self, motion): 150 | return os.path.join(self.root, '100k', f'{motion}_100k.obj') 151 | 152 | def get_calib_path(self, motion, rotation): 153 | return os.path.join(self.root, 'PARAM', motion, f'{rotation}_0_00.npy') 154 | 155 | def get_sampling_geo(self, motion): 156 | cache_files = glob.glob(os.path.join(self.root, 'SAMPLE', motion, "*.pt")) 157 | cache = torch.load(random.choice(cache_files)) 158 | samples = cache["samples"].float() 159 | labels = cache["labels"].float() 160 | return samples.transpose(1, 0), labels[0] 161 | 162 | -------------------------------------------------------------------------------- /monoport/lib/dataset/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | from PIL import Image, ImageFilter 4 | 5 | import torch 6 | import torch.nn.functional as F 7 | import torchvision.transforms as transforms 8 | 9 | 10 | def projection(points, calib): 11 | return np.matmul(calib[:3, :3], points.T).T + calib[:3, 3] 12 | 13 | 14 | def load_image( 15 | image_path, mask_path=None, 16 | crop_size=512, input_size=512, 17 | mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5), 18 | blur=0.0, brightness=0.0, contrast=0.0, saturation=0.0, hue=0.0): 19 | """ 20 | brightness (float or tuple of python:float (min, max)) 21 | – How much to jitter brightness. brightness_factor is chosen uniformly 22 | from [max(0, 1 - brightness), 1 + brightness] or the given [min, max]. 23 | Should be non negative numbers. 24 | 25 | contrast (float or tuple of python:float (min, max)) 26 | – How much to jitter contrast. contrast_factor is chosen uniformly 27 | from [max(0, 1 - contrast), 1 + contrast] or the given [min, max]. 28 | Should be non negative numbers. 29 | 30 | saturation (float or tuple of python:float (min, max)) 31 | – How much to jitter saturation. saturation_factor is chosen uniformly 32 | from [max(0, 1 - saturation), 1 + saturation] or the given [min, max]. 33 | Should be non negative numbers. 34 | 35 | hue (float or tuple of python:float (min, max)) 36 | – How much to jitter hue. hue_factor is chosen uniformly 37 | from [-hue, hue] or the given [min, max]. 38 | Should have 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5. 39 | """ 40 | image_to_tensor = transforms.Compose([ 41 | transforms.ColorJitter( 42 | brightness, contrast, saturation, hue), 43 | transforms.CenterCrop(crop_size), 44 | transforms.Resize(input_size), 45 | transforms.ToTensor(), 46 | transforms.Normalize(mean, std) 47 | ]) 48 | 49 | mask_to_tensor = transforms.Compose([ 50 | transforms.CenterCrop(crop_size), 51 | transforms.Resize(input_size), 52 | transforms.ToTensor(), 53 | transforms.Normalize((0.0,), (1.0,)) 54 | ]) 55 | 56 | if mask_path is not None: 57 | mask = Image.open(mask_path).split()[-1] 58 | image = Image.open(image_path).convert('RGB') 59 | else: 60 | rgba = Image.open(image_path).convert('RGBA') 61 | mask = rgba.split()[-1] 62 | image = rgba.convert('RGB') 63 | 64 | if blur > 0: 65 | radius = np.random.uniform(0, blur) 66 | image = image.filter(ImageFilter.GaussianBlur(radius)) 67 | 68 | image = image_to_tensor(image) 69 | mask = mask_to_tensor(mask) 70 | image = image * (mask > 0.5).float() 71 | return image, mask 72 | -------------------------------------------------------------------------------- /monoport/lib/mesh_util.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def load_obj_mesh_for_Hoppe(mesh_file): 5 | vertex_data = [] 6 | face_data = [] 7 | 8 | if isinstance(mesh_file, str): 9 | f = open(mesh_file, "r") 10 | else: 11 | f = mesh_file 12 | for line in f: 13 | if isinstance(line, bytes): 14 | line = line.decode("utf-8") 15 | if line.startswith('#'): 16 | continue 17 | values = line.split() 18 | if not values: 19 | continue 20 | 21 | if values[0] == 'v': 22 | v = list(map(float, values[1:4])) 23 | vertex_data.append(v) 24 | 25 | elif values[0] == 'f': 26 | # quad mesh 27 | if len(values) > 4: 28 | f = list(map(lambda x: int(x.split('/')[0]), values[1:4])) 29 | face_data.append(f) 30 | f = list(map(lambda x: int(x.split('/')[0]), [values[3], values[4], values[1]])) 31 | face_data.append(f) 32 | # tri mesh 33 | else: 34 | f = list(map(lambda x: int(x.split('/')[0]), values[1:4])) 35 | face_data.append(f) 36 | 37 | vertices = np.array(vertex_data) 38 | faces = np.array(face_data) 39 | faces[faces > 0] -= 1 40 | 41 | normals = compute_normal(vertices, faces) 42 | 43 | return vertices, normals, faces 44 | 45 | 46 | def load_obj_mesh_with_color(mesh_file): 47 | vertex_data = [] 48 | color_data = [] 49 | face_data = [] 50 | 51 | if isinstance(mesh_file, str): 52 | f = open(mesh_file, "r") 53 | else: 54 | f = mesh_file 55 | for line in f: 56 | if isinstance(line, bytes): 57 | line = line.decode("utf-8") 58 | if line.startswith('#'): 59 | continue 60 | values = line.split() 61 | if not values: 62 | continue 63 | 64 | if values[0] == 'v': 65 | v = list(map(float, values[1:4])) 66 | vertex_data.append(v) 67 | c = list(map(float, values[4:7])) 68 | color_data.append(c) 69 | 70 | elif values[0] == 'f': 71 | # quad mesh 72 | if len(values) > 4: 73 | f = list(map(lambda x: int(x.split('/')[0]), values[1:4])) 74 | face_data.append(f) 75 | f = list(map(lambda x: int(x.split('/')[0]), [values[3], values[4], values[1]])) 76 | face_data.append(f) 77 | # tri mesh 78 | else: 79 | f = list(map(lambda x: int(x.split('/')[0]), values[1:4])) 80 | face_data.append(f) 81 | 82 | vertices = np.array(vertex_data) 83 | colors = np.array(color_data) 84 | faces = np.array(face_data) 85 | faces[faces > 0] -= 1 86 | 87 | return vertices, colors, faces 88 | 89 | def load_obj_mesh(mesh_file, with_normal=False, with_texture=False): 90 | vertex_data = [] 91 | norm_data = [] 92 | uv_data = [] 93 | 94 | face_data = [] 95 | face_norm_data = [] 96 | face_uv_data = [] 97 | 98 | if isinstance(mesh_file, str): 99 | f = open(mesh_file, "r") 100 | else: 101 | f = mesh_file 102 | for line in f: 103 | if isinstance(line, bytes): 104 | line = line.decode("utf-8") 105 | if line.startswith('#'): 106 | continue 107 | values = line.split() 108 | if not values: 109 | continue 110 | 111 | if values[0] == 'v': 112 | v = list(map(float, values[1:4])) 113 | vertex_data.append(v) 114 | elif values[0] == 'vn': 115 | vn = list(map(float, values[1:4])) 116 | norm_data.append(vn) 117 | elif values[0] == 'vt': 118 | vt = list(map(float, values[1:3])) 119 | uv_data.append(vt) 120 | 121 | elif values[0] == 'f': 122 | # quad mesh 123 | if len(values) > 4: 124 | f = list(map(lambda x: int(x.split('/')[0]), values[1:4])) 125 | face_data.append(f) 126 | f = list(map(lambda x: int(x.split('/')[0]), [values[3], values[4], values[1]])) 127 | face_data.append(f) 128 | # tri mesh 129 | else: 130 | f = list(map(lambda x: int(x.split('/')[0]), values[1:4])) 131 | face_data.append(f) 132 | 133 | # deal with texture 134 | if len(values[1].split('/')) >= 2: 135 | # quad mesh 136 | if len(values) > 4: 137 | f = list(map(lambda x: int(x.split('/')[1]), values[1:4])) 138 | face_uv_data.append(f) 139 | f = list(map(lambda x: int(x.split('/')[1]), [values[3], values[4], values[1]])) 140 | face_uv_data.append(f) 141 | # tri mesh 142 | elif len(values[1].split('/')[1]) != 0: 143 | f = list(map(lambda x: int(x.split('/')[1]), values[1:4])) 144 | face_uv_data.append(f) 145 | # deal with normal 146 | if len(values[1].split('/')) == 3: 147 | # quad mesh 148 | if len(values) > 4: 149 | f = list(map(lambda x: int(x.split('/')[2]), values[1:4])) 150 | face_norm_data.append(f) 151 | f = list(map(lambda x: int(x.split('/')[2]), [values[3], values[4], values[1]])) 152 | face_norm_data.append(f) 153 | # tri mesh 154 | elif len(values[1].split('/')[2]) != 0: 155 | f = list(map(lambda x: int(x.split('/')[2]), values[1:4])) 156 | face_norm_data.append(f) 157 | 158 | vertices = np.array(vertex_data) 159 | faces = np.array(face_data) 160 | faces[faces > 0] -= 1 161 | 162 | if with_texture and with_normal: 163 | uvs = np.array(uv_data) 164 | face_uvs = np.array(face_uv_data) 165 | face_uvs[face_uvs > 0] -= 1 166 | norms = np.array(norm_data) 167 | if norms.shape[0] == 0: 168 | norms = compute_normal(vertices, faces) 169 | face_normals = faces 170 | else: 171 | norms = normalize_v3(norms) 172 | face_normals = np.array(face_norm_data) 173 | face_normals[face_normals > 0] -= 1 174 | return vertices, faces, norms, face_normals, uvs, face_uvs 175 | 176 | if with_texture: 177 | uvs = np.array(uv_data) 178 | face_uvs = np.array(face_uv_data) - 1 179 | return vertices, faces, uvs, face_uvs 180 | 181 | if with_normal: 182 | norms = np.array(norm_data) 183 | norms = normalize_v3(norms) 184 | face_normals = np.array(face_norm_data) - 1 185 | return vertices, faces, norms, face_normals 186 | 187 | return vertices, faces 188 | 189 | 190 | def normalize_v3(arr): 191 | ''' Normalize a numpy array of 3 component vectors shape=(n,3) ''' 192 | lens = np.sqrt(arr[:, 0] ** 2 + arr[:, 1] ** 2 + arr[:, 2] ** 2) 193 | eps = 0.00000001 194 | lens[lens < eps] = eps 195 | arr[:, 0] /= lens 196 | arr[:, 1] /= lens 197 | arr[:, 2] /= lens 198 | return arr 199 | 200 | 201 | def compute_normal(vertices, faces): 202 | # Create a zeroed array with the same type and shape as our vertices i.e., per vertex normal 203 | norm = np.zeros(vertices.shape, dtype=vertices.dtype) 204 | # Create an indexed view into the vertex array using the array of three indices for triangles 205 | tris = vertices[faces] 206 | # Calculate the normal for all the triangles, by taking the cross product of the vectors v1-v0, and v2-v0 in each triangle 207 | n = np.cross(tris[::, 1] - tris[::, 0], tris[::, 2] - tris[::, 0]) 208 | # n is now an array of normals per triangle. The length of each normal is dependent the vertices, 209 | # we need to normalize these, so that our next step weights each normal equally. 210 | normalize_v3(n) 211 | # now we have a normalized array of normals, one per triangle, i.e., per triangle normals. 212 | # But instead of one per triangle (i.e., flat shading), we add to each vertex in that triangle, 213 | # the triangles' normal. Multiple triangles would then contribute to every vertex, so we need to normalize again afterwards. 214 | # The cool part, we can actually add the normals through an indexed view of our (zeroed) per vertex normal array 215 | norm[faces[:, 0]] += n 216 | norm[faces[:, 1]] += n 217 | norm[faces[:, 2]] += n 218 | normalize_v3(norm) 219 | 220 | return norm 221 | 222 | 223 | def save_obj_mesh(mesh_path, verts, faces): 224 | file = open(mesh_path, 'w') 225 | for v in verts: 226 | file.write('v %.4f %.4f %.4f\n' % (v[0], v[1], v[2])) 227 | for f in faces: 228 | f_plus = f + 1 229 | file.write('f %d %d %d\n' % (f_plus[0], f_plus[1], f_plus[2])) 230 | file.close() 231 | 232 | 233 | def save_obj_mesh_with_color(mesh_path, verts, faces, colors): 234 | file = open(mesh_path, 'w') 235 | 236 | for idx, v in enumerate(verts): 237 | c = colors[idx] 238 | file.write('v %.4f %.4f %.4f %.4f %.4f %.4f\n' % (v[0], v[1], v[2], c[0], c[1], c[2])) 239 | for f in faces: 240 | f_plus = f + 1 241 | file.write('f %d %d %d\n' % (f_plus[0], f_plus[1], f_plus[2])) 242 | file.close() -------------------------------------------------------------------------------- /monoport/lib/modeling/MonoPortNet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from .geometry import index, orthogonal, perspective 6 | from .normalizers import * 7 | from .backbones import * 8 | from .heads import * 9 | 10 | 11 | class MonoPortNet(nn.Module): 12 | def __init__(self, opt_net): 13 | """The geometry network 14 | 15 | Arguments: 16 | opt_net {edict} -- options for netG/netC 17 | """ 18 | super().__init__() 19 | self.opt = opt_net 20 | assert opt_net.projection in ['orthogonal', 'perspective'] 21 | 22 | # modules 23 | self.image_filter = globals()[opt_net.backbone.IMF](opt_net.backbone) 24 | self.surface_classifier = globals()[opt_net.head.IMF](opt_net.head) 25 | 26 | # operations 27 | self.projection = globals()[opt_net.projection] 28 | self.normalizer = globals()[opt_net.normalizer.IMF](opt_net.normalizer) 29 | 30 | 31 | def filter(self, images, feat_prior=None): 32 | """Filter the input images 33 | 34 | Arguments: 35 | images {torch.tensor} -- input images with shape [B, C, H, W] 36 | 37 | Returns: 38 | list(list(torch.tensor)) -- image feature lists. > 39 | """ 40 | feats_stages = self.image_filter(images) 41 | if feat_prior is not None: # for netC 42 | feat_prior = F.interpolate(feat_prior, size=(128, 128)) 43 | feats_stages = [ 44 | [torch.cat([feat_prior, feat_per_lvl], dim=1) 45 | for feat_per_lvl in feats] for feats in feats_stages] 46 | return feats_stages 47 | 48 | def query(self, feats_stages, points, calibs=None, transforms=None): 49 | """Given 3D points, query the network predictions for each point. 50 | 51 | Arguments: 52 | feats_stages {list(list(torch.tensor))} -- image feature lists. First level list 53 | is for multi-stage losses. Second level list is for multi-level features. 54 | points {torch.tensor} -- [B, 3, N] world space coordinates of points. 55 | calibs {torch.tensor} -- [B, 3, 4] calibration matrices for each image. 56 | 57 | Keyword Arguments: 58 | transforms {torch.tensor} -- Optional [B, 2, 3] image space coordinate transforms 59 | 60 | Returns: 61 | list(torch.tensor) -- predictions for each point at each stage. list of [B, Res, N]. 62 | """ 63 | if not self.training: 64 | feats_stages = [feats_stages[-1]] 65 | 66 | if calibs is None: 67 | xyz = points 68 | else: 69 | xyz = self.projection(points, calibs, transforms) 70 | 71 | xy = xyz[:, :2, :] 72 | z = xyz[:, 2:3, :] 73 | 74 | in_img = (xy[:, 0] >= -1.0) & (xy[:, 0] <= 1.0) & (xy[:, 1] >= -1.0) & (xy[:, 1] <= 1.0) 75 | 76 | z_feat = self.normalizer(z, calibs=calibs) 77 | pred_stages = [] 78 | for feats in feats_stages: # each stage 79 | # concatenate feats cross all the levels. [B, Feat_all, N] 80 | # TODO: another option here is to do ADD op if all the levels 81 | # have same feature dimentions. 82 | point_local_feat = torch.cat( 83 | [index(feat_per_lvl, xy) for feat_per_lvl in feats] + [z_feat], 1) 84 | 85 | # [B, Res, N]. Res=1 here. 86 | pred = self.surface_classifier(point_local_feat) 87 | 88 | # out of image plane is always set to 0 89 | preds = in_img[:, None].float() * pred 90 | pred_stages.append(preds) 91 | return pred_stages 92 | 93 | def get_loss(self, pred_stages, labels): 94 | """Calculate loss between predictions and labels 95 | 96 | Arguments: 97 | pred_stages {list(torch.tensor)} -- predictions at each stage. list of [B, Res, N] 98 | labels {torch.tensor} -- labels. typically [B, Res, N] 99 | 100 | Raises: 101 | NotImplementedError: 102 | 103 | Returns: 104 | torch.tensor -- average loss cross stages. 105 | """ 106 | if self.opt.loss.IMF == 'MSE': 107 | loss_func = F.mse_loss 108 | elif self.opt.loss.IMF == 'L1': 109 | loss_func = F.l1_loss 110 | else: 111 | raise NotImplementedError 112 | 113 | loss = 0 114 | for pred in pred_stages: 115 | loss += loss_func(pred, labels) 116 | loss /= len(pred_stages) 117 | return loss 118 | 119 | 120 | def forward(self, images, points, calibs, transforms=None, labels=None, feat_prior=None): 121 | """Forward function given points and calibs 122 | 123 | Arguments: 124 | images {torch.tensor} -- shape of [B, C, H, W] 125 | points {torch.tensor} -- shape of [B, 3, N] 126 | calibs {torch.tesnor} -- shape of [B, 3, 4] 127 | 128 | Keyword Arguments: 129 | transforms {torch.tensor} -- shape of [B, 2, 3] (default: {None}) 130 | labels {torch.tensor} -- shape of [B, Res, N] (default: {None}) 131 | 132 | Returns: 133 | torch.tensor, [torch.scaler] -- return preds at last stages. shape of [B, Res, N] 134 | """ 135 | feats_stages = self.filter(images, feat_prior) 136 | pred_stages = self.query(feats_stages, points, calibs, transforms) 137 | 138 | if labels is not None: 139 | loss = self.get_loss(pred_stages, labels) 140 | return pred_stages[-1], loss 141 | else: 142 | return pred_stages[-1] 143 | 144 | # def load_legacy(self, ckpt_path): 145 | # ckpt = torch.load(ckpt_path, map_location="cpu")["netG"] 146 | # backbone_dict = { 147 | # k.replace("image_filter.", ""): v for k, v in ckpt.items() if "image_filter" in k} 148 | # head_dict = { 149 | # k.replace("surface_classifier.", ""): v for k, v in ckpt.items() if "surface_classifier" in k} 150 | # self.image_filter.load_state_dict(backbone_dict) 151 | # self.surface_classifier.load_state_dict(head_dict) 152 | 153 | def load_legacy_pifu(self, ckpt_path): 154 | ckpt = torch.load(ckpt_path, map_location="cpu") 155 | backbone_dict = { 156 | k.replace("image_filter.", ""): v for k, v in ckpt.items() if "image_filter" in k} 157 | head_dict = { 158 | k.replace("surface_classifier.conv", "filters."): v for k, v in ckpt.items() if "surface_classifier" in k} 159 | self.image_filter.load_state_dict(backbone_dict) 160 | self.surface_classifier.load_state_dict(head_dict) 161 | 162 | 163 | def PIFuNetG(): 164 | from yacs.config import CfgNode as CN 165 | opt_net = CN() 166 | opt_net.projection = "orthogonal" 167 | 168 | # --- netG:backbone options --- 169 | opt_net.backbone = CN() 170 | opt_net.backbone.IMF = 'PIFuHGFilters' 171 | 172 | # --- netG:normalizer options --- 173 | opt_net.normalizer = CN() 174 | opt_net.normalizer.IMF = 'PIFuNomalizer' 175 | 176 | # --- netG:head options --- 177 | opt_net.head = CN() 178 | opt_net.head.IMF = 'PIFuNetGMLP' 179 | 180 | # --- netG:loss options --- 181 | opt_net.loss = CN() 182 | opt_net.loss.IMF = 'MSE' 183 | 184 | return MonoPortNet(opt_net) 185 | 186 | 187 | def PIFuNetC(): 188 | from yacs.config import CfgNode as CN 189 | opt_net = CN() 190 | opt_net.projection = "orthogonal" 191 | 192 | # --- netG:backbone options --- 193 | opt_net.backbone = CN() 194 | opt_net.backbone.IMF = 'PIFuResBlkFilters' 195 | 196 | # --- netG:normalizer options --- 197 | opt_net.normalizer = CN() 198 | opt_net.normalizer.IMF = 'PIFuNomalizer' 199 | 200 | # --- netG:head options --- 201 | opt_net.head = CN() 202 | opt_net.head.IMF = 'PIFuNetCMLP' 203 | 204 | # --- netG:loss options --- 205 | opt_net.loss = CN() 206 | opt_net.loss.IMF = 'L1' 207 | 208 | return MonoPortNet(opt_net) 209 | 210 | -------------------------------------------------------------------------------- /monoport/lib/modeling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/monoport/lib/modeling/__init__.py -------------------------------------------------------------------------------- /monoport/lib/modeling/backbones/HGFilters.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | 6 | def conv3x3(in_planes, out_planes, strd=1, padding=1, bias=False): 7 | "3x3 convolution with padding" 8 | return nn.Conv2d(in_planes, out_planes, kernel_size=3, 9 | stride=strd, padding=padding, bias=bias) 10 | 11 | 12 | class ConvBlock(nn.Module): 13 | def __init__(self, in_planes, out_planes, norm='batch'): 14 | super(ConvBlock, self).__init__() 15 | self.conv1 = conv3x3(in_planes, int(out_planes / 2)) 16 | self.conv2 = conv3x3(int(out_planes / 2), int(out_planes / 4)) 17 | self.conv3 = conv3x3(int(out_planes / 4), int(out_planes / 4)) 18 | 19 | if norm == 'batch': 20 | self.bn1 = nn.BatchNorm2d(in_planes) 21 | self.bn2 = nn.BatchNorm2d(int(out_planes / 2)) 22 | self.bn3 = nn.BatchNorm2d(int(out_planes / 4)) 23 | self.bn4 = nn.BatchNorm2d(in_planes) 24 | elif norm == 'group': 25 | self.bn1 = nn.GroupNorm(32, in_planes) 26 | self.bn2 = nn.GroupNorm(32, int(out_planes / 2)) 27 | self.bn3 = nn.GroupNorm(32, int(out_planes / 4)) 28 | self.bn4 = nn.GroupNorm(32, in_planes) 29 | 30 | if in_planes != out_planes: 31 | self.downsample = nn.Sequential( 32 | self.bn4, 33 | nn.ReLU(True), 34 | nn.Conv2d(in_planes, out_planes, 35 | kernel_size=1, stride=1, bias=False), 36 | ) 37 | else: 38 | self.downsample = None 39 | 40 | def forward(self, x): 41 | residual = x 42 | 43 | out1 = self.bn1(x) 44 | out1 = F.relu(out1, True) 45 | out1 = self.conv1(out1) 46 | 47 | out2 = self.bn2(out1) 48 | out2 = F.relu(out2, True) 49 | out2 = self.conv2(out2) 50 | 51 | out3 = self.bn3(out2) 52 | out3 = F.relu(out3, True) 53 | out3 = self.conv3(out3) 54 | 55 | out3 = torch.cat((out1, out2, out3), 1) 56 | 57 | if self.downsample is not None: 58 | residual = self.downsample(residual) 59 | 60 | out3 += residual 61 | 62 | return out3 63 | 64 | 65 | class HourGlass(nn.Module): 66 | def __init__(self, num_modules, depth, num_features, norm='batch'): 67 | super(HourGlass, self).__init__() 68 | self.num_modules = num_modules 69 | self.depth = depth 70 | self.features = num_features 71 | self.norm = norm 72 | 73 | self._generate_network(self.depth) 74 | 75 | def _generate_network(self, level): 76 | self.add_module('b1_' + str(level), ConvBlock(self.features, self.features, norm=self.norm)) 77 | 78 | self.add_module('b2_' + str(level), ConvBlock(self.features, self.features, norm=self.norm)) 79 | 80 | if level > 1: 81 | self._generate_network(level - 1) 82 | else: 83 | self.add_module('b2_plus_' + str(level), ConvBlock(self.features, self.features, norm=self.norm)) 84 | 85 | self.add_module('b3_' + str(level), ConvBlock(self.features, self.features, norm=self.norm)) 86 | 87 | def _forward(self, level, inp): 88 | # Upper branch 89 | up1 = inp 90 | up1 = self._modules['b1_' + str(level)](up1) 91 | 92 | # Lower branch 93 | low1 = F.avg_pool2d(inp, 2, stride=2) 94 | low1 = self._modules['b2_' + str(level)](low1) 95 | 96 | if level > 1: 97 | low2 = self._forward(level - 1, low1) 98 | else: 99 | low2 = low1 100 | low2 = self._modules['b2_plus_' + str(level)](low2) 101 | 102 | low3 = low2 103 | low3 = self._modules['b3_' + str(level)](low3) 104 | 105 | # NOTE: for newer PyTorch (1.3~), it seems that training results are degraded due to implementation diff in F.grid_sample 106 | # if the pretrained model behaves weirdly, switch with the commented line. 107 | # NOTE: I also found that "bicubic" works better. 108 | up2 = F.interpolate(low3, scale_factor=2, mode='bicubic', align_corners=True) 109 | # up2 = F.interpolate(low3, scale_factor=2, mode='nearest) 110 | 111 | return up1 + up2 112 | 113 | def forward(self, x): 114 | return self._forward(self.depth, x) 115 | 116 | 117 | class HGFilter(nn.Module): 118 | def __init__(self, opt): 119 | super(HGFilter, self).__init__() 120 | self.num_modules = opt.num_stack 121 | 122 | self.opt = opt 123 | 124 | # Base part 125 | self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3) 126 | 127 | if self.opt.norm == 'batch': 128 | self.bn1 = nn.BatchNorm2d(64) 129 | elif self.opt.norm == 'group': 130 | self.bn1 = nn.GroupNorm(32, 64) 131 | 132 | if self.opt.hg_down == 'conv64': 133 | self.conv2 = ConvBlock(64, 64, self.opt.norm) 134 | self.down_conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1) 135 | elif self.opt.hg_down == 'conv128': 136 | self.conv2 = ConvBlock(64, 128, self.opt.norm) 137 | self.down_conv2 = nn.Conv2d(128, 128, kernel_size=3, stride=2, padding=1) 138 | elif self.opt.hg_down == 'ave_pool': 139 | self.conv2 = ConvBlock(64, 128, self.opt.norm) 140 | else: 141 | raise NameError('Unknown Fan Filter setting!') 142 | 143 | self.conv3 = ConvBlock(128, 128, self.opt.norm) 144 | self.conv4 = ConvBlock(128, 256, self.opt.norm) 145 | 146 | # Stacking part 147 | for hg_module in range(self.num_modules): 148 | self.add_module('m' + str(hg_module), HourGlass(1, opt.num_hourglass, 256, self.opt.norm)) 149 | 150 | self.add_module('top_m_' + str(hg_module), ConvBlock(256, 256, self.opt.norm)) 151 | self.add_module('conv_last' + str(hg_module), 152 | nn.Conv2d(256, 256, kernel_size=1, stride=1, padding=0)) 153 | if self.opt.norm == 'batch': 154 | self.add_module('bn_end' + str(hg_module), nn.BatchNorm2d(256)) 155 | elif self.opt.norm == 'group': 156 | self.add_module('bn_end' + str(hg_module), nn.GroupNorm(32, 256)) 157 | 158 | self.add_module('l' + str(hg_module), nn.Conv2d(256, 159 | opt.hourglass_dim, kernel_size=1, stride=1, padding=0)) 160 | 161 | if hg_module < self.num_modules - 1: 162 | self.add_module( 163 | 'bl' + str(hg_module), nn.Conv2d(256, 256, kernel_size=1, stride=1, padding=0)) 164 | self.add_module('al' + str(hg_module), nn.Conv2d(opt.hourglass_dim, 165 | 256, kernel_size=1, stride=1, padding=0)) 166 | 167 | def forward(self, x): 168 | x = F.relu(self.bn1(self.conv1(x)), True) 169 | tmpx = x 170 | if self.opt.hg_down == 'ave_pool': 171 | x = F.avg_pool2d(self.conv2(x), 2, stride=2) 172 | elif self.opt.hg_down in ['conv64', 'conv128']: 173 | x = self.conv2(x) 174 | x = self.down_conv2(x) 175 | else: 176 | raise NameError('Unknown Fan Filter setting!') 177 | 178 | normx = x 179 | 180 | x = self.conv3(x) 181 | x = self.conv4(x) 182 | 183 | previous = x 184 | 185 | outputs = [] 186 | for i in range(self.num_modules): 187 | hg = self._modules['m' + str(i)](previous) 188 | 189 | ll = hg 190 | ll = self._modules['top_m_' + str(i)](ll) 191 | 192 | ll = F.relu(self._modules['bn_end' + str(i)] 193 | (self._modules['conv_last' + str(i)](ll)), True) 194 | 195 | # Predict heatmaps 196 | tmp_out = self._modules['l' + str(i)](ll) 197 | outputs.append((tmp_out,)) 198 | 199 | if i < self.num_modules - 1: 200 | ll = self._modules['bl' + str(i)](ll) 201 | tmp_out_ = self._modules['al' + str(i)](tmp_out) 202 | previous = previous + ll + tmp_out_ 203 | 204 | return outputs 205 | 206 | 207 | def PIFuHGFilters(*args, **kwargs): 208 | from yacs.config import CfgNode as CN 209 | opt = CN() 210 | opt.norm = 'group' 211 | opt.num_stack = 4 212 | opt.num_hourglass = 2 213 | opt.skip_hourglass = False 214 | opt.hg_down = 'ave_pool' 215 | opt.hourglass_dim = 256 216 | return HGFilter(opt) 217 | 218 | 219 | if __name__ == '__main__': 220 | import tqdm 221 | 222 | device = 'cuda:0' 223 | input = torch.randn(1, 3, 512, 512).to(device) 224 | model = PIFuHGFilters().to(device) 225 | model.eval() 226 | 227 | with torch.no_grad(): 228 | outputs = model(input) 229 | for stage, output_stage in enumerate(outputs): 230 | for lvl, output_lvl in enumerate(output_stage): 231 | print (f'stage: {stage}, lvl: {lvl}', output_lvl.shape) 232 | 233 | with torch.no_grad(): # 27.42 fps 234 | for _ in tqdm.tqdm(range(1000)): 235 | outputs = model(input) -------------------------------------------------------------------------------- /monoport/lib/modeling/backbones/ResBlkFilters.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import functools 5 | 6 | 7 | def get_norm_layer(norm_type='group'): 8 | """Return a normalization layer 9 | Parameters: 10 | norm_type (str) -- the name of the normalization layer: batch | instance | none 11 | For BatchNorm, we use learnable affine parameters and track running statistics (mean/stddev). 12 | For InstanceNorm, we do not use learnable affine parameters. We do not track running statistics. 13 | """ 14 | if norm_type == 'batch': 15 | norm_layer = functools.partial(nn.BatchNorm2d, affine=True, track_running_stats=True) 16 | elif norm_type == 'instance': 17 | norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) 18 | elif norm_type == 'group': 19 | norm_layer = functools.partial(nn.GroupNorm, 32) 20 | elif norm_type == 'none': 21 | norm_layer = None 22 | else: 23 | raise NotImplementedError('normalization layer [%s] is not found' % norm_type) 24 | return norm_layer 25 | 26 | 27 | class ResnetBlock(nn.Module): 28 | """Define a Resnet block""" 29 | 30 | def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias, last=False): 31 | """Initialize the Resnet block 32 | A resnet block is a conv block with skip connections 33 | We construct a conv block with build_conv_block function, 34 | and implement skip connections in function. 35 | Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf 36 | """ 37 | super(ResnetBlock, self).__init__() 38 | self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias, last) 39 | 40 | def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias, last=False): 41 | """Construct a convolutional block. 42 | Parameters: 43 | dim (int) -- the number of channels in the conv layer. 44 | padding_type (str) -- the name of padding layer: reflect | replicate | zero 45 | norm_layer -- normalization layer 46 | use_dropout (bool) -- if use dropout layers. 47 | use_bias (bool) -- if the conv layer uses bias or not 48 | Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU)) 49 | """ 50 | conv_block = [] 51 | p = 0 52 | if padding_type == 'reflect': 53 | conv_block += [nn.ReflectionPad2d(1)] 54 | elif padding_type == 'replicate': 55 | conv_block += [nn.ReplicationPad2d(1)] 56 | elif padding_type == 'zero': 57 | p = 1 58 | else: 59 | raise NotImplementedError('padding [%s] is not implemented' % padding_type) 60 | 61 | conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)] 62 | if use_dropout: 63 | conv_block += [nn.Dropout(0.5)] 64 | 65 | p = 0 66 | if padding_type == 'reflect': 67 | conv_block += [nn.ReflectionPad2d(1)] 68 | elif padding_type == 'replicate': 69 | conv_block += [nn.ReplicationPad2d(1)] 70 | elif padding_type == 'zero': 71 | p = 1 72 | else: 73 | raise NotImplementedError('padding [%s] is not implemented' % padding_type) 74 | if last: 75 | conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias)] 76 | else: 77 | conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)] 78 | 79 | return nn.Sequential(*conv_block) 80 | 81 | def forward(self, x): 82 | """Forward function (with skip connections)""" 83 | out = x + self.conv_block(x) # add skip connections 84 | return out 85 | 86 | 87 | class ResnetFilter(nn.Module): 88 | """Resnet-based generator that consists of Resnet blocks between a few downsampling/upsampling operations. 89 | We adapt Torch code and idea from Justin Johnson's neural style transfer project(https://github.com/jcjohnson/fast-neural-style) 90 | """ 91 | 92 | def __init__(self, opt, input_nc=3, output_nc=256, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, 93 | n_blocks=6, padding_type='reflect'): 94 | """Construct a Resnet-based generator 95 | Parameters: 96 | input_nc (int) -- the number of channels in input images 97 | output_nc (int) -- the number of channels in output images 98 | ngf (int) -- the number of filters in the last conv layer 99 | norm_layer -- normalization layer 100 | use_dropout (bool) -- if use dropout layers 101 | n_blocks (int) -- the number of ResNet blocks 102 | padding_type (str) -- the name of padding layer in conv layers: reflect | replicate | zero 103 | """ 104 | assert (n_blocks >= 0) 105 | super(ResnetFilter, self).__init__() 106 | if type(norm_layer) == functools.partial: 107 | use_bias = norm_layer.func == nn.InstanceNorm2d 108 | else: 109 | use_bias = norm_layer == nn.InstanceNorm2d 110 | 111 | model = [nn.ReflectionPad2d(3), 112 | nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias), 113 | norm_layer(ngf), 114 | nn.ReLU(True)] 115 | 116 | n_downsampling = 2 117 | for i in range(n_downsampling): # add downsampling layers 118 | mult = 2 ** i 119 | model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1, bias=use_bias), 120 | norm_layer(ngf * mult * 2), 121 | nn.ReLU(True)] 122 | 123 | mult = 2 ** n_downsampling 124 | for i in range(n_blocks): # add ResNet blocks 125 | if i == n_blocks - 1: 126 | model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, 127 | use_dropout=use_dropout, use_bias=use_bias, last=True)] 128 | else: 129 | model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, 130 | use_dropout=use_dropout, use_bias=use_bias)] 131 | 132 | if opt.use_tanh: 133 | model += [nn.Tanh()] 134 | self.model = nn.Sequential(*model) 135 | 136 | def forward(self, input): 137 | """Standard forward""" 138 | output = self.model(input) 139 | return [(output,)] 140 | 141 | 142 | def PIFuResBlkFilters(*args, **kwargs): 143 | from yacs.config import CfgNode as CN 144 | opt = CN() 145 | opt.use_tanh = False 146 | norm_layer = get_norm_layer('group') 147 | return ResnetFilter(opt, norm_layer=norm_layer) 148 | 149 | 150 | if __name__ == '__main__': 151 | import tqdm 152 | 153 | device = 'cuda:0' 154 | input = torch.randn(1, 3, 512, 512).to(device) 155 | model = PIFuResBlkFilters().to(device) 156 | 157 | with torch.no_grad(): 158 | outputs = model(input) 159 | for stage, output_stage in enumerate(outputs): 160 | for lvl, output_lvl in enumerate(output_stage): 161 | print (f'stage: {stage}, lvl: {lvl}', output_lvl.shape) 162 | 163 | with torch.no_grad(): # 39.68 fps 164 | for _ in tqdm.tqdm(range(1000)): 165 | outputs = model(input) -------------------------------------------------------------------------------- /monoport/lib/modeling/backbones/Yolov4Filters.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class Mish(torch.nn.Module): 6 | def __init__(self): 7 | super().__init__() 8 | 9 | def forward(self, x): 10 | x = x * (torch.tanh(torch.nn.functional.softplus(x))) 11 | return x 12 | 13 | 14 | class Upsample(nn.Module): 15 | def __init__(self): 16 | super(Upsample, self).__init__() 17 | 18 | def forward(self, x, target_size, inference=False): 19 | assert (x.data.dim() == 4) 20 | _, _, tH, tW = target_size 21 | 22 | if inference: 23 | B = x.data.size(0) 24 | C = x.data.size(1) 25 | H = x.data.size(2) 26 | W = x.data.size(3) 27 | 28 | return x.view(B, C, H, 1, W, 1).expand(B, C, H, tH // H, W, tW // W).contiguous().view(B, C, tH, tW) 29 | else: 30 | return F.interpolate(x, size=(tH, tW), mode='nearest') 31 | 32 | 33 | class Conv_Bn_Activation(nn.Module): 34 | def __init__(self, in_channels, out_channels, kernel_size, stride, activation, bn=True, bias=False): 35 | super().__init__() 36 | pad = (kernel_size - 1) // 2 37 | 38 | self.conv = nn.ModuleList() 39 | if bias: 40 | self.conv.append(nn.Conv2d(in_channels, out_channels, kernel_size, stride, pad)) 41 | else: 42 | self.conv.append(nn.Conv2d(in_channels, out_channels, kernel_size, stride, pad, bias=False)) 43 | if bn: 44 | self.conv.append(nn.BatchNorm2d(out_channels)) 45 | if activation == "mish": 46 | self.conv.append(Mish()) 47 | elif activation == "relu": 48 | self.conv.append(nn.ReLU(inplace=True)) 49 | elif activation == "leaky": 50 | self.conv.append(nn.LeakyReLU(0.1, inplace=True)) 51 | elif activation == "linear": 52 | pass 53 | else: 54 | raise NotImplementedError 55 | 56 | def forward(self, x): 57 | for l in self.conv: 58 | x = l(x) 59 | return x 60 | 61 | 62 | class ResBlock(nn.Module): 63 | """ 64 | Sequential residual blocks each of which consists of \ 65 | two convolution layers. 66 | Args: 67 | ch (int): number of input and output channels. 68 | nblocks (int): number of residual blocks. 69 | shortcut (bool): if True, residual tensor addition is enabled. 70 | """ 71 | 72 | def __init__(self, ch, nblocks=1, shortcut=True): 73 | super().__init__() 74 | self.shortcut = shortcut 75 | self.module_list = nn.ModuleList() 76 | for i in range(nblocks): 77 | resblock_one = nn.ModuleList() 78 | resblock_one.append(Conv_Bn_Activation(ch, ch, 1, 1, 'mish')) 79 | resblock_one.append(Conv_Bn_Activation(ch, ch, 3, 1, 'mish')) 80 | self.module_list.append(resblock_one) 81 | 82 | def forward(self, x): 83 | for module in self.module_list: 84 | h = x 85 | for res in module: 86 | h = res(h) 87 | x = x + h if self.shortcut else h 88 | return x 89 | 90 | 91 | class DownSample1(nn.Module): 92 | def __init__(self): 93 | super().__init__() 94 | self.conv1 = Conv_Bn_Activation(3, 32, 3, 1, 'mish') 95 | 96 | self.conv2 = Conv_Bn_Activation(32, 64, 3, 2, 'mish') 97 | self.conv3 = Conv_Bn_Activation(64, 64, 1, 1, 'mish') 98 | # [route] 99 | # layers = -2 100 | self.conv4 = Conv_Bn_Activation(64, 64, 1, 1, 'mish') 101 | 102 | self.conv5 = Conv_Bn_Activation(64, 32, 1, 1, 'mish') 103 | self.conv6 = Conv_Bn_Activation(32, 64, 3, 1, 'mish') 104 | # [shortcut] 105 | # from=-3 106 | # activation = linear 107 | 108 | self.conv7 = Conv_Bn_Activation(64, 64, 1, 1, 'mish') 109 | # [route] 110 | # layers = -1, -7 111 | self.conv8 = Conv_Bn_Activation(128, 64, 1, 1, 'mish') 112 | 113 | def forward(self, input): 114 | x1 = self.conv1(input) 115 | x2 = self.conv2(x1) 116 | x3 = self.conv3(x2) 117 | # route -2 118 | x4 = self.conv4(x2) 119 | x5 = self.conv5(x4) 120 | x6 = self.conv6(x5) 121 | # shortcut -3 122 | x6 = x6 + x4 123 | 124 | x7 = self.conv7(x6) 125 | # [route] 126 | # layers = -1, -7 127 | x7 = torch.cat([x7, x3], dim=1) 128 | x8 = self.conv8(x7) 129 | return x8 130 | 131 | 132 | class DownSample2(nn.Module): 133 | def __init__(self): 134 | super().__init__() 135 | self.conv1 = Conv_Bn_Activation(64, 128, 3, 2, 'mish') 136 | self.conv2 = Conv_Bn_Activation(128, 64, 1, 1, 'mish') 137 | # r -2 138 | self.conv3 = Conv_Bn_Activation(128, 64, 1, 1, 'mish') 139 | 140 | self.resblock = ResBlock(ch=64, nblocks=2) 141 | 142 | # s -3 143 | self.conv4 = Conv_Bn_Activation(64, 64, 1, 1, 'mish') 144 | # r -1 -10 145 | self.conv5 = Conv_Bn_Activation(128, 128, 1, 1, 'mish') 146 | 147 | def forward(self, input): 148 | x1 = self.conv1(input) 149 | x2 = self.conv2(x1) 150 | x3 = self.conv3(x1) 151 | 152 | r = self.resblock(x3) 153 | x4 = self.conv4(r) 154 | 155 | x4 = torch.cat([x4, x2], dim=1) 156 | x5 = self.conv5(x4) 157 | return x5 158 | 159 | 160 | class DownSample3(nn.Module): 161 | def __init__(self): 162 | super().__init__() 163 | self.conv1 = Conv_Bn_Activation(128, 256, 3, 2, 'mish') 164 | self.conv2 = Conv_Bn_Activation(256, 128, 1, 1, 'mish') 165 | self.conv3 = Conv_Bn_Activation(256, 128, 1, 1, 'mish') 166 | 167 | self.resblock = ResBlock(ch=128, nblocks=8) 168 | self.conv4 = Conv_Bn_Activation(128, 128, 1, 1, 'mish') 169 | self.conv5 = Conv_Bn_Activation(256, 256, 1, 1, 'mish') 170 | 171 | def forward(self, input): 172 | x1 = self.conv1(input) 173 | x2 = self.conv2(x1) 174 | x3 = self.conv3(x1) 175 | 176 | r = self.resblock(x3) 177 | x4 = self.conv4(r) 178 | 179 | x4 = torch.cat([x4, x2], dim=1) 180 | x5 = self.conv5(x4) 181 | return x5 182 | 183 | 184 | class DownSample4(nn.Module): 185 | def __init__(self): 186 | super().__init__() 187 | self.conv1 = Conv_Bn_Activation(256, 512, 3, 2, 'mish') 188 | self.conv2 = Conv_Bn_Activation(512, 256, 1, 1, 'mish') 189 | self.conv3 = Conv_Bn_Activation(512, 256, 1, 1, 'mish') 190 | 191 | self.resblock = ResBlock(ch=256, nblocks=8) 192 | self.conv4 = Conv_Bn_Activation(256, 256, 1, 1, 'mish') 193 | self.conv5 = Conv_Bn_Activation(512, 512, 1, 1, 'mish') 194 | 195 | def forward(self, input): 196 | x1 = self.conv1(input) 197 | x2 = self.conv2(x1) 198 | x3 = self.conv3(x1) 199 | 200 | r = self.resblock(x3) 201 | x4 = self.conv4(r) 202 | 203 | x4 = torch.cat([x4, x2], dim=1) 204 | x5 = self.conv5(x4) 205 | return x5 206 | 207 | 208 | class DownSample5(nn.Module): 209 | def __init__(self): 210 | super().__init__() 211 | self.conv1 = Conv_Bn_Activation(512, 1024, 3, 2, 'mish') 212 | self.conv2 = Conv_Bn_Activation(1024, 512, 1, 1, 'mish') 213 | self.conv3 = Conv_Bn_Activation(1024, 512, 1, 1, 'mish') 214 | 215 | self.resblock = ResBlock(ch=512, nblocks=4) 216 | self.conv4 = Conv_Bn_Activation(512, 512, 1, 1, 'mish') 217 | self.conv5 = Conv_Bn_Activation(1024, 1024, 1, 1, 'mish') 218 | 219 | def forward(self, input): 220 | x1 = self.conv1(input) 221 | x2 = self.conv2(x1) 222 | x3 = self.conv3(x1) 223 | 224 | r = self.resblock(x3) 225 | x4 = self.conv4(r) 226 | 227 | x4 = torch.cat([x4, x2], dim=1) 228 | x5 = self.conv5(x4) 229 | return x5 230 | 231 | 232 | class Neck(nn.Module): 233 | def __init__(self, inference=False): 234 | super().__init__() 235 | self.inference = inference 236 | 237 | self.conv1 = Conv_Bn_Activation(1024, 512, 1, 1, 'leaky') 238 | self.conv2 = Conv_Bn_Activation(512, 1024, 3, 1, 'leaky') 239 | self.conv3 = Conv_Bn_Activation(1024, 512, 1, 1, 'leaky') 240 | # SPP 241 | self.maxpool1 = nn.MaxPool2d(kernel_size=5, stride=1, padding=5 // 2) 242 | self.maxpool2 = nn.MaxPool2d(kernel_size=9, stride=1, padding=9 // 2) 243 | self.maxpool3 = nn.MaxPool2d(kernel_size=13, stride=1, padding=13 // 2) 244 | 245 | # R -1 -3 -5 -6 246 | # SPP 247 | self.conv4 = Conv_Bn_Activation(2048, 512, 1, 1, 'leaky') 248 | self.conv5 = Conv_Bn_Activation(512, 1024, 3, 1, 'leaky') 249 | self.conv6 = Conv_Bn_Activation(1024, 512, 1, 1, 'leaky') 250 | self.conv7 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 251 | # UP 252 | self.upsample1 = Upsample() 253 | # R 85 254 | self.conv8 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 255 | # R -1 -3 256 | self.conv9 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 257 | self.conv10 = Conv_Bn_Activation(256, 512, 3, 1, 'leaky') 258 | self.conv11 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 259 | self.conv12 = Conv_Bn_Activation(256, 512, 3, 1, 'leaky') 260 | self.conv13 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 261 | self.conv14 = Conv_Bn_Activation(256, 128, 1, 1, 'leaky') 262 | # UP 263 | self.upsample2 = Upsample() 264 | # R 54 265 | self.conv15 = Conv_Bn_Activation(256, 128, 1, 1, 'leaky') 266 | # R -1 -3 267 | self.conv16 = Conv_Bn_Activation(256, 128, 1, 1, 'leaky') 268 | self.conv17 = Conv_Bn_Activation(128, 256, 3, 1, 'leaky') 269 | self.conv18 = Conv_Bn_Activation(256, 128, 1, 1, 'leaky') 270 | self.conv19 = Conv_Bn_Activation(128, 256, 3, 1, 'leaky') 271 | self.conv20 = Conv_Bn_Activation(256, 128, 1, 1, 'leaky') 272 | 273 | def forward(self, input, downsample4, downsample3, inference=False): 274 | x1 = self.conv1(input) 275 | x2 = self.conv2(x1) 276 | x3 = self.conv3(x2) 277 | # SPP 278 | m1 = self.maxpool1(x3) 279 | m2 = self.maxpool2(x3) 280 | m3 = self.maxpool3(x3) 281 | spp = torch.cat([m3, m2, m1, x3], dim=1) 282 | # SPP end 283 | x4 = self.conv4(spp) 284 | x5 = self.conv5(x4) 285 | x6 = self.conv6(x5) 286 | x7 = self.conv7(x6) 287 | # UP 288 | up = self.upsample1(x7, downsample4.size(), self.inference) 289 | # R 85 290 | x8 = self.conv8(downsample4) 291 | # R -1 -3 292 | x8 = torch.cat([x8, up], dim=1) 293 | 294 | x9 = self.conv9(x8) 295 | x10 = self.conv10(x9) 296 | x11 = self.conv11(x10) 297 | x12 = self.conv12(x11) 298 | x13 = self.conv13(x12) 299 | x14 = self.conv14(x13) 300 | 301 | # UP 302 | up = self.upsample2(x14, downsample3.size(), self.inference) 303 | # R 54 304 | x15 = self.conv15(downsample3) 305 | # R -1 -3 306 | x15 = torch.cat([x15, up], dim=1) 307 | 308 | x16 = self.conv16(x15) 309 | x17 = self.conv17(x16) 310 | x18 = self.conv18(x17) 311 | x19 = self.conv19(x18) 312 | x20 = self.conv20(x19) 313 | return x20, x13, x6 314 | 315 | 316 | class Yolov4Head(nn.Module): 317 | def __init__(self, output_ch, inference=False): 318 | super().__init__() 319 | self.inference = inference 320 | 321 | self.conv1 = Conv_Bn_Activation(128, 256, 3, 1, 'leaky') 322 | self.conv2 = Conv_Bn_Activation(256, output_ch, 1, 1, 'linear', bn=False, bias=True) 323 | 324 | # R -4 325 | self.conv3 = Conv_Bn_Activation(128, 256, 3, 2, 'leaky') 326 | 327 | # R -1 -16 328 | self.conv4 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 329 | self.conv5 = Conv_Bn_Activation(256, 512, 3, 1, 'leaky') 330 | self.conv6 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 331 | self.conv7 = Conv_Bn_Activation(256, 512, 3, 1, 'leaky') 332 | self.conv8 = Conv_Bn_Activation(512, 256, 1, 1, 'leaky') 333 | self.conv9 = Conv_Bn_Activation(256, 512, 3, 1, 'leaky') 334 | self.conv10 = Conv_Bn_Activation(512, output_ch, 1, 1, 'linear', bn=False, bias=True) 335 | 336 | # R -4 337 | self.conv11 = Conv_Bn_Activation(256, 512, 3, 2, 'leaky') 338 | 339 | # R -1 -37 340 | self.conv12 = Conv_Bn_Activation(1024, 512, 1, 1, 'leaky') 341 | self.conv13 = Conv_Bn_Activation(512, 1024, 3, 1, 'leaky') 342 | self.conv14 = Conv_Bn_Activation(1024, 512, 1, 1, 'leaky') 343 | self.conv15 = Conv_Bn_Activation(512, 1024, 3, 1, 'leaky') 344 | self.conv16 = Conv_Bn_Activation(1024, 512, 1, 1, 'leaky') 345 | self.conv17 = Conv_Bn_Activation(512, 1024, 3, 1, 'leaky') 346 | self.conv18 = Conv_Bn_Activation(1024, output_ch, 1, 1, 'linear', bn=False, bias=True) 347 | 348 | def forward(self, input1, input2, input3): 349 | x1 = self.conv1(input1) 350 | x2 = self.conv2(x1) 351 | 352 | x3 = self.conv3(input1) 353 | # R -1 -16 354 | x3 = torch.cat([x3, input2], dim=1) 355 | x4 = self.conv4(x3) 356 | x5 = self.conv5(x4) 357 | x6 = self.conv6(x5) 358 | x7 = self.conv7(x6) 359 | x8 = self.conv8(x7) 360 | x9 = self.conv9(x8) 361 | x10 = self.conv10(x9) 362 | 363 | # R -4 364 | x11 = self.conv11(x8) 365 | # R -1 -37 366 | x11 = torch.cat([x11, input3], dim=1) 367 | 368 | x12 = self.conv12(x11) 369 | x13 = self.conv13(x12) 370 | x14 = self.conv14(x13) 371 | x15 = self.conv15(x14) 372 | x16 = self.conv16(x15) 373 | x17 = self.conv17(x16) 374 | x18 = self.conv18(x17) 375 | 376 | return [x2, x10, x18] 377 | 378 | 379 | class Yolov4(nn.Module): 380 | def __init__(self, yolov4conv137weight=None, output_ch=256, inference=False): 381 | super().__init__() 382 | 383 | # backbone 384 | self.down1 = DownSample1() 385 | self.down2 = DownSample2() 386 | self.down3 = DownSample3() 387 | self.down4 = DownSample4() 388 | self.down5 = DownSample5() 389 | # neck 390 | self.neek = Neck(inference) 391 | # yolov4conv137 392 | if yolov4conv137weight: 393 | _model = nn.Sequential(self.down1, self.down2, self.down3, self.down4, self.down5, self.neek) 394 | pretrained_dict = torch.load(yolov4conv137weight) 395 | 396 | model_dict = _model.state_dict() 397 | # 1. filter out unnecessary keys 398 | pretrained_dict = {k1: v for (k, v), k1 in zip(pretrained_dict.items(), model_dict)} 399 | # 2. overwrite entries in the existing state dict 400 | model_dict.update(pretrained_dict) 401 | _model.load_state_dict(model_dict) 402 | 403 | self.head = Yolov4Head(output_ch, inference) 404 | 405 | def forward(self, input): 406 | d1 = self.down1(input) 407 | d2 = self.down2(d1) 408 | d3 = self.down3(d2) 409 | d4 = self.down4(d3) 410 | d5 = self.down5(d4) 411 | 412 | x20, x13, x6 = self.neek(d5, d4, d3) 413 | 414 | out = self.head(x20, x13, x6) 415 | # return [(out[2],), (out[1],), (out[0],)] 416 | return [(out[0],), (out[1],), (out[2],)] 417 | 418 | 419 | def Yolov4Filters(*args, **kwargs): 420 | ckpt_path = '/media/linux_data/projects/MonoPort-7.9/data/Yolov4/yolov4.conv.137.pth' 421 | return Yolov4( 422 | yolov4conv137weight=ckpt_path, 423 | output_ch=256, 424 | inference=True) 425 | 426 | 427 | if __name__ == '__main__': 428 | import tqdm 429 | 430 | device = 'cuda:0' 431 | input = torch.randn(1, 3, 512, 512).to(device) 432 | model = Yolov4Filters().to(device) 433 | model.eval() 434 | 435 | with torch.no_grad(): 436 | outputs = model(input) 437 | for stage, output_stage in enumerate(outputs): 438 | for lvl, output_lvl in enumerate(output_stage): 439 | print (f'stage: {stage}, lvl: {lvl}', output_lvl.shape) 440 | 441 | with torch.no_grad(): # 52.77 fps 442 | for _ in tqdm.tqdm(range(1000)): 443 | outputs = model(input) -------------------------------------------------------------------------------- /monoport/lib/modeling/backbones/__init__.py: -------------------------------------------------------------------------------- 1 | from .HGFilters import HGFilter, PIFuHGFilters 2 | from .ResBlkFilters import ResnetFilter, PIFuResBlkFilters 3 | from .Yolov4Filters import Yolov4Filters 4 | from .HRNetFilters import HRNetV2_W18_small_v2_balance_last -------------------------------------------------------------------------------- /monoport/lib/modeling/geometry.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | 4 | def index(feat, uv): 5 | ''' 6 | 7 | :param feat: [B, C, H, W] image features 8 | :param uv: [B, 2, N] uv coordinates in the image plane, range [0, 1] 9 | :return: [B, C, N] image features at the uv coordinates 10 | ''' 11 | uv = uv.transpose(1, 2) # [B, N, 2] 12 | uv = uv.unsqueeze(2) # [B, N, 1, 2] 13 | # NOTE: for newer PyTorch, it seems that training results are degraded due to implementation diff in F.grid_sample 14 | # for old versions, simply remove the aligned_corners argument. 15 | samples = torch.nn.functional.grid_sample(feat, uv, align_corners=True) # [B, C, N, 1] 16 | return samples[:, :, :, 0] # [B, C, N] 17 | 18 | 19 | def orthogonal(points, calibrations, transforms=None): 20 | ''' 21 | Compute the orthogonal projections of 3D points into the image plane by given projection matrix 22 | :param points: [B, 3, N] Tensor of 3D points 23 | :param calibrations: [B, 3, 4] Tensor of projection matrix 24 | :param transforms: [B, 2, 3] Tensor of image transform matrix 25 | :return: xyz: [B, 3, N] Tensor of xyz coordinates in the image plane 26 | ''' 27 | rot = calibrations[:, :3, :3] 28 | trans = calibrations[:, :3, 3:4] 29 | pts = torch.baddbmm(trans, rot, points) # [B, 3, N] 30 | if transforms is not None: 31 | scale = transforms[:2, :2] 32 | shift = transforms[:2, 2:3] 33 | pts[:, :2, :] = torch.baddbmm(shift, scale, pts[:, :2, :]) 34 | return pts 35 | 36 | 37 | def perspective(points, calibrations, transforms=None): 38 | ''' 39 | Compute the perspective projections of 3D points into the image plane by given projection matrix 40 | :param points: [Bx3xN] Tensor of 3D points 41 | :param calibrations: [Bx3x4] Tensor of projection matrix 42 | :param transforms: [Bx2x3] Tensor of image transform matrix 43 | :return: xy: [Bx2xN] Tensor of xy coordinates in the image plane 44 | ''' 45 | rot = calibrations[:, :3, :3] 46 | trans = calibrations[:, :3, 3:4] 47 | homo = torch.baddbmm(trans, rot, points) # [B, 3, N] 48 | xy = homo[:, :2, :] / homo[:, 2:3, :] 49 | if transforms is not None: 50 | scale = transforms[:2, :2] 51 | shift = transforms[:2, 2:3] 52 | xy = torch.baddbmm(shift, scale, xy) 53 | 54 | xyz = torch.cat([xy, homo[:, 2:3, :]], 1) 55 | return xyz 56 | -------------------------------------------------------------------------------- /monoport/lib/modeling/heads/SurfaceClassifier.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | 6 | class SurfaceClassifier(nn.Module): 7 | def __init__(self, filter_channels, num_views=1, no_residual=True, last_op=None): 8 | super(SurfaceClassifier, self).__init__() 9 | 10 | self.filters = nn.ModuleList() 11 | self.num_views = num_views 12 | self.no_residual = no_residual 13 | filter_channels = filter_channels 14 | self.last_op = last_op 15 | 16 | if self.no_residual: 17 | for l in range(0, len(filter_channels) - 1): 18 | self.filters.append(nn.Conv1d( 19 | filter_channels[l], 20 | filter_channels[l + 1], 21 | 1)) 22 | # self.add_module("conv%d" % l, self.filters[l]) 23 | else: 24 | for l in range(0, len(filter_channels) - 1): 25 | if 0 != l: 26 | self.filters.append( 27 | nn.Conv1d( 28 | filter_channels[l] + filter_channels[0], 29 | filter_channels[l + 1], 30 | 1)) 31 | else: 32 | self.filters.append(nn.Conv1d( 33 | filter_channels[l], 34 | filter_channels[l + 1], 35 | 1)) 36 | 37 | # self.add_module("conv%d" % l, self.filters[l]) 38 | 39 | def forward(self, feature): 40 | ''' 41 | 42 | :param feature: list of [BxC_inxHxW] tensors of image features 43 | :param xy: [Bx3xN] tensor of (x,y) coodinates in the image plane 44 | :return: [BxC_outxN] tensor of features extracted at the coordinates 45 | ''' 46 | 47 | y = feature 48 | tmpy = feature 49 | for i, f in enumerate(self.filters): 50 | if self.no_residual: 51 | y = f(y) 52 | else: 53 | y = f( 54 | y if i == 0 55 | else torch.cat([y, tmpy], 1) 56 | ) 57 | if i != len(self.filters) - 1: 58 | y = F.leaky_relu(y) 59 | 60 | if self.num_views > 1 and i == len(self.filters) // 2: 61 | y = y.view( 62 | -1, self.num_views, y.shape[1], y.shape[2] 63 | ).mean(dim=1) 64 | tmpy = feature.view( 65 | -1, self.num_views, feature.shape[1], feature.shape[2] 66 | ).mean(dim=1) 67 | 68 | if self.last_op: 69 | y = self.last_op(y) 70 | 71 | return y 72 | 73 | 74 | def PIFuNetGMLP(*args, **kwargs): 75 | num_views = 1 76 | filter_channels = [257, 1024, 512, 256, 128, 1] 77 | no_residual = False 78 | last_op = nn.Sigmoid() 79 | return SurfaceClassifier(filter_channels, num_views, no_residual, last_op) 80 | 81 | 82 | def PIFuNetCMLP(*args, **kwargs): 83 | num_views = 1 84 | filter_channels = [513, 1024, 512, 256, 128, 3] 85 | no_residual = False 86 | last_op = nn.Tanh() 87 | return SurfaceClassifier(filter_channels, num_views, no_residual, last_op) 88 | 89 | 90 | if __name__ == "__main__": 91 | import tqdm 92 | 93 | device = 'cuda:0' 94 | # netG 95 | input = torch.randn(1, 257, 50000).to(device) 96 | model = PIFuNetGMLP().to(device) 97 | 98 | with torch.no_grad(): 99 | outputs = model(input) 100 | print (outputs.shape) 101 | 102 | with torch.no_grad(): # 38.13 fps 103 | for _ in tqdm.tqdm(range(1000)): 104 | outputs = model(input) 105 | 106 | # netC 107 | input = torch.randn(1, 513, 50000).to(device) 108 | model = PIFuNetCMLP().to(device) 109 | 110 | with torch.no_grad(): 111 | outputs = model(input) 112 | print (outputs.shape) 113 | 114 | with torch.no_grad(): # 23.71 fps 115 | for _ in tqdm.tqdm(range(1000)): 116 | outputs = model(input) 117 | -------------------------------------------------------------------------------- /monoport/lib/modeling/heads/__init__.py: -------------------------------------------------------------------------------- 1 | from .SurfaceClassifier import SurfaceClassifier 2 | from .SurfaceClassifier import PIFuNetGMLP 3 | from .SurfaceClassifier import PIFuNetCMLP 4 | -------------------------------------------------------------------------------- /monoport/lib/modeling/normalizers/DepthNormalizer.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | 6 | class DepthNormalizer(nn.Module): 7 | def __init__(self, opt): 8 | super(DepthNormalizer, self).__init__() 9 | self.opt = opt 10 | 11 | def forward(self, z, calibs=None, index_feat=None): 12 | ''' 13 | Normalize z_feature 14 | :param z_feat: [B, 1, N] depth value for z in the image coordinate system 15 | :return: 16 | ''' 17 | if self.opt.soft_onehot: 18 | soft_dim = self.opt.soft_dim 19 | 20 | # [-1, +1] -> [0, soft_dim-1] 21 | z_feat = torch.zeros(z.size(0), soft_dim, z.size(2)).to(z.device) # [1, 64, 10000] 22 | z_norm = (z.clamp(-1, 1) + 1) / 2.0 * (soft_dim - 1) 23 | z_floor = torch.floor(z_norm) #[1, 1, 10000] 24 | z_ceil = torch.ceil(z_norm) 25 | 26 | z_floor_value = 1 - (z_norm - z_floor) #[1, 1, 10000] 27 | z_ceil_value = 1 - (z_ceil - z_norm) 28 | 29 | z_feat = z_feat.scatter(dim=1, index=z_floor.long(), src=z_floor_value) 30 | z_feat = z_feat.scatter(dim=1, index=z_ceil.long(), src=z_ceil_value) 31 | else: 32 | z_feat = z * self.opt.scale 33 | return z_feat 34 | 35 | 36 | def PIFuNomalizer(*args, **kwargs): 37 | from yacs.config import CfgNode as CN 38 | opt = CN() 39 | opt.soft_onehot = False 40 | opt.scale = 512 // 2 / 200.0 41 | return DepthNormalizer(opt) -------------------------------------------------------------------------------- /monoport/lib/modeling/normalizers/__init__.py: -------------------------------------------------------------------------------- 1 | from .DepthNormalizer import DepthNormalizer 2 | from .DepthNormalizer import PIFuNomalizer 3 | -------------------------------------------------------------------------------- /monoport/lib/render/BaseCamera.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class BaseCamera: 5 | def __init__(self, name="BaseCamera"): 6 | self.name = name 7 | # 8 | # The 'magnification' property tells effectively, 9 | # if there is a ruler at one unit away from the camera, 10 | # how much length will the ruler appear in the image the camera will capture. 11 | # This property is useful and transparent for both Perspective and Orthogonal cases. 12 | # 13 | # For instance, 14 | # if the world unit is in meter and magnification_x = 1.8, 15 | # the camera is set at 0.9m height and looking front, 16 | # a 1.8m tall man standing 1m away from the camera will be exactly in the camera view. 17 | # 18 | 19 | # How wide the camera can see from left to right at one unit away 20 | self.magnification_x = 1 21 | # How tall the camera can see from bottom to top at one unit away 22 | self.magnification_y = 1 23 | 24 | # Ratio of camera width to height, e.g. 1:1, 4:3, 16:9. 25 | self.aspect_ratio = 1 26 | 27 | # Close up clamping distance in world unit 28 | self.near = 0.01 29 | # Farthest clamping distance in world unit 30 | self.far = 10000 31 | 32 | def get_name(self): 33 | return self.name 34 | 35 | def set_parameters(self, magnification_x, magnification_y=None): 36 | ''' 37 | 38 | :param magnification_x: 39 | :param magnification_y: Optional for y direction; Use the same value as for x direction if None 40 | ''' 41 | if magnification_y is None: 42 | magnification_y = magnification_x / self.aspect_ratio 43 | 44 | self.magnification_x = magnification_x 45 | self.magnification_y = magnification_y 46 | 47 | def get_projection_mat(self): 48 | # http://www.songho.ca/opengl/gl_projectionmatrix.html 49 | projection_mat = np.eye(4) 50 | projection_mat[0, 0] = 2 / self.magnification_x 51 | projection_mat[1, 1] = 2 / self.magnification_y 52 | projection_mat[2, 2] = -2 / (self.far - self.near) 53 | projection_mat[2, 3] = -(self.far + self.near) / (self.far - self.near) 54 | return projection_mat 55 | -------------------------------------------------------------------------------- /monoport/lib/render/CameraPose.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | class CameraPose: 4 | def __init__(self): 5 | # Camera's center in world coordinate 6 | self.center = np.array([0.0, 0.0, 1.0]) 7 | # Camera's Z axis direction in world coordinate 8 | self.front = np.array([0.0, 0.0, 1.0]) 9 | # Camera's X axis direction in world coordinate 10 | self.right = np.array([1.0, 0.0, 0.0]) 11 | # Camera's Y axis direction in world coordinate 12 | self.up = np.array([0.0, 1.0, 0.0]) 13 | 14 | self.sanity_check() 15 | 16 | def sanity_check(self): 17 | self.center = np.array(self.center).reshape([-1]) 18 | 19 | self.front = np.array(self.front).reshape([-1]) 20 | self.front = self.normalize_vector(self.front) 21 | 22 | self.up = np.array(self.up).reshape([-1]) 23 | self.right = np.cross(self.up, self.front) 24 | self.right = self.normalize_vector(self.right) 25 | 26 | self.up = np.cross(self.front, self.right) 27 | self.up = self.normalize_vector(self.up) 28 | 29 | assert len(self.center) == 3 30 | assert len(self.front) == 3 31 | assert len(self.right) == 3 32 | assert len(self.up) == 3 33 | 34 | @staticmethod 35 | def normalize_vector(v): 36 | v_norm = np.linalg.norm(v) 37 | return v if v_norm == 0 else v / v_norm 38 | 39 | def get_rotation_matrix(self): 40 | rot_mat = np.eye(3) 41 | rot_mat[0, :] = self.right 42 | rot_mat[1, :] = self.up 43 | rot_mat[2, :] = self.front 44 | return rot_mat 45 | 46 | def get_translation_vector(self): 47 | rot_mat = self.get_rotation_matrix() 48 | trans = -np.dot(rot_mat, self.center) 49 | return trans 50 | 51 | def get_model_view_mat(self): 52 | model_view = np.eye(4) 53 | model_view[:3, :3] = self.get_rotation_matrix() 54 | model_view[:3, 3] = self.get_translation_vector() 55 | return model_view -------------------------------------------------------------------------------- /monoport/lib/render/PespectiveCamera.py: -------------------------------------------------------------------------------- 1 | from .BaseCamera import BaseCamera 2 | import numpy as np 3 | import math 4 | 5 | 6 | class PersPectiveCamera(BaseCamera): 7 | def __init__(self): 8 | BaseCamera.__init__(self, "PerspectiveCamera") 9 | 10 | def get_projection_mat(self): 11 | # http://www.songho.ca/opengl/gl_projectionmatrix.html 12 | projection_mat = np.eye(4) 13 | projection_mat[0, 0] = 2 / self.magnification_x 14 | projection_mat[1, 1] = 2 / self.magnification_y 15 | projection_mat[2, 2] = -(self.far + self.near) / (self.far - self.near) 16 | projection_mat[2, 3] = -(2 * self.far * self.near) / (self.far - self.near) 17 | projection_mat[3, 2] = -1 18 | projection_mat[3, 3] = 0 19 | return projection_mat 20 | 21 | def set_by_field_of_view(self, fov_x, fov_y=None): 22 | ''' 23 | Set the intrinsic by given field of view, in angle degrees 24 | :param fov_x: 25 | :param fov_y: Optional for y direction; Use the same value as for x direction if None 26 | ''' 27 | if fov_y is None: 28 | fov_y = fov_x 29 | self.set_parameters( 30 | magnification_x=2 * math.tan(fov_x / 2), 31 | magnification_y=2 * math.tan(fov_y / 2), 32 | ) 33 | 34 | def set_by_35mm_equivalent_focal_length(self, focal_x, focal_y=None): 35 | ''' 36 | Set the intrinsic by given 35mm equivalent focal lengths. 37 | https://en.wikipedia.org/wiki/35_mm_equivalent_focal_length 38 | :param focal_x: 39 | :param focal_y: Optional for y direction; Use the same value as for x direction if None 40 | ''' 41 | if focal_y is None: 42 | focal_y = focal_x 43 | # 35mm equivalent sensor width and height for this camera 44 | film_35mm_height = math.sqrt((36 ** 2 + 24 ** 2) / (1 + self.aspect_ratio ** 2)) 45 | film_35mm_width = film_35mm_height * self.aspect_ratio 46 | 47 | self.set_parameters( 48 | magnification_x=film_35mm_width / focal_x, 49 | magnification_y=film_35mm_height / focal_y 50 | ) 51 | 52 | def set_by_sensor_and_focal_length(self, sensor_width, sensor_height, focal_x, focal_y=None): 53 | self.aspect_ratio = sensor_width / sensor_height 54 | if focal_y is None: 55 | focal_y = focal_x 56 | # 35mm equivalent sensor width and height for this camera 57 | self.set_parameters( 58 | magnification_x=sensor_width / focal_x, 59 | magnification_y=sensor_height / focal_y 60 | ) 61 | -------------------------------------------------------------------------------- /monoport/lib/render/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-Splinter/MonoPort/65551f6422552475b481d295e7a97a5dcec955b3/monoport/lib/render/__init__.py -------------------------------------------------------------------------------- /monoport/lib/render/gl/AlbedoRender.py: -------------------------------------------------------------------------------- 1 | from .Render import * 2 | 3 | 4 | class AlbedoRender(Render): 5 | def _init_shader(self): 6 | self.shader = Shader(vs_file='albedo.vs', fs_file='albedo.fs', gs_file=None) 7 | # layout (location = 0) in vec3 a_Position; 8 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 9 | # layout (location = 1) in vec2 a_TextureCoord; 10 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 11 | 12 | # Declare all uniform used in the program 13 | self.shader.declare_uniform('ModelMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 14 | self.shader.declare_uniform('PerspMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 15 | 16 | # Declare all textures used in the program 17 | self.texture_dict["TargetTexture"] = Texture() 18 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/GLObject.py: -------------------------------------------------------------------------------- 1 | import OpenGL.GL as gl 2 | 3 | 4 | class GLObject(object): 5 | def __del__(self): 6 | self.release() 7 | 8 | def __enter__(self): 9 | bind_func, const = self._bind 10 | bind_func(const, self) 11 | 12 | def __exit__(self, *args): 13 | bind_func, const = self._bind 14 | bind_func(const, 0) 15 | 16 | 17 | class FBO(GLObject): 18 | _bind = gl.glBindFramebuffer, gl.GL_FRAMEBUFFER 19 | 20 | def __init__(self): 21 | self._as_parameter_ = gl.glGenFramebuffers(1) 22 | 23 | def release(self): 24 | try: 25 | if self._as_parameter_ > 0: 26 | gl.glDeleteFramebuffers(1, [self._as_parameter_]) 27 | except Exception: 28 | pass 29 | 30 | 31 | class Texture(GLObject): 32 | _bind = gl.glBindTexture, gl.GL_TEXTURE_2D 33 | 34 | def __init__(self): 35 | self._as_parameter_ = gl.glGenTextures(1) 36 | 37 | def release(self): 38 | try: 39 | if self._as_parameter_ > 0: 40 | gl.glDeleteTextures([self._as_parameter_]) 41 | except Exception: 42 | pass 43 | 44 | 45 | class MultiSampleTexture(GLObject): 46 | _bind = gl.glBindTexture, gl.GL_TEXTURE_2D_MULTISAMPLE 47 | 48 | def __init__(self): 49 | self._as_parameter_ = gl.glGenTextures(1) 50 | 51 | def release(self): 52 | try: 53 | if self._as_parameter_ > 0: 54 | gl.glDeleteTextures([self._as_parameter_]) 55 | except Exception: 56 | pass 57 | 58 | 59 | class RBO(GLObject): 60 | _bind = gl.glBindRenderbuffer, gl.GL_RENDERBUFFER 61 | 62 | def __init__(self): 63 | self._as_parameter_ = gl.glGenRenderbuffers(1) 64 | 65 | def release(self): 66 | try: 67 | if self._as_parameter_ > 0: 68 | gl.glDeleteRenderbuffers(1, [self._as_parameter_]) 69 | except Exception: 70 | pass 71 | 72 | 73 | class VBO(GLObject): 74 | _bind = gl.glBindBuffer, gl.GL_ARRAY_BUFFER 75 | 76 | def __init__(self, type_code, gl_type, dim=3, size=0): 77 | self._as_parameter_ = gl.glGenBuffers(1) 78 | self.type_code = type_code 79 | self.gl_type = gl_type 80 | self.dim = dim 81 | self.size = size 82 | 83 | def release(self): 84 | try: 85 | if self._as_parameter_ > 0: 86 | gl.glDeleteBuffers(1, [self._as_parameter_]) 87 | except Exception: 88 | pass 89 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/NormalRender.py: -------------------------------------------------------------------------------- 1 | from .Render import * 2 | 3 | 4 | class NormalRender(Render): 5 | def _init_shader(self): 6 | self.shader = Shader(vs_file='normal.vs', fs_file='normal.fs', gs_file=None) 7 | # layout (location = 0) in vec3 Position; 8 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 9 | # layout (location = 1) in vec3 Normal; 10 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 11 | 12 | # Declare all uniform used in the program 13 | self.shader.declare_uniform('ModelMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 14 | self.shader.declare_uniform('PerspMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 15 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/PrtRender.py: -------------------------------------------------------------------------------- 1 | from .Render import * 2 | 3 | 4 | class PrtRender(Render): 5 | def __init__(self, 6 | width, height, 7 | multi_sample_rate=1 8 | ): 9 | Render.__init__(self, width, height, multi_sample_rate, num_render_target=8) 10 | 11 | def _init_shader(self): 12 | self.shader = Shader(vs_file='prt.vs', fs_file='prt.fs', gs_file=None) 13 | 14 | # Declare all vertex attributes used in the program 15 | # layout (location = 0) in vec3 a_Position; 16 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 17 | # layout (location = 1) in vec3 a_Normal; 18 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 19 | # layout (location = 2) in vec2 a_TextureCoord; 20 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 21 | # layout (location = 5) in vec3 a_PRT1; 22 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 23 | # layout (location = 6) in vec3 a_PRT2; 24 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 25 | # layout (location = 7) in vec3 a_PRT3; 26 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 27 | 28 | # Declare all uniforms used in the program 29 | self.shader.declare_uniform('ModelMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 30 | self.shader.declare_uniform('PerspMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 31 | 32 | self.shader.declare_uniform('SHCoeffs', type_code='f', gl_type=gl.glUniform3fv) 33 | 34 | self.shader.declare_uniform('UVMode', type_code='i', gl_type=gl.glUniform1i) 35 | 36 | # Declare all textures used in the program 37 | self.texture_dict["AlbedoMap"] = Texture() 38 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/Render.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .Shader import * 3 | 4 | 5 | class Render(object): 6 | def __init__(self, 7 | width, height, 8 | multi_sample_rate=1, 9 | num_render_target=1 10 | ): 11 | self.width = width 12 | self.height = height 13 | 14 | self.vbo_list = [] 15 | self.uniform_dict = {} 16 | self.texture_dict = {} 17 | 18 | # Configure frame buffer 19 | # This is an off screen frame buffer holds the rendering results. 20 | # During display, it is drawn onto the screen by a quad program. 21 | self.color_fbo = FBO() 22 | self.color_tex_list = [] 23 | 24 | with self.color_fbo: 25 | for i in range(num_render_target): 26 | color_tex = Texture() 27 | self.color_tex_list.append(color_tex) 28 | with color_tex: 29 | gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA32F, width, height, 0, 30 | gl.GL_RGBA, gl.GL_FLOAT, None) 31 | gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0 + i, 32 | gl.GL_TEXTURE_2D, color_tex, 0) 33 | gl.glViewport(0, 0, width, height) 34 | assert gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) == gl.GL_FRAMEBUFFER_COMPLETE 35 | 36 | # This is the actual frame buffer the shader program is rendered to. 37 | # For multi-sampling, it is different than the default fbo. 38 | self._shader_fbo = self.color_fbo 39 | self._shader_color_tex_list = self.color_tex_list 40 | 41 | # However, if multi-sampling is enabled, we need additional render target textures. 42 | if multi_sample_rate > 1: 43 | self._shader_fbo = FBO() 44 | self._shader_color_tex_list = [] 45 | with self._shader_fbo: 46 | for i in range(num_render_target): 47 | color_tex = MultiSampleTexture() 48 | self._shader_color_tex_list.append(color_tex) 49 | with color_tex: 50 | gl.glTexImage2DMultisample(gl.GL_TEXTURE_2D_MULTISAMPLE, multi_sample_rate, gl.GL_RGBA32F, 51 | width, height, gl.GL_TRUE) 52 | gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0 + i, 53 | gl.GL_TEXTURE_2D_MULTISAMPLE, color_tex, 0) 54 | 55 | # Configure depth buffer 56 | self._shader_depth_tex = RBO() 57 | with self._shader_fbo: 58 | with self._shader_depth_tex: 59 | gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, multi_sample_rate, 60 | gl.GL_DEPTH24_STENCIL8, width, height) 61 | gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_STENCIL_ATTACHMENT, 62 | gl.GL_RENDERBUFFER, self._shader_depth_tex) 63 | 64 | self._init_shader() 65 | for uniform in self.uniform_dict: 66 | self.uniform_dict[uniform]["handle"] = gl.glGetUniformLocation(self.shader, uniform) 67 | 68 | def _init_shader(self): 69 | self.shader = Shader(vs_file='simple.vs', fs_file='simple.fs', gs_file=None) 70 | # layout (location = 0) in vec3 Position; 71 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 72 | 73 | # Declare all uniform used in the program 74 | self.shader.declare_uniform('ModelMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 75 | self.shader.declare_uniform('PerspMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 76 | 77 | def set_attrib(self, attrib_id, data): 78 | if not 0 <= attrib_id < len(self.vbo_list): 79 | print("Error: Attrib index out if bound.") 80 | return 81 | vbo = self.vbo_list[attrib_id] 82 | with vbo: 83 | data = np.ascontiguousarray(data, vbo.type_code) 84 | vbo.dim = data.shape[-1] 85 | vbo.size = data.shape[0] 86 | gl.glBufferData(gl.GL_ARRAY_BUFFER, data, gl.GL_STATIC_DRAW) 87 | 88 | def set_texture(self, name, texture_image): 89 | if name not in self.texture_dict: 90 | print("Error: Unknown texture name.") 91 | return 92 | width = texture_image.shape[1] 93 | height = texture_image.shape[0] 94 | texture_image = np.flip(texture_image, 0) 95 | img_data = np.fromstring(texture_image.tostring(), np.uint8) 96 | tex = self.texture_dict[name] 97 | with tex: 98 | gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, width, height, 0, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, img_data) 99 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAX_LEVEL, 3) 100 | gl.glGenerateMipmap(gl.GL_TEXTURE_2D) 101 | 102 | def draw(self, 103 | uniform_dict, 104 | clear_color=[0, 0, 0, 0], 105 | ): 106 | with self._shader_fbo: 107 | # Clean up 108 | gl.glClearColor(*clear_color) 109 | gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) 110 | 111 | with self.shader: 112 | # Setup shader uniforms 113 | for uniform_name in uniform_dict: 114 | self.shader.set_uniform(uniform_name, uniform_dict[uniform_name]) 115 | 116 | # Setup up VertexAttrib 117 | for attrib_id in range(len(self.vbo_list)): 118 | vbo = self.vbo_list[attrib_id] 119 | with vbo: 120 | gl.glEnableVertexAttribArray(attrib_id) 121 | gl.glVertexAttribPointer(attrib_id, vbo.dim, vbo.gl_type, gl.GL_FALSE, 0, None) 122 | 123 | # Setup Textures 124 | for i, texture_name in enumerate(self.texture_dict): 125 | gl.glActiveTexture(gl.GL_TEXTURE0 + i) 126 | gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_dict[texture_name]) 127 | gl.glUniform1i(gl.glGetUniformLocation(self.shader, texture_name), i) 128 | 129 | # Setup targets 130 | color_size = len(self.color_tex_list) 131 | attachments = [gl.GL_COLOR_ATTACHMENT0 + i for i in range(color_size)] 132 | gl.glDrawBuffers(color_size, attachments) 133 | 134 | gl.glDrawArrays(gl.GL_TRIANGLES, 0, self.vbo_list[0].size) 135 | 136 | for attrib_id in range(len(self.vbo_list)): 137 | gl.glDisableVertexAttribArray(attrib_id) 138 | 139 | # If render_fbo is not color_fbo, we need to copy data 140 | if self._shader_fbo != self.color_fbo: 141 | for i in range(len(self.color_tex_list)): 142 | gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self._shader_fbo) 143 | gl.glReadBuffer(gl.GL_COLOR_ATTACHMENT0 + i) 144 | gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.color_fbo) 145 | gl.glDrawBuffer(gl.GL_COLOR_ATTACHMENT0 + i) 146 | gl.glBlitFramebuffer(0, 0, self.width, self.height, 0, 0, self.width, self.height, 147 | gl.GL_COLOR_BUFFER_BIT, gl.GL_NEAREST) 148 | gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, 0) 149 | gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, 0) 150 | 151 | def get_color(self, color_id=0): 152 | with self.color_fbo: 153 | gl.glReadBuffer(gl.GL_COLOR_ATTACHMENT0 + color_id) 154 | data = gl.glReadPixels(0, 0, self.width, self.height, gl.GL_RGBA, gl.GL_FLOAT, outputType=None) 155 | frame = data.reshape(self.height, self.width, -1) 156 | frame = frame[::-1] # vertical flip to match GL convention 157 | return frame 158 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/ShRender.py: -------------------------------------------------------------------------------- 1 | from .Render import * 2 | 3 | 4 | class ShRender(Render): 5 | def __init__(self, 6 | width, height, 7 | multi_sample_rate=1 8 | ): 9 | Render.__init__(self, width, height, multi_sample_rate, num_render_target=8) 10 | 11 | def _init_shader(self): 12 | self.shader = Shader(vs_file='sh.vs', fs_file='sh.fs', gs_file=None) 13 | 14 | # Declare all vertex attributes used in the program 15 | # layout (location = 0) in vec3 a_Position; 16 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 17 | # layout (location = 1) in vec3 a_Normal; 18 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 19 | # layout (location = 2) in vec2 a_TextureCoord; 20 | self.vbo_list.append(VBO(type_code='f', gl_type=gl.GL_FLOAT)) 21 | 22 | # Declare all uniforms used in the program 23 | self.shader.declare_uniform('ModelMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 24 | self.shader.declare_uniform('PerspMat', type_code='f', gl_type=gl.glUniformMatrix4fv) 25 | 26 | self.shader.declare_uniform('SHCoeffs', type_code='f', gl_type=gl.glUniform3fv) 27 | 28 | # Declare all textures used in the program 29 | self.texture_dict["AlbedoMap"] = Texture() 30 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/Shader.py: -------------------------------------------------------------------------------- 1 | from .GLObject import * 2 | import numpy as np 3 | 4 | 5 | class Uniform(object): 6 | def __init__(self, program, uniform_name, type_code, gl_type): 7 | self._as_parameter_ = gl.glGetUniformLocation(program, uniform_name) 8 | self.name = uniform_name 9 | self.type_code = type_code 10 | self.gl_type = gl_type 11 | 12 | 13 | class Shader(GLObject): 14 | def __init__(self, 15 | vs_file='simple.vs', 16 | fs_file='simple.fs', 17 | gs_file=None): 18 | # Importing here, when gl context is already present. 19 | # Otherwise get exception on Python3 because of PyOpenGL bug. 20 | from OpenGL.GL import shaders 21 | with open(self._find_shader_file(vs_file), 'r') as f: 22 | vp_code = f.read() 23 | with open(self._find_shader_file(fs_file), 'r') as f: 24 | fp_code = f.read() 25 | if gs_file: 26 | with open(self._find_shader_file(gs_file), 'r') as f: 27 | gp_code = f.read() 28 | self._as_parameter_ = self._shader = shaders.compileProgram( 29 | shaders.compileShader(vp_code, gl.GL_VERTEX_SHADER), 30 | shaders.compileShader(fp_code, gl.GL_FRAGMENT_SHADER), 31 | shaders.compileShader(gp_code, gl.GL_GEOMETRY_SHADER) 32 | ) 33 | else: 34 | self._as_parameter_ = self._shader = shaders.compileProgram( 35 | shaders.compileShader(vp_code, gl.GL_VERTEX_SHADER), 36 | shaders.compileShader(fp_code, gl.GL_FRAGMENT_SHADER) 37 | ) 38 | self._uniforms = {} 39 | 40 | def declare_uniform(self, uniform_name, type_code, gl_type): 41 | if uniform_name not in self._uniforms: 42 | self._uniforms[uniform_name] = Uniform(self._shader, uniform_name, type_code, gl_type) 43 | else: 44 | self._uniforms[uniform_name].type_code = type_code 45 | self._uniforms[uniform_name].gl_type = gl_type 46 | 47 | def set_uniform(self, uniform_name, data): 48 | if uniform_name not in self._uniforms: 49 | print( 50 | "Error. Unknown uniform variable. " 51 | "You need to declare all uniform variables in YourRender::_init_shader() function.") 52 | return 53 | uniform = self._uniforms[uniform_name] 54 | data = np.ascontiguousarray(data, uniform.type_code) 55 | 56 | if uniform.gl_type is gl.glUniformMatrix4fv: 57 | gl.glUniformMatrix4fv(uniform, 1, gl.GL_TRUE, data) 58 | elif uniform.gl_type is gl.glUniform3fv: 59 | gl.glUniform3fv(uniform, data.shape[0], data) 60 | elif uniform.gl_type is gl.glUniform1i: 61 | gl.glUniform1i(uniform, data) 62 | else: 63 | print( 64 | "Error. Unknown uniform type. " 65 | "You need to declare all uniform types in Shader::set_uniform() function.") 66 | return 67 | 68 | @staticmethod 69 | def _find_shader_file(name): 70 | import os 71 | gl_folder = os.path.dirname(os.path.abspath(__file__)) 72 | glsl_file = os.path.join(gl_folder, 'shaders', name) 73 | return glsl_file 74 | 75 | def release(self): 76 | gl.glDeleteProgram(self._as_parameter_) 77 | 78 | def __enter__(self): 79 | return self._shader.__enter__() 80 | 81 | def __exit__(self, *args): 82 | return self._shader.__exit__(*args) 83 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/__init__.py: -------------------------------------------------------------------------------- 1 | from .Render import * 2 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/glcontext.py: -------------------------------------------------------------------------------- 1 | """OpenGL context creation. 2 | 3 | Typical usage: 4 | 5 | # Optional PyOpenGL configuration can be done here. 6 | # import OpenGL 7 | # OpenGL.ERROR_CHECKING = True 8 | 9 | # 'glcontext' must be imported before any OpenGL.* API. 10 | from lib.render.gl.glcontext import create_opengl_context 11 | 12 | # Now it's safe to import OpenGL and EGL functions 13 | import OpenGL.GL as gl 14 | 15 | # create_opengl_context() creates a GL context that is attached to an 16 | # onscreen window of the specified size. Note that rendering to buffers 17 | # of other sizes and formats is still possible with OpenGL Framebuffers. 18 | # 19 | # Users are expected to directly use the GL API in case more advanced 20 | # context management is required. 21 | width, height = 640, 480 22 | create_opengl_context((width, height)) 23 | 24 | # OpenGL context is available here. 25 | 26 | """ 27 | from OpenGL.GL import * 28 | from OpenGL.GLUT import * 29 | 30 | 31 | def create_opengl_context(width, height, name="My Render"): 32 | ''' 33 | Create on screen OpenGL context and make it current. 34 | 35 | Users are expected to directly use GL API in case more advanced 36 | context management is required. 37 | 38 | :param width: window width in pixels 39 | :param height: window height in pixels 40 | :return: 41 | ''' 42 | 43 | glutInit() 44 | display_mode = GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH 45 | 46 | glutInitDisplayMode(display_mode) 47 | glutInitWindowSize(width, height) 48 | glutInitWindowPosition(0, 0) 49 | 50 | glut_window = glutCreateWindow(name) 51 | 52 | glEnable(GL_DEPTH_TEST) 53 | 54 | glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE) 55 | glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE) 56 | glClampColor(GL_CLAMP_VERTEX_COLOR, GL_FALSE) 57 | 58 | return glut_window 59 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/albedo.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TextureCoord; 5 | 6 | uniform sampler2D TargetTexture; 7 | 8 | void main() 9 | { 10 | FragColor = texture(TargetTexture, TextureCoord); 11 | //FragColor = vec4(TextureCoord,1,1); 12 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/albedo.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 a_Position; 3 | layout (location = 1) in vec2 a_TextureCoord; 4 | 5 | out vec2 TextureCoord; 6 | 7 | uniform mat4 ModelMat; 8 | uniform mat4 PerspMat; 9 | 10 | void main() 11 | { 12 | gl_Position = PerspMat * ModelMat * vec4(a_Position, 1.0); 13 | TextureCoord = a_TextureCoord; 14 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/color.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 FragColor; 4 | 5 | in vec3 PassColor; 6 | 7 | void main() 8 | { 9 | FragColor = vec4(PassColor, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/color.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (location = 0) in vec3 Position; 4 | layout (location = 1) in vec3 Color; 5 | 6 | out vec3 PassColor; 7 | 8 | uniform mat4 ModelMat; 9 | uniform mat4 PerspMat; 10 | 11 | void main() 12 | { 13 | gl_Position = PerspMat * ModelMat * vec4(Position, 1.0); 14 | PassColor = Color; 15 | } 16 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/normal.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 FragColor; 4 | 5 | in vec3 CamNormal; 6 | 7 | void main() 8 | { 9 | vec3 cam_norm_normalized = normalize(CamNormal); 10 | vec3 rgb = (cam_norm_normalized + 1.0) / 2.0; 11 | FragColor = vec4(rgb, 1.0); 12 | } 13 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/normal.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (location = 0) in vec3 Position; 4 | layout (location = 1) in vec3 Normal; 5 | 6 | out vec3 CamNormal; 7 | 8 | uniform mat4 ModelMat; 9 | uniform mat4 PerspMat; 10 | 11 | void main() 12 | { 13 | gl_Position = PerspMat * ModelMat * vec4(Position, 1.0); 14 | CamNormal = (ModelMat * vec4(Normal, 0.0)).xyz; 15 | } 16 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/prt.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform vec3 SHCoeffs[9]; 3 | 4 | uniform sampler2D AlbedoMap; 5 | 6 | in VertexData { 7 | vec3 Position; 8 | vec3 Normal; 9 | vec2 TextureCoord; 10 | vec3 PRT1; 11 | vec3 PRT2; 12 | vec3 PRT3; 13 | } VertexIn; 14 | 15 | layout (location = 0) out vec4 FragColor; 16 | layout (location = 1) out vec4 FragNormal; 17 | layout (location = 2) out vec4 FragPosition; 18 | layout (location = 3) out vec4 FragAlbedo; 19 | layout (location = 4) out vec4 FragShading; 20 | layout (location = 5) out vec4 FragPRT1; 21 | layout (location = 6) out vec4 FragPRT2; 22 | layout (location = 7) out vec4 FragPRT3; 23 | 24 | vec4 gammaCorrection(vec4 vec, float g) 25 | { 26 | return vec4(pow(vec.x, 1.0/g), pow(vec.y, 1.0/g), pow(vec.z, 1.0/g), vec.w); 27 | } 28 | 29 | vec3 gammaCorrection(vec3 vec, float g) 30 | { 31 | return vec3(pow(vec.x, 1.0/g), pow(vec.y, 1.0/g), pow(vec.z, 1.0/g)); 32 | } 33 | 34 | void evaluateH(vec3 n, out float H[9]) 35 | { 36 | float c1 = 0.429043, c2 = 0.511664, 37 | c3 = 0.743125, c4 = 0.886227, c5 = 0.247708; 38 | 39 | H[0] = c4; 40 | H[1] = 2.0 * c2 * n[1]; 41 | H[2] = 2.0 * c2 * n[2]; 42 | H[3] = 2.0 * c2 * n[0]; 43 | H[4] = 2.0 * c1 * n[0] * n[1]; 44 | H[5] = 2.0 * c1 * n[1] * n[2]; 45 | H[6] = c3 * n[2] * n[2] - c5; 46 | H[7] = 2.0 * c1 * n[2] * n[0]; 47 | H[8] = c1 * (n[0] * n[0] - n[1] * n[1]); 48 | } 49 | 50 | vec3 evaluateLightingModel(vec3 normal) 51 | { 52 | float H[9]; 53 | evaluateH(normal, H); 54 | vec3 res = vec3(0.0); 55 | for (int i = 0; i < 9; i++) { 56 | res += H[i] * SHCoeffs[i]; 57 | } 58 | return res; 59 | } 60 | 61 | // nC: coarse geometry normal, nH: fine normal from normal map 62 | vec3 evaluateLightingModelHybrid(vec3 nC, vec3 nH, mat3 prt) 63 | { 64 | float HC[9], HH[9]; 65 | evaluateH(nC, HC); 66 | evaluateH(nH, HH); 67 | 68 | vec3 res = vec3(0.0); 69 | vec3 shadow = vec3(0.0); 70 | vec3 unshadow = vec3(0.0); 71 | for(int i = 0; i < 3; ++i){ 72 | for(int j = 0; j < 3; ++j){ 73 | int id = i*3+j; 74 | res += HH[id]* SHCoeffs[id]; 75 | shadow += prt[i][j] * SHCoeffs[id]; 76 | unshadow += HC[id] * SHCoeffs[id]; 77 | } 78 | } 79 | vec3 ratio = clamp(shadow/unshadow,0.0,1.0); 80 | res = ratio * res; 81 | 82 | return res; 83 | } 84 | 85 | vec3 evaluateLightingModelPRT(mat3 prt) 86 | { 87 | vec3 res = vec3(0.0); 88 | for(int i = 0; i < 3; ++i){ 89 | for(int j = 0; j < 3; ++j){ 90 | res += prt[i][j] * SHCoeffs[i*3+j]; 91 | } 92 | } 93 | 94 | return res; 95 | } 96 | 97 | void main() 98 | { 99 | vec2 uv = VertexIn.TextureCoord; 100 | vec3 normal = normalize(VertexIn.Normal); 101 | mat3 prt = mat3(VertexIn.PRT1, VertexIn.PRT2, VertexIn.PRT3); 102 | 103 | FragAlbedo = texture(AlbedoMap, uv); 104 | 105 | vec4 shading = vec4(evaluateLightingModelPRT(prt), 1.0f); 106 | shading = gammaCorrection(shading, 2.2); 107 | 108 | FragColor = clamp(FragAlbedo * shading, 0.0, 1.0); 109 | 110 | FragNormal = vec4(0.5 * (normal + vec3(1.0)), 1.0); 111 | 112 | FragPosition = vec4(VertexIn.Position, 1.0); 113 | 114 | FragShading = vec4(clamp(0.5 * shading.xyz, 0.0, 1.0), 1.0); 115 | 116 | FragPRT1 = vec4(VertexIn.PRT1,1.0); 117 | 118 | FragPRT2 = vec4(VertexIn.PRT2,1.0); 119 | 120 | FragPRT3 = vec4(VertexIn.PRT3,1.0); 121 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/prt.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (location = 0) in vec3 a_Position; 4 | layout (location = 1) in vec3 a_Normal; 5 | layout (location = 2) in vec2 a_TextureCoord; 6 | layout (location = 3) in vec3 a_PRT1; 7 | layout (location = 4) in vec3 a_PRT2; 8 | layout (location = 5) in vec3 a_PRT3; 9 | 10 | // All in camera space 11 | out VertexData { 12 | vec3 Position; 13 | vec3 Normal; 14 | vec2 TextureCoord; 15 | vec3 PRT1; 16 | vec3 PRT2; 17 | vec3 PRT3; 18 | } VertexOut; 19 | 20 | uniform mat4 ModelMat; 21 | uniform mat4 PerspMat; 22 | 23 | uniform int UVMode = 0; 24 | 25 | 26 | float s_c3 = 0.94617469575; // (3*sqrt(5))/(4*sqrt(pi)) 27 | float s_c4 = -0.31539156525;// (-sqrt(5))/(4*sqrt(pi)) 28 | float s_c5 = 0.54627421529; // (sqrt(15))/(4*sqrt(pi)) 29 | 30 | float s_c_scale = 1.0/0.91529123286551084; 31 | float s_c_scale_inv = 0.91529123286551084; 32 | 33 | float s_rc2 = 1.5853309190550713*s_c_scale; 34 | float s_c4_div_c3 = s_c4/s_c3; 35 | float s_c4_div_c3_x2 = (s_c4/s_c3)*2.0; 36 | 37 | float s_scale_dst2 = s_c3 * s_c_scale_inv; 38 | float s_scale_dst4 = s_c5 * s_c_scale_inv; 39 | 40 | void OptRotateBand0(float x[1], mat3 R, out float dst[1]) 41 | { 42 | dst[0] = x[0]; 43 | } 44 | 45 | // 9 multiplies 46 | void OptRotateBand1(float x[3], mat3 R, out float dst[3]) 47 | { 48 | // derived from SlowRotateBand1 49 | dst[0] = ( R[1][1])*x[0] + (-R[1][2])*x[1] + ( R[1][0])*x[2]; 50 | dst[1] = (-R[2][1])*x[0] + ( R[2][2])*x[1] + (-R[2][0])*x[2]; 51 | dst[2] = ( R[0][1])*x[0] + (-R[0][2])*x[1] + ( R[0][0])*x[2]; 52 | } 53 | 54 | // 48 multiplies 55 | void OptRotateBand2(float x[5], mat3 R, out float dst[5]) 56 | { 57 | // Sparse matrix multiply 58 | float sh0 = x[3] + x[4] + x[4] - x[1]; 59 | float sh1 = x[0] + s_rc2*x[2] + x[3] + x[4]; 60 | float sh2 = x[0]; 61 | float sh3 = -x[3]; 62 | float sh4 = -x[1]; 63 | 64 | // Rotations. R0 and R1 just use the raw matrix columns 65 | float r2x = R[0][0] + R[0][1]; 66 | float r2y = R[1][0] + R[1][1]; 67 | float r2z = R[2][0] + R[2][1]; 68 | 69 | float r3x = R[0][0] + R[0][2]; 70 | float r3y = R[1][0] + R[1][2]; 71 | float r3z = R[2][0] + R[2][2]; 72 | 73 | float r4x = R[0][1] + R[0][2]; 74 | float r4y = R[1][1] + R[1][2]; 75 | float r4z = R[2][1] + R[2][2]; 76 | 77 | // dense matrix multiplication one column at a time 78 | 79 | // column 0 80 | float sh0_x = sh0 * R[0][0]; 81 | float sh0_y = sh0 * R[1][0]; 82 | float d0 = sh0_x * R[1][0]; 83 | float d1 = sh0_y * R[2][0]; 84 | float d2 = sh0 * (R[2][0] * R[2][0] + s_c4_div_c3); 85 | float d3 = sh0_x * R[2][0]; 86 | float d4 = sh0_x * R[0][0] - sh0_y * R[1][0]; 87 | 88 | // column 1 89 | float sh1_x = sh1 * R[0][2]; 90 | float sh1_y = sh1 * R[1][2]; 91 | d0 += sh1_x * R[1][2]; 92 | d1 += sh1_y * R[2][2]; 93 | d2 += sh1 * (R[2][2] * R[2][2] + s_c4_div_c3); 94 | d3 += sh1_x * R[2][2]; 95 | d4 += sh1_x * R[0][2] - sh1_y * R[1][2]; 96 | 97 | // column 2 98 | float sh2_x = sh2 * r2x; 99 | float sh2_y = sh2 * r2y; 100 | d0 += sh2_x * r2y; 101 | d1 += sh2_y * r2z; 102 | d2 += sh2 * (r2z * r2z + s_c4_div_c3_x2); 103 | d3 += sh2_x * r2z; 104 | d4 += sh2_x * r2x - sh2_y * r2y; 105 | 106 | // column 3 107 | float sh3_x = sh3 * r3x; 108 | float sh3_y = sh3 * r3y; 109 | d0 += sh3_x * r3y; 110 | d1 += sh3_y * r3z; 111 | d2 += sh3 * (r3z * r3z + s_c4_div_c3_x2); 112 | d3 += sh3_x * r3z; 113 | d4 += sh3_x * r3x - sh3_y * r3y; 114 | 115 | // column 4 116 | float sh4_x = sh4 * r4x; 117 | float sh4_y = sh4 * r4y; 118 | d0 += sh4_x * r4y; 119 | d1 += sh4_y * r4z; 120 | d2 += sh4 * (r4z * r4z + s_c4_div_c3_x2); 121 | d3 += sh4_x * r4z; 122 | d4 += sh4_x * r4x - sh4_y * r4y; 123 | 124 | // extra multipliers 125 | dst[0] = d0; 126 | dst[1] = -d1; 127 | dst[2] = d2 * s_scale_dst2; 128 | dst[3] = -d3; 129 | dst[4] = d4 * s_scale_dst4; 130 | } 131 | 132 | void main() 133 | { 134 | 135 | VertexOut.Position = a_Position; 136 | VertexOut.TextureCoord = a_TextureCoord; 137 | 138 | float PRT0, PRT1[3], PRT2[5]; 139 | PRT0 = a_PRT1[0]; 140 | PRT1[0] = a_PRT1[1]; 141 | PRT1[1] = a_PRT1[2]; 142 | PRT1[2] = a_PRT2[0]; 143 | PRT2[0] = a_PRT2[1]; 144 | PRT2[1] = a_PRT2[2]; 145 | PRT2[2] = a_PRT3[0]; 146 | PRT2[3] = a_PRT3[1]; 147 | PRT2[4] = a_PRT3[2]; 148 | 149 | mat3 R = mat3(ModelMat); 150 | OptRotateBand1(PRT1, R, PRT1); 151 | OptRotateBand2(PRT2, R, PRT2); 152 | 153 | VertexOut.PRT1 = vec3(PRT0,PRT1[0],PRT1[1]); 154 | VertexOut.PRT2 = vec3(PRT1[2],PRT2[0],PRT2[1]); 155 | VertexOut.PRT3 = vec3(PRT2[2],PRT2[3],PRT2[4]); 156 | 157 | if(UVMode > 0) { 158 | VertexOut.Normal = a_Normal.xyz; 159 | gl_Position = vec4(a_TextureCoord, 0.0, 1.0) - vec4(0.5, 0.5, 0, 0); 160 | gl_Position[0] *= 2.0; 161 | gl_Position[1] *= 2.0; 162 | } 163 | else { 164 | VertexOut.Normal = (ModelMat * vec4(a_Normal, 0.0)).xyz; 165 | gl_Position = PerspMat * ModelMat * vec4(a_Position, 1.0); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/prt_uv.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec3 SHCoeffs[9]; 4 | uniform uint analytic; 5 | 6 | uniform uint hasNormalMap; 7 | uniform uint hasAlbedoMap; 8 | 9 | uniform sampler2D AlbedoMap; 10 | uniform sampler2D NormalMap; 11 | 12 | in VertexData { 13 | vec3 Position; 14 | vec3 ModelNormal; 15 | vec3 CameraNormal; 16 | vec2 Texcoord; 17 | vec3 Tangent; 18 | vec3 Bitangent; 19 | vec3 PRT1; 20 | vec3 PRT2; 21 | vec3 PRT3; 22 | } VertexIn; 23 | 24 | layout (location = 0) out vec4 FragColor; 25 | layout (location = 1) out vec4 FragPosition; 26 | layout (location = 2) out vec4 FragNormal; 27 | 28 | vec4 gammaCorrection(vec4 vec, float g) 29 | { 30 | return vec4(pow(vec.x, 1.0/g), pow(vec.y, 1.0/g), pow(vec.z, 1.0/g), vec.w); 31 | } 32 | 33 | vec3 gammaCorrection(vec3 vec, float g) 34 | { 35 | return vec3(pow(vec.x, 1.0/g), pow(vec.y, 1.0/g), pow(vec.z, 1.0/g)); 36 | } 37 | 38 | void evaluateH(vec3 n, out float H[9]) 39 | { 40 | float c1 = 0.429043, c2 = 0.511664, 41 | c3 = 0.743125, c4 = 0.886227, c5 = 0.247708; 42 | 43 | H[0] = c4; 44 | H[1] = 2.0 * c2 * n[1]; 45 | H[2] = 2.0 * c2 * n[2]; 46 | H[3] = 2.0 * c2 * n[0]; 47 | H[4] = 2.0 * c1 * n[0] * n[1]; 48 | H[5] = 2.0 * c1 * n[1] * n[2]; 49 | H[6] = c3 * n[2] * n[2] - c5; 50 | H[7] = 2.0 * c1 * n[2] * n[0]; 51 | H[8] = c1 * (n[0] * n[0] - n[1] * n[1]); 52 | } 53 | 54 | vec3 evaluateLightingModel(vec3 normal) 55 | { 56 | float H[9]; 57 | evaluateH(normal, H); 58 | vec3 res = vec3(0.0); 59 | for (int i = 0; i < 9; i++) { 60 | res += H[i] * SHCoeffs[i]; 61 | } 62 | return res; 63 | } 64 | 65 | // nC: coarse geometry normal, nH: fine normal from normal map 66 | vec3 evaluateLightingModelHybrid(vec3 nC, vec3 nH, mat3 prt) 67 | { 68 | float HC[9], HH[9]; 69 | evaluateH(nC, HC); 70 | evaluateH(nH, HH); 71 | 72 | vec3 res = vec3(0.0); 73 | vec3 shadow = vec3(0.0); 74 | vec3 unshadow = vec3(0.0); 75 | for(int i = 0; i < 3; ++i){ 76 | for(int j = 0; j < 3; ++j){ 77 | int id = i*3+j; 78 | res += HH[id]* SHCoeffs[id]; 79 | shadow += prt[i][j] * SHCoeffs[id]; 80 | unshadow += HC[id] * SHCoeffs[id]; 81 | } 82 | } 83 | vec3 ratio = clamp(shadow/unshadow,0.0,1.0); 84 | res = ratio * res; 85 | 86 | return res; 87 | } 88 | 89 | vec3 evaluateLightingModelPRT(mat3 prt) 90 | { 91 | vec3 res = vec3(0.0); 92 | for(int i = 0; i < 3; ++i){ 93 | for(int j = 0; j < 3; ++j){ 94 | res += prt[i][j] * SHCoeffs[i*3+j]; 95 | } 96 | } 97 | 98 | return res; 99 | } 100 | 101 | void main() 102 | { 103 | vec2 uv = VertexIn.Texcoord; 104 | vec3 nM = normalize(VertexIn.ModelNormal); 105 | vec3 nC = normalize(VertexIn.CameraNormal); 106 | vec3 nml = nC; 107 | mat3 prt = mat3(VertexIn.PRT1, VertexIn.PRT2, VertexIn.PRT3); 108 | 109 | vec4 albedo, shading; 110 | if(hasAlbedoMap == uint(0)) 111 | albedo = vec4(1.0); 112 | else 113 | albedo = texture(AlbedoMap, uv);//gammaCorrection(texture(AlbedoMap, uv), 1.0/2.2); 114 | 115 | if(hasNormalMap == uint(0)) 116 | { 117 | if(analytic == uint(0)) 118 | shading = vec4(evaluateLightingModelPRT(prt), 1.0f); 119 | else 120 | shading = vec4(evaluateLightingModel(nC), 1.0f); 121 | } 122 | else 123 | { 124 | vec3 n_tan = normalize(texture(NormalMap, uv).rgb*2.0-vec3(1.0)); 125 | 126 | mat3 TBN = mat3(normalize(VertexIn.Tangent),normalize(VertexIn.Bitangent),nC); 127 | vec3 nH = normalize(TBN * n_tan); 128 | 129 | if(analytic == uint(0)) 130 | shading = vec4(evaluateLightingModelHybrid(nC,nH,prt),1.0f); 131 | else 132 | shading = vec4(evaluateLightingModel(nH), 1.0f); 133 | 134 | nml = nH; 135 | } 136 | 137 | shading = gammaCorrection(shading, 2.2); 138 | FragColor = clamp(albedo * shading, 0.0, 1.0); 139 | FragPosition = vec4(VertexIn.Position,1.0); 140 | FragNormal = vec4(0.5*(nM+vec3(1.0)),1.0); 141 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/prt_uv.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (location = 0) in vec3 a_Position; 4 | layout (location = 1) in vec3 a_Normal; 5 | layout (location = 2) in vec2 a_TextureCoord; 6 | layout (location = 3) in vec3 a_Tangent; 7 | layout (location = 4) in vec3 a_Bitangent; 8 | layout (location = 5) in vec3 a_PRT1; 9 | layout (location = 6) in vec3 a_PRT2; 10 | layout (location = 7) in vec3 a_PRT3; 11 | 12 | out VertexData { 13 | vec3 Position; 14 | vec3 ModelNormal; 15 | vec3 CameraNormal; 16 | vec2 Texcoord; 17 | vec3 Tangent; 18 | vec3 Bitangent; 19 | vec3 PRT1; 20 | vec3 PRT2; 21 | vec3 PRT3; 22 | } VertexOut; 23 | 24 | uniform mat3 RotMat; 25 | uniform mat4 NormMat; 26 | uniform mat4 ModelMat; 27 | uniform mat4 PerspMat; 28 | 29 | #define pi 3.1415926535897932384626433832795 30 | 31 | float s_c3 = 0.94617469575; // (3*sqrt(5))/(4*sqrt(pi)) 32 | float s_c4 = -0.31539156525;// (-sqrt(5))/(4*sqrt(pi)) 33 | float s_c5 = 0.54627421529; // (sqrt(15))/(4*sqrt(pi)) 34 | 35 | float s_c_scale = 1.0/0.91529123286551084; 36 | float s_c_scale_inv = 0.91529123286551084; 37 | 38 | float s_rc2 = 1.5853309190550713*s_c_scale; 39 | float s_c4_div_c3 = s_c4/s_c3; 40 | float s_c4_div_c3_x2 = (s_c4/s_c3)*2.0; 41 | 42 | float s_scale_dst2 = s_c3 * s_c_scale_inv; 43 | float s_scale_dst4 = s_c5 * s_c_scale_inv; 44 | 45 | void OptRotateBand0(float x[1], mat3 R, out float dst[1]) 46 | { 47 | dst[0] = x[0]; 48 | } 49 | 50 | // 9 multiplies 51 | void OptRotateBand1(float x[3], mat3 R, out float dst[3]) 52 | { 53 | // derived from SlowRotateBand1 54 | dst[0] = ( R[1][1])*x[0] + (-R[1][2])*x[1] + ( R[1][0])*x[2]; 55 | dst[1] = (-R[2][1])*x[0] + ( R[2][2])*x[1] + (-R[2][0])*x[2]; 56 | dst[2] = ( R[0][1])*x[0] + (-R[0][2])*x[1] + ( R[0][0])*x[2]; 57 | } 58 | 59 | // 48 multiplies 60 | void OptRotateBand2(float x[5], mat3 R, out float dst[5]) 61 | { 62 | // Sparse matrix multiply 63 | float sh0 = x[3] + x[4] + x[4] - x[1]; 64 | float sh1 = x[0] + s_rc2*x[2] + x[3] + x[4]; 65 | float sh2 = x[0]; 66 | float sh3 = -x[3]; 67 | float sh4 = -x[1]; 68 | 69 | // Rotations. R0 and R1 just use the raw matrix columns 70 | float r2x = R[0][0] + R[0][1]; 71 | float r2y = R[1][0] + R[1][1]; 72 | float r2z = R[2][0] + R[2][1]; 73 | 74 | float r3x = R[0][0] + R[0][2]; 75 | float r3y = R[1][0] + R[1][2]; 76 | float r3z = R[2][0] + R[2][2]; 77 | 78 | float r4x = R[0][1] + R[0][2]; 79 | float r4y = R[1][1] + R[1][2]; 80 | float r4z = R[2][1] + R[2][2]; 81 | 82 | // dense matrix multiplication one column at a time 83 | 84 | // column 0 85 | float sh0_x = sh0 * R[0][0]; 86 | float sh0_y = sh0 * R[1][0]; 87 | float d0 = sh0_x * R[1][0]; 88 | float d1 = sh0_y * R[2][0]; 89 | float d2 = sh0 * (R[2][0] * R[2][0] + s_c4_div_c3); 90 | float d3 = sh0_x * R[2][0]; 91 | float d4 = sh0_x * R[0][0] - sh0_y * R[1][0]; 92 | 93 | // column 1 94 | float sh1_x = sh1 * R[0][2]; 95 | float sh1_y = sh1 * R[1][2]; 96 | d0 += sh1_x * R[1][2]; 97 | d1 += sh1_y * R[2][2]; 98 | d2 += sh1 * (R[2][2] * R[2][2] + s_c4_div_c3); 99 | d3 += sh1_x * R[2][2]; 100 | d4 += sh1_x * R[0][2] - sh1_y * R[1][2]; 101 | 102 | // column 2 103 | float sh2_x = sh2 * r2x; 104 | float sh2_y = sh2 * r2y; 105 | d0 += sh2_x * r2y; 106 | d1 += sh2_y * r2z; 107 | d2 += sh2 * (r2z * r2z + s_c4_div_c3_x2); 108 | d3 += sh2_x * r2z; 109 | d4 += sh2_x * r2x - sh2_y * r2y; 110 | 111 | // column 3 112 | float sh3_x = sh3 * r3x; 113 | float sh3_y = sh3 * r3y; 114 | d0 += sh3_x * r3y; 115 | d1 += sh3_y * r3z; 116 | d2 += sh3 * (r3z * r3z + s_c4_div_c3_x2); 117 | d3 += sh3_x * r3z; 118 | d4 += sh3_x * r3x - sh3_y * r3y; 119 | 120 | // column 4 121 | float sh4_x = sh4 * r4x; 122 | float sh4_y = sh4 * r4y; 123 | d0 += sh4_x * r4y; 124 | d1 += sh4_y * r4z; 125 | d2 += sh4 * (r4z * r4z + s_c4_div_c3_x2); 126 | d3 += sh4_x * r4z; 127 | d4 += sh4_x * r4x - sh4_y * r4y; 128 | 129 | // extra multipliers 130 | dst[0] = d0; 131 | dst[1] = -d1; 132 | dst[2] = d2 * s_scale_dst2; 133 | dst[3] = -d3; 134 | dst[4] = d4 * s_scale_dst4; 135 | } 136 | 137 | void main() 138 | { 139 | // normalization 140 | mat3 R = mat3(ModelMat) * RotMat; 141 | VertexOut.ModelNormal = a_Normal; 142 | VertexOut.CameraNormal = (R * a_Normal); 143 | VertexOut.Position = a_Position; 144 | VertexOut.Texcoord = a_TextureCoord; 145 | VertexOut.Tangent = (R * a_Tangent); 146 | VertexOut.Bitangent = (R * a_Bitangent); 147 | float PRT0, PRT1[3], PRT2[5]; 148 | PRT0 = a_PRT1[0]; 149 | PRT1[0] = a_PRT1[1]; 150 | PRT1[1] = a_PRT1[2]; 151 | PRT1[2] = a_PRT2[0]; 152 | PRT2[0] = a_PRT2[1]; 153 | PRT2[1] = a_PRT2[2]; 154 | PRT2[2] = a_PRT3[0]; 155 | PRT2[3] = a_PRT3[1]; 156 | PRT2[4] = a_PRT3[2]; 157 | 158 | OptRotateBand1(PRT1, R, PRT1); 159 | OptRotateBand2(PRT2, R, PRT2); 160 | 161 | VertexOut.PRT1 = vec3(PRT0,PRT1[0],PRT1[1]); 162 | VertexOut.PRT2 = vec3(PRT1[2],PRT2[0],PRT2[1]); 163 | VertexOut.PRT3 = vec3(PRT2[2],PRT2[3],PRT2[4]); 164 | 165 | gl_Position = vec4(a_TextureCoord, 0.0, 1.0) - vec4(0.5, 0.5, 0, 0); 166 | gl_Position[0] *= 2.0; 167 | gl_Position[1] *= 2.0; 168 | } 169 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/quad.fs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoord; 5 | 6 | uniform sampler2D screenTexture; 7 | 8 | void main() 9 | { 10 | FragColor = texture(screenTexture, TexCoord); 11 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/quad.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec2 aPos; 3 | layout (location = 1) in vec2 aTexCoord; 4 | 5 | out vec2 TexCoord; 6 | 7 | void main() 8 | { 9 | gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 10 | TexCoord = aTexCoord; 11 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/sh.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform vec3 SHCoeffs[9]; 3 | 4 | uniform sampler2D AlbedoMap; 5 | 6 | in VertexData { 7 | vec3 Position; 8 | vec3 Normal; 9 | vec2 TextureCoord; 10 | } VertexIn; 11 | 12 | layout (location = 0) out vec4 FragColor; 13 | layout (location = 1) out vec4 FragNormal; 14 | layout (location = 2) out vec4 FragPosition; 15 | layout (location = 3) out vec4 FragAlbedo; 16 | layout (location = 4) out vec4 FragShading; 17 | 18 | vec4 gammaCorrection(vec4 vec, float g) 19 | { 20 | return vec4(pow(vec.x, 1.0/g), pow(vec.y, 1.0/g), pow(vec.z, 1.0/g), vec.w); 21 | } 22 | 23 | vec3 gammaCorrection(vec3 vec, float g) 24 | { 25 | return vec3(pow(vec.x, 1.0/g), pow(vec.y, 1.0/g), pow(vec.z, 1.0/g)); 26 | } 27 | 28 | void evaluateH(vec3 n, out float H[9]) 29 | { 30 | float c1 = 0.429043, c2 = 0.511664, 31 | c3 = 0.743125, c4 = 0.886227, c5 = 0.247708; 32 | 33 | H[0] = c4; 34 | H[1] = 2.0 * c2 * n[1]; 35 | H[2] = 2.0 * c2 * n[2]; 36 | H[3] = 2.0 * c2 * n[0]; 37 | H[4] = 2.0 * c1 * n[0] * n[1]; 38 | H[5] = 2.0 * c1 * n[1] * n[2]; 39 | H[6] = c3 * n[2] * n[2] - c5; 40 | H[7] = 2.0 * c1 * n[2] * n[0]; 41 | H[8] = c1 * (n[0] * n[0] - n[1] * n[1]); 42 | } 43 | 44 | vec3 evaluateLightingModel(vec3 normal) 45 | { 46 | float H[9]; 47 | evaluateH(normal, H); 48 | vec3 res = vec3(0.0); 49 | for (int i = 0; i < 9; i++) { 50 | res += H[i] * SHCoeffs[i]; 51 | } 52 | return res; 53 | } 54 | 55 | 56 | void main() 57 | { 58 | vec2 uv = VertexIn.TextureCoord; 59 | vec3 normal = normalize(VertexIn.Normal); 60 | 61 | FragAlbedo = texture(AlbedoMap, uv); 62 | 63 | vec4 shading = vec4(evaluateLightingModel(normal), 1.0f); 64 | shading = gammaCorrection(shading, 2.2); 65 | 66 | FragColor = clamp(FragAlbedo * shading, 0.0, 1.0); 67 | 68 | FragNormal = vec4(0.5 * (normal + vec3(1.0)), 1.0); 69 | 70 | FragPosition = vec4(VertexIn.Position, 1.0); 71 | 72 | FragShading = vec4(clamp(0.5 * shading.xyz, 0.0, 1.0), 1.0); 73 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/sh.vs: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 a_Position; 3 | layout (location = 1) in vec3 a_Normal; 4 | layout (location = 2) in vec2 a_TextureCoord; 5 | 6 | // All in camera space 7 | out VertexData { 8 | vec3 Position; 9 | vec3 Normal; 10 | vec2 TextureCoord; 11 | } VertexOut; 12 | 13 | uniform mat4 ModelMat; 14 | uniform mat4 PerspMat; 15 | 16 | uniform int UVMode = 0; 17 | 18 | 19 | void main() 20 | { 21 | 22 | VertexOut.Position = a_Position; 23 | VertexOut.TextureCoord = a_TextureCoord; 24 | if(UVMode > 0) { 25 | VertexOut.Normal = a_Normal.xyz; 26 | gl_Position = vec4(a_TextureCoord, 0.0, 1.0) - vec4(0.5, 0.5, 0, 0); 27 | gl_Position[0] *= 2.0; 28 | gl_Position[1] *= 2.0; 29 | } 30 | else { 31 | VertexOut.Normal = (ModelMat * vec4(a_Normal, 0.0)).xyz; 32 | gl_Position = PerspMat * ModelMat * vec4(a_Position, 1.0); 33 | } 34 | } -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/simple.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | out vec4 FragColor; 4 | 5 | void main() 6 | { 7 | FragColor = vec4(1.0, 1.0, 1.0, 1.0); 8 | } 9 | -------------------------------------------------------------------------------- /monoport/lib/render/gl/shaders/simple.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (location = 0) in vec3 Position; 4 | 5 | uniform mat4 ModelMat; 6 | uniform mat4 PerspMat; 7 | 8 | void main() 9 | { 10 | gl_Position = PerspMat * ModelMat * vec4(Position, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | torch==1.4.0 3 | torchvision 4 | Pillow 5 | flask 6 | scikit-image 7 | opencv-python 8 | tqdm 9 | imageio 10 | yacs 11 | pyopengl 12 | imageio-ffmpeg 13 | human-det @ git+https://github.com/Project-Splinter/human_det 14 | human-inst-seg @ git+https://github.com/Project-Splinter/human_inst_seg 15 | implicit-seg @ git+https://github.com/Project-Splinter/ImplicitSegCUDA 16 | streamer-pytorch @ git+https://github.com/Project-Splinter/streamer_pytorch 17 | -------------------------------------------------------------------------------- /scripts/download_model.sh: -------------------------------------------------------------------------------- 1 | mkdir -p data/PIFu/ 2 | cd data/PIFu/ 3 | wget "https://drive.google.com/uc?export=download&id=1zEmVXG2VHy0MMzngcRshB4D8Sr_oLHsm" -O net_G 4 | wget "https://drive.google.com/uc?export=download&id=1V83B6GDIjYMfHdpg-KcCSAPgHxpafHgd" -O net_C 5 | cd ../../ -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name='monoport', 5 | url='', 6 | description='', 7 | version='1.0.0', 8 | author='Ruilong Li', 9 | author_email='ruilongl@usc.edu', 10 | license='MIT License', 11 | packages=setuptools.find_packages(), 12 | ) 13 | 14 | --------------------------------------------------------------------------------