├── .gitignore ├── CHANGES.txt ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bin ├── calibrate_cameras ├── capture_chessboards ├── images_to_pointcloud ├── show_webcams └── tune_blockmatcher ├── doc ├── Makefile ├── changes.rst ├── conf.py ├── development.rst ├── index.rst ├── make.bat └── usage.rst ├── setup.py └── stereovision ├── __init__.py ├── blockmatchers.py ├── calibration.py ├── exceptions.py ├── point_cloud.py ├── stereo_cameras.py └── ui_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | StereoVision.egg-info/ 3 | external/ 4 | *.pyc 5 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Pending -- Fix #42 (thank you @Jgil10) 2 | 3 | v1.0.3, 8 Feb 2016 -- Add reference to tutorials 4 | 5 | v1.0.3, 14 January 2016 -- Fix botched merge 6 | 7 | v1.0.1, 21 March 2015 -- Fix UI bug 8 | 9 | v1.0.0, 2 May 2014 -- Initial release 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGES.txt 3 | include LICENSE 4 | include doc/*rst 5 | include doc/conf.py 6 | include doc/make.bat 7 | include doc/Makefile 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. warning:: 2 | This repository is no longer maintained! If you'd like to take over stewardship of the code, please feel free to get in touch. 3 | I am leaving it here for archival and educational purposes. 4 | 5 | StereoVision: Library and utilities for 3d reconstruction from stereo cameras 6 | ============================================================================= 7 | 8 | StereoVision is a package for working with stereo cameras, especially with the 9 | intent of using them to produce 3D point clouds. The focus is on performance, 10 | ease of usability, and the ability to construct 3D imaging setups cheaply. 11 | 12 | StereoVision relies heavily on OpenCV. If you're not sure about what a given 13 | variable does or what values would make sense for it and no explanation is 14 | provided in the StereoVision documentation, refer to OpenCV's documentation in 15 | order to better understand how they work. 16 | 17 | It's available on PyPI, so you can install it like this:: 18 | 19 | pip install StereoVision 20 | 21 | Tutorials are available on the Stackable blog: 22 | 23 | - `Building a stereo rig`_ 24 | - `Stereo calibration`_ 25 | - `Tuning the block matcher`_ 26 | - `Producing point clouds`_ 27 | 28 | If you find a bug or would like to request a feature, please `report it with 29 | the issue tracker `_. If you'd 30 | like to contribute to StereoVision, feel free to `fork it on GitHub 31 | `_. 32 | 33 | StereoVision is released under the GNU General Public License, so feel free to 34 | use it any way you like. It would be nice to let me know if you do anything 35 | cool with it though. 36 | 37 | Author: `Daniel Lee `_ 38 | 39 | .. _Building a stereo rig: https://erget.wordpress.com/2014/02/01/calibrating-a-stereo-camera-with-opencv/ 40 | .. _Stereo calibration: https://erget.wordpress.com/2014/02/28/calibrating-a-stereo-pair-with-python/ 41 | .. _Tuning the block matcher: https://erget.wordpress.com/2014/05/02/producing-3d-point-clouds-from-stereo-photos-tuning-the-block-matcher-for-best-results/ 42 | .. _Producing point clouds: https://erget.wordpress.com/2014/04/27/producing-3d-point-clouds-with-a-stereo-camera-in-opencv 43 | -------------------------------------------------------------------------------- /bin/calibrate_cameras: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | ## Copyright (C) 2014 Daniel Lee 3 | # 4 | # This file is part of StereoVision. 5 | # 6 | # StereoVision is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # StereoVision is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with StereoVision. If not, see . 18 | 19 | """ 20 | Calibrate stereo camera based on detected chessboard corners. 21 | """ 22 | 23 | from argparse import ArgumentParser 24 | from stereovision.ui_utils import (find_files, calibrate_folder, 25 | CHESSBOARD_ARGUMENTS) 26 | 27 | 28 | def main(): 29 | """ 30 | Read all images in input folder and produce camera calibration files. 31 | 32 | First, parse arguments provided by user. Then scan input folder for input 33 | files. Harvest chessboard points from each image in folder, then use them 34 | to calibrate the stereo pair. Report average error to user and export 35 | calibration files to output folder. 36 | """ 37 | parser = ArgumentParser(description="Read images taken with " 38 | "stereo pair and use them to compute " 39 | "camera calibration.", 40 | parents=[CHESSBOARD_ARGUMENTS]) 41 | parser.add_argument("input_folder", help="Input folder assumed to contain " 42 | "only stereo images taken with the stereo camera pair " 43 | "that should be calibrated.") 44 | parser.add_argument("output_folder", help="Folder to write calibration " 45 | "files to.", default="/tmp/") 46 | parser.add_argument("--show-chessboards", help="Display detected " 47 | "chessboard corners.", action="store_true") 48 | args = parser.parse_args() 49 | 50 | print args.input_folder 51 | args.input_files = find_files(args.input_folder) 52 | calibrate_folder(args) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /bin/capture_chessboards: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | # Copyright (C) 2014 Daniel Lee 3 | # 4 | # This file is part of StereoVision. 5 | # 6 | # StereoVision is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # StereoVision is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with StereoVision. If not, see . 18 | 19 | """ 20 | Take pictures of a chessboard visible to both cameras in a stereo pair. 21 | """ 22 | 23 | from argparse import ArgumentParser 24 | import os 25 | 26 | import cv2 27 | from progressbar import ProgressBar, Bar, Percentage 28 | from stereovision.stereo_cameras import ChessboardFinder 29 | from stereovision.ui_utils import calibrate_folder, CHESSBOARD_ARGUMENTS 30 | from stereovision.ui_utils import find_files 31 | import time 32 | 33 | PROGRAM_DESCRIPTION=( 34 | "Take a number of pictures with a stereo camera in which a chessboard is " 35 | "visible to both cameras. The program waits until a chessboard is detected in " 36 | "both camera frames. The pictures are then saved to a file in the specified " 37 | "output folder. After five seconds, the cameras are rescanned to find another " 38 | "chessboard perspective. This continues until the specified number of pictures " 39 | "has been taken." 40 | ) 41 | 42 | 43 | def main(): 44 | parser = ArgumentParser(description=PROGRAM_DESCRIPTION, 45 | parents=[CHESSBOARD_ARGUMENTS]) 46 | parser.add_argument("left", metavar="left", type=int, 47 | help="Device numbers for the left camera.") 48 | parser.add_argument("right", metavar="right", type=int, 49 | help="Device numbers for the right camera.") 50 | parser.add_argument("num_pictures", type=int, help="Number of valid " 51 | "chessboard pictures that should be taken.") 52 | parser.add_argument("output_folder", help="Folder to save the images to.") 53 | parser.add_argument("--calibration-folder", help="Folder to save camera " 54 | "calibration to.") 55 | args = parser.parse_args() 56 | if args.calibration_folder and not args.square_size: 57 | args.print_help() 58 | 59 | progress = ProgressBar(maxval=args.num_pictures, 60 | widgets=[Bar("=", "[", "]"), 61 | " ", Percentage()]) 62 | if not os.path.exists(args.output_folder): 63 | os.makedirs(args.output_folder) 64 | progress.start() 65 | with ChessboardFinder((args.left, args.right)) as pair: 66 | 67 | # Sets initial position of windows, based on image size 68 | set_window_position(pair) 69 | 70 | for i in range(args.num_pictures): 71 | 72 | # Introduces a 5 second delay before the camera pair is scanned for new images 73 | enforce_delay(pair, 5) 74 | 75 | frames, corners = pair.get_chessboard(args.columns, args.rows, True) 76 | for side, frame in zip(("left", "right"), frames): 77 | number_string = str(i + 1).zfill(len(str(args.num_pictures))) 78 | filename = "{}_{}.ppm".format(side, number_string) 79 | output_path = os.path.join(args.output_folder, filename) 80 | cv2.imwrite(output_path, frame) 81 | 82 | progress.update(progress.maxval - (args.num_pictures - i)) 83 | 84 | # Displays the recent accepted image pair. Helps in generating diverse calibration images. 85 | show_selected_frames(frames, corners, pair, args, True) 86 | 87 | progress.finish() 88 | cv2.destroyAllWindows() 89 | 90 | if args.calibration_folder: 91 | args.input_files = find_files(args.output_folder) 92 | args.output_folder = args.calibration_folder 93 | args.show_chessboards = True 94 | calibrate_folder(args) 95 | 96 | 97 | def show_selected_frames(frames, corners, pair, args, draw_corners=False): 98 | """ 99 | Display the most recently captured (left as well as right) images. 100 | If draw_corners is set to true, the identified corners are marked on the images. 101 | """ 102 | 103 | if draw_corners: 104 | for frame, corner in zip(frames, corners): 105 | cv2.drawChessboardCorners(frame, (args.columns, args.rows), corner, True) 106 | 107 | cv2.imshow("{} selected".format(pair.windows[0]), frames[0]) 108 | cv2.imshow("{} selected".format(pair.windows[1]), frames[1]) 109 | 110 | 111 | def enforce_delay(pair, delay): 112 | """ 113 | Enforces a delay of 5 seconds. This helps the user to change the chessboard perspective. 114 | A timer is displayed indicating the time remaining before the next sample is captured. 115 | """ 116 | 117 | font = cv2.FONT_HERSHEY_SIMPLEX 118 | line_type = cv2.LINE_4 119 | line_thickness = 4 120 | 121 | start_time = time.time() 122 | now = start_time 123 | 124 | while now - start_time < delay: 125 | 126 | frames = pair.get_frames() 127 | 128 | # Calculates the time remaining before the next sample is captured 129 | time_remaining = "{:.2f}".format(delay - now + start_time) 130 | 131 | # Estimating the scale factor. 132 | font_scale = get_approx_font_scale(frames[0], time_remaining, font, line_thickness) 133 | 134 | text_size = cv2.getTextSize(time_remaining, font, font_scale, line_thickness)[0] 135 | 136 | # Calculates the position of the text 137 | text_x = (frames[0].shape[1] - text_size[0]) / 2 138 | text_y = (frames[0].shape[0] + text_size[1]) / 2 139 | 140 | for frame, window in zip(frames, pair.windows): 141 | cv2.putText(frame, time_remaining, (text_x, text_y), font, font_scale, (255, 50, 50), 142 | line_thickness, line_type) 143 | cv2.imshow(window, frame) 144 | 145 | cv2.waitKey(1) 146 | now = time.time() 147 | 148 | 149 | def get_approx_font_scale(frame, text, font, line_thickness): 150 | """ 151 | Approximate the font scale for the timer display. 152 | """ 153 | 154 | _, width = frame.shape[:2] 155 | target_width = width / 2 156 | 157 | base_text_size = cv2.getTextSize(text, font, 1.0, line_thickness)[0] 158 | scale_factor = float(target_width) / base_text_size[0] 159 | 160 | return scale_factor 161 | 162 | 163 | def set_window_position(pair): 164 | 165 | """ 166 | Set initial the positions of windows. 167 | The top left and right windows display the live cam stream with timer overlay. 168 | The bottom left and right windows display recently selected frame. 169 | """ 170 | 171 | frames = pair.get_frames() 172 | pair.show_frames(1) 173 | 174 | # Setting initial position of cameras 175 | cv2.moveWindow(pair.windows[0], 0, 0) 176 | cv2.moveWindow(pair.windows[1], frames[1].shape[1], 0) 177 | 178 | # Setting initial position of selected frames 179 | cv2.namedWindow("{} selected".format(pair.windows[0]), cv2.WINDOW_AUTOSIZE) 180 | cv2.moveWindow("{} selected".format(pair.windows[0]), 0, frames[0].shape[0] + 30) 181 | 182 | cv2.namedWindow("{} selected".format(pair.windows[1]), cv2.WINDOW_AUTOSIZE) 183 | cv2.moveWindow("{} selected".format(pair.windows[1]), frames[1].shape[1], frames[1].shape[0] + 30) 184 | 185 | 186 | if __name__ == "__main__": 187 | main() 188 | -------------------------------------------------------------------------------- /bin/images_to_pointcloud: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | # Copyright (C) 2014 Daniel Lee 3 | # 4 | # This file is part of StereoVision. 5 | # 6 | # StereoVision is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # StereoVision is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with StereoVision. If not, see . 18 | 19 | """ 20 | Tool for creating and exporting colored point clouds from stereo image pairs. 21 | """ 22 | 23 | import argparse 24 | 25 | import cv2 26 | from stereovision.blockmatchers import StereoBM, StereoSGBM 27 | from stereovision.calibration import StereoCalibration 28 | from stereovision.stereo_cameras import CalibratedPair 29 | from stereovision.ui_utils import STEREO_BM_FLAG 30 | 31 | 32 | def main(): 33 | """Produce PLY point clouds from stereo image pair.""" 34 | parser = argparse.ArgumentParser(description="Read images taken with " 35 | "stereo pair and use them to produce 3D " 36 | "point clouds that can be viewed with " 37 | "MeshLab.", parents=[STEREO_BM_FLAG]) 38 | parser.add_argument("calibration", help="Path to calibration folder.") 39 | parser.add_argument("left", help="Path to left image") 40 | parser.add_argument("right", help="Path to right image") 41 | parser.add_argument("output", help="Path to output file.") 42 | parser.add_argument("--bm_settings", 43 | help="Path to block matcher's settings.") 44 | args = parser.parse_args() 45 | 46 | image_pair = [cv2.imread(image) for image in [args.left, args.right]] 47 | calib_folder = args.calibration 48 | if args.use_stereobm: 49 | block_matcher = StereoBM() 50 | else: 51 | block_matcher = StereoSGBM() 52 | if args.bm_settings: 53 | block_matcher.load_settings(args.bm_settings) 54 | 55 | camera_pair = CalibratedPair(None, 56 | StereoCalibration(input_folder=calib_folder), 57 | block_matcher) 58 | rectified_pair = camera_pair.calibration.rectify(image_pair) 59 | points = camera_pair.get_point_cloud(rectified_pair) 60 | points = points.filter_infinity() 61 | points.write_ply(args.output) 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /bin/show_webcams: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | # Copyright (C) 2014 Daniel Lee 3 | # 4 | # This file is part of StereoVision. 5 | # 6 | # StereoVision is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # StereoVision is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with StereoVision. If not, see . 18 | 19 | import argparse 20 | import os 21 | import time 22 | 23 | import cv2 24 | 25 | from stereovision.stereo_cameras import StereoPair 26 | 27 | 28 | def main(): 29 | """ 30 | Show the video from two webcams successively. 31 | 32 | For best results, connect the webcams while starting the computer. 33 | I have noticed that in some cases, if the webcam is not already connected 34 | when the computer starts, the USB device runs out of memory. Switching the 35 | camera to another USB port has also caused this problem in my experience. 36 | """ 37 | parser = argparse.ArgumentParser(description="Show video from two " 38 | "webcams.\n\nPress 'q' to exit.") 39 | parser.add_argument("devices", type=int, nargs=2, help="Device numbers " 40 | "for the cameras that should be accessed in order " 41 | " (left, right).") 42 | parser.add_argument("--output_folder", 43 | help="Folder to write output images to.") 44 | parser.add_argument("--interval", type=float, default=1, 45 | help="Interval (s) to take pictures in.") 46 | args = parser.parse_args() 47 | 48 | with StereoPair(args.devices) as pair: 49 | if not args.output_folder: 50 | pair.show_videos() 51 | else: 52 | i = 1 53 | while True: 54 | start = time.time() 55 | while time.time() < start + args.interval: 56 | pair.show_frames(1) 57 | images = pair.get_frames() 58 | for side, image in zip(("left", "right"), images): 59 | filename = "{}_{}.ppm".format(side, i) 60 | output_path = os.path.join(args.output_folder, filename) 61 | cv2.imwrite(output_path, image) 62 | i += 1 63 | 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /bin/tune_blockmatcher: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | # Copyright (C) 2014 Daniel Lee 3 | # 4 | # This file is part of StereoVision. 5 | # 6 | # StereoVision is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # StereoVision is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with StereoVision. If not, see . 18 | 19 | """ 20 | Let user tune all images in the input folder and report chosen values. 21 | 22 | Load a calibration from file and instantiate a ``BlockMatcher`` of the type 23 | requested by the user. Load images successively from input folder and display 24 | their resultant disparity map generated with the ``BlockMatcher`` and the 25 | parameters chosen in the ``BMTuner``'s GUI. Afterwards, report user's chosen 26 | settings and, if a file for the BM settings is provided, save the most common 27 | settings to file. 28 | """ 29 | 30 | from argparse import ArgumentParser 31 | 32 | import cv2 33 | 34 | from stereovision.blockmatchers import StereoBM, StereoSGBM 35 | from stereovision.calibration import StereoCalibration 36 | from stereovision.ui_utils import find_files, BMTuner, STEREO_BM_FLAG 37 | 38 | 39 | def main(): 40 | parser = ArgumentParser(description="Read images taken from a calibrated " 41 | "stereo pair, compute disparity maps from them and " 42 | "show them interactively to the user, allowing the " 43 | "user to tune the stereo block matcher settings in " 44 | "the GUI.", parents=[STEREO_BM_FLAG]) 45 | parser.add_argument("calibration_folder", 46 | help="Directory where calibration files for the stereo " 47 | "pair are stored.") 48 | parser.add_argument("image_folder", 49 | help="Directory where input images are stored.") 50 | parser.add_argument("--bm_settings", 51 | help="File to save last block matcher settings to.", 52 | default="") 53 | args = parser.parse_args() 54 | 55 | calibration = StereoCalibration(input_folder=args.calibration_folder) 56 | input_files = find_files(args.image_folder) 57 | if args.use_stereobm: 58 | block_matcher = StereoBM() 59 | else: 60 | block_matcher = StereoSGBM() 61 | image_pair = [cv2.imread(image) for image in input_files[:2]] 62 | input_files = input_files[2:] 63 | rectified_pair = calibration.rectify(image_pair) 64 | tuner = BMTuner(block_matcher, calibration, rectified_pair) 65 | 66 | while input_files: 67 | image_pair = [cv2.imread(image) for image in input_files[:2]] 68 | rectified_pair = calibration.rectify(image_pair) 69 | tuner.tune_pair(rectified_pair) 70 | input_files = input_files[2:] 71 | 72 | for param in block_matcher.parameter_maxima: 73 | print("{}\n".format(tuner.report_settings(param))) 74 | 75 | if args.bm_settings: 76 | block_matcher.save_settings(args.bm_settings) 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | MODULES=blockmatchers calibration exceptions point_cloud stereo_cameras ui_utils 23 | 24 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 25 | 26 | help: 27 | @echo "Please use \`make ' where is one of" 28 | @echo " html to make standalone HTML files" 29 | @echo " dirhtml to make HTML files named index.html in directories" 30 | @echo " singlehtml to make a single large HTML file" 31 | @echo " pickle to make pickle files" 32 | @echo " json to make JSON files" 33 | @echo " htmlhelp to make HTML files and a HTML help project" 34 | @echo " qthelp to make HTML files and a qthelp project" 35 | @echo " devhelp to make HTML files and a Devhelp project" 36 | @echo " epub to make an epub" 37 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 38 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 39 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 40 | @echo " text to make text files" 41 | @echo " man to make manual pages" 42 | @echo " texinfo to make Texinfo files" 43 | @echo " info to make Texinfo files and run them through makeinfo" 44 | @echo " gettext to make PO message catalogs" 45 | @echo " changes to make an overview of all changed/added/deprecated items" 46 | @echo " xml to make Docutils-native XML files" 47 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 48 | @echo " linkcheck to check all external links for integrity" 49 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | rm -f *svg 54 | 55 | html: autographics 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | 61 | 62 | dirhtml: autographics 63 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 64 | @echo 65 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 66 | 67 | singlehtml: autographics 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | pickle: autographics 73 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 74 | @echo 75 | @echo "Build finished; now you can process the pickle files." 76 | 77 | json: autographics 78 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 79 | @echo 80 | @echo "Build finished; now you can process the JSON files." 81 | 82 | htmlhelp: autographics 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | qthelp: autographics 89 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 90 | @echo 91 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 92 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 93 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/StereoVision.qhcp" 94 | @echo "To view the help file:" 95 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/StereoVision.qhc" 96 | 97 | devhelp: autographics 98 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 99 | @echo 100 | @echo "Build finished." 101 | @echo "To view the help file:" 102 | @echo "# mkdir -p $$HOME/.local/share/devhelp/StereoVision" 103 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/StereoVision" 104 | @echo "# devhelp" 105 | 106 | epub: autographics 107 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 108 | @echo 109 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 110 | 111 | latex: autographics 112 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 113 | @echo 114 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 115 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 116 | "(use \`make latexpdf' here to do that automatically)." 117 | 118 | latexpdf: autographics 119 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 120 | @echo "Running LaTeX files through pdflatex..." 121 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 122 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 123 | 124 | latexpdfja: autographics 125 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 126 | @echo "Running LaTeX files through platex and dvipdfmx..." 127 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 128 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 129 | 130 | text: autographics 131 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 132 | @echo 133 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 134 | 135 | man: autographics 136 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 137 | @echo 138 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 139 | 140 | texinfo: autographics 141 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 142 | @echo 143 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 144 | @echo "Run \`make' in that directory to run these through makeinfo" \ 145 | "(use \`make info' here to do that automatically)." 146 | 147 | info: autographics 148 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 149 | @echo "Running Texinfo files through makeinfo..." 150 | make -C $(BUILDDIR)/texinfo info 151 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 152 | 153 | gettext: autographics 154 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 155 | @echo 156 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 157 | 158 | changes: autographics 159 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 160 | @echo 161 | @echo "The overview file is in $(BUILDDIR)/changes." 162 | 163 | linkcheck: autographics 164 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 165 | @echo 166 | @echo "Link check complete; look for any errors in the above output " \ 167 | "or in $(BUILDDIR)/linkcheck/output.txt." 168 | 169 | doctest: autographics 170 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 171 | @echo "Testing of doctests in the sources finished, look at the " \ 172 | "results in $(BUILDDIR)/doctest/output.txt." 173 | 174 | xml: autographics 175 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 176 | @echo 177 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 178 | 179 | pseudoxml: autographics 180 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 181 | @echo 182 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 183 | 184 | autographics: $(MODULES) 185 | cd .. ; \ 186 | pyreverse stereovision -o svg -p StereoVision 187 | rm ../classes_StereoVision.svg 188 | mv ../packages_StereoVision.svg . 189 | 190 | $(MODULES): 191 | pyreverse ../stereovision/$@.py -o svg -p $@ ; \ 192 | -------------------------------------------------------------------------------- /doc/changes.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | .. include:: ../CHANGES.txt 5 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # StereoVision documentation build configuration file, created by 4 | # sphinx-quickstart on Sun May 4 11:48:18 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('..')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 29 | autodoc_default_flags = ["members", "inherited-members", "show-inheritance"] 30 | autodoc_member_order = "bysource" 31 | autoclass_content = "both" 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'StereoVision' 47 | copyright = u'2014, Daniel Lee' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '1.0.0' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '1.0.0' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all documents. 73 | #default_role = None 74 | 75 | # If true, '()' will be appended to :func: etc. cross-reference text. 76 | #add_function_parentheses = True 77 | 78 | # If true, the current module name will be prepended to all description 79 | # unit titles (such as .. function::). 80 | #add_module_names = True 81 | 82 | # If true, sectionauthor and moduleauthor directives will be shown in the 83 | # output. They are ignored by default. 84 | #show_authors = False 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | # A list of ignored prefixes for module index sorting. 90 | #modindex_common_prefix = [] 91 | 92 | # If true, keep warnings as "system message" paragraphs in the built documents. 93 | #keep_warnings = False 94 | 95 | 96 | # -- Options for HTML output --------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. See the documentation for 99 | # a list of builtin themes. 100 | html_theme = 'nature' 101 | 102 | # Theme options are theme-specific and customize the look and feel of a theme 103 | # further. For a list of options available for each theme, see the 104 | # documentation. 105 | #html_theme_options = {} 106 | 107 | # Add any paths that contain custom themes here, relative to this directory. 108 | #html_theme_path = [] 109 | 110 | # The name for this set of Sphinx documents. If None, it defaults to 111 | # " v documentation". 112 | #html_title = None 113 | 114 | # A shorter title for the navigation bar. Default is the same as html_title. 115 | #html_short_title = None 116 | 117 | # The name of an image file (relative to this directory) to place at the top 118 | # of the sidebar. 119 | #html_logo = None 120 | 121 | # The name of an image file (within the static path) to use as favicon of the 122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 123 | # pixels large. 124 | #html_favicon = None 125 | 126 | # Add any paths that contain custom static files (such as style sheets) here, 127 | # relative to this directory. They are copied after the builtin static files, 128 | # so a file named "default.css" will overwrite the builtin "default.css". 129 | html_static_path = ['_static'] 130 | 131 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 132 | # using the given strftime format. 133 | #html_last_updated_fmt = '%b %d, %Y' 134 | 135 | # If true, SmartyPants will be used to convert quotes and dashes to 136 | # typographically correct entities. 137 | #html_use_smartypants = True 138 | 139 | # Custom sidebar templates, maps document names to template names. 140 | #html_sidebars = {} 141 | 142 | # Additional templates that should be rendered to pages, maps page names to 143 | # template names. 144 | #html_additional_pages = {} 145 | 146 | # If false, no module index is generated. 147 | #html_domain_indices = True 148 | 149 | # If false, no index is generated. 150 | #html_use_index = True 151 | 152 | # If true, the index is split into individual pages for each letter. 153 | #html_split_index = False 154 | 155 | # If true, links to the reST sources are added to the pages. 156 | #html_show_sourcelink = True 157 | 158 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 159 | #html_show_sphinx = True 160 | 161 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 162 | #html_show_copyright = True 163 | 164 | # If true, an OpenSearch description file will be output, and all pages will 165 | # contain a tag referring to it. The value of this option must be the 166 | # base URL from which the finished HTML is served. 167 | #html_use_opensearch = '' 168 | 169 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 170 | #html_file_suffix = None 171 | 172 | # Output file base name for HTML help builder. 173 | htmlhelp_basename = 'StereoVisiondoc' 174 | 175 | 176 | # -- Options for LaTeX output -------------------------------------------------- 177 | 178 | latex_elements = { 179 | # The paper size ('letterpaper' or 'a4paper'). 180 | #'papersize': 'letterpaper', 181 | 182 | # The font size ('10pt', '11pt' or '12pt'). 183 | #'pointsize': '10pt', 184 | 185 | # Additional stuff for the LaTeX preamble. 186 | #'preamble': '', 187 | } 188 | 189 | # Grouping the document tree into LaTeX files. List of tuples 190 | # (source start file, target name, title, author, documentclass [howto/manual]). 191 | latex_documents = [ 192 | ('index', 'StereoVision.tex', u'StereoVision Documentation', 193 | u'Daniel Lee', 'manual'), 194 | ] 195 | 196 | # The name of an image file (relative to this directory) to place at the top of 197 | # the title page. 198 | #latex_logo = None 199 | 200 | # For "manual" documents, if this is true, then toplevel headings are parts, 201 | # not chapters. 202 | #latex_use_parts = False 203 | 204 | # If true, show page references after internal links. 205 | #latex_show_pagerefs = False 206 | 207 | # If true, show URL addresses after external links. 208 | #latex_show_urls = False 209 | 210 | # Documents to append as an appendix to all manuals. 211 | #latex_appendices = [] 212 | 213 | # If false, no module index is generated. 214 | #latex_domain_indices = True 215 | 216 | 217 | # -- Options for manual page output -------------------------------------------- 218 | 219 | # One entry per manual page. List of tuples 220 | # (source start file, name, description, authors, manual section). 221 | man_pages = [ 222 | ('index', 'stereovision', u'StereoVision Documentation', 223 | [u'Daniel Lee'], 1) 224 | ] 225 | 226 | # If true, show URL addresses after external links. 227 | #man_show_urls = False 228 | 229 | 230 | # -- Options for Texinfo output ------------------------------------------------ 231 | 232 | # Grouping the document tree into Texinfo files. List of tuples 233 | # (source start file, target name, title, author, 234 | # dir menu entry, description, category) 235 | texinfo_documents = [ 236 | ('index', 'StereoVision', u'StereoVision Documentation', 237 | u'Daniel Lee', 'StereoVision', 'One line description of project.', 238 | 'Miscellaneous'), 239 | ] 240 | 241 | # Documents to append as an appendix to all manuals. 242 | #texinfo_appendices = [] 243 | 244 | # If false, no module index is generated. 245 | #texinfo_domain_indices = True 246 | 247 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 248 | #texinfo_show_urls = 'footnote' 249 | 250 | # If true, do not generate a @detailmenu in the "Top" node's menu. 251 | #texinfo_no_detailmenu = False 252 | -------------------------------------------------------------------------------- /doc/development.rst: -------------------------------------------------------------------------------- 1 | StereoVision source documentation 2 | ================================= 3 | 4 | .. automodule:: stereovision 5 | 6 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. StereoVision documentation master file, created by 2 | sphinx-quickstart on Sun May 4 11:48:18 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.rst 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | usage 12 | development 13 | changes 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\StereoVision.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\StereoVision.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /doc/usage.rst: -------------------------------------------------------------------------------- 1 | Using StereoVision 2 | ================== 3 | 4 | ``StereoVision`` offers a number of command line utilities that you can use in 5 | order to produce 3d point clouds from stereo images. They are listed in the 6 | following table, roughly in the order that you would use them if you were to 7 | set up a project of your own from scratch. Usage information can be obtained on 8 | the command line by calling them with the ``-h`` and ``--help`` flags. 9 | 10 | ======================== =============================================== 11 | Script name Purpose 12 | ======================== =============================================== 13 | ``show_webcams`` Show output from stereo camera pair, optionally 14 | capture images 15 | ``capture_chessboards`` Capture images of chessboards simultaneously 16 | visible from both cameras in stereo pair for 17 | the purpose of calibrating the camera pair. 18 | Optionally calibrate camera pair online. 19 | ``calibrate_cameras`` Calibrate stereo pair using previously captured 20 | chessboard images 21 | ``tune_blockmatcher`` Manually tune block matching algorithm to 22 | produce good disparity maps with a given stereo 23 | pair 24 | ``images_to_pointcloud`` Convert image pairs captured with a calibrated 25 | stereo pair to a colored 3d point cloud 26 | ======================== =============================================== 27 | 28 | These scripts can also be used as examples for how to use the classes in 29 | ``StereoVision``. 30 | 31 | If you'd like to use the library classes available in ``StereoVision``, see 32 | the `developer documentation `_. 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | from setuptools import setup 19 | 20 | setup(name="StereoVision", 21 | version="1.0.3", 22 | description=("Library and utilities for 3d reconstruction from stereo " 23 | "cameras."), 24 | long_description=open("README.rst").read(), 25 | author="Daniel Lee", 26 | author_email="lee.daniel.1986@gmail.com", 27 | packages=["stereovision"], 28 | scripts=["bin/calibrate_cameras", 29 | "bin/capture_chessboards", 30 | "bin/images_to_pointcloud", 31 | "bin/show_webcams", 32 | "bin/tune_blockmatcher"], 33 | url="http://erget.github.com/StereoVision", 34 | download_url="http://pypi.python.org/pypi/StereoVision", 35 | license="GNU GPL", 36 | requires=["cv2", 37 | "simplejson", 38 | "numpy", 39 | "progressbar"], 40 | provides=["stereovision"], 41 | classifiers=["Development Status :: 5 - Production/Stable", 42 | "Natural Language :: English", 43 | "Operating System :: OS Independent", 44 | "Programming Language :: Python :: 2", 45 | "Intended Audience :: Developers", 46 | "Intended Audience :: Education", 47 | "Intended Audience :: Science/Research", 48 | "License :: Freely Distributable", 49 | "License :: OSI Approved :: GNU General Public License v3 " 50 | "or later (GPLv3+)", 51 | "Natural Language :: English", 52 | "Operating System :: OS Independent", 53 | "Programming Language :: Python :: 2.7", 54 | "Topic :: Multimedia :: Graphics :: Capture"]) 55 | -------------------------------------------------------------------------------- /stereovision/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | """ 19 | Utilities for 3d reconstruction using stereo cameras. 20 | 21 | Modules: 22 | 23 | * ``stereo_cameras`` - Camera interfaces 24 | * ``calibration`` - Tools for calibrating stereo cameras 25 | * ``blockmatchers`` - Blockmatching algorithm matchers 26 | * ``point_cloud`` - Point clouds 27 | * ``ui_utils`` - Utilities for user interaction 28 | * ``exceptions`` - Various exceptions 29 | 30 | Import structure: 31 | 32 | .. image:: packages_StereoVision.svg 33 | :width: 100% 34 | 35 | Camera interfaces 36 | ***************** 37 | 38 | .. automodule:: stereovision.stereo_cameras 39 | 40 | Camera calibration 41 | ****************** 42 | 43 | .. automodule:: stereovision.calibration 44 | 45 | Block matchers 46 | ************** 47 | 48 | .. automodule:: stereovision.blockmatchers 49 | 50 | Point clouds 51 | ************ 52 | 53 | .. automodule:: stereovision.point_cloud 54 | 55 | User interface utilities 56 | ************************ 57 | 58 | .. automodule:: stereovision.ui_utils 59 | 60 | Exceptions 61 | ********** 62 | 63 | .. automodule:: stereovision.exceptions 64 | """ 65 | -------------------------------------------------------------------------------- /stereovision/blockmatchers.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | ''' 19 | Wrapper classes for block matching algorithms. 20 | 21 | Classes: 22 | 23 | * ``BlockMatcher`` - Abstract class that implements interface for subclasses 24 | 25 | * ``StereoBM`` - StereoBM block matching algorithm 26 | * ``StereoSGBM`` - StereoSGBM block matching algorithm 27 | 28 | .. image:: classes_blockmatchers.svg 29 | ''' 30 | 31 | import cv2 32 | import simplejson 33 | 34 | import numpy as np 35 | from stereovision.exceptions import (InvalidSearchRangeError, 36 | InvalidWindowSizeError, 37 | InvalidBMPresetError, 38 | InvalidNumDisparitiesError, 39 | InvalidSADWindowSizeError, 40 | InvalidUniquenessRatioError, 41 | InvalidSpeckleWindowSizeError, 42 | InvalidSpeckleRangeError, 43 | InvalidFirstDisparityChangePenaltyError, 44 | InvalidSecondDisparityChangePenaltyError) 45 | 46 | 47 | class BlockMatcher(object): 48 | 49 | """ 50 | Block matching algorithms. 51 | 52 | This abstract class exposes the interface for subclasses that wrap OpenCV's 53 | block matching algorithms. Doing so makes it possible to use them in the 54 | strategy pattern. In this library, that happens in ``CalibratedPair``, which 55 | uses a unified interface to interact with any kind of block matcher, and 56 | with ``BMTuners``, which can discover the ``BlockMatcher's`` parameters and 57 | allow the user to adjust them online. 58 | 59 | Each ``BlockMatcher`` protects its block matcher's parameters by using 60 | getters and setters. It exposes its settable parameter and their maximum 61 | values, if they exist, in the dictionary ``parameter_maxima``. 62 | 63 | ``load_settings``, ``save_settings`` and ``get_3d`` are implemented on 64 | ``BlockMatcher`` itself, as these are independent of the block matching 65 | algorithm. Subclasses are expected to implement ``_replace_bm`` and 66 | ``get_disparity``, as well as the getters and setters. They are also 67 | expected to call ``BlockMatcher``'s ``__init__`` after setting their own 68 | private variables. 69 | """ 70 | 71 | #: Dictionary of parameter names associated with their maximum values 72 | parameter_maxima = {} 73 | 74 | def __init__(self, settings=None): 75 | """Set block matcher parameters and load from file if necessary.""" 76 | #: Block matcher object used for computing point clouds 77 | self._block_matcher = None 78 | self._replace_bm() 79 | if settings: 80 | self.load_settings(settings) 81 | 82 | def load_settings(self, settings): 83 | """Load settings from file""" 84 | with open(settings) as settings_file: 85 | settings_dict = simplejson.load(settings_file) 86 | for key, value in settings_dict.items(): 87 | self.__setattr__(key, value) 88 | 89 | def save_settings(self, settings_file): 90 | """Save block matcher settings to a file object""" 91 | settings = {} 92 | for parameter in self.parameter_maxima: 93 | settings[parameter] = self.__getattribute__(parameter) 94 | with open(settings_file, "w") as settings_file: 95 | simplejson.dump(settings, settings_file) 96 | 97 | @classmethod 98 | def get_3d(cls, disparity, disparity_to_depth_map): 99 | """Compute point cloud.""" 100 | return cv2.reprojectImageTo3D(disparity, disparity_to_depth_map) 101 | 102 | def _replace_bm(self): 103 | """Replace block matcher with new parameters""" 104 | raise NotImplementedError 105 | 106 | def get_disparity(self, image_pair): 107 | """Compute disparity map from image pair.""" 108 | raise NotImplementedError 109 | 110 | 111 | class StereoBM(BlockMatcher): 112 | 113 | """A stereo block matching ``BlockMatcher``.""" 114 | 115 | parameter_maxima = {"search_range": None, 116 | "window_size": 255, 117 | "stereo_bm_preset": cv2.STEREO_BM_NARROW_PRESET} 118 | 119 | @property 120 | def search_range(self): 121 | """Return private ``_search_range`` value.""" 122 | return self._search_range 123 | 124 | @search_range.setter 125 | def search_range(self, value): 126 | """Set private ``_search_range`` and reset ``_block_matcher``.""" 127 | if value == 0 or not value % 16: 128 | self._search_range = value 129 | else: 130 | raise InvalidSearchRangeError("Search range must be a multiple of " 131 | "16.") 132 | self._replace_bm() 133 | 134 | @property 135 | def window_size(self): 136 | """Return private ``_window_size`` value.""" 137 | return self._window_size 138 | 139 | @window_size.setter 140 | def window_size(self, value): 141 | """Set private ``_window_size`` and reset ``_block_matcher``.""" 142 | if (value > 4 and 143 | value < self.parameter_maxima["window_size"] and 144 | value % 2): 145 | self._window_size = value 146 | else: 147 | raise InvalidWindowSizeError("Window size must be an odd number " 148 | "between 0 and {}.".format( 149 | self.parameter_maxima["window_size"] + 1)) 150 | self._replace_bm() 151 | 152 | @property 153 | def stereo_bm_preset(self): 154 | """Return private ``_bm_preset`` value.""" 155 | return self._bm_preset 156 | 157 | @stereo_bm_preset.setter 158 | def stereo_bm_preset(self, value): 159 | """Set private ``_stereo_bm_preset`` and reset ``_block_matcher``.""" 160 | if value in (cv2.STEREO_BM_BASIC_PRESET, 161 | cv2.STEREO_BM_FISH_EYE_PRESET, 162 | cv2.STEREO_BM_NARROW_PRESET): 163 | self._bm_preset = value 164 | else: 165 | raise InvalidBMPresetError("Stereo BM preset must be defined as " 166 | "cv2.STEREO_BM_*_PRESET.") 167 | self._replace_bm() 168 | 169 | def _replace_bm(self): 170 | """Replace ``_block_matcher`` with current values.""" 171 | self._block_matcher = cv2.StereoBM(preset=self._bm_preset, 172 | ndisparities=self._search_range, 173 | SADWindowSize=self._window_size) 174 | 175 | def __init__(self, stereo_bm_preset=cv2.STEREO_BM_BASIC_PRESET, 176 | search_range=80, 177 | window_size=21, 178 | settings=None): 179 | self._bm_preset = cv2.STEREO_BM_BASIC_PRESET 180 | self._search_range = 0 181 | self._window_size = 5 182 | #: OpenCV camera type for ``_block_matcher`` 183 | self.stereo_bm_preset = stereo_bm_preset 184 | #: Number of disparities for ``_block_matcher`` 185 | self.search_range = search_range 186 | #: Search window size for ``_block_matcher`` 187 | self.window_size = window_size 188 | super(StereoBM, self).__init__(settings) 189 | 190 | def get_disparity(self, pair): 191 | """ 192 | Compute disparity from image pair (left, right). 193 | 194 | First, convert images to grayscale if needed. Then pass to the 195 | ``_block_matcher`` for stereo matching. 196 | """ 197 | gray = [] 198 | if pair[0].ndim == 3: 199 | for side in pair: 200 | gray.append(cv2.cvtColor(side, cv2.COLOR_BGR2GRAY)) 201 | else: 202 | gray = pair 203 | return self._block_matcher.compute(gray[0], gray[1], 204 | disptype=cv2.CV_32F) 205 | 206 | 207 | class StereoSGBM(BlockMatcher): 208 | 209 | """A semi-global block matcher.""" 210 | 211 | parameter_maxima = {"minDisparity": None, 212 | "numDisparities": None, 213 | "SADWindowSize": 11, 214 | "P1": None, 215 | "P2": None, 216 | "disp12MaxDiff": None, 217 | "uniquenessRatio": 15, 218 | "speckleWindowSize": 200, 219 | "speckleRange": 2, 220 | "fullDP": 1} 221 | 222 | @property 223 | def minDisparity(self): 224 | """Return private ``_min_disparity`` value.""" 225 | return self._min_disparity 226 | 227 | @minDisparity.setter 228 | def minDisparity(self, value): 229 | """Set private ``_min_disparity`` and reset ``_block_matcher``.""" 230 | self._min_disparity = value 231 | self._replace_bm() 232 | 233 | @property 234 | def numDisparities(self): 235 | """Return private ``_num_disp`` value.""" 236 | return self._num_disp 237 | 238 | @numDisparities.setter 239 | def numDisparities(self, value): 240 | """Set private ``_num_disp`` and reset ``_block_matcher``.""" 241 | if value > 0 and value % 16 == 0: 242 | self._num_disp = value 243 | else: 244 | raise InvalidNumDisparitiesError("numDisparities must be a " 245 | "positive integer evenly " 246 | "divisible by 16.") 247 | self._replace_bm() 248 | 249 | @property 250 | def SADWindowSize(self): 251 | """Return private ``_sad_window_size`` value.""" 252 | return self._sad_window_size 253 | 254 | @SADWindowSize.setter 255 | def SADWindowSize(self, value): 256 | """Set private ``_sad_window_size`` and reset ``_block_matcher``.""" 257 | if value >= 1 and value <= 11 and value % 2: 258 | self._sad_window_size = value 259 | else: 260 | raise InvalidSADWindowSizeError("SADWindowSize must be odd and " 261 | "between 1 and 11.") 262 | self._replace_bm() 263 | 264 | @property 265 | def uniquenessRatio(self): 266 | """Return private ``_uniqueness`` value.""" 267 | return self._uniqueness 268 | 269 | @uniquenessRatio.setter 270 | def uniquenessRatio(self, value): 271 | """Set private ``_uniqueness`` and reset ``_block_matcher``.""" 272 | if value >= 5 and value <= 15: 273 | self._uniqueness = value 274 | else: 275 | raise InvalidUniquenessRatioError("Uniqueness ratio must be " 276 | "between 5 and 15.") 277 | self._replace_bm() 278 | 279 | @property 280 | def speckleWindowSize(self): 281 | """Return private ``_speckle_window_size`` value.""" 282 | return self._speckle_window_size 283 | 284 | @speckleWindowSize.setter 285 | def speckleWindowSize(self, value): 286 | """Set private ``_speckle_window_size`` and reset ``_block_matcher``.""" 287 | if value >= 0 and value <= 200: 288 | self._speckle_window_size = value 289 | else: 290 | raise InvalidSpeckleWindowSizeError("Speckle window size must be 0 " 291 | "for disabled checks or " 292 | "between 50 and 200.") 293 | self._replace_bm() 294 | 295 | @property 296 | def speckleRange(self): 297 | """Return private ``_speckle_range`` value.""" 298 | return self._speckle_range 299 | 300 | @speckleRange.setter 301 | def speckleRange(self, value): 302 | """Set private ``_speckle_range`` and reset ``_block_matcher``.""" 303 | if value >= 0: 304 | self._speckle_range = value 305 | else: 306 | raise InvalidSpeckleRangeError("Speckle range cannot be negative.") 307 | self._replace_bm() 308 | 309 | @property 310 | def disp12MaxDiff(self): 311 | """Return private ``_max_disparity`` value.""" 312 | return self._max_disparity 313 | 314 | @disp12MaxDiff.setter 315 | def disp12MaxDiff(self, value): 316 | """Set private ``_max_disparity`` and reset ``_block_matcher``.""" 317 | self._max_disparity = value 318 | self._replace_bm() 319 | 320 | @property 321 | def P1(self): 322 | """Return private ``_P1`` value.""" 323 | return self._P1 324 | 325 | @P1.setter 326 | def P1(self, value): 327 | """Set private ``_P1`` and reset ``_block_matcher``.""" 328 | if value < self.P2: 329 | self._P1 = value 330 | else: 331 | raise InvalidFirstDisparityChangePenaltyError("P1 must be less " 332 | "than P2.") 333 | self._replace_bm() 334 | 335 | @property 336 | def P2(self): 337 | """Return private ``_P2`` value.""" 338 | return self._P2 339 | 340 | @P2.setter 341 | def P2(self, value): 342 | """Set private ``_P2`` and reset ``_block_matcher``.""" 343 | if value > self.P1: 344 | self._P2 = value 345 | else: 346 | raise InvalidSecondDisparityChangePenaltyError("P2 must be greater " 347 | "than P1.") 348 | self._replace_bm() 349 | 350 | @property 351 | def fullDP(self): 352 | """Return private ``_full_dp`` value.""" 353 | return self._full_dp 354 | 355 | @fullDP.setter 356 | def fullDP(self, value): 357 | """Set private ``_full_dp`` and reset ``_block_matcher``.""" 358 | self._full_dp = bool(value) 359 | self._replace_bm() 360 | 361 | def _replace_bm(self): 362 | """Replace ``_block_matcher`` with current values.""" 363 | self._block_matcher = cv2.StereoSGBM(minDisparity=self._min_disparity, 364 | numDisparities=self._num_disp, 365 | SADWindowSize=self._sad_window_size, 366 | uniquenessRatio=self._uniqueness, 367 | speckleWindowSize=self._speckle_window_size, 368 | speckleRange=self._speckle_range, 369 | disp12MaxDiff=self._max_disparity, 370 | P1=self._P1, 371 | P2=self._P2, 372 | fullDP=self._full_dp) 373 | 374 | def __init__(self, min_disparity=16, num_disp=96, sad_window_size=3, 375 | uniqueness=10, speckle_window_size=100, speckle_range=32, 376 | p1=216, p2=864, max_disparity=1, full_dp=False, 377 | settings=None): 378 | """Instantiate private variables and call superclass initializer.""" 379 | #: Minimum number of disparities. Normally 0, can be adjusted as 380 | #: needed 381 | self._min_disparity = min_disparity 382 | #: Number of disparities 383 | self._num_disp = num_disp 384 | #: Matched block size 385 | self._sad_window_size = sad_window_size 386 | #: Uniqueness ratio for found matches 387 | self._uniqueness = uniqueness 388 | #: Maximum size of smooth disparity regions to invalid by noise 389 | self._speckle_window_size = speckle_window_size 390 | #: Maximum disparity range within connected component 391 | self._speckle_range = speckle_range 392 | #: Penalty on disparity change by +-1 between neighbor pixels 393 | self._P1 = p1 394 | #: Penalty on disparity change by multiple neighbor pixels 395 | self._P2 = p2 396 | #: Maximum left-right disparity. 0 to disable check 397 | self._max_disparity = max_disparity 398 | #: Boolean to use full-scale two-pass dynamic algorithm 399 | self._full_dp = full_dp 400 | #: StereoSGBM whose state is controlled 401 | self._block_matcher = cv2.StereoSGBM() 402 | super(StereoSGBM, self).__init__(settings) 403 | 404 | def get_disparity(self, pair): 405 | """Compute disparity from image pair (left, right).""" 406 | return self._block_matcher.compute(pair[0], 407 | pair[1]).astype(np.float32) / 16.0 408 | -------------------------------------------------------------------------------- /stereovision/calibration.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | """ 19 | Classes for calibrating homemade stereo cameras. 20 | 21 | Classes: 22 | 23 | * ``StereoCalibration`` - Calibration for stereo camera 24 | * ``StereoCalibrator`` - Class to calibrate stereo camera with 25 | 26 | .. image:: classes_calibration.svg 27 | """ 28 | 29 | import os 30 | 31 | import cv2 32 | 33 | import numpy as np 34 | from stereovision.exceptions import ChessboardNotFoundError 35 | 36 | 37 | class StereoCalibration(object): 38 | 39 | """ 40 | A stereo camera calibration. 41 | 42 | The ``StereoCalibration`` stores the calibration for a stereo pair. It can 43 | also rectify pictures taken from its stereo pair. 44 | """ 45 | 46 | def __str__(self): 47 | output = "" 48 | for key, item in self.__dict__.items(): 49 | output += key + ":\n" 50 | output += str(item) + "\n" 51 | return output 52 | 53 | def _copy_calibration(self, calibration): 54 | """Copy another ``StereoCalibration`` object's values.""" 55 | for key, item in calibration.__dict__.items(): 56 | self.__dict__[key] = item 57 | 58 | def _interact_with_folder(self, output_folder, action): 59 | """ 60 | Export/import matrices as *.npy files to/from an output folder. 61 | 62 | ``action`` is a string. It determines whether the method reads or writes 63 | to disk. It must have one of the following values: ('r', 'w'). 64 | """ 65 | if not action in ('r', 'w'): 66 | raise ValueError("action must be either 'r' or 'w'.") 67 | for key, item in self.__dict__.items(): 68 | if isinstance(item, dict): 69 | for side in ("left", "right"): 70 | filename = os.path.join(output_folder, 71 | "{}_{}.npy".format(key, side)) 72 | if action == 'w': 73 | np.save(filename, self.__dict__[key][side]) 74 | else: 75 | self.__dict__[key][side] = np.load(filename) 76 | else: 77 | filename = os.path.join(output_folder, "{}.npy".format(key)) 78 | if action == 'w': 79 | np.save(filename, self.__dict__[key]) 80 | else: 81 | self.__dict__[key] = np.load(filename) 82 | 83 | def __init__(self, calibration=None, input_folder=None): 84 | """ 85 | Initialize camera calibration. 86 | 87 | If another calibration object is provided, copy its values. If an input 88 | folder is provided, load ``*.npy`` files from that folder. An input 89 | folder overwrites a calibration object. 90 | """ 91 | #: Camera matrices (M) 92 | self.cam_mats = {"left": None, "right": None} 93 | #: Distortion coefficients (D) 94 | self.dist_coefs = {"left": None, "right": None} 95 | #: Rotation matrix (R) 96 | self.rot_mat = None 97 | #: Translation vector (T) 98 | self.trans_vec = None 99 | #: Essential matrix (E) 100 | self.e_mat = None 101 | #: Fundamental matrix (F) 102 | self.f_mat = None 103 | #: Rectification transforms (3x3 rectification matrix R1 / R2) 104 | self.rect_trans = {"left": None, "right": None} 105 | #: Projection matrices (3x4 projection matrix P1 / P2) 106 | self.proj_mats = {"left": None, "right": None} 107 | #: Disparity to depth mapping matrix (4x4 matrix, Q) 108 | self.disp_to_depth_mat = None 109 | #: Bounding boxes of valid pixels 110 | self.valid_boxes = {"left": None, "right": None} 111 | #: Undistortion maps for remapping 112 | self.undistortion_map = {"left": None, "right": None} 113 | #: Rectification maps for remapping 114 | self.rectification_map = {"left": None, "right": None} 115 | if calibration: 116 | self._copy_calibration(calibration) 117 | elif input_folder: 118 | self.load(input_folder) 119 | 120 | def load(self, input_folder): 121 | """Load values from ``*.npy`` files in ``input_folder``.""" 122 | self._interact_with_folder(input_folder, 'r') 123 | 124 | def export(self, output_folder): 125 | """Export matrices as ``*.npy`` files to an output folder.""" 126 | if not os.path.exists(output_folder): 127 | os.makedirs(output_folder) 128 | self._interact_with_folder(output_folder, 'w') 129 | 130 | def rectify(self, frames): 131 | """ 132 | Rectify frames passed as (left, right) pair of OpenCV Mats. 133 | 134 | Remapping is done with nearest neighbor for speed. 135 | """ 136 | new_frames = [] 137 | for i, side in enumerate(("left", "right")): 138 | new_frames.append(cv2.remap(frames[i], 139 | self.undistortion_map[side], 140 | self.rectification_map[side], 141 | cv2.INTER_NEAREST)) 142 | return new_frames 143 | 144 | 145 | class StereoCalibrator(object): 146 | 147 | """A class that calibrates stereo cameras by finding chessboard corners.""" 148 | 149 | def _get_corners(self, image): 150 | """Find subpixel chessboard corners in image.""" 151 | temp = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 152 | ret, corners = cv2.findChessboardCorners(temp, 153 | (self.rows, self.columns)) 154 | if not ret: 155 | raise ChessboardNotFoundError("No chessboard could be found.") 156 | cv2.cornerSubPix(temp, corners, (11, 11), (-1, -1), 157 | (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 158 | 30, 0.01)) 159 | return corners 160 | 161 | def _show_corners(self, image, corners): 162 | """Show chessboard corners found in image.""" 163 | temp = image 164 | cv2.drawChessboardCorners(temp, (self.rows, self.columns), corners, 165 | True) 166 | window_name = "Chessboard" 167 | cv2.imshow(window_name, temp) 168 | if cv2.waitKey(0): 169 | cv2.destroyWindow(window_name) 170 | 171 | def __init__(self, rows, columns, square_size, image_size): 172 | """ 173 | Store variables relevant to the camera calibration. 174 | 175 | ``corner_coordinates`` are generated by creating an array of 3D 176 | coordinates that correspond to the actual positions of the chessboard 177 | corners observed on a 2D plane in 3D space. 178 | """ 179 | #: Number of calibration images 180 | self.image_count = 0 181 | #: Number of inside corners in the chessboard's rows 182 | self.rows = rows 183 | #: Number of inside corners in the chessboard's columns 184 | self.columns = columns 185 | #: Size of chessboard squares in cm 186 | self.square_size = square_size 187 | #: Size of calibration images in pixels 188 | self.image_size = image_size 189 | pattern_size = (self.rows, self.columns) 190 | corner_coordinates = np.zeros((np.prod(pattern_size), 3), np.float32) 191 | corner_coordinates[:, :2] = np.indices(pattern_size).T.reshape(-1, 2) 192 | corner_coordinates *= self.square_size 193 | #: Real world corner coordinates found in each image 194 | self.corner_coordinates = corner_coordinates 195 | #: Array of real world corner coordinates to match the corners found 196 | self.object_points = [] 197 | #: Array of found corner coordinates from calibration images for left 198 | #: and right camera, respectively 199 | self.image_points = {"left": [], "right": []} 200 | 201 | def add_corners(self, image_pair, show_results=False): 202 | """ 203 | Record chessboard corners found in an image pair. 204 | 205 | The image pair should be an iterable composed of two CvMats ordered 206 | (left, right). 207 | """ 208 | side = "left" 209 | self.object_points.append(self.corner_coordinates) 210 | for image in image_pair: 211 | corners = self._get_corners(image) 212 | if show_results: 213 | self._show_corners(image, corners) 214 | self.image_points[side].append(corners.reshape(-1, 2)) 215 | side = "right" 216 | self.image_count += 1 217 | 218 | def calibrate_cameras(self): 219 | """Calibrate cameras based on found chessboard corners.""" 220 | criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 221 | 100, 1e-5) 222 | flags = (cv2.CALIB_FIX_ASPECT_RATIO + cv2.CALIB_ZERO_TANGENT_DIST + 223 | cv2.CALIB_SAME_FOCAL_LENGTH) 224 | calib = StereoCalibration() 225 | (calib.cam_mats["left"], calib.dist_coefs["left"], 226 | calib.cam_mats["right"], calib.dist_coefs["right"], 227 | calib.rot_mat, calib.trans_vec, calib.e_mat, 228 | calib.f_mat) = cv2.stereoCalibrate(self.object_points, 229 | self.image_points["left"], 230 | self.image_points["right"], 231 | self.image_size, 232 | calib.cam_mats["left"], 233 | calib.dist_coefs["left"], 234 | calib.cam_mats["right"], 235 | calib.dist_coefs["right"], 236 | calib.rot_mat, 237 | calib.trans_vec, 238 | calib.e_mat, 239 | calib.f_mat, 240 | criteria=criteria, 241 | flags=flags)[1:] 242 | (calib.rect_trans["left"], calib.rect_trans["right"], 243 | calib.proj_mats["left"], calib.proj_mats["right"], 244 | calib.disp_to_depth_mat, calib.valid_boxes["left"], 245 | calib.valid_boxes["right"]) = cv2.stereoRectify(calib.cam_mats["left"], 246 | calib.dist_coefs["left"], 247 | calib.cam_mats["right"], 248 | calib.dist_coefs["right"], 249 | self.image_size, 250 | calib.rot_mat, 251 | calib.trans_vec, 252 | flags=0) 253 | for side in ("left", "right"): 254 | (calib.undistortion_map[side], 255 | calib.rectification_map[side]) = cv2.initUndistortRectifyMap( 256 | calib.cam_mats[side], 257 | calib.dist_coefs[side], 258 | calib.rect_trans[side], 259 | calib.proj_mats[side], 260 | self.image_size, 261 | cv2.CV_32FC1) 262 | # This is replaced because my results were always bad. Estimates are 263 | # taken from the OpenCV samples. 264 | width, height = self.image_size 265 | focal_length = 0.8 * width 266 | calib.disp_to_depth_mat = np.float32([[1, 0, 0, -0.5 * width], 267 | [0, -1, 0, 0.5 * height], 268 | [0, 0, 0, -focal_length], 269 | [0, 0, 1, 0]]) 270 | return calib 271 | 272 | def check_calibration(self, calibration): 273 | """ 274 | Check calibration quality by computing average reprojection error. 275 | 276 | First, undistort detected points and compute epilines for each side. 277 | Then compute the error between the computed epipolar lines and the 278 | position of the points detected on the other side for each point and 279 | return the average error. 280 | """ 281 | sides = "left", "right" 282 | which_image = {sides[0]: 1, sides[1]: 2} 283 | undistorted, lines = {}, {} 284 | for side in sides: 285 | undistorted[side] = cv2.undistortPoints( 286 | np.concatenate(self.image_points[side]).reshape(-1, 287 | 1, 2), 288 | calibration.cam_mats[side], 289 | calibration.dist_coefs[side], 290 | P=calibration.cam_mats[side]) 291 | lines[side] = cv2.computeCorrespondEpilines(undistorted[side], 292 | which_image[side], 293 | calibration.f_mat) 294 | total_error = 0 295 | this_side, other_side = sides 296 | for side in sides: 297 | for i in range(len(undistorted[side])): 298 | total_error += abs(undistorted[this_side][i][0][0] * 299 | lines[other_side][i][0][0] + 300 | undistorted[this_side][i][0][1] * 301 | lines[other_side][i][0][1] + 302 | lines[other_side][i][0][2]) 303 | other_side, this_side = sides 304 | total_points = self.image_count * len(self.object_points) 305 | return total_error / total_points 306 | -------------------------------------------------------------------------------- /stereovision/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | """ 19 | Various exceptions for working with stereovision. 20 | 21 | Classes: 22 | 23 | * ``ChessboardNotFoundError`` 24 | 25 | * ``BadBlockMatcherArgumentError`` 26 | 27 | * ``StereoBMError`` 28 | 29 | * ``InvalidBMPresetError`` 30 | * ``InvalidSearchRangeError`` 31 | * ``InvalidWindowSizeError`` 32 | 33 | * ``StereoSGBMError`` 34 | 35 | * ``InvalidNumDisparitiesError`` 36 | * ``InvalidSADWindowSizeError`` 37 | * ``InvalidFirstDisparityChangePenaltyError`` 38 | * ``InvalidSecondDisparityChangePenaltyError`` 39 | * ``InvalidUniquenessRatioError`` 40 | * ``InvalidSpeckleWindowSizeError`` 41 | * ``InvalidSpeckleRangeError`` 42 | 43 | .. image:: classes_exceptions.svg 44 | :width: 100% 45 | """ 46 | 47 | class ChessboardNotFoundError(Exception): 48 | """No chessboard could be found in searched image.""" 49 | 50 | 51 | class BadBlockMatcherArgumentError(Exception): 52 | """Bad argument supplied for a ``BlockMatcher``.""" 53 | 54 | class StereoBMError(BadBlockMatcherArgumentError): 55 | """Bad argument supplied for a ``StereoBM``.""" 56 | 57 | class StereoSGBMError(BadBlockMatcherArgumentError): 58 | """Bad argument supplied for a ``StereoSGBM``.""" 59 | 60 | class InvalidBMPresetError(StereoBMError): 61 | """Invalid BM preset.""" 62 | 63 | class InvalidSearchRangeError(StereoBMError): 64 | """Invalid search range.""" 65 | 66 | class InvalidWindowSizeError(StereoBMError): 67 | """Invalid search range.""" 68 | 69 | class InvalidNumDisparitiesError(StereoSGBMError): 70 | """Invalid number of disparities.""" 71 | 72 | class InvalidSADWindowSizeError(StereoSGBMError): 73 | """Invalid search window size.""" 74 | 75 | class InvalidFirstDisparityChangePenaltyError(StereoSGBMError): 76 | """Invalid first disparity change penalty.""" 77 | 78 | class InvalidSecondDisparityChangePenaltyError(StereoSGBMError): 79 | """Invalid second disparity change penalty.""" 80 | 81 | class InvalidUniquenessRatioError(StereoSGBMError): 82 | """Invalid uniqueness ratio.""" 83 | 84 | class InvalidSpeckleWindowSizeError(StereoSGBMError): 85 | """Invalid speckle window size.""" 86 | 87 | class InvalidSpeckleRangeError(StereoSGBMError): 88 | """Invalid speckle range.""" 89 | -------------------------------------------------------------------------------- /stereovision/point_cloud.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | ''' 19 | Point cloud class generated from stereo image pairs. 20 | 21 | Classes: 22 | 23 | * ``PointCloud`` - Point cloud with RGB colors 24 | 25 | .. image:: classes_point_cloud.svg 26 | ''' 27 | 28 | import numpy as np 29 | 30 | 31 | class PointCloud(object): 32 | 33 | """3D point cloud generated from a stereo image pair.""" 34 | 35 | #: Header for exporting point cloud to PLY 36 | ply_header = ( 37 | '''ply 38 | format ascii 1.0 39 | element vertex {vertex_count} 40 | property float x 41 | property float y 42 | property float z 43 | property uchar red 44 | property uchar green 45 | property uchar blue 46 | end_header 47 | ''') 48 | 49 | def __init__(self, coordinates, colors): 50 | """ 51 | Initialize point cloud with given coordinates and associated colors. 52 | 53 | ``coordinates`` and ``colors`` should be numpy arrays of the same 54 | length, in which ``coordinates`` is made of three-dimensional point 55 | positions (X, Y, Z) and ``colors`` is made of three-dimensional spectral 56 | data, e.g. (R, G, B). 57 | """ 58 | self.coordinates = coordinates.reshape(-1, 3) 59 | self.colors = colors.reshape(-1, 3) 60 | 61 | def write_ply(self, output_file): 62 | """Export ``PointCloud`` to PLY file for viewing in MeshLab.""" 63 | points = np.hstack([self.coordinates, self.colors]) 64 | with open(output_file, 'w') as outfile: 65 | outfile.write(self.ply_header.format( 66 | vertex_count=len(self.coordinates))) 67 | np.savetxt(outfile, points, '%f %f %f %d %d %d') 68 | 69 | def filter_infinity(self): 70 | """Filter infinite distances from ``PointCloud.``""" 71 | mask = self.coordinates[:, 2] > self.coordinates[:, 2].min() 72 | coords = self.coordinates[mask] 73 | colors = self.colors[mask] 74 | return PointCloud(coords, colors) 75 | -------------------------------------------------------------------------------- /stereovision/stereo_cameras.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | """ 19 | Classes for interacting with stereo cameras. 20 | 21 | Classes: 22 | 23 | * ``StereoPair`` - Base class for interacting with stereo cameras 24 | 25 | * ``ChessboardFinder`` - Class for finding chessboards with both cameras 26 | * ``CalibratedPair`` - Calibrated stereo camera pair that rectifies its 27 | images 28 | 29 | .. image:: classes_stereo_cameras.svg 30 | """ 31 | 32 | import cv2 33 | 34 | from stereovision.point_cloud import PointCloud 35 | 36 | 37 | class StereoPair(object): 38 | 39 | """ 40 | A stereo pair of cameras. 41 | 42 | This class allows both cameras in a stereo pair to be accessed 43 | simultaneously. It also allows the user to show single frames or videos 44 | captured online with the cameras. It should be instantiated with a context 45 | manager to ensure that the cameras are freed properly after use. 46 | """ 47 | 48 | #: Window names for showing captured frame from each camera 49 | windows = ["{} camera".format(side) for side in ("Left", "Right")] 50 | 51 | def __init__(self, devices): 52 | """ 53 | Initialize cameras. 54 | 55 | ``devices`` is an iterable containing the device numbers. 56 | """ 57 | 58 | if devices[0] != devices[1]: 59 | #: Video captures associated with the ``StereoPair`` 60 | self.captures = [cv2.VideoCapture(device) for device in devices] 61 | else: 62 | # Stereo images come from a single device, as single image 63 | self.captures = [cv2.VideoCapture(devices[0])] 64 | self.get_frames = self.get_frames_singleimage 65 | 66 | def __enter__(self): 67 | return self 68 | 69 | def __exit__(self, type, value, traceback): 70 | for capture in self.captures: 71 | capture.release() 72 | for window in self.windows: 73 | cv2.destroyWindow(window) 74 | 75 | def get_frames(self): 76 | """Get current frames from cameras.""" 77 | return [capture.read()[1] for capture in self.captures] 78 | 79 | def get_frames_singleimage(self): 80 | """ 81 | Get current left and right frames from a single image, 82 | by splitting the image in half. 83 | """ 84 | frame = self.captures[0].read()[1] 85 | height, width, colors = frame.shape 86 | left_frame = frame[:, :int(width / 2), :] 87 | right_frame = frame[:, int(width / 2):, :] 88 | return [left_frame, right_frame] 89 | 90 | def show_frames(self, wait=0): 91 | """ 92 | Show current frames from cameras. 93 | 94 | ``wait`` is the wait interval in milliseconds before the window closes. 95 | """ 96 | for window, frame in zip(self.windows, self.get_frames()): 97 | cv2.imshow(window, frame) 98 | cv2.waitKey(wait) 99 | 100 | def show_videos(self): 101 | """Show video from cameras.""" 102 | while True: 103 | self.show_frames(1) 104 | if cv2.waitKey(1) & 0xFF == ord('q'): 105 | break 106 | 107 | 108 | class ChessboardFinder(StereoPair): 109 | 110 | """A ``StereoPair`` that can find chessboards in its images.""" 111 | 112 | def get_chessboard(self, columns, rows, show=False): 113 | """ 114 | Take a picture with a chessboard visible in both captures. 115 | 116 | ``columns`` and ``rows`` should be the number of inside corners in the 117 | chessboard's columns and rows. ``show`` determines whether the frames 118 | are shown while the cameras search for a chessboard. 119 | """ 120 | found_chessboard = [False, False] 121 | 122 | # Placeholder for corners 123 | found_corners = [None, None] 124 | 125 | while not all(found_chessboard): 126 | frames = self.get_frames() 127 | if show: 128 | self.show_frames(1) 129 | for i, frame in enumerate(frames): 130 | (found_chessboard[i], 131 | found_corners[i]) = cv2.findChessboardCorners(frame, (columns, rows), 132 | flags=cv2.CALIB_CB_FAST_CHECK) 133 | return frames, found_corners 134 | 135 | 136 | class CalibratedPair(StereoPair): 137 | 138 | """ 139 | A ``StereoPair`` that works with rectified images and produces point clouds. 140 | """ 141 | 142 | def __init__(self, devices, calibration, block_matcher): 143 | """ 144 | Initialize cameras. 145 | 146 | ``devices`` is an iterable of the device numbers. If you want to use the 147 | ``CalibratedPair`` in offline mode, it should be None. 148 | ``calibration`` is a ``StereoCalibration`` object. 149 | ``block_matcher`` is a ``BlockMatcher`` object. 150 | """ 151 | if devices: 152 | super(CalibratedPair, self).__init__(devices) 153 | #: ``StereoCalibration`` object holding the camera pair's calibration 154 | self.calibration = calibration 155 | #: ``BlockMatcher`` object for computing disparity and point cloud 156 | self.block_matcher = block_matcher 157 | 158 | def get_frames(self): 159 | """Rectify and return current frames from cameras.""" 160 | frames = super(CalibratedPair, self).get_frames() 161 | return self.calibration.rectify(frames) 162 | 163 | def get_point_cloud(self, pair): 164 | """Get 3D point cloud from image pair.""" 165 | disparity = self.block_matcher.get_disparity(pair) 166 | points = self.block_matcher.get_3d(disparity, 167 | self.calibration.disp_to_depth_mat) 168 | colors = cv2.cvtColor(pair[0], cv2.COLOR_BGR2RGB) 169 | return PointCloud(points, colors) 170 | -------------------------------------------------------------------------------- /stereovision/ui_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014 Daniel Lee 2 | # 3 | # This file is part of StereoVision. 4 | # 5 | # StereoVision is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # StereoVision is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with StereoVision. If not, see . 17 | 18 | """ 19 | Utilities for easing user interaction with the ``stereovision`` package. 20 | 21 | Variables: 22 | 23 | * ``CHESSBOARD_ARGUMENTS`` - ``argparse.ArgumentParser`` for working with 24 | chessboards 25 | * ``STEREO_BM_FLAG`` - ``argparse.ArgumentParser`` for using StereoBM 26 | 27 | Functions: 28 | 29 | * ``find_files`` - Discover stereo images in directory 30 | * ``calibrate_folder`` - Calibrate chessboard images discoverd in a folder 31 | 32 | Classes: 33 | 34 | * ``BMTuner`` - Tune block matching algorithm to camera pair 35 | 36 | .. image:: classes_ui_utils.svg 37 | """ 38 | 39 | from argparse import ArgumentParser 40 | from functools import partial 41 | import os 42 | 43 | import cv2 44 | from progressbar import ProgressBar, Percentage, Bar 45 | from stereovision.calibration import StereoCalibrator 46 | from stereovision.exceptions import BadBlockMatcherArgumentError 47 | 48 | #: Command line arguments for collecting information about chessboards 49 | CHESSBOARD_ARGUMENTS = ArgumentParser(add_help=False) 50 | CHESSBOARD_ARGUMENTS.add_argument("--rows", type=int, 51 | help="Number of inside corners in the " 52 | "chessboard's rows.", default=9) 53 | CHESSBOARD_ARGUMENTS.add_argument("--columns", type=int, 54 | help="Number of inside corners in the " 55 | "chessboard's columns.", default=6) 56 | CHESSBOARD_ARGUMENTS.add_argument("--square-size", help="Size of chessboard " 57 | "squares in cm.", type=float, default=1.8) 58 | 59 | 60 | #: Command line arguments for using StereoBM rather than StereoSGBM 61 | STEREO_BM_FLAG = ArgumentParser(add_help=False) 62 | STEREO_BM_FLAG.add_argument("--use_stereobm", help="Use StereoBM rather than " 63 | "StereoSGBM block matcher.", action="store_true") 64 | 65 | 66 | def find_files(folder): 67 | """Discover stereo photos and return them as a pairwise sorted list.""" 68 | files = [i for i in os.listdir(folder) if i.startswith("left")] 69 | files.sort() 70 | for i in range(len(files)): 71 | insert_string = "right{}".format(files[i * 2][4:]) 72 | files.insert(i * 2 + 1, insert_string) 73 | files = [os.path.join(folder, filename) for filename in files] 74 | return files 75 | 76 | 77 | def calibrate_folder(args): 78 | """ 79 | Calibrate camera based on chessboard images, write results to output folder. 80 | 81 | All images are read from disk. Chessboard points are found and used to 82 | calibrate the stereo pair. Finally, the calibration is written to the folder 83 | specified in ``args``. 84 | 85 | ``args`` needs to contain the following fields: 86 | input_files: List of paths to input files 87 | rows: Number of rows in chessboard 88 | columns: Number of columns in chessboard 89 | square_size: Size of chessboard squares in cm 90 | output_folder: Folder to write calibration to 91 | """ 92 | height, width = cv2.imread(args.input_files[0]).shape[:2] 93 | calibrator = StereoCalibrator(args.rows, args.columns, args.square_size, 94 | (width, height)) 95 | progress = ProgressBar(maxval=len(args.input_files), 96 | widgets=[Bar("=", "[", "]"), 97 | " ", Percentage()]) 98 | print("Reading input files...") 99 | progress.start() 100 | while args.input_files: 101 | left, right = args.input_files[:2] 102 | img_left, im_right = cv2.imread(left), cv2.imread(right) 103 | calibrator.add_corners((img_left, im_right), 104 | show_results=args.show_chessboards) 105 | args.input_files = args.input_files[2:] 106 | progress.update(progress.maxval - len(args.input_files)) 107 | 108 | progress.finish() 109 | print("Calibrating cameras. This can take a while.") 110 | calibration = calibrator.calibrate_cameras() 111 | avg_error = calibrator.check_calibration(calibration) 112 | print("The average error between chessboard points and their epipolar " 113 | "lines is \n" 114 | "{} pixels. This should be as small as possible.".format(avg_error)) 115 | calibration.export(args.output_folder) 116 | 117 | 118 | class BMTuner(object): 119 | 120 | """ 121 | A class for tuning Stereo BM settings. 122 | 123 | Display a normalized disparity picture from two pictures captured with a 124 | ``CalibratedPair`` and allow the user to manually tune the settings for the 125 | ``BlockMatcher``. 126 | 127 | The settable parameters are intelligently read from the ``BlockMatcher``, 128 | relying on the ``BlockMatcher`` exposing them as ``parameter_maxima``. 129 | """ 130 | 131 | #: Window to show results in 132 | window_name = "BM Tuner" 133 | 134 | def _set_value(self, parameter, new_value): 135 | """Try setting new parameter on ``block_matcher`` and update map.""" 136 | try: 137 | self.block_matcher.__setattr__(parameter, new_value) 138 | except BadBlockMatcherArgumentError: 139 | return 140 | self.update_disparity_map() 141 | 142 | def _initialize_trackbars(self): 143 | """ 144 | Initialize trackbars by discovering ``block_matcher``'s parameters. 145 | """ 146 | for parameter in self.block_matcher.parameter_maxima.keys(): 147 | maximum = self.block_matcher.parameter_maxima[parameter] 148 | if not maximum: 149 | maximum = self.shortest_dimension 150 | cv2.createTrackbar(parameter, self.window_name, 151 | self.block_matcher.__getattribute__(parameter), 152 | maximum, 153 | partial(self._set_value, parameter)) 154 | 155 | def _save_bm_state(self): 156 | """Save current state of ``block_matcher``.""" 157 | for parameter in self.block_matcher.parameter_maxima.keys(): 158 | self.bm_settings[parameter].append( 159 | self.block_matcher.__getattribute__(parameter)) 160 | 161 | def __init__(self, block_matcher, calibration, image_pair): 162 | """ 163 | Initialize tuner window and tune given pair. 164 | 165 | ``block_matcher`` is a ``BlockMatcher``, ``calibration`` is a 166 | ``StereoCalibration`` and ``image_pair`` is a rectified image pair. 167 | """ 168 | #: Stereo calibration to find Stereo BM settings for 169 | self.calibration = calibration 170 | #: (left, right) image pair to find disparity between 171 | self.pair = image_pair 172 | #: Block matcher to be tuned 173 | self.block_matcher = block_matcher 174 | #: Shortest dimension of image 175 | self.shortest_dimension = min(self.pair[0].shape[:2]) 176 | #: Settings chosen for ``BlockMatcher`` 177 | self.bm_settings = {} 178 | for parameter in self.block_matcher.parameter_maxima.keys(): 179 | self.bm_settings[parameter] = [] 180 | cv2.namedWindow(self.window_name) 181 | self._initialize_trackbars() 182 | self.tune_pair(image_pair) 183 | 184 | def update_disparity_map(self): 185 | """ 186 | Update disparity map in GUI. 187 | 188 | The disparity image is normalized to the range 0-255 and then divided by 189 | 255, because OpenCV multiplies it by 255 when displaying. This is 190 | because the pixels are stored as floating points. 191 | """ 192 | disparity = self.block_matcher.get_disparity(self.pair) 193 | norm_coeff = 255 / disparity.max() 194 | cv2.imshow(self.window_name, disparity * norm_coeff / 255) 195 | cv2.waitKey() 196 | 197 | def tune_pair(self, pair): 198 | """Tune a pair of images.""" 199 | self._save_bm_state() 200 | self.pair = pair 201 | self.update_disparity_map() 202 | 203 | def report_settings(self, parameter): 204 | """ 205 | Report chosen settings for ``parameter`` in ``block_matcher``. 206 | 207 | ``bm_settings`` is updated to include the latest state before work is 208 | begun. This state is removed at the end so that the method has no side 209 | effects. All settings are reported except for the first one on record, 210 | which is ``block_matcher``'s default setting. 211 | """ 212 | self._save_bm_state() 213 | report = [] 214 | settings_list = self.bm_settings[parameter][1:] 215 | unique_values = list(set(settings_list)) 216 | value_frequency = {} 217 | for value in unique_values: 218 | value_frequency[settings_list.count(value)] = value 219 | frequencies = value_frequency.keys() 220 | frequencies.sort(reverse=True) 221 | header = "{} value | Selection frequency".format(parameter) 222 | left_column_width = len(header[:-21]) 223 | right_column_width = 21 224 | report.append(header) 225 | report.append("{}|{}".format("-" * left_column_width, 226 | "-" * right_column_width)) 227 | for frequency in frequencies: 228 | left_column = str(value_frequency[frequency]).center( 229 | left_column_width) 230 | right_column = str(frequency).center(right_column_width) 231 | report.append("{}|{}".format(left_column, right_column)) 232 | # Remove newest settings 233 | for param in self.block_matcher.parameter_maxima.keys(): 234 | self.bm_settings[param].pop(-1) 235 | return "\n".join(report) 236 | --------------------------------------------------------------------------------