OpenCV之YOLOv3目标检测

news2024/11/19 6:18:20
  • 💂 个人主页:风间琉璃
  • 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
  • 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)订阅专栏

目录

前言

一、预处理

1.获取分类名

2.获取输出层名称

3.图像尺度变换

二、 模型加载和执行推理

三、输出解析

1.网络后处理

2.边界框绘制

3.推理时间

4.FPS计算


前言

YOLOv3(You Only Look Once version 3)是一种流行的实时目标检测算法,由Joseph Redmon等人开发。YOLOv3是YOLO系列的第三个版本,它在速度和准确性方面都取得了显著的改进,被广泛用于计算机视觉应用中的实时目标检测任务。

详细的网络原理参考:目标检测之YOLOv1-v3_风间琉璃•的博客-CSDN博客

参考文章:YOLOv3 – Deep Learning Based Object Detection – YOLOv3 with OpenCV ( Python / C++ )

 YOLOv3模型及配置文件下载

weights: https://pjreddie.com/media/files/yolov3.weights

cfg:https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg

coco: https://github.com/pjreddie/darknet/blob/master/data/coco.names

一、预处理

1.获取分类名

数据集采用的coco数据集,需要将coco.names包含训练模型的所有类名称加载到内存中。

vector<string> classes;  //标签名

string classespath = "F:/data/CQU/VS/YOLOv3/coco.names";

//得到网络对应的标签
void getclasses(string classespath )
{
	ifstream ifs(classespath);
	string line;
	while (getline(ifs, line)) 
	{ 
		classes.push_back(line); 
	}
}

最暴力的方法也可以打开文件,直接类别定义一个80个类别的变量存储。 

2.获取输出层名称

YOLOv3由于可以在三个不同的尺度上进行检测,也就是说它有三个输出层,因此我们需要获得它的三个输出层的名称。

//得到网络输出层的名称,yolov3有3种尺度输出
vector<String> getOutputsNames(const Net& net)
{
#if 1
	//直接获取模型中未连接到任何后续层的输出层的名称,一般是yolov3是三层输出层
	std::vector<String> names = net.getUnconnectedOutLayersNames();
	//for (int i = 0; i < names.size(); i++)
	//{
		//printf("output layer name : %s\n", names[i].c_str());
	//}
	return names;
		
#elif 0
	static vector<String> names;
	if (names.empty())
	{
		//获取模型中未连接到任何后续层的输出层的索引,一般是yolov3是三层输出层
		vector<int> outLayers = net.getUnconnectedOutLayers();
		//获取模型中的所有层的名称
		vector<String> layersNames = net.getLayerNames();

		names.resize(outLayers.size());
		for (size_t i = 0; i < outLayers.size(); ++i)
		{
			names[i] = layersNames[outLayers[i] - 1];
		}
	}
	return names;
#endif
}

这里提供了两种方法,直接获取三层输出层的名称可以使用 getUnconnectedOutLayersNames(),也可以先获取三个输出层的索引,然后通过getLayerNames获得整个网络层的名称,然后再通过索引获取最后输出三层的名称。

3.图像尺度变换

神经网络的输入图像需要采用称为blob的特定格式。从输入图像或视频流中读取帧后,将通过blobFromImage函数将其转换为神经网络的输入blob。

在此过程中,它使用比例因子1/255将图像像素值缩放到0到1的目标范围。它还将图像的大小调整为给定大小(416,416)而不进行裁剪。请注意,不在此处执行任何均值减法,因此将[0,0,0]传递给函数的mean参数,swapRB参数设置为true(即交换R和B),这里个人认为交换与否问题应该不大,看网上大佬的都写的交换。

int width = 416; 
int height = 416; 

//图像预处理
blobFromImage(frame, blob, 1 / 255.0, cv::Size(width, height), Scalar(0, 0, 0), true, false);

二、 模型加载和执行推理

加载网络直接使用readNetFromDarknet或者readNet都行。

String config = "F:/data/CQU/VS/YOLOv3/yolov3.cfg";
String weights = "F:/data/CQU/VS/YOLOv3/yolov3.weights";

//加载yolo网络模型
Net net = readNetFromDarknet(config, weights);

这里可以根据个人的情况设置是否使用CUDA加速,我编译过CUDA的OpenCV,所以这里利用第二种方式

#if 0   
	//cpu推理
	net.setPreferableBackend(DNN_BACKEND_OPENCV);
	net.setPreferableTarget(DNN_TARGET_CPU);

#elif 1
	//使用cuda加速
	net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
	net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);

#endif

 预处理和网络模型都加载好了就可以进行图像的预测了

//设置网络的输入
net.setInput(blob);

//执行推理,这里有多个输出层,好像不能使用 Mat outsnet.forward(outs, getOutputsNames(net));进行推理
//大概率是因为这里输出层是三层
vector<Mat> outs;
net.forward(outs, getOutputsNames(net));

预测的结果保存在outs的Mat类型矩阵中。接下来就需要对这个预测结果进行后处理。 

三、输出解析

1.网络后处理

网络输出的每个边界框都分别由一个包含着类别名字和5个元素的向量表示。前四个元素代表center_x, center_y, width和height。第五个元素表示包含着目标的边界框的置信度

其余的元素是和每个类别(如目标种类)有关的置信度。边界框分配给最高分数对应的那一种类。一个边界框的最高分数也叫做它的置信度(confidence)。如果边界框的置信度低于规定的阀值,算法上不再处理这个边界框。

置信度大于或等于置信度阀值的边界框,将进行非最大抑制。这会减少重叠的边界框数目。

float confThreshold = 0.5; //置信度
float nmsThreshold = 0.4;  // nms阈值

//使用非极大值抑制NMS来删除置信度较低的边界框
void postprocess(Mat& frame, const vector<Mat>& outs)
{
	//对象的类别标签索引
	vector<int> classIds;
	//目标检测的置信度
	vector<float> confidences;
	//目标检测的边界框信息
	vector<Rect> boxes;

	//对三层输出层分别处理
	for (int i = 0; i < outs.size(); i++)
	{
		//遍历来自网络的所有边界框,并仅保留具有高置信度分数的边界框,将框的类别标签分配为具有该框上最高分数的类别
		float* data = (float*)outs[i].data;
		//对某一层处理,输出包含5个元素:x,y,w,h,置信度
		for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
		{
			//获取置信度
			Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
			Point classIdPoint;
			double confidence; //最大置信度
	
			//获取置信度最大的数值和位置
			minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
			if (confidence > confThreshold)
			{
				int centerX = (int)(data[0] * frame.cols);
				int centerY = (int)(data[1] * frame.rows);
				int width = (int)(data[2] * frame.cols);
				int height = (int)(data[3] * frame.rows);
				int left = centerX - width / 2;
				int top = centerY - height / 2;

				classIds.push_back(classIdPoint.x);
				confidences.push_back((float)confidence);
				boxes.push_back(Rect(left, top, width, height));
			}
		}
	}

	//非极大值抑制以消除重叠度较高且置信度较低的边界框
	vector<int> indices; //NMSBoxes输出,包含经过非最大值抑制后所选的边界框的索引
	NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
	for (size_t i = 0; i < indices.size(); ++i)
	{
		int idx = indices[i];
		Rect box = boxes[idx];
		//在图像上绘制检测结果,包括类别标签、置信度和边界框
		drawPred(classIds[idx], confidences[idx], box.x, box.y,box.x + box.width, box.y + box.height, frame);
	}
}

非极大值抑制由参数nmsThreshold控制。如果nmsThreshold设置太少,比如0.1,我们可能检测不到相同或不同种类的重叠目标。如果设置得太高,比如1,可能出现一个目标有多个边界框包围。

NMSBoxes 是一个用于执行非极大值抑制(Non-Maximum Suppression,简称NMS)的函数,通常在目标检测和边界框预测等计算机视觉任务中使用。NMS 的目标是减少一组重叠的边界框,以便仅保留最相关的边界框,从而提高检测或识别性能。 

函数原型:

void cv::dnn::NMSBoxes(const std::vector<cv::Rect>& bboxes, const std::vector<float>& scores, float score_threshold, float nms_threshold, std::vector<int>& indices);

①bboxes:表示输入的边界框列表。每个边界框通常由 cv::Rect 类型表示,其中包含了左上角和右下角的坐标。这个参数用于传递待执行非极大值抑制的边界框列表。

②scores:包含与每个边界框相关的置信度或分数。这些分数用于度量边界框包含的对象的可信度或重要性。

③score_threshold:用于指定一个分数阈值。只有分数高于或等于此阈值的边界框才会被考虑进行非极大值抑制。

④nms_threshold:表示在执行非极大值抑制时两个边界框之间的IoU(交并比)阈值。如果两个边界框的IoU大于等于此阈值,则其中一个边界框将被抑制

⑤indices用于返回经过非极大值抑制后保留的边界框的索引列表。这些索引对应于输入边界框列表中的边界框,只有这些边界框被保留。

该函数的主要作用是根据分数和IoU阈值对输入的边界框列表进行非极大值抑制,然后将保留的边界框的索引存储在 indices 中,以供后续处理使用 。

2.边界框绘制

边界框的绘制就是根据将网络的输出结果经过非极大值抑制后的边界框,绘制在原图,并指定类别标签和置信度分数。

//画预测的目标bounding box
void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
{
	//绘制检测目标的矩形框
	rectangle(frame, Point(left, top), Point(right, bottom), Scalar(255, 178, 50), 2);

	//获取类别名称及其置信度
	string label = format("%.2f", conf);
	if (!classes.empty())
	{
		CV_Assert(classId < (int)classes.size());
		label = classes[classId] + ":" + label;
	}

	//在边界框的顶部显示标签
	int baseLine;
	Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
	//确保顶部位置足够高以容纳标签
	top = max(top, labelSize.height);
	rectangle(frame, Point(left, top - round(1.5 * labelSize.height)), Point(left + round(1.5 * labelSize.width), top + baseLine), Scalar(255, 255, 255), FILLED);
	putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 1);
}

3.推理时间

使用OpenCV dnn进行推理,可以测量推理的时间。主要是使用getPerfProfile函数。其函数原型如下:

cv::Mat cv::dnn::Net::getPerfProfile(std::vector<double>& timings) const;

timings(输出参数):用于存储每个网络层的推理时间。这是一个输出参数,getPerfProfile 会填充这个向量,使您能够获取每个层的执行时间信息。 

getPerfProfile 函数的主要作用是分析深度学习模型的性能,并返回每个网络层的推理时间。让利用freq 将时间从s转换为ms。

//函数getPerfProfile返回推理的总时间(t)以及每个层的时间(在layersTimes中)
//存储每一次推理时间
vector<double> layersTimes; 
//getTickFrequency() 返回时钟频率,通过除以1000,freq将其转换为毫秒,将时间单位从秒(s)转换为毫秒(ms)的缩放因子
double freq = getTickFrequency() / 1000;
//获取推理总时间,并转换将时间单位转换为毫秒
double t = net.getPerfProfile(layersTimes) / freq;

string label = format("Inference time: %.2fms", t);
putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255));

4.FPS计算

在网络推理(如深度学习模型的推理)中,帧率(Frames Per Second,FPS)是一项关键的性能指标,用于表示每秒处理的图像帧数量。通常,帧率越高,系统性能越好。

在图像进行目标检测之前加入下面一句代码,获取当前系统的计时周期

//获得当前系统的计时间周期数,求FPS
double t = (double)getTickCount();

 然后在图像目标检测完加入下面的代码,计算FPS并显示

//FPS计算
t = ((double)getTickCount() - t) / getTickFrequency();//求输入帧后经过的周期数/每秒系统计的周期数=一帧用时多少秒
double fps = 1.0 / t;//求倒数得到每秒经过多少帧,即帧率
string text = format("FPS:%.2f",fps);
cv::putText(frame, text, Point(10, 50), FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2, 8, 0);

运行结果:

YOLOv3 C++

程序源码:下载链接:https://download.csdn.net/download/qq_53144843/88350694
 

// YOLOv3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//


#include <fstream>
#include <sstream>
#include <iostream>

#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;
using namespace cv::dnn;
using namespace std;

//初始化参数
float confThreshold = 0.5; //置信度
float nmsThreshold = 0.4;  // nms阈值
int width = 416; 
int height = 416; 
vector<string> classes;  //标签名

//得到网络输出层名称
vector<String> getOutputsNames(const Net& net); 
//绘制bounding box
void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame);
//解析输出
void postprocess(Mat& frame, const vector<Mat>& outs);
//图片检测
void detect_image(string image_path, string config, string weights, string classespath);
//目标检测
void detect_video(string video_path, string config, string weights, string classespath);

int main()
{
	String config = "F:/data/CQU/VS/YOLOv3/yolov3.cfg";
	String weights = "F:/data/CQU/VS/YOLOv3/yolov3.weights";
	string classespath = "F:/data/CQU/VS/YOLOv3/coco.names";

	string image_path = "F:/data/CQU/VS/YOLOv3/3.jpg";
	string video_path = "F:/data/CQU/VS/YOLOv3/car.mp4";
#if 0
	detect_image(image_path, config, weights, classespath);

#elif 1
	detect_video(video_path, config, weights, classespath);

#endif

	return 0;
}


//得到网络对应的标签
void getclasses(string classespath )
{
	ifstream ifs(classespath);
	string line;
	while (getline(ifs, line)) 
	{ 
		classes.push_back(line); 
	}
}


//得到网络输出层的名称,yolov3有3种尺度输出
vector<String> getOutputsNames(const Net& net)
{
#if 1
	//直接获取模型中未连接到任何后续层的输出层的名称,一般是yolov3是三层输出层
	std::vector<String> names = net.getUnconnectedOutLayersNames();
	//for (int i = 0; i < names.size(); i++)
	//{
		//printf("output layer name : %s\n", names[i].c_str());
	//}
	return names;
		
#elif 0
	static vector<String> names;
	if (names.empty())
	{
		//获取模型中未连接到任何后续层的输出层的索引,一般是yolov3是三层输出层
		vector<int> outLayers = net.getUnconnectedOutLayers();
		//获取模型中的所有层的名称
		vector<String> layersNames = net.getLayerNames();

		names.resize(outLayers.size());
		for (size_t i = 0; i < outLayers.size(); ++i)
		{
			names[i] = layersNames[outLayers[i] - 1];
		}
	}
	return names;
#endif
}




//画预测的目标bounding box
void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
{
	//绘制检测目标的矩形框
	rectangle(frame, Point(left, top), Point(right, bottom), Scalar(255, 178, 50), 2);

	//获取类别名称及其置信度
	string label = format("%.2f", conf);
	if (!classes.empty())
	{
		CV_Assert(classId < (int)classes.size());
		label = classes[classId] + ":" + label;
	}

	//在边界框的顶部显示标签
	int baseLine;
	Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
	//确保顶部位置足够高以容纳标签
	top = max(top, labelSize.height);
	rectangle(frame, Point(left, top - round(1.5 * labelSize.height)), Point(left + round(1.5 * labelSize.width), top + baseLine), Scalar(255, 255, 255), FILLED);
	putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 1);
}


//使用非极大值抑制NMS来删除置信度较低的边界框
void postprocess(Mat& frame, const vector<Mat>& outs)
{
	//对象的类别标签索引
	vector<int> classIds;
	//目标检测的置信度
	vector<float> confidences;
	//目标检测的边界框信息
	vector<Rect> boxes;

	//对三层输出层分别处理
	for (int i = 0; i < outs.size(); i++)
	{
		//遍历来自网络的所有边界框,并仅保留具有高置信度分数的边界框,将框的类别标签分配为具有该框上最高分数的类别
		float* data = (float*)outs[i].data;
		//对某一层处理,输出包含5个元素:x,y,w,h,置信度
		for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
		{
			//获取置信度
			Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
			Point classIdPoint;
			double confidence; //最大置信度
	
			//获取置信度最大的数值和位置
			minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
			if (confidence > confThreshold)
			{
				int centerX = (int)(data[0] * frame.cols);
				int centerY = (int)(data[1] * frame.rows);
				int width = (int)(data[2] * frame.cols);
				int height = (int)(data[3] * frame.rows);
				int left = centerX - width / 2;
				int top = centerY - height / 2;

				classIds.push_back(classIdPoint.x);
				confidences.push_back((float)confidence);
				boxes.push_back(Rect(left, top, width, height));
			}
		}
	}

	//非极大值抑制以消除重叠度较高且置信度较低的边界框
	vector<int> indices; //NMSBoxes输出,包含经过非最大值抑制后所选的边界框的索引
	NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
	for (size_t i = 0; i < indices.size(); ++i)
	{
		int idx = indices[i];
		Rect box = boxes[idx];
		//在图像上绘制检测结果,包括类别标签、置信度和边界框
		drawPred(classIds[idx], confidences[idx], box.x, box.y,box.x + box.width, box.y + box.height, frame);
	}
}



void detect_image(string image_path, string config, string weights, string classespath)
{
	//加载目标检测标签
	getclasses(classespath);

	//加载yolo网络模型
	Net net = readNetFromDarknet(config, weights);

	
#if 0   
	//cpu推理
	net.setPreferableBackend(DNN_BACKEND_OPENCV);
	net.setPreferableTarget(DNN_TARGET_CPU);

#elif 1
	//使用cuda加速
	net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
	net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);

#endif

	cv::Mat frame = cv::imread(image_path);
	
	//图片预处理
	Mat blob = blobFromImage(frame, 1 / 255.0, cv::Size(width, height), Scalar(0, 0, 0), true, false);
	
	//设置网络的输入
	net.setInput(blob);

	//执行推理,这里有多个输出层,好像不能使用 Mat outsnet.forward(outs, getOutputsNames(net));进行推理
	//大概率是因为这里输出层是三层
	vector<Mat> outs;
	net.forward(outs, getOutputsNames(net));

	//剔除置信度较小的 bounding boxes
	postprocess(frame, outs);

	//函数getPerfProfile返回推理的总时间(t)以及每个层的时间(在layersTimes中)
	//存储每一次推理时间
	vector<double> layersTimes; 
	//getTickFrequency() 返回时钟频率,通过除以1000,freq将其转换为毫秒,将时间单位从秒(s)转换为毫秒(ms)的缩放因子
	double freq = getTickFrequency() / 1000;
	//获取推理总时间,并转换将时间单位转换为毫秒
	double t = net.getPerfProfile(layersTimes) / freq;

	string label = format("Inference time: %.2fms", t);
	//putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255));
	imshow("YOLOv3", frame);
	cv::waitKey(0);
}

void detect_video(string video_path, string config, string weights, string classespath)
{
	//加载目标检测标签
	getclasses(classespath);

	//加载网络
	Net net = readNetFromDarknet(config, weights);

#if 0
	//cpu推理
	net.setPreferableBackend(DNN_BACKEND_OPENCV);
	net.setPreferableTarget(DNN_TARGET_CPU);

#elif 1
	//使用cuda加速
	net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
	net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);

#endif

	// 打开视频流,使用视频文件的路径. 0表示默认摄像头
	VideoCapture capture(video_path);
	// 检查是否成功打开视频流
	if(!capture.isOpened())
	{
		std::cerr << "Error: Could not open video stream." << std::endl;

	}
	Mat frame, blob;

	//处理每一帧
	while(capture.read(frame))
	{
		//图像预处理
		blobFromImage(frame, blob, 1 / 255.0, cv::Size(width, height), Scalar(0, 0, 0), true, false);

		//设置网络的输入
		net.setInput(blob);

		//获得当前系统的计时间周期数,求FPS
		double t = (double)getTickCount();

		//执行推理
		vector<Mat> outs;
		net.forward(outs, getOutputsNames(net));

		//剔除置信度较小的 bounding boxes
		postprocess(frame, outs);
		
		//将图像帧转换为CV_8U格式显示
		Mat detectedFrame;
		frame.convertTo(detectedFrame, CV_8U);

		//FPS计算
		t = ((double)getTickCount() - t) / getTickFrequency();//求输入帧后经过的周期数/每秒系统计的周期数=一帧用时多少秒
		double fps = 1.0 / t;//求倒数得到每秒经过多少帧,即帧率
		string text = format("FPS:%.2f",fps);
		cv::putText(frame, text, Point(10, 50), FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2, 8, 0);
		imshow("YOLOv3", frame);
		int c = waitKey(1);
		if (c == 27)
		{
			break;
		}
	}
	capture.release();
	waitKey(0);
}

结束语
感谢你观看我的文章呐~本次航班到这里就结束啦 🛬

希望本篇文章有对你带来帮助 🎉,有学习到一点知识~

躲起来的星星🍥也在努力发光,你也要努力加油(让我们一起努力叭)。

最后,博主要一下你们的三连呀(点赞、评论、收藏),不要钱的还是可以搞一搞的嘛~

不知道评论啥的,即使扣个666也是对博主的鼓舞吖 💞 感谢 💐

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

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

相关文章

【JavaSE笔记】初识Java

一、前言 Java是一种非常优秀的程序设计语言&#xff0c;它具有令人赏心悦目的语法和易于理解的语义。 本文将通过一个简单的Java程序&#xff0c;介绍Java的一些基础内容。 二、Java基本结构 1、简单的Java程序 从最简单的一个Java程序开始逐渐了解Java语言。 以下是一段…

数学建模——微分方程介绍

一、基础知识 1、一阶微分方程 称为一阶微分方程。y(x0)y0为定解条件。 其常规求解方法&#xff1a; &#xff08;1&#xff09;变量分离 再两边积分就可以求出通解。 &#xff08;2&#xff09;一阶线性求解公式 通解公式&#xff1a; 有些一阶微分方程需要通过整体代换…

echarts的折线图,在点击图例后,提示出现变化,不报错。tooltip的formatter怎么写

在点击图例的年后&#xff0c;提示框会相应的变化&#xff0c;多选和单选都会响应变化。tooptip的重度在formatter tooltip:{show:true,trigger:"axis",alwaysShowContent:true,triggerOn:"mousemove",textStyle:{color:"#fff"},backgroundColor…

结构体-时间的计算

任务描述 本关任务需要你编写函数计算一个时间之前“xx小时xx分xx秒”的时间是多少。 以24小时制的格式记录当前时间&#xff0c;譬如“09:19:52”&#xff0c;表示上午9点19分52秒&#xff0c;则“1小时20分30秒”前的时间应该是“同一天”的“07:59:22”。 提示&#xff1a;…

Scapy 解析 pcap 文件从HTTP流量中提取图片

Scapy 解析 pcap 文件从HTTP流量中提取图片 前言一、网络环境示例二、嗅探流量示例三、pcap 文件处理最后参考 ​ 作者&#xff1a;高玉涵 ​ 时间&#xff1a;2023.9.17 10:25 ​ 环境&#xff1a;Linux kali 5.15.0-kali3-amd64&#xff0c;Python 3.11.4&#xff0c;scapy…

解决Springboot使用Junit测试时对数据库的修改无效

现象 在使用Junit做单元测试的过程中&#xff0c;比如对mybatis的dao进行单元测试&#xff0c;发现对数据库的select操作正常&#xff0c;可以获取数据&#xff0c;但insert、update、delete操作即使运行不报错&#xff0c;仍然不能不能对数据产生修改和插入。 原因和解决 原…

d3dx9_42.dll丢失修复指南,如何修复丢失的d3dx9_42.dll文件

d3dx9_42.dll是DirectX 9的一个动态链接库文件&#xff0c;它是许多游戏和软件的必需组件。如果缺少这个文件&#xff0c;可能会导致程序无法正常运行。本文将详细讲解d3dx9_42.dll的作用以及丢失的原因&#xff0c;并提供5种修复方法。 一、d3dx9_42.dll的作用 1. d3dx9_42.d…

IDEA创建完Maven工程后,右下角一直显示正在下载Maven插件

原因&#xff1a; 这是由于新建的Maven工程&#xff0c;IDEA会用它内置的默认的Maven版本&#xff0c;使用国外的网站下载Maven所需的插件&#xff0c;速度很慢 。 解决方式&#xff1a; 每次创建 Project 后都需要设置 Maven 家目录位置&#xff08;就是我们自己下载的Mav…

操作系统学习笔记-精简复习版

文章目录 操作系统概述1、操作系统2、主要功能3、用户态和内核态4、系统调用 进程管理1、进程和线程2、引入线程的好处3、线程间同步4、进程控制块 PCB5、进程的状态6、进程的通信方式7、进程的调度算法8、僵尸进程&孤儿进程9、死锁 内存管理1、内存碎片2、内存管理3、虚拟…

2023面试知识点一

1、新生代和老年代的比例 默认的&#xff0c;新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )&#xff0c;即&#xff1a;新生代 ( Young ) 1/3 的堆空间大小。老年代 ( Old ) 2/3 的堆空间大小。其中&#xff0c;新生代 ( …

WebGoat搭建和Yakit学习

环境搭建 jdk版本&#xff1a;openjdk version "17.0.5“ WebGoat版本&#xff1a;webgoat-server-8.1.0.jar 环境不同有很大可能不能搭建成功 运行命令&#xff1a;java -jar webgoat-server-8.1.0.jar --server.port8888 --server.address192.168.142.131 搭建完成后…

SOLIDWORKS Composer位置关键帧的使用

SOLIDWORKS Composer是专业的SOLIDWORKS及3D文件处理的动画制作软件&#xff0c;作为SOLIDWORKS 产品线下的一个明星存在。 SOLIDWORKS Composer几乎可以处理任何SOLIDWORKS的模型文件并将之转化成可以动作的机械动画&#xff0c;可以引用在企业的网站、产品说明书以及工作指导…

卡尔曼滤波(Kalman Filter)原理浅析-数学理论推导-1

目录 前言数学理论推导1. 递归算法2. 数学基础结语参考 前言 最近项目需求涉及到目标跟踪部分&#xff0c;准备从 DeepSORT 多目标跟踪算法入手。DeepSORT 中涉及的内容有点多&#xff0c;以前也就对其进行了简单的了解&#xff0c;但是真正去做发现总是存在这样或者那样的困惑…

springboot和vue:一、cs/bs区别+maven介绍与其仓库配置

cs/bs的区别&#xff1a; ​​ C/S&#xff1a; 1.交互性强&#xff0c;具有安全访问模式&#xff0c;网络流量低&#xff0c;响应速度快&#xff0c; 2.因为客户端负责大多数业务逻辑和UI演示&#xff0c;所以也被称为胖客户端。 3.C/S结构的软件需要针对不同的操作系统开发…

QT之QML开发 锚点布局

QML中经常会用到锚点布局&#xff0c;本篇简单演示一下锚点布局的使用。 目录 1.锚点布局的说明 2.锚点布局三等分窗口 3.锚点布局拆分窗口 4.窗口拖动 5.完整代码 1.锚点布局的说明 2.锚点布局三等分窗口 main.qml import QtQuick 2.15 import QtQuick.Window 2.15 imp…

二叉树的具体原理及实现

文章目录 一.树的专业术语二.二叉树的原理三.常见的二叉树分类1.完全二叉树2.平衡二叉树3.二叉搜索树 四.二叉搜索树算法具体实现五.二叉搜索树具体实现代码 一.树的专业术语 首先先介绍树的专业术语 二.二叉树的原理 二叉搜索树&#xff08;Binary Search Tree&#xff0c…

Java判断时间间隔来决定任务执行

数据库中的任务记录表的某条记录&#xff0c;状态一直无法变更&#xff0c;导致整个任务进程卡住&#xff0c;看了代码如下&#xff0c; 正常情况是要走到这个else里面&#xff0c;执行dockingGroup.setStatus(JobStatus.FAILED.getCode());将任务状态改为失败的 &#xff0c;查…

Windows/Linux(命令、安装包和源码安装)平台各个版本QT详细安装教程

前言 本文章主要介绍了Windows/Linux平台下&#xff0c;QT4&#xff0c;QT5&#xff0c;QT6的安装步骤。为什么要把QT版本分开介绍呢&#xff0c;因为这三个版本&#xff0c;安装步骤都不一样。Windows平台&#xff0c;QT4的Qt Creator&#xff0c;QT库和编译器是分开的&#…

【奇思妙想】【节省磁盘空间】我有一些文件,我不想移动它们,但又想节省磁盘空间,该怎么做呢?

2023年9月17日&#xff0c;周日晚上 今天晚上整理电脑的磁盘空间时&#xff0c;无意间想到了这个事情 我有一个文件夹 这个文件夹里面有很多不常用的文件 但我不想把它们移走或删除&#xff0c;那我怎么才能从中节省磁盘空间呢&#xff1f; 很简单&#xff0c;把这些不常用的…

Maven常见面试题总结

Maven简介 Maven 是一个项目管理和整合工具。Maven 包含了一个项目对象模型 (Project Object Model)&#xff0c;一组标准集合&#xff0c;一个项目生命周期管理系统(Project Lifecycle Management System)&#xff0c;一个依赖管理系统(Dependency Management System)&#x…