Esempio n. 1
0
class DynamicCameraOffset:
    def __init__(self):
        self.sm = SubMaster(['laneSpeed'])
        self.pm = PubMaster(['dynamicCameraOffset'])
        self.op_params = opParams()
        self.camera_offset = self.op_params.get('camera_offset')

        self.left_lane_oncoming = False  # these variables change
        self.right_lane_oncoming = False
        self.last_left_lane_oncoming = False
        self.last_right_lane_oncoming = False
        self.last_oncoming_time = 0
        self.i = 0.0

        self._setup_static()

    def _setup_static(self):  # these variables are static
        self._enabled = self.op_params.get('dynamic_camera_offset')
        self._min_enable_speed = 35 * CV.MPH_TO_MS
        self._min_lane_width_certainty = 0.4
        hug = 0.1  # how much to hug
        self._center_ratio = 0.5
        self._hug_left_ratio = self._center_ratio - hug
        self._hug_right_ratio = self._center_ratio + hug

        self._keep_offset_for = self.op_params.get(
            'dynamic_camera_offset_time')  # seconds after losing oncoming lane
        self._ramp_angles = [0, 12.5, 25]
        self._ramp_angle_mods = [1, 0.85,
                                 0.1]  # multiply offset by this based on angle

        self._ramp_down_times = [
            self._keep_offset_for, self._keep_offset_for + 2.
        ]
        self._ramp_down_multipliers = [
            1, 0
        ]  # ramp down 1.5s after time has passed

        self._poly_prob_speeds = [
            0, 25 * CV.MPH_TO_MS, 35 * CV.MPH_TO_MS, 60 * CV.MPH_TO_MS
        ]
        self._poly_probs = [0.2, 0.25, 0.45,
                            0.55]  # we're good if only one line is above this

        self._k_p = 1.5
        _i_rate = 1 / 20
        self._k_i = 1.2 * _i_rate

    def update(self, v_ego, active, angle_steers, lane_width_estimate,
               lane_width_certainty, polys, probs):
        if self._enabled:
            self.sm.update(0)
            self.camera_offset = self.op_params.get(
                'camera_offset')  # update base offset from user
            self.left_lane_oncoming = self.sm['laneSpeed'].leftLaneOncoming
            self.right_lane_oncoming = self.sm['laneSpeed'].rightLaneOncoming
            self.lane_width_estimate, self.lane_width_certainty = lane_width_estimate, lane_width_certainty
            self.l_poly, self.r_poly = polys
            self.l_prob, self.r_prob = probs

            dynamic_offset = self._get_camera_offset(v_ego, active,
                                                     angle_steers)
            self._send_state(
            )  # for alerts, before speed check so alerts don't get stuck on
            if dynamic_offset is not None:
                return self.camera_offset + dynamic_offset

            self.i = 0  # reset when not active
        return self.camera_offset  # don't offset if no lane line in direction we're going to hug

    def _get_camera_offset(self, v_ego, active, angle_steers):
        self.keeping_left, self.keeping_right = False, False  # reset keeping
        time_since_oncoming = sec_since_boot() - self.last_oncoming_time
        if not active:  # no alert when not engaged
            return
        if np.isnan(self.l_poly[3]) or np.isnan(self.r_poly[3]):
            return
        if v_ego < self._min_enable_speed:
            return
        _min_poly_prob = interp(v_ego, self._poly_prob_speeds,
                                self._poly_probs)
        if self.l_prob < _min_poly_prob and self.r_prob < _min_poly_prob:  # we only need one line and an accurate current lane width
            return

        left_lane_oncoming = self.left_lane_oncoming
        right_lane_oncoming = self.right_lane_oncoming

        if self.have_oncoming:
            if self.lane_width_certainty < self._min_lane_width_certainty:
                return
            self.last_oncoming_time = sec_since_boot()
            self.last_left_lane_oncoming = self.left_lane_oncoming  # only update last oncoming vars when currently have oncoming. one should always be True for the 2 second ramp down
            self.last_right_lane_oncoming = self.right_lane_oncoming
        elif time_since_oncoming > self._keep_offset_for:  # return if it's 2+ seconds after last oncoming, no need to offset
            return
        else:  # no oncoming and not yet 2 seconds after we lost an oncoming lane. use last oncoming lane for 2 seconds to ramp down offset
            left_lane_oncoming = self.last_left_lane_oncoming
            right_lane_oncoming = self.last_right_lane_oncoming

        estimated_lane_position = self._get_camera_position()

        hug_modifier = interp(
            abs(angle_steers), self._ramp_angles,
            self._ramp_angle_mods)  # don't offset as much when angle is high
        if left_lane_oncoming:
            self.keeping_right = True
            hug_ratio = (self._hug_right_ratio * hug_modifier) + (
                self._center_ratio * (1 - hug_modifier))  # weighted average
        elif right_lane_oncoming:
            self.keeping_left = True
            hug_ratio = (self._hug_left_ratio *
                         hug_modifier) + (self._center_ratio *
                                          (1 - hug_modifier))
        else:
            raise Exception('Error, no lane is oncoming but we\'re here!')

        error = estimated_lane_position - hug_ratio
        self.i += error * self._k_i  # PI controller
        offset = self.i + error * self._k_p

        if time_since_oncoming <= self._keep_offset_for and not self.have_oncoming:  # not yet 3 seconds after last oncoming, ramp down from 1.5 second
            offset *= interp(time_since_oncoming, self._ramp_down_times,
                             self._ramp_down_multipliers)  # ramp down offset

        return offset

    def _send_state(self):
        dco_send = messaging.new_message('dynamicCameraOffset')
        dco_send.dynamicCameraOffset.keepingLeft = self.keeping_left
        dco_send.dynamicCameraOffset.keepingRight = self.keeping_right
        self.pm.send('dynamicCameraOffset', dco_send)

    @property
    def have_oncoming(self):
        return self.left_lane_oncoming != self.right_lane_oncoming  # only one lane oncoming

    def _get_camera_position(self):
        """
    Returns the position of the camera in the lane as a percentage. left to right: [0, 1]; 0.5 is centered
    You MUST verify that either left or right polys and lane width are accurate before calling this function.
    """
        left_line_pos = self.l_poly[
            3] + self.camera_offset  # polys have not been offset yet
        right_line_pos = self.r_poly[3] + self.camera_offset
        cam_pos_left = left_line_pos / self.lane_width_estimate  # estimated position of car in lane based on left line
        cam_pos_right = 1 - abs(
            right_line_pos
        ) / self.lane_width_estimate  # estimated position of car in lane based on right line

        # find car's camera position using weighted average of lane poly certainty
        # if certainty of both lines are high, then just average ~equally
        l_prob = self.l_prob / (self.l_prob + self.r_prob
                                )  # this and next line sums to 1
        r_prob = self.r_prob / (self.l_prob + self.r_prob)
        # be biased towards position found from most probable lane line
        return cam_pos_left * l_prob + cam_pos_right * r_prob
Esempio n. 2
0
 def send_data(self, pm: messaging.PubMaster) -> None:
   pm.send('liveCalibration', self.get_msg())
    msgs = list(LogReader(segment.log_path))

    pm = PubMaster(['liveCalibration', 'frame'])
    model_sock = sub_sock('model')

    # Read encodeIdx
    for msg in msgs:
        if msg.which() == 'encodeIdx':
            frame_id_lookup[msg.encodeIdx.frameId] = (msg.encodeIdx.segmentNum,
                                                      msg.encodeIdx.segmentId)

    # Send some livecalibration messages to initalize visiond
    for msg in msgs:
        if msg.which() == 'liveCalibration':
            pm.send('liveCalibration', msg.as_builder())

    time.sleep(1.0)
    values = {}

    out_msgs = []
    for msg in tqdm(msgs):
        w = msg.which()

        if w == 'liveCalibration':
            pm.send(w, msg.as_builder())

        if w == 'frame':
            msg = msg.as_builder()

            frame_id = msg.frame.frameId