├── result.jpg ├── images ├── first.jpg ├── second.jpg ├── newspaper1.jpg ├── newspaper2.jpg ├── newspaper3.jpg ├── newspaper4.jpg └── final_written.jpg ├── result_newspaper.jpg ├── README.md ├── LICENSE ├── stitch_using_stitcher_class.cpp └── stitch_scratch.py /result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/result.jpg -------------------------------------------------------------------------------- /images/first.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/first.jpg -------------------------------------------------------------------------------- /images/second.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/second.jpg -------------------------------------------------------------------------------- /images/newspaper1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/newspaper1.jpg -------------------------------------------------------------------------------- /images/newspaper2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/newspaper2.jpg -------------------------------------------------------------------------------- /images/newspaper3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/newspaper3.jpg -------------------------------------------------------------------------------- /images/newspaper4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/newspaper4.jpg -------------------------------------------------------------------------------- /result_newspaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/result_newspaper.jpg -------------------------------------------------------------------------------- /images/final_written.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krshrimali/Panorama-Image-Stitching-using-OpenCV/HEAD/images/final_written.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Panorama-OpenCV 2 | 3 | From the documentation of stitch_final.py 4 | 5 | Usage: python stitch_final.py -dest 6 | 7 | The usage: 8 | 9 | (Example) : 10 | 1) Please clone the repository to the working folder. 11 | 2) Execute following command 12 | 13 | python stitch_final.py images/destination.jpg 14 | 15 | 3) It will ask whether to crop, or rotate or not, please follow the given directions. 16 | 4) The image will be shown and saved to the given destination. 17 | 18 | [Theory etc. to be added later] - repo under maintenance...! Man at work, yes? :) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kushashwa Ravi Shrimali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /stitch_using_stitcher_class.cpp: -------------------------------------------------------------------------------- 1 | // CPP program to Stitch 2 | // input images (panorama) using OpenCV 3 | // Reference : https://docs.opencv.org/3.3.0/d8/d19/tutorial_stitcher.html 4 | // Credits : https://www.geeksforgeeks.org/stitching-input-images-panorama-using-opencv-c/ 5 | #include 6 | #include 7 | 8 | // Include header files from OpenCV directory 9 | // required to stitch images. 10 | #include "opencv2/imgcodecs.hpp" 11 | #include "opencv2/highgui.hpp" 12 | #include "opencv2/stitching.hpp" 13 | 14 | using namespace std; 15 | using namespace cv; 16 | 17 | // Define mode for stitching as panoroma 18 | // (One out of many functions of Stitcher) 19 | Stitcher::Mode mode = Stitcher::PANORAMA; 20 | 21 | // Array for pictures 22 | vector imgs; 23 | 24 | int main(int argc, char* argv[]) 25 | { 26 | // Get all the images that need to be 27 | // stitched as arguments from command line 28 | for (int i = 1; i < argc; ++i) 29 | { 30 | // Read the ith argument or image 31 | // and push into the image array 32 | Mat img = imread(argv[i]); 33 | if (img.empty()) 34 | { 35 | // Exit if image is not present 36 | cout << "Can't read image '" << argv[i] << "'\n"; 37 | return -1; 38 | } 39 | imgs.push_back(img); 40 | } 41 | 42 | // Define object to store the stitched image 43 | Mat pano; 44 | 45 | // Create a Stitcher class object with mode panoroma 46 | Ptr stitcher = Stitcher::create(mode, false); 47 | 48 | // Command to stitch all the images present in the image array 49 | Stitcher::Status status = stitcher->stitch(imgs, pano); 50 | 51 | if (status != Stitcher::OK) 52 | { 53 | // Check if images could not be stiched 54 | // status is OK if images are stiched successfully 55 | cout << "Can't stitch images\n"; 56 | return -1; 57 | } 58 | 59 | // Store a new image stiched from the given 60 | //set of images as "result.jpg" 61 | imwrite("result_newspaper.jpg", pano); 62 | 63 | // Show the result 64 | imshow("Result", pano); 65 | 66 | waitKey(0); 67 | return 0; 68 | } -------------------------------------------------------------------------------- /stitch_scratch.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Panorama using OpenCV (Python) 3 | 4 | Usage: python stitch_final.py --first --second 5 | 6 | Author: Kushashwa Ravi Shrimali 7 | ''' 8 | # importing necessary libraries 9 | import cv2 10 | import numpy as np 11 | import argparse 12 | from scipy import ndimage 13 | from image_align import * 14 | 15 | # utility function - resize image to defined height and width 16 | def resize(src, width=None, height=None, inter=cv2.INTER_AREA): 17 | ''' 18 | Utility Function: Resizes Image to new width and height 19 | 20 | Mention new width and height, else None expected. 21 | Default interpolation - INTER_AREA 22 | ''' 23 | 24 | print("Resizing... ") 25 | # set dimension to None, to return if no dimension specified 26 | dim = None 27 | (h, w) = src.shape[:2] # take height and width 28 | 29 | # handle None values in width and height 30 | if width is None and height is None: 31 | return src 32 | 33 | # to resize, aspect ratio has to be kept in check 34 | # for no distortion of the shape of the imag3 35 | if width is None: 36 | # calculate aspect ratio changed according to new height 37 | ratio = height / float(h) 38 | # change width according to the ratio 39 | dim = (int(w * ratio), height) 40 | else: 41 | # calculate aspect ratio changed according to new width 42 | ratio = width/float(w) 43 | dim = (width, int(h * ratio)) 44 | # apply resizing using calculated dimensions 45 | result = cv2.resize(src, dim, interpolation=inter) 46 | return result 47 | 48 | def detectFeaturesKeys(image): 49 | ''' 50 | Detects features and key points. 51 | Return : (keypoints, features) 52 | For OpenCV 3.x only (to-do : opencv 2.x) 53 | 54 | Usage: detectFeaturesKeys() 55 | ''' 56 | descriptor = cv2.xfeatures2d.SIFT_create() 57 | (kps, features) = descriptor.detectAndCompute(image, None) 58 | 59 | kps = np.float32([kp_.pt for kp_ in kps]) 60 | return (kps, features) 61 | 62 | def draw_keyPoints_BFMatcher(imageA, kpA, imageB, kpB, featuresA, featuresB): 63 | # reference : OpenCV Tutorial for Drawing Key Points (Brute Force method) 64 | good = [] 65 | print("Showing matches") 66 | 67 | bf = cv2.BFMatcher() 68 | matches_new = bf.knnMatch(featuresA, featuresB, k = 2) 69 | 70 | for m, n in matches_new: 71 | if m.distance < 0.75 * n.distance: 72 | good.append([m]) 73 | 74 | # passing None for output image 75 | # details 76 | # https://stackoverflow.com/questions/31631352/typeerror-required-argument-outimg-pos-6-not-found 77 | # cv2.drawMatchesKnn 78 | img_drawn = cv2.drawMatchesKnn(imageA, kpA, imageB, kpB, good, None, flags=2) 79 | cv2.imshow("Key Points", img_drawn) 80 | cv2.waitKey(0) 81 | cv2.destroyAllWindows() 82 | 83 | def matchKeyPoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh): 84 | ''' 85 | Match Key Points 86 | Usage: matchKeyPoints(imageA, imageB, keypoints_imageA, keypoints_imageB, features_imageA, 87 | features_imageB, ratio, reprojThresh) 88 | 89 | Returns: None if no match found 90 | (matches, H, status) if matches found. 91 | ''' 92 | matcher = cv2.DescriptorMatcher_create("BruteForce") 93 | rawMatches = matcher.knnMatch(featuresA, featuresB, 2) 94 | 95 | matches = [] 96 | 97 | for m in rawMatches: 98 | if len(m) == 2 and m[0].distance < m[1].distance * ratio: 99 | matches.append((m[0].trainIdx, m[0].queryIdx)) 100 | 101 | # writing function to debug newspaper image panorama issue 102 | # draw_keyPoints_BFMatcher(imageA, kpA, imageB, kpB, featuresA, featuresB) 103 | 104 | 105 | if len(matches) > 4: 106 | ptsA = np.float32([kpsA[i] for (_, i) in matches]) 107 | ptsB = np.float32([kpsB[i] for (i, _) in matches]) 108 | 109 | (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh) 110 | 111 | return (matches, H, status) 112 | return None 113 | 114 | 115 | def stitcher(images, reprojThresh = 4.0, ratio = 0.75): 116 | ''' 117 | Stitcher function : stitches the two images to form the panorama 118 | Usage: stitcher(, reprojThresh = 4.0, ratio = 0.75) 119 | 120 | returns list of three images: [imageA, imageB, panorama_image] 121 | ''' 122 | # unwrap images from right to left 123 | # imageB = images[0] 124 | # imageA = images[1] 125 | 126 | (imageB, imageA) = images 127 | show(imageB) 128 | show(imageA) 129 | 130 | # detect features and key points 131 | (kpsA, featuresA) = detectFeaturesKeys(imageA) 132 | (kpsB, featuresB) = detectFeaturesKeys(imageB) 133 | 134 | # match features between the two images 135 | matched_features = matchKeyPoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh) 136 | 137 | if(matched_features is None): 138 | print("No features matched.") 139 | return None 140 | 141 | # unwrap homography matrix (H), matches and status 142 | (matches, H, status) = matched_features 143 | 144 | # warping transformation 145 | warped_image = cv2.warpPerspective(imageA, H, 146 | (imageA.shape[1] + imageB.shape[1], imageA.shape[0])) 147 | warped_image[0:imageB.shape[0], 0:imageB.shape[1]] = imageB 148 | 149 | # wrap all the images to a list and return 150 | result = warped_image 151 | return result 152 | 153 | def show(img): 154 | cv2.imshow("Image", img) 155 | cv2.waitKey(0) 156 | cv2.destroyAllWindows() 157 | 158 | def rotate_and_crop(result_): 159 | # select ROI from image 160 | # reference: https://www.learnopencv.com/how-to-select-a-bounding-box-roi-in-opencv-cpp-python/ 161 | r = cv2.selectROI(result_, False) 162 | result_ = result_[int(r[1]):int(r[1] + r[3]), int(r[0]):int(r[0] + r[2])] 163 | 164 | rotate_ = input("Do you want to rotate the image? (yes/no) ") 165 | if(rotate_.lower() == "yes"): 166 | degrees = int(input("Degree by which you want to rotate the image: ")) 167 | 168 | rows, cols, channels = result_.shape 169 | M = cv2.getRotationMatrix2D((cols/2,rows/2), degrees, 1) 170 | dst = cv2.warpAffine(result_, M, (cols,rows)) 171 | 172 | result_ = ndimage.rotate(result_, degrees) 173 | show(result_) 174 | 175 | return result_ 176 | 177 | if __name__ == "__main__": 178 | # construct argument parse 179 | arg = argparse.ArgumentParser() 180 | 181 | arg.add_argument("-dest", "--destination", required=True, help = "Destination Name") 182 | 183 | args = vars(arg.parse_args()) 184 | 185 | # count_of_images = int(input("Number of images to stitch: ")) 186 | initial = "images/test2/" 187 | images_list = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg", "6.jpg", "7.jpg", "8.jpg"] 188 | images_list = [initial + x for x in images_list] 189 | print(images_list) 190 | count = 0 191 | 192 | while True: 193 | if(count == 0): 194 | imageA = cv2.imread(images_list[count]) 195 | imageB = cv2.imread(images_list[count + 1]) 196 | imageA = resize(imageA, 400) 197 | imageB = resize(imageB, 400) 198 | print(imageA.shape, imageB.shape) 199 | count += 2 200 | else: 201 | imageB = cv2.imread(images_list[count]) 202 | imageB = resize(imageB, 400) 203 | print(imageA.shape, imageB.shape) 204 | count += 1 205 | images = [imageA, imageB] 206 | imageA = stitcher(images) 207 | # imageA = resize(imageA, 400) 208 | show(imageA) 209 | print(imageA.shape, imageB.shape) 210 | 211 | # imageA = rotate_and_crop(imageA) 212 | 213 | if(count == len(images_list)): 214 | break 215 | # imageA = rotate_and_crop(imageA) 216 | show(imageA) 217 | cv2.imwrite(args["destination"], imageA) 218 | 219 | ''' 220 | images = [imageB, imageA] 221 | 222 | (imageA, imageB, result_A_B) = stitcher(images, 1) 223 | 224 | show(result_A_B) 225 | 226 | # result_A_B = resize(result_A_B, 400) 227 | 228 | images = [imageC, result_A_B] 229 | (imageC, result_A_B, result_AB_C) = stitcher(images, 1) 230 | show(result_AB_C) 231 | 232 | images = [imageD, result_AB_C] 233 | (imageD, result_AB_C, result_AB_CD) = stitcher(images, 1) 234 | 235 | # result_ = resize(result_, 400) 236 | 237 | # select ROI from image 238 | # reference: https://www.learnopencv.com/how-to-select-a-bounding-box-roi-in-opencv-cpp-python/ 239 | r = cv2.selectROI(result_, False) 240 | result_ = result_[int(r[1]):int(r[1] + r[3]), int(r[0]):int(r[0] + r[2])] 241 | 242 | rotate_ = input("Do you want to rotate the image? (yes/no) ") 243 | if(rotate_.lower() == "yes"): 244 | degrees = int(input("Degree by which you want to rotate the image: ")) 245 | 246 | rows, cols, channels = result_.shape 247 | M = cv2.getRotationMatrix2D((cols/2,rows/2), degrees, 1) 248 | dst = cv2.warpAffine(result_, M, (cols,rows)) 249 | 250 | result_ = ndimage.rotate(result_, degrees) 251 | show(result_) 252 | 253 | # write the image to the destination 254 | cv2.imwrite(args["destination"], result_) 255 | ''' 256 | --------------------------------------------------------------------------------