使用激光雷达(LiDAR)和相机进行3D物体跟踪

news2024/11/26 14:54:26

d789c49321fb62f91ee8173063246f70.gif

使用相机和激光雷达进行时间到碰撞(TTC)计算

在我的先前文章中,我介绍了通过检测关键点和匹配描述符进行2D特征跟踪的主题。在本文中,我将利用这些文章中的概念,以及更多的内容,开发一个软件流水线,使用相机和激光雷达测量在3D空间中检测和跟踪对象,并使用两者估计每个时间步长与前方车辆的时间到碰撞(TTC)(如本文开头的GIF所示)。我完成了这个项目,作为我Udacity传感器融合纳米学位课程的一部分。

要理解整个过程,请参考下面的流程图。我的先前文章详细介绍了流程图中的第5、6和7点。本文将简要介绍代码片段中的其余部分。

e29b87e4bbb86b0b5a1774e56974e5f5.jpeg建立TTC计算的基本块

该项目分为4个部分:

1. 首先,通过使用关键点对应关系来开发一种匹配3D对象的方法。

2. 其次,基于激光雷达测量计算TTC。

3. 然后,使用相机执行相同的操作,这首先要将关键点匹配到感兴趣区域,然后基于这些匹配计算TTC。

4. 最后,对框架进行各种测试。目标是识别用于TTC估计的最适合的检测器/描述符组合,同时搜索可能导致相机或激光雷达传感器测量错误的问题。

检测和分类对象

我们需要一种方法来检测图像中的车辆,以便我们可以隔离匹配的关键点以及投影的激光雷达点,并将它们与特定对象关联起来。然而,为了计算特定车辆的时间到碰撞,我们需要隔离该车辆上的关键点,以便TTC估计不会因包括例如道路表面、静止物体或场景中其他车辆上的匹配而扭曲。实现这一目标的一种方法是使用对象检测自动识别场景中的车辆。基于这些边界框,我们可以轻松地将关键点匹配到对象,并实现稳定的TTC估计。

一种易于使用且基于深度学习的方法是YOLO,这是一个非常快速的检测框架,它随OpenCV库一起提供。我使用YOLOv3,它可以根据COCO(一个大规模的对象检测、分割和字幕数据集)在图像和视频中识别80种不同的对象。

这是图像通过神经网络后的最终输出。所有检测到的车辆都被包含在边界框中。

08ffa0b7daf087f06d618fcb3ec9780c.jpeg

裁剪和使用边界框对激光雷达点进行聚类

由于我们的目标是计算与前车的TTC,我们应该只关注紧挨着自动车的车辆,并忽略所有其他车辆。在对激光雷达点进行聚类后,我们过滤掉所有位于相邻车道和道路上的点。然后,我们遍历所有激光雷达点,将它们投影到相机平面,并将它们关联到各自的边界框。这是在过滤之前的样子;对象的激光雷达点被包含在各自的边界框内。

189691506e9fcaaf562d254a380b8dcf.jpeg

在过滤掉与我们的功能无关的对象之后,图像将如下所示:

e5bd482bf5274f2520a831e7643a0e08.jpeg

跟踪3D对象边界框

一旦我们对多个图像执行了上述步骤,我们就可以开始在连续图像之间跟踪边界框。为了在帧之间匹配边界框,我们遍历所有关键点匹配对,并在两个帧中关联相应的边界框。然后,我们存储匹配的唯一框ID,并创建一个边界框匹配对。

具有最高关键点匹配次数的边界框对然后被选择为最佳匹配对(bbBestMatches),因此我们能够在连续帧中唯一跟踪相同的对象。

以下图像说明了这个概念。左图是来自T-1时刻的帧,右图是T时刻的帧。前一帧中的边界框(框ID 1)在当前帧中有两个边界框(框ID 3,4)上的关键点匹配。然而,由于BB1和BB3之间的关键点匹配对具有与BB1和BB4相比的最高匹配次数,所以边界框对1–3被选为最佳匹配对,因此代表相同的对象。

9698a1f9d95ee02c4da0d38bb53228a2.jpeg

以下是实现相同的代码:

/**
 * @brief Match list of 3D objects (vector<BoundingBox>) between current and previous frame
 *
 * @param matches       List of best matches between previous and current frame
 * @param bbBestMatches Output list of best matches between previous and current frame (matched boxID pairs)
 * @param prevFrame     Previous frame
 * @param currFrame     Current frame
 */
void matchBoundingBoxes(std::vector<cv::DMatch> &matches, std::map<int, int> &bbBestMatches, DataFrame &prevFrame, DataFrame &currFrame)
{


    /* STEP 1 : Go through all keypoint matches and associate them with their respective bounding boxes in both images */


    std::multimap<int, int> bbTempMatches;


    // Loop over all the keypoint match pairs
    for (auto matchIt = matches.begin(); matchIt != matches.end(); ++matchIt)
    {
        int prevImgBoxID = -1;
        int currImgBoxID = -1;


        // Loop through all the BBoxes in previous image and find the box ID of the 'query' keypoint(match keypoint in previous image)
        for (auto it = prevFrame.boundingBoxes.begin(); it != prevFrame.boundingBoxes.end(); ++it)
        {
            cv::KeyPoint keyPt;
            keyPt = prevFrame.keypoints[matchIt->queryIdx];
            if (it->roi.contains(keyPt.pt))
            // if ((keyPt.pt.x > (it->roi.x)) && (keyPt.pt.x < (it->roi.x + it->roi.width)) &&
            //     (keyPt.pt.y > (it->roi.y)) && (keyPt.pt.y < (it->roi.y + it->roi.height)))
            {
                it->keypoints.push_back(keyPt);
                prevImgBoxID = it->boxID;
                break;
            }
        }


        // Loop through all the BBoxes in current image and find the box ID of the 'train' keypoint(match keypoint in current image)
        for (auto it = currFrame.boundingBoxes.begin(); it != currFrame.boundingBoxes.end(); ++it)
        {
            cv::KeyPoint keyPt;
            keyPt = currFrame.keypoints[matchIt->trainIdx];
            if (it->roi.contains(keyPt.pt))
            // if ((keyPt.pt.x > (it->roi.x)) && (keyPt.pt.x < (it->roi.x + it->roi.width)) &&
            //     (keyPt.pt.y > (it->roi.y)) && (keyPt.pt.y < (it->roi.y + it->roi.height)))
            {
                it->keypoints.push_back(keyPt);
                currImgBoxID = it->boxID;
                break;
            }
        }


        // Store the box ID pairs in a temporary multimap
        if ((prevImgBoxID != -1) && (currImgBoxID != -1)) // Exclude pairs which are not part of either BBoxes
        {
            bbTempMatches.insert(std::make_pair(prevImgBoxID, currImgBoxID));
        }


    } // eof Loop over all keypoint match pairs


    /* STEP 2: For each BBox pair count the number of keypoint matches*/


    // Find all the unique keys (BBox IDs in the prev image) from the multimap
    std::set<int> unique_keys;
    int last_key = INT_MIN; // some value that won't appear


    for (auto it = bbTempMatches.begin(); it != bbTempMatches.end(); ++it)
    {
        if (it->first != last_key)
        {
            unique_keys.insert(it->first);
            last_key = it->first;
        }
    }


    // Display contents of multimap
    if (false)
    {
        for (auto itr = bbTempMatches.begin(); itr != bbTempMatches.end(); ++itr)
        {
            cout << '\t' << itr->first << '\t' << itr->second
                 << '\n';
        }
    }


    // Create a map to count occurrences of each key-value pair
    std::map<std::pair<int, int>, int> count_map;


    // Loop through each element in the multimap
    for (auto it = bbTempMatches.begin(); it != bbTempMatches.end(); ++it)
    {
        // Create a pair from the key and value of multimap
        std::pair<int, int> key_value_pair = std::make_pair(it->first, it->second);


        // Check if the pair already exists in count_map
        if (count_map.find(key_value_pair) == count_map.end())
        {
            // If not, insert it with a count of 1
            count_map.insert(std::make_pair(key_value_pair, 1));
        }
        else
        {
            // If it exists, increment the count
            count_map[key_value_pair]++;
        }
    }


    // Display the count of each key-value pair
    if (false)
    {
        for (auto it = count_map.begin(); it != count_map.end(); ++it)
        {
            std::cout << "(" << it->first.first << ", " << it->first.second << "): " << it->second << std::endl;
        }
    }


    /* STEP 3: The BBox pair with highest number of keypoint match occurences is the best matched BBox pair*/


    // Iterate through each unique bounding box IDs in the previous image
    for (auto it = unique_keys.begin(); it != unique_keys.end(); ++it)
    {
        int BBoxIdx1 = -1; // BBox index
        int BBoxIdx2 = -1;
        int maxKyPtCnt = INT_MIN;


        // Loop through all the BBox matched pairs and find the ones with highest keypoint occurences
        for (auto it1 = count_map.begin(); it1 != count_map.end(); ++it1)
        {
            int currKyPtCnt = it1->second; // Number of occurences for the current BBox pair


            if (it1->first.first == *it)
            {
                if (currKyPtCnt >= maxKyPtCnt)
                {
                    maxKyPtCnt = currKyPtCnt;
                    BBoxIdx1 = it1->first.first;
                    BBoxIdx2 = it1->first.second;
                }
            }
        }


        if ((BBoxIdx1 != -1) && (BBoxIdx2 != -1)) // Exclude pairs which are not part of either BBoxes
        {
            bbBestMatches.insert(std::make_pair(BBoxIdx1, BBoxIdx2));
        }
    }


    // Display the count of each BBox pair
    if (false)
    {
        for (auto it = bbBestMatches.begin(); it != bbBestMatches.end(); ++it)
        {
            std::cout << "(" << it->first << ", " << it->second << "): " << std::endl;
        }
    }
}

所有属于当前帧边界框的关键点匹配都被聚类并通过根据它们相对于边界框中所有匹配的欧几里德距离消除离群匹配来进行过滤。

这一步是为了使用相机计算鲁棒的TTC,我们稍后会看到。

5342447c882388ad6da956b88bc8a89a.png

下面的代码展示了如何实现上述的过滤器。

/**
 * @brief Cluster keypoint matches with the current bounding box
 *
 * @param boundingBox   Current bounding box
 * @param kptsPrev      Previous frame keypoints
 * @param kptsCurr      Current frame keypoints
 * @param kptMatches    Keypoint matches between previous and current frame
 */
void clusterKptMatchesWithROI(BoundingBox &boundingBox, std::vector<cv::KeyPoint> &kptsPrev, std::vector<cv::KeyPoint> &kptsCurr, std::vector<cv::DMatch> &kptMatches)
{
    /* STEP 1: Associate the keypoint match with the bounding box and calculate keypoint match distance*/


    // Loop through all the keypoint matches and find the ones which are part of the current bounding box
    // Also find the distance between the matched keypoints
    std::vector<double> distKptMatches;
    for (auto it = kptMatches.begin(); it != kptMatches.end(); ++it)
    {
        cv::KeyPoint keyPtPrev, keyPtCurr;
        double dist;
        keyPtPrev = kptsPrev[it->queryIdx];
        keyPtCurr = kptsCurr[it->trainIdx];


        if (boundingBox.roi.contains(keyPtCurr.pt))
        {
            boundingBox.kptMatches.push_back(*it);
            dist = cv::norm(keyPtCurr.pt - keyPtPrev.pt);
            distKptMatches.push_back(dist);
        }
    }


    /*STEP 2: Remove outlier keypoint matches from the bounding box*/


    // Get the Q1 and Q3 percentile for the distance vector
    double q1 = percentile(distKptMatches, 0.25);
    double q3 = percentile(distKptMatches, 0.75);
    // Find the IQR for the distance vector
    double iqr = q3 - q1;


    // Go through all the matched keypoint pairs in the current bounding box and remove the ones which are outliers from the bounding box
    auto it1 = boundingBox.kptMatches.begin();
    while (it1 != boundingBox.kptMatches.end())
    {
        cv::KeyPoint keyPtPrev, keyPtCurr;
        double dist;
        keyPtPrev = kptsPrev[it1->queryIdx];
        keyPtCurr = kptsCurr[it1->trainIdx];
        dist = cv::norm(keyPtCurr.pt - keyPtPrev.pt);


        if ((dist < (q1 - 1.5 * iqr)) || (dist > (q3 + 1.5 * iqr)))
        {
            it1 = boundingBox.kptMatches.erase(it1); // erase() returns the next iterator
        }
        else
        {
            ++it1;
        }
    }
}

计算使用 LiDAR 点的时间到碰撞(TTC)

这一步使用仅基于 LiDAR 测量的方式计算前车的时间到碰撞(TTC)。

adce8b3edd748dc4f120bc5fbb88feec.jpeg

如上图所示,d0 是时刻 t0 时自车与前车之间的距离,d1 是时刻 t1 时的距离。基于恒定速度模型,计算 TTC 的方程如下:

3e6ee6e503d645fc5b1999587e643815.jpeg使用 LiDAR 计算 TTC

一旦知道相对速度 v0,就可以通过将两车之间的剩余距离除以 v0 来轻松计算碰撞时间。因此,鉴于 LiDAR 传感器能够进行精确的距离测量,可以基于 CVM 和上述方程组开发一个用于 TTC 估计的系统。然而,请注意,雷达传感器将是 TTC 计算的更优解,因为它可以直接测量相对速度,而在 LiDAR 传感器中,我们需要从两个(带有噪音的)距离测量中计算 v0。

为了使 TTC 对离群值具有鲁棒性,我使用 IQR 方法在距离测量中进行了滤除。以下是 TTC 的实现:

/**
 * @brief Compute time-to-collision (TTC) based on Lidar measurements
 *
 * @param lidarPointsPrev Previous Lidar points
 * @param lidarPointsCurr Current Lidar points
 * @param frameRate       Frame rate of the camera
 * @param TTC             Output TTC
 */
void computeTTCLidar(std::vector<LidarPoint> &lidarPointsPrev,
                     std::vector<LidarPoint> &lidarPointsCurr, double frameRate, double &TTC, double &velLidar)
{
    // auxiliary variables
    double dT = 1.0 / frameRate; // time between two measurements in seconds
    std::vector<double> lidarPointsPrevX, lidarPointsCurrX;
    std::vector<double> filtLidarPointsPrevX, filtLidarPointsCurrX;


    // Create vector of all x points from previous and current Lidar points
    for (auto it = lidarPointsPrev.begin(); it != lidarPointsPrev.end(); ++it)
    {
        lidarPointsPrevX.push_back(it->x);
    }


    for (auto it = lidarPointsCurr.begin(); it != lidarPointsCurr.end(); ++it)
    {
        lidarPointsCurrX.push_back(it->x);
    }


    // Filter out outliers from the X-distance vector
    filtLidarPointsPrevX = removeOutliers(lidarPointsPrevX);
    filtLidarPointsCurrX = removeOutliers(lidarPointsCurrX);


    // find closest distance to Lidar points within ego lane
    double minXPrev = INT_MAX, minXCurr = INT_MAX;
    for (auto it = filtLidarPointsPrevX.begin(); it != filtLidarPointsPrevX.end(); ++it)
    {
        minXPrev = minXPrev > *it ? *it : minXPrev;
    }


    for (auto it = filtLidarPointsCurrX.begin(); it != filtLidarPointsCurrX.end(); ++it)
    {
        minXCurr = minXCurr > *it ? *it : minXCurr;
    }


    // compute TTC from both measurements
    TTC = minXCurr * dT / (minXPrev - minXCurr);
    // Compute velocity
    velLidar = (minXPrev - minXCurr) / dT;
}




/**
 * @brief Function to remove outliers based on IQR
 *
 * @param data                  Input dataset
 * @return std::vector<double>  Filtered dataset
 */
std::vector<double> removeOutliers(const std::vector<double> &data)
{
    std::vector<double> sorted_data = data;
    std::sort(sorted_data.begin(), sorted_data.end());


    double q1 = percentile(sorted_data, 0.25);
    double q3 = percentile(sorted_data, 0.75);


    double iqr = q3 - q1;


    std::vector<double> filtered_data;
    for (double x : data)
    {
        if (x >= q1 - 1.5 * iqr && x <= q3 + 1.5 * iqr)
        {
            filtered_data.push_back(x);
        }
    }


    return filtered_data;
}


/**
 * @brief Function to calculate the percentile of a sorted dataset
 *
 * @param dataIn    Input dataset
 * @param p         Percentile
 * @return double   Value of the percentile
 */
double percentile(const std::vector<double> &dataIn, double p)
{
    std::vector<double> data = dataIn;
    std::sort(data.begin(), data.end());


    int N = data.size();
    double n = (N - 1) * p + 1;


    // If n is an integer, then percentile is a data point
    if (n == floor(n))
    {
        return data[n - 1];
    }
    else
    {
        int k = floor(n);
        double d = n - k;
        return data[k - 1] + d * (data[k] - data[k - 1]);
    }
}

下面的图表显示了所有18个连续帧的 LiDAR TTC 值。TTC 值不一致且变化很大。这可能是因为使用恒定速度模型计算 TTC。这不是一个好的假设,因为前车的速度并非恒定。图像中的红线显示了如果使用恒定速度模型时的 TTC。

另一个原因可能是由于使用 Lidar 点的最小 X 距离计算 TTC,这是一个单一点。这个点也可能是噪声。通过使用 Lidar 点的 X 距离的中值,这可能会得到改善。

2b8a8062601d1ee17c8b4633399efd94.jpeg

使用相机计算 TTC

这一步使用仅相机计算前车的时间到碰撞(TTC)。

通过测量连续的距离,可以使用 3D Lidar 传感器计算 TTC。然而,使用 2D 相机进行 TTC 计算是具有挑战性的,原因是缺乏 3D 测量和用于运动跟踪的准确车辆识别。

单眼相机无法测量度量距离。它们是依赖于环境光的被反射到相机镜头中的被动传感器。因此,与 Lidar 技术测量光的运行时不同,无法测量光的运行时。

为了测量对象之间的距离,可以使用两个对齐的相机来定位两个图像中的共同兴趣点并三角测量它们的距离。然而,由于其体积大、成本高以及找到对应特征的计算负载,立体相机在 ADAS 和自动驾驶车辆中变得不太受欢迎。

尽管单眼相机有其局限性,我们仍然可以尝试在没有距离测量的情况下计算 TTC。我们将使用恒定速度运动模型,并用相机可以准确测量的图像平面上的像素距离替换度量距离。

在下图中,您可以看到前车的高度 H 如何通过透视投影映射到图像平面上。我们可以看到相同的高度 H 在图像平面上取决于车辆的距离 d0 和 d1。显然,高度 h,H,d 和针孔相机的焦距 f 之间存在几何关系 —— 这就是我们想要在下文中利用的。

f3b91653233c2a553e19c10fdcbe5595.jpeg

以下一组方程显示了如何仅使用投影计算 TTC。

df630a7f98b11a961c8d77c82eb03bba.jpeg使用相机计算 TTC

因此,通过观察图像传感器上的相对高度变化,可以测量时间到碰撞。不需要距离测量,因此我们可以使用单眼相机通过直接观察图像中相对高度(也称为尺度变化)的变化来估计时间到碰撞。

神经网络(YOLO)为每辆车返回一个边界框,但它们的尺寸可能不准确。使用边界框的高度或宽度进行 TTC 计算将导致显著的误差。

与依赖于整个车辆的检测不同,我们现在希望在较小的尺度上分析其结构。如果能够找到可以从一帧跟踪到下一帧的唯一可识别的关键点,我们可以使用车辆上所有关键点之间的距离相对于彼此的距离来计算 TTC 方程中高度比的鲁棒估计。下图说明了这个概念。

3084250b50bfd7b6bc72771cde0a693a.jpeg关键点缩放

在(a)中,已检测到一组关键点,并计算了关键点 1-7 之间的相对距离。在(b)中,使用称为描述符的高维相似性测量,成功地匹配了连续图像之间的 4 个关键点(其中关键点 3 不匹配)。

可以使用彼此之间所有相对距离的比率来计算可靠的 TTC 估计,方法是将高度比 ℎ1/ℎ0 替换为所有距离比率 dk/dk` 的均值或中值。

以下代码实现了先前的概念。同样,为了使 TTC 对离群值具有鲁棒性,我们使用 IQR 方法。

/**
 * @brief Compute time-to-collision (TTC) based on keypoint correspondences in successive images
 * 
 * @param kptsPrev      Previous frame keypoints
 * @param kptsCurr      Current frame keypoints
 * @param kptMatches    Keypoint matches between previous and current frame
 * @param frameRate     Frame rate of the camera 
 * @param TTC           Output TTC
 * @param visImg        Output visualization image
 */
void computeTTCCamera(std::vector<cv::KeyPoint> &kptsPrev, std::vector<cv::KeyPoint> &kptsCurr,
                      std::vector<cv::DMatch> kptMatches, double frameRate, double &TTC, cv::Mat *visImg)
{
    /* STEP 1: Compute distance ratios between all matched keypoints*/


    vector<double> distRatios; // stores the distance ratios for all keypoints between curr. and prev. frame
    for (auto it1 = kptMatches.begin(); it1 != kptMatches.end() - 1; ++it1)
    { // outer keypoint match  loop


        // get current keypoint and its matched partner in the prev. frame
        cv::KeyPoint kpOuterCurr = kptsCurr.at(it1->trainIdx);
        cv::KeyPoint kpOuterPrev = kptsPrev.at(it1->queryIdx);


        for (auto it2 = kptMatches.begin() + 1; it2 != kptMatches.end(); ++it2)
        { // inner keypoint match loop


            double minDist = 100.0; // min. required distance in pixels


            // get next keypoint and its matched partner in the prev. frame
            cv::KeyPoint kpInnerCurr = kptsCurr.at(it2->trainIdx);
            cv::KeyPoint kpInnerPrev = kptsPrev.at(it2->queryIdx);


            // compute distances and distance ratios
            double distCurr = cv::norm(kpOuterCurr.pt - kpInnerCurr.pt);
            double distPrev = cv::norm(kpOuterPrev.pt - kpInnerPrev.pt);


            if (distPrev > std::numeric_limits<double>::epsilon() && distCurr >= minDist)
            { // avoid division by zero


                double distRatio = distCurr / distPrev;
                distRatios.push_back(distRatio);
            }
        } // eof inner loop over all matched kpts
    }     // eof outer loop over all matched kpts


    // only continue if list of distance ratios is not empty
    if (distRatios.size() == 0)
    {
        TTC = NAN;
        return;
    }


    /* STEP 2: Filter out outliers from the distance ratio vector*/


    // Remove the outliers from the distance ratio vector
    std::vector<double> filtDistRatios = removeOutliers(distRatios);


    /* STEP 3: Compute camera-based TTC from distance ratios*/


    // compute camera-based TTC from mean distance ratios
    double meanDistRatio = std::accumulate(filtDistRatios.begin(), filtDistRatios.end(), 0.0) / filtDistRatios.size();


    double dT = 1 / frameRate;
    // TTC = -dT / (1 - meanDistRatio);


    // Alternate method to compute camera-based TTC from distance ratios using median
    double medianDistRatio;


    std::sort(filtDistRatios.begin(), filtDistRatios.end());
    if (distRatios.size() % 2 == 0)
    {
        medianDistRatio = (filtDistRatios[filtDistRatios.size() / 2 - 1] + filtDistRatios[filtDistRatios.size() / 2]) / 2;
    }
    else
    {
        medianDistRatio = filtDistRatios[filtDistRatios.size() / 2];
    }


    TTC = -dT / (1 - medianDistRatio);
}

上述 TTC 计算是针对多种检测器-描述符组合进行的。在所有组合中,对于以下组合,获得了最佳的 TTC 相机数值:

为了获得快速执行时间:FAST — ORB

为了获得良好的准确性:SHITOMASI — BRIEF

1daf12a7515c7615c300480622c39dc2.jpeg

正如图中所示,在绘制中,与其他组合相比,SHITOMASI — BRIEF 组合的 TTC 计算非常一致且变化较小。FAST — ORB 组合的 TTC 计算与 TTC Lidar 值不太接近,且变化较大。这可能是因为 FAST 检测器对照明和视点变化不太稳健,而 ORB 描述符对视角变化也不太稳健。这可能是 FAST — ORB 组合性能较差的原因。此外,边界框并不总是完美地包含仅包含前置对象的部分,有时还包含来自道路某些部分的关键点匹配。

·  END  ·

HAPPY LIFE

fdf93db108331b9d4bc7402b3f057f3b.png

本文仅供学习交流使用,如有侵权请联系作者删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1287307.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ArcGIS Pro微课1000例】0050:如何清除坐标系信息

文章目录 一、目的二、方法1. 使用【定义投影】工具2. 清除数据的投影信息3. 删除坐标文件 一、目的 地理信息数据的坐标系是将地理信息数据进行融合、叠加、分析的重要数学框架&#xff0c;而其描述信息是非常重要的元数据&#xff0c;涉及整个国家的测绘坐标系统&#xff0c…

华清远见嵌入式学习——C++——作业6

作业要求&#xff1a; 代码&#xff1a; #include <iostream>using namespace std;class Animal { public:virtual void perform() 0;};class Lion:public Animal { private:string foods;string feature; public:Lion(){}Lion(string foods,string feature):foods(foo…

1_控制系统总体结构

1、总体结构 控制系统结构图&#xff1a; 黑色块为参数、黄色块为计算模块 1.1 其中参数含义 车辆属性参数&#xff1a; 参数含义 C α f C_{\alpha f} Cαf​自行车模型总轮胎侧偏刚度&#xff08;前轮&#xff09; C α r C_{\alpha r} Cαr​自行车模型总轮胎侧偏刚度&a…

Docker镜像构建:技术深度解析与实践

目录 ​编辑 一、Docker镜像基础与优化 Docker镜像概念 Dockerfile详解 层级缓存机制 二、镜像构建的高级技术 多阶段构建 安全性考量 三、构建性能优化与调试 性能优化策略 构建过程调试 四、代码实战 实例&#xff1a;构建优化的Docker镜像 调试技巧 实例&…

openlayers地图使用---跟随地图比例尺动态标绘大小的一种方式

openlayers地图使用—跟随地图比例尺动态标绘大小的一种方式 预期&#xff1a;随着地图比例尺放大缩小&#xff0c;地图上的标绘随着变化尺寸 结果图 页面元素 <script src"https://cdn.bootcdn.net/ajax/libs/openlayers/8.1.0/dist/ol.min.js"></script…

SpringBoot : ch12 多模块配置YAML文件

前言 当您使用SpringBoot框架进行项目开发时&#xff0c;通常需要配置一些参数和属性。在实际开发中&#xff0c;可能需要将这些配置参数分成多个不同的YAML文件&#xff0c;并将它们组织到不同的模块中。这样可以方便管理和维护配置文件&#xff0c;并且可以避免配置文件的冲…

智能优化算法应用:基于瞬态优化算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于瞬态优化算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于瞬态优化算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.瞬态优化算法4.实验参数设定5.算法结果6.参考…

java8 升级 java11

1.安装java11 1.1 安装参考 ​​​​​​LINUX安装JDK_liunx上安装ocean-CSDN博客 1.2 检查 java -version 2.Maven 项目pom文件修改 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEnc…

12.05

以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴子等。现在&#xff0c;动物园里有一位讲解员&…

【JavaWeb】前端工程化(VUE3)

前端工程化&#xff08;VUE3&#xff09; 文章目录 前端工程化&#xff08;VUE3&#xff09;一、概述二、ECMA6Script2.1 es6的变量和模板字符串2.2 es6的解构表达式2.3 es6的箭头函数2.4 rest和spread2.5 es6的对象创建和拷贝2.6 es6的模块化处理 三、前端工程化环境搭建3.1 N…

玩转大数据7:数据湖与数据仓库的比较与选择

1. 引言 在当今数字化的世界中&#xff0c;数据被视为一种宝贵的资源&#xff0c;而数据湖和数据仓库则是两种重要的数据处理工具。本文将详细介绍这两种工具的概念、作用以及它们之间的区别和联系。 1.1. 数据湖的概念和作用 数据湖是一个集中式存储和处理大量数据的平台&a…

Java-宋红康-(课P132)-多线程的概念

b站视频&#xff1a; https://www.bilibili.com/video/BV1PY411e7J6?p132&vd_source969a2f5c0c775c9626d7d7abe1828db0 目录 1.1 概述 1.2 程序、进程与线程 1.3 进程与线程的关系 1.4 线程调度 1.5 多线程程序的优点 1.6 补充概念-单核CPU和多核CPU 1.1 概述 我们…

Oracle merge into语句(merge into Statement)

在Oracle中&#xff0c;常规的DML语句只能完成单一功能&#xff0c;&#xff0c;例如insert/delete/update只能三选一&#xff0c;而merge into语句可以同时对一张表进行更新/插入/删除。 目录 一、基本语法 二、用法示例 2.1 同时更新和插入 2.2 where子句 2.3 delete子句 2.4…

Python神器解析时间序列数据:数据分析者必读

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 时间序列数据是在许多领域中都至关重要的数据类型&#xff0c;它涵盖了一系列按时间顺序排列的数据点。Python作为一种强大的数据分析工具&#xff0c;提供了许多库和工具&#xff0c;能够有效地处理、分析和可视…

掌握Python Pingouin:数据统计新利器解析!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Pingouin库基于pandas、scipy和statsmodels&#xff0c;为用户提供了执行常见统计分析的功能。它支持各种统计方法和假设检验&#xff0c;例如 t-tests、ANOVA、correlation analysis 等。让我们看一些示例代码&…

全网最新最全面的Appium自动化:Appium常用操作之点击滑动类操作

点击&滑动类操作 在进行app自动化的时候,经常会进行点击或滑动的操作,比如点击坐标&#xff0c;左右滑动,上下滑动等&#xff0c;Appium相应提供了解决方案。 坐标的开启步骤&#xff1a; 开发者选项——指针位置开启 坐标展示&#xff1a; 在flick和swipe中都提到了坐标…

C/C++---------------LeetCode第118. 杨辉三角

杨辉三角 题目及要求动态规划在mian内使用 题目及要求 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]] 提示: 1 < numRow…

算法通关村第二关—链表反转的拓展问题(白银)

链表反转的拓展问题 一、指定区间反转 LeetCode92&#xff1a;给你单链表的头指针head和两个整数left和right,其中left<right。请你反转从位置left到位置right的链表节点&#xff0c;返回反转后的链表。 1.1 头插法 反转的整体思想是&#xff0c;在需要反转的区间里&…

Linux 调试器 --- g d b 使用

目录 一&#xff1a;gdb简介 二&#xff1a;示例代码 三&#xff1a;使用 1.启动gdb 2.各种指令 <1>: 查看源代码 <2>:设置断点 <3>:查看断点信息 <4>:删除断点 <5>: run <6>:逐过程调试 <7>:逐语句调试 <8>:查…

AntV和AntD之间的区别与联系

前言&#xff1a;最近在调研前端的一些框架&#xff0c;技术栈主要是用react&#xff0c;所以找到了2个十分相似解决方案&#xff0c;拿来对比一下&#xff08;antd和antv都是基于react&#xff09; antd对比antv antd antv 解决方案企业级 UI 设计语言数据可视化解决方案提供…