目录

1 背景介绍

2 基于MultiTracker的多目标跟踪

2.1 创建单个对象跟踪器

2.2 读取视频的第一帧

2.3 在第一帧中确定我们跟踪的对象

2.4 初始化MultiTrackerer

2.5 更新MultiTracker和显示结果

3 结果和代码

4 参考


在这篇文章中,我们将介绍如何在OpenCV中使用MultiTracker类实现多目标跟踪API。在深入了解详细信息之前,请查看下面列出的关于目标跟踪的帖子,以了解在OpenCV中实现的单个目标跟踪器的基础知识。同时需要安装opencv_contrib库,详细见:

https://blog.csdn.net/LuohenYJ/article/details/89029816

https://blog.csdn.net/LuohenYJ/article/details/89083351

1 背景介绍

计算机视觉和机器学习的大多数初学者都学习对象检测。如果您是初学者,您可能会想到为什么我们需要对象跟踪。我们不能只检测每一帧中的物体吗?

让我们探讨一下跟踪有用的几个原因。

首先,当在视频帧中检测到多个对象(比如人)时,跟踪有助于跨帧确定对象的身份。

其次,在某些情况下,目标检测可能会失败,但仍可能跟踪对象,因为跟踪会考虑前一帧中对象的位置和外观。

第三,一些跟踪算法非常快,因为它们进行本地搜索而不是全局搜索。因此,我们可以通过每第n帧执行目标检测并在中间帧中跟踪对象来为我们的系统获得非常高的性能。

那么,为什么不在第一次检测后无限期地跟踪对象呢?跟踪算法有时可能会丢失其正在跟踪的对象。例如,当对象的运动太大时,跟踪算法可能无法跟上。通常会在目标跟踪一段时间后再次目标检测。

在本教程中,我们将只关注跟踪部分。我们要跟踪的对象将通过指定它们周围的边界框来获取。

2 基于MultiTracker 的多目标跟踪

OpenCV中的多目标跟踪器MultiTracker类提供了多目标跟踪的实现。但是这只是一个初步的实现,因为它只处理跟踪对象,而不对被跟踪对象进行任何优化。

2.1 创建单个对象跟踪器

多对象跟踪器只是单个对象跟踪器的集合。我们首先定义一个将跟踪器类型作为输入并创建跟踪器对象的函数。OpenCV有8种不同的跟踪器类型:BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN,MOSSE,CSRT。本文不使用GOTURN跟踪器。一般我们先给定跟踪器类的名称,再返回单跟踪器对象,然后建立多跟踪器类。

C++代码:

vector<string> trackerTypes = {"BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"};

/**
* @brief Create a Tracker By Name object 根据设定的类型初始化跟踪器
*
* @param trackerType
* @return Ptr<Tracker>
*/
Ptr<Tracker> createTrackerByName(string trackerType)
{
Ptr<Tracker> tracker;
if (trackerType == trackerTypes[0])
tracker = TrackerBoosting::create();
else if (trackerType == trackerTypes[1])
tracker = TrackerMIL::create();
else if (trackerType == trackerTypes[2])
tracker = TrackerKCF::create();
else if (trackerType == trackerTypes[3])
tracker = TrackerTLD::create();
else if (trackerType == trackerTypes[4])
tracker = TrackerMedianFlow::create();
else if (trackerType == trackerTypes[5])
tracker = TrackerGOTURN::create();
else if (trackerType == trackerTypes[6])
tracker = TrackerMOSSE::create();
else if (trackerType == trackerTypes[7])
tracker = TrackerCSRT::create();
else
{
cout << "Incorrect tracker name" << endl;
cout << "Available trackers are: " << endl;
for (vector<string>::iterator it = trackerTypes.begin(); it != trackerTypes.end(); ++it)
{
std::cout << " " << *it << endl;
}
}
return tracker;
}

python代码:

from __future__ import print_function
import sys
import cv2
from random import randint trackerTypes = ['BOOSTING', 'MIL', 'KCF','TLD', 'MEDIANFLOW', 'GOTURN', 'MOSSE', 'CSRT'] def createTrackerByName(trackerType):
# Create a tracker based on tracker name
if trackerType == trackerTypes[0]:
tracker = cv2.TrackerBoosting_create()
elif trackerType == trackerTypes[1]:
tracker = cv2.TrackerMIL_create()
elif trackerType == trackerTypes[2]:
tracker = cv2.TrackerKCF_create()
elif trackerType == trackerTypes[3]:
tracker = cv2.TrackerTLD_create()
elif trackerType == trackerTypes[4]:
tracker = cv2.TrackerMedianFlow_create()
elif trackerType == trackerTypes[5]:
tracker = cv2.TrackerGOTURN_create()
elif trackerType == trackerTypes[6]:
tracker = cv2.TrackerMOSSE_create()
elif trackerType == trackerTypes[7]:
tracker = cv2.TrackerCSRT_create()
else:
tracker = None
print('Incorrect tracker name')
print('Available trackers are:')
for t in trackerTypes:
print(t) return tracker

2.2 读取视频的第一帧

多对象跟踪器需要两个输入即一个视频帧和我们想要跟踪的所有对象的位置(边界框)。

给定此信息,跟踪器在所有后续帧中跟踪这些指定对象的位置。在下面的代码中,我们首先使用VideoCapture类加载视频并读取第一帧。稍后将使用它来初始化MultiTracker。

C++代码:

	// Set tracker type. Change this to try different trackers. 选择追踪器类型
string trackerType = trackerTypes[6]; // set default values for tracking algorithm and video 视频读取
string videoPath = "video/run.mp4"; // Initialize MultiTracker with tracking algo 边界框
vector<Rect> bboxes; // create a video capture object to read videos 读视频
cv::VideoCapture cap(videoPath);
Mat frame; // quit if unable to read video file
if (!cap.isOpened())
{
cout << "Error opening video file " << videoPath << endl;
return -1;
} // read first frame 读第一帧
cap >> frame;

python代码:

# Set video to load
videoPath = "video/run.mp4" # Create a video capture object to read videos
cap = cv2.VideoCapture(videoPath) # Read first frame
success, frame = cap.read()
# quit if unable to read the video file
if not success:
print('Failed to read video')
sys.exit(1)

2.3 在第一帧中确定我们跟踪的对象

接下来,我们需要在第一帧中找到我们想要跟踪的对象。OpenCV提供了一个名为selectROIs的函数,它弹出一个GUI来选择边界框(也称为感兴趣区域(ROI))。在C++版本中可以通过selectROIs允许您获取多个边界框,但在Python版本中,只能通过selectROI获得一个边界框。因此,在Python版本中,我们需要一个循环来获取多个边界框。对于每个对象,我们还选择随机颜色来显示边界框。selectROI函数步骤为先在图像上画框,然后按ENTER确定完成画框画下一个框。按ESC退出画框开始执行程序

代码如下所示。

C++代码:

// Get bounding boxes for first frame
// selectROI's default behaviour is to draw box starting from the center
// when fromCenter is set to false, you can draw box starting from top left corner
bool showCrosshair = true;
bool fromCenter = false;
cout << "\n==========================================================\n";
cout << "OpenCV says press c to cancel objects selection process" << endl;
cout << "It doesn't work. Press Escape to exit selection process" << endl;
cout << "\n==========================================================\n";
cv::selectROIs("MultiTracker", frame, bboxes, showCrosshair, fromCenter); // quit if there are no objects to track
if(bboxes.size() < 1)
return 0; vector<Scalar> colors;
getRandomColors(colors, bboxes.size()); // Fill the vector with random colors
void getRandomColors(vector<Scalar>& colors, int numColors)
{
RNG rng(0);
for(int i=0; i < numColors; i++)
colors.push_back(Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255)));
}

python代码:

## Select boxes
bboxes = []
colors = [] # OpenCV's selectROI function doesn't work for selecting multiple objects in Python
# So we will call this function in a loop till we are done selecting all objects
while True:
# draw bounding boxes over objects
# selectROI's default behaviour is to draw box starting from the center
# when fromCenter is set to false, you can draw box starting from top left corner
bbox = cv2.selectROI('MultiTracker', frame)
bboxes.append(bbox)
colors.append((randint(0, 255), randint(0, 255), randint(0, 255)))
print("Press q to quit selecting boxes and start tracking")
print("Press any other key to select next object")
k = cv2.waitKey(0) & 0xFF
if (k == 113): # q is pressed
break print('Selected bounding boxes {}'.format(bboxes))

2.4 初始化MultiTrackerer

到目前为止,我们已经读取了第一帧并获得了对象周围的边界框。这是我们初始化多对象跟踪器所需的所有信息。我们首先创建一个MultiTracker对象,并添加你要跟踪目标数的单个对象跟踪器。在此示例中,我们使用CSRT单个对象跟踪器,但您可以通过将下面的trackerType变量更改为本文开头提到的8个跟踪器时间之一来尝试其他跟踪器类型。该CSRT跟踪器是不是最快的,但它产生在我们尝试很多情况下,最好的结果。

您也可以使用包含在同一MultiTracker中的不同跟踪器,但当然,它没有多大意义。能用的不多。CSRT精度最高,KCF速度精度综合最好,MOSSE速度最快。

MultiTracker类只是这些单个对象跟踪器的包装器。正如我们在上一篇文章中所知道的那样,使用第一帧和边界框初始化单个对象跟踪器,该边界框指示我们想要跟踪的对象的位置。MultiTracker将此信息传递给它内部包装的单个目标跟踪器。

C++代码:

	// Create multitracker 创建多目标跟踪类
Ptr<MultiTracker> multiTracker = cv::MultiTracker::create(); // initialize multitracker 初始化
for (int i = 0; i < bboxes.size(); i++)
{
multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(bboxes[i]));
}

python代码:

# Specify the tracker type
trackerType = "CSRT" # Create MultiTracker object
multiTracker = cv2.MultiTracker_create() # Initialize MultiTracker
for bbox in bboxes:
multiTracker.add(createTrackerByName(trackerType), frame, bbox)

2.5 更新MultiTracker和显示结果

最后,我们的MultiTracker准备就绪,我们可以在新的帧中跟踪多个对象。我们使用MultiTracker类的update方法在新帧中定位对象。每个被跟踪对象的每个边界框都使用不同的颜色绘制。

Update函数会返回true和false。update如果跟踪失败会返回false,C++代码加了判断,Python没有加。但是要注意的是update函数哪怕返回了false,也会继续更新函数,给出边界框。所以返回false,建议停止追踪。

C++代码:

	while (cap.isOpened())
{
// get frame from the video 逐帧处理
cap >> frame; // stop the program if reached end of video
if (frame.empty())
{
break;
} //update the tracking result with new frame 更新每一帧
bool ok = multiTracker->update(frame);
if (ok == true)
{
cout << "Tracking success" << endl;
}
else
{
cout << "Tracking failure" << endl;
}
// draw tracked objects 画框
for (unsigned i = 0; i < multiTracker->getObjects().size(); i++)
{
rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1);
} // show frame
imshow("MultiTracker", frame); // quit on x button
if (waitKey(1) == 27)
{
break;
}
}

python代码:

# Process video and track objects
while cap.isOpened():
success, frame = cap.read()
if not success:
break # get updated location of objects in subsequent frames
success, boxes = multiTracker.update(frame) # draw tracked objects
for i, newbox in enumerate(boxes):
p1 = (int(newbox[0]), int(newbox[1]))
p2 = (int(newbox[0] + newbox[2]), int(newbox[1] + newbox[3]))
cv2.rectangle(frame, p1, p2, colors[i], 2, 1) # show frame
cv2.imshow('MultiTracker', frame) # quit on ESC button
if cv2.waitKey(1) & 0xFF == 27: # Esc pressed
break

3 结果和代码

就结果而言,多目标跟踪就是生成多个单目标跟踪器,每个单目标跟踪器跟踪一个对象。如果你想和目标检测结合,其中的对象框如果要自己设定,push一个Rect对象就行了。

//自己设定对象的检测框
//x,y,width,height
//bboxes.push_back(Rect(388, 155, 30, 40));
//bboxes.push_back(Rect(492, 205, 50, 80));

总体来说精度和单目标跟踪器差不多,所耗时间差不多5到7倍,不同算法不同。

代码下载地址:

https://github.com/luohenyueji/OpenCV-Practical-Exercise

完整代码如下:

C++:

// Opencv_MultiTracker.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
// #include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp> using namespace cv;
using namespace std; vector<string> trackerTypes = {"BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"}; /**
* @brief Create a Tracker By Name object 根据设定的类型初始化跟踪器
*
* @param trackerType
* @return Ptr<Tracker>
*/
Ptr<Tracker> createTrackerByName(string trackerType)
{
Ptr<Tracker> tracker;
if (trackerType == trackerTypes[0])
tracker = TrackerBoosting::create();
else if (trackerType == trackerTypes[1])
tracker = TrackerMIL::create();
else if (trackerType == trackerTypes[2])
tracker = TrackerKCF::create();
else if (trackerType == trackerTypes[3])
tracker = TrackerTLD::create();
else if (trackerType == trackerTypes[4])
tracker = TrackerMedianFlow::create();
else if (trackerType == trackerTypes[5])
tracker = TrackerGOTURN::create();
else if (trackerType == trackerTypes[6])
tracker = TrackerMOSSE::create();
else if (trackerType == trackerTypes[7])
tracker = TrackerCSRT::create();
else
{
cout << "Incorrect tracker name" << endl;
cout << "Available trackers are: " << endl;
for (vector<string>::iterator it = trackerTypes.begin(); it != trackerTypes.end(); ++it)
{
std::cout << " " << *it << endl;
}
}
return tracker;
} /**
* @brief Get the Random Colors object 随机涂色
*
* @param colors
* @param numColors
*/
void getRandomColors(vector<Scalar> &colors, int numColors)
{
RNG rng(0);
for (int i = 0; i < numColors; i++)
{
colors.push_back(Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));
}
} int main(int argc, char *argv[])
{
// Set tracker type. Change this to try different trackers. 选择追踪器类型
string trackerType = trackerTypes[7]; // set default values for tracking algorithm and video 视频读取
string videoPath = "video/run.mp4"; // Initialize MultiTracker with tracking algo 边界框
vector<Rect> bboxes; // create a video capture object to read videos 读视频
cv::VideoCapture cap(videoPath);
Mat frame; // quit if unable to read video file
if (!cap.isOpened())
{
cout << "Error opening video file " << videoPath << endl;
return -1;
} // read first frame 读第一帧
cap >> frame; // draw bounding boxes over objects 在第一帧内确定对象框
/*
先在图像上画框,然后按ENTER确定画下一个框。按ESC退出画框开始执行程序
*/
cout << "\n==========================================================\n";
cout << "OpenCV says press c to cancel objects selection process" << endl;
cout << "It doesn't work. Press Esc to exit selection process" << endl;
cout << "\n==========================================================\n";
cv::selectROIs("MultiTracker", frame, bboxes, false); //自己设定对象的检测框
//x,y,width,height
//bboxes.push_back(Rect(388, 155, 30, 40));
//bboxes.push_back(Rect(492, 205, 50, 80));
// quit if there are no objects to track 如果没有选择对象
if (bboxes.size() < 1)
{
return 0;
} vector<Scalar> colors;
//给各个框涂色
getRandomColors(colors, bboxes.size()); // Create multitracker 创建多目标跟踪类
Ptr<MultiTracker> multiTracker = cv::MultiTracker::create(); // initialize multitracker 初始化
for (int i = 0; i < bboxes.size(); i++)
{
multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(bboxes[i]));
} // process video and track objects 开始处理图像
cout << "\n==========================================================\n";
cout << "Started tracking, press ESC to quit." << endl;
while (cap.isOpened())
{
// get frame from the video 逐帧处理
cap >> frame; // stop the program if reached end of video
if (frame.empty())
{
break;
} //update the tracking result with new frame 更新每一帧
bool ok = multiTracker->update(frame);
if (ok == true)
{
cout << "Tracking success" << endl;
}
else
{
cout << "Tracking failure" << endl;
}
// draw tracked objects 画框
for (unsigned i = 0; i < multiTracker->getObjects().size(); i++)
{
rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1);
} // show frame
imshow("MultiTracker", frame); // quit on x button
if (waitKey(1) == 27)
{
break;
}
}
waitKey(0);
return 0;
}

Python:

from __future__ import print_function
import sys
import cv2
from random import randint trackerTypes = ['BOOSTING', 'MIL', 'KCF','TLD', 'MEDIANFLOW', 'GOTURN', 'MOSSE', 'CSRT'] def createTrackerByName(trackerType):
# Create a tracker based on tracker name
if trackerType == trackerTypes[0]:
tracker = cv2.TrackerBoosting_create()
elif trackerType == trackerTypes[1]:
tracker = cv2.TrackerMIL_create()
elif trackerType == trackerTypes[2]:
tracker = cv2.TrackerKCF_create()
elif trackerType == trackerTypes[3]:
tracker = cv2.TrackerTLD_create()
elif trackerType == trackerTypes[4]:
tracker = cv2.TrackerMedianFlow_create()
elif trackerType == trackerTypes[5]:
tracker = cv2.TrackerGOTURN_create()
elif trackerType == trackerTypes[6]:
tracker = cv2.TrackerMOSSE_create()
elif trackerType == trackerTypes[7]:
tracker = cv2.TrackerCSRT_create()
else:
tracker = None
print('Incorrect tracker name')
print('Available trackers are:')
for t in trackerTypes:
print(t) return tracker if __name__ == '__main__': print("Default tracking algoritm is CSRT \n"
"Available tracking algorithms are:\n")
for t in trackerTypes:
print(t) trackerType = "CSRT" # Set video to load
videoPath = "video/run.mp4" # Create a video capture object to read videos
cap = cv2.VideoCapture(videoPath) # Read first frame
success, frame = cap.read()
# quit if unable to read the video file
if not success:
print('Failed to read video')
sys.exit(1) ## Select boxes
bboxes = []
colors = [] # OpenCV's selectROI function doesn't work for selecting multiple objects in Python
# So we will call this function in a loop till we are done selecting all objects
while True:
# draw bounding boxes over objects
# selectROI's default behaviour is to draw box starting from the center
# when fromCenter is set to false, you can draw box starting from top left corner
bbox = cv2.selectROI('MultiTracker', frame)
bboxes.append(bbox)
colors.append((randint(64, 255), randint(64, 255), randint(64, 255)))
print("Press q to quit selecting boxes and start tracking")
print("Press any other key to select next object")
k = cv2.waitKey(0) & 0xFF
if (k == 113): # q is pressed
break print('Selected bounding boxes {}'.format(bboxes)) ## Initialize MultiTracker
# There are two ways you can initialize multitracker
# 1. tracker = cv2.MultiTracker("CSRT")
# All the trackers added to this multitracker
# will use CSRT algorithm as default
# 2. tracker = cv2.MultiTracker()
# No default algorithm specified # Initialize MultiTracker with tracking algo
# Specify tracker type # Create MultiTracker object
multiTracker = cv2.MultiTracker_create() # Initialize MultiTracker
for bbox in bboxes:
multiTracker.add(createTrackerByName(trackerType), frame, bbox) # Process video and track objects
while cap.isOpened():
success, frame = cap.read()
if not success:
break # get updated location of objects in subsequent frames
success, boxes = multiTracker.update(frame) # draw tracked objects
for i, newbox in enumerate(boxes):
p1 = (int(newbox[0]), int(newbox[1]))
p2 = (int(newbox[0] + newbox[2]), int(newbox[1] + newbox[3]))
cv2.rectangle(frame, p1, p2, colors[i], 2, 1) # show frame
cv2.imshow('MultiTracker', frame) # quit on ESC button
if cv2.waitKey(1) & 0xFF == 27: # Esc pressed
break

4 参考

https://www.learnopencv.com/multitracker-multiple-object-tracking-using-opencv-c-python/

[OpenCV实战]16 使用OpenCV实现多目标跟踪的更多相关文章

  1. [OpenCV实战]14 使用OpenCV实现单目标跟踪

    目录 1 背景 1.1 什么是目标跟踪 1.2 跟踪与检测 2 OpenCV的目标跟踪函数 2.1 函数调用 2.2 函数详解 2.3 综合评价 3 参考 在本教程中,我们将了解OpenCV 3中引入 ...

  2. [OpenCV实战]48 基于OpenCV实现图像质量评价

    本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...

  3. [OpenCV实战]45 基于OpenCV实现图像哈希算法

    目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...

  4. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  5. [OpenCV实战]44 使用OpenCV进行图像超分放大

    图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...

  6. [OpenCV实战]47 基于OpenCV实现视觉显著性检测

    人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...

  7. [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡

    本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...

  8. [OpenCV实战]52 在OpenCV中使用颜色直方图

    颜色直方图是一种常见的图像特征,顾名思义颜色直方图就是用来反映图像颜色组成分布的直方图.颜色直方图的横轴表示像素值或像素值范围,纵轴表示该像素值范围内像素点的个数或出现频率.颜色直方图属于计算机视觉中 ...

  9. [OpenCV实战]31 使用OpenCV将一个三角形仿射变换到另一个三角形

    目录 1 什么是仿射变换? 2 使用OpenCV进行三角形仿射变换 2.1 定义输入和输出 2.2 计算边界框 2.3 裁剪图像和更改坐标 2.4 计算仿射变换矩形 2.5 应用仿射变换到三角形 2. ...

随机推荐

  1. Struts中action访问不到的原因。

    因为需要在项目中构造restful的链接,action通配符使用/进行分割.但是struts默认不支持反斜杠. 所以需要在Struts.xml配置 <constant name="st ...

  2. Linux 文件操作接口

    目录 Linux 文件操作接口 C语言文件操作接口 C语言文件描述 fopen() r模式打开文件 w模式打开文件 a模式打开文件 其他模式类似 fclose() fwrite() fread() 系 ...

  3. 齐博x1如果把万能表单直接插入到内容中去

    很多时候,你创建了一个万能表单可能像下面这个情况,在文章中加一个链接叫别人点击填表,其实这个很不人性化,用户也容易忽略. 其实你完全可以像下面这样,把表单直接引用到文章中来.给用户更直观的感觉 那是如 ...

  4. 各大厂 C/C++ 编程规范详解

    来吧!各大厂知名规范体系~ 各有特点各有侧重~ Google C++ Style Guide Google C++ Style Guide,[中文版],简称 GSG,谷歌的 C++ 编程规范,在国内有 ...

  5. 2022,一个Java程序猿的外设配置

    工欲善其事,必先利其器. 是的没错,我就是个器材党,哈哈.正赶上搬家布置了新桌面,经过我的精心挑选和安装,也是凑齐了我新一套的桌面外设.写下来记录一下. 键盘 套件:腹灵MK870 轴体:佳达隆G白P ...

  6. AK/SK加密认证

    AK/SK认证的实现 AK/SK概述 1.什么是AKSK ak/sk是一种身份认证方式,常用于系统间接口调用时的身份验证,其中ak为Access Key ID,sk为Secret Access Key ...

  7. Git配置和使用?Git你真的会用么?Git与SVN的主要区别

    1.Git环境配置    在学习Git之前,首先要知道什么是版本控制 1.1 版本控制:版本迭代.新的版本!版本管理器 版本控制是开发过程中用于管理我们的文件.目录或工程内容的修改内容,查看修改历史记 ...

  8. 强软弱引用,ThreadLocal和内存泄漏

    强引用 写法:Object obj=new Object() 引用强度:最强 只要被引用着,就不会被gc(垃圾回收)回收掉. 软引用 写法:SoftReference<String> sr ...

  9. 1742C

    题目链接 题目大意: 在一个8x8的方格中你每次可以将一行全部涂成红色或者将一列涂成蓝色.问最后一次操作是什么操作: 如果是行操作就输出R 如果是列操作就输出B 解题思路: 我们可一枚举每行每列,如果 ...

  10. Windows Server 2019 安装 Oracle 19C RAC(VMWare虚拟机环境)

    软件 Windows Server 2019 Standard Oracle 19C Oracle Grid 19 VMware Workstation 16 规划 共享存储,使用Windows Se ...