【草稿】L-Shape fitting

1.rectangle_fitting.py

# Object shape recognition with L-shape fitting

import matplotlib.pyplot as plt
import numpy as np
import itertools
from enum import Enum

from simulator import VehicleSimulator, LidarSimulator

show_animation = True


class LShapeFitting():
    class Criteria(Enum):
        AREA = 1
        CLOSENESS = 2
        VARIANCE = 3

    def __init__(self):
        # Parameters
        self.criteria = self.Criteria.VARIANCE
        self.min_dist_of_closeness_crit = 0.01  # [m]
        self.dtheta_deg_for_serarch = 1.0  # [deg]
        self.R0 = 3.0  # [m] range segmentation param
        self.Rd = 0.001  # [m] range segmentation param

    def fitting(self, ox, oy):

        # Adaptive Range Segmentation进行自适应分割
        idsets = self._adoptive_range_segmentation(ox, oy)
        # output:[{0}, {1, 2, 3, 4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}]

        # Rectangle search
        rects = []
        for ids in idsets:  # for each cluster
            cx = [ox[i] for i in range(len(ox)) if i in ids]
            cy = [oy[i] for i in range(len(oy)) if i in ids]
            rects.append(self._rectangle_search(cx, cy))

        return rects, idsets

    def _calc_area_criterion(self, c1, c2):  # 计算面积准则
        c1_max = max(c1)
        c2_max = max(c2)
        c1_min = min(c1)
        c2_min = min(c2)

        alpha = -(c1_max - c1_min) * (c2_max - c2_min)

        return alpha

    def _calc_closeness_criterion(self, c1, c2):  # 计算最贴近准则
        c1_max = max(c1)
        c2_max = max(c2)
        c1_min = min(c1)
        c2_min = min(c2)

        D1 = [min([np.linalg.norm(c1_max - ic1), np.linalg.norm(ic1 - c1_min)]) for ic1 in c1]
        D2 = [min([np.linalg.norm(c2_max - ic2), np.linalg.norm(ic2 - c2_min)]) for ic2 in c2]

        beta = 0
        for i, _ in enumerate(D1):
            d = max(min([D1[i], D2[i]]), self.min_dist_of_closeness_crit)
            beta += (1.0 / d)  # 取了个倒数

        return beta

    def _calc_variance_criterion(self, c1, c2):  # 计算方差准则
        c1_max = max(c1)  # 最大的x'
        c2_max = max(c2)  # 最大的y'
        c1_min = min(c1)  # 最小的x'
        c2_min = min(c2)  # 最小的y'

        # 分别取c1、c2中的方差最小值作为D1、D2
        D1 = [min([np.linalg.norm(c1_max - ic1), np.linalg.norm(ic1 - c1_min)]) for ic1 in c1]
        D2 = [min([np.linalg.norm(c2_max - ic2), np.linalg.norm(ic2 - c2_min)]) for ic2 in c2]

        E1, E2 = [], []
        for (d1, d2) in zip(D1, D2):  # 打包为对应的一个个元组
            if d1 < d2:
                E1.append(d1)
            else:
                E2.append(d2)

        V1 = 0.0
        if E1:
            V1 = - np.var(E1)

        V2 = 0.0
        if E2:
            V2 = - np.var(E2)

        gamma = V1 + V2

        return gamma

    def _rectangle_search(self, x, y):

        X = np.array([x, y]).T

        dtheta = np.deg2rad(self.dtheta_deg_for_serarch)  # 搜索角度分辨率转换为弧度
        minp = (-float('inf'), None)  # 储存最小的准则分数与对应的航向角
        for theta in np.arange(0.0, np.pi / 2.0 - dtheta, dtheta):

            e1 = np.array([np.cos(theta), np.sin(theta)])
            e2 = np.array([-np.sin(theta), np.cos(theta)])

            c1 = X @ e1.T  # 表示矩阵乘运算 新的x'下行是新的y'
            c2 = X @ e2.T

            # Select criteria
            if self.criteria == self.Criteria.AREA:
                cost = self._calc_area_criterion(c1, c2)
            elif self.criteria == self.Criteria.CLOSENESS:
                cost = self._calc_closeness_criterion(c1, c2)
            elif self.criteria == self.Criteria.VARIANCE:
                cost = self._calc_variance_criterion(c1, c2)

            if minp[0] < cost:
                minp = (cost, theta)

        # calculate best rectangle
        sin_s = np.sin(minp[1])
        cos_s = np.cos(minp[1])

        c1_s = X @ np.array([cos_s, sin_s]).T
        c2_s = X @ np.array([-sin_s, cos_s]).T

        rect = RectangleData()
        rect.a[0] = cos_s
        rect.b[0] = sin_s
        rect.c[0] = min(c1_s)
        rect.a[1] = -sin_s
        rect.b[1] = cos_s
        rect.c[1] = min(c2_s)
        rect.a[2] = cos_s
        rect.b[2] = sin_s
        rect.c[2] = max(c1_s)
        rect.a[3] = -sin_s
        rect.b[3] = cos_s
        rect.c[3] = max(c2_s)

        return rect

    def _adoptive_range_segmentation(self, ox, oy):

        # Setup initial cluster
        S = []
        for i, _ in enumerate(ox):  # 为ox加上索引
            C = set()
            R = self.R0 + self.Rd * np.linalg.norm([ox[i], oy[i]])  # 求整体元素平方和再开根号
            for j, _ in enumerate(ox):
                d = np.sqrt((ox[i] - ox[j]) ** 2 + (oy[i] - oy[j]) ** 2)  # 求另一点到该点的距离
                if d <= R:
                    C.add(j)
            S.append(C)
        # Merge cluster
        while 1:
            no_change = True
            for (c1, c2) in list(itertools.permutations(range(len(S)), 2)):  # 随机找两个排列组合
                if S[c1] & S[c2]:  # 如果这两个集合中有交集,则将二者取并后赋值给其中一个再从S中删除掉另一个
                    S[c1] = (S[c1] | S.pop(c2))
                    no_change = False
                    break
            if no_change:
                break

        return S


class RectangleData():
    def __init__(self):
        self.a = [None] * 4
        self.b = [None] * 4
        self.c = [None] * 4

        self.rect_c_x = [None] * 5
        self.rect_c_y = [None] * 5

    def plot(self):
        self.calc_rect_contour()
        plt.plot(self.rect_c_x, self.rect_c_y, "-r")

    def calc_rect_contour(self):
        self.rect_c_x[0], self.rect_c_y[0] = self.calc_cross_point(self.a[0:2], self.b[0:2], self.c[0:2])  # 取前不取后

        self.rect_c_x[1], self.rect_c_y[1] = self.calc_cross_point(self.a[1:3], self.b[1:3], self.c[1:3])

        self.rect_c_x[2], self.rect_c_y[2] = self.calc_cross_point(self.a[2:4], self.b[2:4], self.c[2:4])

        self.rect_c_x[3], self.rect_c_y[3] = self.calc_cross_point([self.a[3], self.a[0]], [self.b[3], self.b[0]],
                                                                   [self.c[3], self.c[0]])

        self.rect_c_x[4], self.rect_c_y[4] = self.rect_c_x[0], self.rect_c_y[0]

    def calc_cross_point(self, a, b, c):  # 将代表矩形四个点的坐标转换再转回去
        x = (b[0] * -c[1] - b[1] * -c[0]) / (a[0] * b[1] - a[1] * b[0])
        y = (a[1] * -c[0] - a[0] * -c[1]) / (a[0] * b[1] - a[1] * b[0])
        return x, y


def main():
    # simulation parameters
    simtime = 30.0  # simulation time模拟时间
    dt = 0.2  # time tick模拟间隔
    angle_reso = np.deg2rad(3.0)  # sensor angle resolution传感器角分辨率
    # 模拟两辆车V1和V2(ix, iy, iyaw, iv, max_v, w, L)
    v1 = VehicleSimulator(-10.0, 0.0, np.deg2rad(90.0), 0.0, 50.0 / 3.6, 3.0, 5.0)
    v2 = VehicleSimulator(20.0, 10.0, np.deg2rad(180.0), 0.0, 50.0 / 3.6, 4.0, 10.0)

    lshapefitting = LShapeFitting()
    lidar_sim = LidarSimulator()  # 模拟雷达对象

    time = 0.0
    while time <= simtime:
        time += dt

        v1.update(dt, 0.1, 0.0)  # 时间、加速度大小、加速度方向变化率
        v2.update(dt, 0.1, -0.05)

        ox, oy = lidar_sim.get_observation_points([v1, v2], angle_reso)  # 获取观测数据点

        rects, idsets = lshapefitting.fitting(ox, oy)

        if show_animation:  # pragma: no cover
            plt.cla()
            plt.axis("equal")
            plt.plot(0.0, 0.0, "*r")
            v1.plot()
            v2.plot()

            # Plot range observation
            for ids in idsets:
                x = [ox[i] for i in range(len(ox)) if i in ids]
                y = [oy[i] for i in range(len(ox)) if i in ids]

                for (ix, iy) in zip(x, y):
                    plt.plot([0.0, ix], [0.0, iy], "-og")

                plt.plot([ox[i] for i in range(len(ox)) if i in ids],
                         [oy[i] for i in range(len(ox)) if i in ids],
                         "o")
            for rect in rects:
                rect.plot()

            plt.pause(0.1)

    print("Done")


if __name__ == '__main__':
    main()

2.simulator

import numpy as np
import matplotlib.pyplot as plt
import math
import random


class VehicleSimulator():  # 模拟车辆扫描点云数据

    def __init__(self, ix, iy, iyaw, iv, max_v, w, L):
        # Positions Vehicle is expected to occupy
        self.x = ix  # 中心点为x,y
        self.y = iy
        self.yaw = iyaw  # 偏角
        self.v = iv  # 速度
        self.max_v = max_v  # 最大速度限制
        self.W = w  # 车辆宽
        self.L = L  # 车辆长
        self._calc_vehicle_contour()  # 插值后的车辆轮廓点对vc_x,vc_y

    def update(self, dt, a, omega):  # 对应参数分别为:变化时间、加速度、加速度方向变化率
        self.x += self.v * np.cos(self.yaw) * dt
        self.y += self.v * np.sin(self.yaw) * dt
        self.yaw += omega * dt
        self.v += a * dt
        if self.v >= self.max_v:
            self.v = self.max_v

    def plot(self):
        plt.plot(self.x, self.y, ".b")  # 绘制中心点

        # convert global coordinate
        gx, gy = self.calc_global_contour()  # 绘制实际车辆轮廓
        plt.plot(gx, gy, "--b")

    # Vehicle Pose Estimation加入车辆的姿态的具体坐标
    def calc_global_contour(self):  # zip将对应列表元素打包为元组的列表
        gx = [(ix * np.cos(self.yaw) + iy * np.sin(self.yaw)) + self.x for (ix, iy) in zip(self.vc_x, self.vc_y)]
        gy = [(ix * np.sin(self.yaw) - iy * np.cos(self.yaw)) + self.y for (ix, iy) in zip(self.vc_x, self.vc_y)]

        return gx, gy

    def _calc_vehicle_contour(self):  # 计算车辆轮廓

        self.vc_x = []
        self.vc_y = []

        self.vc_x.append(self.L / 2.0)  # 右上
        self.vc_y.append(self.W / 2.0)

        self.vc_x.append(self.L / 2.0)  # 右下
        self.vc_y.append(-self.W / 2.0)

        self.vc_x.append(-self.L / 2.0)  # 左下
        self.vc_y.append(-self.W / 2.0)

        self.vc_x.append(-self.L / 2.0)  # 左上
        self.vc_y.append(self.W / 2.0)

        self.vc_x.append(self.L / 2.0)  # 右上(重复了因为得回去)
        self.vc_y.append(self.W / 2.0)

        self.vc_x, self.vc_y = self._interporate(self.vc_x, self.vc_y)

    def _interporate(self, x, y):  # 插值,插值完成后出现100对点
        rx, ry = [], []
        dtheta = 0.05
        for i in range(len(x) - 1):  # range()取前不取后
            # 每一段之间都插入20对点对
            rx.extend([(1.0 - θ) * x[i] + θ * x[i + 1] for θ in np.arange(0.0, 1.0, dtheta)])
            ry.extend([(1.0 - θ) * y[i] + θ * y[i + 1] for θ in np.arange(0.0, 1.0, dtheta)])
        # 最后又回到从最后一个点到第一个点之间(为了形状闭合)
        rx.extend([(1.0 - θ) * x[len(x) - 1] + θ * x[1] for θ in np.arange(0.0, 1.0, dtheta)])
        ry.extend([(1.0 - θ) * y[len(y) - 1] + θ * y[1] for θ in np.arange(0.0, 1.0, dtheta)])

        return rx, ry


class LidarSimulator():
    def __init__(self):
        self.range_noise = 0.01

    def get_observation_points(self, vlist, angle_reso):  # 获取观测点数据(车辆列表、传感器分辨率
        x, y, angle, r = [], [], [], []

        # storing points所有车辆的点
        for v in vlist:

            gx, gy = v.calc_global_contour()  # 获取车辆姿态的具体坐标

            for vx, vy in zip(gx, gy):
                vangle = math.atan2(vy, vx)  # 模拟扫描点的弧度值
                vr = np.hypot(vx, vy) * random.uniform(1.0 - self.range_noise, 1.0 + self.range_noise)  # 加噪声后的半径

                x.append(vx)  # x、y、弧度、半径(母线)
                y.append(vy)
                angle.append(vangle)
                r.append(vr)

        # ray casting filter以弧度和距离返回在扫描线上的点的x,y坐标
        rx, ry = self.ray_casting_filter(x, y, angle, r, angle_reso)

        return rx, ry

    def ray_casting_filter(self, xl, yl, thetal, rangel, angle_reso):  # 将模拟的点筛选过滤一下
        rx, ry = [], []
        rangedb = [float("inf") for _ in range(int(np.floor((np.pi * 2.0) / angle_reso)) + 1)]  # 一周期扫描间隔次数

        for i in range(len(thetal)):
            angleid = int(round(thetal[i] / angle_reso))

            if rangedb[angleid] > rangel[i]:
                rangedb[angleid] = rangel[i]

        for i in range(len(rangedb)):
            t = i * angle_reso
            if rangedb[i] != float("inf"):
                rx.append(rangedb[i] * np.cos(t))
                ry.append(rangedb[i] * np.sin(t))

        return rx, ry


def main():
    print("Begin!!")
    print("Fitting Done!!")


if __name__ == '__main__':
    main()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值