├── README.md ├── code ├── matchers.py ├── pano.py └── txtlists │ ├── files1.txt │ ├── files2.txt │ ├── files2.txt~ │ └── files3.txt ├── images ├── 1.jpg ├── 1Hill.JPG ├── 2.jpg ├── 2Hill.JPG ├── 3.jpg ├── 3Hill.JPG ├── S1.jpg ├── S2.jpg ├── S3.jpg ├── S5.jpg ├── S6.jpg ├── wd1.jpg ├── wd2.jpg └── wd3.jpg ├── lunchroom_ultimate.jpg ├── test.jpg ├── test1.jpg ├── test12.jpg └── wd123.jpg /README.md: -------------------------------------------------------------------------------- 1 | # Multiple Image stitching in Python 2 | 3 | This repository contains an implementation of multiple image stitching. For explanation refer my blog post : [Creating a panorama using multiple images](http://kushalvyas.github.io/stitching.html) 4 | 5 | ### Requirements : 6 | 7 | - Python 2.7 8 | - Numpy >= 1.8 9 | - OpenCV 3.1.0 10 | 11 | 12 | ### Project Structure : 13 | 14 | |_ code -| 15 | | |-- pano.py 16 | | |-- txtlists-| 17 | | |--files1.txt .... 18 | | 19 | |_ images - | 20 | | |- img1.jpg 21 | | |- abc.jpg 22 | | .... and so on ... 23 | 24 | Demo txtfile : 25 | files2.txt : 26 | 27 | ../../images/1.jpg 28 | ../../images/2.jpg 29 | ../../images/3.jpg 30 | ../../images/4.jpg 31 | 32 | ### To run : 33 | 34 | `python pano.py ` 35 | 36 | 37 | ## Outputs !! 38 | 39 |
40 |
41 | Stitching with Lunchroom example 42 |

43 |
44 | Stitching with home example 45 |

46 |
47 | Stitching with building example 48 |

49 |
50 | Stitching using Hill example 51 |

52 |
53 | Stitching using room example 54 |
55 |
56 | 57 | ### Other WebSources for Images : 58 | Base paper for panorama using scale invariant features : 59 | 60 | [1] "Automatic Panoramic Image Stitching using Invariant Features", Download.springer.com, 2016. [Online]. Available: matthewalunbrown.com/papers/ijcv2007.pdf 61 | 62 | 63 | Test images taken from : 64 | 65 | [2]"PASSTA Datasets", Cvl.isy.liu.se, 2016. [Online]. Available: http://www.cvl.isy.liu.se/en/research/datasets/passta/. 66 | 67 | [3] "OpenCV Stitching example (Stitcher class, Panorama)", Study.marearts.com, 2013. [Online]. Available: http://study.marearts.com/2013/11/opencv-stitching-example-stitcher-class.html. 68 | 69 | [4] "Github daeyun Image-Stitching Test Images", 2016. [Online]. Available: https://github.com/daeyun/Image-Stitching/tree/master/img/hill. 70 | 71 | [5] "Github tsherlock Test Images", 2016. [Online]. Available: . https://github.com/tsherlock/panorama/ 72 | -------------------------------------------------------------------------------- /code/matchers.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | class matchers: 5 | def __init__(self): 6 | self.surf = cv2.xfeatures2d.SURF_create() 7 | FLANN_INDEX_KDTREE = 0 8 | index_params = dict(algorithm=0, trees=5) 9 | search_params = dict(checks=50) 10 | self.flann = cv2.FlannBasedMatcher(index_params, search_params) 11 | 12 | def match(self, i1, i2, direction=None): 13 | imageSet1 = self.getSURFFeatures(i1) 14 | imageSet2 = self.getSURFFeatures(i2) 15 | print "Direction : ", direction 16 | matches = self.flann.knnMatch( 17 | imageSet2['des'], 18 | imageSet1['des'], 19 | k=2 20 | ) 21 | good = [] 22 | for i , (m, n) in enumerate(matches): 23 | if m.distance < 0.7*n.distance: 24 | good.append((m.trainIdx, m.queryIdx)) 25 | 26 | if len(good) > 4: 27 | pointsCurrent = imageSet2['kp'] 28 | pointsPrevious = imageSet1['kp'] 29 | 30 | matchedPointsCurrent = np.float32( 31 | [pointsCurrent[i].pt for (__, i) in good] 32 | ) 33 | matchedPointsPrev = np.float32( 34 | [pointsPrevious[i].pt for (i, __) in good] 35 | ) 36 | 37 | H, s = cv2.findHomography(matchedPointsCurrent, matchedPointsPrev, cv2.RANSAC, 4) 38 | return H 39 | return None 40 | 41 | def getSURFFeatures(self, im): 42 | gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) 43 | kp, des = self.surf.detectAndCompute(gray, None) 44 | return {'kp':kp, 'des':des} -------------------------------------------------------------------------------- /code/pano.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | import sys 4 | from matchers import matchers 5 | import time 6 | 7 | class Stitch: 8 | def __init__(self, args): 9 | self.path = args 10 | fp = open(self.path, 'r') 11 | filenames = [each.rstrip('\r\n') for each in fp.readlines()] 12 | print filenames 13 | self.images = [cv2.resize(cv2.imread(each),(480, 320)) for each in filenames] 14 | self.count = len(self.images) 15 | self.left_list, self.right_list, self.center_im = [], [],None 16 | self.matcher_obj = matchers() 17 | self.prepare_lists() 18 | 19 | def prepare_lists(self): 20 | print "Number of images : %d"%self.count 21 | self.centerIdx = self.count/2 22 | print "Center index image : %d"%self.centerIdx 23 | self.center_im = self.images[int(self.centerIdx)] 24 | for i in range(self.count): 25 | if(i<=self.centerIdx): 26 | self.left_list.append(self.images[i]) 27 | else: 28 | self.right_list.append(self.images[i]) 29 | print "Image lists prepared" 30 | 31 | def leftshift(self): 32 | # self.left_list = reversed(self.left_list) 33 | a = self.left_list[0] 34 | for b in self.left_list[1:]: 35 | H = self.matcher_obj.match(a, b, 'left') 36 | print "Homography is : ", H 37 | xh = np.linalg.inv(H) 38 | print "Inverse Homography :", xh 39 | ds = np.dot(xh, np.array([a.shape[1], a.shape[0], 1])); 40 | ds = ds/ds[-1] 41 | print "final ds=>", ds 42 | f1 = np.dot(xh, np.array([0,0,1])) 43 | f1 = f1/f1[-1] 44 | xh[0][-1] += abs(f1[0]) 45 | xh[1][-1] += abs(f1[1]) 46 | ds = np.dot(xh, np.array([a.shape[1], a.shape[0], 1])) 47 | offsety = abs(int(f1[1])) 48 | offsetx = abs(int(f1[0])) 49 | dsize = (int(ds[0])+offsetx, int(ds[1]) + offsety) 50 | print "image dsize =>", dsize 51 | tmp = cv2.warpPerspective(a, xh, dsize) 52 | # cv2.imshow("warped", tmp) 53 | # cv2.waitKey() 54 | tmp[offsety:b.shape[0]+offsety, offsetx:b.shape[1]+offsetx] = b 55 | a = tmp 56 | 57 | self.leftImage = tmp 58 | 59 | 60 | def rightshift(self): 61 | for each in self.right_list: 62 | H = self.matcher_obj.match(self.leftImage, each, 'right') 63 | print "Homography :", H 64 | txyz = np.dot(H, np.array([each.shape[1], each.shape[0], 1])) 65 | txyz = txyz/txyz[-1] 66 | dsize = (int(txyz[0])+self.leftImage.shape[1], int(txyz[1])+self.leftImage.shape[0]) 67 | tmp = cv2.warpPerspective(each, H, dsize) 68 | cv2.imshow("tp", tmp) 69 | cv2.waitKey() 70 | # tmp[:self.leftImage.shape[0], :self.leftImage.shape[1]]=self.leftImage 71 | tmp = self.mix_and_match(self.leftImage, tmp) 72 | print "tmp shape",tmp.shape 73 | print "self.leftimage shape=", self.leftImage.shape 74 | self.leftImage = tmp 75 | # self.showImage('left') 76 | 77 | 78 | 79 | def mix_and_match(self, leftImage, warpedImage): 80 | i1y, i1x = leftImage.shape[:2] 81 | i2y, i2x = warpedImage.shape[:2] 82 | print leftImage[-1,-1] 83 | 84 | t = time.time() 85 | black_l = np.where(leftImage == np.array([0,0,0])) 86 | black_wi = np.where(warpedImage == np.array([0,0,0])) 87 | print time.time() - t 88 | print black_l[-1] 89 | 90 | for i in range(0, i1x): 91 | for j in range(0, i1y): 92 | try: 93 | if(np.array_equal(leftImage[j,i],np.array([0,0,0])) and np.array_equal(warpedImage[j,i],np.array([0,0,0]))): 94 | # print "BLACK" 95 | # instead of just putting it with black, 96 | # take average of all nearby values and avg it. 97 | warpedImage[j,i] = [0, 0, 0] 98 | else: 99 | if(np.array_equal(warpedImage[j,i],[0,0,0])): 100 | # print "PIXEL" 101 | warpedImage[j,i] = leftImage[j,i] 102 | else: 103 | if not np.array_equal(leftImage[j,i], [0,0,0]): 104 | bw, gw, rw = warpedImage[j,i] 105 | bl,gl,rl = leftImage[j,i] 106 | # b = (bl+bw)/2 107 | # g = (gl+gw)/2 108 | # r = (rl+rw)/2 109 | warpedImage[j, i] = [bl,gl,rl] 110 | except: 111 | pass 112 | # cv2.imshow("waRPED mix", warpedImage) 113 | # cv2.waitKey() 114 | return warpedImage 115 | 116 | 117 | 118 | 119 | def trim_left(self): 120 | pass 121 | 122 | def showImage(self, string=None): 123 | if string == 'left': 124 | cv2.imshow("left image", self.leftImage) 125 | # cv2.imshow("left image", cv2.resize(self.leftImage, (400,400))) 126 | elif string == "right": 127 | cv2.imshow("right Image", self.rightImage) 128 | cv2.waitKey() 129 | 130 | 131 | if __name__ == '__main__': 132 | try: 133 | args = sys.argv[1] 134 | except: 135 | args = "txtlists/files1.txt" 136 | finally: 137 | print "Parameters : ", args 138 | s = Stitch(args) 139 | s.leftshift() 140 | # s.showImage('left') 141 | s.rightshift() 142 | print "done" 143 | cv2.imwrite("test12.jpg", s.leftImage) 144 | print "image written" 145 | cv2.destroyAllWindows() 146 | 147 | -------------------------------------------------------------------------------- /code/txtlists/files1.txt: -------------------------------------------------------------------------------- 1 | ../images/S1.jpg 2 | ../images/S2.jpg 3 | ../images/S3.jpg 4 | ../images/S5.jpg 5 | ../images/S6.jpg -------------------------------------------------------------------------------- /code/txtlists/files2.txt: -------------------------------------------------------------------------------- 1 | ../images/1.jpg 2 | ../images/2.jpg 3 | ../images/3.jpg -------------------------------------------------------------------------------- /code/txtlists/files2.txt~: -------------------------------------------------------------------------------- 1 | ../../images/class/1.jpg 2 | ../../images/class/2.jpg 3 | ../../images/class/3.jpg 4 | ../../images/class/4.jpg -------------------------------------------------------------------------------- /code/txtlists/files3.txt: -------------------------------------------------------------------------------- 1 | ../images/1Hill.JPG 2 | ../images/2Hill.JPG 3 | ../images/3Hill.JPG -------------------------------------------------------------------------------- /images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/1.jpg -------------------------------------------------------------------------------- /images/1Hill.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/1Hill.JPG -------------------------------------------------------------------------------- /images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/2.jpg -------------------------------------------------------------------------------- /images/2Hill.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/2Hill.JPG -------------------------------------------------------------------------------- /images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/3.jpg -------------------------------------------------------------------------------- /images/3Hill.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/3Hill.JPG -------------------------------------------------------------------------------- /images/S1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/S1.jpg -------------------------------------------------------------------------------- /images/S2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/S2.jpg -------------------------------------------------------------------------------- /images/S3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/S3.jpg -------------------------------------------------------------------------------- /images/S5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/S5.jpg -------------------------------------------------------------------------------- /images/S6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/S6.jpg -------------------------------------------------------------------------------- /images/wd1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/wd1.jpg -------------------------------------------------------------------------------- /images/wd2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/wd2.jpg -------------------------------------------------------------------------------- /images/wd3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/images/wd3.jpg -------------------------------------------------------------------------------- /lunchroom_ultimate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/lunchroom_ultimate.jpg -------------------------------------------------------------------------------- /test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/test.jpg -------------------------------------------------------------------------------- /test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/test1.jpg -------------------------------------------------------------------------------- /test12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/test12.jpg -------------------------------------------------------------------------------- /wd123.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kushalvyas/Python-Multiple-Image-Stitching/abc1a9b5b70fd0630b40562d83c0038a868c7b04/wd123.jpg --------------------------------------------------------------------------------