class App: window_name = 'Speedometer' spaces = 20 training_corners_mod = 2 def __init__(self, video_src=0, pos_x=0, pos_y=0, quality=0.3, damping=20, speed_multi=2, save='', multiprocessed=False, epochs=1, training_accuracy=20, training_length=40, max_level = 3, save_net='', load_net='', max_net_err=0): """ :param video_src: video source :param pos_x: initial x translation of capture window :param pos_y: initial y translation of capture window :param quality: quality used by goodFeaturesToTrack :param damping: speed = speeds[-damping:]/damping :param speed_multi: speed value multiplicator (only without neural net) :param save: if specified output file will be saved under this param :param multiprocessed: if true neural net training will be done in separated thread :param epochs: number of epochs for neural network :param training_accuracy: number of frames which will be training set for neural network :return: App instance """ # Tracks related attributes tracks_number = 100 self.track_len = 10 self.tracks_count = 0 self.detect_interval = 5 self.tracks = deque([deque(maxlen=tracks_number)]) ########################### # Speed and distance measure realted attributes self.speed = 0 self.last_speeds = deque([0], maxlen=damping) self.speed_multi = speed_multi self.distance = 0 self.multiplier = 0.0001 ################################### self.feature_params = { 'maxCorners': 10, 'qualityLevel': quality, 'minDistance': 7, 'blockSize': 7 } self.lk_params = { 'winSize': (21, 21), 'maxLevel': max_level, 'criteria': (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)} self.app_params = { 'speed_multi': self.multiplier * speed_multi, 'tracks_number': tracks_number, } # Frames related attributes self.frame_idx = 1 self.prev_gray = None self.cam = create_capture(video_src) _, frame = self.cam.read() fps = self.cam.get(cv2.CAP_PROP_FPS) self.frame_duration = 1 / fps if fps else 0.04 self.callbacks = defaultdict(lambda: lambda val, mod: val + mod, { 'winSize': lambda val, mod: (val[0]+mod, val[1]+mod), 'qualityLevel': lambda val, mod: np.abs(val + mod * 0.1), 'speed_multi': lambda val, mod: np.abs(val + mod * self.multiplier), 'criteria': lambda val, mod: (val[0], val[1] + mod, val[2] + mod * 0.01) }) ############################ # Interface position related attributes height, width, _ = frame.shape params_len = (len(self.feature_params) + len(self.lk_params) + len(self.app_params)) self.start_x = 4 * width/10 + pos_x self.stop_x = 5 * width/10 + pos_x self.start_y = 19 * height/20 + pos_y self.stop_y = height + pos_y self.frame_height = height self.frame_width = width self.middle_x = width / 2 self.interface = { 'left': { 'right_border': self.spaces * 3, 'top_border': self.frame_height - self.spaces * params_len, 'clicked': False, # Depends on not feature or lk params in interface 'index_mod': 2 }, 'right': { 'left_border': self.frame_width - self.spaces * 5, 'top_border': self.frame_height - self.spaces * 3, 'clicked': False } } self.clicked = False ############################################ # Neural network related attributes self.network_tuple = (2, 3, 1) self.training = None if load_net: self.network = NeuralNetwork(epochs=epochs, load=load_net, save=save_net, scale=width) else: self.network = None self.multiprocessed = multiprocessed self.epochs = epochs self.save_net = save_net self.load_net = load_net self.samples = training_accuracy self.training_frames = training_length self.max_net_error = max_net_err #################################### # Video capture related attributes self.out = None if save: self.out = create_writer(save, (frame.shape[1], frame.shape[0]), fps) ################################### def _on_mouse(self, event, x, y, *_): if (x < self.interface['left']['right_border'] and y > self.interface['left']['top_border']): self._interface('left', event, x, y) elif (x > self.interface['right']['left_border'] and y > self.interface['right']['top_border']): self._interface('right', event, y) elif event == cv2.EVENT_LBUTTONDOWN: # Begin capturing new feature detection zone self.clicked = True self.start_x = self.stop_x = x self.start_y = self.stop_y = y self.tracks = deque( [deque(maxlen=self.app_params['tracks_number'])]) ############################################## elif event == cv2.EVENT_LBUTTONUP: # End capturing new feature detection zone self.clicked = False ########################################### elif self.clicked and event == cv2.EVENT_MOUSEMOVE: # Capturing new feature detection zone self.stop_x = x self.stop_y = y ###################################### def _interface(self, which, event, *args): if event == cv2.EVENT_LBUTTONDOWN: self.interface[which]['clicked'] = True elif (event == cv2.EVENT_LBUTTONUP and self.interface[which]['clicked']): method = getattr(self, '_%s_interface' % which) method(*args) def _left_interface(self, x, y): index_x = x // self.spaces index_y = ((self.frame_height - y) // self.spaces - self.interface['left']['index_mod']) self.interface['left']['clicked'] = False key = (self.lk_params.keys() + self.feature_params.keys())[ index_y] if index_y == -1: temp = self.app_params key = 'speed_multi' elif key in self.lk_params: temp = self.lk_params else: temp = self.feature_params if index_x == 1: temp[key] = self.callbacks[key](temp[key], 1) elif index_x == 2: temp[key] = self.callbacks[key](temp[key], -1) def _right_interface(self, y): index_y = (self.frame_height - y) // self.spaces if index_y == 1: self.network = NeuralNetwork(self.network_tuple, epochs=self.epochs, save=self.save_net, scale=self.frame_height, max_error=self.max_net_error) self.training = self.training_frames # use less features per zone during training self.feature_params['maxCorners'] //= self.training_corners_mod self.app_params['tracks_number'] //= self.training_corners_mod elif index_y == 2: self.distance = 0 def run(self, skip=1, stop=None, tests=False): self.start = skip if stop is None: stop = self.cam.get(cv2.CAP_PROP_FRAME_COUNT) else: stop -= skip while skip: _ = self.cam.read() skip -= 1 if not tests: cv2.namedWindow(self.window_name) cv2.setMouseCallback(self.window_name, self._on_mouse) while self.frame_idx < stop: _, frame = self.cam.read() frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) vis = frame.copy() if self.tracks: sum_r = self._optical_flow(frame_gray, self.prev_gray) self._measure_speed(sum_r) if self.training: self.training -= 1 elif not self.training and self.training is not None: if self.multiprocessed: if self.network.is_done(): self.training = None self._restore_limits_after_training() elif not self.network.training.is_alive(): self.network.training.start() else: self.training = None self._restore_limits_after_training() self.network.train() if not self.frame_idx % self.detect_interval: self._get_new_tracks(frame_gray) self.frame_idx += 1 self.prev_gray = frame_gray if not tests: self._show_and_save(vis) key = cv2.waitKey(1) if 0xFF & key == 27: # quit on esc key self._clean_up() break elif key == 32: # pause on space cv2.waitKey() self._clean_up() return self.distance def _restore_limits_after_training(self): self.feature_params['maxCorners'] *= self.training_corners_mod self.app_params['tracks_number'] *= self.training_corners_mod def _optical_flow(self, current_frame, previous_frame): sum = 0 expected_res = 0 self.tracks_count = 0 for i, track in enumerate(self.tracks): if track: p0 = np.reshape([tr[-1] for tr in track], (-1, 1, 2)) p1 = cv2.calcOpticalFlowPyrLK(previous_frame, current_frame, p0, None, **self.lk_params)[0] p0r = cv2.calcOpticalFlowPyrLK(current_frame, previous_frame, p1, None, **self.lk_params)[0] d = abs(p0-p0r).reshape(-1, 2).max(-1) good = d < 1 new_tracks = deque(maxlen=self.app_params['tracks_number']) sum_r = 0 for tr, (x, y), good_flag in zip(track, p1.reshape(-1, 2), good): if not good_flag: continue tr.append((x, y)) new_tracks.append(tr) # pixel shift radius = tr[-1][-1] - tr[-2][-1] if self.training and not i: self.network.add_sample(y=tr[-1][-1], dy=radius, result=radius) elif self.training: self.network.add_sample(y=tr[-1][-1], dy=radius, result=expected_res) if self.training is None and self.network: # If neural network is ready use it sum_r += self.network.result(y=tr[-1][-1], dy=radius) else: sum_r += radius if self.training and not i: # Result used to teaching neural network expected_res = sum_r / len(track) self.tracks[i] = new_tracks self.tracks_count += len(new_tracks) if new_tracks: sum += (self.app_params['speed_multi'] * sum_r / len(new_tracks)) return sum / len(self.tracks) def _measure_speed(self, sum_r): if self.tracks: # measure speed based on frame duration and pixels movement self.last_speeds.append(3.6 * sum_r / self.frame_duration) # speed is calculated as arithmetic average of last speeds self.speed = sum(self.last_speeds)/len(self.last_speeds) # distance self.distance += np.abs(sum_r) def _get_new_tracks(self, frame_gray): if self.training: # If there is active training features capture zone is divided into # small pieces where one on bottom is treated as teacher for the # rest self.tracks = deque( [deque([], maxlen=self.app_params['tracks_number'])] * self.samples) masks = deque() counter = self.samples while counter: # Get sub capture zones step = (self.stop_y - self.start_y) // self.samples stop_y = self.start_y + counter * step start_y = self.start_y + (counter - 1) * step self.t_points = (start_y, stop_y) masks.append(np.zeros_like(frame_gray)) masks[-1][start_y:stop_y, self.start_x:self.stop_x] = 1 counter -= 1 else: self.tracks = deque( [deque([], maxlen=self.app_params['tracks_number'])]) masks = deque([np.zeros_like(frame_gray)]) masks[0][self.start_y:self.stop_y, self.start_x:self.stop_x] = 1 for i, mask in enumerate(masks): # Don't get same tracks for x, y in [np.int32(tr[-1]) for tr in self.tracks[i]]: cv2.circle(mask, (x, y), 5, 0) # Get new features features = cv2.goodFeaturesToTrack(frame_gray, mask=mask, **self.feature_params) if features is not None: # If new features detected add them to the rest for x, y in features.reshape(-1, 2): self.tracks[i].append(deque([(x, y)], maxlen=self.track_len)) def _show_and_save(self, vis): # Output - up left corner draw_str(vis, (self.spaces, self.spaces), 'frame number: %d' % (self.frame_idx + self.start)) draw_str(vis, (self.spaces, self.spaces * 2), 'track count: %d' % self.tracks_count) draw_str(vis, (self.spaces, self.spaces * 3), 'speed: %.2f km/h' % np.abs(self.speed)) draw_str(vis, (self.spaces, self.spaces * 4), 'speed without dump: %.2f km/h' % np.abs(self.last_speeds[-1])) draw_str(vis, (self.spaces, self.spaces * 5), 'average speed: %.2f km/h' % (3.6 * self.distance / (self.frame_idx * self.frame_duration))) draw_str(vis, (self.spaces, self.spaces * 6), 'traveled distance: %.2f m' % self.distance) # Neural network training button - right bottom corner # Reset distance button - right bottom corner, above training draw_str(vis, (self.frame_width - self.spaces, self.frame_height - self.spaces), 'train', 'r') draw_str(vis, (self.frame_width - self.spaces, self.frame_height - self.spaces * 2), 'reset distance', 'r') # Program params - left bottom corner draw_str(vis, (self.spaces, self.frame_height - self.spaces), '+ - speed multi = %s' % (self.app_params['speed_multi'] / self.multiplier)) for i, (key, var) in enumerate(self.lk_params.items() + self.feature_params.items(), self.interface['left']['index_mod']): draw_str(vis, (self.spaces, self.frame_height - i * self.spaces), '+ - %s = %s' % (key, var)) # tracks if self.training is None: for track in self.tracks: for tr in track: cv2.circle(vis, (tr[0][0], tr[0][-1]), 2, (255, 0, 0)) cv2.circle(vis, (tr[-1][0], tr[-1][-1]), 2, (0, 0, 255)) cv2.polylines(vis, [np.int32(tr)], False, (0, 255, 0)) # rectangle where we tracking cv2.rectangle(vis, (self.start_x, self.start_y), (self.stop_x, self.stop_y), (255, 0, 0)) # Training progress indicator if self.training: percentage = 10 * float( self.training_frames - self.training) / self.training_frames draw_str(vis, (self.middle_x - self.spaces, self.spaces), 'Getting samples...', 'm') draw_str(vis, (self.middle_x - self.spaces, self.spaces * 2), '%s %d%%' % ('#' * int(percentage), percentage * 10), 'm') elif self.training is not None: draw_str(vis, (self.middle_x - self.spaces, self.spaces), 'Training...', 'm') cv2.imshow(self.window_name, vis) if self.out is not None: self.out.write(vis) def _clean_up(self): if self.out is not None: self.out.release() self.cam.release() cv2.destroyAllWindows()