[OpenCV实战]16 使用OpenCV实现多目标跟踪
目录
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实现多目标跟踪的更多相关文章
- [OpenCV实战]14 使用OpenCV实现单目标跟踪
		
目录 1 背景 1.1 什么是目标跟踪 1.2 跟踪与检测 2 OpenCV的目标跟踪函数 2.1 函数调用 2.2 函数详解 2.3 综合评价 3 参考 在本教程中,我们将了解OpenCV 3中引入 ...
 - [OpenCV实战]48 基于OpenCV实现图像质量评价
		
本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...
 - [OpenCV实战]45 基于OpenCV实现图像哈希算法
		
目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...
 - [OpenCV实战]50 用OpenCV制作低成本立体相机
		
本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...
 - [OpenCV实战]44 使用OpenCV进行图像超分放大
		
图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...
 - [OpenCV实战]47 基于OpenCV实现视觉显著性检测
		
人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...
 - [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡
		
本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...
 - [OpenCV实战]52 在OpenCV中使用颜色直方图
		
颜色直方图是一种常见的图像特征,顾名思义颜色直方图就是用来反映图像颜色组成分布的直方图.颜色直方图的横轴表示像素值或像素值范围,纵轴表示该像素值范围内像素点的个数或出现频率.颜色直方图属于计算机视觉中 ...
 - [OpenCV实战]31 使用OpenCV将一个三角形仿射变换到另一个三角形
		
目录 1 什么是仿射变换? 2 使用OpenCV进行三角形仿射变换 2.1 定义输入和输出 2.2 计算边界框 2.3 裁剪图像和更改坐标 2.4 计算仿射变换矩形 2.5 应用仿射变换到三角形 2. ...
 
随机推荐
- 配置DNS域名解析服务
			
概: DNS技术作为互联网基础设施中的重要一环,为用户提供不间断.稳定且快速的域名查询服务,保证互联网正常运转.在互联网中,用户基本上都是基于DNS服务,使用域名访问网络上的计算机,DNS服务是我 ...
 - uoj220【NOI2016】网格
			
刚了几个小时啊,这tm要是noi我怕不是直接滚粗了.我判答案为1的情况试了几种做法,最后终于想到了一个靠谱的做法,然后细节巨多,调了好久,刚拿到97分时代码有6.2KB了,后来发现有些东西好像没啥用就 ...
 - 在js中正则表达式验证小时分钟,将输入的字符串转换为对应的小时和分钟
			
文章目录 1.预备知识 2.在js中的代码片段 3.测试结果 1.预备知识 splict()方法 Date()的相关方法 setHours()的用法 2.在js中的代码片段 //验证小时和分钟 var ...
 - JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象
			
文章目录 1.工厂方法创建对象 1.1 代码块 1.2.测试结果 2.原型对象 2.1 代码 2.2 测试结果 3.toString 3.1 代码 3.2 测试结果 4.数组 4.1 代码 5.字面量 ...
 - 1、在SrpingBoot的环境当中使用JSP及相关功能
			
创建webapp目录 由于SpringBoot项目不建议直接访问jsp页面,但是我现在要做的事情需要去访问,那么我就需要在原有的项目基础上为访问jsp页面进行一个调整 首先在项目当中,java和res ...
 - 44.drf缓存
			
DRF原有缓存 Django缓存.配置:https://www.cnblogs.com/Mickey-7/p/15792083.html Django为基于类的视图提供了一个 method_dec ...
 - CSS 属性选择器 ~=, |=, ^=, $=, *= 的区别
			
CSS 属性选择器 ~=, |=, ^=, $=, *= 的区别 总结: "value 是完整单词" 类型的比较符号: ~=, |= "拼接字符串" 类型的比较 ...
 - 读书笔记《A Philosophy of Software Design - John Ousterhout 软件设计哲学》
			
软件设计哲学这本书很薄,值得一读.这本书将大家平时碰到的很多软件问题从更深刻的层面进行了抽象分析,同时又给出了具体的解决方案.可以说既有理论高度,又能贴近实践. 但针对软件问题,这本书并没有提出太多与 ...
 - 项目上的业务《接收一个xml信息包进行解析,xml中包含base64解析为电子文件》
			
我就直接贴代码了,不太会说,附上注释. ps:需要根据系统字段和xml里面的标签字段进行建表,之后把xml标签的值进行添加.创建表的方法就是拼的sql. // 在线接收接口 @Transactiona ...
 - 前端项目通过‘URL 重写’部署在 IIS 中,访问 WebAPI 接口
			
〇.前言 在前端项目开发时,我们可以通过配置代理 proxy 来访问本地或者是远程接口,但是部署后代理就失效了.如果项目部署在 IIS 上,就没法去对应到指定接口,此时就需要 IIS 中的'URL重写 ...