├── CMakeLists.txt ├── InputImages ├── Field │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ └── 8.jpg └── Sun │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ └── 5.jpg ├── .gitignore ├── README.md ├── main.py └── main.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | PROJECT(PanoramaStitching) 2 | 3 | ADD_EXECUTABLE(PanoramaStitching main.cpp) -------------------------------------------------------------------------------- /InputImages/Field/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/1.jpg -------------------------------------------------------------------------------- /InputImages/Field/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/2.jpg -------------------------------------------------------------------------------- /InputImages/Field/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/3.jpg -------------------------------------------------------------------------------- /InputImages/Field/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/4.jpg -------------------------------------------------------------------------------- /InputImages/Field/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/5.jpg -------------------------------------------------------------------------------- /InputImages/Field/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/6.jpg -------------------------------------------------------------------------------- /InputImages/Field/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/7.jpg -------------------------------------------------------------------------------- /InputImages/Field/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Field/8.jpg -------------------------------------------------------------------------------- /InputImages/Sun/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Sun/1.jpg -------------------------------------------------------------------------------- /InputImages/Sun/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Sun/2.jpg -------------------------------------------------------------------------------- /InputImages/Sun/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Sun/3.jpg -------------------------------------------------------------------------------- /InputImages/Sun/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Sun/4.jpg -------------------------------------------------------------------------------- /InputImages/Sun/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEDIARAHUL135/PanoramaStitching/HEAD/InputImages/Sun/5.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python extra files 2 | /.vscode 3 | /__pycache__ 4 | /.pylintrc 5 | 6 | 7 | # Cpp extra files 8 | /*.vcxproj* 9 | /CMakeCache.txt 10 | /CMakeFiles/ 11 | /*.sln 12 | /*.cmake 13 | /.vs 14 | /Debug/ 15 | /*.dir/ 16 | /x64/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PanoramaStitching 2 | 3 | This project aims to stitch two images together in the form of a panorama. Code is available in Python and C++. 4 | 5 | You can find complete explaination of the logic and documentation of the code [here](https://www.scribd.com/document/510892500/Panorama-Stitching). 6 | 7 | 8 | ### Installation 9 | 10 | Clone this repository and follow the steps below to run the code. 11 | 12 | ##### Python 13 | * Install the following dependencies. 14 | * `opencv-python` 15 | * `numpy` 16 | * `matplotlib` 17 | * Set the path of the two input images in the file `main.py` at lines 128-129. 18 | * Run the code using terminal 19 | * Navigate to the cwd. 20 | * Run: `$ python main.py` 21 | 22 | #### C++ 23 | * Make sure you have OpenCV binaries installed. 24 | * Set the path of the two input images in the file `main.cpp` at lines 172-173. 25 | * Run the file `main.cpp`. For CMake users, `CMakeLists.txt` is also created. 26 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | from matplotlib import pyplot as plt 4 | 5 | 6 | 7 | def FindMatches(BaseImage, SecImage): 8 | # Using SIFT to find the keypoints and decriptors in the images 9 | Sift = cv2.SIFT_create() 10 | BaseImage_kp, BaseImage_des = Sift.detectAndCompute(cv2.cvtColor(BaseImage, cv2.COLOR_BGR2GRAY), None) 11 | SecImage_kp, SecImage_des = Sift.detectAndCompute(cv2.cvtColor(SecImage, cv2.COLOR_BGR2GRAY), None) 12 | 13 | # Using Brute Force matcher to find matches. 14 | BF_Matcher = cv2.BFMatcher() 15 | InitialMatches = BF_Matcher.knnMatch(BaseImage_des, SecImage_des, k=2) 16 | 17 | # Applying ratio test and filtering out the good matches. 18 | GoodMatches = [] 19 | for m, n in InitialMatches: 20 | if m.distance < 0.75 * n.distance: 21 | GoodMatches.append([m]) 22 | 23 | return GoodMatches, BaseImage_kp, SecImage_kp 24 | 25 | 26 | 27 | def FindHomography(Matches, BaseImage_kp, SecImage_kp): 28 | # If less than 4 matches found, exit the code. 29 | if len(Matches) < 4: 30 | print("\nNot enough matches found between the images.\n") 31 | exit(0) 32 | 33 | # Storing coordinates of points corresponding to the matches found in both the images 34 | BaseImage_pts = [] 35 | SecImage_pts = [] 36 | for Match in Matches: 37 | BaseImage_pts.append(BaseImage_kp[Match[0].queryIdx].pt) 38 | SecImage_pts.append(SecImage_kp[Match[0].trainIdx].pt) 39 | 40 | # Changing the datatype to "float32" for finding homography 41 | BaseImage_pts = np.float32(BaseImage_pts) 42 | SecImage_pts = np.float32(SecImage_pts) 43 | 44 | # Finding the homography matrix(transformation matrix). 45 | (HomographyMatrix, Status) = cv2.findHomography(SecImage_pts, BaseImage_pts, cv2.RANSAC, 4.0) 46 | 47 | return HomographyMatrix, Status 48 | 49 | 50 | def GetNewFrameSizeAndMatrix(HomographyMatrix, Sec_ImageShape, Base_ImageShape): 51 | # Reading the size of the image 52 | (Height, Width) = Sec_ImageShape 53 | 54 | # Taking the matrix of initial coordinates of the corners of the secondary image 55 | # Stored in the following format: [[x1, x2, x3, x4], [y1, y2, y3, y4], [1, 1, 1, 1]] 56 | # Where (xi, yi) is the coordinate of the i th corner of the image. 57 | InitialMatrix = np.array([[0, Width - 1, Width - 1, 0], 58 | [0, 0, Height - 1, Height - 1], 59 | [1, 1, 1, 1]]) 60 | 61 | # Finding the final coordinates of the corners of the image after transformation. 62 | # NOTE: Here, the coordinates of the corners of the frame may go out of the 63 | # frame(negative values). We will correct this afterwards by updating the 64 | # homography matrix accordingly. 65 | FinalMatrix = np.dot(HomographyMatrix, InitialMatrix) 66 | 67 | [x, y, c] = FinalMatrix 68 | x = np.divide(x, c) 69 | y = np.divide(y, c) 70 | 71 | # Finding the dimentions of the stitched image frame and the "Correction" factor 72 | min_x, max_x = int(round(min(x))), int(round(max(x))) 73 | min_y, max_y = int(round(min(y))), int(round(max(y))) 74 | 75 | New_Width = max_x 76 | New_Height = max_y 77 | Correction = [0, 0] 78 | if min_x < 0: 79 | New_Width -= min_x 80 | Correction[0] = abs(min_x) 81 | if min_y < 0: 82 | New_Height -= min_y 83 | Correction[1] = abs(min_y) 84 | 85 | # Again correcting New_Width and New_Height 86 | # Helpful when secondary image is overlaped on the left hand side of the Base image. 87 | if New_Width < Base_ImageShape[1] + Correction[0]: 88 | New_Width = Base_ImageShape[1] + Correction[0] 89 | if New_Height < Base_ImageShape[0] + Correction[1]: 90 | New_Height = Base_ImageShape[0] + Correction[1] 91 | 92 | # Finding the coordinates of the corners of the image if they all were within the frame. 93 | x = np.add(x, Correction[0]) 94 | y = np.add(y, Correction[1]) 95 | OldInitialPoints = np.float32([[0, 0], 96 | [Width - 1, 0], 97 | [Width - 1, Height - 1], 98 | [0, Height - 1]]) 99 | NewFinalPonts = np.float32(np.array([x, y]).transpose()) 100 | 101 | # Updating the homography matrix. Done so that now the secondary image completely 102 | # lies inside the frame 103 | HomographyMatrix = cv2.getPerspectiveTransform(OldInitialPoints, NewFinalPonts) 104 | 105 | return [New_Height, New_Width], Correction, HomographyMatrix 106 | 107 | 108 | 109 | def StitchImages(BaseImage, SecImage): 110 | # Finding matches between the 2 images and their keypoints 111 | Matches, BaseImage_kp, SecImage_kp = FindMatches(BaseImage, SecImage) 112 | 113 | # Finding homography matrix. 114 | HomographyMatrix, Status = FindHomography(Matches, BaseImage_kp, SecImage_kp) 115 | 116 | # Finding size of new frame of stitched images and updating the homography matrix 117 | NewFrameSize, Correction, HomographyMatrix = GetNewFrameSizeAndMatrix(HomographyMatrix, SecImage.shape[:2], BaseImage.shape[:2]) 118 | 119 | # Finally placing the images upon one another. 120 | StitchedImage = cv2.warpPerspective(SecImage, HomographyMatrix, (NewFrameSize[1], NewFrameSize[0])) 121 | StitchedImage[Correction[1]:Correction[1]+BaseImage.shape[0], Correction[0]:Correction[0]+BaseImage.shape[1]] = BaseImage 122 | 123 | return StitchedImage 124 | 125 | 126 | if __name__ == "__main__": 127 | # Reading the 2 images. 128 | Image1 = cv2.imread("InputImages/Sun/1.jpg") 129 | Image2 = cv2.imread("InputImages/Sun/2.jpg") 130 | 131 | # Checking if images read 132 | if Image1 is None or Image2 is None: 133 | print("\nImages not read properly or does not exist.\n") 134 | exit(0) 135 | 136 | # Calling function for stitching images. 137 | StitchedImage = StitchImages(Image1, Image2) 138 | 139 | # Displaying the stitched images. 140 | plt.imshow(StitchedImage) 141 | plt.show() 142 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | 3 | #include "opencv2/opencv.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | 10 | void FindMatches(cv::Mat BaseImage, cv::Mat SecImage, std::vector& GoodMatches, std::vector& BaseImage_kp, std::vector& SecImage_kp) 11 | { 12 | using namespace cv; 13 | //using namespace cv::xfeatures2d; 14 | 15 | // Using SIFT to find the keypointsand decriptors in the images 16 | Ptr Sift = SIFT::create(); 17 | cv::Mat BaseImage_des, SecImage_des; 18 | cv::Mat BaseImage_Gray, SecImage_Gray; 19 | cv::cvtColor(BaseImage, BaseImage_Gray, cv::COLOR_BGR2GRAY); 20 | cv::cvtColor(SecImage, SecImage_Gray, cv::COLOR_BGR2GRAY); 21 | Sift->detectAndCompute(BaseImage_Gray, cv::noArray(), BaseImage_kp, BaseImage_des); 22 | Sift->detectAndCompute(SecImage_Gray, cv::noArray(), SecImage_kp, SecImage_des); 23 | 24 | // Using Brute Force matcher to find matches. 25 | cv::BFMatcher BF_Matcher; 26 | std::vector> InitialMatches; 27 | BF_Matcher.knnMatch(BaseImage_des, SecImage_des, InitialMatches, 2); 28 | 29 | // Applying ratio test and filtering out the good matches. 30 | for (int i = 0; i < InitialMatches.size(); ++i) 31 | { 32 | if (InitialMatches[i][0].distance < 0.75 * InitialMatches[i][1].distance) 33 | { 34 | GoodMatches.push_back(InitialMatches[i][0]); 35 | } 36 | } 37 | } 38 | 39 | 40 | void FindHomography(std::vector Matches, std::vector BaseImage_kp, std::vector SecImage_kp, cv::Mat& HomographyMatrix) 41 | { 42 | // If less than 4 matches found, exit the code. 43 | if (Matches.size() < 4) 44 | { 45 | std::cout << "\nNot enough matches found between the images.\n"; 46 | exit(0); 47 | } 48 | // Storing coordinates of points corresponding to the matches found in both the images 49 | std::vector BaseImage_pts, SecImage_pts; 50 | for (int i = 0 ; i < Matches.size() ; i++) 51 | { 52 | cv::DMatch Match = Matches[i]; 53 | BaseImage_pts.push_back(BaseImage_kp[Match.queryIdx].pt); 54 | SecImage_pts.push_back(SecImage_kp[Match.trainIdx].pt); 55 | } 56 | 57 | // Finding the homography matrix(transformation matrix). 58 | HomographyMatrix = cv::findHomography(SecImage_pts, BaseImage_pts, cv::RANSAC, (4.0)); 59 | } 60 | 61 | 62 | 63 | void GetNewFrameSizeAndMatrix(cv::Mat &HomographyMatrix, int* Sec_ImageShape, int* Base_ImageShape, int* NewFrameSize, int* Correction) 64 | { 65 | // Reading the size of the image 66 | int Height = Sec_ImageShape[0], Width = Sec_ImageShape[1]; 67 | 68 | // Taking the matrix of initial coordinates of the corners of the secondary image 69 | // Stored in the following format : [[x1, x2, x3, x4], [y1, y2, y3, y4], [1, 1, 1, 1]] 70 | // Where(xi, yi) is the coordinate of the i th corner of the image. 71 | double initialMatrix[3][4] = { {0, (double)Width - 1, (double)Width - 1, 0}, 72 | {0, 0, (double)Height - 1, (double)Height - 1}, 73 | {1.0, 1.0, 1.0, 1.0} }; 74 | cv::Mat InitialMatrix = cv::Mat(3, 4, CV_64F, initialMatrix);// .inv(); 75 | 76 | 77 | // Finding the final coordinates of the corners of the image after transformation. 78 | // NOTE: Here, the coordinates of the corners of the frame may go out of the 79 | // frame(negative values).We will correct this afterwards by updating the 80 | // homography matrix accordingly. 81 | cv::Mat FinalMatrix = HomographyMatrix * InitialMatrix; 82 | 83 | cv::Mat x = FinalMatrix(cv::Rect(0, 0, FinalMatrix.cols, 1)); 84 | cv::Mat y = FinalMatrix(cv::Rect(0, 1, FinalMatrix.cols, 1)); 85 | cv::Mat c = FinalMatrix(cv::Rect(0, 2, FinalMatrix.cols, 1)); 86 | 87 | 88 | cv::Mat x_by_c = x.mul(1 / c); 89 | cv::Mat y_by_c = y.mul(1 / c); 90 | 91 | // Finding the dimentions of the stitched image frame and the "Correction" factor 92 | double min_x, max_x, min_y, max_y; 93 | cv::minMaxLoc(x_by_c, &min_x, &max_x); 94 | cv::minMaxLoc(y_by_c, &min_y, &max_y); 95 | min_x = (int)round(min_x); max_x = (int)round(max_x); 96 | min_y = (int)round(min_y); max_y = (int)round(max_y); 97 | 98 | 99 | int New_Width = max_x, New_Height = max_y; 100 | Correction[0] = 0; Correction[1] = 0; 101 | if (min_x < 0) 102 | { 103 | New_Width -= min_x; 104 | Correction[0] = abs(min_x); 105 | } 106 | if (min_y < 0) 107 | { 108 | New_Height -= min_y; 109 | Correction[1] = abs(min_y); 110 | } 111 | 112 | // Again correcting New_Widthand New_Height 113 | // Helpful when secondary image is overlaped on the left hand side of the Base image. 114 | New_Width = (New_Width < Base_ImageShape[1] + Correction[0]) ? Base_ImageShape[1] + Correction[0] : New_Width; 115 | New_Height = (New_Height < Base_ImageShape[0] + Correction[1]) ? Base_ImageShape[0] + Correction[1] : New_Height; 116 | 117 | 118 | // Finding the coordinates of the corners of the image if they all were within the frame. 119 | cv::add(x_by_c, Correction[0], x_by_c); 120 | cv::add(y_by_c, Correction[1], y_by_c); 121 | 122 | 123 | cv::Point2f OldInitialPoints[4], NewFinalPonts[4]; 124 | OldInitialPoints[0] = cv::Point2f(0, 0); 125 | OldInitialPoints[1] = cv::Point2f(Width - 1, 0); 126 | OldInitialPoints[2] = cv::Point2f(Width - 1, Height - 1); 127 | OldInitialPoints[3] = cv::Point2f(0, Height - 1); 128 | for (int i = 0; i < 4; i++) 129 | NewFinalPonts[i] = cv::Point2f(x_by_c.at(0, i), y_by_c.at(0, i)); 130 | 131 | 132 | // Updating the homography matrix.Done so that now the secondary image completely 133 | // lies inside the frame 134 | HomographyMatrix = cv::getPerspectiveTransform(OldInitialPoints, NewFinalPonts); 135 | 136 | // Setting variable for returning 137 | NewFrameSize[0] = New_Height; NewFrameSize[1] = New_Width; 138 | 139 | } 140 | 141 | cv::Mat StitchImages(cv::Mat BaseImage, cv::Mat SecImage) 142 | { 143 | // Finding matches between the 2 images and their keypoints 144 | std::vector Matches; 145 | std::vector BaseImage_kp, SecImage_kp; 146 | FindMatches(BaseImage, SecImage, Matches, BaseImage_kp, SecImage_kp); 147 | 148 | // Finding homography matrix. 149 | cv::Mat HomographyMatrix; 150 | FindHomography(Matches, BaseImage_kp, SecImage_kp, HomographyMatrix); 151 | 152 | 153 | // Finding size of new frame of stitched images and updating the homography matrix 154 | int Sec_ImageShape[2] = { SecImage.rows, SecImage.cols }; 155 | int Base_ImageShape[2] = { BaseImage.rows, BaseImage.cols }; 156 | int NewFrameSize[2], Correction[2]; 157 | //NewFrameSize, Correction, HomographyMatrix = 158 | GetNewFrameSizeAndMatrix(HomographyMatrix, Sec_ImageShape, Base_ImageShape, NewFrameSize, Correction); 159 | 160 | 161 | // Finally placing the images upon one another. 162 | cv::Mat StitchedImage; 163 | cv::warpPerspective(SecImage, StitchedImage, HomographyMatrix, cv::Size(NewFrameSize[1], NewFrameSize[0])); 164 | BaseImage.copyTo(StitchedImage(cv::Rect(Correction[0], Correction[1], BaseImage.cols, BaseImage.rows))); 165 | 166 | return StitchedImage; 167 | } 168 | 169 | int main() 170 | { 171 | // Reading the 2 images. 172 | cv::Mat Image1 = cv::imread("InputImages/Field/8.jpg"); 173 | cv::Mat Image2 = cv::imread("InputImages/Field/7.jpg"); 174 | 175 | // Checking if images read 176 | if (Image1.empty() || Image2.empty()) 177 | { 178 | std::cout << "\nImages not read properly or does not exist.\n"; 179 | exit(0); 180 | } 181 | 182 | // Calling function for stitching images. 183 | cv::Mat StitchedImage = StitchImages(Image1, Image2); 184 | 185 | cv::imshow("Stitched Images", StitchedImage); 186 | cv::waitKey(0); 187 | 188 | return 0; 189 | } --------------------------------------------------------------------------------