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

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 pairsfor (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 multimapif ((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 multimapstd::set<int> unique_keys;int last_key = INT_MIN; // some value that won't appearfor (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 multimapif (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 pairstd::map<std::pair<int, int>, int> count_map;// Loop through each element in the multimapfor (auto it = bbTempMatches.begin(); it != bbTempMatches.end(); ++it){// Create a pair from the key and value of multimapstd::pair<int, int> key_value_pair = std::make_pair(it->first, it->second);// Check if the pair already exists in count_mapif (count_map.find(key_value_pair) == count_map.end()){// If not, insert it with a count of 1count_map.insert(std::make_pair(key_value_pair, 1));}else{// If it exists, increment the countcount_map[key_value_pair]++;}}// Display the count of each key-value pairif (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 imagefor (auto it = unique_keys.begin(); it != unique_keys.end(); ++it){int BBoxIdx1 = -1; // BBox indexint BBoxIdx2 = -1;int maxKyPtCnt = INT_MIN;// Loop through all the BBox matched pairs and find the ones with highest keypoint occurencesfor (auto it1 = count_map.begin(); it1 != count_map.end(); ++it1){int currKyPtCnt = it1->second; // Number of occurences for the current BBox pairif (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 pairif (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 keypointsstd::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 vectordouble q1 = percentile(distKptMatches, 0.25);double q3 = percentile(distKptMatches, 0.75);// Find the IQR for the distance vectordouble 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 boxauto 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 variablesdouble dT = 1.0 / frameRate; // time between two measurements in secondsstd::vector<double> lidarPointsPrevX, lidarPointsCurrX;std::vector<double> filtLidarPointsPrevX, filtLidarPointsCurrX;// Create vector of all x points from previous and current Lidar pointsfor (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 vectorfiltLidarPointsPrevX = removeOutliers(lidarPointsPrevX);filtLidarPointsCurrX = removeOutliers(lidarPointsCurrX);// find closest distance to Lidar points within ego lanedouble 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 measurementsTTC = minXCurr * dT / (minXPrev - minXCurr);// Compute velocityvelLidar = (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 pointif (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. framefor (auto it1 = kptMatches.begin(); it1 != kptMatches.end() - 1; ++it1){ // outer keypoint match  loop// get current keypoint and its matched partner in the prev. framecv::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 loopdouble minDist = 100.0; // min. required distance in pixels// get next keypoint and its matched partner in the prev. framecv::KeyPoint kpInnerCurr = kptsCurr.at(it2->trainIdx);cv::KeyPoint kpInnerPrev = kptsPrev.at(it2->queryIdx);// compute distances and distance ratiosdouble 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 zerodouble 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 emptyif (distRatios.size() == 0){TTC = NAN;return;}/* STEP 2: Filter out outliers from the distance ratio vector*/// Remove the outliers from the distance ratio vectorstd::vector<double> filtDistRatios = removeOutliers(distRatios);/* STEP 3: Compute camera-based TTC from distance ratios*/// compute camera-based TTC from mean distance ratiosdouble 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 mediandouble 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.mzph.cn/news/197640.shtml

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

相关文章

STM32串口通信初探:使用HAL库实现基本功能

在本文中&#xff0c;我们将探索如何使用STM32的HAL库来实现串口通信的基本功能。串口通信是一种常见的外设通信方式&#xff0c;用于在微控制器和其他外部设备之间进行数据传输。在STM32系列微控制器中&#xff0c;HAL库提供了简单且灵活的方法来实现串口通信。我们将重点讨论…

深入理解强化学习——马尔可夫决策过程:蒙特卡洛方法-[代码实现]

分类目录&#xff1a;《深入理解强化学习》总目录 在文章《深入理解强化学习——马尔可夫决策过程&#xff1a;蒙特卡洛方法-[基础知识]》中我们介绍了利用蒙特卡洛方法计算马尔可夫决策过程价值的方法&#xff0c;本文将用代码定义一个采样函数。采样函数需要遵守状态转移矩阵…

使用栈解决括号匹配问题(详解)

项目结构 项目头文件的代码或截图 头文件代码 #ifndef LINKSTACK_H #define LINKSTACK_H #include <stdio.h> #include <stdlib.h> // 链式栈的节点 typedef struct LINKNODE {struct LINKNODE* next; }LinkNode; // 链式栈 typedef struct LINKSTACK {LinkNode h…

【Java 基础】19 多线程基础

文章目录 进程和线程进程&#xff08;Process&#xff09;线程&#xff08;Thread&#xff09; 线程的创建1&#xff09;继承 Thread 类2&#xff09;实现 Runnable 接口3&#xff09;使用 Lambda 表达式4&#xff09;总结 线程的状态状态的分类状态间转换 多线程是一种 同时执…

6、原型模式(Prototype Pattern,不常用)

原型模式指通过调用原型实例的Clone方法或其他手段来创建对象。 原型模式属于创建型设计模式&#xff0c;它以当前对象为原型&#xff08;蓝本&#xff09;来创建另一个新的对象&#xff0c;而无须知道创建的细节。原型模式在Java中通常使用Clone技术实现&#xff0c;在JavaSc…

SpringBoot系列之集成Jedis教程

SpringBoot系列之集成Jedis教程&#xff0c;Jedis是老牌的redis客户端框架&#xff0c;提供了比较齐全的redis使用命令&#xff0c;是一款开源的Java 客户端框架&#xff0c;本文使用Jedis3.1.0加上Springboot2.0&#xff0c;配合spring-boot-starter-data-redis使用&#xff0…

基恩士软件的基本操作(六,KV脚本的使用)

目录 什么是KV脚本&#xff1f; KV脚本有什么用&#xff1f; 怎么使用KV脚本&#xff08;脚本不能与梯形图并联使用&#xff09;&#xff1f; 插入框脚本&#xff08;CtrlB&#xff09; 插入域脚本&#xff08;CtrlR&#xff09; 区别 脚本语句&#xff08;.T是字符串类…

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(上)

前言&#xff1a; 由于c语言的程序编译链接的这块知识点不清楚&#xff0c;回来复习一遍&#xff0c;以便于好理解c知识&#xff0c;我会尽快更新下一篇文章。 目录 1.程序的翻译环境和执行环境 2.翻译环境&#xff08;编译链接&#xff09; 编译&#xff08;编译器&#xf…

算符优先语法分析程序设计与实现

制作一个简单的C语言词法分析程序_用c语言编写词法分析程序-CSDN博客文章浏览阅读378次。C语言的程序中&#xff0c;有很单词多符号和保留字。一些单词符号还有对应的左线性文法。所以我们需要先做出一个单词字符表&#xff0c;给出对应的识别码&#xff0c;然后跟据对应的表格…

电子学会C/C++编程等级考试2022年09月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最长上升子序列 一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … &l…

IoT DC3 是一个基于 Spring Cloud 全开源物联网平台 linux docker部署傻瓜化步骤

如有不了解可先参考我的另一篇文章本地部署:IoT DC3 是一个基于 Spring Cloud 的开源的、分布式的物联网(IoT)平台本地部署步骤 如有不了解可先参考我的另一篇文章本地部署: 1 环境准备: JDK 8 以上 docker 安装好 下载docker-compose-dev.yml 文件 执行基础环境docker安装 …

gitlab-jenkins-shell-helm-chart-k8s自动化部署微服务

1.准备好编译环境的容器&#xff0c;所有容器的镜像制作在gemdale-dockerfile这个代码库里面&#xff0c;也可以直接拉取官方镜像部署 docker run --name node1420-patternx -v /data/var/www/:/data/var/www/ -v /var/jenkins_home/:/var/jenkins_home/ -v /mnt/hgfs/:/mnt/h…

zabbix的自动发现机制:

zabbix的自动发现机制: zabbix客户端主动的和服务端联系&#xff0c;将自己的地址和端口发送给服务端&#xff0c;实现自动添加监控主机 客户端是主动的一方 缺点&#xff1b;如果自定义网段中主机数量太多&#xff0c;等级耗时会很久&#xff0c;而且这个自动发现机制不是很…

后端部署-阿里云服务器-开设端口-域名解析-安全证书-备案

本文以阿里云的轻量级数据库为例子。 前言 要搭建一个完整的后端系统一般的步骤&#xff1a; 获得一台服务器----->开设端口----->搭建后台所需要的语言和应用---->利用公网ip地址测试后端程序------->购买域名和证书-------->域名绑定和解析------->icp备…

Nginx安装

Nginx简介 Nginx 是一个高性能的HTTP和反向代理web服务器&#xff0c;其特点是占有内存少&#xff0c;并发能力强&#xff0c;其并发能力在同类型的网页服务器中表现较好。 Nginx安装 下载地址 安装稳定版本 下载完成后进行解压 可以双击nginx.exe 启动nginx 也可以打开cm…

【mysql】基于binlog数据恢复指令和坑

文章目录 1.binlog相关配置是否开启binlogbinlog日志格式 2.导出binlog日志mysqlbinlog指令updateinsertdeletebinlog中的事件 3.数据恢复4.特别注意的坑为什么bash脚本执行mysqlbinlog&#xff0c;无法找到指令为什么执行mysqlbinlog&#xff0c;无法数据恢复 1.binlog相关配置…

nodejs+vue+微信小程序+python+PHP就业求职招聘信息平台的设计与实现-计算机毕业设计推荐

主要有前端和后端&#xff0c;前端显示整个网站的信息&#xff0c;后端主要对前端和网站的基本信息进行管理。用户端模块主要是系统中普通用户在注册、登录系统可以看到自己的基本信息&#xff0c;维护自己的信息&#xff1b;管理员端模块主要是管理员登录后对整个系统相关操作…

【Vue】使用 Vue CLI 脚手架创建 Vue 项目(使用GUI创建)

前言 在开始使用Vue进行开发之前&#xff0c;我们需要先创建一个Vue项目。Vue CLI&#xff08;Command Line Interface&#xff09;是一个官方提供的脚手架工具&#xff0c;可以帮助我们快速创建Vue项目。Vue CLI也提供了一个可视化的GUI界面来创建和管理Vue项目。 步骤 打开终…

微信小程序uni.chooseImage()无效解决方案

Bug场景&#xff1a; 微信小程序在上传图片时可以通过 uni.chooseImage()方案进行上传&#xff0c;这里不再赘述具体参数。一直项目都可以正常使用&#xff0c;突然有一天发现无法使用该方法&#xff0c;于是查了一下&#xff0c;发现是用户隐私协议问题。故记录一下解决方案。…

JVM Optimization Learning(五)

一、JVM Optimization 1、G1 G1官网说明&#xff1a;Garbage First Garbage Collector Tuning The Garbage First Garbage Collector (G1 GC) is the low-pause, server-style generational garbage collector for Java HotSpot VM. The G1 GC uses concurrent and paralle…