-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
314 lines (249 loc) · 10.9 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
import cv2
from enum import Enum
import logging
import threading
import time
import copy
import numpy
from pattern_type import PatternType
from pattern_finder import PatternFinder
from camera_calibrator import CameraCalibrator
from drawing_helpers import draw_vertical_line, draw_corners, draw_grid
from coordinate_mapper import find_checkerboard_corners, CoordinateMapper
class Mode(Enum):
Initial = 0,
Calibration = 1,
Calibrated = 2
pattern_dims = (5, 8)
pattern_type = PatternType.Checkerboard
calibrator = CameraCalibrator(pattern_type, pattern_dims, None)
mode = Mode.Initial
crop_scale = 0.0
def crop_frame(frame, crop_corners):
target_frame = frame[
crop_corners[0][1]:crop_corners[1][1],
crop_corners[0][0]:crop_corners[1][0]
]
return target_frame
def configure_windows():
cv2.namedWindow('distorted')
cv2.namedWindow('undistorted')
cv2.createTrackbar('alpha', 'undistorted', 0, 100, change_alpha)
cv2.setTrackbarPos('alpha', 'undistorted', int(calibrator.alpha * 100))
cv2.createTrackbar('crop', 'undistorted', 0, 100, change_crop)
def start_camera():
logging.debug("Starting camera")
cap = cv2.VideoCapture(cv2.CAP_XIAPI)
# cap = cv2.VideoCapture(0)
if not cap.isOpened():
raise RuntimeError("Could not open camera")
logging.debug("Camera started")
return cap
def main():
global mode
logging.basicConfig(format='[%(asctime)s] [%(threadName)13s] %(levelname)7s: %(message)s', level=logging.DEBUG)
#Start camera
cap = start_camera()
#Configure windows
configure_windows()
#Setup pattern finder
pattern_finder = PatternFinder(pattern_type, pattern_dims)
pattern_finder.start()
#Setup calibration variables
pattern_found = False
last_calibration_sample_time = None
#Setup coordinate mapper
coordinate_mapper = CoordinateMapper(
checkerboard_distance=0.20,
checkerboard_width=0.279,
checkerboard_height=0.159
)
#Setup display flags
show_coordinate_grid = False
while True:
#Try to get a frame from camera
success, distorted_frame_clean = cap.read()
if not success:
logging.warning("Could not retrieve frame from camera")
continue # Let's just try again
#Make original frame read-only
distorted_frame_clean.flags.writeable = False
image_size = tuple(distorted_frame_clean.shape[0:2][::-1])
calibrator.image_size = image_size
#Create copy that we can draw on
distorted_frame = copy.deepcopy(distorted_frame_clean)
"""
#Draw a grid on distorted frame
draw_grid(
frame=distorted_frame,
x_range=(0, image_size[0], image_size[0] // 10),
y_range=(0, image_size[1], image_size[1] // 10),
color=(0, 0, 0), thickness=1, line_type=cv2.LINE_AA
)
"""
undistorted_frame_clean = None
undistorted_frame = None
if mode == Mode.Calibrated:
#If calibration has been loaded, show the
undistorted_frame_clean = cv2.remap(distorted_frame_clean, calibrator.map_x, calibrator.map_y,
cv2.INTER_LINEAR)
undistorted_frame_clean.flags.writeable = False
undistorted_frame = cv2.remap(distorted_frame, calibrator.map_x, calibrator.map_y, cv2.INTER_LINEAR)
if not pattern_finder.recognition_in_progress:
#Get the results of last recognition
pattern_found = pattern_finder.pattern_found
pattern_points = pattern_finder.pattern_points
#Start a new recognition
#Calculate the corners of cropped image according to crop_scale
crop_corners = (
(int(image_size[0]/2*crop_scale), int(image_size[1]/2*crop_scale)),
(int(image_size[0] - image_size[0]/2*crop_scale), int(image_size[1] - image_size[1]/2*crop_scale))
)
if pattern_points is not None:
pattern_points += crop_corners[0]
#If calibration has been loaded, let's find pattern from calibrated image
if mode == Mode.Calibrated:
target_frame = undistorted_frame
target_frame_clean = undistorted_frame_clean
else:
target_frame = distorted_frame
target_frame_clean = distorted_frame_clean
#Show crop on target frame with a rectangle, and crop the clean frame
cv2.rectangle(target_frame, crop_corners[0], crop_corners[1], (255, 0, 0), 1, cv2.LINE_AA)
cropped_frame = crop_frame(target_frame_clean, crop_corners)
#Start a new pattern Recognition
pattern_finder.start_pattern_recognition(cropped_frame)
if pattern_found:
if mode == Mode.Calibration:
if last_calibration_sample_time is None or time.time() - last_calibration_sample_time > 0.2:
last_calibration_sample_time = time.time()
calibrator.add_sample(pattern_points)
show_ui_taking_sample(distorted_frame)
if calibrator.number_of_samples > 100:
show_ui_calibrating(distorted_frame)
calibrator.calibrate()
calibrator.calculate_new_camera_matrix()
calibrator.generate_maps()
logging.info("Calibration Finished")
logging.info("Calibration error-rate: {}".format(calibrator.accuracy))
mode = Mode.Calibrated
calibrator.save_results("AutoSave")
continue #Needed for initialising "undistorted_frame"
# If view is calibrated, then draw chessboard on undistorted frame, otherwise use the original/distorted one.
if mode == Mode.Calibrated:
target_frame = undistorted_frame
else:
target_frame = distorted_frame
cv2.drawChessboardCorners(target_frame, pattern_dims, pattern_points, pattern_found)
# Find four corners of the board and use then to calculate mapping constants
corners = find_checkerboard_corners(pattern_points, pattern_dims)
# TODO: This should be done somehow differently?
coordinate_mapper.image_dims = (distorted_frame_clean.shape[1], distorted_frame_clean.shape[0])
coordinate_mapper.calculate_constants(corners)
if show_coordinate_grid:
coordinate_mapper.draw_intersection_lines(target_frame, corners)
coordinate_mapper.draw_grid(target_frame)
draw_corners(target_frame, corners)
# Vertical line in the center
draw_vertical_line(target_frame, target_frame.shape[1] // 2, (100, 0, 0), 1,
cv2.LINE_AA)
logging.info("Constants {}".format(coordinate_mapper.constants))
# Display the distorted frame (original with additional lines)
distorted_frame = cv2.resize(distorted_frame, (0, 0), fx=0.5, fy=0.5)
cv2.imshow('distorted', distorted_frame)
if mode == Mode.Calibrated:
undistorted_frame = cv2.resize(undistorted_frame, (0, 0), fx=0.5, fy=0.5)
cv2.imshow('undistorted', undistorted_frame)
# TODO: Why does it work strange?
#if calibrator.roi is not None:
# x, y, w, h = [elem // 2 for elem in calibrator.roi]
# if w != 0 and h != 0:
# cropped = undistorted_frame[y:y + h, x:x + w]
# cv2.imshow('undistorted_cropped', cropped)
key_no = cv2.waitKey(30) & 0xFF
if key_no == 255:
# no key was pressed:
pass
elif key_no == ord('q'):
logging.info("Quitting...")
break
elif key_no == ord('g'):
show_coordinate_grid = not show_coordinate_grid
elif key_no == ord('c'):
logging.info("Calibration started")
calibrator.clear()
mode = Mode.Calibration
elif key_no == ord('s'):
calibrator.save_results("ManualSave")
logging.info("Calibration results saved")
elif key_no == ord('l'):
timestamp = input("Type timestamp to load: ")
try:
calibrator.load_results(timestamp)
except IOError:
logging.exception("Could not load all calibration files.")
else:
mode = Mode.Calibrated
calibrator.calculate_new_camera_matrix()
calibrator.generate_maps()
cv2.setTrackbarPos('alpha', 'undistorted', int(calibrator.alpha * 100))
logging.info("Calibration results loaded")
elif key_no == ord('p'):
calibrator.plot()
else:
print("Press:\n"
"\t'q' to quit\n"
"\t'c' to start calibration\n"
"\t'g' to toggle grid\n"
"\t's' to save calibration results\n"
"\t'l' to load calibration results\n"
)
## End of the main loop ##
cap.release()
cv2.destroyAllWindows()
last_change = None
target_alpha = None
def timing_function():
global last_change
while True:
time.sleep(0.1)
if last_change is not None and time.time()-last_change > 1.0:
calibrator.calculate_new_camera_matrix(target_alpha)
calibrator.generate_maps()
last_change = None
timing_thread = threading.Thread(target=timing_function)
timing_thread.daemon = True
timing_thread.start()
def change_alpha(value):
global target_alpha, last_change
if mode != Mode.Calibrated:
return
target_alpha = value / 100
last_change = time.time()
def change_crop(value):
global crop_scale
crop_scale = value/100
def show_ui_calibrating(frame):
cv2.rectangle(
frame,
(0, 0),
(frame.shape[1], frame.shape[0]),
(255, 255, 255),
-1
)
cv2.putText(frame, "CALIBRATING...", (frame.shape[1]//3, frame.shape[0]//2),
cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 0), 2, cv2.LINE_AA)
frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5)
cv2.imshow('distorted', frame)
cv2.waitKey(20)
def show_ui_taking_sample(frame):
color = (255, 255, 255)
cv2.circle(frame, (frame.shape[1]//10, frame.shape[0]//10), frame.shape[0]//25, color, -1, cv2.LINE_AA)
cv2.circle(frame, (frame.shape[1]//10*9, frame.shape[0]//10), frame.shape[0]//25, color, -1, cv2.LINE_AA)
cv2.circle(frame, (frame.shape[1]//10, frame.shape[0]//10*9), frame.shape[0]//25, color, -1, cv2.LINE_AA)
cv2.circle(frame, (frame.shape[1]//10*9, frame.shape[0]//10*9), frame.shape[0]//20, color, -1, cv2.LINE_AA)
if __name__ == "__main__":
try:
main()
except Exception:
logging.exception("Uncaught exception from main():")