├── LICENSE.txt ├── clock_deblur.py ├── color_masking.py ├── data ├── clock_motion.png ├── david.png ├── stefan.jpg ├── webreg.txt ├── webreg_0.jpg ├── webreg_1.jpg └── window.jpg ├── digits_recognition ├── machine_learning.py ├── modern_digits_learned │ ├── 0-0.png │ ├── 0-1.png │ ├── 0-2.png │ ├── 1-0.png │ ├── 1-1.png │ ├── 1-2.png │ ├── 2-0.png │ ├── 2-1.png │ ├── 2-2.png │ ├── 3-0.png │ ├── 3-1.png │ ├── 3-2.png │ ├── 4-0.png │ ├── 4-1.png │ ├── 4-2.png │ ├── 5-0.png │ ├── 5-1.png │ ├── 5-2.png │ ├── 6-0.png │ ├── 6-1.png │ ├── 6-2.png │ ├── 7-0.png │ ├── 7-1.png │ ├── 7-2.png │ ├── 8-0.png │ ├── 8-1.png │ ├── 8-2.png │ ├── 9-0.png │ ├── 9-1.png │ └── 9-2.png ├── modern_digits_to_detect │ ├── small00000000.png │ ├── small00000001.png │ ├── small00000010.png │ ├── small00000011.png │ ├── small00000020.png │ ├── small00000021.png │ ├── small00000030.png │ ├── small00000031.png │ ├── small00000040.png │ ├── small00000041.png │ ├── small00000050.png │ ├── small00000051.png │ ├── small00000060.png │ ├── small00000061.png │ ├── small00000070.png │ ├── small00000071.png │ ├── small00000080.png │ ├── small00000081.png │ ├── small00000090.png │ ├── small00000091.png │ ├── small00000100.png │ ├── small00000101.png │ ├── small00000110.png │ ├── small00000111.png │ ├── small00000120.png │ ├── small00000121.png │ ├── small00000130.png │ ├── small00000131.png │ ├── small00000140.png │ ├── small00000141.png │ ├── small00000150.png │ ├── small00000151.png │ ├── small00000160.png │ ├── small00000161.png │ ├── small00000170.png │ ├── small00000171.png │ ├── small00000180.png │ ├── small00000181.png │ ├── small00000190.png │ ├── small00000191.png │ ├── small00000200.png │ ├── small00000201.png │ ├── small00000210.png │ ├── small00000211.png │ ├── small00000220.png │ ├── small00000221.png │ ├── small00000230.png │ ├── small00000231.png │ ├── small00000240.png │ ├── small00000241.png │ ├── small00000250.png │ ├── small00000251.png │ ├── small00000260.png │ ├── small00000261.png │ ├── small00000270.png │ ├── small00000271.png │ ├── small00000280.png │ ├── small00000281.png │ ├── small00000290.png │ ├── small00000291.png │ ├── small00000300.png │ ├── small00000301.png │ ├── small00000310.png │ ├── small00000311.png │ ├── small00000320.png │ ├── small00000321.png │ ├── small00000330.png │ ├── small00000331.png │ ├── small00000340.png │ ├── small00000341.png │ ├── small00000350.png │ ├── small00000351.png │ ├── small00000360.png │ ├── small00000361.png │ ├── small00000370.png │ ├── small00000371.png │ ├── small00000380.png │ ├── small00000381.png │ ├── small00000390.png │ ├── small00000391.png │ ├── small00000400.png │ ├── small00000401.png │ └── small00000410.png ├── run_modern_digits.py └── segmentation.py ├── dither.py ├── fisheye.py ├── mm_color_cluster.py ├── mm_color_select.py ├── mutual_information.py ├── pano ├── data │ ├── DFM_4209.jpg │ ├── DFM_4210.jpg │ ├── DFM_4211.jpg │ ├── JDW_0302-Edit.jpg │ ├── JDW_0303-Edit.jpg │ ├── JDW_0304-Edit.jpg │ ├── JDW_9518.jpg │ ├── JDW_9519.jpg │ ├── JDW_9520.jpg │ └── LICENSE.txt ├── pano-advanced.ipynb └── pano.ipynb ├── radial.py └── register.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014, the scikit-image team 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | 3. Neither the name of skimage nor the names of its contributors may be 15 | used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 27 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /clock_deblur.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | from skimage import io, color 5 | from skimage.viewer import ImageViewer 6 | from skimage.viewer.widgets import Slider 7 | from skimage.viewer.plugins.base import Plugin 8 | 9 | 10 | image = io.imread('data/clock_motion.png') 11 | M, N = image.shape 12 | 13 | ## Should pad, but doesn't make much difference in this case 14 | MM, NN = 2 * M + 1, 2 * N + 1 15 | 16 | def hann(image): 17 | wy = np.hanning(image.shape[0])[:, None] 18 | wx = np.hanning(image.shape[1]) 19 | 20 | 21 | ## Apply Hann window to prevent ringing 22 | wy = np.hanning(M)[:, None] 23 | wx = np.hanning(N) 24 | 25 | f = np.zeros((MM, NN)) 26 | f[:M, :N] = wy * wx * image 27 | 28 | F = np.fft.fft2(f) 29 | 30 | v, u = np.ogrid[:MM, :NN] 31 | v -= (MM - 1) // 2 32 | u -= (NN - 1) // 2 33 | 34 | 35 | def apply_inverse_filter(image, T, a, b, K=5, clip=500): 36 | uavb = u * a + v * b 37 | H = T * np.sinc(uavb) * np.exp(-1j * np.pi * uavb) 38 | H = np.fft.fftshift(H) 39 | 40 | HH = 1./H 41 | HH[np.abs(HH) > K] = K 42 | 43 | gg = np.abs(np.fft.ifft2(F * HH)) 44 | gg = gg[:M, :N] 45 | gg = np.clip(gg, 0, clip) 46 | gg -= gg.min() 47 | gg /= gg.max() 48 | 49 | return gg 50 | 51 | viewer = ImageViewer(image) 52 | 53 | plugin = Plugin(image_filter=apply_inverse_filter) 54 | plugin += Slider('T', 0, 1, value=0.5, value_type='float', update_on='release') 55 | plugin += Slider('a', -0.1, 0.1, value=0, value_type='float', update_on='release') 56 | plugin += Slider('b', -0.1, 0.1, value=0, value_type='float', update_on='release') 57 | plugin += Slider('K', 0, 100, value=15, value_type='float', update_on='release') 58 | plugin += Slider('clip', 0, 1000, value=750, value_type='float', update_on='release') 59 | viewer += plugin 60 | viewer.show() 61 | -------------------------------------------------------------------------------- /color_masking.py: -------------------------------------------------------------------------------- 1 | from skimage import data, io, color, img_as_float 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | alpha = 0.6 6 | 7 | img = img_as_float(data.camera()) 8 | rows, cols = img.shape 9 | 10 | # Construct a colour image to superimpose 11 | color_mask = np.zeros((rows, cols, 3)) 12 | color_mask[30:140, 30:140] = [1, 0, 0] # Red block 13 | color_mask[170:270, 40:120] = [0, 1, 0] # Green block 14 | color_mask[200:350, 200:350] = [0, 0, 1] # Blue block 15 | 16 | # Construct RGB version of grey-level image 17 | img_color = np.dstack((img, img, img)) 18 | 19 | # Convert the input image and color mask to Hue Saturation Value (HSV) 20 | # colorspace 21 | img_hsv = color.rgb2hsv(img_color) 22 | color_mask_hsv = color.rgb2hsv(color_mask) 23 | 24 | # Replace the hue and saturation of the original image 25 | # with that of the color mask 26 | img_hsv[..., 0] = color_mask_hsv[..., 0] 27 | img_hsv[..., 1] = color_mask_hsv[..., 1] * alpha 28 | 29 | img_masked = color.hsv2rgb(img_hsv) 30 | 31 | # Display the output 32 | f, (ax0, ax1, ax2) = plt.subplots(1, 3, 33 | subplot_kw={'xticks': [], 'yticks': []}, 34 | figsize=(12, 8)) 35 | ax0.imshow(img, cmap=plt.cm.gray) 36 | ax1.imshow(color_mask) 37 | ax2.imshow(img_masked) 38 | plt.show() 39 | -------------------------------------------------------------------------------- /data/clock_motion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/data/clock_motion.png -------------------------------------------------------------------------------- /data/david.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/data/david.png -------------------------------------------------------------------------------- /data/stefan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/data/stefan.jpg -------------------------------------------------------------------------------- /data/webreg.txt: -------------------------------------------------------------------------------- 1 | http://vision.ece.ucsb.edu/registration/demo/examples.shtml 2 | 3 | -------------------------------------------------------------------------------- /data/webreg_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/data/webreg_0.jpg -------------------------------------------------------------------------------- /data/webreg_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/data/webreg_1.jpg -------------------------------------------------------------------------------- /data/window.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/data/window.jpg -------------------------------------------------------------------------------- /digits_recognition/machine_learning.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author: Francois Boulogne 4 | # Author: Gael Varoquaux 5 | # License: Simplified BSD 6 | 7 | import os 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | import skimage.io 13 | 14 | def load_knowndata(filenames, show=False): 15 | training = {'images': [], 'targets': [], 'data': [], 'name': []} 16 | 17 | for index, filename in enumerate(filenames): 18 | target = os.path.splitext(os.path.basename(filename))[0] 19 | target = int(target.split('-')[0]) 20 | image = skimage.io.imread(filename) 21 | training['targets'].append(target) 22 | training['images'].append(image) 23 | training['name'].append(filename) 24 | training['data'].append(image.flatten().tolist()) 25 | if show: 26 | plt.subplot(6, 5, index + 1) 27 | plt.axis('off') 28 | plt.imshow(image, cmap=pl.cm.gray_r, interpolation='nearest') 29 | plt.title('Training: %i' % target) 30 | if show: 31 | plt.show() 32 | # To apply an classifier on this data, we need to flatten the image, to 33 | # turn the data in a (samples, feature) matrix: 34 | training['images'] = np.array(training['images']) 35 | training['targets'] = np.array(training['targets']) 36 | training['data'] = np.array(training['data']) 37 | return training 38 | 39 | def load_unknowndata(filenames): 40 | training = {'images': [], 'targets': [], 'data': [], 'name': []} 41 | 42 | for index, filename in enumerate(filenames): 43 | image = skimage.io.imread(filename) 44 | training['targets'].append(-1) # Target = -1: unkown 45 | training['images'].append(image) 46 | training['name'].append(filename) 47 | training['data'].append(image.flatten().tolist()) 48 | 49 | # To apply an classifier on this data, we need to flatten the image, to 50 | # turn the data in a (samples, feature) matrix: 51 | training['images'] = np.array(training['images']) 52 | training['targets'] = np.array(training['targets']) 53 | training['data'] = np.array(training['data']) 54 | return training 55 | -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/0-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/0-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/0-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/0-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/0-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/1-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/1-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/1-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/1-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/2-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/2-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/2-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/2-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/3-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/3-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/3-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/3-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/4-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/4-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/4-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/4-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/5-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/5-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/5-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/5-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/6-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/6-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/6-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/6-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/7-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/7-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/7-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/7-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/7-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/8-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/8-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/8-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/8-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/8-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/9-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/9-0.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/9-1.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_learned/9-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_learned/9-2.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000000.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000001.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000010.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000011.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000020.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000021.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000030.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000031.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000040.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000041.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000050.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000051.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000060.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000060.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000061.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000070.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000070.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000071.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000071.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000080.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000081.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000081.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000090.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000090.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000091.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000091.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000100.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000101.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000110.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000110.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000111.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000120.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000121.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000121.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000130.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000130.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000131.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000131.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000140.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000140.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000141.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000141.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000150.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000151.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000151.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000160.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000161.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000161.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000170.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000170.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000171.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000171.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000180.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000181.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000181.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000190.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000190.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000191.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000191.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000200.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000201.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000210.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000210.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000211.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000211.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000220.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000220.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000221.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000221.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000230.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000230.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000231.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000231.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000240.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000241.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000241.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000250.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000251.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000251.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000260.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000260.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000261.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000261.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000270.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000271.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000271.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000280.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000281.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000281.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000290.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000290.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000291.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000291.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000300.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000301.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000301.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000310.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000311.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000311.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000320.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000321.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000321.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000330.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000330.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000331.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000331.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000340.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000340.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000341.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000341.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000350.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000350.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000351.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000351.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000360.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000361.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000361.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000370.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000370.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000371.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000371.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000380.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000380.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000381.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000381.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000390.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000390.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000391.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000391.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000400.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000401.png -------------------------------------------------------------------------------- /digits_recognition/modern_digits_to_detect/small00000410.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/digits_recognition/modern_digits_to_detect/small00000410.png -------------------------------------------------------------------------------- /digits_recognition/run_modern_digits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author: Francois Boulogne 4 | 5 | import os 6 | import os.path 7 | import shutil 8 | import glob 9 | import skimage.io 10 | import matplotlib.pyplot as plt 11 | 12 | from segmentation import segment_digit 13 | from machine_learning import load_knowndata, load_unknowndata 14 | from sklearn import svm 15 | 16 | if __name__ == '__main__': 17 | data_bundle = 'modern_digits' 18 | 19 | output_dir = data_bundle + '_isolated_digits' 20 | results_dir = data_bundle + '_results' 21 | for thisdir in (output_dir, results_dir): 22 | shutil.rmtree(thisdir, ignore_errors=True) 23 | os.makedirs(thisdir) 24 | show = False 25 | 26 | for filename in glob.glob(data_bundle + '_to_detect/*.png'): 27 | print(filename) 28 | # Load picture 29 | image = skimage.io.imread(filename, as_grey=True) 30 | # crop picture 31 | image = image[0:200, 47:] 32 | 33 | segment_digit(image, filename, output_dir, black_on_white=False, show=False) 34 | 35 | filenames = sorted(glob.glob(data_bundle + '_learned/*-*.png')) 36 | training = load_knowndata(filenames, show) 37 | 38 | # Create a classifier: a support vector classifier 39 | classifier = svm.SVC(gamma=1e-8) 40 | 41 | # We learn the digits on the first half of the digits 42 | classifier.fit(training['data'], training['targets']) 43 | 44 | filenames = sorted(glob.glob(os.path.join(output_dir, '*.png'))) 45 | unknown = load_unknowndata(filenames) 46 | 47 | filenames = set([os.path.splitext(os.path.basename(fn))[0].split('-')[0] for fn in filenames]) 48 | 49 | for filename in filenames: 50 | fn = sorted(glob.glob(os.path.join(output_dir, filename + '*.png'))) 51 | unknown = load_unknowndata(fn) 52 | # Now predict the value of the digit on the second half: 53 | # expected = digits.target[n_samples / 2:] 54 | predicted = classifier.predict(unknown['data']) 55 | 56 | result = '' 57 | for pred, image, name in zip(predicted, unknown['images'], unknown['name']): 58 | result += str(pred) 59 | 60 | # Check 61 | fn = os.path.join(data_bundle + '_to_detect', filename + '.png') 62 | image = skimage.io.imread(fn) 63 | plt.imshow(image[:150, 56:], cmap=plt.cm.gray_r, interpolation='nearest') 64 | plt.title('Predicted: %s' % result) 65 | plt.savefig(os.path.join(results_dir, filename)) 66 | -------------------------------------------------------------------------------- /digits_recognition/segmentation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Author: Francois Boulogne 4 | 5 | import os.path 6 | 7 | import numpy as np 8 | from scipy import ndimage 9 | import matplotlib.pyplot as plt 10 | import matplotlib.patches as mpatches 11 | 12 | import skimage.io 13 | from skimage.filter import threshold_otsu 14 | from skimage.segmentation import clear_border 15 | from skimage.morphology import closing, square 16 | from skimage.measure import regionprops, label 17 | from skimage.color import label2rgb 18 | 19 | 20 | def segment_digit(image, filename, output_dir, digit_height=100, digit_width=52, 21 | border=7, black_on_white=True, closingpx=4, show=False): 22 | """ 23 | Segement each digit of a picture 24 | 25 | :param image: grey scale picture 26 | :param filename: filename of the picture source 27 | :param output_dir: path for the output 28 | :param digit_height: height of a digit 29 | :param digit_width: width of a digit 30 | :param border: pixels to shift the border 31 | :param black_on_white: black digit on clear background 32 | :param closingpx: number of pixels to close 33 | """ 34 | # apply threshold 35 | thresh = threshold_otsu(image) 36 | if black_on_white: 37 | bw = closing(image > thresh, square(closingpx)) 38 | else: 39 | bw = closing(image < thresh, square(closingpx)) 40 | 41 | filled = ndimage.binary_fill_holes(bw) 42 | 43 | # remove artifacts connected to image border 44 | cleared = filled.copy() 45 | clear_border(cleared) 46 | 47 | # label image regions 48 | label_image = label(cleared, background=None) 49 | borders = np.logical_xor(filled, cleared) 50 | label_image[borders] = -1 51 | image_label_overlay = label2rgb(label_image, image=image) 52 | 53 | if show: 54 | fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6)) 55 | ax.imshow(image_label_overlay) 56 | 57 | regions = regionprops(label_image) 58 | 59 | for item, region in enumerate(regions): 60 | # skip small elements 61 | if region['Area'] < 300: 62 | continue 63 | 64 | # draw rectangle around segmented digits 65 | minr, minc, maxr, maxc = region['BoundingBox'] 66 | #minr = maxr - digit_height 67 | #minc = maxc - digit_width 68 | rect = mpatches.Rectangle((minc, minr), maxc - minc, maxr - minr, 69 | fill=False, edgecolor='red', linewidth=2) 70 | 71 | # uniq size 72 | img = np.zeros((100, 75), 'uint8') 73 | img[bw[minr:maxr, minc:maxc] != 0] = 255 74 | newname = os.path.splitext(os.path.basename(filename))[0] + '-' + str(item) + '.png' 75 | skimage.io.imsave(os.path.join(output_dir, newname), img) 76 | if show: 77 | ax.add_patch(rect) 78 | 79 | if show: 80 | plt.show() 81 | -------------------------------------------------------------------------------- /dither.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from skimage import img_as_float, io 3 | from skimage.filters import threshold_otsu 4 | import numpy as np 5 | 6 | 7 | def quantize(image, L=1, N=4): 8 | """Quantize an image. 9 | 10 | Parameters 11 | ---------- 12 | image : array_like 13 | Input image. 14 | L : float 15 | Maximum input value. 16 | N : int 17 | Number of quantization levels. 18 | 19 | """ 20 | T = np.linspace(0, L, N, endpoint=False)[1:] 21 | return np.digitize(image.flat, T).reshape(image.shape) 22 | 23 | 24 | def dither(image, N=4, positions=None, weights=None): 25 | """Quantize an image, using dithering. 26 | 27 | Parameters 28 | ---------- 29 | image : ndarray 30 | Input image. 31 | N : int 32 | Number of quantization levels. 33 | positions : list of (i, j) offsets 34 | Position offset to which the quantization error is distributed. 35 | By default, implement Sierra's "Filter Lite". 36 | weights : list of ints 37 | Weights for propagated error. 38 | By default, implement Sierra's "Filter Lite". 39 | 40 | References 41 | ---------- 42 | http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT 43 | 44 | """ 45 | image = img_as_float(image.copy()) 46 | 47 | if positions is None or weights is None: 48 | positions = [(0, 1), (1, -1), (1, 0)] 49 | weights = [2, 1, 1] 50 | 51 | weights = weights / np.sum(weights) 52 | 53 | T = np.linspace(0, 1, N, endpoint=False)[1:] 54 | rows, cols = image.shape 55 | 56 | out = np.zeros_like(image, dtype=float) 57 | for i in range(rows): 58 | for j in range(cols): 59 | # Quantize 60 | out[i, j], = np.digitize([image[i, j]], T) 61 | 62 | # Propagate quantization noise 63 | d = (image[i, j] - out[i, j] / (N - 1)) 64 | for (ii, jj), w in zip(positions, weights): 65 | ii = i + ii 66 | jj = j + jj 67 | if ii < rows and jj < cols: 68 | image[ii, jj] += d * w 69 | 70 | return out 71 | 72 | 73 | def floyd_steinberg(image, N): 74 | offsets = [(0, 1), (1, -1), (1, 0), (1, 1)] 75 | weights = [ 7, 76 | 3, 5, 1] 77 | return dither(image, N, offsets, weights) 78 | 79 | 80 | def stucki(image, N): 81 | offsets = [(0, 1), (0, 2), (1, -2), (1, -1), 82 | (1, 0), (1, 1), (1, 2), 83 | (2, -2), (2, -1), (2, 0), (2, 1), (2, 2)] 84 | weights = [ 8, 4, 85 | 2, 4, 8, 4, 2, 86 | 1, 2, 4, 2, 1] 87 | return dither(image, N, offsets, weights) 88 | 89 | 90 | # Image with 255 color levels 91 | img = img_as_float(io.imread('data/david.png')) 92 | 93 | # Quantize to N levels 94 | N = 2 95 | img_quant = quantize(img, N=N) 96 | 97 | img_dither_random = img + np.abs(np.random.normal(size=img.shape, 98 | scale=1./(3 * N))) 99 | img_dither_random = quantize(img_dither_random, L=1, N=N) 100 | 101 | img_dither_fs = floyd_steinberg(img, N=N) 102 | img_dither_stucki = stucki(img, N=N) 103 | 104 | import matplotlib.pyplot as plt 105 | f, ax = plt.subplots(2, 3, subplot_kw={'xticks': [], 'yticks': []}) 106 | ax[0, 0].imshow(img, cmap=plt.cm.gray, interpolation='nearest') 107 | ax[0, 1].imshow(img_quant, cmap=plt.cm.gray, interpolation='nearest') 108 | ax[0, 2].imshow(img > threshold_otsu(img), cmap=plt.cm.gray, interpolation='nearest') 109 | #ax[0, 2].set_visible(False) 110 | ax[1, 0].imshow(img_dither_random, cmap=plt.cm.gray, interpolation='nearest') 111 | ax[1, 1].imshow(img_dither_fs, cmap=plt.cm.gray, interpolation='nearest') 112 | ax[1, 2].imshow(img_dither_stucki, cmap=plt.cm.gray, interpolation='nearest') 113 | 114 | ax[0, 0].set_title('Input') 115 | ax[0, 1].set_title('Quantization (N=%d)' % N) 116 | ax[0, 2].set_title('Otsu threshold') 117 | ax[1, 0].set_title('Dithering: Image + Noise') 118 | ax[1, 1].set_title('Floyd-Steinberg') 119 | ax[1, 2].set_title('Stucki') 120 | 121 | plt.show() 122 | 123 | -------------------------------------------------------------------------------- /fisheye.py: -------------------------------------------------------------------------------- 1 | from skimage import transform, data, io 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | image = io.imread('data/stefan.jpg') 7 | face = image[:185, 15:] 8 | 9 | 10 | def fisheye(xy): 11 | center = np.mean(xy, axis=0) 12 | xc, yc = (xy - center).T 13 | 14 | # Polar coordinates 15 | r = np.sqrt(xc**2 + yc**2) 16 | theta = np.arctan2(yc, xc) 17 | 18 | r = 0.8 * np.exp(r**(1/2.1) / 1.8) 19 | 20 | return np.column_stack(( 21 | r * np.cos(theta), r * np.sin(theta) 22 | )) + center 23 | 24 | out = transform.warp(face, fisheye) 25 | 26 | f, (ax0, ax1) = plt.subplots(1, 2, 27 | subplot_kw=dict(xticks=[], yticks=[])) 28 | ax0.imshow(face) 29 | ax1.imshow(out) 30 | 31 | plt.show() 32 | -------------------------------------------------------------------------------- /mm_color_cluster.py: -------------------------------------------------------------------------------- 1 | # Auto-clustering, suggested by Matt Terry 2 | 3 | from skimage import io, color, exposure 4 | from sklearn import cluster, preprocessing 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | 8 | 9 | url = 'http://blogs.mathworks.com/images/steve/2010/mms.jpg' 10 | 11 | import os 12 | if not os.path.exists('mm.jpg'): 13 | print("Downloading M&M's...") 14 | from urllib.request import urlretrieve 15 | urlretrieve(url, 'mm.jpg') 16 | 17 | 18 | print("Image I/O...") 19 | mm = io.imread('mm.jpg') 20 | mm_lab = color.rgb2lab(mm) 21 | ab = mm_lab[..., 1:] 22 | 23 | print("Mini-batch K-means...") 24 | X = ab.reshape(-1, 2) 25 | kmeans = cluster.MiniBatchKMeans(n_clusters=6) 26 | y = kmeans.fit(X).labels_ 27 | 28 | labels = y.reshape(mm.shape[:2]) 29 | N = labels.max() 30 | 31 | 32 | def no_ticks(ax): 33 | ax.set_xticks([]) 34 | ax.set_yticks([]) 35 | 36 | # Display all clusters 37 | for i in range(N): 38 | mask = (labels == i) 39 | mm_cluster = mm_lab.copy() 40 | mm_cluster[..., 1:][~mask] = 0 41 | 42 | ax = plt.subplot2grid((2, N), (1, i)) 43 | ax.imshow(color.lab2rgb(mm_cluster)) 44 | no_ticks(ax) 45 | 46 | 47 | ax = plt.subplot2grid((2, N), (0, 0), colspan=2) 48 | ax.imshow(mm) 49 | no_ticks(ax) 50 | 51 | 52 | # Display histogram 53 | 54 | L, a, b = mm_lab.T 55 | 56 | left, right = -100, 100 57 | bins = np.arange(left, right) 58 | H, x_edges, y_edges = np.histogram2d(a.flatten(), b.flatten(), bins, 59 | normed=True) 60 | 61 | ax = plt.subplot2grid((2, N), (0, 2)) 62 | H_bright = exposure.rescale_intensity(H, in_range=(0, 5e-4)) 63 | ax.imshow(H_bright, 64 | extent=[left, right, right, left], cmap=plt.cm.gray) 65 | ax.set_title('Histogram') 66 | ax.set_xlabel('b') 67 | ax.set_ylabel('a') 68 | 69 | 70 | # Voronoi diagram 71 | mid_bins = bins[:-1] + 0.5 72 | L = len(mid_bins) 73 | 74 | yy, xx = np.meshgrid(mid_bins, mid_bins) 75 | Z = kmeans.predict(np.column_stack([xx.ravel(), yy.ravel()])) 76 | Z = Z.reshape((L, L)) 77 | 78 | ax = plt.subplot2grid((2, N), (0, 3)) 79 | ax.imshow(Z, interpolation='nearest', 80 | extent=[left, right, right, left], 81 | cmap=plt.cm.Spectral, alpha=0.8) 82 | ax.imshow(H_bright, alpha=0.2, 83 | extent=[left, right, right, left], 84 | cmap=plt.cm.gray) 85 | ax.set_title('Clustered histogram') 86 | no_ticks(ax) 87 | 88 | 89 | plt.show() 90 | -------------------------------------------------------------------------------- /mm_color_select.py: -------------------------------------------------------------------------------- 1 | # Based on a blog post by Steve Eddins: 2 | # http://blogs.mathworks.com/steve/2010/12/23/two-dimensional-histograms/ 3 | 4 | from skimage import io, color, exposure 5 | import numpy as np 6 | 7 | 8 | url = 'http://blogs.mathworks.com/images/steve/2010/mms.jpg' 9 | 10 | import os 11 | if not os.path.exists('mm.jpg'): 12 | print("Downloading M&M's...") 13 | from urllib.request import urlretrieve 14 | urlretrieve(url, 'mm.jpg') 15 | 16 | 17 | mm = io.imread('mm.jpg') 18 | mm_lab = color.rgb2lab(mm) 19 | L, a, b = mm_lab.T 20 | 21 | left, right = -100, 100 22 | bins = np.arange(left, right) 23 | H, x_edges, y_edges = np.histogram2d(a.flatten(), b.flatten(), bins, 24 | normed=True) 25 | 26 | 27 | import matplotlib.pyplot as plt 28 | from matplotlib.widgets import RectangleSelector 29 | from matplotlib.patches import Rectangle 30 | 31 | f = plt.figure() 32 | ax0 = plt.subplot2grid((2, 2), (0, 0)) 33 | ax1 = plt.subplot2grid((2, 2), (0, 1), rowspan=2) 34 | ax2 = plt.subplot2grid((2, 2), (1, 0)) 35 | 36 | f.suptitle('Select values by dragging the mouse on the histogram') 37 | 38 | ax0.imshow(mm) 39 | ax0.set_title('Input') 40 | ax0.set_xticks([]) 41 | ax0.set_yticks([]) 42 | 43 | ax1.imshow(exposure.rescale_intensity(H, in_range=(0, 5e-4)), 44 | extent=[left, right, right, left], cmap=plt.cm.gray) 45 | ax1.set_title('Histogram') 46 | ax1.set_xlabel('b') 47 | ax1.set_ylabel('a') 48 | 49 | rectprops=dict( 50 | facecolor='gray', 51 | edgecolor='white', 52 | alpha=0.3 53 | ) 54 | selected_rectangle = Rectangle((0, 0), 0, 0, transform=ax1.transData, 55 | **rectprops) 56 | 57 | ax1.add_patch(selected_rectangle) 58 | 59 | result = ax2.imshow(mm) 60 | ax2.set_title('L + masked a, b') 61 | 62 | 63 | def histogram_select(e_click, e_release): 64 | x0, y0 = e_click.xdata, e_click.ydata 65 | x1, y1 = e_release.xdata, e_release.ydata 66 | 67 | x0, x1 = min(x0, x1), max(x0, x1) 68 | y0, y1 = min(y0, y1), max(y0, y1) 69 | 70 | selected_rectangle.set_xy((x0, y0)) 71 | selected_rectangle.set_height(y1 - y0) 72 | selected_rectangle.set_width(x1 - x0) 73 | 74 | green_mm_lab = mm_lab.copy() 75 | L, a, b = green_mm_lab.T 76 | 77 | mask = ((a > y0) & (a < y1)) & ((b > x0) & (b < x1)) 78 | green_mm_lab[..., 1:][~mask.T] = 0 79 | 80 | green_mm = color.lab2rgb(green_mm_lab) 81 | 82 | result.set_data(green_mm) 83 | f.canvas.draw() 84 | 85 | rs = RectangleSelector(ax1, histogram_select, drawtype='box', 86 | spancoords='data', rectprops=rectprops) 87 | 88 | plt.show() 89 | -------------------------------------------------------------------------------- /mutual_information.py: -------------------------------------------------------------------------------- 1 | from skimage.viewer import ImageViewer 2 | from skimage import data, img_as_float 3 | 4 | from skimage.viewer.plugins.plotplugin import PlotPlugin 5 | from skimage.viewer.widgets import Slider 6 | from skimage.exposure import rescale_intensity 7 | from skimage.transform import rotate 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | 13 | class RotatedImageViewer(ImageViewer): 14 | def __init__(self, image, **kwargs): 15 | super(RotatedImageViewer, self).__init__(image, **kwargs) 16 | 17 | slider_kwds = dict(value=0, low=0, high=5, update_on='release', 18 | callback=self.update_angle, value_type='float') 19 | 20 | self.slider = Slider('angle', **slider_kwds) 21 | self.layout.addWidget(self.slider) 22 | self.origin_image = image 23 | 24 | def update_angle(self, name, angle): 25 | self.image = rotate(self.original_image, angle) 26 | self.histogram.draw(angle=angle) 27 | 28 | 29 | class Histogram(PlotPlugin): 30 | name = 'Histogram' 31 | 32 | def __init__(self, original_viewer, **kwargs): 33 | super(Histogram, self).__init__(height=400, **kwargs) 34 | self.bins = np.linspace(0, 1, 100) 35 | self.mpl_image = None 36 | self.original_viewer = original_viewer 37 | 38 | def attach(self, image_viewer): 39 | super(Histogram, self).attach(image_viewer) 40 | self.rotated_viewer = image_viewer 41 | 42 | self.ax.set_title('Histogram') 43 | self.ax.set_xlabel('Value in image 1') 44 | self.ax.set_ylabel('Value in image 2') 45 | 46 | self.draw(angle=0) 47 | 48 | def draw(self, angle=0): 49 | image1 = self.original_viewer.image 50 | image2 = self.rotated_viewer.image 51 | 52 | hist, x_edges, y_edges = np.histogram2d(image1.flatten(), 53 | image2.flatten(), 54 | self.bins, normed=True) 55 | 56 | hist = np.log(1 + hist) 57 | hist = rescale_intensity(hist, in_range=(0, 3)) 58 | 59 | if self.mpl_image is None: 60 | self.mpl_image = self.ax.imshow(hist, extent=[0, 1, 0, 1], 61 | cmap=plt.cm.gray) 62 | else: 63 | self.mpl_image.set_data(hist) 64 | self.ax.figure.canvas.draw() 65 | 66 | return hist 67 | 68 | 69 | image = img_as_float(data.camera()) 70 | viewer = ImageViewer(image) 71 | rotated_viewer = RotatedImageViewer(image) 72 | 73 | histogram = Histogram(viewer) 74 | rotated_viewer += histogram 75 | rotated_viewer.histogram = histogram 76 | 77 | super(ImageViewer, viewer).show() 78 | rotated_viewer.show() 79 | -------------------------------------------------------------------------------- /pano/data/DFM_4209.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/DFM_4209.jpg -------------------------------------------------------------------------------- /pano/data/DFM_4210.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/DFM_4210.jpg -------------------------------------------------------------------------------- /pano/data/DFM_4211.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/DFM_4211.jpg -------------------------------------------------------------------------------- /pano/data/JDW_0302-Edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/JDW_0302-Edit.jpg -------------------------------------------------------------------------------- /pano/data/JDW_0303-Edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/JDW_0303-Edit.jpg -------------------------------------------------------------------------------- /pano/data/JDW_0304-Edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/JDW_0304-Edit.jpg -------------------------------------------------------------------------------- /pano/data/JDW_9518.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/JDW_9518.jpg -------------------------------------------------------------------------------- /pano/data/JDW_9519.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/JDW_9519.jpg -------------------------------------------------------------------------------- /pano/data/JDW_9520.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-image/skimage-demos/277264c12c28c6cc513e09b664502b9c872b4906/pano/data/JDW_9520.jpg -------------------------------------------------------------------------------- /pano/data/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The Petra dataset (c) 2014 by Francois Malan 2 | 3 | The Petra dataset is licensed under a 4 | Creative Commons Attribution 3.0 Unported License. 5 | 6 | You should have received a copy of the license along with this 7 | work. If not, see . 8 | 9 | 10 | All files with the prefix "JDW_" are (c) 2014 by Joshua D. Warner. 11 | 12 | They were taken, handheld, with a Nikon D7000 digital camera in 13 | Arches National Park, Utah, USA. 14 | 15 | These Arches photos are licensed under a 16 | Creative Commons Attribution 4.0 International (CC BY 4.0) License. 17 | 18 | You should have received a copy of the license along with this 19 | work. If not, see 20 | -------------------------------------------------------------------------------- /pano/pano-advanced.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "sha256:98449d2829ff7a1c971024907a786a862f1986ff1a4d25948a713a91743e53df" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "%matplotlib inline\n", 16 | "from __future__ import division, print_function" 17 | ], 18 | "language": "python", 19 | "metadata": {}, 20 | "outputs": [] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "# scikit-image advanced panorama demo\n", 27 | "\n", 28 | "Enhanced from the original demo as featured in [the scikit-image paper](https://peerj.com/articles/453/). This version does not require Enblend, instead stitching images along minimum-cost paths.\n", 29 | "\n", 30 | "This notebook may be found at https://github.com/scikit-image/scikit-image-demos" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "###First things first\n", 38 | "\n", 39 | "Import NumPy, matplotlib, some necessary scikit-image functions, and define a utility function to compare multiple images with matplotlib" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "collapsed": false, 45 | "input": [ 46 | "import numpy as np\n", 47 | "import matplotlib.pyplot as plt\n", 48 | "from skimage import io\n", 49 | "\n", 50 | "def compare(*images, **kwargs):\n", 51 | " \"\"\"\n", 52 | " Utility function to display images side by side.\n", 53 | " \n", 54 | " Parameters\n", 55 | " ----------\n", 56 | " image0, image1, image2, ... : ndarrray\n", 57 | " Images to display.\n", 58 | " labels : list\n", 59 | " Labels for the different images.\n", 60 | " \"\"\"\n", 61 | " f, axes = plt.subplots(1, len(images), **kwargs)\n", 62 | " axes = np.array(axes, ndmin=1)\n", 63 | " \n", 64 | " labels = kwargs.pop('labels', None)\n", 65 | " if labels is None:\n", 66 | " labels = [''] * len(images)\n", 67 | " \n", 68 | " for n, (image, label) in enumerate(zip(images, labels)):\n", 69 | " axes[n].imshow(image, interpolation='nearest', cmap='gray')\n", 70 | " axes[n].set_title(label)\n", 71 | " axes[n].axis('off')\n", 72 | " \n", 73 | " plt.tight_layout()" 74 | ], 75 | "language": "python", 76 | "metadata": {}, 77 | "outputs": [] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "###Load data\n", 84 | "\n", 85 | "The ``ImageCollection`` class provides an easy and efficient way to load and represent multiple images. Images in the ``ImageCollection`` are not read from disk until accessed.\n", 86 | "\n", 87 | "Load a series of images into an ``ImageCollection`` with a wildcard, as they share similar names. " 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "collapsed": false, 93 | "input": [ 94 | "pano_imgs = io.ImageCollection('data/JDW_9*')" 95 | ], 96 | "language": "python", 97 | "metadata": {}, 98 | "outputs": [] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "Inspect these images using the convenience function defined earlier" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "collapsed": false, 110 | "input": [ 111 | "compare(*pano_imgs, figsize=(15, 10))" 112 | ], 113 | "language": "python", 114 | "metadata": {}, 115 | "outputs": [] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "Credit: Images of Private Arch and the trail to Delicate Arch in Arches National Park, USA, taken by Joshua D. Warner.
\n", 122 | "License: CC-BY 4.0" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "#0. Pre-processing\n", 130 | "\n", 131 | "This stage usually involves one or more of the following:\n", 132 | "* Resizing, often downscaling with fixed aspect ratio\n", 133 | "* Conversion to grayscale, as many feature descriptors are not defined for color images\n", 134 | "* Cropping to region(s) of interest\n", 135 | "\n", 136 | "For convenience our example data is already resized smaller, and we won't bother cropping. However, they are presently in color so coversion to grayscale with `skimage.color.rgb2gray` is appropriate." 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "collapsed": false, 142 | "input": [ 143 | "from skimage.color import rgb2gray\n", 144 | "\n", 145 | "pano0 = rgb2gray(pano_imgs[0])\n", 146 | "pano1 = rgb2gray(pano_imgs[1])\n", 147 | "pano2 = rgb2gray(pano_imgs[2])" 148 | ], 149 | "language": "python", 150 | "metadata": {}, 151 | "outputs": [] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "collapsed": false, 156 | "input": [ 157 | "# View the results\n", 158 | "compare(pano0, pano1, pano2, figsize=(15, 10))" 159 | ], 160 | "language": "python", 161 | "metadata": {}, 162 | "outputs": [] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "# 1. Feature detection and matching\n", 169 | "\n", 170 | "We need to estimate a projective transformation that relates these images together. The steps will be\n", 171 | "\n", 172 | "1. Define one image as a _target_ or _destination_ image, which will remain anchored while the others are warped\n", 173 | "2. Detect features in all three images\n", 174 | "3. Match features from left and right images against the features in the center, anchored image.\n", 175 | "\n", 176 | "In this three-shot series, the middle image `pano1` is the logical anchor point.\n", 177 | "\n", 178 | "We detect \"Oriented FAST and rotated BRIEF\" (ORB) features in both images. \n", 179 | "\n", 180 | "**Note:** For efficiency in this tutorial we're only finding 400 keypoints. The results are good but have small variations. If you wanted a more robust estimate in practice, run multiple times and pick the best result or generate additional keypoints." 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "collapsed": false, 186 | "input": [ 187 | "from skimage.feature import ORB\n", 188 | "\n", 189 | "# Initialize ORB\n", 190 | "# 800 keypoints is large enough for robust results, \n", 191 | "# but low enough to run within a few seconds. \n", 192 | "orb = ORB(n_keypoints=800, fast_threshold=0.05)\n", 193 | "\n", 194 | "# Detect keypoints in pano0\n", 195 | "orb.detect_and_extract(pano0)\n", 196 | "keypoints0 = orb.keypoints\n", 197 | "descriptors0 = orb.descriptors\n", 198 | "\n", 199 | "# Detect keypoints in pano1\n", 200 | "orb.detect_and_extract(pano1)\n", 201 | "keypoints1 = orb.keypoints\n", 202 | "descriptors1 = orb.descriptors\n", 203 | "\n", 204 | "# Detect keypoints in pano2\n", 205 | "orb.detect_and_extract(pano2)\n", 206 | "keypoints2 = orb.keypoints\n", 207 | "descriptors2 = orb.descriptors" 208 | ], 209 | "language": "python", 210 | "metadata": {}, 211 | "outputs": [] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "Match features from images 0 <-> 1 and 1 <-> 2." 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "collapsed": false, 223 | "input": [ 224 | "from skimage.feature import match_descriptors\n", 225 | "\n", 226 | "# Match descriptors between left/right images and the center\n", 227 | "matches01 = match_descriptors(descriptors0, descriptors1, cross_check=True)\n", 228 | "matches12 = match_descriptors(descriptors1, descriptors2, cross_check=True)" 229 | ], 230 | "language": "python", 231 | "metadata": {}, 232 | "outputs": [] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "metadata": {}, 237 | "source": [ 238 | "Inspect these matched features side-by-side using the convenience function ``skimage.feature.plot_matches``. " 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "collapsed": false, 244 | "input": [ 245 | "from skimage.feature import plot_matches\n", 246 | "fig, ax = plt.subplots(1, 1, figsize=(15, 12))\n", 247 | "\n", 248 | "# Best match subset for pano0 -> pano1\n", 249 | "plot_matches(ax, pano0, pano1, keypoints0, keypoints1, matches01)\n", 250 | "ax.axis('off');" 251 | ], 252 | "language": "python", 253 | "metadata": {}, 254 | "outputs": [] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "metadata": {}, 259 | "source": [ 260 | "Most of these line up similarly, but it isn't perfect. There are a number of obvious outliers or false matches." 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "collapsed": false, 266 | "input": [ 267 | "fig, ax = plt.subplots(1, 1, figsize=(15, 12))\n", 268 | "\n", 269 | "# Best match subset for pano2 -> pano1\n", 270 | "plot_matches(ax, pano1, pano2, keypoints1, keypoints2, matches12)\n", 271 | "ax.axis('off');" 272 | ], 273 | "language": "python", 274 | "metadata": {}, 275 | "outputs": [] 276 | }, 277 | { 278 | "cell_type": "markdown", 279 | "metadata": {}, 280 | "source": [ 281 | "Similar to above, decent signal but numerous false matches." 282 | ] 283 | }, 284 | { 285 | "cell_type": "markdown", 286 | "metadata": {}, 287 | "source": [ 288 | "# 2. Transform estimation\n", 289 | "\n", 290 | "To filter out the false matches, we apply RANdom SAMple Consensus (RANSAC), a powerful method of rejecting outliers available in ``skimage.transform.ransac``. The transformation is estimated using an iterative process based on randomly chosen subsets, finally selecting the model which corresponds best with the majority of matches.\n", 291 | "\n", 292 | "We need to do this twice, once each for the transforms from left -> center and right -> center." 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "collapsed": false, 298 | "input": [ 299 | "from skimage.transform import ProjectiveTransform\n", 300 | "from skimage.measure import ransac\n", 301 | "\n", 302 | "# Select keypoints from \n", 303 | "# * source (image to be registered): pano0\n", 304 | "# * target (reference image): pano1, our middle frame registration target\n", 305 | "src = keypoints0[matches01[:, 0]][:, ::-1]\n", 306 | "dst = keypoints1[matches01[:, 1]][:, ::-1]\n", 307 | "\n", 308 | "model_robust01, inliers01 = ransac((src, dst), ProjectiveTransform,\n", 309 | " min_samples=4, residual_threshold=1, max_trials=300)\n", 310 | "\n", 311 | "# Select keypoints from \n", 312 | "# * source (image to be registered): pano2\n", 313 | "# * target (reference image): pano1, our middle frame registration target\n", 314 | "src = keypoints2[matches12[:, 1]][:, ::-1]\n", 315 | "dst = keypoints1[matches12[:, 0]][:, ::-1]\n", 316 | "\n", 317 | "model_robust12, inliers12 = ransac((src, dst), ProjectiveTransform,\n", 318 | " min_samples=4, residual_threshold=1, max_trials=300)" 319 | ], 320 | "language": "python", 321 | "metadata": {}, 322 | "outputs": [] 323 | }, 324 | { 325 | "cell_type": "markdown", 326 | "metadata": {}, 327 | "source": [ 328 | "The `inliers` returned from RANSAC select the best subset of matches. How do they look?" 329 | ] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "collapsed": false, 334 | "input": [ 335 | "fig, ax = plt.subplots(1, 1, figsize=(15, 12))\n", 336 | "\n", 337 | "# Best match subset for pano0 -> pano1\n", 338 | "plot_matches(ax, pano0, pano1, keypoints0, keypoints1, matches01[inliers01])\n", 339 | "\n", 340 | "ax.axis('off');" 341 | ], 342 | "language": "python", 343 | "metadata": {}, 344 | "outputs": [] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "collapsed": false, 349 | "input": [ 350 | "fig, ax = plt.subplots(1, 1, figsize=(15, 12))\n", 351 | "\n", 352 | "# Best match subset for pano2 -> pano1\n", 353 | "plot_matches(ax, pano1, pano2, keypoints1, keypoints2, matches12[inliers12])\n", 354 | "\n", 355 | "ax.axis('off');" 356 | ], 357 | "language": "python", 358 | "metadata": {}, 359 | "outputs": [] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "metadata": {}, 364 | "source": [ 365 | "Most of the false matches are rejected!" 366 | ] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "metadata": {}, 371 | "source": [ 372 | "# 3. Warping\n", 373 | "\n", 374 | "Next, we want to produce the panorama itself. To do that, we must _warp_, or transform, two of the three images so they will properly align with the stationary image.\n", 375 | "\n", 376 | "### Extent of output image\n", 377 | "The first step is to find the shape of the output image required to contain all three transformed images. To do this we consider the extents of all warped images." 378 | ] 379 | }, 380 | { 381 | "cell_type": "code", 382 | "collapsed": false, 383 | "input": [ 384 | "from skimage.transform import SimilarityTransform\n", 385 | "\n", 386 | "# Shape of middle image, our registration target\n", 387 | "r, c = pano1.shape[:2]\n", 388 | "\n", 389 | "# Note that transformations take coordinates in (x, y) format,\n", 390 | "# not (row, column), in order to be consistent with most literature\n", 391 | "corners = np.array([[0, 0],\n", 392 | " [0, r],\n", 393 | " [c, 0],\n", 394 | " [c, r]])\n", 395 | "\n", 396 | "# Warp the image corners to their new positions\n", 397 | "warped_corners01 = model_robust01(corners)\n", 398 | "warped_corners12 = model_robust12(corners)\n", 399 | "\n", 400 | "# Find the extents of both the reference image and the warped\n", 401 | "# target image\n", 402 | "all_corners = np.vstack((warped_corners01, warped_corners12, corners))\n", 403 | "\n", 404 | "# The overally output shape will be max - min\n", 405 | "corner_min = np.min(all_corners, axis=0)\n", 406 | "corner_max = np.max(all_corners, axis=0)\n", 407 | "output_shape = (corner_max - corner_min)\n", 408 | "\n", 409 | "# Ensure integer shape with np.ceil and dtype conversion\n", 410 | "output_shape = np.ceil(output_shape[::-1]).astype(int)" 411 | ], 412 | "language": "python", 413 | "metadata": {}, 414 | "outputs": [] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "metadata": {}, 419 | "source": [ 420 | "### Apply estimated transforms\n", 421 | "\n", 422 | "Warp the images with `skimage.transform.warp` according to the estimated transformation model. A shift, or _translation_ is necessary as our middle image needs to be placed in the middle, so it isn't truly stationary.\n", 423 | "\n", 424 | "Values outside the input images are set to -1 to distinguish the \"background\", which is identified for later use.\n", 425 | "\n", 426 | "**Note:** ``warp`` takes the _inverse_ mapping as an input." 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "collapsed": false, 432 | "input": [ 433 | "from skimage.transform import warp\n", 434 | "\n", 435 | "# This in-plane offset is the only necessary transformation for the middle image\n", 436 | "offset1 = SimilarityTransform(translation= -corner_min)\n", 437 | "\n", 438 | "\n", 439 | "# Warp pano0 to pano1 using 3rd order interpolation\n", 440 | "transform01 = (model_robust01 + offset1).inverse \n", 441 | "pano0_warped = warp(pano0, transform01, order=3,\n", 442 | " output_shape=output_shape, cval=-1)\n", 443 | "\n", 444 | "pano0_mask = (pano0_warped != -1) # Mask == 1 inside image\n", 445 | "pano0_warped[~pano0_mask] = 0 # Return background values to 0\n", 446 | "\n", 447 | "\n", 448 | "# Translate pano1 into place\n", 449 | "pano1_warped = warp(pano1, offset1.inverse, order=3,\n", 450 | " output_shape=output_shape, cval=-1)\n", 451 | "\n", 452 | "pano1_mask = (pano1_warped != -1) # Mask == 1 inside image\n", 453 | "pano1_warped[~pano1_mask] = 0 # Return background values to 0\n", 454 | "\n", 455 | "\n", 456 | "# Warp pano2 on to pano1 \n", 457 | "transform12 = (model_robust12 + offset1).inverse\n", 458 | "pano2_warped = warp(pano2, transform12, order=3,\n", 459 | " output_shape=output_shape, cval=-1)\n", 460 | "\n", 461 | "pano2_mask = (pano2_warped != -1) # Mask == 1 inside image\n", 462 | "pano2_warped[~pano2_mask] = 0 # Return background values to 0" 463 | ], 464 | "language": "python", 465 | "metadata": {}, 466 | "outputs": [] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "metadata": {}, 471 | "source": [ 472 | "Inspect the warped images:" 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "collapsed": false, 478 | "input": [ 479 | "compare(pano0_warped, pano1_warped, pano2_warped, figsize=(15, 10));" 480 | ], 481 | "language": "python", 482 | "metadata": {}, 483 | "outputs": [] 484 | }, 485 | { 486 | "cell_type": "markdown", 487 | "metadata": {}, 488 | "source": [ 489 | "#4. Combining images the easy (and bad) way\n", 490 | "\n", 491 | "This method simply \n", 492 | "\n", 493 | "1. sums the warped images\n", 494 | "2. tracks how many images overlapped to create each point\n", 495 | "3. normalizes the result." 496 | ] 497 | }, 498 | { 499 | "cell_type": "code", 500 | "collapsed": false, 501 | "input": [ 502 | "# Add the three images together. This could create dtype overflows!\n", 503 | "# We know they are are floating point images after warping, so it's OK.\n", 504 | "merged = (pano0_warped + pano1_warped + pano2_warped)\n", 505 | "\n", 506 | "# Track the overlap by adding the masks together\n", 507 | "overlap = (pano0_mask * 1.0 + # Multiply by 1.0 for bool -> float conversion\n", 508 | " pano1_mask + \n", 509 | " pano2_mask)\n", 510 | "\n", 511 | "# Normalize through division by `overlap` - but ensure the minimum is 1\n", 512 | "normalized = merged / np.maximum(overlap, 1)" 513 | ], 514 | "language": "python", 515 | "metadata": {}, 516 | "outputs": [] 517 | }, 518 | { 519 | "cell_type": "markdown", 520 | "metadata": {}, 521 | "source": [ 522 | "Finally, view the results!" 523 | ] 524 | }, 525 | { 526 | "cell_type": "code", 527 | "collapsed": false, 528 | "input": [ 529 | "fig, ax = plt.subplots(figsize=(15, 12))\n", 530 | "\n", 531 | "ax.imshow(normalized, cmap='gray')\n", 532 | "\n", 533 | "plt.tight_layout()\n", 534 | "ax.axis('off');" 535 | ], 536 | "language": "python", 537 | "metadata": {}, 538 | "outputs": [] 539 | }, 540 | { 541 | "cell_type": "code", 542 | "collapsed": false, 543 | "input": [], 544 | "language": "python", 545 | "metadata": {}, 546 | "outputs": [] 547 | }, 548 | { 549 | "cell_type": "code", 550 | "collapsed": false, 551 | "input": [], 552 | "language": "python", 553 | "metadata": {}, 554 | "outputs": [] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "collapsed": false, 559 | "input": [], 560 | "language": "python", 561 | "metadata": {}, 562 | "outputs": [] 563 | }, 564 | { 565 | "cell_type": "code", 566 | "collapsed": false, 567 | "input": [], 568 | "language": "python", 569 | "metadata": {}, 570 | "outputs": [] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "collapsed": false, 575 | "input": [], 576 | "language": "python", 577 | "metadata": {}, 578 | "outputs": [] 579 | }, 580 | { 581 | "cell_type": "code", 582 | "collapsed": false, 583 | "input": [], 584 | "language": "python", 585 | "metadata": {}, 586 | "outputs": [] 587 | }, 588 | { 589 | "cell_type": "code", 590 | "collapsed": false, 591 | "input": [], 592 | "language": "python", 593 | "metadata": {}, 594 | "outputs": [] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "collapsed": false, 599 | "input": [], 600 | "language": "python", 601 | "metadata": {}, 602 | "outputs": [] 603 | }, 604 | { 605 | "cell_type": "code", 606 | "collapsed": false, 607 | "input": [], 608 | "language": "python", 609 | "metadata": {}, 610 | "outputs": [] 611 | }, 612 | { 613 | "cell_type": "code", 614 | "collapsed": false, 615 | "input": [], 616 | "language": "python", 617 | "metadata": {}, 618 | "outputs": [] 619 | }, 620 | { 621 | "cell_type": "code", 622 | "collapsed": false, 623 | "input": [], 624 | "language": "python", 625 | "metadata": {}, 626 | "outputs": [] 627 | }, 628 | { 629 | "cell_type": "code", 630 | "collapsed": false, 631 | "input": [], 632 | "language": "python", 633 | "metadata": {}, 634 | "outputs": [] 635 | }, 636 | { 637 | "cell_type": "code", 638 | "collapsed": false, 639 | "input": [], 640 | "language": "python", 641 | "metadata": {}, 642 | "outputs": [] 643 | }, 644 | { 645 | "cell_type": "markdown", 646 | "metadata": {}, 647 | "source": [ 648 | "**What happened?!** Why are there nasty dark lines at boundaries, and why does the middle look so blurry?\n", 649 | "\n", 650 | "\n", 651 | "**This is an artifact (boundary effect) from the warping method**. When the image is warped with interpolation, the edge values are affected by the background. We could have bright lines if we'd chosen `cval=1` in the `warp` calls, but regardless of choice there will always be discontinuities.\n", 652 | "\n", 653 | "...Unless you use `order=0` in `warp`, which is nearest neighbor. Then these edges are perfect (try it!). But who wants to be limited to an inferior interpolation method? And, even then, it's blurry! Isn't there a better way?" 654 | ] 655 | }, 656 | { 657 | "cell_type": "markdown", 658 | "metadata": {}, 659 | "source": [ 660 | "#5. Stitching images along a minimum-cost path\n", 661 | "\n", 662 | "Let's step back a moment and consider: Is it even reasonable to blend pixels?\n", 663 | "\n", 664 | "Take a look at a difference image, which is just one image subtracted from the other." 665 | ] 666 | }, 667 | { 668 | "cell_type": "code", 669 | "collapsed": false, 670 | "input": [ 671 | "fig, ax = plt.subplots(figsize=(15,12))\n", 672 | "\n", 673 | "# Generate difference image and inspect it\n", 674 | "difference_image = pano0_warped - pano1_warped\n", 675 | "ax.imshow(difference_image, cmap='gray')\n", 676 | "\n", 677 | "ax.axis('off');" 678 | ], 679 | "language": "python", 680 | "metadata": {}, 681 | "outputs": [] 682 | }, 683 | { 684 | "cell_type": "markdown", 685 | "metadata": {}, 686 | "source": [ 687 | "The surrounding flat gray is zero, so we see the overlap region matches fairly well in the middle... but off to the sides where things start to look a little embossed, a simple average would blur the result. Indeed it did when we averaged everything (look again at the previous averaged panorama). _This is almost always the case for panoramas!_\n", 688 | "\n", 689 | "So, perhaps a different approach is needed?\n", 690 | "\n", 691 | "Let's attempt to find a vertical path through this difference image which stays as close to zero as possible. If we use that to build a mask, defining a transition between images, the result should appear _seamless_." 692 | ] 693 | }, 694 | { 695 | "cell_type": "markdown", 696 | "metadata": {}, 697 | "source": [ 698 | "# Seamless image stitching with Minimum-Cost Paths and `skimage.graph`\n", 699 | "\n", 700 | "The `skimage.graph` submodule allows you to start at any point on an array, and find the path to any other point which will _minimize_ the sum of values on the path.\n", 701 | "\n", 702 | "The overarching array is called a _cost array_, while the path found is a minimum-cost path or MCP.\n", 703 | "\n", 704 | "To accomplish this we need\n", 705 | "\n", 706 | "* Starting and ending points for the path\n", 707 | "* A cost array (a modified difference image)\n", 708 | "\n", 709 | "This method is so powerful that, with a carefully constructed costs array, the seed points are essentially irrelevant. It just works!" 710 | ] 711 | }, 712 | { 713 | "cell_type": "markdown", 714 | "metadata": {}, 715 | "source": [ 716 | "### Define seed points" 717 | ] 718 | }, 719 | { 720 | "cell_type": "code", 721 | "collapsed": false, 722 | "input": [ 723 | "ymax = output_shape[1] - 1\n", 724 | "xmax = output_shape[0] - 1\n", 725 | "\n", 726 | "# Start anywhere along the top and bottom, left of center.\n", 727 | "mask_pts01 = [[0, ymax // 3],\n", 728 | " [xmax, ymax // 3]]\n", 729 | "\n", 730 | "# Start anywhere along the top and bottom, right of center.\n", 731 | "mask_pts12 = [[0, 2*ymax // 3],\n", 732 | " [xmax, 2*ymax // 3]]" 733 | ], 734 | "language": "python", 735 | "metadata": {}, 736 | "outputs": [] 737 | }, 738 | { 739 | "cell_type": "markdown", 740 | "metadata": {}, 741 | "source": [ 742 | "### Construct cost array\n", 743 | "\n", 744 | "This utility function exists to give a \"break\" to the costs moving from the edge to the difference region.\n", 745 | "\n", 746 | "We will visually explore the results shortly. Examine the code later - for now, just use it." 747 | ] 748 | }, 749 | { 750 | "cell_type": "code", 751 | "collapsed": false, 752 | "input": [ 753 | "from skimage.measure import label\n", 754 | "\n", 755 | "def generate_costs(diff_image, mask, vertical=True, gradient_cutoff=2.):\n", 756 | " \"\"\"\n", 757 | " Ensures equal-cost paths from edges to region of interest.\n", 758 | " \n", 759 | " Parameters\n", 760 | " ----------\n", 761 | " diff_image : ndarray of floats\n", 762 | " Difference of two overlapping images.\n", 763 | " mask : ndarray of bools\n", 764 | " Mask representing the region of interest in ``diff_image``.\n", 765 | " vertical : bool\n", 766 | " Control operation orientation.\n", 767 | " gradient_cutoff : float\n", 768 | " Controls how far out of parallel lines can be to edges before\n", 769 | " correction is terminated. The default (2.) is good for most cases.\n", 770 | " \n", 771 | " Returns\n", 772 | " -------\n", 773 | " costs_arr : ndarray of floats\n", 774 | " Adjusted costs array, ready for use.\n", 775 | " \"\"\"\n", 776 | " if vertical is not True:\n", 777 | " return tweak_costs(diff_image.T, mask.T, vertical=vertical,\n", 778 | " gradient_cutoff=gradient_cutoff).T\n", 779 | " \n", 780 | " # Start with a high-cost array of 1's\n", 781 | " costs_arr = np.ones_like(diff_image)\n", 782 | " \n", 783 | " # Obtain extent of overlap\n", 784 | " row, col = mask.nonzero()\n", 785 | " cmin = col.min()\n", 786 | " cmax = col.max()\n", 787 | "\n", 788 | " # Label discrete regions\n", 789 | " cslice = slice(cmin, cmax + 1)\n", 790 | " labels = label(mask[:, cslice])\n", 791 | " \n", 792 | " # Find distance from edge to region\n", 793 | " upper = (labels == 0).sum(axis=0)\n", 794 | " lower = (labels == 2).sum(axis=0)\n", 795 | " \n", 796 | " # Reject areas of high change\n", 797 | " ugood = np.abs(np.gradient(upper)) < gradient_cutoff\n", 798 | " lgood = np.abs(np.gradient(lower)) < gradient_cutoff\n", 799 | " \n", 800 | " # Give areas slightly farther from edge a cost break\n", 801 | " costs_upper = np.ones_like(upper, dtype=np.float64)\n", 802 | " costs_lower = np.ones_like(lower, dtype=np.float64)\n", 803 | " costs_upper[ugood] = upper.min() / np.maximum(upper[ugood], 1)\n", 804 | " costs_lower[lgood] = lower.min() / np.maximum(lower[lgood], 1)\n", 805 | " \n", 806 | " # Expand from 1d back to 2d\n", 807 | " vdist = mask.shape[0]\n", 808 | " costs_upper = costs_upper[np.newaxis, :].repeat(vdist, axis=0)\n", 809 | " costs_lower = costs_lower[np.newaxis, :].repeat(vdist, axis=0)\n", 810 | " \n", 811 | " # Place these in output array\n", 812 | " costs_arr[:, cslice] = costs_upper * (labels == 0)\n", 813 | " costs_arr[:, cslice] += costs_lower * (labels == 2)\n", 814 | " \n", 815 | " # Finally, place the difference image\n", 816 | " costs_arr[mask] = diff_image[mask]\n", 817 | " \n", 818 | " return costs_arr" 819 | ], 820 | "language": "python", 821 | "metadata": {}, 822 | "outputs": [] 823 | }, 824 | { 825 | "cell_type": "markdown", 826 | "metadata": {}, 827 | "source": [ 828 | "Next we use this function to generate the cost array. We will start with the difference, but must modify it further or it will take the trivial path around either image, as the background is all zeros!\n", 829 | "\n", 830 | "We also set the top and bottom edges to be zero, so our path can freely slide to the optimal vertical path." 831 | ] 832 | }, 833 | { 834 | "cell_type": "code", 835 | "collapsed": false, 836 | "input": [ 837 | "# Start with the absolute value of the difference image.\n", 838 | "# np.abs is necessary because we don't want negative costs!\n", 839 | "costs01 = generate_costs(np.abs(pano0_warped - pano1_warped),\n", 840 | " pano0_mask & pano1_mask)" 841 | ], 842 | "language": "python", 843 | "metadata": {}, 844 | "outputs": [] 845 | }, 846 | { 847 | "cell_type": "markdown", 848 | "metadata": {}, 849 | "source": [ 850 | "Allow the path to \"slide\" along top and bottom edges to the optimal horizontal position by setting top and bottom edges to zero cost." 851 | ] 852 | }, 853 | { 854 | "cell_type": "code", 855 | "collapsed": false, 856 | "input": [ 857 | "costs01[0, :] = 0\n", 858 | "costs01[-1, :] = 0" 859 | ], 860 | "language": "python", 861 | "metadata": {}, 862 | "outputs": [] 863 | }, 864 | { 865 | "cell_type": "markdown", 866 | "metadata": {}, 867 | "source": [ 868 | "Our costs array now looks like this" 869 | ] 870 | }, 871 | { 872 | "cell_type": "code", 873 | "collapsed": false, 874 | "input": [ 875 | "fig, ax = plt.subplots(figsize=(8, 8))\n", 876 | "\n", 877 | "ax.imshow(costs01, cmap='gray');" 878 | ], 879 | "language": "python", 880 | "metadata": {}, 881 | "outputs": [] 882 | }, 883 | { 884 | "cell_type": "markdown", 885 | "metadata": {}, 886 | "source": [ 887 | "The tweak we made with `generate_costs` is subtle but important." 888 | ] 889 | }, 890 | { 891 | "cell_type": "markdown", 892 | "metadata": {}, 893 | "source": [ 894 | "### Find the minimum-cost path (MCP)\n", 895 | "\n", 896 | "Use `skimage.graph.route_through_array` to calculate an optimal path through the costs array" 897 | ] 898 | }, 899 | { 900 | "cell_type": "code", 901 | "collapsed": false, 902 | "input": [ 903 | "from skimage.graph import route_through_array\n", 904 | "\n", 905 | "# Arguments are:\n", 906 | "# cost array\n", 907 | "# start pt\n", 908 | "# end pt\n", 909 | "# can it traverse diagonally\n", 910 | "pts, _ = route_through_array(costs01, mask_pts01[0], mask_pts01[1], fully_connected=True)\n", 911 | "\n", 912 | "# Convert list of lists to 2d coordinate array for easier indexing\n", 913 | "pts = np.array(pts)" 914 | ], 915 | "language": "python", 916 | "metadata": {}, 917 | "outputs": [] 918 | }, 919 | { 920 | "cell_type": "markdown", 921 | "metadata": {}, 922 | "source": [ 923 | "Let's see how it worked" 924 | ] 925 | }, 926 | { 927 | "cell_type": "code", 928 | "collapsed": false, 929 | "input": [ 930 | "fig, ax = plt.subplots(figsize=(15, 15))\n", 931 | "\n", 932 | "# Plot the difference image\n", 933 | "ax.imshow(pano0_warped - pano1_warped, cmap='gray')\n", 934 | "\n", 935 | "# Overlay the minimum-cost path\n", 936 | "ax.plot(pts[:, 1], pts[:, 0]) \n", 937 | "\n", 938 | "plt.tight_layout()\n", 939 | "ax.axis('off');" 940 | ], 941 | "language": "python", 942 | "metadata": {}, 943 | "outputs": [] 944 | }, 945 | { 946 | "cell_type": "markdown", 947 | "metadata": {}, 948 | "source": [ 949 | "That looks like a great seam to stitch these images together - the entire path looks very close to zero." 950 | ] 951 | }, 952 | { 953 | "cell_type": "markdown", 954 | "metadata": {}, 955 | "source": [ 956 | "### Irregularities\n", 957 | "\n", 958 | "Due to the random element in the RANSAC transform estimation, everyone will have a slightly different path. **Your path will look different from mine, and different from your neighbor's.** That's intended! _The awesome thing about MCP is that everyone just calculated the best possible path to stitch together their unique transforms!_" 959 | ] 960 | }, 961 | { 962 | "cell_type": "markdown", 963 | "metadata": {}, 964 | "source": [ 965 | "### Filling the mask\n", 966 | "\n", 967 | "Our goal is to turn that path into a mask, which will be 1 where we want the left image to show through and zero elsewhere. We need to fill the left side of the mask with ones over to our path. \n", 968 | "\n", 969 | "Place the path into a new, empty array to begin building our mask." 970 | ] 971 | }, 972 | { 973 | "cell_type": "code", 974 | "collapsed": false, 975 | "input": [ 976 | "# Start with an array of zeros and place the path\n", 977 | "mask0 = np.zeros_like(pano0_warped, dtype=np.uint8)\n", 978 | "mask0[pts[:, 0], pts[:, 1]] = 1" 979 | ], 980 | "language": "python", 981 | "metadata": {}, 982 | "outputs": [] 983 | }, 984 | { 985 | "cell_type": "markdown", 986 | "metadata": {}, 987 | "source": [ 988 | "Ensure the path appears as expected" 989 | ] 990 | }, 991 | { 992 | "cell_type": "code", 993 | "collapsed": false, 994 | "input": [ 995 | "fig, ax = plt.subplots(figsize=(11, 11))\n", 996 | "\n", 997 | "# View the path in black and white\n", 998 | "ax.imshow(mask0, cmap='gray')\n", 999 | "\n", 1000 | "ax.axis('off');" 1001 | ], 1002 | "language": "python", 1003 | "metadata": {}, 1004 | "outputs": [] 1005 | }, 1006 | { 1007 | "cell_type": "markdown", 1008 | "metadata": {}, 1009 | "source": [ 1010 | "Label the various contiguous regions in the image using `skimage.measure.label`" 1011 | ] 1012 | }, 1013 | { 1014 | "cell_type": "code", 1015 | "collapsed": false, 1016 | "input": [ 1017 | "from skimage.measure import label\n", 1018 | "\n", 1019 | "# Labeling starts with zero at point (0, 0)\n", 1020 | "mask0[label(mask0, connectivity=1) == 0] = 1\n", 1021 | "\n", 1022 | "# The result\n", 1023 | "plt.imshow(mask0, cmap='gray');" 1024 | ], 1025 | "language": "python", 1026 | "metadata": {}, 1027 | "outputs": [] 1028 | }, 1029 | { 1030 | "cell_type": "markdown", 1031 | "metadata": {}, 1032 | "source": [ 1033 | "Looks great!\n", 1034 | "\n", 1035 | "### Rinse and repeat\n", 1036 | "\n", 1037 | "Apply the same principles to images 1 and 2: first, build the cost array" 1038 | ] 1039 | }, 1040 | { 1041 | "cell_type": "code", 1042 | "collapsed": false, 1043 | "input": [ 1044 | "# Start with the absolute value of the difference image.\n", 1045 | "# np.abs necessary because we don't want negative costs!\n", 1046 | "costs12 = generate_costs(np.abs(pano1_warped - pano2_warped),\n", 1047 | " pano1_mask & pano2_mask)\n", 1048 | "\n", 1049 | "# Allow the path to \"slide\" along top and bottom edges to the optimal \n", 1050 | "# horizontal position by setting top and bottom edges to zero cost\n", 1051 | "costs12[0, :] = 0\n", 1052 | "costs12[-1, :] = 0" 1053 | ], 1054 | "language": "python", 1055 | "metadata": {}, 1056 | "outputs": [] 1057 | }, 1058 | { 1059 | "cell_type": "markdown", 1060 | "metadata": {}, 1061 | "source": [ 1062 | "**Add an additional constraint this time**, to prevent this path crossing the prior one!" 1063 | ] 1064 | }, 1065 | { 1066 | "cell_type": "code", 1067 | "collapsed": false, 1068 | "input": [ 1069 | "costs12[mask0 > 0] = 1" 1070 | ], 1071 | "language": "python", 1072 | "metadata": {}, 1073 | "outputs": [] 1074 | }, 1075 | { 1076 | "cell_type": "markdown", 1077 | "metadata": {}, 1078 | "source": [ 1079 | "Check the result" 1080 | ] 1081 | }, 1082 | { 1083 | "cell_type": "code", 1084 | "collapsed": false, 1085 | "input": [ 1086 | "fig, ax = plt.subplots(figsize=(8, 8))\n", 1087 | "ax.imshow(costs12, cmap='gray');" 1088 | ], 1089 | "language": "python", 1090 | "metadata": {}, 1091 | "outputs": [] 1092 | }, 1093 | { 1094 | "cell_type": "markdown", 1095 | "metadata": {}, 1096 | "source": [ 1097 | "Your results may look slightly different.\n", 1098 | "\n", 1099 | "Compute the minimal cost path" 1100 | ] 1101 | }, 1102 | { 1103 | "cell_type": "code", 1104 | "collapsed": false, 1105 | "input": [ 1106 | "# Arguments are:\n", 1107 | "# cost array\n", 1108 | "# start pt\n", 1109 | "# end pt\n", 1110 | "# can it traverse diagonally\n", 1111 | "pts, _ = route_through_array(costs12, mask_pts12[0], mask_pts12[1], fully_connected=True)\n", 1112 | "\n", 1113 | "# Convert list of lists to 2d coordinate array for easier indexing\n", 1114 | "pts = np.array(pts)" 1115 | ], 1116 | "language": "python", 1117 | "metadata": {}, 1118 | "outputs": [] 1119 | }, 1120 | { 1121 | "cell_type": "markdown", 1122 | "metadata": {}, 1123 | "source": [ 1124 | "Verify a reasonable result" 1125 | ] 1126 | }, 1127 | { 1128 | "cell_type": "code", 1129 | "collapsed": false, 1130 | "input": [ 1131 | "fig, ax = plt.subplots(figsize=(15, 15))\n", 1132 | "\n", 1133 | "# Plot the difference image\n", 1134 | "ax.imshow(pano1_warped - pano2_warped, cmap='gray')\n", 1135 | "\n", 1136 | "# Overlay the minimum-cost path\n", 1137 | "ax.plot(pts[:, 1], pts[:, 0]);\n", 1138 | "\n", 1139 | "ax.axis('off');" 1140 | ], 1141 | "language": "python", 1142 | "metadata": {}, 1143 | "outputs": [] 1144 | }, 1145 | { 1146 | "cell_type": "markdown", 1147 | "metadata": {}, 1148 | "source": [ 1149 | "Initialize the mask by placing the path in a new array" 1150 | ] 1151 | }, 1152 | { 1153 | "cell_type": "code", 1154 | "collapsed": false, 1155 | "input": [ 1156 | "mask2 = np.zeros_like(pano0_warped, dtype=np.uint8)\n", 1157 | "mask2[pts[:, 0], pts[:, 1]] = 1" 1158 | ], 1159 | "language": "python", 1160 | "metadata": {}, 1161 | "outputs": [] 1162 | }, 1163 | { 1164 | "cell_type": "markdown", 1165 | "metadata": {}, 1166 | "source": [ 1167 | "Fill the right side this time, again using `skimage.measure.label` - the label of interest is 2" 1168 | ] 1169 | }, 1170 | { 1171 | "cell_type": "code", 1172 | "collapsed": false, 1173 | "input": [ 1174 | "mask2[label(mask2, connectivity=1) == 2] = 1\n", 1175 | "\n", 1176 | "# The result\n", 1177 | "plt.imshow(mask2, cmap='gray');" 1178 | ], 1179 | "language": "python", 1180 | "metadata": {}, 1181 | "outputs": [] 1182 | }, 1183 | { 1184 | "cell_type": "markdown", 1185 | "metadata": {}, 1186 | "source": [ 1187 | "### Final mask\n", 1188 | "\n", 1189 | "The last mask for the middle image is one of exclusion - it will be displayed everywhere `mask0` and `mask2` are not." 1190 | ] 1191 | }, 1192 | { 1193 | "cell_type": "code", 1194 | "collapsed": false, 1195 | "input": [ 1196 | "mask1 = ~(mask0 | mask2).astype(bool)" 1197 | ], 1198 | "language": "python", 1199 | "metadata": {}, 1200 | "outputs": [] 1201 | }, 1202 | { 1203 | "cell_type": "markdown", 1204 | "metadata": {}, 1205 | "source": [ 1206 | "Define a convenience function to place masks in alpha channels" 1207 | ] 1208 | }, 1209 | { 1210 | "cell_type": "code", 1211 | "collapsed": false, 1212 | "input": [ 1213 | "def add_alpha(img, mask=None):\n", 1214 | " \"\"\"\n", 1215 | " Adds a masked alpha channel to an image.\n", 1216 | " \n", 1217 | " Parameters\n", 1218 | " ----------\n", 1219 | " img : (M, N[, 3]) ndarray\n", 1220 | " Image data, should be rank-2 or rank-3 with RGB channels\n", 1221 | " mask : (M, N[, 3]) ndarray, optional\n", 1222 | " Mask to be applied. If None, the alpha channel is added\n", 1223 | " with full opacity assumed (1) at all locations.\n", 1224 | " \"\"\"\n", 1225 | " from skimage.color import gray2rgb\n", 1226 | " if mask is None:\n", 1227 | " mask = np.ones_like(img)\n", 1228 | " \n", 1229 | " if img.ndim == 2:\n", 1230 | " img = gray2rgb(img)\n", 1231 | " \n", 1232 | " return np.dstack((img, mask))" 1233 | ], 1234 | "language": "python", 1235 | "metadata": {}, 1236 | "outputs": [] 1237 | }, 1238 | { 1239 | "cell_type": "markdown", 1240 | "metadata": {}, 1241 | "source": [ 1242 | "Obtain final, alpha blended individual images and inspect them" 1243 | ] 1244 | }, 1245 | { 1246 | "cell_type": "code", 1247 | "collapsed": false, 1248 | "input": [ 1249 | "pano0_final = add_alpha(pano0_warped, mask0)\n", 1250 | "pano1_final = add_alpha(pano1_warped, mask1)\n", 1251 | "pano2_final = add_alpha(pano2_warped, mask2)\n", 1252 | "\n", 1253 | "compare(pano0_final, pano1_final, pano2_final, figsize=(15, 15))" 1254 | ], 1255 | "language": "python", 1256 | "metadata": {}, 1257 | "outputs": [] 1258 | }, 1259 | { 1260 | "cell_type": "markdown", 1261 | "metadata": {}, 1262 | "source": [ 1263 | "What we have here is the world's most complicated and precisely-fitting jigsaw puzzle...\n", 1264 | "\n", 1265 | "Plot all three together and view the results!" 1266 | ] 1267 | }, 1268 | { 1269 | "cell_type": "code", 1270 | "collapsed": false, 1271 | "input": [ 1272 | "fig, ax = plt.subplots(figsize=(15, 12))\n", 1273 | "\n", 1274 | "# This is a perfect combination, but matplotlib's interpolation\n", 1275 | "# makes it appear to have gaps. So we turn it off.\n", 1276 | "ax.imshow(pano0_final, interpolation='none')\n", 1277 | "ax.imshow(pano1_final, interpolation='none')\n", 1278 | "ax.imshow(pano2_final, interpolation='none')\n", 1279 | "\n", 1280 | "fig.tight_layout()\n", 1281 | "ax.axis('off');" 1282 | ], 1283 | "language": "python", 1284 | "metadata": {}, 1285 | "outputs": [] 1286 | }, 1287 | { 1288 | "cell_type": "markdown", 1289 | "metadata": {}, 1290 | "source": [ 1291 | "Fantastic! Without the black borders, you'd never know this was composed of separate images!" 1292 | ] 1293 | }, 1294 | { 1295 | "cell_type": "markdown", 1296 | "metadata": {}, 1297 | "source": [ 1298 | "#Bonus round: now, in color!\n", 1299 | "\n", 1300 | "We converted to grayscale for ORB feature detection, back in the initial **preprocessing** steps. Since we stored our transforms and masks, adding color is straightforward!\n", 1301 | "\n", 1302 | "Transform the colored images" 1303 | ] 1304 | }, 1305 | { 1306 | "cell_type": "code", 1307 | "collapsed": false, 1308 | "input": [ 1309 | "# Identical transforms as before, except\n", 1310 | "# * Operating on original color images\n", 1311 | "# * filling with cval=0 as we know the masks\n", 1312 | "pano0_color = warp(pano_imgs[0], (model_robust01 + offset1).inverse, order=3,\n", 1313 | " output_shape=output_shape, cval=0)\n", 1314 | "\n", 1315 | "pano1_color = warp(pano_imgs[1], offset1.inverse, order=3,\n", 1316 | " output_shape=output_shape, cval=0)\n", 1317 | "\n", 1318 | "pano2_color = warp(pano_imgs[2], (model_robust12 + offset1).inverse, order=3,\n", 1319 | " output_shape=output_shape, cval=0)" 1320 | ], 1321 | "language": "python", 1322 | "metadata": {}, 1323 | "outputs": [] 1324 | }, 1325 | { 1326 | "cell_type": "markdown", 1327 | "metadata": {}, 1328 | "source": [ 1329 | "Then apply the custom alpha channel masks" 1330 | ] 1331 | }, 1332 | { 1333 | "cell_type": "code", 1334 | "collapsed": false, 1335 | "input": [ 1336 | "pano0_final = add_alpha(pano0_color, mask0)\n", 1337 | "pano1_final = add_alpha(pano1_color, mask1)\n", 1338 | "pano2_final = add_alpha(pano2_color, mask2)" 1339 | ], 1340 | "language": "python", 1341 | "metadata": {}, 1342 | "outputs": [] 1343 | }, 1344 | { 1345 | "cell_type": "markdown", 1346 | "metadata": {}, 1347 | "source": [ 1348 | "View the result!" 1349 | ] 1350 | }, 1351 | { 1352 | "cell_type": "code", 1353 | "collapsed": false, 1354 | "input": [ 1355 | "fig, ax = plt.subplots(figsize=(15, 12))\n", 1356 | "\n", 1357 | "# Turn off matplotlib's interpolation\n", 1358 | "ax.imshow(pano0_final, interpolation='none')\n", 1359 | "ax.imshow(pano1_final, interpolation='none')\n", 1360 | "ax.imshow(pano2_final, interpolation='none')\n", 1361 | "\n", 1362 | "fig.tight_layout()\n", 1363 | "ax.axis('off');" 1364 | ], 1365 | "language": "python", 1366 | "metadata": {}, 1367 | "outputs": [] 1368 | }, 1369 | { 1370 | "cell_type": "markdown", 1371 | "metadata": {}, 1372 | "source": [ 1373 | "Save the combined, color panorama locally as `'./pano-advanced-output.png'`" 1374 | ] 1375 | }, 1376 | { 1377 | "cell_type": "code", 1378 | "collapsed": false, 1379 | "input": [ 1380 | "from skimage.color import gray2rgb\n", 1381 | "\n", 1382 | "# Start with empty image\n", 1383 | "pano_combined = np.zeros_like(pano0_color)\n", 1384 | "\n", 1385 | "# Place the masked portion of each image into the array\n", 1386 | "# masks are 2d, they need to be (M, N, 3) to match the color images\n", 1387 | "pano_combined += pano0_color * gray2rgb(mask0)\n", 1388 | "pano_combined += pano1_color * gray2rgb(mask1)\n", 1389 | "pano_combined += pano2_color * gray2rgb(mask2)\n", 1390 | "\n", 1391 | "\n", 1392 | "# Save the output - precision loss warning is expected\n", 1393 | "# moving from floating point -> uint8\n", 1394 | "io.imsave('./pano-advanced-output.png', pano_combined)" 1395 | ], 1396 | "language": "python", 1397 | "metadata": {}, 1398 | "outputs": [] 1399 | }, 1400 | { 1401 | "cell_type": "code", 1402 | "collapsed": false, 1403 | "input": [], 1404 | "language": "python", 1405 | "metadata": {}, 1406 | "outputs": [] 1407 | }, 1408 | { 1409 | "cell_type": "code", 1410 | "collapsed": false, 1411 | "input": [], 1412 | "language": "python", 1413 | "metadata": {}, 1414 | "outputs": [] 1415 | }, 1416 | { 1417 | "cell_type": "code", 1418 | "collapsed": false, 1419 | "input": [], 1420 | "language": "python", 1421 | "metadata": {}, 1422 | "outputs": [] 1423 | }, 1424 | { 1425 | "cell_type": "code", 1426 | "collapsed": false, 1427 | "input": [], 1428 | "language": "python", 1429 | "metadata": {}, 1430 | "outputs": [] 1431 | }, 1432 | { 1433 | "cell_type": "code", 1434 | "collapsed": false, 1435 | "input": [], 1436 | "language": "python", 1437 | "metadata": {}, 1438 | "outputs": [] 1439 | }, 1440 | { 1441 | "cell_type": "code", 1442 | "collapsed": false, 1443 | "input": [], 1444 | "language": "python", 1445 | "metadata": {}, 1446 | "outputs": [] 1447 | }, 1448 | { 1449 | "cell_type": "code", 1450 | "collapsed": false, 1451 | "input": [], 1452 | "language": "python", 1453 | "metadata": {}, 1454 | "outputs": [] 1455 | }, 1456 | { 1457 | "cell_type": "code", 1458 | "collapsed": false, 1459 | "input": [], 1460 | "language": "python", 1461 | "metadata": {}, 1462 | "outputs": [] 1463 | }, 1464 | { 1465 | "cell_type": "code", 1466 | "collapsed": false, 1467 | "input": [], 1468 | "language": "python", 1469 | "metadata": {}, 1470 | "outputs": [] 1471 | }, 1472 | { 1473 | "cell_type": "code", 1474 | "collapsed": false, 1475 | "input": [], 1476 | "language": "python", 1477 | "metadata": {}, 1478 | "outputs": [] 1479 | }, 1480 | { 1481 | "cell_type": "code", 1482 | "collapsed": false, 1483 | "input": [], 1484 | "language": "python", 1485 | "metadata": {}, 1486 | "outputs": [] 1487 | }, 1488 | { 1489 | "cell_type": "code", 1490 | "collapsed": false, 1491 | "input": [], 1492 | "language": "python", 1493 | "metadata": {}, 1494 | "outputs": [] 1495 | }, 1496 | { 1497 | "cell_type": "code", 1498 | "collapsed": false, 1499 | "input": [], 1500 | "language": "python", 1501 | "metadata": {}, 1502 | "outputs": [] 1503 | }, 1504 | { 1505 | "cell_type": "code", 1506 | "collapsed": false, 1507 | "input": [], 1508 | "language": "python", 1509 | "metadata": {}, 1510 | "outputs": [] 1511 | }, 1512 | { 1513 | "cell_type": "code", 1514 | "collapsed": false, 1515 | "input": [], 1516 | "language": "python", 1517 | "metadata": {}, 1518 | "outputs": [] 1519 | }, 1520 | { 1521 | "cell_type": "code", 1522 | "collapsed": false, 1523 | "input": [], 1524 | "language": "python", 1525 | "metadata": {}, 1526 | "outputs": [] 1527 | }, 1528 | { 1529 | "cell_type": "code", 1530 | "collapsed": false, 1531 | "input": [], 1532 | "language": "python", 1533 | "metadata": {}, 1534 | "outputs": [] 1535 | }, 1536 | { 1537 | "cell_type": "code", 1538 | "collapsed": false, 1539 | "input": [], 1540 | "language": "python", 1541 | "metadata": {}, 1542 | "outputs": [] 1543 | }, 1544 | { 1545 | "cell_type": "code", 1546 | "collapsed": false, 1547 | "input": [], 1548 | "language": "python", 1549 | "metadata": {}, 1550 | "outputs": [] 1551 | }, 1552 | { 1553 | "cell_type": "code", 1554 | "collapsed": false, 1555 | "input": [], 1556 | "language": "python", 1557 | "metadata": {}, 1558 | "outputs": [] 1559 | }, 1560 | { 1561 | "cell_type": "code", 1562 | "collapsed": false, 1563 | "input": [], 1564 | "language": "python", 1565 | "metadata": {}, 1566 | "outputs": [] 1567 | }, 1568 | { 1569 | "cell_type": "code", 1570 | "collapsed": false, 1571 | "input": [], 1572 | "language": "python", 1573 | "metadata": {}, 1574 | "outputs": [] 1575 | }, 1576 | { 1577 | "cell_type": "code", 1578 | "collapsed": false, 1579 | "input": [], 1580 | "language": "python", 1581 | "metadata": {}, 1582 | "outputs": [] 1583 | }, 1584 | { 1585 | "cell_type": "code", 1586 | "collapsed": false, 1587 | "input": [], 1588 | "language": "python", 1589 | "metadata": {}, 1590 | "outputs": [] 1591 | }, 1592 | { 1593 | "cell_type": "code", 1594 | "collapsed": false, 1595 | "input": [], 1596 | "language": "python", 1597 | "metadata": {}, 1598 | "outputs": [] 1599 | }, 1600 | { 1601 | "cell_type": "markdown", 1602 | "metadata": {}, 1603 | "source": [ 1604 | "#Once more, from the top\n", 1605 | "\n", 1606 | "I hear what you're saying. \"But Josh, those were too easy! The panoramas had too much overlap! Does this still work in the real world?\"" 1607 | ] 1608 | }, 1609 | { 1610 | "cell_type": "markdown", 1611 | "metadata": {}, 1612 | "source": [ 1613 | "**Go back to the top. Under \"Load Data\" replace the string `'data/JDW_03*'` with `'data/JDW_9*'`, and re-run all of the cells in order.**" 1614 | ] 1615 | }, 1616 | { 1617 | "cell_type": "code", 1618 | "collapsed": false, 1619 | "input": [], 1620 | "language": "python", 1621 | "metadata": {}, 1622 | "outputs": [] 1623 | } 1624 | ], 1625 | "metadata": {} 1626 | } 1627 | ] 1628 | } -------------------------------------------------------------------------------- /radial.py: -------------------------------------------------------------------------------- 1 | """Remove radial distortion. 2 | 3 | """ 4 | 5 | from __future__ import division 6 | 7 | import scipy as sp 8 | import scipy.optimize 9 | import scipy.ndimage 10 | 11 | from skimage.transform import warp 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | import math 16 | import sys 17 | 18 | class RadialDistortionInterface: 19 | """Mouse interaction interface for radial distortion removal. 20 | 21 | """ 22 | def __init__(self, img): 23 | height, width = img.shape[:2] 24 | self.figure = plt.imshow(img, extent=(0, width, height, 0)) 25 | plt.title('Removal of radial distortion') 26 | plt.xlabel('Select sets of three points with left mouse button,\n' 27 | 'click right button to process.') 28 | plt.connect('button_press_event', self.button_press) 29 | plt.connect('motion_notify_event', self.mouse_move) 30 | 31 | self.img = np.atleast_3d(img) 32 | self.points = [] 33 | self.centre = np.array([(width - 1)/2., (height - 1)/2.]) 34 | 35 | self.height = height 36 | self.width = width 37 | 38 | self.make_cursorline() 39 | self.figure.axes.set_autoscale_on(False) 40 | 41 | plt.show() 42 | plt.close() 43 | 44 | def make_cursorline(self): 45 | self.cursorline, = plt.plot([0],[0],'r:+', 46 | linewidth=2,markersize=15,markeredgecolor='b') 47 | 48 | def button_press(self,event): 49 | """Register mouse clicks. 50 | 51 | """ 52 | if (event.button == 1 and event.xdata and event.ydata): 53 | self.points.append((event.xdata,event.ydata)) 54 | print("Coordinate entered: (%f,%f)" % (event.xdata, event.ydata)) 55 | 56 | if len(self.points) % 3 == 0: 57 | plt.gca().lines.append(self.cursorline) 58 | self.make_cursorline() 59 | 60 | if (event.button != 1 and len(self.points) >= 3): 61 | print("Removing distortion...") 62 | plt.gca().lines = [] 63 | plt.draw() 64 | self.remove_distortion() 65 | self.points = [] 66 | 67 | def mouse_move(self,event): 68 | """Handle cursor drawing. 69 | 70 | """ 71 | pt_sets, pts_last_set = divmod(len(self.points),3) 72 | pts = np.zeros((3,2)) 73 | if pts_last_set > 0: 74 | # Line follows up to 3 clicked points: 75 | pts[:pts_last_set] = self.points[-pts_last_set:] 76 | # The last point of the line follows the mouse cursor 77 | pts[pts_last_set:] = [event.xdata,event.ydata] 78 | self.cursorline.set_data(pts[:,0], pts[:,1]) 79 | plt.draw() 80 | 81 | def remove_distortion(self, reshape=True): 82 | def radial_tf(xy, p): 83 | """Radially distort coordinates. 84 | 85 | Given a coordinate (x,y), apply the radial distortion defined by 86 | 87 | L(r) = 1 + p[2]r + p[3]r^2 + p[4]r^3 88 | 89 | where 90 | 91 | r = sqrt((x-p[0])^2 + (y-p[1])^2) 92 | 93 | so that 94 | 95 | x' = L(r)x and y' = L(r)y 96 | 97 | Parameters 98 | ---------- 99 | xy : (M, 2) ndarray 100 | Input coordinates. 101 | p : tuple 102 | Warp parameters: 103 | - p[0],p[1] -- Distortion centre 104 | - p[2], p[3], p[4] -- Radial distortion parameters 105 | 106 | Returns 107 | ------- 108 | xy : (M, 2) ndarray 109 | Radially warped coordinates. 110 | 111 | """ 112 | xy = np.array(xy, ndmin=2, copy=False) 113 | 114 | x = xy[:, 0] 115 | y = xy[:, 1] 116 | 117 | x = x - p[0] 118 | y = y - p[1] 119 | 120 | r = np.sqrt(x**2 + y**2) 121 | f = 1 + p[2]*r + p[3]*r**2 + p[4]*r**3 122 | 123 | return np.array([x*f + p[0], y*f + p[1]]).T 124 | 125 | def height_difference(p): 126 | """Measure deviation of distorted data points from straight line. 127 | 128 | References 129 | ---------- 130 | http://paulbourke.net/geometry/pointlineplane/ 131 | """ 132 | out = 0 133 | for sets in 3 * np.arange(len(self.points) // 3): 134 | pts = np.array(self.points[sets:sets+3]) 135 | xy = radial_tf(pts, p) 136 | 137 | x, y = xy[:, 0], xy[:, 1] 138 | x, y = xy.T 139 | 140 | # Find point on line (point0 <-> point2) closest to point1 (midpoint) 141 | u0 = ((x[0] - x[2])**2 + (y[0] - y[2])**2) 142 | if u0 == 0: 143 | return 1 144 | 145 | u = ((x[1] - x[0]) * (x[2] - x[0]) + \ 146 | (y[1] - y[0]) * (y[2] - y[0])) / u0 147 | 148 | # Intersection point 149 | ip_x = x[0] + u * (x[2] - x[0]) 150 | ip_y = y[0] + u * (y[2] - y[0]) 151 | 152 | # Distance between tip of triangle and and midpoint 153 | out += (ip_x - x[1])**2 + (ip_y - y[1])**2 154 | 155 | return out 156 | 157 | # Find the distortion parameters for which the data points lie on a 158 | # straight line 159 | rc = sp.optimize.fmin(height_difference, 160 | [self.centre[0], self.centre[1], 0., 0., 0.]) 161 | 162 | # Determine inverse coefficient 163 | xy = np.array([np.linspace(0, self.width), 164 | np.linspace(0, self.height)]).T 165 | 166 | def inv_min(p): 167 | # Take coordinates from a straight line and transform 168 | # to the "restored" domain with known rc 169 | xy_tf = radial_tf(xy, rc) 170 | 171 | # Transform back to the original image domain, 172 | # this time with the parameters p to be estimated 173 | xy_tf_back = radial_tf(xy_tf, p) 174 | 175 | return np.sum((xy_tf_back - xy)**2) 176 | 177 | # Find reverse transform via optimization 178 | rci = sp.optimize.fmin(inv_min, [rc[0], rc[1], 0., 0., 0.]) 179 | 180 | # Find extents of forward transform 181 | out_shape = np.array((self.height, self.width)) 182 | if reshape: 183 | top_corner = radial_tf([0., 0.], rc) 184 | bottom_corner = radial_tf([self.width - 1, self.height-1], rc) 185 | out_shape = (bottom_corner - top_corner)[0, ::-1] 186 | 187 | def radial_tf_shifted(xy, p): 188 | xy += top_corner 189 | xy = radial_tf(xy, p) 190 | return xy 191 | 192 | restored_image = warp(self.img, radial_tf_shifted, {'p': rci}, 193 | output_shape=out_shape.astype(int)) 194 | 195 | plt.figure() 196 | plt.imshow(restored_image) 197 | 198 | # Plot forward and reverse transforms 199 | x = np.linspace(self.width / 2, self.width) 200 | y = np.linspace(self.height / 2, self.height) 201 | r = np.sqrt((x - self.centre[0])**2 + (y - self.centre[1])**2) 202 | 203 | xy = np.array([x, y]).T 204 | 205 | xyr = radial_tf(xy, rc) - self.centre 206 | xyri = radial_tf(xy, rci) - self.centre 207 | 208 | rf = np.hypot(*xyr.T) 209 | rr = np.hypot(*xyri.T) 210 | 211 | a = plt.axes([0.15,.15,.15,.15]) 212 | plt.plot(r, rf, label='Forward mapping') 213 | plt.plot(r, rr, ':', label='Reverse mapping') 214 | plt.grid() 215 | #plt.xlabel('Input radius') 216 | #plt.ylabel('Transformed radius') 217 | #plt.legend() 218 | #plt.setp(a, xticks=[], yticks=[]) 219 | 220 | plt.show() 221 | 222 | from skimage.io import imread 223 | 224 | if len(sys.argv) != 2: 225 | print("Usage: %s " % sys.argv[0]) 226 | else: 227 | img = imread(sys.argv[1]) 228 | rdi = RadialDistortionInterface(img) 229 | -------------------------------------------------------------------------------- /register.py: -------------------------------------------------------------------------------- 1 | from skimage import io, transform 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import os 6 | 7 | 8 | # Load the two landscape photos 9 | img0 = io.imread('data/webreg_0.jpg') 10 | img1 = io.imread('data/webreg_1.jpg') 11 | 12 | 13 | def choose_corresponding_points(): 14 | """Utility function for finding corresponding features in images. 15 | 16 | Alternately click on image 0 and 1, indicating the same feature. 17 | 18 | """ 19 | f, (ax0, ax1) = plt.subplots(1, 2) 20 | ax0.imshow(img0) 21 | ax1.imshow(img1) 22 | 23 | coords = plt.ginput(8, timeout=0) 24 | 25 | np.savez('_reg_coords.npz', source=coords[::2], target=coords[1::2]) 26 | 27 | plt.close() 28 | 29 | 30 | # Re use previous coordinates, if found 31 | if not os.path.exists('_reg_coords.npz'): 32 | choose_corresponding_points() 33 | 34 | coords = np.load('_reg_coords.npz') 35 | 36 | # Estimate the transformation between the two sets of coordinates, 37 | # assuming it is an affine transform 38 | tf = transform.estimate_transform('similarity', coords['source'], coords['target']) 39 | 40 | # Use a translation transformation to center both images for display purposes 41 | offset = transform.SimilarityTransform(translation=(-200, -170)) 42 | 43 | img0_warped = transform.warp(img0, inverse_map=offset, 44 | output_shape=(600, 600)) 45 | 46 | img1_warped = transform.warp(img1, inverse_map=offset + tf, 47 | output_shape=(600, 600)) 48 | 49 | 50 | # Find where both images overlap; in that region average their values 51 | mask = (img0_warped != 0) & (img1_warped != 0) 52 | registered = img0_warped + img1_warped 53 | registered[mask] /= 2 54 | 55 | # Display the results 56 | f, (ax0, ax1, ax2) = plt.subplots(1, 3, subplot_kw={'xticks': [], 'yticks': []}) 57 | ax0.imshow(img0) 58 | ax1.imshow(img1) 59 | ax2.imshow(registered) 60 | 61 | # ## ## Display image boundaries 62 | # y, x = img1.shape[:2] 63 | # box = np.array([[0, 0], [0, y], [x, y], [x, 0], [0, 0]]) 64 | # tf_box = (offset + tf).inverse(box) 65 | # plt.plot(tf_box[:, 0], tf_box[:, 1], 'r-') 66 | 67 | plt.show() 68 | --------------------------------------------------------------------------------