├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── TMGA │ ├── img │ │ ├── orig │ │ │ ├── Building-left.JPG │ │ │ ├── Building-right.JPG │ │ │ ├── Grass-left.JPG │ │ │ ├── Grass-right.JPG │ │ │ ├── Lake-left.JPG │ │ │ ├── Lake-right.JPG │ │ │ ├── NewHarbor-left.JPG │ │ │ ├── NewHarbor-right.JPG │ │ │ ├── Palace-left.JPG │ │ │ ├── Palace-right.JPG │ │ │ ├── Road-left.JPG │ │ │ ├── Road-right.JPG │ │ │ ├── School-left.JPG │ │ │ ├── School-right.JPG │ │ │ ├── Tree-left.JPG │ │ │ └── Tree-right.JPG │ │ └── result │ │ │ ├── Building-ORB-RANSAC.jpg │ │ │ ├── Building-ORB-TMGA.jpg │ │ │ ├── Building-SIFT-RANSAC.jpg │ │ │ ├── Building-SIFT-TMGA.jpg │ │ │ ├── Grass-ORB-RANSAC.jpg │ │ │ ├── Grass-ORB-TMGA.jpg │ │ │ ├── Grass-SIFT-RANSAC.jpg │ │ │ ├── Grass-SIFT-TMGA.jpg │ │ │ ├── Lake-ORB-RANSAC.jpg │ │ │ ├── Lake-ORB-TMGA.jpg │ │ │ ├── Lake-SIFT-RANSAC.jpg │ │ │ ├── Lake-SIFT-TMGA.jpg │ │ │ ├── NewHarbor-ORB-RANSAC.jpg │ │ │ ├── NewHarbor-ORB-TMGA.jpg │ │ │ ├── NewHarbor-SIFT-RANSAC.jpg │ │ │ ├── NewHarbor-SIFT-TMGA.jpg │ │ │ ├── Palace-ORB-RANSAC.jpg │ │ │ ├── Palace-ORB-TMGA.jpg │ │ │ ├── Palace-SIFT-RANSAC.jpg │ │ │ ├── Palace-SIFT-TMGA.jpg │ │ │ ├── Road-ORB-RANSAC.jpg │ │ │ ├── Road-ORB-TMGA.jpg │ │ │ ├── Road-SIFT-RANSAC.jpg │ │ │ ├── Road-SIFT-TMGA.jpg │ │ │ ├── School-ORB-RANSAC.jpg │ │ │ ├── School-ORB-TMGA.jpg │ │ │ ├── School-SIFT-RANSAC.jpg │ │ │ ├── School-SIFT-TMGA.jpg │ │ │ ├── Tree-ORB-RANSAC.jpg │ │ │ ├── Tree-ORB-TMGA.jpg │ │ │ ├── Tree-SIFT-RANSAC.jpg │ │ │ └── Tree-SIFT-TMGA.jpg │ └── index.md ├── orb解析 │ ├── img │ │ ├── BRIEF点对.png │ │ ├── descriptor.png │ │ ├── fast角点.png │ │ ├── keypoints.png │ │ └── runtime.png │ └── orb解析.md └── ransac解析 │ └── ransac解析.md ├── example ├── 19-SIFT.jpg ├── 19-left.JPG ├── 19-right.JPG ├── 3-left.JPG ├── 3-right.JPG └── 3-surf.jpg └── py ├── 605thesis.txt ├── README.html ├── README.md ├── batch_stich.py ├── blend.py ├── k_means.py ├── main.py ├── ransac.py ├── ransac_evolve.txt ├── shrink_to_25.py ├── stitch.py ├── thesis-evolution.txt ├── thesis.py └── thesis.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | /docs/**/*.jpg filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | *.pdf 4 | .history/ 5 | .vscode/ 6 | resource/ 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Environments 92 | .env 93 | .venv 94 | env/ 95 | venv/ 96 | ENV/ 97 | env.bak/ 98 | venv.bak/ 99 | 100 | # Spyder project settings 101 | .spyderproject 102 | .spyproject 103 | 104 | # Rope project settings 105 | .ropeproject 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | *.bak 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lingfeng Zhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 图像拼接 2 | 3 | ## 介绍 4 | 5 | 此Repo是有关图像拼接的毕业设计的代码实现 6 | 7 | ## 进度 8 | 9 | ### 2018年3月24日: 10 | 11 | 利用Python的OpenCV库实现了简单的图像拼接,示例: 12 | 13 | | 左图 | 右图 | 结果 | 14 | | :--: | :--: | :--: | 15 | | ![3-left](example/3-left.JPG) | ![3-right](example/3-right.JPG) | ![3-surf](example/3-surf.jpg) | 16 | | ![19-left](example/19-left.JPG) | ![19-right](example/19-right.JPG) | ![19-SIFT](example/19-SIFT.jpg) | 17 | 18 | ### 2018年4月1日: 19 | 20 | 阅读OpenCV的ORB代码,并进行改动并编译。[67d825](../../commit/67d825b4d58d8a625effdb2d2688caaee8f32c34) 21 | 22 | 关于ORB的分析见[orb解析](./doc/orb解析/orb解析.md)。 23 | 24 | 25 | 26 | ### 2018年4月11日: 27 | 28 | 利用K-means算法进行特征点匹配的筛选。[fb4d88](../../commit/fb4d88449815402e2f2fdd0692478866eb20a1f0) 29 | 30 | 结果:不理想 31 | 32 | 33 | 34 | ### 2018年4月12日: 35 | 36 | 局部变换矩阵,在整体变换矩阵的基础上对与偏心过大的点进行单独的变换。[ca567e](../../commit/ca567e5bd39e4dd077962cfe29c08a43bc17d392) 37 | 38 | 结果:不理想 39 | 40 | 41 | 42 | ### 2018年4月24日: 43 | 44 | 实现python版本的RANSAC算法,并在其基础上对其进行改进。e36663 45 | 46 | 结果:实现了和OpenCV的cv2.findHomography函数功能基本一致的结果。 47 | 48 | 49 | 50 | ### 2018年4月26日: 51 | 52 | 利用遗传算法对RANSAC进行改进。ce3a8b 53 | 54 | 结果:能初步达成效果,但最终效果不及RANSAC 55 | 56 | 57 | 58 | ### 2018年4月27日: 59 | 60 | 试图使用变换矩阵作为遗传算法的基因。af87e9 61 | 62 | 结果:没有明显提高。 63 | 64 | 65 | 66 | ## TODO 67 | 68 | * 改进ORB的旋转不变性 69 | * 改进ORB的匹配或者特征点选取方式以使其基金SIFT的拼接效果 70 | * 在RANSAC算法的基础上改进特征点选择方式使得ORB的特征选取更有代表性 -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Building-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:61cc7a42640cbf48eb50316841379a78b2097bb2df2d45edd289982bdd8bce9c 3 | size 470374 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Building-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1575352e003fc5086245412671de9fe5ab53b7fea04dff24454deda5d886f61d 3 | size 469404 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Grass-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2b1ed8eeca5894c51df20f55af335df1d9c42a22b8c94c3d5ea17f2b710fbcdb 3 | size 811357 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Grass-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7f2d3e5938e375b64d41d94ee36b4c78acf6bc2c8b8659219f2c325951e923de 3 | size 707020 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Lake-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0005fead303d15af2fc95a7cfc2a11d19e9ebc654c482ed8d43630a53c2617ee 3 | size 302690 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Lake-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0378e0662d5fcac98152a57d2951c77df9fb119c54c93443544904d3e42ce286 3 | size 290409 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/NewHarbor-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f2a7a2c64fbabec5cd42a5242c79deb2561afebb34fbe5b04f99e30cd5cec7f2 3 | size 420599 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/NewHarbor-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4855d9c03e0ff8f7572499d906a442d04c1221ee0b56f02935a1c587a61caa3c 3 | size 634622 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Palace-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:38660eb8acaf8d0ce9522a557becaa10bc77a69fbf34ee28735364abe4b46613 3 | size 705465 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Palace-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:743f44aff936298ce6b7e4d6fc7bdff84a65be0db75d1aa472c81283b720e29e 3 | size 529108 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Road-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6428d6278c645cc95fb4ed018a856cb406a8cae309708aec4b5f30c122b67dc1 3 | size 329427 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Road-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:479156ca86056ed674cf687316c91caa18ccca88cc349b4b0ecbe7a814283d1b 3 | size 349638 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/School-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3c370a963839248f60425cdee48378377d64156d87944616a34668a9ff311052 3 | size 844816 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/School-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f6035049b4ce5e7406744901fa03e2d783712127ccec02381d481b1022ead5fc 3 | size 619769 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Tree-left.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:919b36f5aab1c42cb420a0063ba76b076302884839564f351117bbb61075534b 3 | size 161256 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/orig/Tree-right.JPG: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:17929914f4a1ae248adea30476af23a34a796e9ddd76aa34790b077a14ec5515 3 | size 90832 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Building-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9d7fa0e8dafab5d501c3efb1e28e4a6a31cd4fe40a20c1adb9d4d815c8b007a6 3 | size 1199910 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Building-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:94a7a42d9ea251eaf388232fa131e5bda6da69c479ef1dd25f1082d59450f057 3 | size 1133961 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Building-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7d325494124f866c82d2393946a985ac7a569e34c881d36738baa9653417d31b 3 | size 1135459 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Building-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:76e0603d1a46233546a20ef076e4a0f52367ba98ce850c6c85417d594b196b95 3 | size 1131752 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Grass-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:34f27b41f5727681fcff488300c7ff18a2da936e834b4d954cb92f149b6d7230 3 | size 1759378 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Grass-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:aaecfe8b72ad6f864f3847a0f5b81179b24fb835324f61909b853ad7338a808d 3 | size 1746533 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Grass-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:28a3c7b4994f3d52e14ad38456761278e03118aebfad37730d834adad85b2c75 3 | size 1780955 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Grass-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ae9028a79c533bb7ef54f12a2282596a0aadac17ed58a9f03f13877455a14b67 3 | size 1809490 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Lake-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:95e2772743000593f6e475bc2633f118cbcd5aa6306f76ce998af5d81cb6ad23 3 | size 340264 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Lake-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:99d3b7df634cd61c1e497c56e7096849fd4964c4261f7aa7279658d12ca00d12 3 | size 350597 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Lake-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5d188861087f96becc378d4920a3b6f3fafd35605f198a874bd126f1be1a83ec 3 | size 392022 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Lake-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2ce2088539650aebac778e73ab34e8ec02ea03e72f4f75eed697e8a7d0d29f9a 3 | size 412682 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/NewHarbor-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:948df6061e1095c4363d77dda4f38363fc33ccd33b608fb419148b35f4a351b3 3 | size 2800593 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/NewHarbor-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:41fb93399e2e621c06cd948461041caadd1c902a0a47ff898c72d29628ba3e5c 3 | size 2715160 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/NewHarbor-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bfe23c57024aafd3e84aeddd6db91e5b08c3dd8d7cd3d94b9afcc6175810f5fa 3 | size 2382573 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/NewHarbor-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4396ed63b70075484cd51a8d9c8a1c7b76bf95f8b936fc144ba1ebe4b625ac18 3 | size 2270788 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Palace-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b65234f408142956da6655c43c390c29fe77bfd87075ad56ce57febfb83e713d 3 | size 1742121 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Palace-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9639a9506803693085646e9e7d8e2db6955803126010c42717096ae4eafb645f 3 | size 1684878 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Palace-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f973901058452633b2c70f2a2d865c4db27250a28a262c017106a82cad8658a1 3 | size 1680234 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Palace-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4743ce6c0311ea9bc8e7876524855ccfa2c2c542bcff15adb064dbdeff6e1984 3 | size 1616845 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Road-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:617acf05a7e51aef9fa175f064322718a93dd74d1fac3ef388e5b5483d2ffa79 3 | size 981220 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Road-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:49f88ad1ee3a3f2c52ff2a65270304dc4aec9bc1d04fda55a846927d9747d7e4 3 | size 495325 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Road-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ce6c45ae985d9b597c7c6e0836ddecdeeba10be8a52ab9f291cd3287d3336607 3 | size 496293 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Road-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5bfd2eb38a2802cd2701dfef705ac446200b2f41ea28f7d44ca5dd0110723216 3 | size 519298 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/School-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f0039faeef276737813a528b514156514a5d3ad5fe845c0c350b8a443f787b57 3 | size 1699218 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/School-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2d0503a879657176410dda59b7c0d670db0cef86f985617427846d69cde74667 3 | size 2056931 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/School-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:603ddcfe271d9498ca4b1e611843ce1ae20dc5469602ab866a3fda81227cf8e2 3 | size 1649646 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/School-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:8128791acd9bcf034a6f7f01c73efd787818df54ad4bae3f6cfc1cba0e1603e7 3 | size 1618155 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Tree-ORB-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e32b15d0fc4714d07b85af74b3d22462838b026f494e469e642fe309010c58af 3 | size 486971 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Tree-ORB-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7aa4a3c914fd2a8fad2d16d58f348360b5a7a4cad93f2d0cc473bc927f13a534 3 | size 494127 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Tree-SIFT-RANSAC.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:389db208a21fb75db133bc98e9d65e322976902cb3d425a2d2362c66c10a8695 3 | size 497296 4 | -------------------------------------------------------------------------------- /docs/TMGA/img/result/Tree-SIFT-TMGA.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:24edc0c02a0638917347f232c2a7e45bfae9f1aac718e93d85212b30c5b23f48 3 | size 498180 4 | -------------------------------------------------------------------------------- /docs/TMGA/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TMGA 3 | --- 4 | 5 | # A Method of Using Genetic Algorithm in Image Stitching 6 | 7 | > Lingfeng Zhao, Yujie Huang,Minge Jing,Xiaoyang Zeng, Yibo Fan 8 | 9 | > State Key Laboratory of ASIC & System, Fudan University, Shanghai, China 10 | 11 | ## Abstract 12 | 13 | Image stitching is an important part of computer vision, and how to do it more efficiently with highquality is a heated topic. In this paper, the authors propose a new method called TMGA for image stitching to get an improved performance in calculating Transform Matrix by using Genetic Algorithm. The proposed TMGA not only counts the number of interior points, but also takes standard error and degree of dispersion into consideration compared the traditional methods. The results demonstrate that the proposed algorithm can gain a high-quality transform matrix and improves the result of the stitching. 14 | 15 | ## Images 16 | 17 | ### Original Image 18 | 19 | | Image name | Image1 | Image2 | 20 | | ---------- | ------------------------------------------- | ---------------------------------------------- | 21 | | Road | ![Road-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Road-left.JPG) | ![Road-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Road-right.JPG) | 22 | | Lake | ![Lake-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Lake-left.JPG) | ![Lake-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Lake-right.JPG) | 23 | | Tree | ![Tree-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Tree-left.JPG) | ![Tree-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Tree-right.JPG) | 24 | | Building | ![Building-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Building-left.JPG) | ![Building-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Building-right.JPG) | 25 | | School | ![School-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/School-left.JPG) | ![School-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/School-right.JPG) | 26 | | Grass | ![Grass-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Grass-left.JPG) | ![Grass-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Grass-right.JPG) | 27 | | Palace | ![Palace-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Palace-left.JPG) | ![Palace-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/Palace-right.JPG) | 28 | | NewHarbor | ![NewHarbor-left](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/NewHarbor-left.JPG) | ![NewHarbor-right](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/orig/NewHarbor-right.JPG) | 29 | 30 | ### Stitching Result 31 | 32 | | Image name | ORB with RANSAC | ORB with TMGA | SIFT with RANSAC | SIFT with TMGA | 33 | | ---------- | ------------------------------------------- | ---------------------------------------------- | ---------- | ---------- | 34 | | Road | ![Road-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Road-ORB-RANSAC.jpg) | ![Road-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Road-ORB-TMGA.jpg) | ![Road-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Road-SIFT-RANSAC.jpg) | ![Road-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Road-SIFT-TMGA.jpg) | 35 | | Lake | ![Lake-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Lake-ORB-RANSAC.jpg) | ![Lake-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Lake-ORB-TMGA.jpg) | ![Lake-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Lake-SIFT-RANSAC.jpg) | ![Lake-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Lake-SIFT-TMGA.jpg) | 36 | | Tree | ![Tree-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Tree-ORB-RANSAC.jpg) | ![Tree-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Tree-ORB-TMGA.jpg) | ![Tree-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Tree-SIFT-RANSAC.jpg) | ![Tree-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Tree-SIFT-TMGA.jpg) | 37 | | Building | ![Building-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Building-ORB-RANSAC.jpg) | ![Building-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Building-ORB-TMGA.jpg) | ![Building-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Building-SIFT-RANSAC.jpg) | ![Building-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Building-SIFT-TMGA.jpg) | 38 | | School | ![School-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/School-ORB-RANSAC.jpg) | ![School-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/School-ORB-TMGA.jpg) | ![School-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/School-SIFT-RANSAC.jpg) | ![School-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/School-SIFT-TMGA.jpg) | 39 | | Grass | ![Grass-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Grass-ORB-RANSAC.jpg) | ![Grass-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Grass-ORB-TMGA.jpg) | ![Grass-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Grass-SIFT-RANSAC.jpg) | ![Grass-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Grass-SIFT-TMGA.jpg) | 40 | | Palace | ![Palace-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Palace-ORB-RANSAC.jpg) | ![Palace-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Palace-ORB-TMGA.jpg) | ![Palace-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Palace-SIFT-RANSAC.jpg) | ![Palace-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/Palace-SIFT-TMGA.jpg) | 41 | | NewHarbor | ![NewHarbor-ORB-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/NewHarbor-ORB-RANSAC.jpg) | ![NewHarbor-ORB-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/NewHarbor-ORB-TMGA.jpg) | ![NewHarbor-SIFT-RANSAC](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/NewHarbor-SIFT-RANSAC.jpg) | ![NewHarbor-SIFT-TMGA](https://github.com/zhaobenx/Image-stitcher/raw/master/docs/TMGA/img/result/NewHarbor-SIFT-TMGA.jpg) | 42 | 43 | -------------------------------------------------------------------------------- /docs/orb解析/img/BRIEF点对.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/docs/orb解析/img/BRIEF点对.png -------------------------------------------------------------------------------- /docs/orb解析/img/descriptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/docs/orb解析/img/descriptor.png -------------------------------------------------------------------------------- /docs/orb解析/img/fast角点.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/docs/orb解析/img/fast角点.png -------------------------------------------------------------------------------- /docs/orb解析/img/keypoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/docs/orb解析/img/keypoints.png -------------------------------------------------------------------------------- /docs/orb解析/img/runtime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/docs/orb解析/img/runtime.png -------------------------------------------------------------------------------- /docs/orb解析/orb解析.md: -------------------------------------------------------------------------------- 1 | --- 2 | typora-copy-images-to: img 3 | --- 4 | 5 | # ORB算法与源码解析 6 | 7 | 来自opencv 8 | 9 | ## 算法介绍 10 | 11 | 是ORB特征是将**FAST**特征点的检测方法与**BRIEF**特征描述子结合起来,并在它们原来的基础上做了改进与优化。 12 | 13 | 1. 利用**FAST**特征点检测的方法来检测特征点。 14 | 2. 利用**Harris**角点的度量方法,从**FAST**特征点从挑选出Harris角点响应值最大的$N$个特征点。 15 | 3. 解决局部不变性(尺度不变性和旋转不变性),即选择从不同高斯金字塔进行检测和对特征点添加角度特征。 16 | 4. 为特征点增加特征描述即描述子。 17 | 18 | 19 | ### FAST检测 20 | 21 | **FAST**特征点的检测方式即寻找一个像素点周围半径为3中的角点,如图示: 22 | 23 | ![fast角点](img/fast角点.png) 24 | 25 | 26 | 27 | ### Harris评判 28 | 29 | **Harris**方法是一种特征点评判方式。 30 | 31 | 其中**Harris**角点的响应函数定义为: 32 | 33 | 34 | $$ 35 | R=det \boldsymbol{M} - \alpha(trace\boldsymbol{M})^2 36 | $$ 37 | 38 | (其中 $det$指的是矩阵行列式的值;值得指的是矩阵的迹,即对角线元素之和;$M$指的是像素矩阵) 39 | 40 | ### 旋转不变性 41 | 42 | 尺度不变性的解决通过构建不同高斯金字塔(即对图像进行缩放)分别进行特征检测。而旋转不变性则是为每个特征点增加角度特征,角度的计算公式即: 43 | 44 | $$ 45 | m_{pq} = \sum_{x,y}x^py^qI(x,y) 46 | $$ 47 | 48 | $$ 49 | C = \left(\frac{m_{10}}{m_{00}},\frac{m_{01}}{m_{00}}\right) 50 | $$ 51 | 52 | $$ 53 | \theta = arctan(m_{01},m_{10}) 54 | $$ 55 | 56 | 其中$M_{pq}$的定义是在以特征点为中心的一个园内,对所有像素值(以灰度为单位)以x,y坐标为权进行的加权和。即算出像素的“重心”。从而可利用*公式(4)*来计算出特征点的方向。 57 | 58 | ### 描述子计算 59 | 60 | 描述子使用**BRIEF**方法来进行的特征描述方式,其原理即在特征点一定邻域内按照一定的股则选取一些点对,并对这些点对的灰度值进行比较,将结果的布尔值储存下来,最终结果为长度为128/256/512长度的比特串。 61 | 62 | 下图为一些点对的选择方法: 63 | 64 | ​ ![BRIEF点对](img/BRIEF点对.png) 65 | 66 | (上图分别为:1. x,y方向平均分布采样 2. x,y均服从Gauss(0,S^2/25)各向同性采样 3. x服从Gauss(0,S^2/25),y服从Gauss(0,S^2/100)采样 4. x,y从网格中随机获取 5. x一直在(0,0),y从网格中随机选取) 67 | 68 | 为了解决旋转不变性,这里选择了**Steer BREIF**,即在计算描述值时考虑到旋转后的结果,其公式为: 69 | 70 | $$ 71 | S_{\theta} = R_{\theta}S 72 | $$ 73 | 74 | 其中的$S$为选取的点对($x_n$与$y_n$是一个点对两个点的灰度值),$R_{\theta}$为旋转不变性(公式(4))求得的旋转角度。 75 | 76 | $$ 77 | S =\begin{pmatrix}x_1&x_2&\cdots&x_{2n} \\ y_1&y_2&\cdots&y_{2n}\end{pmatrix} 78 | $$ 79 | 80 | $$ 81 | R_{\theta} = \begin{bmatrix}cos\theta & sin\theta \\ –sin\theta &cos\theta\end{bmatrix} 82 | $$ 83 | 84 | ### 匹配 85 | 86 | 由以上几部即完成了ORB特征的提取,提取后将输出两个值,分别为关键点和描述值,其中关键点的结构如下: 87 | 88 | ```c++ 89 | class KeyPoint 90 | { 91 | public: 92 | Point2f pt; //!< 特征点坐标 93 | float size; //!< 特征点描述区域大小 94 | float angle; //!< 特征点朝向,取[0, 360),取-1代表不可用 95 | float response; //!< 被选取的强度,可用于排序 96 | int octave; //!< 描述此点属于第几层金字塔 97 | int class_id; //!< 分类id,如果特征点需要被分类 98 | }; 99 | ``` 100 | 101 | 而描述值则为Mat类型,用于储存点对。 102 | 103 | 最终匹配时跟剧描述值之间的**汉明距离**(即bit相同的个数)来进行比较。 104 | 105 | 106 | 107 | ## 代码解析 108 | 109 | 代码实现于`opencv\modules\features2d\src\orb.cpp`,采用OpenCV版本号为3.4.1。 110 | 111 | ### FAST检测 112 | 113 | 代码833-837行,利用Fast检测方法,检测出若干潜在关键点 114 | 115 | ```c++ 116 | // Detect FAST features, 20 is a good threshold 117 | { 118 | Ptr fd = FastFeatureDetector::create(fastThreshold, true); 119 | fd->detect(img, keypoints, mask); 120 | } 121 | ``` 122 | 123 | ### Harris评价 124 | 125 | 888行, 126 | 127 | ```c++ 128 | HarrisResponses(imagePyramid, layerInfo, allKeypoints, 7, HARRIS_K); 129 | ``` 130 | 131 | 而其具体实现在本文件131-172行 132 | 133 | ```c++ 134 | static void 135 | HarrisResponses(const Mat& img, const std::vector& layerinfo, 136 | std::vector& pts, int blockSize, float harris_k) 137 | { 138 | CV_Assert( img.type() == CV_8UC1 && blockSize*blockSize <= 2048 ); 139 | 140 | size_t ptidx, ptsize = pts.size(); 141 | 142 | const uchar* ptr00 = img.ptr(); 143 | int step = (int)(img.step/img.elemSize1()); 144 | int r = blockSize/2; 145 | 146 | float scale = 1.f/((1 << 2) * blockSize * 255.f); 147 | float scale_sq_sq = scale * scale * scale * scale; 148 | 149 | AutoBuffer ofsbuf(blockSize*blockSize); 150 | int* ofs = ofsbuf; 151 | for( int i = 0; i < blockSize; i++ ) 152 | for( int j = 0; j < blockSize; j++ ) 153 | ofs[i*blockSize + j] = (int)(i*step + j); 154 | 155 | for( ptidx = 0; ptidx < ptsize; ptidx++ ) 156 | { 157 | int x0 = cvRound(pts[ptidx].pt.x); 158 | int y0 = cvRound(pts[ptidx].pt.y); 159 | int z = pts[ptidx].octave; 160 | 161 | const uchar* ptr0 = ptr00 + (y0 - r + layerinfo[z].y)*step + x0 - r + layerinfo[z].x; 162 | int a = 0, b = 0, c = 0; 163 | 164 | for( int k = 0; k < blockSize*blockSize; k++ ) 165 | { 166 | const uchar* ptr = ptr0 + ofs[k]; 167 | int Ix = (ptr[1] - ptr[-1])*2 + (ptr[-step+1] - ptr[-step-1]) + (ptr[step+1] - ptr[step-1]); 168 | int Iy = (ptr[step] - ptr[-step])*2 + (ptr[step-1] - ptr[-step-1]) + (ptr[step+1] - ptr[-step+1]); 169 | a += Ix*Ix; 170 | b += Iy*Iy; 171 | c += Ix*Iy; 172 | } 173 | pts[ptidx].response = ((float)a * b - (float)c * c - 174 | harris_k * ((float)a + b) * ((float)a + b))*scale_sq_sq; 175 | } 176 | } 177 | ``` 178 | 179 | 代码最后几行即为计算我们公式(1)的具体计算,其中用到了这样的公式,即在矩阵$\boldsymbol{M}=\begin{bmatrix}A&C\\C&B\end{bmatrix}$中: 180 | 181 | $$ 182 | det\boldsymbol{M} = \lambda_1\lambda_2=AB-C^2 183 | $$ 184 | 185 | $$ 186 | trace\boldsymbol{M}=\lambda_2+\lambda_2=A+B 187 | $$ 188 | 189 | ### 旋转不变性 190 | 191 | 角度的计算有下代码实现,实现在176-210行。 192 | 193 | 194 | ```cpp 195 | static void ICAngles(const Mat& img, const std::vector& layerinfo, 196 | std::vector& pts, const std::vector & u_max, int half_k) 197 | { 198 | int step = (int)img.step1(); 199 | size_t ptidx, ptsize = pts.size(); 200 | 201 | for( ptidx = 0; ptidx < ptsize; ptidx++ ) 202 | { 203 | const Rect& layer = layerinfo[pts[ptidx].octave]; 204 | const uchar* center = &img.at(cvRound(pts[ptidx].pt.y) + layer.y, cvRound(pts[ptidx].pt.x) + layer.x); 205 | 206 | int m_01 = 0, m_10 = 0; 207 | 208 | // Treat the center line differently, v=0 209 | for (int u = -half_k; u <= half_k; ++u) 210 | m_10 += u * center[u]; 211 | 212 | // Go line by line in the circular patch 213 | for (int v = 1; v <= half_k; ++v) 214 | { 215 | // Proceed over the two lines 216 | int v_sum = 0; 217 | int d = u_max[v]; 218 | for (int u = -d; u <= d; ++u) 219 | { 220 | int val_plus = center[u + v*step], val_minus = center[u - v*step]; 221 | v_sum += (val_plus - val_minus); 222 | m_10 += u * (val_plus + val_minus); 223 | } 224 | m_01 += v * v_sum; 225 | } 226 | 227 | pts[ptidx].angle = fastAtan2((float)m_01, (float)m_10); 228 | } 229 | } 230 | ``` 231 | 232 | ### 计算描述子 233 | 234 | 实现在214行到415行,摘取重要部分: 235 | 236 | ```c++ 237 | static void 238 | computeOrbDescriptors( const Mat& imagePyramid, const std::vector& layerInfo, 239 | const std::vector& layerScale, std::vector& keypoints, 240 | Mat& descriptors, const std::vector& _pattern, int dsize, int wta_k ) 241 | { 242 | // ... 243 | //矩阵相乘 244 | #define GET_VALUE(idx) \ 245 | (x = pattern[idx].x*a - pattern[idx].y*b, \ 246 | y = pattern[idx].x*b + pattern[idx].y*a, \ 247 | ix = cvRound(x), \ 248 | iy = cvRound(y), \ 249 | *(center + iy*step + ix) ) 250 | //... 251 | //实现公式(5) 252 | for (i = 0; i < dsize; ++i, pattern += 16) 253 | { 254 | int t0, t1, val; 255 | t0 = GET_VALUE(0); t1 = GET_VALUE(1); 256 | val = t0 < t1; 257 | t0 = GET_VALUE(2); t1 = GET_VALUE(3); 258 | val |= (t0 < t1) << 1; 259 | t0 = GET_VALUE(4); t1 = GET_VALUE(5); 260 | val |= (t0 < t1) << 2; 261 | t0 = GET_VALUE(6); t1 = GET_VALUE(7); 262 | val |= (t0 < t1) << 3; 263 | t0 = GET_VALUE(8); t1 = GET_VALUE(9); 264 | val |= (t0 < t1) << 4; 265 | t0 = GET_VALUE(10); t1 = GET_VALUE(11); 266 | val |= (t0 < t1) << 5; 267 | t0 = GET_VALUE(12); t1 = GET_VALUE(13); 268 | val |= (t0 < t1) << 6; 269 | t0 = GET_VALUE(14); t1 = GET_VALUE(15); 270 | val |= (t0 < t1) << 7; 271 | 272 | desc[i] = (uchar)val; 273 | } 274 | //... 275 | } 276 | ``` 277 | 278 | ## 代码运行时数据 279 | 280 | 在运行时,选取的特征描述子是256bit的,即32个`unsigned char`的大小,可以由下图的运行数据大小得知: 281 | 282 | 283 | 284 | 运行后的数据: 285 | 286 | ![runtime](img/runtime.png) 287 | 288 | 部分关键点及其数据: 289 | 290 | ![keypoints](img/keypoints.png) 291 | 292 | 对应的描述子: 293 | 294 | ![descriptor](img/descriptor.png) 295 | 296 | 297 | 298 | (三十二个为一个描述子,图中并未截全一个描述子)s -------------------------------------------------------------------------------- /docs/ransac解析/ransac解析.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | # RANSAC算法与源码解析 4 | 5 | > 来自OpenCV 6 | 7 | ## 算法介绍 8 | 9 | OpenCV中滤除误匹配对采用RANSAC算法寻找一个最佳单应性矩阵H,矩阵大小为3×3。RANSAC目的是找到最优的参数矩阵使得满足该矩阵的数据点个数最多,通常令$h_{33}$=1来归一化矩阵。由于单应性矩阵有8个未知参数,至少需要8个线性方程求解,对应到点位置信息上,一组点对可以列出两个方程,则至少包含4组匹配点对。 10 | 11 | 12 | 13 | 具体方法: 14 | 15 | 1. 随机从数据集中随机抽出4个样本数据 (此4个样本之间不能共线),计算出变换矩阵H,记为模型M; 16 | 2. 计算数据集中所有数据与模型M的投影误差,若误差小于阈值,加入内点集 I ; 17 | 3. 如果当前内点集 $I$ 元素个数大于最优内点集 $I_{best} $, 则更新 $I_{best} = I$,同时更新迭代次数k ; 18 | 4. 如果迭代次数大于k,则退出 ; 否则迭代次数加1,并重复上述步骤; 19 | 20 | 注:迭代次数k在不大于最大迭代次数的情况下,是在不断更新而不是固定的; 21 | 22 | ​ $k = \frac{log(1-p)}{log(1-w^m)}$ 23 | 24 | 其中,p为置信度,一般取0.995;w为"内点"的比例 ; m为计算模型所需要的最少样本数=4; 25 | 26 | 其中代价函数为 : 27 | $$ 28 | \sum ^{n}_{i=1}{[(x_i'-\frac{h_{11}x_i+h_{12}y_i+h_{13}}{h_{31}x_i+h_{32}y_i+h_{33}})^2 29 | + (y_i'-\frac{h_{w1}x_i+h_{22}y_i+h_{23}}{h_{31}x_i+h_{32}y_i+h_{33}})^2 30 | ]} 31 | $$ 32 | 33 | ### 代码赏析 34 | 35 | ### 变换矩阵的求得 36 | 37 | 本项目中所用的变换矩阵函数,代码实现于`opencv/modules/calib3d/src/fundam.cpp`中的 38 | 39 | ```c++ 40 | cv::Mat cv::findHomography( InputArray _points1, InputArray _points2, 41 | int method, double ransacReprojThreshold, OutputArray _mask, 42 | const int maxIters, const double confidence) 43 | ``` 44 | 45 | 从250行至433行。 46 | 47 | ### RANSAC筛选 48 | 49 | 其中调用的`createRANSACPointSetRegistrator`函数来进行RANSAC挑选,其实现于`opencv/modules/calib3d/src/ptsetreg.cpp`中的76至268行的`class RANSACPointSetRegistrator : public PointSetRegistrator`类中。 50 | 51 | 此类有四个方法,分别是: 52 | 53 | `int findInliers( const Mat& m1, const Mat& m2, const Mat& model, Mat& err, Mat& mask, double thresh ) const` 检测给定点集、变换矩阵和阈值下内点的个数 54 | 55 | `bool getSubset( const Mat& m1, const Mat& m2,Mat& ms1, Mat& ms2, RNG& rng,int maxAttempts=1000 ) const`返回是否有子集 56 | 57 | `bool run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const CV_OVERRIDE` 58 | 59 | 主函数 60 | 61 | `void setCallback(const Ptr& _cb) CV_OVERRIDE { cb = _cb; }` 62 | 63 | 其主要逻辑在于164行-258行的: 64 | 65 | ```c++ 66 | bool run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const CV_OVERRIDE 67 | ``` 68 | 69 | 关键代码: 70 | 71 | ```c++ 72 | for( iter = 0; iter < niters; iter++ )//>迭代 73 | { 74 | int i, nmodels; 75 | if( count > modelPoints ) 76 | { 77 | bool found = getSubset( m1, m2, ms1, ms2, rng, 10000 );//> rng为随机数, 10000为最大尝试次数, 即任选四组点对进行下一步计算 78 | if( !found ) 79 | { 80 | if( iter == 0 ) 81 | return false;//> 失败 点数不够 82 | break; 83 | } 84 | } 85 | 86 | nmodels = cb->runKernel( ms1, ms2, model );//> 计算新的变换矩阵 87 | if( nmodels <= 0 ) 88 | continue; 89 | CV_Assert( model.rows % nmodels == 0 ); 90 | Size modelSize(model.cols, model.rows/nmodels); 91 | 92 | for( i = 0; i < nmodels; i++ ) 93 | { 94 | Mat model_i = model.rowRange( i*modelSize.height, (i+1)*modelSize.height ); 95 | int goodCount = findInliers( m1, m2, model_i, err, mask, threshold );//> 计算内点,err变量即为变量的偏差,返回为最佳匹配值 96 | 97 | if( goodCount > MAX(maxGoodCount, modelPoints-1) )//> 如果新得出的优秀匹配点比之前的多 98 | { 99 | std::swap(mask, bestMask);//> 更新内点集,通过mask来标示 100 | model_i.copyTo(bestModel);//> 新变换矩阵 101 | maxGoodCount = goodCount; 102 | niters = RANSACUpdateNumIters( confidence, (double)(count - goodCount)/count, modelPoints, niters );//> 更新阈值k 103 | } 104 | } 105 | } 106 | 107 | ``` 108 | 109 | 105-162行,用以获取随机的四个点对: 110 | 111 | ```c++ 112 | bool getSubset( const Mat& m1, const Mat& m2, 113 | Mat& ms1, Mat& ms2, RNG& rng, 114 | int maxAttempts=1000 ) const 115 | { 116 | cv::AutoBuffer _idx(modelPoints); 117 | int* idx = _idx; 118 | int i = 0, j, k, iters = 0; 119 | int d1 = m1.channels() > 1 ? m1.channels() : m1.cols; 120 | int d2 = m2.channels() > 1 ? m2.channels() : m2.cols; 121 | int esz1 = (int)m1.elemSize1()*d1, esz2 = (int)m2.elemSize1()*d2; 122 | int count = m1.checkVector(d1), count2 = m2.checkVector(d2); 123 | const int *m1ptr = m1.ptr(), *m2ptr = m2.ptr(); 124 | 125 | ms1.create(modelPoints, 1, CV_MAKETYPE(m1.depth(), d1)); 126 | ms2.create(modelPoints, 1, CV_MAKETYPE(m2.depth(), d2)); 127 | 128 | int *ms1ptr = ms1.ptr(), *ms2ptr = ms2.ptr(); 129 | 130 | CV_Assert( count >= modelPoints && count == count2 ); 131 | CV_Assert( (esz1 % sizeof(int)) == 0 && (esz2 % sizeof(int)) == 0 ); 132 | esz1 /= sizeof(int); 133 | esz2 /= sizeof(int); 134 | 135 | for(; iters < maxAttempts; iters++) 136 | { 137 | for( i = 0; i < modelPoints && iters < maxAttempts; ) 138 | { 139 | int idx_i = 0; 140 | for(;;)//> 随机选取四个点 141 | { 142 | idx_i = idx[i] = rng.uniform(0, count); 143 | for( j = 0; j < i; j++ ) 144 | if( idx_i == idx[j] ) 145 | break; 146 | if( j == i ) 147 | break; 148 | } 149 | for( k = 0; k < esz1; k++ ) 150 | ms1ptr[i*esz1 + k] = m1ptr[idx_i*esz1 + k]; 151 | for( k = 0; k < esz2; k++ ) 152 | ms2ptr[i*esz2 + k] = m2ptr[idx_i*esz2 + k]; 153 | if( checkPartialSubsets && !cb->checkSubset( ms1, ms2, i+1 ))//> 去除重复点 154 | { 155 | // we may have selected some bad points; 156 | // so, let's remove some of them randomly 157 | i = rng.uniform(0, i+1); 158 | iters++; 159 | continue; 160 | } 161 | i++; 162 | } 163 | if( !checkPartialSubsets && i == modelPoints && !cb->checkSubset(ms1, ms2, i)) 164 | continue; 165 | break; 166 | } 167 | 168 | return i == modelPoints && iters < maxAttempts; 169 | } 170 | 171 | ``` 172 | 173 | 174 | 175 | 在53-73行,实现了迭代次数的判断 $k = \frac{log(1-p)}{log(1-w^m)}$ : 176 | 177 | ```c++ 178 | int RANSACUpdateNumIters( double p, double ep, int modelPoints, int maxIters ) 179 | { 180 | if( modelPoints <= 0 ) 181 | CV_Error( Error::StsOutOfRange, "the number of model points should be positive" ); 182 | 183 | p = MAX(p, 0.); 184 | p = MIN(p, 1.); 185 | ep = MAX(ep, 0.); 186 | ep = MIN(ep, 1.); 187 | 188 | // avoid inf's & nan's 189 | double num = MAX(1. - p, DBL_MIN); 190 | double denom = 1. - std::pow(1. - ep, modelPoints); 191 | if( denom < DBL_MIN ) 192 | return 0; 193 | 194 | num = std::log(num); 195 | denom = std::log(denom); 196 | 197 | return denom >= 0 || -num >= maxIters*(-denom) ? maxIters : cvRound(num/denom);//> 四舍五入 198 | } 199 | ``` 200 | 201 | -------------------------------------------------------------------------------- /example/19-SIFT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/example/19-SIFT.jpg -------------------------------------------------------------------------------- /example/19-left.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/example/19-left.JPG -------------------------------------------------------------------------------- /example/19-right.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/example/19-right.JPG -------------------------------------------------------------------------------- /example/3-left.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/example/3-left.JPG -------------------------------------------------------------------------------- /example/3-right.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/example/3-right.JPG -------------------------------------------------------------------------------- /example/3-surf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaobenx/Image-stitcher/c68f0d42f4a9d53c945a58f2c0b0fb3c84f836ec/example/3-surf.jpg -------------------------------------------------------------------------------- /py/605thesis.txt: -------------------------------------------------------------------------------- 1 | #===================================================# 2 | Image Road start stitching, using Method.SIFT 3 | Using the genetic method 4 | max distance: 98.8028335571289 5 | Min distance: 49.264591217041016 6 | match_len: 22 7 | !!!DATA: array([[18.11553346, 18.11553346, 18.11553346, 18.11553346, 18.11553346, 8 | 18.11553346, 18.11553346, 18.11553346, 18.11553346, 18.11553346, 9 | 18.11553346, 18.11553346, 18.11553346, 18.11553346, 18.11553346, 10 | 18.11553346, 18.11553346, 18.15834311, 18.15834311, 18.15834311], 11 | [17.99923316, 17.99923316, 17.99923316, 17.99923316, 17.99923316, 12 | 17.99923316, 17.99923316, 17.99923316, 17.99923316, 17.99923316, 13 | 17.99923316, 17.99923316, 17.99923316, 18.11553346, 18.11553346, 14 | 18.11553346, 18.11553346, 18.11553346, 18.15834311, 18.15834311], 15 | [17.9717021 , 17.9717021 , 17.9717021 , 17.9717021 , 17.98643223, 16 | 17.98643223, 17.99923316, 17.99923316, 17.99923316, 17.99923316, 17 | 17.99923316, 17.99923316, 17.99923316, 17.99923316, 18.09913891, 18 | 18.09913891, 18.11553346, 18.11553346, 18.11553346, 18.15834311], 19 | [17.96604297, 17.96604297, 17.96604297, 17.96604297, 17.9717021 , 20 | 17.9717021 , 17.98643223, 17.98643223, 17.98643223, 17.9956474 , 21 | 17.99923316, 17.99923316, 17.99923316, 17.99923316, 17.99923316, 22 | 17.99923316, 18.09913891, 18.11553346, 18.11553346, 18.11553346]]) 23 | Good points and average distance: (21, 21171.995535714286) 24 | 1089 1135 25 | Generating mask 26 | Blending 27 | Time: 1.2805564403533936 28 | #===================================================# 29 | Image Road start stitching, using Method.SIFT 30 | max distance: 98.8028335571289 31 | Min distance: 49.264591217041016 32 | match_len: 22 33 | Good points and average distance: (18, 15353.609375) 34 | 1016 1106 35 | Generating mask 36 | Blending 37 | Time: 1.0268702507019043 38 | #===================================================# 39 | Image Road start stitching, using Method.ORB 40 | Using the genetic method 41 | max distance: 21.0 42 | Min distance: 5.0 43 | match_len: 25 44 | !!!DATA: array([[24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077, 45 | 24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077, 46 | 24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077, 47 | 24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077], 48 | [24.62755285, 24.62755285, 24.62755285, 24.62755285, 24.62755285, 49 | 24.62755285, 24.6567805 , 24.70518033, 24.70518033, 24.70518033, 50 | 24.70518033, 24.70518033, 24.70518033, 24.71827077, 24.71827077, 51 | 24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077], 52 | [24.4648366 , 24.4648366 , 24.4648366 , 24.54565117, 24.59828591, 53 | 24.62339116, 24.62755285, 24.6567805 , 24.69704633, 24.69704633, 54 | 24.69704633, 24.70518033, 24.70518033, 24.70518033, 24.71827077, 55 | 24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077], 56 | [24.45153597, 24.45153597, 24.45153597, 24.46515858, 24.54565117, 57 | 24.59828591, 24.62339116, 24.64775564, 24.6567805 , 24.6567805 , 58 | 24.69704633, 24.69704633, 24.69704633, 24.70518033, 24.70518033, 59 | 24.71827077, 24.71827077, 24.71827077, 24.71827077, 24.71827077]]) 60 | Good points and average distance: (25, 3910.5184375) 61 | 1027 1088 62 | Generating mask 63 | Blending 64 | Time: 1.1619024276733398 65 | #===================================================# 66 | Image Road start stitching, using Method.ORB 67 | max distance: 21.0 68 | Min distance: 5.0 69 | match_len: 25 70 | Good points and average distance: (22, 2812.6594460227275) 71 | 1982 2729 72 | Generating mask 73 | Blending 74 | Time: 1.6662921905517578 75 | #===================================================# 76 | Image Lake start stitching, using Method.SIFT 77 | Using the genetic method 78 | max distance: 64.79969024658203 79 | Min distance: 32.264530181884766 80 | match_len: 9 81 | !!!DATA: array([[9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66093055, 82 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084, 83 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084, 84 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084], 85 | [9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66093055, 86 | 9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66151084, 87 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084, 88 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084], 89 | [9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66093055, 90 | 9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66093055, 91 | 9.66093055, 9.66151084, 9.66151084, 9.66151084, 9.66151084, 92 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084], 93 | [9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66093055, 94 | 9.66093055, 9.66093055, 9.66093055, 9.66093055, 9.66093055, 95 | 9.66093055, 9.66151084, 9.66151084, 9.66151084, 9.66151084, 96 | 9.66151084, 9.66151084, 9.66151084, 9.66151084, 9.66151084]]) 97 | Good points and average distance: (9, 257231.75) 98 | 1844 1073 99 | Generating mask 100 | Blending 101 | Time: 1.6451725959777832 102 | #===================================================# 103 | Image Lake start stitching, using Method.SIFT 104 | max distance: 64.79969024658203 105 | Min distance: 32.264530181884766 106 | match_len: 9 107 | Good points and average distance: (9, 257231.75) 108 | 1694 1000 109 | Generating mask 110 | Blending 111 | Time: 1.4475231170654297 112 | #===================================================# 113 | Image Lake start stitching, using Method.ORB 114 | Using the genetic method 115 | max distance: 25.0 116 | Min distance: 12.0 117 | match_len: 40 118 | !!!DATA: array([[36.20332806, 36.20332806, 36.2377386 , 36.25723028, 36.294514 , 119 | 36.294514 , 36.294514 , 36.294514 , 36.30222497, 36.30731145, 120 | 36.30731145, 36.30731145, 36.30731145, 36.30731145, 36.30731145, 121 | 36.30739026, 36.30739026, 36.30739026, 36.30739026, 36.30750479], 122 | [36.19514372, 36.19514372, 36.20332806, 36.2377386 , 36.27040913, 123 | 36.27040913, 36.27759632, 36.28694876, 36.294514 , 36.30222497, 124 | 36.30731145, 36.30731145, 36.30731145, 36.30731145, 36.30731145, 125 | 36.30731145, 36.30731145, 36.30731145, 36.30731145, 36.30739026], 126 | [35.98846153, 36.17470115, 36.20332806, 36.20332806, 36.25723028, 127 | 36.2585378 , 36.27040913, 36.27759632, 36.28702368, 36.29649575, 128 | 36.30590888, 36.30731145, 36.30731145, 36.30731145, 36.30731145, 129 | 36.30731145, 36.30731145, 36.30731145, 36.30731145, 36.30731145], 130 | [35.69784969, 35.99352905, 36.19514372, 36.20332806, 36.24343007, 131 | 36.25723028, 36.26411865, 36.27759632, 36.28694876, 36.294514 , 132 | 36.3054982 , 36.30590888, 36.30731145, 36.30731145, 36.30731145, 133 | 36.30731145, 36.30731145, 36.30731145, 36.30731145, 36.30731145]]) 134 | Good points and average distance: (37, 111996.39864864865) 135 | 1544 961 136 | Generating mask 137 | Blending 138 | Time: 0.6649727821350098 139 | #===================================================# 140 | Image Lake start stitching, using Method.ORB 141 | max distance: 25.0 142 | Min distance: 12.0 143 | match_len: 40 144 | Good points and average distance: (37, 115602.04054054055) 145 | 1536 924 146 | Generating mask 147 | Blending 148 | Time: 0.4910871982574463 149 | #===================================================# 150 | Image Tree start stitching, using Method.SIFT 151 | Using the genetic method 152 | max distance: 92.71461486816406 153 | Min distance: 46.14108657836914 154 | match_len: 10 155 | !!!DATA: array([[10.52246755, 10.52246755, 10.52246755, 10.53002997, 10.53002997, 156 | 10.53948523, 10.53948523, 10.55165496, 10.55165496, 10.55165496, 157 | 10.55165496, 10.56111984, 10.56135103, 10.56254687, 10.56254687, 158 | 10.56254687, 10.56566219, 10.56566219, 10.56566219, 10.56566219], 159 | [10.52027238, 10.52027238, 10.52097452, 10.52246755, 10.52964238, 160 | 10.53487216, 10.53487216, 10.53948523, 10.53948523, 10.53948523, 161 | 10.55165496, 10.56111984, 10.56111984, 10.56135103, 10.56135103, 162 | 10.56254687, 10.56254687, 10.56254687, 10.56254687, 10.56566219], 163 | [10.50431285, 10.51485359, 10.52027238, 10.52097452, 10.52246755, 164 | 10.53002997, 10.53372486, 10.53487216, 10.53487216, 10.53948523, 165 | 10.54545377, 10.55165496, 10.56111984, 10.56135103, 10.56135103, 166 | 10.56135103, 10.56254687, 10.56254687, 10.56254687, 10.56566219], 167 | [10.46199265, 10.50503336, 10.52027238, 10.52027238, 10.52147931, 168 | 10.52964238, 10.53002997, 10.53372486, 10.53372486, 10.53487216, 169 | 10.53962219, 10.55165496, 10.56111984, 10.56111984, 10.56135103, 170 | 10.56135103, 10.56141854, 10.56254687, 10.56254687, 10.56254687]]) 171 | Good points and average distance: (10, 56586.50625) 172 | 1571 997 173 | Generating mask 174 | Blending 175 | Time: 1.3164358139038086 176 | #===================================================# 177 | Image Tree start stitching, using Method.SIFT 178 | max distance: 92.71461486816406 179 | Min distance: 46.14108657836914 180 | match_len: 10 181 | Good points and average distance: (10, 56586.50625) 182 | 1577 998 183 | Generating mask 184 | Blending 185 | Time: 1.31693696975708 186 | #===================================================# 187 | Image Tree start stitching, using Method.ORB 188 | Using the genetic method 189 | max distance: 27.0 190 | Min distance: 14.0 191 | match_len: 40 192 | !!!DATA: array([[37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 193 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 194 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 195 | 37.3285748 , 37.3285748 , 37.3285748 , 37.3285748 , 37.3285748 ], 196 | [37.09735063, 37.09735063, 37.28945282, 37.28945282, 37.28945282, 197 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 198 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 199 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282], 200 | [36.81132026, 36.81132026, 37.09735063, 37.28945282, 37.28945282, 201 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 202 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 203 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282], 204 | [35.84422874, 36.81099823, 36.81153466, 37.09735063, 37.09735063, 205 | 37.09735063, 37.09735063, 37.28945282, 37.28945282, 37.28945282, 206 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282, 207 | 37.28945282, 37.28945282, 37.28945282, 37.28945282, 37.28945282]]) 208 | Good points and average distance: (39, 20218.764423076922) 209 | 1607 1001 210 | Generating mask 211 | Blending 212 | Time: 0.6384522914886475 213 | #===================================================# 214 | Image Tree start stitching, using Method.ORB 215 | max distance: 27.0 216 | Min distance: 14.0 217 | match_len: 40 218 | Good points and average distance: (36, 20019.178819444445) 219 | 1565 979 220 | Generating mask 221 | Blending 222 | Time: 0.5143654346466064 223 | #===================================================# 224 | Image Building start stitching, using Method.SIFT 225 | Using the genetic method 226 | max distance: 56.665687561035156 227 | Min distance: 28.1069393157959 228 | match_len: 37 229 | !!!DATA: array([list([23.232337168000186, 22.891068604513052, 22.891068604513052, 22.891068604513052]), 230 | list([23.232337168000186, 23.232337168000186, 23.232337168000186, 23.232337168000186]), 231 | list([23.232337168000186, 23.232337168000186, 23.232337168000186, 23.232337168000186]), 232 | list([23.232337168000186, 23.232337168000186, 23.232337168000186, 23.232337168000186]), 233 | list([23.232781004226293, 23.232685330398716, 23.232337168000186, 23.232337168000186]), 234 | list([23.232781004226293, 23.232685330398716, 23.232685330398716, 23.232337168000186]), 235 | list([23.232952795003584, 23.232781004226293, 23.232685330398716, 23.232685330398716]), 236 | list([23.232952795003584, 23.232948804751643, 23.232781004226293, 23.232781004226293]), 237 | list([23.232952795003584, 23.232948804751643, 23.232948804751643, 23.232781004226293]), 238 | list([23.232952795003584, 23.232948804751643, 23.232948804751643, 23.232948804751643]), 239 | list([23.232952795003584, 23.232952795003584, 23.232948804751643, 23.232948804751643]), 240 | list([23.232952795003584, 23.232952795003584, 23.232948804751643, 23.232948804751643]), 241 | list([23.232952795003584, 23.232952795003584, 23.232952795003584, 23.232948804751643]), 242 | list([23.263469920865326, 23.26324097023469, 23.240870620495325, 23.232952795003584]), 243 | list([23.263469920865326, 23.263469920865326, 23.26324097023469, 23.240870620495325]), 244 | list([23.263469920865326, 23.263469920865326, 23.26324097023469, 23.262616665269107]), 245 | list([23.263469920865326, 23.263469920865326, 23.263469920865326, 23.263469920865326]), 246 | list([23.264675126742294, 23.264675126742294, 23.263469920865326, 23.263469920865326]), 247 | list([23.264675126742294, 23.264675126742294, 23.263469920865326, 23.263469920865326]), 248 | list([23.264675126742294, 23.264675126742294, 23.264675126742294, 23.264675126742294])], 249 | dtype=object) 250 | Good points and average distance: (24, 68308.48958333333) 251 | 2395 2581 252 | Generating mask 253 | Blending 254 | Time: 4.251520395278931 255 | #===================================================# 256 | Image Building start stitching, using Method.SIFT 257 | max distance: 56.665687561035156 258 | Min distance: 28.1069393157959 259 | match_len: 37 260 | Good points and average distance: (24, 68308.48958333333) 261 | 2402 2570 262 | Generating mask 263 | Blending 264 | Time: 4.047906398773193 265 | #===================================================# 266 | Image Building start stitching, using Method.ORB 267 | Using the genetic method 268 | max distance: 18.0 269 | Min distance: 8.0 270 | match_len: 40 271 | !!!DATA: array([[36.36783548, 36.36783548, 36.38240343, 36.38297445, 36.38297445, 272 | 36.38297445, 36.38297445, 36.40583081, 36.40583081, 36.40583081, 273 | 36.40583081, 36.40583081, 36.40583081, 36.40583081, 36.41106954, 274 | 36.41106954, 36.41106954, 36.41106954, 36.41106954, 36.41106954], 275 | [36.28873209, 36.28873209, 36.36783548, 36.38240343, 36.38240343, 276 | 36.38240343, 36.38240343, 36.38297445, 36.38297445, 36.39969978, 277 | 36.40583081, 36.40583081, 36.40583081, 36.40583081, 36.40583081, 278 | 36.41106954, 36.41106954, 36.41106954, 36.41106954, 36.41106954], 279 | [36.28415817, 36.28415817, 36.28873209, 36.36783548, 36.36783548, 280 | 36.36783548, 36.36783548, 36.38240343, 36.38240343, 36.38297445, 281 | 36.39969978, 36.40583081, 36.40583081, 36.40583081, 36.40583081, 282 | 36.41106954, 36.41106954, 36.41106954, 36.41106954, 36.41106954], 283 | [36.13705325, 36.13705325, 36.28415817, 36.36783548, 36.36783548, 284 | 36.36783548, 36.36783548, 36.38240343, 36.38240343, 36.38297445, 285 | 36.39969978, 36.40583081, 36.40583081, 36.40583081, 36.40583081, 286 | 36.40583081, 36.41106954, 36.41106954, 36.41106954, 36.41106954]]) 287 | Good points and average distance: (37, 76107.67567567568) 288 | 2405 2569 289 | Generating mask 290 | Blending 291 | Time: 2.7859864234924316 292 | #===================================================# 293 | Image Building start stitching, using Method.ORB 294 | max distance: 18.0 295 | Min distance: 8.0 296 | match_len: 40 297 | Good points and average distance: (36, 75293.09722222222) 298 | 2404 2582 299 | Generating mask 300 | Blending 301 | Time: 2.0230937004089355 302 | #===================================================# 303 | Image School start stitching, using Method.SIFT 304 | Using the genetic method 305 | max distance: 169.44320678710938 306 | Min distance: 82.55906677246094 307 | match_len: 8 308 | !!!DATA: array([[5.64816763, 5.64816763, 5.64816763, 5.64816763, 5.64816763, 309 | 5.64816763, 5.64816763, 5.68012718, 5.68013677, 5.68013677, 310 | 5.68013677, 5.69116551, 5.69116551, 5.69116551, 5.69116551, 311 | 5.69116551, 5.69116551, 5.69116551, 5.72411353, 5.72411353], 312 | [5.37826055, 5.37826055, 5.39093786, 5.39093786, 5.47230204, 313 | 5.47230204, 5.58089263, 5.64816763, 5.68012718, 5.68012718, 314 | 5.68012718, 5.68013677, 5.69116551, 5.69116551, 5.69116551, 315 | 5.69116551, 5.69116551, 5.69116551, 5.69116551, 5.69116551], 316 | [5.22631703, 5.37006151, 5.37826055, 5.37826055, 5.39093786, 317 | 5.44238831, 5.52944219, 5.62928711, 5.64816763, 5.64816763, 318 | 5.64816763, 5.68012718, 5.68013677, 5.68013677, 5.69116221, 319 | 5.69116551, 5.69116551, 5.69116551, 5.69116551, 5.69116551], 320 | [5.19809194, 5.22631703, 5.37006151, 5.37006151, 5.37826055, 321 | 5.39093786, 5.47230204, 5.58089263, 5.62928711, 5.62928711, 322 | 5.64816763, 5.64816763, 5.68013677, 5.68013677, 5.68013677, 323 | 5.69116551, 5.69116551, 5.69116551, 5.69116551, 5.69116551]]) 324 | Good points and average distance: (5, 198586.625) 325 | 2328 2464 326 | Generating mask 327 | Blending 328 | Time: 4.036421775817871 329 | #===================================================# 330 | Image School start stitching, using Method.SIFT 331 | max distance: 169.44320678710938 332 | Min distance: 82.55906677246094 333 | match_len: 8 334 | Good points and average distance: (6, 185690.08333333334) 335 | 2333 2515 336 | Generating mask 337 | Blending 338 | Time: 4.019939661026001 339 | #===================================================# 340 | Image School start stitching, using Method.ORB 341 | Using the genetic method 342 | max distance: 31.0 343 | Min distance: 15.0 344 | match_len: 14 345 | !!!DATA: array([[11.83476377, 11.83476377, 11.83476377, 11.83476377, 11.83476377, 346 | 11.83476377, 11.83476377, 11.83476377, 11.84654993, 11.84654993, 347 | 11.84654993, 11.84654993, 11.84654993, 11.84654993, 11.84654993, 348 | 11.84654993, 11.84654993, 11.84654993, 11.84654993, 11.84654993], 349 | [11.51064645, 11.51064645, 11.51064645, 11.51064645, 11.51274697, 350 | 11.51274697, 11.51274697, 11.83476377, 11.83476377, 11.83476377, 351 | 11.83476377, 11.83476377, 11.83476377, 11.83476377, 11.83476377, 352 | 11.84654993, 11.84654993, 11.84654993, 11.84654993, 11.84654993], 353 | [11.06236839, 11.06236839, 11.06236839, 11.06236839, 11.51064645, 354 | 11.51064645, 11.5121039 , 11.51274697, 11.83476377, 11.83476377, 355 | 11.83476377, 11.83476377, 11.83476377, 11.83476377, 11.83476377, 356 | 11.83476377, 11.84654993, 11.84654993, 11.84654993, 11.84654993], 357 | [10.80487133, 10.80487133, 10.80487133, 10.80487133, 11.06236839, 358 | 11.06236839, 11.51064645, 11.5121039 , 11.83476377, 11.83476377, 359 | 11.83476377, 11.83476377, 11.83476377, 11.83476377, 11.83476377, 360 | 11.83476377, 11.84654993, 11.84654993, 11.84654993, 11.84654993]]) 361 | Good points and average distance: (13, 48705.46153846154) 362 | 2785 3005 363 | Generating mask 364 | Blending 365 | Time: 2.5973451137542725 366 | #===================================================# 367 | Image School start stitching, using Method.ORB 368 | max distance: 31.0 369 | Min distance: 15.0 370 | match_len: 14 371 | Good points and average distance: (11, 37520.65909090909) 372 | 2355 2607 373 | Generating mask 374 | Blending 375 | Time: 1.909858226776123 376 | #===================================================# 377 | Image Grass start stitching, using Method.SIFT 378 | Using the genetic method 379 | max distance: 75.02666473388672 380 | Min distance: 37.48332977294922 381 | match_len: 11 382 | !!!DATA: array([[11.83230155, 11.83230155, 11.83230155, 11.83230155, 11.83230155, 383 | 11.83230155, 11.83230155, 11.83230155, 11.83230155, 11.83230155, 384 | 11.83230155, 11.83230155, 11.83230155, 11.83230155, 11.83230155, 385 | 11.83230155, 11.83230155, 11.83230155, 11.83230155, 11.83230155], 386 | [11.82961866, 11.82961866, 11.82961866, 11.82961866, 11.82961866, 387 | 11.82961866, 11.82961866, 11.82961866, 11.82961866, 11.82961866, 388 | 11.82961866, 11.82961866, 11.83230155, 11.83230155, 11.83230155, 389 | 11.83230155, 11.83230155, 11.83230155, 11.83230155, 11.83230155], 390 | [11.82088752, 11.82088752, 11.82088752, 11.82853022, 11.82853022, 391 | 11.82853022, 11.82853022, 11.82853022, 11.82853022, 11.82853022, 392 | 11.82853022, 11.82853022, 11.82961866, 11.82961866, 11.82961866, 393 | 11.82961866, 11.82961866, 11.82961866, 11.83230155, 11.83230155], 394 | [11.82035756, 11.82035756, 11.82035756, 11.82088752, 11.82853022, 395 | 11.82853022, 11.82853022, 11.82853022, 11.82853022, 11.82853022, 396 | 11.82853022, 11.82853022, 11.82853022, 11.82853022, 11.82853022, 397 | 11.82853022, 11.82853022, 11.82853022, 11.83230155, 11.83230155]]) 398 | Good points and average distance: (11, 96893.73863636363) 399 | 2477 2624 400 | Generating mask 401 | Blending 402 | Time: 4.1755125522613525 403 | #===================================================# 404 | Image Grass start stitching, using Method.SIFT 405 | max distance: 75.02666473388672 406 | Min distance: 37.48332977294922 407 | match_len: 11 408 | Good points and average distance: (9, 54313.663194444445) 409 | 2524 2521 410 | Generating mask 411 | Blending 412 | Time: 4.037367105484009 413 | #===================================================# 414 | Image Grass start stitching, using Method.ORB 415 | Using the genetic method 416 | max distance: 21.0 417 | Min distance: 9.0 418 | match_len: 18 419 | !!!DATA: array([[16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 420 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 421 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 422 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 ], 423 | [16.43186494, 16.43186494, 16.43186494, 16.43186494, 16.4491902 , 424 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 425 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 426 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 ], 427 | [16.26181432, 16.41257775, 16.43186494, 16.43186494, 16.43186494, 428 | 16.43186494, 16.43186494, 16.43861316, 16.43861316, 16.43861316, 429 | 16.43861316, 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 430 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 ], 431 | [16.23830914, 16.26181432, 16.41257775, 16.41257775, 16.43186494, 432 | 16.43186494, 16.43186494, 16.43186494, 16.43861316, 16.43861316, 433 | 16.43861316, 16.43861316, 16.43861316, 16.4491902 , 16.4491902 , 434 | 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 , 16.4491902 ]]) 435 | Good points and average distance: (17, 147235.48529411765) 436 | 2463 2610 437 | Generating mask 438 | Blending 439 | Time: 2.0805065631866455 440 | #===================================================# 441 | Image Grass start stitching, using Method.ORB 442 | max distance: 21.0 443 | Min distance: 9.0 444 | match_len: 18 445 | Good points and average distance: (16, 129428.1171875) 446 | 2477 2643 447 | Generating mask 448 | Blending 449 | Time: 1.9653944969177246 450 | #===================================================# 451 | Image Palace start stitching, using Method.SIFT 452 | Using the genetic method 453 | max distance: 64.1248779296875 454 | Min distance: 31.93743896484375 455 | match_len: 39 456 | !!!DATA: array([[36.93010895, 36.93010895, 36.93010895, 36.93010895, 37.28534356, 457 | 37.28534356, 37.28534356, 37.28534356, 37.28909305, 37.28909305, 458 | 37.28909305, 37.30148667, 37.30148667, 37.30148667, 37.30148667, 459 | 37.35119056, 37.35119056, 37.35119056, 37.35119056, 37.35119056], 460 | [36.68125497, 36.81725468, 36.81725468, 36.93010895, 36.93010895, 461 | 36.93010895, 36.93010895, 37.28534356, 37.28534356, 37.28534356, 462 | 37.28909305, 37.30148667, 37.30148667, 37.30148667, 37.30148667, 463 | 37.30148667, 37.30148667, 37.30148667, 37.31623949, 37.31623949], 464 | [36.25597379, 36.68125497, 36.68125497, 36.81725468, 36.93010895, 465 | 36.93010895, 36.93010895, 36.93010895, 37.28534356, 37.28534356, 466 | 37.28909305, 37.28909305, 37.30148667, 37.30148667, 37.30148667, 467 | 37.30148667, 37.30148667, 37.30148667, 37.30148667, 37.31623949], 468 | [36.06546805, 36.25597379, 36.25597379, 36.70391257, 36.81725468, 469 | 36.93010895, 36.93010895, 36.93010895, 37.23931602, 37.28534356, 470 | 37.28534356, 37.28909305, 37.28909305, 37.30148667, 37.30148667, 471 | 37.30148667, 37.30148667, 37.30148667, 37.30148667, 37.30148667]]) 472 | Good points and average distance: (38, 386711.0) 473 | 2226 3103 474 | Generating mask 475 | Blending 476 | Time: 5.751944065093994 477 | #===================================================# 478 | Image Palace start stitching, using Method.SIFT 479 | max distance: 64.1248779296875 480 | Min distance: 31.93743896484375 481 | match_len: 39 482 | Good points and average distance: (38, 386711.0) 483 | 2225 3107 484 | Generating mask 485 | Blending 486 | Time: 5.505410671234131 487 | #===================================================# 488 | Image Palace start stitching, using Method.ORB 489 | Using the genetic method 490 | max distance: 21.0 491 | Min distance: 7.0 492 | match_len: 17 493 | !!!DATA: array([[14.86553584, 14.91743125, 14.91743125, 14.97747019, 14.98294619, 494 | 14.98294619, 14.98294619, 14.99382314, 14.99398142, 14.99398142, 495 | 14.99398142, 14.99398142, 14.99398142, 14.99398142, 14.99398142, 496 | 14.99398142, 14.99398142, 14.99398142, 14.99398142, 14.99398142], 497 | [14.75135421, 14.86553584, 14.86553584, 14.91743125, 14.97747019, 498 | 14.98197678, 14.98197678, 14.98294619, 14.99382314, 14.99382314, 499 | 14.99398142, 14.99398142, 14.99398142, 14.99398142, 14.99398142, 500 | 14.99398142, 14.99398142, 14.99398142, 14.99398142, 14.99398142], 501 | [14.47648919, 14.75135421, 14.75135421, 14.91743125, 14.97747019, 502 | 14.98197678, 14.98197678, 14.98217621, 14.98294619, 14.99382314, 503 | 14.99382314, 14.99398142, 14.99398142, 14.99398142, 14.99398142, 504 | 14.99398142, 14.99398142, 14.99398142, 14.99398142, 14.99398142], 505 | [14.45338323, 14.53224777, 14.53224777, 14.9170704 , 14.96752587, 506 | 14.97747019, 14.98197678, 14.98197678, 14.98294619, 14.99307483, 507 | 14.99382314, 14.99398142, 14.99398142, 14.99398142, 14.99398142, 508 | 14.99398142, 14.99398142, 14.99398142, 14.99398142, 14.99398142]]) 509 | Good points and average distance: (16, 258111.3125) 510 | 2230 3140 511 | Generating mask 512 | Blending 513 | Time: 2.2951416969299316 514 | #===================================================# 515 | Image Palace start stitching, using Method.ORB 516 | max distance: 21.0 517 | Min distance: 7.0 518 | match_len: 17 519 | Good points and average distance: (15, 247492.95) 520 | 2401 3283 521 | Generating mask 522 | Blending 523 | Time: 2.368182420730591 524 | #===================================================# 525 | Image NewHarbor start stitching, using Method.SIFT 526 | Using the genetic method 527 | max distance: 88.91568756103516 528 | Min distance: 45.45327377319336 529 | match_len: 40 530 | !!!DATA: array([[24.773442 , 25.18840191, 25.18840191, 25.18840191, 25.18840191, 531 | 25.18840191, 25.18840191, 25.18840191, 25.3342389 , 25.3342389 , 532 | 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 533 | 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 ], 534 | [24.68717652, 24.773442 , 24.773442 , 25.18473314, 25.18473314, 535 | 25.18473314, 25.18473314, 25.18473314, 25.18840191, 25.18840191, 536 | 25.18840191, 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 537 | 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 ], 538 | [24.57586837, 24.76314624, 24.76314624, 24.773442 , 24.773442 , 539 | 24.94500095, 24.94500095, 25.17356375, 25.18473314, 25.18473314, 540 | 25.18840191, 25.18840191, 25.3342389 , 25.3342389 , 25.3342389 , 541 | 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 ], 542 | [24.515401 , 24.68717652, 24.68717652, 24.76314624, 24.76314624, 543 | 24.773442 , 24.773442 , 24.94500095, 25.17356375, 25.17356375, 544 | 25.18473314, 25.18840191, 25.3342389 , 25.3342389 , 25.3342389 , 545 | 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 , 25.3342389 ]]) 546 | Good points and average distance: (26, 135508.48076923078) 547 | 5547 3298 548 | Generating mask 549 | Blending 550 | Time: 8.68319320678711 551 | #===================================================# 552 | Image NewHarbor start stitching, using Method.SIFT 553 | max distance: 88.91568756103516 554 | Min distance: 45.45327377319336 555 | match_len: 40 556 | Good points and average distance: (24, 132731.45833333334) 557 | 5562 3374 558 | Generating mask 559 | Blending 560 | Time: 8.668111801147461 561 | #===================================================# 562 | Image NewHarbor start stitching, using Method.ORB 563 | Using the genetic method 564 | max distance: 29.0 565 | Min distance: 14.0 566 | match_len: 16 567 | !!!DATA: array([[12.49782784, 12.49782784, 12.49782784, 12.49782784, 12.49782784, 568 | 12.49782784, 12.49782784, 12.49782784, 12.49782784, 12.49782784, 569 | 12.49782784, 12.49782784, 12.49782784, 12.49782784, 12.49782784, 570 | 12.49782784, 12.49782784, 12.49782784, 12.49782784, 12.49782784], 571 | [12.3133432 , 12.3133432 , 12.3133432 , 12.3133432 , 12.3133432 , 572 | 12.3133432 , 12.3133432 , 12.3133432 , 12.3133432 , 12.3133432 , 573 | 12.3133432 , 12.3133432 , 12.3133432 , 12.49782784, 12.49782784, 574 | 12.49782784, 12.49782784, 12.49782784, 12.49782784, 12.49782784], 575 | [11.90577763, 11.90577763, 11.90577763, 11.90577763, 11.90577763, 576 | 11.90577763, 11.90577763, 11.90577763, 11.90577763, 11.90577763, 577 | 11.90577763, 11.90577763, 11.90577763, 12.3133432 , 12.3133432 , 578 | 12.49782784, 12.49782784, 12.49782784, 12.49782784, 12.49782784], 579 | [11.61362739, 11.61362739, 11.61362739, 11.61841607, 11.90577763, 580 | 11.90577763, 11.90577763, 11.90577763, 11.90577763, 11.90577763, 581 | 11.90577763, 11.90577763, 11.90577763, 11.90577763, 11.90577763, 582 | 12.3133432 , 12.49782784, 12.49782784, 12.49782784, 12.49782784]]) 583 | Good points and average distance: (14, 106148.22321428571) 584 | 6954 4890 585 | Generating mask 586 | Blending 587 | Time: 10.533944606781006 588 | #===================================================# 589 | Image NewHarbor start stitching, using Method.ORB 590 | max distance: 29.0 591 | Min distance: 14.0 592 | match_len: 16 593 | Good points and average distance: (13, 109256.16346153847) 594 | 7375 5248 595 | Generating mask 596 | Blending 597 | Time: 12.458117723464966 598 | [ INFO:0] Initialize OpenCL runtime... 599 | -------------------------------------------------------------------------------- /py/README.md: -------------------------------------------------------------------------------- 1 | # 图像拼接代码介绍 2 | 3 | ## 主入口 4 | 5 | ```python 6 | matcher = Matcher(img1, img2, Method.SIFT) 7 | matcher.match(show_match=True) 8 | sticher = Sticher(img1, img2, matcher) 9 | sticher.stich() 10 | ``` 11 | 12 | 分为两部分,`Matcher`和`Sticher`,分别用作图像的内容识别及图像的拼接 13 | 14 | ## Matcher介绍 15 | 16 | ### 构造函数 17 | 18 | ```python 19 | class Matcher(): 20 | 21 | def __init__(self, image1: np.ndarray, image2: np.ndarray, method: Enum=Method.SURF, threshold=800) -> None: 22 | """输入两幅图像,计算其特征值 23 | 24 | Args: 25 | image1 (np.ndarray): 图像一 26 | image2 (np.ndarray): 图像二 27 | method (Enum, optional): Defaults to Method.SURF. 特征值检测方法 28 | threshold (int, optional): Defaults to 800. 特征值阈值 29 | 30 | """ 31 | ... 32 | ``` 33 | 34 | 此类用于输入两幅图像,计算其特征值,输入两幅图像分别为`numpy`数组格式的图像,其中的`method`参数要求输入SURF、SIFT或者ORB,`threshold`参数为特征值检测所需的阈值。 35 | 36 | ### 特征值计算 37 | 38 | ```python 39 | def compute_keypoint(self) -> None: 40 | """计算特征点 41 | 42 | Args: 43 | image (np.ndarray): 图像 44 | """ 45 | ... 46 | ``` 47 | 48 | 利用给出的特征值检测方法对图像进行特征值检测。 49 | 50 | ### 匹配 51 | 52 | ```python 53 | def match(self, max_match_lenth=20, threshold=0.04, show_match=False): 54 | """对图片进行匹配 55 | max_match_lenth (int, optional): Defaults to 20. 最大匹配点数量 56 | threshold (float, optional): Defaults to 0.04. 默认最大匹配距离差 57 | show_match (bool, optional): Defaults to False. 是否展示匹配结果 58 | """ 59 | ... 60 | ``` 61 | 62 | 对两幅图片计算得出的特征值进行匹配,对ORB来说使用OpenCV的`BFMatcher`算法,而对于其他特征检测方法则使用`FlannBasedMatcher`算法。 63 | 64 | ## Sticher介绍 65 | 66 | ### 构造函数 67 | 68 | ```python 69 | class Sticher: 70 | 71 | def __init__(self, image1: np.ndarray, image2: np.ndarray, matcher: Matcher): 72 | """输入图像和匹配,对图像进行拼接 73 | 目前采用简单矩阵匹配和平均值拼合 74 | 75 | Args: 76 | image1 (np.ndarray): 图像一 77 | image2 (np.ndarray): 图像二 78 | matcher (Matcher): 匹配结果 79 | """ 80 | ... 81 | ``` 82 | 83 | 输入图像和匹配,对图像进行拼接,目前采用简单矩阵匹配和平均值拼合。 84 | 85 | ### 拼合 86 | 87 | ```python 88 | def stich(self, show_result=True, show_match_point=True): 89 | """对图片进行拼合 90 | show_result (bool, optional): Defaults to True. 是否展示拼合图像 91 | show_match_point (bool, optional): Defaults to True. 是否展示拼合点 92 | """ 93 | ... 94 | ``` 95 | 96 | 对两幅图像进行拼合,采用透视变换矩阵,并利用平均值对图片进行无缝接合。 97 | 98 | ### 融合 99 | 100 | ```python 101 | def blend(self, image1: np.ndarray, image2: np.ndarray) -> np.ndarray: 102 | """对图像进行拼合 103 | 104 | Args: 105 | image1 (np.ndarray): 图像一 106 | image2 (np.ndarray): 图像二 107 | 108 | Returns: 109 | np.ndarray: 融合结果 110 | """ 111 | ... 112 | ``` 113 | 114 | 目前采用简单平均方式。 115 | 116 | ### 辅助函数 117 | 118 | #### 平均值 119 | 120 | ```python 121 | def average(self, image1: np.ndarray, image2: np.ndarray) -> np.ndarry: 122 | """平均算法拼合 123 | 124 | Args: 125 | image1 (np.ndarray): 图片一 126 | image2 (np.ndarray): 图片二 127 | 128 | Returns: 129 | np.ndarray: 拼合后图像 130 | """ 131 | ... 132 | ``` 133 | 134 | 返回两幅图片的平均值。 135 | 136 | #### 边界计算 137 | 138 | ```python 139 | def get_transformed_size(self) ->Tuple[int, int, int, int]: 140 | """计算形变后的边界 141 | 142 | Returns: 143 | Tuple[int, int, int, int]: 分别为左右上下边界 144 | """ 145 | ... 146 | ``` 147 | 148 | 计算形变后的边界,从而对图片进行相应的位移,保证全部图像都出现在屏幕上。 149 | 150 | #### 坐标变换 151 | 152 | ```python 153 | def get_transformed_position(self, x: Union[float, Tuple[float, float]], y: float=None, M=None) -> Tuple[float, float]: 154 | """求得某点在变换矩阵(self.M)下的新坐标 155 | 156 | Args: 157 | x (Union[float, Tuple[float, float]]): x坐标或(x,y)坐标 158 | y (float, optional): Defaults to None. y坐标,可无 159 | M (np.ndarry, optional): Defaults to None. 利用M进行坐标变换运算 160 | 161 | Returns: 162 | Tuple[float, float]: 新坐标 163 | """ 164 | ... 165 | ``` 166 | 167 | 求得某点在变换矩阵(self.M)下的新坐标,如有选参数`M`,则利用M进行坐标变换运算。 -------------------------------------------------------------------------------- /py/batch_stich.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-04-10 20:11:11 4 | @Author: ZHAO Lingfeng 5 | @Version : 0.0.1 6 | """ 7 | import glob 8 | import os 9 | 10 | import cv2 11 | from stich import Stitcher, Method 12 | 13 | 14 | def main(): 15 | import time 16 | # main() 17 | os.chdir(os.path.dirname(__file__)) 18 | 19 | number = 19 20 | file1 = "../resource/{}-right*.jpg".format(number) 21 | file2 = "../resource/{}-left.jpg".format(number) 22 | 23 | start_time = time.time() 24 | try: 25 | for method in (Method.ORB, Method.SIFT): 26 | 27 | for f in glob.glob(file1): 28 | print(f, method) 29 | name = f.replace('right', method.name) 30 | # print(file2, name) 31 | 32 | img2 = cv2.imread(file2) 33 | img1 = cv2.imread(f) 34 | stitcher = Stitcher(img1, img2, method=method) 35 | stitcher.stich(show_result=False) 36 | cv2.imwrite(name, stitcher.image) 37 | print("Time: ", time.time() - start_time) 38 | # print("M: ", stitcher.M) 39 | except Exception as e: 40 | print("Error!: ", e) 41 | print('\a') 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /py/blend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-05-04 19:47:47 4 | @Author: ZHAO Lingfeng 5 | @Version : 0.0.1 6 | """ 7 | import os 8 | 9 | import numpy as np 10 | from scipy.ndimage.filters import gaussian_filter 11 | 12 | # 13 | import cv2 14 | 15 | 16 | def show_image(image: np.ndarray) -> None: 17 | from PIL import Image 18 | Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)).show() 19 | 20 | 21 | class Blend: 22 | pass 23 | 24 | 25 | class GaussianBlend(Blend): 26 | 27 | LEVEL = 6 28 | 29 | def __init__(self, image1: np.ndarray, image2: np.ndarray, mask: np.ndarray): 30 | self.image1 = image1 31 | self.image2 = image2 32 | if np.issubdtype(mask.dtype, np.integer): 33 | self.mask = mask / 255 34 | else: 35 | self.mask = mask 36 | 37 | def blend(self): 38 | print("Calculating pyramid") 39 | la1 = self.get_laplacian_pyramid(self.image1) 40 | la2 = self.get_laplacian_pyramid(self.image2) 41 | 42 | gm = self.get_gaussian_pyramid(self.mask) 43 | 44 | result = np.zeros(self.image1.shape, int) 45 | # new_la = [] 46 | for i in range(self.LEVEL): 47 | mask = next(gm) 48 | # new_la.append(next(la1) * mask + next(la2) * (1.0 - mask)) 49 | 50 | result += (next(la1) * mask + next(la2) * (1.0 - mask)).astype(int) 51 | del mask 52 | print(i, " level blended") 53 | return np.clip(result, 0, 255).astype('uint8') 54 | 55 | # print("Rebuilding ") 56 | # return self.rebuild_image(new_la) 57 | 58 | @classmethod 59 | def get_laplacian_pyramid(cls, image: np.ndarray): 60 | # output = [] 61 | last = image 62 | 63 | for i in range(cls.LEVEL - 1): 64 | this = gaussian_filter(last, (1, 1, 0)) 65 | laplace = cls.subtract(last, this) 66 | # output.append(laplace) 67 | yield laplace 68 | last = this 69 | # output.append(last) 70 | yield last 71 | # return output 72 | 73 | @classmethod 74 | def get_gaussian_pyramid(cls, image: np.ndarray): 75 | # G = [] 76 | tmp = image 77 | for i in range(cls.LEVEL): 78 | # G.append(tmp) 79 | yield tmp 80 | tmp = gaussian_filter(tmp, (1, 1, 0)) 81 | 82 | # return G 83 | 84 | @staticmethod 85 | def rebuild_image(laplacian_pyramid: np.ndarray): 86 | result = np.sum(laplacian_pyramid, axis=0) 87 | return np.clip(result, 0, 255).astype('uint8') 88 | 89 | @staticmethod 90 | def subtract(array1: np.ndarray, array2: np.ndarray): 91 | """give non minus subtract 92 | 93 | Args: 94 | array1 (np.ndarray): array1 95 | array2 (np.ndarray): array2 96 | 97 | Returns: 98 | np.ndarray: (array1 - array2)>0?(array1 - array2):0 99 | """ 100 | array1 = array1.astype(int) 101 | array2 = array2.astype(int) 102 | result = array1 - array2 103 | # result[np.where(result < 0)] = 0 104 | 105 | return result # .astype(np.uint8) 106 | 107 | 108 | def average_blend(image1: np.ndarray, image2: np.ndarray) -> np.ndarray: 109 | """平均算法拼合 110 | 111 | Args: 112 | image1 (np.ndarray): 图片一 113 | image2 (np.ndarray): 图片二 114 | 115 | Returns: 116 | np.ndarray: 拼合后图像 117 | """ 118 | 119 | assert(image1.shape == image2.shape) 120 | result = np.zeros(image1.shape, dtype='uint8') 121 | 122 | # image1 != 0 && image2 !=0: 123 | overlap = np.logical_and( 124 | np.all(np.not_equal(image1, [0, 0, 0]), axis=2), 125 | np.all(np.not_equal(image2, [0, 0, 0]), axis=2), 126 | ) 127 | # 重叠处用平均值 128 | result[overlap] = np.average( 129 | np.array([image1[overlap], image2[overlap]]), axis=0 130 | ) .astype(np.uint8) 131 | # 非重叠处采选最大值 132 | not_overlap = np.logical_not(overlap) 133 | result[not_overlap] = np.maximum( 134 | image1[not_overlap], image2[not_overlap]) 135 | 136 | return result 137 | 138 | 139 | def gaussian_blend(image1: np.ndarray, image2: np.ndarray, mask: np.ndarray, mask_blend=3): 140 | if mask_blend: 141 | mask = gaussian_filter(mask.astype(float), (mask_blend, mask_blend, 0)) 142 | # show_image((mask * 255).astype('uint8')) 143 | # show_image(image1) 144 | # show_image(image2) 145 | 146 | return GaussianBlend(image1, image2, mask).blend() 147 | 148 | 149 | def direct_blend(image1: np.ndarray, image2: np.ndarray, mask: np.ndarray, mask_blend=0): 150 | if mask_blend: 151 | mask = gaussian_filter(mask.astype(float), (mask_blend, mask_blend, 0)) 152 | if np.issubdtype(mask.dtype, np.integer): 153 | mask = mask / 255 154 | # show_image((mask * 255).astype('uint8')) 155 | # show_image(image1) 156 | # show_image(image2) 157 | 158 | return (image1 * mask + image2 * (1 - mask)).astype('uint8') 159 | 160 | 161 | def test(): 162 | os.chdir(os.path.dirname(__file__)) 163 | 164 | image1 = cv2.imread("../example/3-left.jpg") 165 | image2 = cv2.imread("../example/3-right.jpg") 166 | show_image(np.concatenate((image1, image2), axis=0)) 167 | mask = np.zeros(image1.shape) 168 | mask[:600] = 1.0 169 | mask = gaussian_filter(mask, (5, 5, 0)) 170 | show_image(gaussian_blend(image1, image2, mask)) 171 | 172 | 173 | def main(): 174 | test() 175 | 176 | 177 | if __name__ == "__main__": 178 | main() 179 | -------------------------------------------------------------------------------- /py/k_means.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-04-02 22:12:12 4 | @Author: ZHAO Lingfeng 5 | @Version : 0.0.1 6 | """ 7 | from typing import Tuple 8 | 9 | import cv2 10 | import numpy as np 11 | 12 | 13 | def difference(array: np.ndarray): 14 | x = [] 15 | for i in range(len(array) - 1): 16 | x.append(array[i + 1] - array[i]) 17 | 18 | return np.array(x) 19 | 20 | 21 | def find_peek(array: np.ndarray): 22 | peek = difference(difference(array)) 23 | # print(peek) 24 | peek_pos = np.argmax(peek) + 2 25 | return peek_pos 26 | 27 | 28 | def k_means(points: np.ndarray): 29 | """返回一个数组经kmeans分类后的k值以及标签,k值由计算拐点给出 30 | 31 | Args: 32 | points (np.ndarray): 需分类数据 33 | 34 | Returns: 35 | Tuple[int, np.ndarry]: k值以及标签数组 36 | """ 37 | 38 | # Define criteria = ( type, max_iter = 10 , epsilon = 1.0 ) 39 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) 40 | 41 | # Set flags (Just to avoid line break in the code) 42 | flags = cv2.KMEANS_RANDOM_CENTERS 43 | length = [] 44 | max_k = min(10, points.shape[0]) 45 | for k in range(2, max_k + 1): 46 | avg = 0 47 | for i in range(5): 48 | compactness, _, _ = cv2.kmeans( 49 | points, k, None, criteria, 10, flags) 50 | avg += compactness 51 | avg /= 5 52 | length.append(avg) 53 | 54 | peek_pos = find_peek(length) 55 | k = peek_pos + 2 56 | # print(k) 57 | return k, cv2.kmeans(points, k, None, criteria, 10, flags)[1] # labels 58 | 59 | 60 | def get_group_center(points1: np.ndarray, points2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: 61 | """输入两个相对应的点对数组,返回经kmeans优化后的两个数组 62 | 63 | Args: 64 | points1 (np.ndarray): 数组一 65 | points2 (np.ndarray): 数组二 66 | 67 | Returns: 68 | Tuple[np.ndarray, np.ndarray]: 两数组 69 | """ 70 | 71 | k, labels = k_means(points1) 72 | labels = labels.flatten() 73 | selected_centers1 = [] 74 | selected_centers2 = [] 75 | for i in range(k): 76 | center1 = np.mean(points1[labels == i], axis=0) 77 | center2 = np.mean(points2[labels == i], axis=0) 78 | # center1 = points1[labels == i][0] 79 | # center2 = points2[labels == i][0] 80 | 81 | selected_centers1.append(center1) 82 | selected_centers2.append(center2) 83 | 84 | selected_centers1, selected_centers2 = np.array( 85 | selected_centers1), np.array(selected_centers2) 86 | 87 | # return selected_centers1, selected_centers2 88 | # return np.append(selected_centers1, points1, axis=0), np.append(selected_centers2, points2, axis=0) 89 | return points1, points2 90 | 91 | 92 | def main(): 93 | x = np.array([[1, 1], [1, 2], [2, 2], [3, 3]], dtype=np.float32) 94 | y = np.array([[1, 1], [1, 2], [2, 2], [3, 3]], dtype=np.float32) 95 | print(get_group_center(x, y)) 96 | pass 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /py/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | import cv2 6 | 7 | from stitch import Stitcher, Method 8 | 9 | # os.chdir(os.path.dirname(__file__)) 10 | # os.chdir(os.getcwd()) 11 | # debug: 12 | 13 | 14 | def log(*args): 15 | import logging 16 | logging.basicConfig(level=logging.DEBUG, 17 | format='%(asctime)s - %(levelname)s:\n %(message)s') 18 | logging.debug(''.join([str(x) for x in args])) 19 | 20 | 21 | def read_image(*images): 22 | res = [] 23 | for i in images: 24 | res.append(cv2.imread(i)) 25 | return tuple(res) 26 | 27 | 28 | def show_help(): 29 | print(""" 30 | Usage: 31 | {} path_to_image1 path_to_image2 ... path_to_imagen 32 | ----- 33 | To create stitched image 34 | """.format(os.path.basename(__file__))) 35 | exit(1) 36 | 37 | 38 | def main(): 39 | if len(sys.argv) < 2: 40 | show_help() 41 | 42 | images = read_image(*sys.argv[1:]) 43 | image_last = images[0] 44 | for index, image in enumerate(images): 45 | if index == 0: 46 | continue 47 | if image is None: 48 | print('File not exist') 49 | sys.exit(1) 50 | stitcher = Stitcher(image_last, image, Method.SIFT, False) 51 | stitcher.stich(show_match_point=0) 52 | image_last = stitcher.image 53 | cv2.imwrite(sys.argv[1] + 'tmp-{}.jpg'.format(index), image_last) 54 | 55 | cv2.imwrite(sys.argv[1] + 'pano.jpg', image_last) 56 | print("Done") 57 | 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /py/ransac.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-04-21 21:04:04 4 | @Author: ZHAO Lingfeng 5 | @Version : 0.0.1 6 | """ 7 | import random 8 | from copy import deepcopy 9 | from typing import Tuple 10 | 11 | import numpy as np 12 | from scipy import linalg 13 | 14 | __all__ = ['Ransac', 'PositionRansac', 'GeneticRansac', 'NewGeneticRansac', 'GeneticTransform'] 15 | 16 | 17 | class Ransac: 18 | 19 | def __init__(self, data1: np.ndarray, data2: np.ndarray, max_iter_times=1000): 20 | """利用Ransac算法求得变换矩阵 21 | 22 | Args: 23 | data1 (np.ndarray): n*2形状的点数组 24 | data2 (np.ndarray): n*2形状的点数组 25 | max_iter_times (int, optional): Defaults to 1000. 最大迭代次数 26 | 27 | Raises: 28 | ValueError: 输入数组形状不同 29 | """ 30 | random.seed(19270817) 31 | # print(repr(data1), repr(data2)) 32 | self.data1 = data1 33 | self.data2 = data2 34 | if(self.data1.shape != self.data2.shape): 35 | raise ValueError("Argument shape not equal") 36 | 37 | self.points_length = self.data1.shape[0] 38 | self.good_points = 0 39 | self.max_iter_times = max_iter_times 40 | 41 | # 0 for not chosen 42 | self.mask = np.zeros(self.points_length, dtype=np.bool) 43 | 44 | def random_calculate(self, max_try_times=1000)-> np.ndarray: 45 | """随机取四个点对,计算其变换矩阵 46 | max_try_times (int, optional): Defaults to 1000. 选取尝试次数 47 | 48 | Returns: 49 | np.ndarray: 变换矩阵M 50 | """ 51 | 52 | if self.points_length - np.count_nonzero(self.mask) < 4: 53 | return False 54 | 55 | rand_point = random.sample(range(self.points_length), 4) 56 | # try_times = 0 57 | # while np.any(self.mask[rand_point]): 58 | # try_times += 1 59 | # rand_point = random.sample(range(self.points_length), 4) 60 | # if try_times > max_try_times: 61 | # return False 62 | # self.mask[rand_point] = True 63 | M = self.get_perspective_transform(self.data1[rand_point], self.data2[rand_point]) 64 | return M 65 | 66 | @staticmethod 67 | def get_perspective_transform(src: np.ndarray, dst: np.ndarray)-> np.ndarray: 68 | """获取透视变换矩阵 69 | 70 | Args: 71 | src (np.ndarray): 2*4形状 72 | dst (np.ndarray): 2*4形状 73 | 74 | Returns: 75 | np.ndarray: 变换矩阵M 76 | """ 77 | X = np.array((8, 1), np.float) 78 | A = np.zeros((8, 8), np.float) 79 | B = np.zeros((8), np.float) 80 | 81 | for i in range(4): 82 | A[i][0] = A[i + 4][3] = src[i][0] 83 | A[i][1] = A[i + 4][4] = src[i][1] 84 | A[i][2] = A[i + 4][5] = 1 85 | A[i][3] = A[i][4] = A[i][5] = A[i + 4][0] = A[i + 4][1] = A[i + 4][2] = 0 86 | A[i][6] = -src[i][0] * dst[i][0] 87 | A[i][7] = -src[i][1] * dst[i][0] 88 | A[i + 4][6] = -src[i][0] * dst[i][1] 89 | A[i + 4][7] = -src[i][1] * dst[i][1] 90 | B[i] = dst[i][0] 91 | B[i + 4] = dst[i][1] 92 | try: 93 | X = linalg.solve(A, B).copy() 94 | except Exception as e: 95 | # print("Error when calcuating M: ", e) 96 | return False 97 | else: 98 | X.resize((3, 3), refcheck=False) 99 | X[2][2] = 1 100 | return X 101 | 102 | @staticmethod 103 | def perspective_transform(points: np.ndarray, M: np.ndarray)->np.ndarray: 104 | """求得在M变换矩阵情况下points数组里每个点的新变换坐标 105 | 106 | Args: 107 | points (np.ndarray): n*2形状的数组 108 | M (np.ndarray): 3*3的透视变换矩阵 109 | 110 | Returns: 111 | np.ndarray: 变换后的点 112 | """ 113 | 114 | array_i = np.hstack((points, np.ones((points.shape[0], 1), dtype=points.dtype))).T 115 | x = np.dot(M, array_i) 116 | result = np.vstack((x[0] / x[2], x[1] / x[2])).T 117 | return result 118 | 119 | # TODO: get a better threshold or dynamic change it 120 | @classmethod 121 | def get_good_points(cls, points1: np.ndarray, points2: np.ndarray, M: np.ndarray, threshold=3)-> int: 122 | """求得在给定变换矩阵下,计算将points1变换后与points2之间的距离 123 | 124 | Args: 125 | points1 (np.ndarray): n*2形状数组 126 | points2 (np.ndarray): n*2形状数组 127 | M (np.ndarray): 3*3透视变换矩阵 128 | threshold (int, optional): Defaults to 3. 距离阈值 129 | 130 | Returns: 131 | int: 优秀点个数 132 | """ 133 | 134 | transformed = cls.perspective_transform(points1, M) 135 | dis = np.sum((transformed - points2) * (transformed - points2), axis=1) 136 | good = dis < threshold * threshold 137 | return np.sum(good) 138 | 139 | @staticmethod 140 | def get_itereration_time(proportion: float, p=0.995, points=4)->int: 141 | """更新迭代次数 142 | 143 | Args: 144 | proportion (float): 优秀点比例 145 | p (float, optional): Defaults to 0.995. 确信值 146 | points (int, optional): Defaults to 4. 四个点 147 | 148 | Returns: 149 | int: 更新后的所需迭代次数 150 | """ 151 | 152 | proportion = max(min(proportion, 1), 0) 153 | p = max(min(p, 1), 0) 154 | # print(p,proportion) 155 | k = np.log(1 - p) / np.log(1 - np.power(proportion, 4)) 156 | return int(k) 157 | 158 | def run(self, distance=3)->np.ndarray: 159 | """进行计算 160 | 161 | Returns: 162 | 计算的透视变换矩阵 163 | """ 164 | 165 | # best_array = [] 166 | # now_array = [] 167 | iter_times = 0 168 | best_M = None 169 | # random.seed(1) 170 | while iter_times < self.max_iter_times: 171 | M = self.random_calculate() 172 | if M is False: 173 | break 174 | good_nums = self.get_good_points(self.data1, self.data2, M, distance) 175 | if good_nums > self.good_points: 176 | self.good_points = good_nums 177 | best_M = M 178 | self.max_iter_times = min(self.max_iter_times, 179 | self.get_itereration_time(good_nums / self.points_length)) 180 | iter_times += 1 181 | # best_array.append(self.good_points) 182 | # now_array.append(good_nums) 183 | # print("!") 184 | # print(repr(np.array(best_array))) 185 | # print(repr(np.array(now_array))) 186 | 187 | return best_M 188 | 189 | 190 | class PositionRansac(Ransac): 191 | 192 | def __init__(self, data1: np.ndarray, data2: np.ndarray): 193 | """基于点位置加权的RANSAC算法,使得在小相差下对齐更均衡。 194 | 195 | Args: 196 | data1 (np.ndarray): n*2形状的点数组 197 | data2 (np.ndarray): n*2形状的点数组 198 | max_iter_times (int, optional): Defaults to 1000. 最大迭代次数 199 | 200 | Raises: 201 | ValueError: 输入数组形状不同 202 | """ 203 | raise NotImplementedError 204 | super().__init__(data1, data2) 205 | 206 | def random_calculate(self, index=None)-> np.ndarray: 207 | """随机取四个点对,计算其变换矩阵 208 | index (int, optional): Defaults to None. 是否包含某索引的点 209 | 210 | Returns: 211 | np.ndarray: 变换矩阵M 212 | """ 213 | if index is not None: 214 | rand_point = random.sample(range(self.points_length), 3) 215 | while index in rand_point: 216 | rand_point = random.sample(range(self.points_length), 3) 217 | rand_point.append(index) 218 | else: 219 | rand_point = random.sample(range(self.points_length), 4) 220 | 221 | M = self.get_perspective_transform(self.data1[rand_point], self.data2[rand_point]) 222 | return M 223 | 224 | def run_ransac(self, index=None) -> np.ndarray: 225 | """进行计算 226 | 227 | Returns: 228 | 计算的透视变换矩阵 229 | """ 230 | max_iter_times = self.max_iter_times 231 | iter_times = 0 232 | best_M = None 233 | # random.seed(1) 234 | while iter_times < max_iter_times: 235 | M = self.random_calculate(index) 236 | if M is False: 237 | return best_M 238 | good_nums = self.get_good_points(self.data1, self.data2, M) 239 | if good_nums > self.good_points: 240 | self.good_points = good_nums 241 | best_M = M 242 | max_iter_times = min(max_iter_times, 243 | self.get_itereration_time(good_nums / self.points_length)) 244 | iter_times += 1 245 | 246 | return best_M 247 | 248 | def get_farthest_point(self, M: np.ndarray) -> int: 249 | """获取在M变换矩阵下变换距离最远的点 250 | 251 | Args: 252 | M (np.ndarray): 变换矩阵 253 | 254 | Returns: 255 | int: 最远点的索引 256 | """ 257 | transformed = self.perspective_transform(self.data1, M) 258 | dis = np.sum((transformed - self.data2) * (transformed - self.data2), axis=1) 259 | farthest = np.argmax(dis) 260 | return farthest 261 | 262 | def run(self) -> np.ndarray: 263 | 264 | # tmp_M = self.run_ransac() 265 | # farthest = self.get_farthest_point(tmp_M) 266 | # TODO: 距离尝试 267 | # TODO: 利用 k-means算法初步筛选出最远点,然后在保证在点数相同时选择最优点 268 | 269 | return 270 | 271 | 272 | class GeneticRansac(Ransac): 273 | 274 | class Individual: 275 | def __init__(self, dna, value=0): 276 | self.dna = dna 277 | self.value = value 278 | 279 | def __lt__(self, other: 'Individual'): 280 | return self.value < other.value 281 | 282 | def __repr__(self): 283 | return "dna: <{}>, value: {}".format(self.dna, self.value) 284 | 285 | __str__ = __repr__ 286 | 287 | SAMPLE = 30 288 | MUTATION_RATE = 0.1 289 | 290 | def __init__(self, data1: np.ndarray, data2: np.ndarray): 291 | super().__init__(data1, data2) 292 | if self.points_length > 7: 293 | self.population = [] 294 | for i in range(self.SAMPLE): 295 | indv = self.Individual(np.random.choice(range(self.points_length), 4, replace=0)) 296 | self.population.append(indv) 297 | 298 | def run(self): 299 | # 总数过小 300 | if self.points_length < 7: 301 | return super().run() 302 | 303 | for i in range(40): 304 | # 计算总距离 305 | for i in self.population: 306 | M = self.get_perspective_transform(self.data1[i.dna], self.data2[i.dna]) 307 | # 共线或其他失败情况 308 | if M is not False: 309 | good = self.get_good_points(self.data1, self.data2, M) 310 | i.value = good 311 | else: 312 | i.value = 0 313 | self.population = sorted(self.population, reverse=True) 314 | 315 | # 去除一半 316 | all_value = sum([x.value for x in self.population]) 317 | prop = [x.value / all_value for x in self.population] 318 | # TODO: 实现轮盘选择 319 | # self.population = self.population[:self.SAMPLE // 2] 320 | self.population = np.random.choice(self.population, size=self.SAMPLE // 2, replace=False, p=prop) 321 | 322 | children = [] 323 | # 交叉 324 | while len(children) + len(self.population) <= self.SAMPLE: 325 | mother = random.choice(self.population) 326 | father = random.choice(self.population) 327 | corss_index = random.choices(range(4), k=2) 328 | child = deepcopy(mother) 329 | child.dna[corss_index] = father.dna[corss_index] 330 | # 变异 331 | if random.random() < self.MUTATION_RATE: 332 | rand_index = random.choice(range(4)) 333 | rand_point = random.choice(range(self.points_length)) 334 | while child.dna[rand_index] == rand_point: 335 | rand_point = random.choice(range(self.points_length)) 336 | child.dna[rand_index] = rand_point 337 | 338 | children.append(child) 339 | 340 | self.population = np.concatenate((self.population, children)) 341 | 342 | # 获取最优或次优点 343 | M = self.get_perspective_transform(self.data1[self.population[0].dna], self.data2[self.population[0].dna]) 344 | 345 | i = 1 346 | while M is False: 347 | M = self.get_perspective_transform(self.data1[self.population[i].dna], self.data2[self.population[i].dna]) 348 | i += 1 349 | if i > self.SAMPLE: 350 | raise Exception("GA error!!") 351 | self.good_points = self.get_good_points(self.data1, self.data2, M) 352 | return M 353 | 354 | 355 | class NewGeneticRansac(Ransac): 356 | 357 | class Individual: 358 | def __init__(self, dna: np.ndarray, value=0): 359 | self.dna = dna 360 | self.value = value 361 | 362 | def __lt__(self, other: 'Individual'): 363 | return self.value < other.value 364 | 365 | def __repr__(self): 366 | return "dna: <{}>, value: {}".format(self.dna, self.value) 367 | 368 | __str__ = __repr__ 369 | 370 | SAMPLE = 30 371 | GENERATION = 20 372 | MUTATION_RATE = 0.1 373 | MUTATION_COUNT = 1 374 | 375 | def __init__(self, data1: np.ndarray, data2: np.ndarray): 376 | """在ransac的基础上利用遗传算法优化点选 377 | 378 | Args: 379 | data1 (np.ndarray): 位置点1 380 | data2 (np.ndarray): 位置点2 381 | """ 382 | self.data1 = data1 383 | self.data2 = data2 384 | if(self.data1.shape != self.data2.shape): 385 | raise ValueError("Argument shape not equal") 386 | 387 | self.points_length = self.data1.shape[0] 388 | 389 | self.population = [] 390 | for i in range(self.SAMPLE): 391 | ransac = Ransac(data1, data2) 392 | M = ransac.run() 393 | indv = self.Individual(M, ransac.good_points) 394 | self.population.append(indv) 395 | 396 | def run(self): 397 | 398 | for i in range(self.GENERATION): 399 | 400 | self.population = sorted(self.population, reverse=True) 401 | 402 | # 去除一半 403 | self.population = self.population[:self.SAMPLE // 2] 404 | # TODO: 实现轮盘选择 405 | # prop = [x.value / all_value for x in self.population] 406 | # all_value = sum([x.value for x in self.population]) 407 | # self.population = np.random.choice(self.population, size=self.SAMPLE // 2, replace=False, p=prop) 408 | # print("The {} generation after selection: ".format(i), [i.value for i in self.population]) 409 | 410 | children = [] 411 | # 交叉 412 | while len(children) + len(self.population) <= self.SAMPLE: 413 | mother = random.choice(self.population) 414 | father = random.choice(self.population) 415 | # # 交换染色体 416 | # corss_index = random.choices(range(8), k=4) 417 | # child = deepcopy(mother) 418 | # child.dna.put([corss_index], father.dna.take([corss_index])) 419 | # 平均染色体 420 | child = self.Individual((mother.dna + father.dna) / 2) 421 | 422 | # 变异 423 | if random.random() < self.MUTATION_RATE: 424 | rand_index = np.random.choice(range(8), self.MUTATION_COUNT, replace=0) 425 | # 利用卡方分布变异 426 | gene = child.dna.take(rand_index) 427 | gene *= np.random.chisquare(10, rand_index.size) 428 | child.dna.put(rand_index, gene) 429 | child.value = self.get_good_points(self.data1, self.data2, child.dna) 430 | children.append(child) 431 | 432 | self.population = np.concatenate((self.population, children)) 433 | # print("The {} generation after propagate:".format(i), [i.value for i in self.population]) 434 | # end loop for reproduction 435 | 436 | # 获取最优点 437 | self.population = sorted(self.population, reverse=True) 438 | if self.population[0].value > 0: 439 | self.good_points = self.population[0].value 440 | return self.population[0].dna 441 | else: 442 | raise RuntimeError("Cannot get transfrom matrix") 443 | 444 | 445 | class GeneticTransform(Ransac): 446 | 447 | class Individual: 448 | def __init__(self, dna: np.ndarray, value=0): 449 | self.dna = dna 450 | self.value = value 451 | 452 | def __lt__(self, other: 'Individual'): 453 | return self.value < other.value 454 | 455 | def __repr__(self): 456 | return "dna: <{}>, value: {}".format(self.dna, self.value) 457 | 458 | __str__ = __repr__ 459 | 460 | SAMPLE = 40 461 | GROUP_SIZE = 8 462 | GENERATION = 20 463 | MUTATION_RATE = 0.2 464 | MUTATION_COUNT = 1 465 | 466 | def __init__(self, data1: np.ndarray, data2: np.ndarray): 467 | """在ransac的基础上利用遗传算法优化点选 468 | 469 | Args: 470 | data1 (np.ndarray): 位置点1 471 | data2 (np.ndarray): 位置点2 472 | """ 473 | random.seed(19580829) 474 | np.random.seed(19580829) 475 | 476 | self.data1 = data1 477 | self.data2 = data2 478 | if(self.data1.shape != self.data2.shape): 479 | raise ValueError("Argument shape not equal") 480 | 481 | self.points_length = self.data1.shape[0] 482 | if self.points_length < 4: 483 | raise RuntimeError("Not enough points to calculate") 484 | self.GROUP_SIZE = min(self.GROUP_SIZE, max(self.points_length // 2, 4)) 485 | 486 | self.population = [] 487 | try_times = 0 488 | 489 | while len(self.population) < self.SAMPLE: 490 | try_times += 1 491 | if try_times > self.SAMPLE * 4: 492 | if len(self.population) > 1: 493 | break 494 | else: 495 | raise RuntimeError("Cannot find enough points") 496 | choice = np.random.choice(range(self.points_length), self.GROUP_SIZE, replace=0) 497 | M = self.get_lss_matrix(self.data1[choice], self.data2[choice]) 498 | good_points, distance = self.get_value(self.data1, self.data2, M) 499 | if good_points < 4: 500 | continue 501 | indv = self.Individual(M, self.get_judgement(self.data1, self.data2, M)) 502 | self.population.append(indv) 503 | 504 | @classmethod 505 | def get_judgement(cls, points1: np.ndarray, points2: np.ndarray, M: np.ndarray, threshold=3) -> float: 506 | """用于生成适应度 507 | 508 | Args: 509 | good_points (int): 好点的个数 510 | distance (float): 好点的平方和 511 | """ 512 | transformed = cls.perspective_transform(points1, M) 513 | dis = np.sum((transformed - points2) * (transformed - points2), axis=1) 514 | good = dis < threshold * threshold 515 | good_points = np.sum(good) 516 | if good_points == 0: 517 | return -np.sum(dis) 518 | 519 | distance = np.sum(dis[good]) 520 | return good_points - distance / good_points 521 | 522 | @staticmethod 523 | def get_lss_matrix(data1: np.ndarray, data2: np.ndarray) -> np.ndarray: 524 | """利用最小二乘法计算变换矩阵 525 | 526 | Args: 527 | data1 (np.ndarray): 数据一 528 | data2 (np.ndarray): 数据二 529 | 530 | Returns: 531 | np.ndarray: 变换矩阵 532 | """ 533 | 534 | data_length = data1.shape[0] 535 | X = np.array((8, 1), np.float) 536 | A = np.zeros((2 * data_length, 8), np.float) 537 | B = np.zeros((2 * data_length), np.float) 538 | 539 | for i in range(data_length): 540 | A[2 * i][0] = A[2 * i + 1][3] = data1[i][0] 541 | A[2 * i][1] = A[2 * i + 1][4] = data1[i][1] 542 | A[2 * i][2] = A[2 * i + 1][5] = 1 543 | A[2 * i][3] = A[2 * i][4] = A[2 * i][5] = A[2 * i + 1][0] = A[2 * i + 1][1] = A[2 * i + 1][2] = 0 544 | A[2 * i][6] = -data1[i][0] * data2[i][0] 545 | A[2 * i][7] = -data1[i][1] * data2[i][0] 546 | A[2 * i + 1][6] = -data1[i][0] * data2[i][1] 547 | A[2 * i + 1][7] = -data1[i][1] * data2[i][1] 548 | B[2 * i] = data2[i][0] 549 | B[2 * i + 1] = data2[i][1] 550 | 551 | try: 552 | # X = linalg.solve(A, B).copy() 553 | dot = np.dot 554 | AT = A.T 555 | X = dot(dot(linalg.inv(np.dot(AT, A)), AT), B) 556 | 557 | except Exception as e: 558 | print("Error when calcuating M: ", e) 559 | return False 560 | else: 561 | X.resize((3, 3), refcheck=False) 562 | X[2][2] = 1 563 | return X 564 | 565 | @classmethod 566 | def get_value(cls, points1: np.ndarray, points2: np.ndarray, M: np.ndarray, threshold=3)-> Tuple[int, float]: 567 | """求得在给定变换矩阵下,计算将points1变换后与points2之间的距离 568 | 569 | Args: 570 | points1 (np.ndarray): n*2形状数组 571 | points2 (np.ndarray): n*2形状数组 572 | M (np.ndarray): 3*3透视变换矩阵 573 | threshold (int, optional): Defaults to 3. 距离阈值 574 | 575 | Returns: 576 | int: 优秀点个数 577 | float: 优秀点偏差平方和 578 | """ 579 | 580 | transformed = cls.perspective_transform(points1, M) 581 | dis = np.sum((transformed - points2) * (transformed - points2), axis=1) 582 | good = dis < threshold * threshold 583 | error = np.sum(dis[good]) 584 | return np.sum(good), error 585 | 586 | def run(self): 587 | 588 | data = [] 589 | for i in range(self.GENERATION): 590 | 591 | self.population = sorted(self.population, reverse=True) 592 | data.append([j.value for j in self.population[:min(4, len(self.population))]]) 593 | # 去除一半 594 | self.population = self.population[:len(self.population) // 2] 595 | # TODO: 实现轮盘选择 596 | # prop = [x.value / all_value for x in self.population] 597 | # all_value = sum([x.value for x in self.population]) 598 | # self.population = np.random.choice(self.population, size=self.SAMPLE // 2, replace=False, p=prop) 599 | # print("The {} generation after selection: ".format(i), [i.value for i in self.population]) 600 | 601 | # 对前几项进行变异操作 602 | for j in range(min(12, 4 * len(self.population))): 603 | new = deepcopy(self.population[j // 3]) 604 | rand_index = np.random.choice(range(8), self.MUTATION_COUNT, replace=0) 605 | # 用正态分布~利用卡方分布变异~ 606 | gene = new.dna.take(rand_index) 607 | # gene *= np.random.chisquare(2, rand_index.size) 608 | gene *= np.random.normal(1, .1, rand_index.size) 609 | new.dna.put(rand_index, gene) 610 | new.value = self.get_judgement(self.data1, self.data2, new.dna) 611 | self.population.append(new) 612 | 613 | children = [] 614 | # 交叉 615 | while len(children) + len(self.population) <= self.SAMPLE: 616 | mother = random.choice(self.population) 617 | father = random.choice(self.population) 618 | # 交换染色体 619 | corss_index = random.choices(range(8), k=4) 620 | child = deepcopy(mother) 621 | child.dna.put([corss_index], father.dna.take([corss_index])) 622 | # # 平均染色体 623 | # child = self.Individual((mother.dna + father.dna) / 2) 624 | 625 | # 变异 626 | if random.random() < self.MUTATION_RATE: 627 | rand_index = np.random.choice(range(8), self.MUTATION_COUNT, replace=0) 628 | # 利用卡方分布变异 629 | gene = child.dna.take(rand_index) 630 | gene *= np.random.chisquare(2, rand_index.size) 631 | child.dna.put(rand_index, gene) 632 | child.value = self.get_judgement(self.data1, self.data2, child.dna) 633 | children.append(child) 634 | 635 | self.population = np.concatenate((self.population, children)) 636 | # print("The {} generation after propagate:".format(i), [i.value for i in self.population]) 637 | # end loop for reproduction 638 | 639 | # 获取最优点 640 | self.log(np.array(data).T) 641 | self.population = sorted(self.population, reverse=True) 642 | if self.population[0].value > 3: 643 | self.good_points = self.population[0].value 644 | return self.population[0].dna 645 | else: 646 | raise RuntimeError("Cannot get transfrom matrix") 647 | 648 | def log(self, data): 649 | """Save information during the evolution 650 | 651 | Args: 652 | data (any): 数据 653 | """ 654 | print("!!!DATA: ", repr(data)) 655 | 656 | 657 | class GeneticTransformWithPosition(Ransac): 658 | 659 | class Individual: 660 | def __init__(self, dna: np.ndarray, value=0): 661 | self.dna = dna 662 | self.value = value 663 | 664 | def __lt__(self, other: 'Individual'): 665 | return self.value < other.value 666 | 667 | def __repr__(self): 668 | return "dna: <{}>, value: {}".format(self.dna, self.value) 669 | 670 | __str__ = __repr__ 671 | 672 | SAMPLE = 40 673 | GROUP_SIZE = 8 674 | GENERATION = 20 675 | MUTATION_RATE = 0.2 676 | MUTATION_COUNT = 1 677 | 678 | def __init__(self, data1: np.ndarray, data2: np.ndarray): 679 | """在ransac的基础上利用遗传算法优化点选 680 | 681 | Args: 682 | data1 (np.ndarray): 位置点1 683 | data2 (np.ndarray): 位置点2 684 | """ 685 | random.seed(19580829) 686 | np.random.seed(19580829) 687 | 688 | self.data1 = data1 689 | self.data2 = data2 690 | if(self.data1.shape != self.data2.shape): 691 | raise ValueError("Argument shape not equal") 692 | 693 | self.points_length = self.data1.shape[0] 694 | if self.points_length < 4: 695 | raise RuntimeError("Not enough points to calculate") 696 | # 初始化计算点到重心总距离以用于对距离的衡量 697 | centroid = np.sum(data1, axis=0) / self.points_length 698 | self.distance = np.sum(np.square(data1 - centroid)) / self.points_length 699 | 700 | self.GROUP_SIZE = min(self.GROUP_SIZE, max(self.points_length // 2, 4)) 701 | 702 | self.population = [] 703 | try_times = 0 704 | 705 | while len(self.population) < self.SAMPLE: 706 | try_times += 1 707 | if try_times > self.SAMPLE * 4: 708 | if len(self.population) > 1: 709 | break 710 | else: 711 | raise RuntimeError("Cannot find enough points") 712 | choice = np.random.choice(range(self.points_length), self.GROUP_SIZE, replace=0) 713 | M = self.get_lss_matrix(self.data1[choice], self.data2[choice]) 714 | good_points, distance = self.get_value(self.data1, self.data2, M) 715 | if good_points < 4: 716 | continue 717 | indv = self.Individual(M, self.get_judgement(self.data1, self.data2, M)) 718 | self.population.append(indv) 719 | 720 | def get_judgement(self, points1: np.ndarray, points2: np.ndarray, M: np.ndarray, threshold=3) -> float: 721 | """用于生成适应度 722 | 723 | Args: 724 | good_points (int): 好点的个数 725 | distance (float): 好点的平方和 726 | """ 727 | transformed = self.perspective_transform(points1, M) 728 | dis = np.sum((transformed - points2) * (transformed - points2), axis=1) 729 | good = dis < threshold * threshold 730 | """ 731 | 这里我们应用位置偏离来作为适应度函数 732 | 即利用所有点与重心的偏离值的平均值来作为适应度函数 733 | """ 734 | good_points = np.sum(good) 735 | 736 | if good_points == 0: 737 | return -np.sum(dis) 738 | 739 | centroid = np.sum(points1[good], axis=0) / good_points 740 | distance = np.sum(np.square(points1[good] - centroid)) 741 | """鉴于距离相差较远,我们利用 tanh 来对其进行归一,从而加大点数量对结果的影响而不是只是分散距离""" 742 | stand_error = np.tanh(((distance / good_points) - self.distance) / self.distance) + 1 # 保持正数 743 | interier_error = np.sum(dis[good]) / good_points 744 | 745 | return good_points + stand_error - interier_error 746 | 747 | @staticmethod 748 | def get_lss_matrix(data1: np.ndarray, data2: np.ndarray) -> np.ndarray: 749 | """利用最小二乘法计算变换矩阵 750 | 751 | Args: 752 | data1 (np.ndarray): 数据一 753 | data2 (np.ndarray): 数据二 754 | 755 | Returns: 756 | np.ndarray: 变换矩阵 757 | """ 758 | 759 | data_length = data1.shape[0] 760 | X = np.array((8, 1), np.float) 761 | A = np.zeros((2 * data_length, 8), np.float) 762 | B = np.zeros((2 * data_length), np.float) 763 | 764 | for i in range(data_length): 765 | A[2 * i][0] = A[2 * i + 1][3] = data1[i][0] 766 | A[2 * i][1] = A[2 * i + 1][4] = data1[i][1] 767 | A[2 * i][2] = A[2 * i + 1][5] = 1 768 | A[2 * i][3] = A[2 * i][4] = A[2 * i][5] = A[2 * i + 1][0] = A[2 * i + 1][1] = A[2 * i + 1][2] = 0 769 | A[2 * i][6] = -data1[i][0] * data2[i][0] 770 | A[2 * i][7] = -data1[i][1] * data2[i][0] 771 | A[2 * i + 1][6] = -data1[i][0] * data2[i][1] 772 | A[2 * i + 1][7] = -data1[i][1] * data2[i][1] 773 | B[2 * i] = data2[i][0] 774 | B[2 * i + 1] = data2[i][1] 775 | 776 | try: 777 | # X = linalg.solve(A, B).copy() 778 | dot = np.dot 779 | AT = A.T 780 | X = dot(dot(linalg.inv(np.dot(AT, A)), AT), B) 781 | 782 | except Exception as e: 783 | print("Error when calcuating M: ", e) 784 | return False 785 | else: 786 | X.resize((3, 3), refcheck=False) 787 | X[2][2] = 1 788 | return X 789 | 790 | @classmethod 791 | def get_value(cls, points1: np.ndarray, points2: np.ndarray, M: np.ndarray, threshold=3)-> Tuple[int, float]: 792 | """求得在给定变换矩阵下,计算将points1变换后与points2之间的距离 793 | 794 | Args: 795 | points1 (np.ndarray): n*2形状数组 796 | points2 (np.ndarray): n*2形状数组 797 | M (np.ndarray): 3*3透视变换矩阵 798 | threshold (int, optional): Defaults to 3. 距离阈值 799 | 800 | Returns: 801 | int: 优秀点个数 802 | float: 优秀点偏差平方和 803 | """ 804 | 805 | transformed = cls.perspective_transform(points1, M) 806 | dis = np.sum((transformed - points2) * (transformed - points2), axis=1) 807 | good = dis < threshold * threshold 808 | 809 | if np.sum(good) > 0: 810 | centroid = np.sum(points1[good], axis=0) / np.sum(good) 811 | distance = np.sum(np.square(points1[good] - centroid)) 812 | error = distance / np.sum(good) 813 | else: 814 | error = 0 815 | return np.sum(good), error 816 | 817 | def run(self): 818 | 819 | data = [] 820 | for i in range(self.GENERATION): 821 | 822 | self.population = sorted(self.population, reverse=True) 823 | data.append([j.value for j in self.population[:min(4, len(self.population))]]) 824 | # 去除一半 825 | self.population = self.population[:len(self.population) // 2] 826 | # TODO: 实现轮盘选择 827 | # prop = [x.value / all_value for x in self.population] 828 | # all_value = sum([x.value for x in self.population]) 829 | # self.population = np.random.choice(self.population, size=self.SAMPLE // 2, replace=False, p=prop) 830 | # print("The {} generation after selection: ".format(i), [i.value for i in self.population]) 831 | 832 | # 对前几项进行变异操作 833 | for j in range(min(12, 4 * len(self.population))): 834 | new = deepcopy(self.population[j // 3]) 835 | rand_index = np.random.choice(range(8), self.MUTATION_COUNT, replace=0) 836 | # 用正态分布~利用卡方分布变异~ 837 | gene = new.dna.take(rand_index) 838 | # gene *= np.random.chisquare(2, rand_index.size) 839 | gene *= np.random.normal(1, .1, rand_index.size) 840 | new.dna.put(rand_index, gene) 841 | new.value = self.get_judgement(self.data1, self.data2, new.dna) 842 | self.population.append(new) 843 | 844 | children = [] 845 | # 交叉 846 | while len(children) + len(self.population) <= self.SAMPLE: 847 | mother = random.choice(self.population) 848 | father = random.choice(self.population) 849 | # 交换染色体 850 | corss_index = random.choices(range(8), k=4) 851 | child = deepcopy(mother) 852 | child.dna.put([corss_index], father.dna.take([corss_index])) 853 | # # 平均染色体 854 | # child = self.Individual((mother.dna + father.dna) / 2) 855 | 856 | # 变异 857 | if random.random() < self.MUTATION_RATE: 858 | rand_index = np.random.choice(range(8), self.MUTATION_COUNT, replace=0) 859 | # 利用卡方分布变异 860 | gene = child.dna.take(rand_index) 861 | gene *= np.random.chisquare(2, rand_index.size) 862 | child.dna.put(rand_index, gene) 863 | child.value = self.get_judgement(self.data1, self.data2, child.dna) 864 | children.append(child) 865 | 866 | self.population = np.concatenate((self.population, children)) 867 | # print("The {} generation after propagate:".format(i), [i.value for i in self.population]) 868 | # end loop for reproduction 869 | 870 | # 获取最优点 871 | self.log(np.array(data).T) 872 | self.population = sorted(self.population, reverse=True) 873 | if self.population[0].value > 3: 874 | self.good_points = self.population[0].value 875 | return self.population[0].dna 876 | else: 877 | raise RuntimeError("Cannot get transfrom matrix") 878 | 879 | def log(self, data): 880 | """Save information during the evolution 881 | 882 | Args: 883 | data (any): 数据 884 | """ 885 | print("!!!DATA: ", repr(data)) 886 | 887 | 888 | if(1): 889 | GeneticTransform = GeneticTransformWithPosition 890 | import warnings 891 | warnings.warn( 892 | "In this program, original ga algorithm is replaced with a exprimental one, to disable this, go to this file and comment the line") 893 | 894 | 895 | def main(): 896 | pass 897 | test() 898 | 899 | 900 | def test(): 901 | import time 902 | 903 | data_point1 = np.array([[1, 2], [3, 3], [5, 5], [6, 8]]) 904 | data_point2 = np.array([[4, 2], [5, 3], [12, 5], [64, 8]]) 905 | ransac = Ransac(data_point1, data_point2) 906 | M = ransac.get_perspective_transform(data_point1, data_point2) 907 | print(M) 908 | print("Supposed to be:\n", 909 | "[[-0.76850095, 1.15180266, 1.77229602]\n", 910 | "[ 0.20777989, -0.31593928, 2.07779886],\n", 911 | "[-0.10388994, -0.03462998, 1. ]]") 912 | print(ransac.get_good_points(data_point1, data_point1, M)) 913 | 914 | test_data = np.random.rand(40, 2) * 100 915 | dst_data = Ransac.perspective_transform(test_data, M) 916 | dst_data[:10, :] = np.random.rand(10, 2) 917 | start = time.time() 918 | ransac = Ransac(test_data, dst_data) 919 | result_M = ransac.run() 920 | print("Spent time: ", time.time() - start) 921 | print("Result M:") 922 | print(result_M) 923 | print("Max itereration times") 924 | print(ransac.max_iter_times) 925 | 926 | # Test for GA 927 | print("===== Test for ga =====") 928 | data1 = np.array([[520.12805, 243.64803], [4679.038, 627.0568], [508.80002, 277.2], [259.78067, 403.10794], [661.2, 199.20001], [695.13727, 232.90681], 929 | [831.51373, 134.78401], [512.395, 243.65637], [329.65274, 756.0514], [502., 292.], 930 | [523.2, 244.8], [522.72003, 244.8], [1870.3875, 1967.8467], [5208.884, 904.0897], 931 | [661., 199.], [1872.2125, 1967.764], [1003.2909, 240.07318], [316., 736.], [ 932 | 612., 170.40001], [520.4737, 244.68483], [534.98895, 199.06564], 933 | [523., 245.], [582., 220.], [5072., 756.], [391.68002, 116.64001], [4572.2886, 1094.861], [1871.217, 1968.2616], [1003.2909, 238.87878]], dtype=np.float32) 934 | data2 = np.array([[1080.0001, 221.18402], [5297.137, 400.12195], [1069.2001, 253.20001], [845.0337, 394.15], [1218., 168.], [1250.5305, 200.65819], 935 | [1374.797, 93.31201], [1074.9546, 222.15727], [910.1282, 730.9691], 936 | [1093., 177.], [1082., 221.], [1081.4401, 220.32], [2401.2292, 1874.5347], 937 | [708., 389.], [1218., 168.], [2403.7178, 1875.1985], [1540.7682, 186.32545], [ 938 | 901., 711.], [1153., 261.], [1078.2721, 221.87523], [1094.8611, 176.67076], 939 | [847., 44.], [1426., 175.], [1244.4, 326.40002], [823., 32.], [5173.633, 906.1633], [2401.2292, 1873.7054], [1540.7682, 185.13106]], dtype=np.float32) 940 | start = time.time() 941 | gr = GeneticRansac(data1, data2) 942 | print("Spent time: ", time.time() - start) 943 | print(gr.run()) 944 | print("===== Test for new ga =====") 945 | start = time.time() 946 | gr = NewGeneticRansac(data1, data2) 947 | print("Spent time: ", time.time() - start) 948 | print(gr.run()) 949 | print("===== Test for genetic tranform =====") 950 | print(GeneticTransform.get_lss_matrix(data_point1, data_point2)) 951 | print(GeneticTransform(data1, data2).run()) 952 | print(GeneticTransform.get_value(data_point1, data_point2, M)) 953 | 954 | 955 | if __name__ == "__main__": 956 | main() 957 | -------------------------------------------------------------------------------- /py/ransac_evolve.txt: -------------------------------------------------------------------------------- 1 | Image 3 start stitching, using Method.SIFT 2 | Using the genetic method 3 | max distance: 98.8028335571289 4 | Min distance: 49.264591217041016 5 | match_len: 22 6 | Good points and average distance: (18, 10.932089497253846) 7 | 1017 1104 8 | Generating mask 9 | Blending 10 | Time: 1.5152592658996582 11 | #===================================================# 12 | Image 3 start stitching, using Method.SIFT 13 | max distance: 98.8028335571289 14 | Min distance: 49.264591217041016 15 | match_len: 22 16 | ! 17 | array([14, 14, 14, 14, 15, 15, 15, 18]) 18 | array([14, 10, 11, 5, 15, 14, 12, 18]) 19 | Good points and average distance: (18, 20.18702599541669) 20 | 1038 1126 21 | Generating mask 22 | Blending 23 | Time: 1.5981316566467285 24 | #===================================================# 25 | Image 3 start stitching, using Method.ORB 26 | Using the genetic method 27 | max distance: 21.0 28 | Min distance: 5.0 29 | match_len: 25 30 | Good points and average distance: (25, 32.043230744614725) 31 | 1027 1088 32 | Generating mask 33 | Blending 34 | Time: 1.661045789718628 35 | #===================================================# 36 | Image 3 start stitching, using Method.ORB 37 | max distance: 21.0 38 | Min distance: 5.0 39 | match_len: 25 40 | ! 41 | array([18, 18, 18, 18, 18, 20, 20, 20, 20, 20]) 42 | array([18, 16, 12, 6, 4, 20, 20, 10, 18, 10]) 43 | Good points and average distance: (20, 60.3707035317136) 44 | 1086 1432 45 | Generating mask 46 | Blending 47 | Time: 0.6970057487487793 48 | #===================================================# 49 | Image 19 start stitching, using Method.SIFT 50 | Using the genetic method 51 | max distance: 64.79969024658203 52 | Min distance: 32.264530181884766 53 | match_len: 9 54 | Good points and average distance: (9, 3.0464024342360734) 55 | 1844 1073 56 | Generating mask 57 | Blending 58 | Time: 2.5668134689331055 59 | #===================================================# 60 | Image 19 start stitching, using Method.SIFT 61 | max distance: 64.79969024658203 62 | Min distance: 32.264530181884766 63 | match_len: 9 64 | ! 65 | array([8, 8]) 66 | array([8, 7]) 67 | Good points and average distance: (8, 3.683806721072607e-11) 68 | 1567 1035 69 | Generating mask 70 | Blending 71 | Time: 1.823291301727295 72 | #===================================================# 73 | Image 19 start stitching, using Method.ORB 74 | Using the genetic method 75 | max distance: 25.0 76 | Min distance: 12.0 77 | match_len: 40 78 | Good points and average distance: (38, 64.85370923411021) 79 | 1543 965 80 | Generating mask 81 | Blending 82 | Time: 1.1398084163665771 83 | #===================================================# 84 | Image 19 start stitching, using Method.ORB 85 | max distance: 25.0 86 | Min distance: 12.0 87 | match_len: 40 88 | ! 89 | array([ 5, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30]) 90 | array([ 5, 30, 9, 5, 24, 28, 14, 25, 16, 29, 12, 16, 11]) 91 | Good points and average distance: (30, 57.34616045406859) 92 | 1543 960 93 | Generating mask 94 | Blending 95 | Time: 0.8766212463378906 96 | #===================================================# 97 | Image 20 start stitching, using Method.SIFT 98 | Using the genetic method 99 | max distance: 92.71461486816406 100 | Min distance: 46.14108657836914 101 | match_len: 10 102 | Good points and average distance: (10, 4.343378065113296) 103 | 1571 997 104 | Generating mask 105 | Blending 106 | Time: 2.0853466987609863 107 | #===================================================# 108 | Image 20 start stitching, using Method.SIFT 109 | max distance: 92.71461486816406 110 | Min distance: 46.14108657836914 111 | match_len: 10 112 | ! 113 | array([ 4, 10]) 114 | array([ 4, 10]) 115 | Good points and average distance: (10, 9.497811912915923) 116 | 1579 988 117 | Generating mask 118 | Blending 119 | Time: 1.3600058555603027 120 | #===================================================# 121 | Image 20 start stitching, using Method.ORB 122 | Using the genetic method 123 | max distance: 27.0 124 | Min distance: 14.0 125 | match_len: 40 126 | Good points and average distance: (39, 104.8976212931571) 127 | 1607 1001 128 | Generating mask 129 | Blending 130 | Time: 0.7945644855499268 131 | #===================================================# 132 | Image 20 start stitching, using Method.ORB 133 | max distance: 27.0 134 | Min distance: 14.0 135 | match_len: 40 136 | ! 137 | array([29, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30]) 138 | array([29, 5, 9, 9, 7, 5, 8, 2, 12, 30, 8, 26, 4]) 139 | Good points and average distance: (30, 87.16254340156931) 140 | 1579 991 141 | Generating mask 142 | Blending 143 | Time: 0.6123478412628174 144 | #===================================================# 145 | Image 22 start stitching, using Method.SIFT 146 | Using the genetic method 147 | max distance: 56.665687561035156 148 | Min distance: 28.1069393157959 149 | match_len: 37 150 | Good points and average distance: (24, 37.186141551398606) 151 | 2395 2581 152 | Generating mask 153 | Blending 154 | Time: 5.022632122039795 155 | #===================================================# 156 | Image 22 start stitching, using Method.SIFT 157 | max distance: 56.665687561035156 158 | Min distance: 28.1069393157959 159 | match_len: 37 160 | ! 161 | array([ 5, 11, 11]) 162 | array([ 5, 11, 4]) 163 | Good points and average distance: (11, 30.989806866311703) 164 | 2297 2207 165 | Generating mask 166 | Blending 167 | Time: 4.231024980545044 168 | #===================================================# 169 | Image 22 start stitching, using Method.ORB 170 | Using the genetic method 171 | max distance: 18.0 172 | Min distance: 8.0 173 | match_len: 40 174 | Good points and average distance: (37, 52.50594105638672) 175 | 2405 2569 176 | Generating mask 177 | Blending 178 | Time: 2.4052321910858154 179 | #===================================================# 180 | Image 22 start stitching, using Method.ORB 181 | max distance: 18.0 182 | Min distance: 8.0 183 | match_len: 40 184 | ! 185 | array([12, 14, 14, 14, 14, 36]) 186 | array([12, 14, 4, 9, 10, 36]) 187 | Good points and average distance: (36, 43.769678258932544) 188 | 2397 2593 189 | Generating mask 190 | Blending 191 | Time: 2.3655812740325928 192 | #===================================================# 193 | Image 23 start stitching, using Method.SIFT 194 | Using the genetic method 195 | max distance: 169.44320678710938 196 | Min distance: 82.55906677246094 197 | match_len: 8 198 | Good points and average distance: (5, 1.3498181311013289) 199 | 2328 2464 200 | Generating mask 201 | Blending 202 | Time: 4.788411617279053 203 | #===================================================# 204 | Image 23 start stitching, using Method.SIFT 205 | max distance: 169.44320678710938 206 | Min distance: 82.55906677246094 207 | match_len: 8 208 | ! 209 | array([4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 210 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]) 211 | array([4, 5, 4, 4, 4, 4, 4, 5, 5, 4, 5, 4, 4, 5, 4, 5, 4, 4, 4, 4, 4, 4, 212 | 4, 5, 4, 4, 4, 4, 4, 4, 5, 4]) 213 | Good points and average distance: (5, 2.3908831710540936) 214 | 2411 2494 215 | Generating mask 216 | Blending 217 | Time: 4.614869594573975 218 | #===================================================# 219 | Image 23 start stitching, using Method.ORB 220 | Using the genetic method 221 | max distance: 31.0 222 | Min distance: 15.0 223 | match_len: 14 224 | Good points and average distance: (13, 28.759643882944143) 225 | 2785 3005 226 | Generating mask 227 | Blending 228 | Time: 3.0411500930786133 229 | #===================================================# 230 | Image 23 start stitching, using Method.ORB 231 | max distance: 31.0 232 | Min distance: 15.0 233 | match_len: 14 234 | ! 235 | array([ 6, 6, 10, 10, 10, 10, 10, 10, 11, 11, 11]) 236 | array([ 6, 6, 10, 6, 5, 5, 10, 6, 11, 8, 7]) 237 | Good points and average distance: (11, 26.665471859780013) 238 | 2591 2873 239 | Generating mask 240 | Blending 241 | Time: 2.7789688110351562 242 | #===================================================# 243 | Image 24 start stitching, using Method.SIFT 244 | Using the genetic method 245 | max distance: 75.02666473388672 246 | Min distance: 37.48332977294922 247 | match_len: 11 248 | Good points and average distance: (11, 1.844682961284418) 249 | 2477 2624 250 | Generating mask 251 | Blending 252 | Time: 7.372722625732422 253 | #===================================================# 254 | Image 24 start stitching, using Method.SIFT 255 | max distance: 75.02666473388672 256 | Min distance: 37.48332977294922 257 | match_len: 11 258 | ! 259 | array([ 5, 5, 11]) 260 | array([ 5, 5, 11]) 261 | Good points and average distance: (11, 10.12827853925404) 262 | 2493 2693 263 | Generating mask 264 | Blending 265 | Time: 5.104132652282715 266 | #===================================================# 267 | Image 24 start stitching, using Method.ORB 268 | Using the genetic method 269 | max distance: 21.0 270 | Min distance: 9.0 271 | match_len: 18 272 | Good points and average distance: (17, 22.80218605781505) 273 | 2477 2606 274 | Generating mask 275 | Blending 276 | Time: 2.6523547172546387 277 | #===================================================# 278 | Image 24 start stitching, using Method.ORB 279 | max distance: 21.0 280 | Min distance: 9.0 281 | match_len: 18 282 | ! 283 | array([ 9, 14, 15, 15, 15, 15, 15, 15]) 284 | array([ 9, 14, 15, 5, 5, 6, 7, 4]) 285 | Good points and average distance: (15, 42.33270675596344) 286 | 2508 2689 287 | Generating mask 288 | Blending 289 | Time: 3.0191903114318848 290 | #===================================================# 291 | Image 27 start stitching, using Method.SIFT 292 | Using the genetic method 293 | max distance: 64.1248779296875 294 | Min distance: 31.93743896484375 295 | match_len: 39 296 | Good points and average distance: (38, 60.214962948896556) 297 | 2226 3103 298 | Generating mask 299 | Blending 300 | Time: 6.800878524780273 301 | #===================================================# 302 | Image 27 start stitching, using Method.SIFT 303 | max distance: 64.1248779296875 304 | Min distance: 31.93743896484375 305 | match_len: 39 306 | ! 307 | array([20, 24, 35, 35, 35]) 308 | array([20, 24, 35, 16, 7]) 309 | Good points and average distance: (35, 110.99754578956838) 310 | 2228 3105 311 | Generating mask 312 | Blending 313 | Time: 6.587231397628784 314 | #===================================================# 315 | Image 27 start stitching, using Method.ORB 316 | Using the genetic method 317 | max distance: 21.0 318 | Min distance: 7.0 319 | match_len: 17 320 | Good points and average distance: (16, 33.045773747785056) 321 | 2230 3140 322 | Generating mask 323 | Blending 324 | Time: 2.5573511123657227 325 | #===================================================# 326 | Image 27 start stitching, using Method.ORB 327 | max distance: 21.0 328 | Min distance: 7.0 329 | match_len: 17 330 | ! 331 | array([12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13]) 332 | array([12, 10, 6, 12, 13, 11, 6, 7, 10, 4, 9, 13]) 333 | Good points and average distance: (13, 27.64814619002486) 334 | 2115 3052 335 | Generating mask 336 | Blending 337 | Time: 2.305124044418335 338 | #===================================================# 339 | Image 29 start stitching, using Method.SIFT 340 | Using the genetic method 341 | max distance: 88.91568756103516 342 | Min distance: 45.45327377319336 343 | match_len: 40 344 | Good points and average distance: (26, 47.19115836581838) 345 | 5547 3298 346 | Generating mask 347 | Blending 348 | Time: 10.34068512916565 349 | #===================================================# 350 | Image 29 start stitching, using Method.SIFT 351 | max distance: 88.91568756103516 352 | Min distance: 45.45327377319336 353 | match_len: 40 354 | ! 355 | array([ 5, 5, 5, 5, 5, 6, 8, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 356 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 20, 20, 20, 20, 357 | 20, 22, 22, 22, 22, 22]) 358 | array([ 5, 5, 5, 4, 4, 6, 8, 5, 5, 5, 6, 16, 5, 6, 4, 8, 5, 359 | 8, 4, 5, 5, 5, 4, 5, 6, 7, 4, 4, 6, 20, 6, 4, 2, 5, 360 | 5, 22, 6, 9, 9, 4]) 361 | Good points and average distance: (22, 14.806612001420628) 362 | 5766 3409 363 | Generating mask 364 | Blending 365 | Time: 10.871376514434814 366 | #===================================================# 367 | Image 29 start stitching, using Method.ORB 368 | Using the genetic method 369 | max distance: 29.0 370 | Min distance: 14.0 371 | match_len: 16 372 | Good points and average distance: (14, 30.805548842145893) 373 | 6954 4890 374 | Generating mask 375 | Blending 376 | Time: 17.047736406326294 377 | #===================================================# 378 | Image 29 start stitching, using Method.ORB 379 | max distance: 29.0 380 | Min distance: 14.0 381 | match_len: 16 382 | ! 383 | array([ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11, 11, 11, 384 | 11, 11, 11]) 385 | array([ 8, 6, 7, 7, 5, 4, 6, 6, 8, 5, 11, 6, 9, 9, 4, 4, 5, 386 | 9, 4, 4]) 387 | Good points and average distance: (11, 20.38021242159148) 388 | 5530 3421 389 | Generating mask 390 | Blending 391 | Time: 6.658377170562744 392 | #===================================================# 393 | [ INFO:0] Initialize OpenCL runtime... 394 | -------------------------------------------------------------------------------- /py/shrink_to_25.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-05-09 11:15:15 4 | @Author: ZHAO Lingfeng 5 | @Version : 0.0.1 6 | """ 7 | import os 8 | 9 | from PIL import Image 10 | 11 | 12 | def main(): 13 | while 1: 14 | file = input("input image path or drag image here:\n") 15 | try: 16 | image = Image.open(file) 17 | x, y = image.size 18 | image = image.resize((x // 2, y // 2)) 19 | image.save(file) 20 | print(os.path.basename(file), " Done!") 21 | 22 | except Exception as e: 23 | print("wrong image file: ", e) 24 | continue 25 | 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /py/stitch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-03-13 15:29:29 4 | @Author: Ben 5 | @Version : 0.0.1 6 | """ 7 | 8 | from enum import Enum 9 | from typing import List, Tuple, Union 10 | import unittest 11 | import os 12 | import random 13 | 14 | import cv2 15 | import numpy as np 16 | 17 | import k_means 18 | import ransac 19 | import blend 20 | 21 | 22 | def show_image(image: np.ndarray) -> None: 23 | from PIL import Image 24 | Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)).show() 25 | 26 | 27 | class Method(Enum): 28 | 29 | SURF = cv2.xfeatures2d.SURF_create 30 | SIFT = cv2.xfeatures2d.SIFT_create 31 | ORB = cv2.ORB_create 32 | 33 | 34 | colors = ((123, 234, 12), (23, 44, 240), (224, 120, 34), (21, 234, 190), 35 | (80, 160, 200), (243, 12, 100), (25, 90, 12), (123, 10, 140)) 36 | 37 | 38 | class Area: 39 | 40 | def __init__(self, *points): 41 | 42 | self.points = list(points) 43 | 44 | def is_inside(self, x: Union[float, Tuple[float, float]], y: float=None) -> bool: 45 | if isinstance(x, tuple): 46 | x, y = x 47 | raise NotImplementedError() 48 | 49 | 50 | class Matcher(): 51 | 52 | def __init__(self, image1: np.ndarray, image2: np.ndarray, method: Enum=Method.SIFT, threshold=800) -> None: 53 | """输入两幅图像,计算其特征值 54 | 此类用于输入两幅图像,计算其特征值,输入两幅图像分别为numpy数组格式的图像,其中的method参数要求输入SURF、SIFT或者ORB,threshold参数为特征值检测所需的阈值。 55 | 56 | Args: 57 | image1 (np.ndarray): 图像一 58 | image2 (np.ndarray): 图像二 59 | method (Enum, optional): Defaults to Method.SIFT. 特征值检测方法 60 | threshold (int, optional): Defaults to 800. 特征值阈值 61 | 62 | """ 63 | 64 | self.image1 = image1 65 | self.image2 = image2 66 | self.method = method 67 | self.threshold = threshold 68 | 69 | self._keypoints1: List[cv2.KeyPoint] = None 70 | self._descriptors1: np.ndarray = None 71 | self._keypoints2: List[cv2.KeyPoint] = None 72 | self._descriptors2: np.ndarray = None 73 | 74 | if self.method == Method.ORB: 75 | # error if not set this 76 | self.matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) 77 | else: 78 | # self.matcher = cv2.BFMatcher(crossCheck=True) 79 | self.matcher = cv2.FlannBasedMatcher() 80 | 81 | self.match_points = [] 82 | 83 | self.image_points1 = np.array([]) 84 | self.image_points2 = np.array([]) 85 | 86 | def compute_keypoint(self) -> None: 87 | """计算特征点 88 | 利用给出的特征值检测方法对图像进行特征值检测。 89 | 90 | Args: 91 | image (np.ndarray): 图像 92 | """ 93 | feature = self.method.value(self.threshold) 94 | self._keypoints1, self._descriptors1 = feature.detectAndCompute( 95 | self.image1, None) 96 | self._keypoints2, self._descriptors2 = feature.detectAndCompute( 97 | self.image2, None) 98 | 99 | def match(self, max_match_lenth=20, threshold=0.04, show_match=False): 100 | """对两幅图片计算得出的特征值进行匹配,对ORB来说使用OpenCV的BFMatcher算法,而对于其他特征检测方法则使用FlannBasedMatcher算法。 101 | 102 | max_match_lenth (int, optional): Defaults to 20. 最大匹配点数量 103 | threshold (float, optional): Defaults to 0.04. 默认最大匹配距离差 104 | show_match (bool, optional): Defaults to False. 是否展示匹配结果 105 | """ 106 | 107 | self.compute_keypoint() 108 | 109 | '''计算两张图片中的配对点,并至多取其中最优的`max_match_lenth`个''' 110 | self.match_points = sorted(self.matcher.match( 111 | self._descriptors1, self._descriptors2), key=lambda x: x.distance) 112 | 113 | match_len = min(len(self.match_points), max_match_lenth) 114 | 115 | # in case distance is 0 116 | max_distance = max(2 * self.match_points[0].distance, 20) 117 | 118 | for i in range(match_len): 119 | if self.match_points[i].distance > max_distance: 120 | match_len = i 121 | break 122 | print('max distance: ', self.match_points[match_len].distance) 123 | print("Min distance: ", self.match_points[0].distance) 124 | print('match_len: ', match_len) 125 | assert(match_len >= 4) 126 | self.match_points = self.match_points[:match_len] 127 | 128 | if show_match: 129 | img3 = cv2.drawMatches(self.image1, self._keypoints1, self.image2, self._keypoints2, 130 | self.match_points, None, flags=0) 131 | show_image(img3) 132 | # cv2.imwrite('../resource/3-sift-match.jpg', img3) 133 | 134 | '''由最佳匹配取得匹配点对,并进行形变拼接''' 135 | image_points1, image_points2 = [], [] 136 | for i in self.match_points: 137 | image_points1.append(self._keypoints1[i.queryIdx].pt) 138 | image_points2.append(self._keypoints2[i.trainIdx].pt) 139 | 140 | self.image_points1 = np.float32(image_points1) 141 | self.image_points2 = np.float32(image_points2) 142 | 143 | # print(image_points1) 144 | 145 | 146 | def get_weighted_points(image_points: np.ndarray): 147 | 148 | average = np.average(image_points, axis=0) 149 | 150 | max_index = np.argmax(np.linalg.norm((image_points - average), axis=1)) 151 | return np.append(image_points, np.array([image_points[max_index]]), axis=0) 152 | 153 | 154 | class Stitcher: 155 | 156 | def __init__(self, image1: np.ndarray, image2: np.ndarray, method: Enum=Method.SIFT, use_kmeans=False): 157 | """输入图像和匹配,对图像进行拼接 158 | 目前采用简单矩阵匹配和平均值拼合 159 | 160 | Args: 161 | image1 (np.ndarray): 图像一 162 | image2 (np.ndarray): 图像二 163 | matcher (Matcher): 匹配结果 164 | use_kmeans (bool): 是否使用kmeans 优化点选择 165 | """ 166 | 167 | self.image1 = image1 168 | self.image2 = image2 169 | self.method = method 170 | self.use_kmeans = use_kmeans 171 | self.matcher = Matcher(image1, image2, method=method) 172 | self.M = np.eye(3) 173 | 174 | self.image = None 175 | 176 | def stich(self, show_result=True, max_match_lenth=40, show_match_point=True, use_partial=False, use_new_match_method=False, use_gauss_blend=True): 177 | """对图片进行拼合 178 | 179 | show_result (bool, optional): Defaults to True. 是否展示拼合图像 180 | show_match_point (bool, optional): Defaults to True. 是否展示拼合点 181 | """ 182 | self.matcher.match(max_match_lenth=max_match_lenth, 183 | show_match=show_match_point) 184 | 185 | if self.use_kmeans: 186 | self.image_points1, self.image_points2 = k_means.get_group_center( 187 | self.matcher.image_points1, self.matcher.image_points2) 188 | else: 189 | self.image_points1, self.image_points2 = ( 190 | self.matcher.image_points1, self.matcher.image_points2) 191 | 192 | if use_new_match_method: 193 | self.M = ransac.GeneticTransform(self.image_points1, self.image_points2).run() 194 | else: 195 | self.M, _ = cv2.findHomography( 196 | self.image_points1, self.image_points2, method=cv2.RANSAC) 197 | # self.M = ransac.Ransac(self.image_points1, self.image_points2).run() 198 | 199 | print("Good points and average distance: ", ransac.GeneticTransform.get_value( 200 | self.image_points1, self.image_points2, self.M)) 201 | 202 | left, right, top, bottom = self.get_transformed_size() 203 | # print(self.get_transformed_size()) 204 | width = int(max(right, self.image2.shape[1]) - min(left, 0)) 205 | height = int(max(bottom, self.image2.shape[0]) - min(top, 0)) 206 | print(width, height) 207 | # width, height = min(width, 10000), min(height, 10000) 208 | if width * height > 8000 * 5000: 209 | # raise MemoryError("Too large to get the combination") 210 | factor = width*height/(8000*5000) 211 | width = int(width/factor) 212 | height = int(height/factor) 213 | 214 | if use_partial: 215 | self.partial_transform() 216 | 217 | # 移动矩阵 218 | self.adjustM = np.array( 219 | [[1, 0, max(-left, 0)], # 横向 220 | [0, 1, max(-top, 0)], # 纵向 221 | [0, 0, 1] 222 | ], dtype=np.float64) 223 | # print('adjustM: ', adjustM) 224 | self.M = np.dot(self.adjustM, self.M) 225 | transformed_1 = cv2.warpPerspective( 226 | self.image1, self.M, (width, height)) 227 | transformed_2 = cv2.warpPerspective( 228 | self.image2, self.adjustM, (width, height)) 229 | 230 | self.image = self.blend(transformed_1, transformed_2, use_gauss_blend=use_gauss_blend) 231 | 232 | if show_match_point: 233 | for point1, point2 in zip(self.image_points1, self.image_points2): 234 | point1 = self.get_transformed_position(tuple(point1)) 235 | point1 = tuple(map(int, point1)) 236 | point2 = self.get_transformed_position(tuple(point2), M=self.adjustM) 237 | point2 = tuple(map(int, point2)) 238 | 239 | cv2.line(self.image, point1, point2, random.choice(colors), 3) 240 | cv2.circle(self.image, point1, 10, (20, 20, 255), 5) 241 | cv2.circle(self.image, point2, 8, (20, 200, 20), 5) 242 | if show_result: 243 | show_image(self.image) 244 | 245 | def partial_transform(self): 246 | """Deprecated, should not be used. 247 | """ 248 | 249 | raise DeprecationWarning("Out of work, should not be used") 250 | 251 | def distance(p1, p2): 252 | return np.sqrt( 253 | (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])) 254 | width = self.image1.shape[0] 255 | height = self.image1.shape[1] 256 | offset_x = np.min(self.image_points1[:, 0]) 257 | offset_y = np.min(self.image_points1[:, 1]) 258 | 259 | x_mid = int((np.max(self.image_points1[:, 0]) + offset_x) / 2) 260 | y_mid = int((np.max(self.image_points1[:, 1]) + offset_y) / 2) 261 | 262 | center = [0, 0] 263 | up = x_mid 264 | down = width - x_mid 265 | left = y_mid 266 | right = height - y_mid 267 | 268 | ne, se, sw, nw = [], [], [], [] 269 | transform_acer = [[center, [up, 0], [up, right]], 270 | [center, [down, 0], [0, right]], 271 | [center, [down, left], [0, left]], 272 | [[up, 0], [up, left], [up, left]]] 273 | transform_acer = [[center, [0, up], [right, up]], 274 | [center, [0, down], [right, 0]], 275 | [center, [left, down], [left, 0]], 276 | [[0, up], [left, up], [left, up]]] 277 | 278 | # 对点的位置进行分类 279 | for index in range(self.image_points1.shape[0]): 280 | point = self.image_points1[index] 281 | if point[0] > y_mid: 282 | if point[1] > x_mid: 283 | se.append(index) 284 | else: 285 | ne.append(index) 286 | else: 287 | if point[1] > x_mid: 288 | sw.append(index) 289 | else: 290 | nw.append(index) 291 | 292 | # 求点最少处位置,排除零 293 | minmum = np.argmin( 294 | list(map(lambda x: len(x) if len(x) > 0 else 65536, [ne, se, sw, nw]))) 295 | # 当足够少时 296 | min_part = (ne, se, sw, nw)[minmum] 297 | 298 | # debug: 299 | print("minum part: ", minmum, "point len: ", len( 300 | min_part), "|", list(map(len, (ne, se, sw, nw)))) 301 | for index in min_part: 302 | point = self.image_points1[index] 303 | cv2.circle(self.image1, tuple( 304 | map(int, point)), 20, (0, 255, 255), 5) 305 | 306 | # cv2.circle(self.image1, tuple(map(int, (y_mid, x_mid))), 307 | # 25, (255, 100, 60), 7) 308 | # end debug 309 | if len(min_part) < len(self.image_points1) / 8: 310 | for index in min_part: 311 | point = self.image_points1[index].tolist() 312 | print("Point: ", point) 313 | # maybe can try other value? 314 | if distance(self.get_transformed_position(tuple(point)), 315 | self.image_points2[index]) > 10: 316 | def relevtive_point(p): 317 | return (p[0] - y_mid if p[0] > y_mid else p[0], 318 | p[1] - x_mid if p[1] > x_mid else p[1]) 319 | cv2.circle(self.image1, tuple(map(int, point)), 320 | 40, (255, 0, 0), 10) 321 | src_point = transform_acer[minmum].copy() 322 | src_point.append(relevtive_point(point)) 323 | other_point = self.get_transformed_position( 324 | tuple(self.image_points2[index]), M=np.linalg.inv(self.M)) 325 | dest_point = transform_acer[minmum].copy() 326 | dest_point.append(relevtive_point(other_point)) 327 | 328 | def a(x): return np.array(x, dtype=np.float32) 329 | print(src_point, dest_point) 330 | partial_M = cv2.getPerspectiveTransform( 331 | a(src_point), a(dest_point)) 332 | 333 | if minmum == 1 or minmum == 2: 334 | boder_0, boder_1 = x_mid, width 335 | else: 336 | boder_0, boder_1 = 0, x_mid 337 | if minmum == 2 or minmum == 3: 338 | boder_2, boder_3 = 0, y_mid 339 | else: 340 | boder_2, boder_3 = y_mid, height 341 | 342 | print("Changed:", 343 | "\nM: ", partial_M, 344 | "\npart: ", minmum, 345 | "\ndistance: ", distance(self.get_transformed_position(tuple(point)), 346 | self.image_points2[index]) 347 | ) 348 | part = self.image1[boder_0:boder_1, boder_2:boder_3] 349 | 350 | print(boder_0, boder_1, boder_2, boder_3) 351 | for point in transform_acer[minmum]: 352 | print(point) 353 | cv2.circle(part, tuple( 354 | map(int, point)), 40, (220, 200, 200), 10) 355 | for point in src_point: 356 | print(point) 357 | cv2.circle(part, tuple( 358 | map(int, point)), 22, (226, 43, 138), 8) 359 | 360 | part = cv2.warpPerspective( 361 | part, partial_M, (part.shape[1], part.shape[0])) 362 | cv2.circle(part, tuple(map(int, relevtive_point(other_point))), 363 | 40, (20, 97, 199), 6) 364 | # show_image(part) 365 | self.image1[boder_0:boder_1, boder_2:boder_3] = part 366 | return 367 | 368 | def blend(self, image1: np.ndarray, image2: np.ndarray, use_gauss_blend=True) -> np.ndarray: 369 | """对图像进行融合 370 | 371 | Args: 372 | image1 (np.ndarray): 图像一 373 | image2 (np.ndarray): 图像二 374 | 375 | Returns: 376 | np.ndarray: 融合结果 377 | """ 378 | 379 | mask = self.generate_mask(image1, image2) 380 | print("Blending") 381 | if use_gauss_blend: 382 | result = blend.gaussian_blend(image1, image2, mask, mask_blend=10) 383 | else: 384 | result = blend.direct_blend(image1, image2, mask, mask_blend=0) 385 | 386 | return result 387 | 388 | def generate_mask(self, image1: np.ndarray, image2: np.ndarray): 389 | """生成供融合使用的遮罩,由变换后图像的垂直平分线来构成分界线 390 | 391 | Args: 392 | shape (tuple): 遮罩大小 393 | 394 | Returns: 395 | np.ndarray: 01数组 396 | """ 397 | print("Generating mask") 398 | # x, y 399 | center1 = self.image1.shape[1] / 2, self.image1.shape[0] / 2 400 | center1 = self.get_transformed_position(center1) 401 | center2 = self.image2.shape[1] / 2, self.image2.shape[0] / 2 402 | center2 = self.get_transformed_position(center2, M=self.adjustM) 403 | # 垂直平分线 y=-(x2-x1)/(y2-y1)* [x-(x1+x2)/2]+(y1+y2)/2 404 | x1, y1 = center1 405 | x2, y2 = center2 406 | 407 | # note that opencv is (y, x) 408 | def function(y, x, *z): 409 | return (y2 - y1) * y < -(x2 - x1) * (x - (x1 + x2) / 2) + (y2 - y1) * (y1 + y2) / 2 410 | 411 | mask = np.fromfunction(function, image1.shape) 412 | 413 | # mask = mask&_i2+mask&i1+i1&_i2 414 | mask = np.logical_and(mask, np.logical_not(image2)) \ 415 | + np.logical_and(mask, image1)\ 416 | + np.logical_and(image1, np.logical_not(image2)) 417 | 418 | return mask 419 | 420 | def get_transformed_size(self) ->Tuple[int, int, int, int]: 421 | """计算形变后的边界 422 | 计算形变后的边界,从而对图片进行相应的位移,保证全部图像都出现在屏幕上。 423 | 424 | Returns: 425 | Tuple[int, int, int, int]: 分别为左右上下边界 426 | """ 427 | 428 | conner_0 = (0, 0) # x, y 429 | conner_1 = (self.image1.shape[1], 0) 430 | conner_2 = (self.image1.shape[1], self.image1.shape[0]) 431 | conner_3 = (0, self.image1.shape[0]) 432 | points = [conner_0, conner_1, conner_2, conner_3] 433 | 434 | # top, bottom: y, left, right: x 435 | top = min(map(lambda x: self.get_transformed_position(x)[1], points)) 436 | bottom = max( 437 | map(lambda x: self.get_transformed_position(x)[1], points)) 438 | left = min(map(lambda x: self.get_transformed_position(x)[0], points)) 439 | right = max(map(lambda x: self.get_transformed_position(x)[0], points)) 440 | 441 | return left, right, top, bottom 442 | 443 | def get_transformed_position(self, x: Union[float, Tuple[float, float]], y: float=None, M=None) -> Tuple[float, float]: 444 | """求得某点在变换矩阵(self.M)下的新坐标 445 | 446 | Args: 447 | x (Union[float, Tuple[float, float]]): x坐标或(x,y)坐标 448 | y (float, optional): Defaults to None. y坐标,可无 449 | M (np.ndarray, optional): Defaults to None. 利用M进行坐标变换运算 450 | 451 | Returns: 452 | Tuple[float, float]: 新坐标 453 | """ 454 | 455 | if isinstance(x, tuple): 456 | x, y = x 457 | p = np.array([x, y, 1])[np.newaxis].T 458 | if M is not None: 459 | M = M 460 | else: 461 | M = self.M 462 | pa = np.dot(M, p) 463 | return pa[0, 0] / pa[2, 0], pa[1, 0] / pa[2, 0] 464 | 465 | 466 | class Test(unittest.TestCase): 467 | 468 | def _test_matcher(self): 469 | image1 = np.random.randint(100, 256, size=(400, 400, 3), dtype='uint8') 470 | # np.random.randint(256, size=(400, 400, 3), dtype='uint8') 471 | image2 = np.copy(image1) 472 | for method in Method: 473 | matcher = Matcher(image1, image2, method) 474 | 475 | matcher.match(show_match=True) 476 | 477 | def test_transform_coord(self): 478 | stitcher = Stitcher(None, None, None, None) 479 | self.assertEqual((0, 0), stitcher.get_transformed_position(0, 0)) 480 | self.assertEqual((10, 20), stitcher.get_transformed_position(10, 20)) 481 | 482 | stitcher.M[0, 2] = 20 483 | stitcher.M[1, 2] = 10 484 | self.assertEqual((20, 10), stitcher.get_transformed_position(0, 0)) 485 | self.assertEqual((30, 30), stitcher.get_transformed_position(10, 20)) 486 | 487 | stitcher.M = np.eye(3) 488 | stitcher.M[0, 1] = 2 489 | stitcher.M[1, 0] = 4 490 | self.assertEqual((0, 0), stitcher.get_transformed_position(0, 0)) 491 | self.assertEqual((50, 60), stitcher.get_transformed_position(10, 20)) 492 | 493 | def test_get_transformed_size(self): 494 | image1 = np.empty((500, 400, 3), dtype='uint8') 495 | image1[:, :] = 255, 150, 100 496 | image1[:, 399] = 10, 20, 200 497 | 498 | # show_image(image1) 499 | image2 = np.empty((400, 400, 3), dtype='uint8') 500 | image2[:, :] = 50, 150, 255 501 | 502 | stitcher = Stitcher(image1, image2, None, None) 503 | stitcher.M[0, 2] = -20 504 | stitcher.M[1, 2] = 10 505 | stitcher.M[0, 1] = .2 506 | stitcher.M[1, 0] = .1 507 | left, right, top, bottom = stitcher.get_transformed_size() 508 | print(stitcher.get_transformed_size()) 509 | width = int(max(right, image2.shape[1]) - min(left, 0)) 510 | height = int(max(bottom, image2.shape[0]) - min(top, 0)) 511 | print(width, height) 512 | show_image(cv2.warpPerspective(image1, stitcher.M, (width, height))) 513 | 514 | def test_stich(self): 515 | image1 = np.empty((500, 400, 3), dtype='uint8') 516 | image1[:, :] = 255, 150, 100 517 | image1[:, 399] = 10, 20, 200 518 | 519 | # show_image(image1) 520 | image2 = np.empty((400, 400, 3), dtype='uint8') 521 | image2[:, :] = 50, 150, 255 522 | 523 | points = np.float32([[0, 0], [20, 20], [12, 12], [40, 20]]) 524 | stitcher = Stitcher(image1, image2, points, points) 525 | stitcher.M[0, 2] = 20 526 | stitcher.M[1, 2] = 10 527 | stitcher.M[0, 1] = .2 528 | stitcher.M[1, 0] = .1 529 | stitcher.stich() 530 | 531 | 532 | def main(): 533 | unittest.main() 534 | 535 | 536 | if __name__ == "__main__": 537 | import time 538 | # main() 539 | os.chdir(os.path.dirname(__file__)) 540 | 541 | start_time = time.time() 542 | img1 = cv2.imread("../resource/29-left.jpg") 543 | img2 = cv2.imread("../resource/29-right.jpg") 544 | stitcher = Stitcher(img1, img2, Method.SIFT, False) 545 | stitcher.stich(max_match_lenth=40, use_partial=False, use_new_match_method=1, use_gauss_blend=0) 546 | 547 | # cv2.imwrite('../resource/19-sift-gf.jpg', stitcher.image) 548 | 549 | print("Time: ", time.time() - start_time) 550 | print("M: ", stitcher.M) 551 | -------------------------------------------------------------------------------- /py/thesis-evolution.txt: -------------------------------------------------------------------------------- 1 | Image 3 start stitching, using Method.SIFT 2 | Using the genetic method 3 | max distance: 98.8028335571289 4 | Min distance: 49.264591217041016 5 | match_len: 22 6 | !!!DATA: array([[17.39121214, 17.39121214, 17.39121214, 17.39121214, 17.39121214, 7 | 17.39121214, 17.39121214, 17.39121214, 17.39121214, 17.39121214, 8 | 17.39121214, 17.39121214, 17.39121214, 17.39121214, 17.39266169, 9 | 17.39266169, 17.39266169, 17.39266169, 17.39266169, 17.39266169], 10 | [17.36368108, 17.36368108, 17.36368108, 17.36368108, 17.36368108, 11 | 17.38782152, 17.38782152, 17.38782152, 17.38782152, 17.38782152, 12 | 17.38782152, 17.38782152, 17.38782152, 17.39121214, 17.39121214, 13 | 17.39121214, 17.39266169, 17.39266169, 17.39266169, 17.39266169], 14 | [17.35802195, 17.35802195, 17.35802195, 17.35802195, 17.35802195, 15 | 17.36368108, 17.36368108, 17.36368108, 17.36368108, 17.37178418, 16 | 17.37178418, 17.37178418, 17.37178418, 17.39121214, 17.39121214, 17 | 17.39121214, 17.39121214, 17.39249375, 17.39266169, 17.39266169], 18 | [17.30532205, 17.30532205, 17.34145689, 17.34145689, 17.34145689, 19 | 17.35802195, 17.35802195, 17.35802195, 17.35802195, 17.36368108, 20 | 17.36368108, 17.36368108, 17.36368108, 17.38782152, 17.39121214, 21 | 17.39121214, 17.39121214, 17.39238748, 17.39249375, 17.39266169]]) 22 | Good points and average distance: (18, 10.932089497253846) 23 | 1017 1104 24 | Generating mask 25 | Blending 26 | Time: 1.6481800079345703 27 | #===================================================# 28 | Image 3 start stitching, using Method.SIFT 29 | max distance: 98.8028335571289 30 | Min distance: 49.264591217041016 31 | match_len: 22 32 | Good points and average distance: (18, 8.749268396019406) 33 | 1016 1106 34 | Generating mask 35 | Blending 36 | Time: 1.5725891590118408 37 | #===================================================# 38 | Image 3 start stitching, using Method.ORB 39 | Using the genetic method 40 | max distance: 21.0 41 | Min distance: 5.0 42 | match_len: 25 43 | !!!DATA: array([[23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077, 44 | 23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077, 45 | 23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077, 46 | 23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077], 47 | [23.62755285, 23.62755285, 23.62755285, 23.62755285, 23.62755285, 48 | 23.62755285, 23.6567805 , 23.70518033, 23.70518033, 23.70518033, 49 | 23.70518033, 23.70518033, 23.70518033, 23.71827077, 23.71827077, 50 | 23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077], 51 | [23.4648366 , 23.4648366 , 23.4648366 , 23.54565117, 23.59828591, 52 | 23.62339116, 23.62755285, 23.6567805 , 23.69704633, 23.69704633, 53 | 23.69704633, 23.70518033, 23.70518033, 23.70518033, 23.71827077, 54 | 23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077], 55 | [23.45153597, 23.45153597, 23.45153597, 23.46515858, 23.54565117, 56 | 23.59828591, 23.62339116, 23.64775564, 23.6567805 , 23.6567805 , 57 | 23.69704633, 23.69704633, 23.69704633, 23.70518033, 23.70518033, 58 | 23.71827077, 23.71827077, 23.71827077, 23.71827077, 23.71827077]]) 59 | Good points and average distance: (25, 32.043230744614725) 60 | 1027 1088 61 | Generating mask 62 | Blending 63 | Time: 1.617082118988037 64 | #===================================================# 65 | Image 3 start stitching, using Method.ORB 66 | max distance: 21.0 67 | Min distance: 5.0 68 | match_len: 25 69 | Good points and average distance: (22, 29.271584694530826) 70 | 1982 2729 71 | Generating mask 72 | Blending 73 | Time: 3.4444739818573 74 | #===================================================# 75 | Image 19 start stitching, using Method.SIFT 76 | Using the genetic method 77 | max distance: 64.79969024658203 78 | Min distance: 32.264530181884766 79 | match_len: 9 80 | !!!DATA: array([[8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66093055, 81 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084, 82 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084, 83 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084], 84 | [8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66093055, 85 | 8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66151084, 86 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084, 87 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084], 88 | [8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66093055, 89 | 8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66093055, 90 | 8.66093055, 8.66151084, 8.66151084, 8.66151084, 8.66151084, 91 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084], 92 | [8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66093055, 93 | 8.66093055, 8.66093055, 8.66093055, 8.66093055, 8.66093055, 94 | 8.66093055, 8.66151084, 8.66151084, 8.66151084, 8.66151084, 95 | 8.66151084, 8.66151084, 8.66151084, 8.66151084, 8.66151084]]) 96 | Good points and average distance: (9, 3.0464024342360734) 97 | 1844 1073 98 | Generating mask 99 | Blending 100 | Time: 2.5703320503234863 101 | #===================================================# 102 | Image 19 start stitching, using Method.SIFT 103 | max distance: 64.79969024658203 104 | Min distance: 32.264530181884766 105 | match_len: 9 106 | Good points and average distance: (9, 0.7188029580416416) 107 | 1694 1000 108 | Generating mask 109 | Blending 110 | Time: 1.8343133926391602 111 | #===================================================# 112 | Image 19 start stitching, using Method.ORB 113 | Using the genetic method 114 | max distance: 25.0 115 | Min distance: 12.0 116 | match_len: 40 117 | !!!DATA: array([[35.45704109, 35.96950198, 35.96950198, 35.96950198, 35.96950198, 118 | 36.16413738, 36.16413738, 36.16413738, 36.16413738, 36.16413738, 119 | 36.16413738, 36.16413738, 36.16413738, 36.16413738, 36.16514425, 120 | 36.20910498, 36.20910498, 36.20910498, 36.29231656, 36.29332344], 121 | [35.3663592 , 35.45704109, 35.96950198, 35.96950198, 35.96950198, 122 | 35.9910527 , 36.00652206, 36.1313662 , 36.1313662 , 36.1313662 , 123 | 36.16413738, 36.16413738, 36.16413738, 36.16413738, 36.16413738, 124 | 36.16803775, 36.16803775, 36.18289504, 36.21011186, 36.29231656], 125 | [35.2503589 , 35.38270102, 35.90908657, 35.96950198, 35.96950198, 126 | 35.96950198, 35.9910527 , 36.11330531, 36.1313662 , 36.1313662 , 127 | 36.16059314, 36.16413738, 36.16413738, 36.16413738, 36.16413738, 128 | 36.16514425, 36.16514425, 36.16803775, 36.20925672, 36.21011186], 129 | [34.95974707, 35.3663592 , 35.54752204, 35.96950198, 35.96950198, 130 | 35.96950198, 35.96950198, 36.00652206, 36.12726373, 36.12726373, 131 | 36.1313662 , 36.16413738, 36.16413738, 36.16413738, 36.16413738, 132 | 36.16425829, 36.16514425, 36.16514425, 36.20910498, 36.21011186]]) 133 | Good points and average distance: (38, 64.85370923411021) 134 | 1543 965 135 | Generating mask 136 | Blending 137 | Time: 0.8626115322113037 138 | #===================================================# 139 | Image 19 start stitching, using Method.ORB 140 | max distance: 25.0 141 | Min distance: 12.0 142 | match_len: 40 143 | Good points and average distance: (37, 59.61659384066628) 144 | 1536 924 145 | Generating mask 146 | Blending 147 | Time: 0.8161249160766602 148 | #===================================================# 149 | Image 20 start stitching, using Method.SIFT 150 | Using the genetic method 151 | max distance: 92.71461486816406 152 | Min distance: 46.14108657836914 153 | match_len: 10 154 | !!!DATA: array([[9.52246755, 9.52246755, 9.52246755, 9.53002997, 9.53002997, 155 | 9.53948523, 9.53948523, 9.55165496, 9.55165496, 9.55165496, 156 | 9.55165496, 9.56111984, 9.56135103, 9.56254687, 9.56254687, 157 | 9.56254687, 9.56566219, 9.56566219, 9.56566219, 9.56566219], 158 | [9.52027238, 9.52027238, 9.52097452, 9.52246755, 9.52964238, 159 | 9.53487216, 9.53487216, 9.53948523, 9.53948523, 9.53948523, 160 | 9.55165496, 9.56111984, 9.56111984, 9.56135103, 9.56135103, 161 | 9.56254687, 9.56254687, 9.56254687, 9.56254687, 9.56566219], 162 | [9.50431285, 9.51485359, 9.52027238, 9.52097452, 9.52246755, 163 | 9.53002997, 9.53372486, 9.53487216, 9.53487216, 9.53948523, 164 | 9.54545377, 9.55165496, 9.56111984, 9.56135103, 9.56135103, 165 | 9.56135103, 9.56254687, 9.56254687, 9.56254687, 9.56566219], 166 | [9.46199265, 9.50503336, 9.52027238, 9.52027238, 9.52147931, 167 | 9.52964238, 9.53002997, 9.53372486, 9.53372486, 9.53487216, 168 | 9.53962219, 9.55165496, 9.56111984, 9.56111984, 9.56135103, 169 | 9.56135103, 9.56141854, 9.56254687, 9.56254687, 9.56254687]]) 170 | Good points and average distance: (10, 4.343378065113296) 171 | 1571 997 172 | Generating mask 173 | Blending 174 | Time: 1.4270174503326416 175 | #===================================================# 176 | Image 20 start stitching, using Method.SIFT 177 | max distance: 92.71461486816406 178 | Min distance: 46.14108657836914 179 | match_len: 10 180 | Good points and average distance: (10, 3.9319547490350826) 181 | 1577 998 182 | Generating mask 183 | Blending 184 | Time: 1.297940731048584 185 | #===================================================# 186 | Image 20 start stitching, using Method.ORB 187 | Using the genetic method 188 | max distance: 27.0 189 | Min distance: 14.0 190 | match_len: 40 191 | !!!DATA: array([[36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 192 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 193 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 194 | 36.3103174 , 36.3103174 , 36.3103174 , 36.3103174 , 36.3103174 ], 195 | [36.07909323, 36.07909323, 36.27119542, 36.27119542, 36.27119542, 196 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 197 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 198 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542], 199 | [35.78412907, 35.78412907, 36.07909323, 36.27119542, 36.27119542, 200 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 201 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 202 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542], 203 | [35.11817228, 35.78380705, 35.78434347, 36.07909323, 36.07909323, 204 | 36.07909323, 36.07909323, 36.27119542, 36.27119542, 36.27119542, 205 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542, 206 | 36.27119542, 36.27119542, 36.27119542, 36.27119542, 36.27119542]]) 207 | Good points and average distance: (39, 104.8976212931571) 208 | 1607 1001 209 | Generating mask 210 | Blending 211 | Time: 0.8425984382629395 212 | #===================================================# 213 | Image 20 start stitching, using Method.ORB 214 | max distance: 27.0 215 | Min distance: 14.0 216 | match_len: 40 217 | Good points and average distance: (36, 57.06089134346969) 218 | 1565 979 219 | Generating mask 220 | Blending 221 | Time: 0.6959941387176514 222 | #===================================================# 223 | Image 22 start stitching, using Method.SIFT 224 | Using the genetic method 225 | max distance: 56.665687561035156 226 | Min distance: 28.1069393157959 227 | match_len: 37 228 | !!!DATA: array([list([22.418239476616286, 22.07697091312915]), 229 | list([22.418239476616286, 22.418239476616286, 22.418239476616286, 22.418239476616286]), 230 | list([22.418239476616286, 22.418239476616286, 22.418239476616286, 22.418239476616286]), 231 | list([22.418239476616286, 22.418239476616286, 22.418239476616286, 22.418239476616286]), 232 | list([22.41868331284239, 22.418587639014813, 22.418239476616286, 22.418239476616286]), 233 | list([22.41868331284239, 22.418587639014813, 22.418587639014813, 22.418239476616286]), 234 | list([22.41885510361968, 22.41868331284239, 22.418587639014813, 22.418587639014813]), 235 | list([22.41885510361968, 22.41885111336774, 22.41868331284239, 22.41868331284239]), 236 | list([22.41885510361968, 22.41885111336774, 22.41885111336774, 22.41868331284239]), 237 | list([22.41885510361968, 22.41885111336774, 22.41885111336774, 22.41885111336774]), 238 | list([22.41885510361968, 22.41885510361968, 22.41885111336774, 22.41885111336774]), 239 | list([22.41885510361968, 22.41885510361968, 22.41885111336774, 22.41885111336774]), 240 | list([22.41885510361968, 22.41885510361968, 22.41885510361968, 22.41885111336774]), 241 | list([22.449372229481423, 22.44914327885079, 22.426772929111422, 22.41885510361968]), 242 | list([22.449372229481423, 22.449372229481423, 22.44914327885079, 22.426772929111422]), 243 | list([22.449372229481423, 22.449372229481423, 22.44914327885079, 22.448518973885204]), 244 | list([22.449372229481423, 22.449372229481423, 22.449372229481423, 22.449372229481423]), 245 | list([22.45057743535839, 22.45057743535839, 22.449372229481423, 22.449372229481423]), 246 | list([22.45057743535839, 22.45057743535839, 22.449372229481423, 22.449372229481423]), 247 | list([22.45057743535839, 22.45057743535839, 22.45057743535839, 22.45057743535839])], 248 | dtype=object) 249 | Good points and average distance: (24, 37.186141551398606) 250 | 2395 2581 251 | Generating mask 252 | Blending 253 | Time: 5.534061908721924 254 | #===================================================# 255 | Image 22 start stitching, using Method.SIFT 256 | max distance: 56.665687561035156 257 | Min distance: 28.1069393157959 258 | match_len: 37 259 | Good points and average distance: (24, 32.72579010142691) 260 | 2402 2570 261 | Generating mask 262 | Blending 263 | Time: 5.882205963134766 264 | #===================================================# 265 | Image 22 start stitching, using Method.ORB 266 | Using the genetic method 267 | max distance: 18.0 268 | Min distance: 8.0 269 | match_len: 40 270 | !!!DATA: array([[35.53768646, 35.53768646, 35.55225441, 35.55282542, 35.55282542, 271 | 35.55282542, 35.55282542, 35.57568179, 35.57568179, 35.57568179, 272 | 35.57568179, 35.57568179, 35.57568179, 35.57568179, 35.58092051, 273 | 35.58092051, 35.58092051, 35.58092051, 35.58092051, 35.58092051], 274 | [35.45858307, 35.45858307, 35.53768646, 35.55225441, 35.55225441, 275 | 35.55225441, 35.55225441, 35.55282542, 35.55282542, 35.56955076, 276 | 35.57568179, 35.57568179, 35.57568179, 35.57568179, 35.57568179, 277 | 35.58092051, 35.58092051, 35.58092051, 35.58092051, 35.58092051], 278 | [35.45400915, 35.45400915, 35.45858307, 35.53768646, 35.53768646, 279 | 35.53768646, 35.53768646, 35.55225441, 35.55225441, 35.55282542, 280 | 35.56955076, 35.57568179, 35.57568179, 35.57568179, 35.57568179, 281 | 35.58092051, 35.58092051, 35.58092051, 35.58092051, 35.58092051], 282 | [35.30690423, 35.30690423, 35.45400915, 35.53768646, 35.53768646, 283 | 35.53768646, 35.53768646, 35.55225441, 35.55225441, 35.55282542, 284 | 35.56955076, 35.57568179, 35.57568179, 35.57568179, 35.57568179, 285 | 35.57568179, 35.58092051, 35.58092051, 35.58092051, 35.58092051]]) 286 | Good points and average distance: (37, 52.50594105638672) 287 | 2405 2569 288 | Generating mask 289 | Blending 290 | Time: 3.5345232486724854 291 | #===================================================# 292 | Image 22 start stitching, using Method.ORB 293 | max distance: 18.0 294 | Min distance: 8.0 295 | match_len: 40 296 | Good points and average distance: (36, 35.54711796830143) 297 | 2404 2582 298 | Generating mask 299 | Blending 300 | Time: 3.2778217792510986 301 | #===================================================# 302 | Image 23 start stitching, using Method.SIFT 303 | Using the genetic method 304 | max distance: 169.44320678710938 305 | Min distance: 82.55906677246094 306 | match_len: 8 307 | !!!DATA: array([[4.65409048, 4.65409048, 4.65409048, 4.65409048, 4.65409048, 308 | 4.65409048, 4.65409048, 4.65409048, 4.65412664, 4.65412664, 309 | 4.65412664, 4.69708835, 4.69708835, 4.69708835, 4.69708835, 310 | 4.69708835, 4.69708835, 4.69708835, 4.73003637, 4.73003637], 311 | [4.52182335, 4.52182335, 4.52182335, 4.52182335, 4.52182335, 312 | 4.52182335, 4.52182335, 4.52182335, 4.65409048, 4.65409048, 313 | 4.65412664, 4.65412664, 4.6555098 , 4.6555098 , 4.6555098 , 314 | 4.69708835, 4.69708835, 4.69708835, 4.69708835, 4.69708835], 315 | [4.26845538, 4.42614665, 4.42614665, 4.42614665, 4.51581137, 316 | 4.51581137, 4.51581137, 4.51581137, 4.52182335, 4.52182335, 317 | 4.65409048, 4.65412664, 4.65412664, 4.65412664, 4.65412664, 318 | 4.6555098 , 4.69708835, 4.69708835, 4.69708835, 4.69708835], 319 | [4.21977359, 4.26845538, 4.26845538, 4.26845538, 4.47822488, 320 | 4.51581137, 4.51581137, 4.51581137, 4.51581137, 4.51581137, 321 | 4.53488723, 4.65409048, 4.65412664, 4.65412664, 4.65412664, 322 | 4.65412664, 4.6555098 , 4.69708505, 4.69708835, 4.69708835]]) 323 | Good points and average distance: (5, 1.3498181311013289) 324 | 2328 2464 325 | Generating mask 326 | Blending 327 | Time: 5.716076612472534 328 | #===================================================# 329 | Image 23 start stitching, using Method.SIFT 330 | max distance: 169.44320678710938 331 | Min distance: 82.55906677246094 332 | match_len: 8 333 | Good points and average distance: (6, 5.406810913405451) 334 | 2333 2515 335 | Generating mask 336 | Blending 337 | Time: 5.324771881103516 338 | #===================================================# 339 | Image 23 start stitching, using Method.ORB 340 | Using the genetic method 341 | max distance: 31.0 342 | Min distance: 15.0 343 | match_len: 14 344 | !!!DATA: array([[10.77593354, 10.77593354, 10.77593354, 10.77593354, 10.77593354, 345 | 10.77593354, 10.77593354, 10.77593354, 10.7877197 , 10.7877197 , 346 | 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 , 347 | 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 ], 348 | [10.37285463, 10.37285463, 10.37285463, 10.37285463, 10.45391675, 349 | 10.45391675, 10.45391675, 10.77593354, 10.77593354, 10.77593354, 350 | 10.77593354, 10.77593354, 10.77593354, 10.77593354, 10.77593354, 351 | 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 ], 352 | [10.30149811, 10.30149811, 10.30149811, 10.30149811, 10.37285463, 353 | 10.37285463, 10.45327368, 10.45391675, 10.77593354, 10.77593354, 354 | 10.77593354, 10.77593354, 10.77593354, 10.77593354, 10.77593354, 355 | 10.77593354, 10.7877197 , 10.7877197 , 10.7877197 , 10.7877197 ], 356 | [ 9.89957075, 9.90768229, 9.90768229, 9.90768229, 10.30149811, 357 | 10.30149811, 10.37285463, 10.45327368, 10.77593354, 10.77593354, 358 | 10.77593354, 10.77593354, 10.77593354, 10.77593354, 10.77593354, 359 | 10.77593354, 10.77593354, 10.7877197 , 10.7877197 , 10.7877197 ]]) 360 | Good points and average distance: (13, 28.759643882944143) 361 | 2785 3005 362 | Generating mask 363 | Blending 364 | Time: 4.80304217338562 365 | #===================================================# 366 | Image 23 start stitching, using Method.ORB 367 | max distance: 31.0 368 | Min distance: 15.0 369 | match_len: 14 370 | Good points and average distance: (11, 11.547786029707446) 371 | 2355 2607 372 | Generating mask 373 | Blending 374 | Time: 3.3154613971710205 375 | #===================================================# 376 | Image 24 start stitching, using Method.SIFT 377 | Using the genetic method 378 | max distance: 75.02666473388672 379 | Min distance: 37.48332977294922 380 | match_len: 11 381 | !!!DATA: array([[10.83230155, 10.83230155, 10.83230155, 10.83230155, 10.83230155, 382 | 10.83230155, 10.83230155, 10.83230155, 10.83230155, 10.83230155, 383 | 10.83230155, 10.83230155, 10.83230155, 10.83230155, 10.83230155, 384 | 10.83230155, 10.83230155, 10.83230155, 10.83230155, 10.83230155], 385 | [10.82961866, 10.82961866, 10.82961866, 10.82961866, 10.82961866, 386 | 10.82961866, 10.82961866, 10.82961866, 10.82961866, 10.82961866, 387 | 10.82961866, 10.82961866, 10.83230155, 10.83230155, 10.83230155, 388 | 10.83230155, 10.83230155, 10.83230155, 10.83230155, 10.83230155], 389 | [10.82088752, 10.82088752, 10.82088752, 10.82853022, 10.82853022, 390 | 10.82853022, 10.82853022, 10.82853022, 10.82853022, 10.82853022, 391 | 10.82853022, 10.82853022, 10.82961866, 10.82961866, 10.82961866, 392 | 10.82961866, 10.82961866, 10.82961866, 10.83230155, 10.83230155], 393 | [10.82035756, 10.82035756, 10.82035756, 10.82088752, 10.82853022, 394 | 10.82853022, 10.82853022, 10.82853022, 10.82853022, 10.82853022, 395 | 10.82853022, 10.82853022, 10.82853022, 10.82853022, 10.82853022, 396 | 10.82853022, 10.82853022, 10.82853022, 10.83230155, 10.83230155]]) 397 | Good points and average distance: (11, 1.844682961284418) 398 | 2477 2624 399 | Generating mask 400 | Blending 401 | Time: 5.876267671585083 402 | #===================================================# 403 | Image 24 start stitching, using Method.SIFT 404 | max distance: 75.02666473388672 405 | Min distance: 37.48332977294922 406 | match_len: 11 407 | Good points and average distance: (9, 0.5766786432847203) 408 | 2524 2521 409 | Generating mask 410 | Blending 411 | Time: 5.798865079879761 412 | #===================================================# 413 | Image 24 start stitching, using Method.ORB 414 | Using the genetic method 415 | max distance: 21.0 416 | Min distance: 9.0 417 | match_len: 18 418 | !!!DATA: array([[15.65098132, 15.65098132, 15.65098132, 15.65098132, 15.65441276, 419 | 15.65441276, 15.65441276, 15.65772953, 15.65772953, 15.65772953, 420 | 15.65772953, 15.65772953, 15.65772953, 15.65866082, 15.65869494, 421 | 15.65869494, 15.65869494, 15.65869494, 15.65869494, 15.65869494], 422 | [15.4959344 , 15.4959344 , 15.4959344 , 15.64372127, 15.65098132, 423 | 15.65098132, 15.65317515, 15.65740726, 15.65740726, 15.65740726, 424 | 15.65772953, 15.65772953, 15.65772953, 15.65772953, 15.65866082, 425 | 15.65869494, 15.65869494, 15.65869494, 15.65869494, 15.65869494], 426 | [15.45742551, 15.45742551, 15.45742551, 15.63475228, 15.64372127, 427 | 15.65098132, 15.65098132, 15.65441276, 15.65740726, 15.65740726, 428 | 15.65740726, 15.65772953, 15.65772953, 15.65772953, 15.65772953, 429 | 15.65866082, 15.65869494, 15.65869494, 15.65869494, 15.65869494], 430 | [15.38399698, 15.38399698, 15.38399698, 15.4959344 , 15.63475228, 431 | 15.64372127, 15.65098132, 15.65317515, 15.65441276, 15.65740726, 432 | 15.65740726, 15.65772953, 15.65772953, 15.65772953, 15.65772953, 433 | 15.65772953, 15.65866082, 15.65869494, 15.65869494, 15.65869494]]) 434 | Good points and average distance: (17, 22.80218605781505) 435 | 2477 2606 436 | Generating mask 437 | Blending 438 | Time: 3.5805540084838867 439 | #===================================================# 440 | Image 24 start stitching, using Method.ORB 441 | max distance: 21.0 442 | Min distance: 9.0 443 | match_len: 18 444 | Good points and average distance: (16, 15.091973017247177) 445 | 2477 2643 446 | Generating mask 447 | Blending 448 | Time: 3.648155450820923 449 | #===================================================# 450 | Image 27 start stitching, using Method.SIFT 451 | Using the genetic method 452 | max distance: 64.1248779296875 453 | Min distance: 31.93743896484375 454 | match_len: 39 455 | !!!DATA: array([[35.9943141 , 35.9943141 , 35.9943141 , 35.9943141 , 36.34954871, 456 | 36.34954871, 36.34954871, 36.34954871, 36.3532982 , 36.3532982 , 457 | 36.3532982 , 36.36569182, 36.36569182, 36.36569182, 36.36569182, 458 | 36.41539571, 36.41539571, 36.41539571, 36.41539571, 36.41539571], 459 | [35.747235 , 35.88145983, 35.88145983, 35.9943141 , 35.9943141 , 460 | 35.9943141 , 35.9943141 , 36.34954871, 36.34954871, 36.34954871, 461 | 36.3532982 , 36.36569182, 36.36569182, 36.36569182, 36.36569182, 462 | 36.36569182, 36.36569182, 36.36569182, 36.38044464, 36.38044464], 463 | [35.32017894, 35.747235 , 35.747235 , 35.88145983, 35.9943141 , 464 | 35.9943141 , 35.9943141 , 35.9943141 , 36.34954871, 36.34954871, 465 | 36.3532982 , 36.3532982 , 36.36569182, 36.36569182, 36.36569182, 466 | 36.36569182, 36.36569182, 36.36569182, 36.36569182, 36.38044464], 467 | [35.2025865 , 35.32017894, 35.32017894, 35.76811772, 35.88145983, 468 | 35.9943141 , 35.9943141 , 35.9943141 , 36.30352117, 36.34954871, 469 | 36.34954871, 36.3532982 , 36.3532982 , 36.36569182, 36.36569182, 470 | 36.36569182, 36.36569182, 36.36569182, 36.36569182, 36.36569182]]) 471 | Good points and average distance: (38, 60.214962948896556) 472 | 2226 3103 473 | Generating mask 474 | Blending 475 | Time: 7.742629051208496 476 | #===================================================# 477 | Image 27 start stitching, using Method.SIFT 478 | max distance: 64.1248779296875 479 | Min distance: 31.93743896484375 480 | match_len: 39 481 | Good points and average distance: (38, 44.86339740937679) 482 | 2225 3107 483 | Generating mask 484 | Blending 485 | Time: 7.862168788909912 486 | #===================================================# 487 | Image 27 start stitching, using Method.ORB 488 | Using the genetic method 489 | max distance: 21.0 490 | Min distance: 7.0 491 | match_len: 17 492 | !!!DATA: array([[13.80619356, 13.85808897, 13.85808897, 13.91812791, 13.92360391, 493 | 13.92360391, 13.92360391, 13.93448086, 13.93463914, 13.93463914, 494 | 13.93463914, 13.93463914, 13.93463914, 13.93463914, 13.93463914, 495 | 13.93463914, 13.93463914, 13.93463914, 13.93463914, 13.93463914], 496 | [13.69201193, 13.80619356, 13.80619356, 13.85808897, 13.91812791, 497 | 13.9226345 , 13.9226345 , 13.92360391, 13.93448086, 13.93448086, 498 | 13.93463914, 13.93463914, 13.93463914, 13.93463914, 13.93463914, 499 | 13.93463914, 13.93463914, 13.93463914, 13.93463914, 13.93463914], 500 | [13.4206513 , 13.69201193, 13.69201193, 13.85808897, 13.91812791, 501 | 13.9226345 , 13.9226345 , 13.92283393, 13.92360391, 13.93448086, 502 | 13.93448086, 13.93463914, 13.93463914, 13.93463914, 13.93463914, 503 | 13.93463914, 13.93463914, 13.93463914, 13.93463914, 13.93463914], 504 | [13.41714691, 13.4206513 , 13.4206513 , 13.85772812, 13.90818359, 505 | 13.91812791, 13.9226345 , 13.9226345 , 13.92360391, 13.93373255, 506 | 13.93448086, 13.93463914, 13.93463914, 13.93463914, 13.93463914, 507 | 13.93463914, 13.93463914, 13.93463914, 13.93463914, 13.93463914]]) 508 | Good points and average distance: (16, 33.045773747785056) 509 | 2230 3140 510 | Generating mask 511 | Blending 512 | Time: 3.806696891784668 513 | #===================================================# 514 | Image 27 start stitching, using Method.ORB 515 | max distance: 21.0 516 | Min distance: 7.0 517 | match_len: 17 518 | Good points and average distance: (15, 32.12952841256415) 519 | 2401 3283 520 | Generating mask 521 | Blending 522 | Time: 4.107934236526489 523 | #===================================================# 524 | Image 29 start stitching, using Method.SIFT 525 | Using the genetic method 526 | max distance: 88.91568756103516 527 | Min distance: 45.45327377319336 528 | match_len: 40 529 | !!!DATA: array([[23.64728597, 24.03911846, 24.03911846, 24.03911846, 24.03911846, 530 | 24.03911846, 24.03911846, 24.03911846, 24.18495545, 24.18495545, 531 | 24.18495545, 24.18495545, 24.18495545, 24.18495545, 24.18495545, 532 | 24.18495545, 24.18495545, 24.18495545, 24.18495545, 24.18495545], 533 | [23.56102048, 23.64728597, 23.64728597, 23.64728597, 23.64728597, 534 | 23.64728597, 23.64728597, 24.0242803 , 24.03911846, 24.03911846, 535 | 24.03911846, 24.17273456, 24.18495545, 24.18495545, 24.18495545, 536 | 24.18495545, 24.18495545, 24.18495545, 24.18495545, 24.18495545], 537 | [23.44971234, 23.6369902 , 23.6369902 , 23.6369902 , 23.6369902 , 538 | 23.6369902 , 23.6369902 , 23.64728597, 24.0242803 , 24.0242803 , 539 | 24.03911846, 24.03911846, 24.18495545, 24.18495545, 24.18495545, 540 | 24.18495545, 24.18495545, 24.18495545, 24.18495545, 24.18495545], 541 | [23.38924497, 23.56102048, 23.56102048, 23.56102048, 23.58463175, 542 | 23.58463175, 23.6369902 , 23.64728597, 23.64728597, 23.64728597, 543 | 24.0242803 , 24.03911846, 24.17273456, 24.18495545, 24.18495545, 544 | 24.18495545, 24.18495545, 24.18495545, 24.18495545, 24.18495545]]) 545 | Good points and average distance: (26, 47.19115836581838) 546 | 5547 3298 547 | Generating mask 548 | Blending 549 | Time: 14.706602811813354 550 | #===================================================# 551 | Image 29 start stitching, using Method.SIFT 552 | max distance: 88.91568756103516 553 | Min distance: 45.45327377319336 554 | match_len: 40 555 | Good points and average distance: (24, 9.361046958843033) 556 | 5562 3374 557 | Generating mask 558 | Blending 559 | Time: 14.56857681274414 560 | #===================================================# 561 | Image 29 start stitching, using Method.ORB 562 | Using the genetic method 563 | max distance: 29.0 564 | Min distance: 14.0 565 | match_len: 16 566 | !!!DATA: array([[11.79960365, 11.79960365, 11.79960365, 11.79960365, 11.79960365, 567 | 11.79960365, 11.79960365, 11.79960365, 11.79960365, 11.79960365, 568 | 11.79960365, 11.79960365, 11.79960365, 11.79960365, 11.79960365, 569 | 11.79960365, 11.79960365, 11.79960365, 11.79960365, 11.79960365], 570 | [11.67124731, 11.67124731, 11.67124731, 11.67124731, 11.67124731, 571 | 11.67124731, 11.67124731, 11.67124731, 11.67124731, 11.67124731, 572 | 11.67124731, 11.67124731, 11.67124731, 11.79960365, 11.79960365, 573 | 11.79960365, 11.79960365, 11.79960365, 11.79960365, 11.79960365], 574 | [11.25001458, 11.25001458, 11.25001458, 11.25001458, 11.25001458, 575 | 11.25001458, 11.25001458, 11.25001458, 11.25001458, 11.25001458, 576 | 11.25001458, 11.25001458, 11.25001458, 11.67124731, 11.67124731, 577 | 11.79960365, 11.79960365, 11.79960365, 11.79960365, 11.79960365], 578 | [10.96689079, 10.96689079, 10.96689079, 10.97167948, 11.25001458, 579 | 11.25001458, 11.25001458, 11.25001458, 11.25001458, 11.25001458, 580 | 11.25001458, 11.25001458, 11.25001458, 11.25001458, 11.25001458, 581 | 11.67124731, 11.79960365, 11.79960365, 11.79960365, 11.79960365]]) 582 | Good points and average distance: (14, 30.805548842145893) 583 | 6954 4890 584 | Generating mask 585 | Blending 586 | Time: 27.447067260742188 587 | #===================================================# 588 | Image 29 start stitching, using Method.ORB 589 | max distance: 29.0 590 | Min distance: 14.0 591 | match_len: 16 592 | Good points and average distance: (13, 22.41099547810859) 593 | 7375 5248 594 | Generating mask 595 | Blending 596 | Time: 28.668940782546997 597 | #===================================================# 598 | [ INFO:0] Initialize OpenCL runtime... 599 | -------------------------------------------------------------------------------- /py/thesis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 2018-05-18 18:12:12 4 | @Author: ZHAO Lingfeng 5 | @Version : 0.0.1 6 | """ 7 | import time 8 | import os 9 | 10 | import numpy as np 11 | import cv2 12 | 13 | import stitch 14 | 15 | show_image = stitch.show_image 16 | os.chdir(os.path.dirname(__file__)) 17 | 18 | 19 | def work(): 20 | os.chdir(os.path.dirname(__file__)) 21 | base_path = "../../论文/md/img/comparison" 22 | # os.chdir(base_path) 23 | # image = [3] 24 | # image = [3, 19, 20, 22, 23, 24, 27, 29] 25 | image = ("Road", "Lake", "Tree", "Building", "School", "Grass", "Palace", "NewHarbor") 26 | for name in image: 27 | try: 28 | # img1 = cv2.imread(("/{}-left.jpg".format(name))) 29 | # img2 = cv2.imread(("/{}-right.jpg".format(name)).encode('gbk').decode()) 30 | img1 = cv2.imdecode(np.fromfile(base_path + "/{}-left.jpg".format(name), dtype='uint8'), -1) 31 | img2 = cv2.imdecode(np.fromfile(base_path + "/{}-right.jpg".format(name), dtype='uint8'), -1) 32 | for method in (stitch.Method.SIFT, stitch.Method.ORB): 33 | for use_genetic in (True, False): 34 | try: 35 | print("#===================================================#") 36 | print("Image {} start stitching, using {}".format(name, method)) 37 | if use_genetic: 38 | print("Using the genetic method") 39 | 40 | start_time = time.time() 41 | stitcher = stitch.Stitcher(img1, img2, method, False) 42 | stitcher.stich(max_match_lenth=40, use_partial=False, 43 | use_new_match_method=use_genetic, show_match_point=False, show_result=False, use_gauss_blend=1) 44 | if use_genetic: 45 | # cv2.imwrite('/result/{}-{}-genetic.jpg'.format(name, 46 | # method), stitcher.image) 47 | cv2.imencode('.jpg', stitcher.image)[1].tofile(base_path + 48 | '/result/{}-{}-genetic.jpg'.format(name, method)) 49 | else: 50 | # cv2.imwrite('/result/{}-{}.jpg'.format(name, method), stitcher.image) 51 | cv2.imencode('.jpg', stitcher.image)[1].tofile(base_path + 52 | '/result/{}-{}.jpg'.format(name, method)) 53 | 54 | print("Time: ", time.time() - start_time) 55 | # print("M: ", stitcher.M) 56 | 57 | except Exception as e: 58 | print("Error happens: ", e) 59 | except Exception as e: 60 | print("Error happens: ", e) 61 | 62 | 63 | def preview(): 64 | os.chdir(os.path.dirname(__file__)) 65 | image = [3, 19, 20, 22, 23, 24, 27, 29] 66 | # image = [3] 67 | for name in image: 68 | try: 69 | img1 = cv2.imread(("../resource/{}-left.jpg".format(name))) 70 | img2 = cv2.imread(("../resource/{}-right.jpg".format(name))) 71 | for method in (stitch.Method.SIFT, stitch.Method.ORB): 72 | for use_genetic in (True, False): 73 | try: 74 | print("Image {} start stitching, using {}".format(name, method)) 75 | if use_genetic: 76 | print("Using the genetic method") 77 | 78 | start_time = time.time() 79 | stitcher = stitch.Stitcher(img1, img2, method, False) 80 | stitcher.stich(max_match_lenth=40, use_partial=False, 81 | use_new_match_method=use_genetic, show_match_point=True, show_result=True, use_gauss_blend=False) 82 | if use_genetic: 83 | cv2.imwrite('../resource/result_new/{}-{}-genetic.jpg'.format(name, 84 | method), stitcher.image) 85 | else: 86 | cv2.imwrite('../resource/result_new/{}-{}.jpg'.format(name, method), stitcher.image) 87 | 88 | print("Time: ", time.time() - start_time) 89 | # print("M: ", stitcher.M) 90 | print("#===================================================#") 91 | 92 | except Exception as e: 93 | print("Error happens: ", e) 94 | except Exception as e: 95 | print("Error happens: ", e) 96 | 97 | 98 | def time_of_detector(): 99 | import time 100 | image = [3, 19, 20] 101 | for name in image: 102 | image = cv2.imread(("../resource/{}-left.jpg".format(name))) 103 | for method in (cv2.xfeatures2d.SURF_create, cv2.xfeatures2d.SIFT_create, cv2.ORB_create): 104 | 105 | start = time.time() 106 | for i in range(10): 107 | method().detectAndCompute(image, None) 108 | print("Image {}, using {}".format(name, method)) 109 | print("Spend time: ", time.time() - start) 110 | 111 | 112 | def main(): 113 | preview() 114 | # work() 115 | # time_of_detector() 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | --------------------------------------------------------------------------------