ishaang14 commited on
Commit
a0bbd98
·
verified ·
1 Parent(s): a7758ff

Update mosaicking/app.py

Browse files
Files changed (1) hide show
  1. mosaicking/app.py +214 -193
mosaicking/app.py CHANGED
@@ -1,193 +1,214 @@
1
- import streamlit as st
2
- import os
3
- import cv2
4
- import numpy as np
5
- from ransac import *
6
- from match_features import *
7
- from scipy import optimize
8
- from optimize_fcn import *
9
- from PIL import Image
10
-
11
- class GenerateMosaic:
12
-
13
- def __init__(self, parent_folder, img_name_list):
14
- self.img_all = {}
15
- self.parent_folder = parent_folder
16
- self.img_name_list = img_name_list
17
- self.middle_id = int(np.floor(len(img_name_list) / 2))
18
-
19
- def mosaic(self):
20
-
21
- H_all = {}
22
- for i in range(len(self.img_name_list) - 1):
23
- st.write(f"Processing {self.img_name_list[i]} & {self.img_name_list[i + 1]}")
24
-
25
- key = f'H{i}{i + 1}'
26
-
27
- img_1_path = os.path.join(self.parent_folder, self.img_name_list[i])
28
- img_2_path = os.path.join(self.parent_folder, self.img_name_list[i + 1])
29
- # st.image([img_1_path, img_2_path], width=200)
30
- col1, gap, col2, a,b,c,v,f,d = st.columns(9)
31
-
32
- with col1:
33
- st.image(img_1_path, width=200)
34
-
35
- with col2:
36
- st.image(img_2_path, width=200)
37
- # Get SIFT descriptors
38
- siftmatch_obj = SiftMatching(img_1_path, img_2_path, results_fldr='', nfeatures=2000, gamma=0.6)
39
- correspondence = siftmatch_obj.run()
40
-
41
- # Run RANSAC to remove outliers
42
- ransac_obj = RANSAC()
43
- inliers_cnt, inliers, outliers, sample_pts, final_H = ransac_obj.run_ransac(correspondence)
44
-
45
- # Draw inliers and outliers
46
- result_path = os.path.join(siftmatch_obj.result_fldr, f'{siftmatch_obj.prefix}_inliers.jpg')
47
- ransac_obj.draw_lines(np.concatenate((inliers, sample_pts), axis=0), siftmatch_obj.img_1_bgr,
48
- siftmatch_obj.img_2_bgr, result_path,
49
- line_color=RANSAC._GREEN, pt_color=[0, 0, 0])
50
-
51
- result_path = os.path.join(siftmatch_obj.result_fldr, f'{siftmatch_obj.prefix}_outliers.jpg')
52
- ransac_obj.draw_lines(outliers, siftmatch_obj.img_1_bgr, siftmatch_obj.img_2_bgr, result_path,
53
- line_color=RANSAC._RED, pt_color=[0, 0, 0])
54
-
55
- # Optimize the homography
56
- x = np.concatenate((inliers, sample_pts), axis=0)
57
- opt_obj = OptimizeFunction(fun=fun_LM_homography, x0=final_H.flatten(), jac=jac_LM_homography,
58
- args=(x[:, 0:2], x[:, 2:]))
59
- LM_sol = opt_obj.levenberg_marquardt(delta_thresh=1e-24, tau=0.8)
60
-
61
- H_all[key] = LM_sol.x.reshape(3, 3)
62
- H_all[key] = H_all[key] / H_all[key][-1, -1]
63
-
64
- H_all = self.compute_H_wrt_middle_img(H_all)
65
- self.stitch(H_all, siftmatch_obj.result_fldr)
66
-
67
- def stitch(self, H_all, result_fldr):
68
-
69
- canvas_img, mask, offset = self.get_blank_canvas(H_all)
70
-
71
- for i, img_name in enumerate(self.img_name_list):
72
-
73
- key = f"H{i}{self.middle_id}"
74
- H = H_all[key]
75
- img_path = os.path.join(self.parent_folder, img_name)
76
-
77
- img_rgb = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
78
-
79
- canvas_img = fit_image_in_target_space(img_rgb, canvas_img, mask, np.linalg.inv(H),
80
- offset=offset)
81
- mask[np.where(canvas_img)[0:2]] = 0
82
-
83
- result_path = os.path.join(result_fldr, f'panorama_{i}.jpg')
84
- cv2.imwrite(result_path, canvas_img[:, :, (2, 1, 0)])
85
-
86
- # Show all generated images
87
- for img_name in ["panorama_1.jpg"]:
88
- img_path = os.path.join(result_fldr, img_name)
89
- if os.path.exists(img_path):
90
- img = Image.open(img_path)
91
- st.image(img, caption=img_name, use_column_width=True)
92
-
93
- def get_blank_canvas(self, H_all):
94
-
95
- img_path = os.path.join(self.parent_folder, self.img_name_list[0])
96
- img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
97
-
98
- img_h, img_w, _ = img.shape
99
-
100
- min_crd_canvas = np.array([np.inf, np.inf, np.inf])
101
- max_crd_canvas = np.array([-np.inf, -np.inf, -np.inf])
102
-
103
- for i in range(len(self.img_name_list)):
104
- key = f"H{i}{self.middle_id}"
105
- H = H_all[key]
106
- min_crd, max_crd = self.compute_extent(H, img_w, img_h)
107
-
108
- min_crd_canvas = np.minimum(min_crd, min_crd_canvas)
109
- max_crd_canvas = np.maximum(max_crd, max_crd_canvas)
110
-
111
- width_canvas = np.ceil(max_crd_canvas - min_crd_canvas)[0] + 1
112
- height_canvas = np.ceil(max_crd_canvas - min_crd_canvas)[1] + 1
113
-
114
- canvas_img = np.zeros((int(height_canvas), int(width_canvas), 3), dtype=np.int64)
115
-
116
- offset = min_crd_canvas.astype(np.int64)
117
- offset[2] = 0
118
-
119
- mask = np.ones((int(height_canvas), int(width_canvas)))
120
-
121
- return canvas_img, mask, offset
122
-
123
- def compute_extent(self, H, img_w, img_h):
124
-
125
- corners_img = np.array([[0, 0], [img_w, 0], [img_w, img_h], [0, img_h]])
126
-
127
- t_one = np.ones((corners_img.shape[0], 1))
128
- t_out_pts = np.concatenate((corners_img, t_one), axis=1)
129
- canvas_crd_corners = np.matmul(H, t_out_pts.T)
130
- canvas_crd_corners = canvas_crd_corners / canvas_crd_corners[-1, :]
131
-
132
- min_crd = np.amin(canvas_crd_corners.T, axis=0)
133
- max_crd = np.amax(canvas_crd_corners.T, axis=0)
134
- return min_crd, max_crd
135
-
136
- def compute_H_wrt_middle_img(self, H_all):
137
-
138
- num_imgs = len(H_all) + 1
139
- key = f"H{self.middle_id}{self.middle_id}"
140
- H_all[key] = np.eye(3)
141
-
142
- for i in range(0, self.middle_id):
143
- key = f"H{i}{self.middle_id}"
144
- j = i
145
- temp = np.eye(3)
146
- while j < self.middle_id:
147
- key_t = f"H{j}{j + 1}"
148
- temp = np.matmul(H_all[key_t], temp)
149
- j += 1
150
-
151
- H_all[key] = temp
152
-
153
- for i in range(self.middle_id + 1, num_imgs):
154
- key = f"H{i}{self.middle_id}"
155
-
156
- temp = np.eye(3)
157
- j = i - 1
158
-
159
- while j >= self.middle_id:
160
- key_t = f"H{j}{j + 1}"
161
- temp = np.matmul(np.linalg.inv(H_all[key_t]), temp)
162
- j -= 1
163
-
164
- H_all[key] = temp
165
-
166
- return H_all
167
-
168
- # Streamlit Interface
169
- st.title("Mosaic Generator")
170
-
171
- # User Input: Folder Path
172
- parent_folder = st.text_input("Enter the path of the parent folder containing images:")
173
-
174
- # Fetch images from the folder
175
- if parent_folder and os.path.exists(parent_folder):
176
- img_name_list = os.listdir(parent_folder)
177
- img_name_list = [img for img in img_name_list if img.endswith(('.jpg', '.png'))]
178
-
179
- if len(img_name_list) < 2:
180
- st.warning("Please provide at least two images for mosaic generation.")
181
- else:
182
- # User selects how many images to use
183
- if len(img_name_list) > 2:
184
- num_images = st.slider("Select the number of images to use for the mosaic:", min_value=2, max_value=len(img_name_list), value=2)
185
- elif len(img_name_list) == 2:
186
- num_images = 2
187
- img_name_list = img_name_list[:num_images]
188
-
189
- if st.button("Generate Mosaic"):
190
- obj = GenerateMosaic(parent_folder=parent_folder, img_name_list=img_name_list)
191
- obj.mosaic()
192
- else:
193
- st.warning("Please enter a valid folder path.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import cv2
4
+ import numpy as np
5
+ from ransac import *
6
+ from match_features import *
7
+ from scipy import optimize
8
+ from optimize_fcn import *
9
+ from PIL import Image
10
+ import tempfile
11
+
12
+ class GenerateMosaic:
13
+
14
+ def __init__(self, image_files):
15
+ self.img_all = {}
16
+ self.image_files = image_files # List of uploaded file objects
17
+ self.middle_id = int(np.floor(len(image_files) / 2))
18
+ # Create a temporary directory to store uploaded images
19
+ self.temp_dir = tempfile.mkdtemp()
20
+ self.img_paths = self._save_uploaded_images()
21
+
22
+ def _save_uploaded_images(self):
23
+ """Save uploaded images to temp directory and return their paths"""
24
+ img_paths = []
25
+ for i, img_file in enumerate(self.image_files):
26
+ img_path = os.path.join(self.temp_dir, f"image_{i}.jpg")
27
+ with open(img_path, "wb") as f:
28
+ f.write(img_file.getbuffer())
29
+ img_paths.append(img_path)
30
+ return img_paths
31
+
32
+ def mosaic(self):
33
+ H_all = {}
34
+ for i in range(len(self.img_paths) - 1):
35
+ st.write(f"Processing image {i+1} & image {i+2}")
36
+
37
+ key = f'H{i}{i + 1}'
38
+
39
+ img_1_path = self.img_paths[i]
40
+ img_2_path = self.img_paths[i + 1]
41
+
42
+ col1, gap, col2, a, b, c, v, f, d = st.columns(9)
43
+
44
+ with col1:
45
+ st.image(img_1_path, width=200)
46
+
47
+ with col2:
48
+ st.image(img_2_path, width=200)
49
+
50
+ # Get SIFT descriptors
51
+ siftmatch_obj = SiftMatching(img_1_path, img_2_path, results_fldr=self.temp_dir, nfeatures=2000, gamma=0.6)
52
+ correspondence = siftmatch_obj.run()
53
+
54
+ # Run RANSAC to remove outliers
55
+ ransac_obj = RANSAC()
56
+ inliers_cnt, inliers, outliers, sample_pts, final_H = ransac_obj.run_ransac(correspondence)
57
+
58
+ # Draw inliers and outliers
59
+ result_path = os.path.join(siftmatch_obj.result_fldr, f'{siftmatch_obj.prefix}_inliers.jpg')
60
+ ransac_obj.draw_lines(np.concatenate((inliers, sample_pts), axis=0), siftmatch_obj.img_1_bgr,
61
+ siftmatch_obj.img_2_bgr, result_path,
62
+ line_color=RANSAC._GREEN, pt_color=[0, 0, 0])
63
+
64
+ result_path = os.path.join(siftmatch_obj.result_fldr, f'{siftmatch_obj.prefix}_outliers.jpg')
65
+ ransac_obj.draw_lines(outliers, siftmatch_obj.img_1_bgr, siftmatch_obj.img_2_bgr, result_path,
66
+ line_color=RANSAC._RED, pt_color=[0, 0, 0])
67
+
68
+ # Optimize the homography
69
+ x = np.concatenate((inliers, sample_pts), axis=0)
70
+ opt_obj = OptimizeFunction(fun=fun_LM_homography, x0=final_H.flatten(), jac=jac_LM_homography,
71
+ args=(x[:, 0:2], x[:, 2:]))
72
+ LM_sol = opt_obj.levenberg_marquardt(delta_thresh=1e-24, tau=0.8)
73
+
74
+ H_all[key] = LM_sol.x.reshape(3, 3)
75
+ H_all[key] = H_all[key] / H_all[key][-1, -1]
76
+
77
+ H_all = self.compute_H_wrt_middle_img(H_all)
78
+ self.stitch(H_all, siftmatch_obj.result_fldr)
79
+
80
+ def stitch(self, H_all, result_fldr):
81
+ canvas_img, mask, offset = self.get_blank_canvas(H_all)
82
+
83
+ for i, img_path in enumerate(self.img_paths):
84
+ key = f"H{i}{self.middle_id}"
85
+ H = H_all[key]
86
+
87
+ img_rgb = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
88
+
89
+ canvas_img = fit_image_in_target_space(img_rgb, canvas_img, mask, np.linalg.inv(H),
90
+ offset=offset)
91
+ mask[np.where(canvas_img)[0:2]] = 0
92
+
93
+ result_path = os.path.join(result_fldr, f'panorama_{i}.jpg')
94
+ cv2.imwrite(result_path, canvas_img[:, :, (2, 1, 0)])
95
+
96
+ # Show the final panorama
97
+ final_img_path = os.path.join(result_fldr, f'panorama_{len(self.img_paths)-1}.jpg')
98
+ if os.path.exists(final_img_path):
99
+ img = Image.open(final_img_path)
100
+ st.image(img, caption="Final Panorama", use_column_width=True)
101
+
102
+ # Add download button for the final image
103
+ with open(final_img_path, "rb") as file:
104
+ btn = st.download_button(
105
+ label="Download Panorama",
106
+ data=file,
107
+ file_name="panorama.jpg",
108
+ mime="image/jpeg"
109
+ )
110
+
111
+ def get_blank_canvas(self, H_all):
112
+ img_path = self.img_paths[0]
113
+ img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
114
+
115
+ img_h, img_w, _ = img.shape
116
+
117
+ min_crd_canvas = np.array([np.inf, np.inf, np.inf])
118
+ max_crd_canvas = np.array([-np.inf, -np.inf, -np.inf])
119
+
120
+ for i in range(len(self.img_paths)):
121
+ key = f"H{i}{self.middle_id}"
122
+ H = H_all[key]
123
+ min_crd, max_crd = self.compute_extent(H, img_w, img_h)
124
+
125
+ min_crd_canvas = np.minimum(min_crd, min_crd_canvas)
126
+ max_crd_canvas = np.maximum(max_crd, max_crd_canvas)
127
+
128
+ width_canvas = np.ceil(max_crd_canvas - min_crd_canvas)[0] + 1
129
+ height_canvas = np.ceil(max_crd_canvas - min_crd_canvas)[1] + 1
130
+
131
+ canvas_img = np.zeros((int(height_canvas), int(width_canvas), 3), dtype=np.int64)
132
+
133
+ offset = min_crd_canvas.astype(np.int64)
134
+ offset[2] = 0
135
+
136
+ mask = np.ones((int(height_canvas), int(width_canvas)))
137
+
138
+ return canvas_img, mask, offset
139
+
140
+ def compute_extent(self, H, img_w, img_h):
141
+ corners_img = np.array([[0, 0], [img_w, 0], [img_w, img_h], [0, img_h]])
142
+
143
+ t_one = np.ones((corners_img.shape[0], 1))
144
+ t_out_pts = np.concatenate((corners_img, t_one), axis=1)
145
+ canvas_crd_corners = np.matmul(H, t_out_pts.T)
146
+ canvas_crd_corners = canvas_crd_corners / canvas_crd_corners[-1, :]
147
+
148
+ min_crd = np.amin(canvas_crd_corners.T, axis=0)
149
+ max_crd = np.amax(canvas_crd_corners.T, axis=0)
150
+ return min_crd, max_crd
151
+
152
+ def compute_H_wrt_middle_img(self, H_all):
153
+ num_imgs = len(H_all) + 1
154
+ key = f"H{self.middle_id}{self.middle_id}"
155
+ H_all[key] = np.eye(3)
156
+
157
+ for i in range(0, self.middle_id):
158
+ key = f"H{i}{self.middle_id}"
159
+ j = i
160
+ temp = np.eye(3)
161
+ while j < self.middle_id:
162
+ key_t = f"H{j}{j + 1}"
163
+ temp = np.matmul(H_all[key_t], temp)
164
+ j += 1
165
+
166
+ H_all[key] = temp
167
+
168
+ for i in range(self.middle_id + 1, num_imgs):
169
+ key = f"H{i}{self.middle_id}"
170
+
171
+ temp = np.eye(3)
172
+ j = i - 1
173
+
174
+ while j >= self.middle_id:
175
+ key_t = f"H{j}{j + 1}"
176
+ temp = np.matmul(np.linalg.inv(H_all[key_t]), temp)
177
+ j -= 1
178
+
179
+ H_all[key] = temp
180
+
181
+ return H_all
182
+
183
+ # Streamlit Interface
184
+ st.title("Mosaic Generator")
185
+
186
+ # User Input: Direct Image Upload
187
+ uploaded_files = st.file_uploader("Upload images for mosaic generation",
188
+ type=["jpg", "jpeg", "png"],
189
+ accept_multiple_files=True)
190
+
191
+ if uploaded_files:
192
+ if len(uploaded_files) < 2:
193
+ st.warning("Please upload at least two images for mosaic generation.")
194
+ else:
195
+ st.success(f"{len(uploaded_files)} images uploaded!")
196
+
197
+ # Display thumbnails of uploaded images
198
+ cols = st.columns(min(5, len(uploaded_files)))
199
+ for i, img_file in enumerate(uploaded_files):
200
+ cols[i % len(cols)].image(img_file, caption=f"Image {i+1}", width=150)
201
+
202
+ if st.button("Generate Mosaic"):
203
+ with st.spinner("Processing images and generating mosaic..."):
204
+ obj = GenerateMosaic(image_files=uploaded_files)
205
+ obj.mosaic()
206
+ else:
207
+ st.info("Please upload at least two images to generate a mosaic.")
208
+
209
+ st.markdown("""
210
+ ### Tips for best results:
211
+ 1. Upload images in the order they should be stitched (left to right or right to left)
212
+ 2. For best results, use images taken from the same position with rotation only
213
+ 3. Higher resolution images will produce better mosaics but take longer to process
214
+ """)