Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Udayraj123
GitHub Repository: Udayraj123/OMRChecker
Path: blob/master/src/processors/CropOnMarkers.py
298 views
1
import os
2
3
import cv2
4
import numpy as np
5
6
from src.constants.image_processing import (
7
DEFAULT_BLACK_COLOR,
8
DEFAULT_BORDER_REMOVE,
9
DEFAULT_GAUSSIAN_BLUR_PARAMS_MARKER,
10
DEFAULT_LINE_WIDTH,
11
DEFAULT_NORMALIZE_PARAMS,
12
DEFAULT_WHITE_COLOR,
13
ERODE_RECT_COLOR,
14
EROSION_PARAMS,
15
MARKER_RECTANGLE_COLOR,
16
NORMAL_RECT_COLOR,
17
QUADRANT_DIVISION,
18
)
19
from src.logger import logger
20
from src.processors.interfaces.ImagePreprocessor import ImagePreprocessor
21
from src.utils.image import ImageUtils
22
from src.utils.interaction import InteractionUtils
23
24
25
class CropOnMarkers(ImagePreprocessor):
26
def __init__(self, *args, **kwargs):
27
super().__init__(*args, **kwargs)
28
config = self.tuning_config
29
marker_ops = self.options
30
self.threshold_circles = []
31
# img_utils = ImageUtils()
32
33
# options with defaults
34
self.marker_path = os.path.join(
35
self.relative_dir, marker_ops.get("relativePath", "omr_marker.jpg")
36
)
37
self.min_matching_threshold = marker_ops.get("min_matching_threshold", 0.3)
38
self.max_matching_variation = marker_ops.get("max_matching_variation", 0.41)
39
self.marker_rescale_range = tuple(
40
int(r) for r in marker_ops.get("marker_rescale_range", (35, 100))
41
)
42
self.marker_rescale_steps = int(marker_ops.get("marker_rescale_steps", 10))
43
self.apply_erode_subtract = marker_ops.get("apply_erode_subtract", True)
44
self.marker = self.load_marker(marker_ops, config)
45
46
def __str__(self):
47
return self.marker_path
48
49
def exclude_files(self):
50
return [self.marker_path]
51
52
def apply_filter(self, image, file_path):
53
config = self.tuning_config
54
image_instance_ops = self.image_instance_ops
55
image_eroded_sub = ImageUtils.normalize_util(
56
image
57
if self.apply_erode_subtract
58
else (
59
image
60
- cv2.erode(
61
image,
62
kernel=np.ones(EROSION_PARAMS["kernel_size"]),
63
iterations=EROSION_PARAMS["iterations"],
64
)
65
)
66
)
67
# Quads on warped image
68
quads = {}
69
h1, w1 = image_eroded_sub.shape[:2]
70
midh, midw = (
71
h1 // QUADRANT_DIVISION["height_factor"],
72
w1 // QUADRANT_DIVISION["width_factor"],
73
)
74
origins = [[0, 0], [midw, 0], [0, midh], [midw, midh]]
75
quads[0] = image_eroded_sub[0:midh, 0:midw]
76
quads[1] = image_eroded_sub[0:midh, midw:w1]
77
quads[2] = image_eroded_sub[midh:h1, 0:midw]
78
quads[3] = image_eroded_sub[midh:h1, midw:w1]
79
80
# Draw Quadlines
81
image_eroded_sub[:, midw : midw + 2] = DEFAULT_WHITE_COLOR
82
image_eroded_sub[midh : midh + 2, :] = DEFAULT_WHITE_COLOR
83
84
best_scale, all_max_t = self.getBestMatch(image_eroded_sub)
85
if best_scale is None:
86
if config.outputs.show_image_level >= 1:
87
InteractionUtils.show("Quads", image_eroded_sub, config=config)
88
return None
89
90
optimal_marker = ImageUtils.resize_util_h(
91
self.marker, u_height=int(self.marker.shape[0] * best_scale)
92
)
93
_h, w = optimal_marker.shape[:2]
94
centres = []
95
sum_t, max_t = 0, 0
96
quarter_match_log = "Matching Marker: "
97
for k in range(0, 4):
98
res = cv2.matchTemplate(quads[k], optimal_marker, cv2.TM_CCOEFF_NORMED)
99
max_t = res.max()
100
quarter_match_log += f"Quarter{str(k + 1)}: {str(round(max_t, 3))}\t"
101
if (
102
max_t < self.min_matching_threshold
103
or abs(all_max_t - max_t) >= self.max_matching_variation
104
):
105
logger.error(
106
file_path,
107
"\nError: No circle found in Quad",
108
k + 1,
109
"\n\t min_matching_threshold",
110
self.min_matching_threshold,
111
"\t max_matching_variation",
112
self.max_matching_variation,
113
"\t max_t",
114
max_t,
115
"\t all_max_t",
116
all_max_t,
117
)
118
if config.outputs.show_image_level >= 1:
119
InteractionUtils.show(
120
f"No markers: {file_path}",
121
image_eroded_sub,
122
0,
123
config=config,
124
)
125
InteractionUtils.show(
126
f"res_Q{str(k + 1)} ({str(max_t)})",
127
res,
128
1,
129
config=config,
130
)
131
return None
132
133
pt = np.argwhere(res == max_t)[0]
134
pt = [pt[1], pt[0]]
135
pt[0] += origins[k][0]
136
pt[1] += origins[k][1]
137
# print(">>",pt)
138
image = cv2.rectangle(
139
image,
140
tuple(pt),
141
(pt[0] + w, pt[1] + _h),
142
MARKER_RECTANGLE_COLOR,
143
DEFAULT_LINE_WIDTH,
144
)
145
# display:
146
image_eroded_sub = cv2.rectangle(
147
image_eroded_sub,
148
tuple(pt),
149
(pt[0] + w, pt[1] + _h),
150
ERODE_RECT_COLOR if self.apply_erode_subtract else NORMAL_RECT_COLOR,
151
4,
152
)
153
centres.append([pt[0] + w / 2, pt[1] + _h / 2])
154
sum_t += max_t
155
156
logger.info(quarter_match_log)
157
logger.info(f"Optimal Scale: {best_scale}")
158
# analysis data
159
self.threshold_circles.append(sum_t / 4)
160
161
image = ImageUtils.four_point_transform(image, np.array(centres))
162
# appendSaveImg(1,image_eroded_sub)
163
# appendSaveImg(1,image_norm)
164
165
image_instance_ops.append_save_img(2, image_eroded_sub)
166
# Debugging image -
167
# res = cv2.matchTemplate(image_eroded_sub,optimal_marker,cv2.TM_CCOEFF_NORMED)
168
# res[ : , midw:midw+2] = 255
169
# res[ midh:midh+2, : ] = 255
170
# show("Markers Matching",res)
171
if config.outputs.show_image_level >= 2 and config.outputs.show_image_level < 4:
172
image_eroded_sub = ImageUtils.resize_util_h(
173
image_eroded_sub, image.shape[0]
174
)
175
image_eroded_sub[:, -DEFAULT_BORDER_REMOVE:] = DEFAULT_BLACK_COLOR
176
h_stack = np.hstack((image_eroded_sub, image))
177
InteractionUtils.show(
178
f"Warped: {file_path}",
179
ImageUtils.resize_util(
180
h_stack, int(config.dimensions.display_width * 1.6)
181
),
182
0,
183
0,
184
[0, 0],
185
config=config,
186
)
187
# iterations : Tuned to 2.
188
# image_eroded_sub = image_norm - cv2.erode(image_norm, kernel=np.ones((5,5)),iterations=2)
189
return image
190
191
def load_marker(self, marker_ops, config):
192
if not os.path.exists(self.marker_path):
193
logger.error(
194
"Marker not found at path provided in template:",
195
self.marker_path,
196
)
197
exit(31)
198
199
marker = cv2.imread(self.marker_path, cv2.IMREAD_GRAYSCALE)
200
201
if "sheetToMarkerWidthRatio" in marker_ops:
202
marker = ImageUtils.resize_util(
203
marker,
204
config.dimensions.processing_width
205
/ int(marker_ops["sheetToMarkerWidthRatio"]),
206
)
207
marker = cv2.GaussianBlur(
208
marker,
209
DEFAULT_GAUSSIAN_BLUR_PARAMS_MARKER["kernel_size"],
210
DEFAULT_GAUSSIAN_BLUR_PARAMS_MARKER["sigma_x"],
211
)
212
marker = cv2.normalize(
213
marker,
214
None,
215
alpha=DEFAULT_NORMALIZE_PARAMS["alpha"],
216
beta=DEFAULT_NORMALIZE_PARAMS["beta"],
217
norm_type=cv2.NORM_MINMAX,
218
)
219
220
if self.apply_erode_subtract:
221
marker -= cv2.erode(
222
marker,
223
kernel=np.ones(EROSION_PARAMS["kernel_size"]),
224
iterations=EROSION_PARAMS["iterations"],
225
)
226
227
return marker
228
229
# Resizing the marker within scaleRange at rate of descent_per_step to
230
# find the best match.
231
def getBestMatch(self, image_eroded_sub):
232
config = self.tuning_config
233
descent_per_step = (
234
self.marker_rescale_range[1] - self.marker_rescale_range[0]
235
) // self.marker_rescale_steps
236
_h, _w = self.marker.shape[:2]
237
res, best_scale = None, None
238
all_max_t = 0
239
240
for r0 in np.arange(
241
self.marker_rescale_range[1],
242
self.marker_rescale_range[0],
243
-1 * descent_per_step,
244
): # reverse order
245
s = float(r0 * 1 / 100)
246
if s == 0.0:
247
continue
248
rescaled_marker = ImageUtils.resize_util_h(
249
self.marker, u_height=int(_h * s)
250
)
251
# res is the black image with white dots
252
res = cv2.matchTemplate(
253
image_eroded_sub, rescaled_marker, cv2.TM_CCOEFF_NORMED
254
)
255
256
max_t = res.max()
257
if all_max_t < max_t:
258
# print('Scale: '+str(s)+', Circle Match: '+str(round(max_t*100,2))+'%')
259
best_scale, all_max_t = s, max_t
260
261
if all_max_t < self.min_matching_threshold:
262
logger.warning(
263
"\tTemplate matching too low! Consider rechecking preProcessors applied before this."
264
)
265
if config.outputs.show_image_level >= 1:
266
InteractionUtils.show("res", res, 1, 0, config=config)
267
268
if best_scale is None:
269
logger.warning(
270
"No matchings for given scaleRange:", self.marker_rescale_range
271
)
272
return best_scale, all_max_t
273
274