-
Notifications
You must be signed in to change notification settings - Fork 354
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #875 from Aryan-Chharia/imagestitching
Image Stitching Project using OpenCV library
- Loading branch information
Showing
10 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# Image Stitching using OpenCV library | ||
|
||
## Project Overview | ||
|
||
This project demonstrates how to stitch multiple images together using the OpenCV library in Python. It involves loading images from a dataset, stitching them together, and post-processing the stitched image to remove any unwanted borders. | ||
|
||
## Process Details | ||
Load Images: Reads all images from the specified dataset folder. | ||
Image Stitching: Uses OpenCV's stitching functionality to combine the images into a panorama. | ||
Post-Processing: Adds a black border to the stitched image, converts it to grayscale, applies a threshold, and finds the largest contour to remove any black borders. | ||
Save Results: Saves the stitched image and the post-processed image in the Result/ directory. | ||
|
||
Example: | ||
If you have images in the Dataset/Glacier folder, set dataset_name = 'Glacier' in the script. The resulting images will be saved in the Result/Glacier folder as output1.jpg (stitched image) and output2.jpg (processed image). | ||
|
||
## Directory Structure | ||
|
||
The project directory is organized as follows: | ||
|
||
- `Dataset/`: Contains the input images for different datasets (e.g., Glacier, Mountain). | ||
- `Result/`: Stores the stitched images and the post-processed images for different datasets. | ||
- `stitching.py`: The main script that performs the image stitching and border removal. | ||
- `README.md`: This file, containing the project description and usage instructions. | ||
|
||
## Requirements | ||
|
||
Make sure you have the following packages installed: | ||
|
||
- numpy | ||
- opencv-python | ||
- imutils | ||
- glob | ||
- os | ||
|
||
You can install the required packages using the pip command | ||
|
||
## Usage | ||
|
||
### Dataset Preparation: | ||
|
||
Place the images you want to stitch together in a folder under the Dataset/ directory. | ||
Ensure the images are in JPG format. If they are not, you can either convert them to jpg or change the extension, used in the code, to the extension you want. | ||
|
||
### Running the Script: | ||
|
||
Update the dataset_name variable in stitching.py to the name of the folder containing your images. | ||
Adjust the threshold variable if necessary (default is 0.97). | ||
Run the stitching.py script | ||
|
||
## Results: | ||
|
||
The stitched image and the post-processed image will be saved in the corresponding folder under the Result/ directory. | ||
The script will also display the images using OpenCV's imshow function. | ||
|
||
![My Image](Result/Mountain/output1.jpg) | ||
![My Image](Result/Mountain/output2.jpg) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 9, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# import the necessary modules\n", | ||
"import numpy as np\n", | ||
"import cv2\n", | ||
"import glob\n", | ||
"import imutils\n", | ||
"import os" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 10, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Configurable parameters\n", | ||
"dataset_name = 'Mountain' # change the name according to the folder name in the dataset folder\n", | ||
"threshold = 0.97 \n", | ||
"\n", | ||
"# Define a threshold to determine if the border is small\n", | ||
"# The threshold represents the proportion of the image area that the largest contour should cover.\n", | ||
"# If the area of the largest contour is less than this threshold proportion of the total image area,\n", | ||
"# it indicates that the black border around the stitched image is small enough to be removed." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 11, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Load images\n", | ||
"file_path = os.path.join('Dataset', dataset_name, '*.jpg') # construct the file path where the images are\n", | ||
"image_paths = glob.glob(file_path) # extract the images' path from the file path\n", | ||
"images = []\n", | ||
"# for each image path in image paths, extract the images and append them to the images list\n", | ||
"# if the image is not loaded because of some reason, give an error\n", | ||
"for image_path in image_paths: \n", | ||
" img = cv2.imread(image_path)\n", | ||
" if img is not None:\n", | ||
" images.append(img)\n", | ||
" else:\n", | ||
" print(f\"Error loading image: {image_path}\")" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 12, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Check OpenCV version and create the stitcher accordingly\n", | ||
"if cv2.__version__.startswith('3'):\n", | ||
" stitcher = cv2.createStitcher()\n", | ||
"else:\n", | ||
" stitcher = cv2.Stitcher_create()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 13, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Stitch the images and return the stitched image\n", | ||
"error, stitched_img = stitcher.stitch(images)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 14, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# If the image has been successfully stitched, then we proceed futhur else show the error\n", | ||
"# Here cv2.Stitcher_OK checks if the image is stitched properly\n", | ||
"\n", | ||
"if error == cv2.Stitcher_OK:\n", | ||
" # Save the stitched image\n", | ||
" result_folder = os.path.join('Result', dataset_name)\n", | ||
" os.makedirs(result_folder, exist_ok=True) # this checks if directory where we have to store \n", | ||
" # is there or not, else create it\n", | ||
"\n", | ||
" output_path1 = os.path.join(result_folder, 'output1.jpg') # output path\n", | ||
" \n", | ||
" cv2.imwrite(output_path1, stitched_img) # saves the image\n", | ||
" cv2.imshow(\"Stitched Image\", stitched_img) # show the image\n", | ||
" cv2.waitKey(0) # wait till any key is pressed\n", | ||
" cv2.destroyAllWindows() # closes all the windows opened if any key is pressed\n", | ||
"\n", | ||
"# The stitched image often has a black border around it (see the result folder for an example)\n", | ||
"# Hence, we need to remove the black border to get the finished image\n", | ||
"# The following code selects a ROI (region of interest), which is usually a rectangle smaller than the image,\n", | ||
"# and removes the black border by finding and cropping to the largest contour within the thresholded image.\n", | ||
"\n", | ||
"\n", | ||
" # Add a border to the stitched image\n", | ||
" stitched_img = cv2.copyMakeBorder(stitched_img, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=(0, 0, 0))\n", | ||
"\n", | ||
" # Convert to grayscale and apply threshold to find the black border\n", | ||
" gray = cv2.cvtColor(stitched_img, cv2.COLOR_BGR2GRAY)\n", | ||
" _, thresh_img = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY)\n", | ||
"\n", | ||
" # Find contours in the thresholded image\n", | ||
" contours = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n", | ||
" contours = imutils.grab_contours(contours)\n", | ||
" areaOI = max(contours, key=cv2.contourArea)\n", | ||
"\n", | ||
" # Define a threshold to determine if the border is small\n", | ||
" if cv2.contourArea(areaOI) < threshold * gray.size:\n", | ||
" # Create a mask with the largest contour\n", | ||
" mask = np.zeros(thresh_img.shape, dtype=\"uint8\")\n", | ||
" x, y, w, h = cv2.boundingRect(areaOI)\n", | ||
" cv2.rectangle(mask, (x, y), (x + w, y + h), 255, -1)\n", | ||
"\n", | ||
" # Erode the mask until the black border is removed\n", | ||
" minRectangle = mask.copy()\n", | ||
" sub = mask.copy()\n", | ||
"\n", | ||
" while cv2.countNonZero(sub) > 0:\n", | ||
" minRectangle = cv2.erode(minRectangle, None)\n", | ||
" sub = cv2.subtract(minRectangle, thresh_img)\n", | ||
"\n", | ||
" # Find the new bounding box\n", | ||
" contours = cv2.findContours(minRectangle, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n", | ||
" contours = imutils.grab_contours(contours)\n", | ||
" areaOI = max(contours, key=cv2.contourArea)\n", | ||
" x, y, w, h = cv2.boundingRect(areaOI)\n", | ||
"\n", | ||
" # Crop the image to the bounding box\n", | ||
" stitched_img = stitched_img[y:y + h, x:x + w]\n", | ||
"\n", | ||
" # Save the processed image\n", | ||
" output_path2 = os.path.join(result_folder, 'output2.jpg')\n", | ||
" cv2.imwrite(output_path2, stitched_img)\n", | ||
" cv2.imshow(\"Stitched Image Processed\", stitched_img)\n", | ||
" cv2.waitKey(0)\n", | ||
" cv2.destroyAllWindows()\n", | ||
"else:\n", | ||
" print(\"Error during stitching:\", error)\n", | ||
" print(\"Images could not be stitched! Likely not enough keypoints being detected!\")" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.12.2" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |