├── .gitignore ├── README.md ├── Stitching Tutorial.ipynb ├── docs ├── Stitching Tutorial.md ├── Stitching Tutorial_files │ ├── Stitching Tutorial_12_0.png │ ├── Stitching Tutorial_19_1.png │ ├── Stitching Tutorial_19_3.png │ ├── Stitching Tutorial_35_0.png │ ├── Stitching Tutorial_35_1.png │ ├── Stitching Tutorial_39_0.png │ ├── Stitching Tutorial_39_1.png │ ├── Stitching Tutorial_39_2.png │ ├── Stitching Tutorial_43_0.png │ ├── Stitching Tutorial_48_0.png │ ├── Stitching Tutorial_50_0.png │ ├── Stitching Tutorial_52_0.png │ ├── Stitching Tutorial_54_0.png │ ├── Stitching Tutorial_58_0.png │ ├── Stitching Tutorial_58_1.png │ ├── Stitching Tutorial_58_2.png │ ├── Stitching Tutorial_61_0.png │ ├── Stitching Tutorial_66_0.png │ ├── Stitching Tutorial_68_0.png │ ├── Stitching Tutorial_70_0.png │ ├── Stitching Tutorial_70_1.png │ ├── Stitching Tutorial_73_0.png │ ├── Stitching Tutorial_77_0.png │ ├── Stitching Tutorial_80_0.png │ ├── Stitching Tutorial_82_0.png │ ├── Stitching Tutorial_84_0.png │ └── Stitching Tutorial_8_0.png └── static_files │ ├── inputs.png │ ├── match_graph.png │ ├── matches1.png │ ├── matches2.png │ ├── panorama.png │ ├── seams1.png │ └── seams2.png ├── imgs ├── barcode1.png ├── barcode2.png ├── budapest1.jpg ├── budapest2.jpg ├── budapest3.jpg ├── budapest4.jpg ├── budapest5.jpg ├── budapest6.jpg ├── exposure_error_1.jpg ├── exposure_error_2.jpg ├── mask1.png ├── mask2.png ├── weir_1.jpg ├── weir_2.jpg ├── weir_3.jpg └── weir_noise.jpg └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | /* 3 | /*/ 4 | 5 | # But not these files... 6 | !.gitignore 7 | !figures/ 8 | !docs/ 9 | !imgs/ 10 | !Stitching Tutorial.ipynb 11 | !README.md 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OpenCV Stitching Tutorial 2 | 3 | Preview the tutorial [here](https://github.com/lukasalexanderweber/stitching_tutorial/blob/master/docs/Stitching%20Tutorial.md) 4 | 5 | ## Installation 6 | 7 | Install the dependencies. 8 | 9 | ```bash 10 | pip install -r requirements.txt 11 | ``` 12 | 13 | ## Run the Notebook 14 | 15 | To run the tutorial an your machine and experiment e.g. with your own images put your images in the `imgs` directory and run the notebook using 16 | 17 | ```bash 18 | jupyter notebook 19 | ``` 20 | 21 | ## Contributing 22 | 23 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 24 | 25 | Please make sure to update the preview using 26 | 27 | ```bash 28 | jupyter nbconvert --to markdown "Stitching Tutorial.ipynb" 29 | ``` 30 | 31 | and put the resulting files in the `docs` directory 32 | -------------------------------------------------------------------------------- /docs/Stitching Tutorial.md: -------------------------------------------------------------------------------- 1 | # Stitching Tutorial 2 | 3 | The Workflow of the Stitching Pipeline can be seen in the following. Note that the image comes from the [OpenCV Documentation](https://docs.opencv.org/3.4/d1/d46/group__stitching.html). 4 | 5 | ![image stitching pipeline](https://github.com/opencv/opencv/blob/master/modules/stitching/doc/StitchingPipeline.jpg?raw=true) 6 | 7 | With the following block, we allow displaying resulting images within the notebook: 8 | 9 | 10 | ```python 11 | from matplotlib import pyplot as plt 12 | import cv2 as cv 13 | import numpy as np 14 | 15 | def plot_image(img, figsize_in_inches=(5,5)): 16 | fig, ax = plt.subplots(figsize=figsize_in_inches) 17 | ax.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)) 18 | plt.show() 19 | 20 | def plot_images(imgs, figsize_in_inches=(5,5)): 21 | fig, axs = plt.subplots(1, len(imgs), figsize=figsize_in_inches) 22 | for col, img in enumerate(imgs): 23 | axs[col].imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)) 24 | plt.show() 25 | ``` 26 | 27 | With the following block, we load the correct img paths to the used image sets: 28 | 29 | 30 | ```python 31 | from pathlib import Path 32 | def get_image_paths(img_set): 33 | return [str(path.relative_to('.')) for path in Path('imgs').rglob(f'{img_set}*')] 34 | 35 | weir_imgs = get_image_paths('weir') 36 | budapest_imgs = get_image_paths('buda') 37 | exposure_error_imgs = get_image_paths('exp') 38 | barcode_imgs = get_image_paths('barc') 39 | barcode_masks = get_image_paths('mask') 40 | ``` 41 | 42 | ## Resize Images 43 | 44 | The first step is to resize the images to medium (and later to low) resolution. The class which can be used is the `Images` class. If the images should not be stitched on full resolution, this can be achieved by setting the `final_megapix` parameter to a number above 0. 45 | 46 | `Images.of(images, medium_megapix=0.6, low_megapix=0.1, final_megapix=-1)` 47 | 48 | 49 | ```python 50 | from stitching.images import Images 51 | 52 | images = Images.of(weir_imgs) 53 | 54 | medium_imgs = list(images.resize(Images.Resolution.MEDIUM)) 55 | low_imgs = list(images.resize(Images.Resolution.LOW)) 56 | final_imgs = list(images.resize(Images.Resolution.FINAL)) 57 | ``` 58 | 59 | **NOTE:** Everytime `list()` is called in this notebook means that the function returns a generator (generators improve the overall stitching performance). To get all elements at once we use `list(generator_object)` 60 | 61 | 62 | ```python 63 | plot_images(low_imgs, (20,20)) 64 | ``` 65 | 66 | 67 | 68 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_8_0.png) 69 | 70 | 71 | 72 | 73 | ```python 74 | original_size = images.sizes[0] 75 | medium_size = images.get_image_size(medium_imgs[0]) 76 | low_size = images.get_image_size(low_imgs[0]) 77 | final_size = images.get_image_size(final_imgs[0]) 78 | 79 | print(f"Original Size: {original_size} -> {'{:,}'.format(np.prod(original_size))} px ~ 1 MP") 80 | print(f"Medium Size: {medium_size} -> {'{:,}'.format(np.prod(medium_size))} px ~ 0.6 MP") 81 | print(f"Low Size: {low_size} -> {'{:,}'.format(np.prod(low_size))} px ~ 0.1 MP") 82 | print(f"Final Size: {final_size} -> {'{:,}'.format(np.prod(final_size))} px ~ 1 MP") 83 | ``` 84 | 85 | Original Size: (1333, 750) -> 999,750 px ~ 1 MP 86 | Medium Size: (1033, 581) -> 600,173 px ~ 0.6 MP 87 | Low Size: (422, 237) -> 100,014 px ~ 0.1 MP 88 | Final Size: (1333, 750) -> 999,750 px ~ 1 MP 89 | 90 | 91 | ## Find Features 92 | 93 | On the medium images, we now want to find features that can describe conspicuous elements within the images which might be found in other images as well. The class which can be used is the `FeatureDetector` class. 94 | 95 | `FeatureDetector(detector='orb', nfeatures=500)` 96 | 97 | 98 | ```python 99 | from stitching.feature_detector import FeatureDetector 100 | 101 | finder = FeatureDetector() 102 | features = [finder.detect_features(img) for img in medium_imgs] 103 | keypoints_center_img = finder.draw_keypoints(medium_imgs[1], features[1]) 104 | ``` 105 | 106 | 107 | ```python 108 | plot_image(keypoints_center_img, (15,10)) 109 | ``` 110 | 111 | 112 | 113 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_12_0.png) 114 | 115 | 116 | 117 | ## Match Features 118 | 119 | Now we can match the features of the pairwise images. The class which can be used is the FeatureMatcher class. 120 | 121 | `FeatureMatcher(matcher_type='homography', range_width=-1)` 122 | 123 | 124 | ```python 125 | from stitching.feature_matcher import FeatureMatcher 126 | 127 | matcher = FeatureMatcher() 128 | matches = matcher.match_features(features) 129 | ``` 130 | 131 | We can look at the confidences, which are calculated by: 132 | 133 | `confidence = number of inliers / (8 + 0.3 * number of matches)` (Lines 435-7 of [this file](https://github.com/opencv/opencv/blob/68d15fc62edad980f1ffa15ee478438335f39cc3/modules/stitching/src/matchers.cpp)) 134 | 135 | The inliers are calculated using the random sample consensus (RANSAC) method, e.g. in [this file](https://github.com/opencv/opencv/blob/68d15fc62edad980f1ffa15ee478438335f39cc3/modules/stitching/src/matchers.cpp) in Line 425. We can plot the inliers which is shown later. 136 | 137 | 138 | ```python 139 | matcher.get_confidence_matrix(matches) 140 | ``` 141 | 142 | 143 | 144 | 145 | array([[0. , 2.45009074, 0.56 , 0.44247788], 146 | [2.45009074, 0. , 2.01729107, 0.42016807], 147 | [0.56 , 2.01729107, 0. , 0.38709677], 148 | [0.44247788, 0.42016807, 0.38709677, 0. ]]) 149 | 150 | 151 | 152 | It can be seen that: 153 | 154 | - image 1 has a high matching confidence with image 2 and low confidences with image 3 and 4 155 | - image 2 has a high matching confidence with image 1 and image 3 and low confidences with image 4 156 | - image 3 has a high matching confidence with image 2 and low confidences with image 1 and 4 157 | - image 4 has low matching confidences with image 1, 2 and 3 158 | 159 | With a `confidence_threshold`, which is introduced in detail in the next step, we can plot the relevant matches with the inliers: 160 | 161 | 162 | ```python 163 | all_relevant_matches = matcher.draw_matches_matrix(medium_imgs, features, matches, conf_thresh=1, 164 | inliers=True, matchColor=(0, 255, 0)) 165 | 166 | for idx1, idx2, img in all_relevant_matches: 167 | print(f"Matches Image {idx1+1} to Image {idx2+1}") 168 | plot_image(img, (20,10)) 169 | ``` 170 | 171 | Matches Image 1 to Image 2 172 | 173 | 174 | 175 | 176 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_19_1.png) 177 | 178 | 179 | 180 | Matches Image 2 to Image 3 181 | 182 | 183 | 184 | 185 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_19_3.png) 186 | 187 | 188 | 189 | ## Subset 190 | 191 | Above we saw that the noise image has no connection to the other images which are part of the panorama. We now want to create a subset with only the relevant images. The class which can be used is the `Subsetter` class. We can specify the `confidence_threshold` from when a match is regarded as good match. We saw that in our case `1` is sufficient. For the parameter `matches_graph_dot_file` a file name can be passed, in which a matches graph in dot notation is saved. 192 | 193 | `Subsetter(confidence_threshold=1, matches_graph_dot_file=None)` 194 | 195 | 196 | ```python 197 | from stitching.subsetter import Subsetter 198 | 199 | subsetter = Subsetter() 200 | dot_notation = subsetter.get_matches_graph(images.names, matches) 201 | print(dot_notation) 202 | ``` 203 | 204 | graph matches_graph{ 205 | "weir_1.jpg" -- "weir_2.jpg"[label="Nm=157, Ni=135, C=2.45009"]; 206 | "weir_2.jpg" -- "weir_3.jpg"[label="Nm=89, Ni=70, C=2.01729"]; 207 | "weir_noise.jpg"; 208 | } 209 | 210 | 211 | The matches graph visualizes what we've saw in the confidence matrix: image 1 conneced to image 2 conneced to image 3. Image 4 is not part of the panorama (note that the confidences can vary since this is a static image). 212 | 213 | ![match_graph](https://github.com/lukasalexanderweber/opencv_stitching_tutorial/blob/master/docs/static_files/match_graph.png?raw=true) 214 | 215 | [GraphvizOnline](https://dreampuf.github.io/GraphvizOnline) is used to plot the graph 216 | 217 | We now want to subset all variables we've created till here, incl. the attributes `img_names` and `img_sizes` of the `ImageHandler` 218 | 219 | 220 | ```python 221 | indices = subsetter.get_indices_to_keep(features, matches) 222 | 223 | medium_imgs = subsetter.subset_list(medium_imgs, indices) 224 | low_imgs = subsetter.subset_list(low_imgs, indices) 225 | final_imgs = subsetter.subset_list(final_imgs, indices) 226 | features = subsetter.subset_list(features, indices) 227 | matches = subsetter.subset_matches(matches, indices) 228 | 229 | images.subset(indices) 230 | 231 | print(images.names) 232 | print(matcher.get_confidence_matrix(matches)) 233 | ``` 234 | 235 | ['imgs\\weir_1.jpg', 'imgs\\weir_2.jpg', 'imgs\\weir_3.jpg'] 236 | [[0. 2.45009074 0.56 ] 237 | [2.45009074 0. 2.01729107] 238 | [0.56 2.01729107 0. ]] 239 | 240 | 241 | ## Camera Estimation, Adjustion and Correction 242 | 243 | With the features and matches we now want to calibrate cameras which can be used to warp the images so they can be composed correctly. The classes which can be used are `CameraEstimator`, `CameraAdjuster` and `WaveCorrector`: 244 | 245 | ``` 246 | CameraEstimator(estimator='homography') 247 | CameraAdjuster(adjuster='ray', refinement_mask='xxxxx') 248 | WaveCorrector(wave_correct_kind='horiz') 249 | ``` 250 | 251 | 252 | ```python 253 | from stitching.camera_estimator import CameraEstimator 254 | from stitching.camera_adjuster import CameraAdjuster 255 | from stitching.camera_wave_corrector import WaveCorrector 256 | 257 | camera_estimator = CameraEstimator() 258 | camera_adjuster = CameraAdjuster() 259 | wave_corrector = WaveCorrector() 260 | 261 | cameras = camera_estimator.estimate(features, matches) 262 | cameras = camera_adjuster.adjust(features, matches, cameras) 263 | cameras = wave_corrector.correct(cameras) 264 | ``` 265 | 266 | ## Warp Images 267 | 268 | With the obtained cameras we now want to warp the images itself into the final plane. The class which can be used is the `Warper` class: 269 | 270 | `Warper(warper_type='spherical', scale=1)` 271 | 272 | 273 | ```python 274 | from stitching.warper import Warper 275 | 276 | warper = Warper() 277 | ``` 278 | 279 | At first, we set the the medium focal length of the cameras as scale: 280 | 281 | 282 | ```python 283 | warper.set_scale(cameras) 284 | ``` 285 | 286 | Warp low resolution images 287 | 288 | 289 | ```python 290 | low_sizes = images.get_scaled_img_sizes(Images.Resolution.LOW) 291 | camera_aspect = images.get_ratio(Images.Resolution.MEDIUM, Images.Resolution.LOW) # since cameras were obtained on medium imgs 292 | 293 | warped_low_imgs = list(warper.warp_images(low_imgs, cameras, camera_aspect)) 294 | warped_low_masks = list(warper.create_and_warp_masks(low_sizes, cameras, camera_aspect)) 295 | low_corners, low_sizes = warper.warp_rois(low_sizes, cameras, camera_aspect) 296 | ``` 297 | 298 | Warp final resolution images 299 | 300 | 301 | ```python 302 | final_sizes = images.get_scaled_img_sizes(Images.Resolution.FINAL) 303 | camera_aspect = images.get_ratio(Images.Resolution.MEDIUM, Images.Resolution.FINAL) 304 | 305 | warped_final_imgs = list(warper.warp_images(final_imgs, cameras, camera_aspect)) 306 | warped_final_masks = list(warper.create_and_warp_masks(final_sizes, cameras, camera_aspect)) 307 | final_corners, final_sizes = warper.warp_rois(final_sizes, cameras, camera_aspect) 308 | ``` 309 | 310 | We can plot the results. Not much scaling and rotating is needed to align the images. Thus, the images are only slightly adjusted in this example 311 | 312 | 313 | ```python 314 | plot_images(warped_low_imgs, (10,10)) 315 | plot_images(warped_low_masks, (10,10)) 316 | ``` 317 | 318 | 319 | 320 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_35_0.png) 321 | 322 | 323 | 324 | 325 | 326 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_35_1.png) 327 | 328 | 329 | 330 | With the warped corners and sizes we know where the images will be placed on the final plane: 331 | 332 | 333 | ```python 334 | print(final_corners) 335 | print(final_sizes) 336 | ``` 337 | 338 | [(-1362, 4152), (-677, 4114), (-21, 4094)] 339 | [(1496, 847), (1310, 744), (1312, 746)] 340 | 341 | 342 | ## Excursion: Timelapser 343 | 344 | The Timelapser functionality is a nice way to grasp how the images are warped into a final plane. The class which can be used is the `Timelapser` class: 345 | 346 | `Timelapser(timelapse='no')` 347 | 348 | 349 | ```python 350 | from stitching.timelapser import Timelapser 351 | 352 | timelapser = Timelapser('as_is') 353 | timelapser.initialize(final_corners, final_sizes) 354 | 355 | for img, corner in zip(warped_final_imgs, final_corners): 356 | timelapser.process_frame(img, corner) 357 | frame = timelapser.get_frame() 358 | plot_image(frame, (10,10)) 359 | ``` 360 | 361 | 362 | 363 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_39_0.png) 364 | 365 | 366 | 367 | 368 | 369 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_39_1.png) 370 | 371 | 372 | 373 | 374 | 375 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_39_2.png) 376 | 377 | 378 | 379 | ## Crop 380 | 381 | We can see that none of the images have the full height of the final plane. To get a panorama without black borders we can now estimate the largest joint interior rectangle and crop the single images accordingly. The class which can be used is the `Cropper` class: 382 | 383 | `Cropper(crop=True)` 384 | 385 | 386 | ```python 387 | from stitching.cropper import Cropper 388 | 389 | cropper = Cropper() 390 | ``` 391 | 392 | We can estimate a panorama mask of the potential final panorama (using a Blender which will be introduced later) 393 | 394 | 395 | ```python 396 | mask = cropper.estimate_panorama_mask(warped_low_imgs, warped_low_masks, low_corners, low_sizes) 397 | plot_image(mask, (5,5)) 398 | ``` 399 | 400 | 401 | 402 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_43_0.png) 403 | 404 | 405 | 406 | The estimation of the largest interior rectangle is not yet implemented in OpenCV, but a [Numba](https://numba.pydata.org/) Implementation by my own. You check out the details [here](https://github.com/lukasalexanderweber/lir). Compiling the Code takes a bit (only once, the compiled code is then [cached](https://numba.pydata.org/numba-doc/latest/developer/caching.html) for future function calls) 407 | 408 | 409 | ```python 410 | lir = cropper.estimate_largest_interior_rectangle(mask) 411 | ``` 412 | 413 | After compilation the estimation is really fast: 414 | 415 | 416 | ```python 417 | lir = cropper.estimate_largest_interior_rectangle(mask) 418 | print(lir) 419 | ``` 420 | 421 | Rectangle(x=4, y=20, width=834, height=213) 422 | 423 | 424 | 425 | ```python 426 | plot = lir.draw_on(mask, size=2) 427 | plot_image(plot, (5,5)) 428 | ``` 429 | 430 | 431 | 432 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_48_0.png) 433 | 434 | 435 | 436 | By zero centering the the warped corners, the rectangle of the images within the final plane can be determined. Here the center image is shown: 437 | 438 | 439 | ```python 440 | low_corners = cropper.get_zero_center_corners(low_corners) 441 | rectangles = cropper.get_rectangles(low_corners, low_sizes) 442 | 443 | plot = rectangles[1].draw_on(plot, (0, 255, 0), 2) # The rectangle of the center img 444 | plot_image(plot, (5,5)) 445 | ``` 446 | 447 | 448 | 449 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_50_0.png) 450 | 451 | 452 | 453 | Using the overlap new corners and sizes can be determined: 454 | 455 | 456 | ```python 457 | overlap = cropper.get_overlap(rectangles[1], lir) 458 | plot = overlap.draw_on(plot, (255, 0, 0), 2) 459 | plot_image(plot, (5,5)) 460 | ``` 461 | 462 | 463 | 464 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_52_0.png) 465 | 466 | 467 | 468 | With the blue Rectangle in the coordinate system of the original image (green) we are able to crop it 469 | 470 | 471 | ```python 472 | intersection = cropper.get_intersection(rectangles[1], overlap) 473 | plot = intersection.draw_on(warped_low_masks[1], (255, 0, 0), 2) 474 | plot_image(plot, (2.5,2.5)) 475 | ``` 476 | 477 | 478 | 479 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_54_0.png) 480 | 481 | 482 | 483 | Using all this information we can crop the images and masks and obtain new corners and sizes 484 | 485 | 486 | ```python 487 | cropper.prepare(warped_low_imgs, warped_low_masks, low_corners, low_sizes) 488 | 489 | cropped_low_masks = list(cropper.crop_images(warped_low_masks)) 490 | cropped_low_imgs = list(cropper.crop_images(warped_low_imgs)) 491 | low_corners, low_sizes = cropper.crop_rois(low_corners, low_sizes) 492 | 493 | lir_aspect = images.get_ratio(Images.Resolution.LOW, Images.Resolution.FINAL) # since lir was obtained on low imgs 494 | cropped_final_masks = list(cropper.crop_images(warped_final_masks, lir_aspect)) 495 | cropped_final_imgs = list(cropper.crop_images(warped_final_imgs, lir_aspect)) 496 | final_corners, final_sizes = cropper.crop_rois(final_corners, final_sizes, lir_aspect) 497 | ``` 498 | 499 | Redo the timelapse with cropped Images: 500 | 501 | 502 | ```python 503 | timelapser = Timelapser('as_is') 504 | timelapser.initialize(final_corners, final_sizes) 505 | 506 | for img, corner in zip(cropped_final_imgs, final_corners): 507 | timelapser.process_frame(img, corner) 508 | frame = timelapser.get_frame() 509 | plot_image(frame, (10,10)) 510 | ``` 511 | 512 | 513 | 514 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_58_0.png) 515 | 516 | 517 | 518 | 519 | 520 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_58_1.png) 521 | 522 | 523 | 524 | 525 | 526 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_58_2.png) 527 | 528 | 529 | 530 | Now we need stategies how to compose the already overlaying images into one panorama image. Strategies are: 531 | 532 | - Seam Masks 533 | - Exposure Error Compensation 534 | - Blending 535 | 536 | ## Seam Masks 537 | 538 | Seam masks find a transition line between images with the least amount of interference. The class which can be used is the `SeamFinder` class: 539 | 540 | `SeamFinder(finder='dp_color')` 541 | 542 | The Seams are obtained on the warped low resolution images and then resized to the warped final resolution images. The Seam Masks can be used in the Blending step to specify how the images should be composed. 543 | 544 | 545 | ```python 546 | from stitching.seam_finder import SeamFinder 547 | 548 | seam_finder = SeamFinder() 549 | 550 | seam_masks = seam_finder.find(cropped_low_imgs, low_corners, cropped_low_masks) 551 | seam_masks = [seam_finder.resize(seam_mask, mask) for seam_mask, mask in zip(seam_masks, cropped_final_masks)] 552 | 553 | seam_masks_plots = [SeamFinder.draw_seam_mask(img, seam_mask) for img, seam_mask in zip(cropped_final_imgs, seam_masks)] 554 | plot_images(seam_masks_plots, (15,10)) 555 | ``` 556 | 557 | 558 | 559 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_61_0.png) 560 | 561 | 562 | 563 | ## Exposure Error Compensation 564 | 565 | Frequently exposure errors respectively exposure differences between images occur which lead to artefacts in the final panorama. The class which can be used is the `ExposureErrorCompensator` class: 566 | 567 | `ExposureErrorCompensator(compensator='gain_blocks', nr_feeds=1, block_size=32)` 568 | 569 | The Exposure Error are estimated on the warped low resolution images and then applied on the warped final resolution images. 570 | 571 | **Note:** In this example the compensation has nearly no effect and the result is not shown. To understand the stitching pipeline they are compensated anyway. A fitting example for images where Exposure Error Compensation is important can be found at the end of the notebook. 572 | 573 | 574 | ```python 575 | from stitching.exposure_error_compensator import ExposureErrorCompensator 576 | 577 | compensator = ExposureErrorCompensator() 578 | 579 | compensator.feed(low_corners, cropped_low_imgs, cropped_low_masks) 580 | 581 | compensated_imgs = [compensator.apply(idx, corner, img, mask) 582 | for idx, (img, mask, corner) 583 | in enumerate(zip(cropped_final_imgs, cropped_final_masks, final_corners))] 584 | ``` 585 | 586 | ## Blending 587 | 588 | With all the previous processing the images can finally be blended to a whole panorama. The class which can be used is the `Blender` class: 589 | 590 | `Blender(blender_type='multiband', blend_strength=5)` 591 | 592 | The blend strength thereby specifies on which overlap the images should be overlayed along the transitions of the masks. This is also visualized at the end of the notebook. 593 | 594 | 595 | ```python 596 | from stitching.blender import Blender 597 | 598 | blender = Blender() 599 | blender.prepare(final_corners, final_sizes) 600 | for img, mask, corner in zip(compensated_imgs, seam_masks, final_corners): 601 | blender.feed(img, mask, corner) 602 | panorama, _ = blender.blend() 603 | ``` 604 | 605 | 606 | ```python 607 | plot_image(panorama, (20,20)) 608 | ``` 609 | 610 | 611 | 612 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_66_0.png) 613 | 614 | 615 | 616 | There is the functionality to plot the seams as lines or polygons onto the final panorama to see which part of the panorama is from which image. The basis is to blend single colored dummy images with the obtained seam masks and panorama dimensions: 617 | 618 | 619 | ```python 620 | blended_seam_masks = seam_finder.blend_seam_masks(seam_masks, final_corners, final_sizes) 621 | plot_image(blended_seam_masks, (5,5)) 622 | ``` 623 | 624 | 625 | 626 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_68_0.png) 627 | 628 | 629 | 630 | This blend can be converted into lines or weighted on top of the resulting panorama: 631 | 632 | 633 | ```python 634 | plot_image(seam_finder.draw_seam_lines(panorama, blended_seam_masks, linesize=3), (15,10)) 635 | plot_image(seam_finder.draw_seam_polygons(panorama, blended_seam_masks), (15,10)) 636 | ``` 637 | 638 | 639 | 640 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_70_0.png) 641 | 642 | 643 | 644 | 645 | 646 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_70_1.png) 647 | 648 | 649 | 650 | # Stitcher 651 | 652 | All the functionality above is automated within the `Stitcher` class: 653 | 654 | `Stitcher(**kwargs)` 655 | 656 | 657 | ```python 658 | from stitching import Stitcher 659 | 660 | stitcher = Stitcher() 661 | panorama = stitcher.stitch(weir_imgs) # the user is warned that only a subset of input images is stitched 662 | ``` 663 | 664 | C:\Users\laweb\Envs\stitching\lib\site-packages\stitching\subsetter.py:32: StitchingWarning: Not all images are included in the final panorama. 665 | If this is not intended, use the 'matches_graph_dot_file' 666 | parameter to analyze your matches. You might want to 667 | lower the 'confidence_threshold' or try another 'detector' 668 | to include all your images. 669 | warnings.warn( 670 | 671 | 672 | 673 | ```python 674 | plot_image(panorama, (20,20)) 675 | ``` 676 | 677 | 678 | 679 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_73_0.png) 680 | 681 | 682 | 683 | ## Affine Stitcher 684 | 685 | For images that were obtained on e.g. a flatbed scanner affine transformations are sufficient. The `AffineStitcher` convenience class sets the needed parameters as default: 686 | 687 | `AffineStitcher(**kwargs)` 688 | 689 | 690 | ```python 691 | from stitching import AffineStitcher 692 | 693 | print(AffineStitcher.AFFINE_DEFAULTS) 694 | # Comparison: 695 | # print(Stitcher.DEFAULT_SETTINGS) 696 | # print(AffineStitcher.DEFAULT_SETTINGS) 697 | ``` 698 | 699 | {'estimator': 'affine', 'wave_correct_kind': 'no', 'matcher_type': 'affine', 'adjuster': 'affine', 'warper_type': 'affine', 'compensator': 'no'} 700 | 701 | 702 | We can now process the Budapest example (with two additional parameters) 703 | 704 | 705 | ```python 706 | settings = {# The whole plan should be considered 707 | "crop": False, 708 | # The matches confidences aren't that good 709 | "confidence_threshold": 0.5} 710 | 711 | stitcher = AffineStitcher(**settings) 712 | panorama = stitcher.stitch(budapest_imgs) 713 | 714 | plot_image(panorama, (20,20)) 715 | ``` 716 | 717 | 718 | 719 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_77_0.png) 720 | 721 | 722 | 723 | ## Exposure Error and Blend Strenght Example 724 | 725 | 726 | ```python 727 | imgs = exposure_error_imgs 728 | 729 | stitcher = Stitcher(compensator="no", blender_type="no") 730 | panorama1 = stitcher.stitch(imgs) 731 | 732 | stitcher = Stitcher(compensator="no") 733 | panorama2 = stitcher.stitch(imgs) 734 | 735 | stitcher = Stitcher(compensator="no", blend_strength=20) 736 | panorama3 = stitcher.stitch(imgs) 737 | 738 | stitcher = Stitcher(blender_type="no") 739 | panorama4 = stitcher.stitch(imgs) 740 | 741 | stitcher = Stitcher(blend_strength=20) 742 | panorama5 = stitcher.stitch(imgs) 743 | ``` 744 | 745 | 746 | ```python 747 | fig, axs = plt.subplots(3, 2, figsize=(20,20)) 748 | axs[0, 0].imshow(cv.cvtColor(panorama1, cv.COLOR_BGR2RGB)) 749 | axs[0, 0].set_title('Along Seam Masks with Exposure Errors') 750 | axs[0, 1].axis('off') 751 | axs[1, 0].imshow(cv.cvtColor(panorama2, cv.COLOR_BGR2RGB)) 752 | axs[1, 0].set_title('Blended with the default blend strenght of 5') 753 | axs[1, 1].imshow(cv.cvtColor(panorama3, cv.COLOR_BGR2RGB)) 754 | axs[1, 1].set_title('Blended with a bigger blend strenght of 20') 755 | axs[2, 0].imshow(cv.cvtColor(panorama4, cv.COLOR_BGR2RGB)) 756 | axs[2, 0].set_title('Along Seam Masks with Exposure Error Compensation') 757 | axs[2, 1].imshow(cv.cvtColor(panorama5, cv.COLOR_BGR2RGB)) 758 | axs[2, 1].set_title('Blended with Exposure Compensation and bigger blend strenght of 20') 759 | 760 | # Hide x labels and tick labels for top plots and y ticks for right plots. 761 | for ax in axs.flat: 762 | ax.label_outer() 763 | ``` 764 | 765 | 766 | 767 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_80_0.png) 768 | 769 | 770 | 771 | ## Feature Masking 772 | 773 | Sometimes specific parts of your images disrupt the stitching workflow or you only want to match at specific parts of your images. See e.g. how the QR-Code manipulates the stitching: 774 | 775 | 776 | ```python 777 | stitcher = Stitcher(crop=False) 778 | panorama = stitcher.stitch(barcode_imgs) 779 | 780 | plot_image(panorama, (20,20)) 781 | ``` 782 | 783 | 784 | 785 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_82_0.png) 786 | 787 | 788 | 789 | With the feature masks you can exclude the QR-Code areas of your images from feature detection and matching: 790 | 791 | 792 | ```python 793 | stitcher = Stitcher() 794 | panorama = stitcher.stitch(barcode_imgs, barcode_masks) 795 | 796 | plot_image(panorama, (20,20)) 797 | ``` 798 | 799 | 800 | 801 | ![png](Stitching%20Tutorial_files/Stitching%20Tutorial_84_0.png) 802 | 803 | 804 | 805 | See the [Pull Request](https://github.com/OpenStitching/stitching/pull/130) for more detailed intermediate images 806 | 807 | 808 | ```python 809 | 810 | ``` 811 | -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_12_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_19_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_19_1.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_19_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_19_3.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_35_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_35_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_35_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_35_1.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_39_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_39_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_39_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_39_1.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_39_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_39_2.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_43_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_43_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_48_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_48_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_50_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_50_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_52_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_52_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_54_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_54_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_58_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_58_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_58_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_58_1.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_58_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_58_2.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_61_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_61_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_66_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_66_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_68_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_68_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_70_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_70_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_70_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_70_1.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_73_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_73_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_77_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_77_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_80_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_80_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_82_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_82_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_84_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_84_0.png -------------------------------------------------------------------------------- /docs/Stitching Tutorial_files/Stitching Tutorial_8_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/Stitching Tutorial_files/Stitching Tutorial_8_0.png -------------------------------------------------------------------------------- /docs/static_files/inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/inputs.png -------------------------------------------------------------------------------- /docs/static_files/match_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/match_graph.png -------------------------------------------------------------------------------- /docs/static_files/matches1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/matches1.png -------------------------------------------------------------------------------- /docs/static_files/matches2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/matches2.png -------------------------------------------------------------------------------- /docs/static_files/panorama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/panorama.png -------------------------------------------------------------------------------- /docs/static_files/seams1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/seams1.png -------------------------------------------------------------------------------- /docs/static_files/seams2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/docs/static_files/seams2.png -------------------------------------------------------------------------------- /imgs/barcode1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/barcode1.png -------------------------------------------------------------------------------- /imgs/barcode2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/barcode2.png -------------------------------------------------------------------------------- /imgs/budapest1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/budapest1.jpg -------------------------------------------------------------------------------- /imgs/budapest2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/budapest2.jpg -------------------------------------------------------------------------------- /imgs/budapest3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/budapest3.jpg -------------------------------------------------------------------------------- /imgs/budapest4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/budapest4.jpg -------------------------------------------------------------------------------- /imgs/budapest5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/budapest5.jpg -------------------------------------------------------------------------------- /imgs/budapest6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/budapest6.jpg -------------------------------------------------------------------------------- /imgs/exposure_error_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/exposure_error_1.jpg -------------------------------------------------------------------------------- /imgs/exposure_error_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/exposure_error_2.jpg -------------------------------------------------------------------------------- /imgs/mask1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/mask1.png -------------------------------------------------------------------------------- /imgs/mask2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/mask2.png -------------------------------------------------------------------------------- /imgs/weir_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/weir_1.jpg -------------------------------------------------------------------------------- /imgs/weir_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/weir_2.jpg -------------------------------------------------------------------------------- /imgs/weir_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/weir_3.jpg -------------------------------------------------------------------------------- /imgs/weir_noise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenStitching/stitching_tutorial/604b60a0e3f8835097b7b2fca8b7bcad85cf2769/imgs/weir_noise.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | stitching 2 | notebook 3 | matplotlib 4 | --------------------------------------------------------------------------------