OpenCV实现手势虚拟拖拽
前言:
Hello大家好,我是Dream。 今天来学习一下如何使用OpenCV实现手势虚拟拖拽,欢迎大家一起前来探讨学习~
一、主要步骤及库的功能介绍
1.主要步骤
要实现本次实验,主要步骤如下:
- 导入OpenCV库。
- 通过OpenCV读取摄像头的视频流。
- 使用肤色检测算法(如色彩空间转换和阈值分割)来识别手部区域。
- 对手部区域进行轮廓检测,找到手的轮廓。
- 根据手的轮廓,获取手指关键点的像素坐标。对于拖拽手势,可以关注食指和中指的位置。
- 计算食指和中指指尖之间的距离并判断是否满足条件触发拖拽动作。
- 如果满足条件,可以使用勾股定理计算距离,并将矩形区域变色以示触发拖拽。
- 根据手指的位置更新矩形的坐标,使矩形跟随手指运动。
- 当手指放开时停止矩形的移动。
2.需要的库介绍
导入的库在实现手势虚拟拖拽的代码中起着重要的作用,下面是对每个库的简要介绍:
-
OpenCV (cv2): OpenCV是一个开源的计算机视觉库,提供了丰富的图像和视频处理功能,使用OpenCV来读取摄像头视频流、进行图像处理和计算。
-
mediapipe (mp): mediapipe提供一系列预训练的机器学习模型和工具,用于实现计算机视觉和机器学习任务。我们使用mediapipe来进行手部关键点检测和姿势估计。
-
time: 我们使用time库来计时和进行时间相关的操作。
-
math: 在本次实验我们使用math库来计算距离和角度。
二、导入所需要的模块
# 导入OpenCV
import cv2
# 导入mediapipe
import mediapipe as mp# 导入其他依赖包
import time
import math
三、方块管理类
(SquareManager)是一个方块管理器,用于创建、显示、更新和处理方块的相关操作。
1.初始化方块管理器
初始化方块管理器,传入方块的长度(rect_width)
作为参数,并初始化方块列表、距离、激活状态和激活的方块ID等属性。
class SquareManager:def __init__(self, rect_width):# 方框长度self.rect_width = rect_width# 方块列表self.square_count = 0self.rect_left_x_list = []self.rect_left_y_list = []self.alpha_list = []# 中指与矩形左上角点的距离self.L1 = 0self.L2 = 0# 激活移动模式self.drag_active = False# 激活的方块IDself.active_index = -1
2.创建一个方块
创建一个方块,将方块的左上角坐标和透明度添加到相应的列表中。
# 创建一个方块,但是没有显示def create(self, rect_left_x, rect_left_y, alpha=0.4):# 将方块的左上角坐标和透明度添加到相应的列表中self.rect_left_x_list.append(rect_left_x)self.rect_left_y_list.append(rect_left_y)self.alpha_list.append(alpha)self.square_count += 1
3.更新显示方块的位置
根据方块的状态,在图像上绘制方块,并使用透明度将叠加图像叠加到原始图像上。
# 更新显示方块的位置def display(self, class_obj):# 遍历方块列表for i in range(0, self.square_count):x = self.rect_left_x_list[i]y = self.rect_left_y_list[i]alpha = self.alpha_list[i]overlay = class_obj.image.copy()# 如果方块处于激活状态,绘制紫色方块;否则绘制蓝色方块if (i == self.active_index):cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 255), -1)else:cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 0), -1)# 使用透明度将叠加图像叠加到原始图像上class_obj.image = cv2.addWeighted(overlay, alpha, class_obj.image, 1 - alpha, 0)
4.判断落点方块
判断给定的坐标是否在方块内,并返回方块的ID。
# 判断落在哪个方块上,返回方块的IDdef checkOverlay(self, check_x, check_y):# 遍历方块列表for i in range(0, self.square_count):x = self.rect_left_x_list[i]y = self.rect_left_y_list[i]# 检查指定点是否在方块内if (x < check_x < (x + self.rect_width)) and (y < check_y < (y + self.rect_width)):# 保存被激活的方块IDself.active_index = ireturn ireturn -1
5.计算距离、更新位置
setLen 方法:计算激活方块与指尖的距离。
updateSquare 方法:根据给定的新坐标更新激活方块的位置。
# 计算与指尖的距离def setLen(self, check_x, check_y):# 计算距离self.L1 = check_x - self.rect_left_x_list[self.active_index]self.L2 = check_y - self.rect_left_y_list[self.active_index]# 更新方块位置def updateSquare(self, new_x, new_y):self.rect_left_x_list[self.active_index] = new_x - self.L1self.rect_left_y_list[self.active_index] = new_y - self.L2
三、识别控制类
1.初始化识别控制类
class HandControlVolume:def __init__(self):# 初始化mediapipeself.mp_drawing = mp.solutions.drawing_utilsself.mp_drawing_styles = mp.solutions.drawing_stylesself.mp_hands = mp.solutions.hands# 中指与矩形左上角点的距离self.L1 = 0self.L2 = 0# image实例,以便另一个类调用self.image = None
HandControlVolume
用于初始化mediapipe
以及存储中指与矩形左上角点的距离和image
实例。
__init__
方法:在初始化对象时,初始化mediapipe,包括drawing_utils、drawing_styles和hands。此外,还初始化了中指与矩形左上角点的距离和image实例。
通过mediapipe,可以进行手部关键点检测和姿势估计,进而进行手势识别和处理。为了使其他类能够调用image实例,将其作为该类的属性进行存储,方便地处理手势识别和控制操作。
2.主函数
这部分代码主要用于初始化和准备处理视频流以进行手势识别和交互。
def recognize(self):# 计算刷新率fpsTime = time.time()# OpenCV读取视频流cap = cv2.VideoCapture(0)# 视频分辨率resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))# 画面显示初始化参数rect_percent_text = 0# 初始化方块管理器squareManager = SquareManager(150)# 创建多个方块for i in range(0, 5):squareManager.create(200 * i + 20, 200, 0.6)with self.mp_hands.Hands(min_detection_confidence=0.7,min_tracking_confidence=0.5,max_num_hands=2) as hands:while cap.isOpened():# 初始化矩形success, self.image = cap.read()self.image = cv2.resize(self.image, (resize_w, resize_h))if not success:print("空帧.")continue
-
resize_w
和resize_h
:根据摄像头分辨率获取的视频帧的宽度和高度,并作为后续处理的图像尺寸进行缩放。 -
rect_percent_text
:画面显示初始化参数,可能被用于屏幕上的文本显示。 -
squareManager
:初始化了方块管理器类的实例,并设置方块的长度为150。
使用一个循环,创建了五个方块,并通过create
方法将其添加到方块管理器中。进入循环,从视频流中读取帧图像,并将其调整为指定的尺寸。如果成功读取帧图像,则会进一步处理,否则将输出错误消息。
3.提高性能和处理图像
self.image.flags.writeable = False# 转为RGBself.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)# 镜像self.image = cv2.flip(self.image, 1)# mediapipe模型处理results = hands.process(self.image)self.image.flags.writeable = Trueself.image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR)
-
self.image.flags.writeable = False
:设置图像为不可写,以提高性能并避免数据拷贝。 -
self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
:将BGR格式的图像转换为RGB格式,因为mediapipe模型处理的输入图像需要是RGB格式。 -
self.image = cv2.flip(self.image, 1)
:将图像进行镜像翻转,以与mediapipe模型期望的手部位置对应。 -
results = hands.process(self.image)
:将处理后的图像传递给mediapipe的hands模型,进行手势识别和处理。 -
self.image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR)
:将图像从RGB格式转换回BGR格式,以便后续的显示和处理。
4.检测手掌,标记关键点和连接关系
if results.multi_hand_landmarks:# 遍历每个手掌for hand_landmarks in results.multi_hand_landmarks:# 在画面标注手指self.mp_drawing.draw_landmarks(self.image,hand_landmarks,self.mp_hands.HAND_CONNECTIONS,self.mp_drawing_styles.get_default_hand_landmarks_style(),self.mp_drawing_styles.get_default_hand_connections_style())
-
if results.multi_hand_landmarks:
:检查是否检测到手掌。如果有检测到手掌,则进入下一步处理;否则跳过。 -
for hand_landmarks in results.multi_hand_landmarks:
:遍历检测到的每个手掌。 -
self.mp_drawing.draw_landmarks
:使用mediapipe的draw_landmarks
方法,在图像上标记手指的关键点和连接关系。
self.image
:输入的图像。hand_landmarks
:手掌的关键点。
self.mp_hands.HAND_CONNECTIONS
:手指之间的连接关系。
5.解析检测到的手掌并提取手指的关键点
检测到的手掌并提取手指的关键点,然后将手指的坐标存储起来。
landmark_list = []# 用来存储手掌范围的矩形坐标paw_x_list = []paw_y_list = []for landmark_id, finger_axis in enumerate(hand_landmarks.landmark):landmark_list.append([landmark_id, finger_axis.x, finger_axis.y,finger_axis.z])paw_x_list.append(finger_axis.x)paw_y_list.append(finger_axis.y)if landmark_list:# 比例缩放到像素ratio_x_to_pixel = lambda x: math.ceil(x * resize_w)ratio_y_to_pixel = lambda y: math.ceil(y * resize_h)# 设计手掌左上角、右下角坐标paw_left_top_x, paw_right_bottom_x = map(ratio_x_to_pixel,[min(paw_x_list), max(paw_x_list)])paw_left_top_y, paw_right_bottom_y = map(ratio_y_to_pixel,[min(paw_y_list), max(paw_y_list)])# 给手掌画框框cv2.rectangle(self.image, (paw_left_top_x - 30, paw_left_top_y - 30),(paw_right_bottom_x + 30, paw_right_bottom_y + 30), (0, 255, 0), 2)# 获取中指指尖坐标middle_finger_tip = landmark_list[12]middle_finger_tip_x = ratio_x_to_pixel(middle_finger_tip[1])middle_finger_tip_y = ratio_y_to_pixel(middle_finger_tip[2])# 获取食指指尖坐标index_finger_tip = landmark_list[8]index_finger_tip_x = ratio_x_to_pixel(index_finger_tip[1])index_finger_tip_y = ratio_y_to_pixel(index_finger_tip[2])# 中间点between_finger_tip = (middle_finger_tip_x + index_finger_tip_x) // 2, (middle_finger_tip_y + index_finger_tip_y) // 2thumb_finger_point = (middle_finger_tip_x, middle_finger_tip_y)index_finger_point = (index_finger_tip_x, index_finger_tip_y)
-
landmark_list
:一个列表,用于存储手指的关键点信息。 -
paw_x_list
和paw_y_list
:用于存储手掌范围的矩形框的横纵坐标。 -
在循环中,将每个手指的关键点的索引、x坐标、y坐标和z坐标存储在
landmark_list
中,同时将手掌范围的横纵坐标存储在paw_x_list
和paw_y_list
中。
如果landmark_list
不为空,即有手指的关键点被检测到ratio_x_to_pixel
和 ratio_y_to_pixel
:两个lambda函数,用于将相对比例转换为像素坐标的函数。根据手掌范围的矩形坐标,计算手掌区域的左上角和右下角坐标,并画出方框。使用landmark_list
中的信息获取中指指尖坐标和食指指尖坐标,并将它们转换为像素坐标。计算中指指尖坐标和食指指尖坐标的中间点。将中指指尖的坐标和食指指尖的坐标存储在thumb_finger_point
和index_finger_point
中。
解析检测到的手掌信息,并提取手指的关键点坐标,将手指坐标转换为像素坐标,并将中指指尖和食指指尖的位置标记在图像上。
6.绘制指尖圆圈和连接线,计算距离
circle_func = lambda point: cv2.circle(self.image, point, 10, (255, 0, 255), -1)self.image = circle_func(thumb_finger_point)self.image = circle_func(index_finger_point)self.image = circle_func(between_finger_tip)# 画2点连线self.image = cv2.line(self.image, thumb_finger_point, index_finger_point, (255, 0, 255), 5)# 勾股定理计算长度line_len = math.hypot((index_finger_tip_x - middle_finger_tip_x),(index_finger_tip_y - middle_finger_tip_y))# 将指尖距离映射到文字rect_percent_text = math.ceil(line_len)
-
cv2.line
函数,在图像上绘制中指指尖和食指指尖之间的连接线。 -
math.hypot
函数计算直角三角形斜边的长度。 -
将指尖之间的距离映射到
rect_percent_text
变量中,用作后续文本显示的参数。
7.跟踪手指之间的距离
if squareManager.drag_active:# 更新方块squareManager.updateSquare(between_finger_tip[0], between_finger_tip[1])if (line_len > 100):# 取消激活squareManager.drag_active = FalsesquareManager.active_index = -1elif (line_len < 100) and (squareManager.checkOverlay(between_finger_tip[0],between_finger_tip[1]) != -1) and (squareManager.drag_active == False):# 激活squareManager.drag_active = True# 计算距离squareManager.setLen(between_finger_tip[0], between_finger_tip[1])
如果squareManager
的drag_active
属性为True,即矩形的移动模式已经激活,使用squareManager.updateSquare
方法更新矩形的位置。如果两个手指之间的距离大于100,即手指之间的距离超过了阈值,取消矩形的激活模式,将drag_active
设置为False,将active_index
设置为-1。
否则,如果两个手指之间的距离小于100,且手指之间存在重叠的矩形,并且矩形的移动模式未激活。激活矩形的移动模式,将drag_active
设置为True。根据手指之间的距离,计算并设置矩形的长度,使用squareManager.setLen
方法。
8.显示图像
squareManager.display(self)# 显示距离cv2.putText(self.image, "Distance:" + str(rect_percent_text), (10, 120), cv2.FONT_HERSHEY_PLAIN, 3,(255, 0, 0), 3)# 显示当前激活cv2.putText(self.image, "Active:" + ("None" if squareManager.active_index == -1 else str(squareManager.active_index)), (10, 170),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)# 显示刷新率FPScTime = time.time()fps_text = 1 / (cTime - fpsTime)fpsTime = cTimecv2.putText(self.image, "FPS: " + str(int(fps_text)), (10, 70),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)# 显示画面cv2.imshow('virtual drag and drop', self.image)if cv2.waitKey(5) & 0xFF == 27:breakcap.release()control = HandControlVolume()
control.recognize()
主函数(recognize
)的结尾部分,用于显示图像、矩形的状态和刷新率,并等待按键响应。使用squareManager.display
方法显示矩形。cv2.waitKey
函数等待按键输入,如果按下的键是ESC键(对应的ASCII码为27),则退出循环。
在屏幕上显示处理后的图像、矩形的状态和刷新率,并等待按键响应。这样可以实现交互式的虚拟拖放功能。接下来我们看一下实际的操作效果。
四、实战演示
通过演示我们可以实现通过手部对方块进行拖拽,效果可以达到良好的状态。
五、源码分享
import cv2
import mediapipe as mp
import time
import math
class SquareManager:def __init__(self, rect_width):# 方框长度self.rect_width = rect_width# 方块listself.square_count = 0self.rect_left_x_list = []self.rect_left_y_list = []self.alpha_list = []# 中指与矩形左上角点的距离self.L1 = 0self.L2 = 0# 激活移动模式self.drag_active = False# 激活的方块IDself.active_index = -1# 创建一个方块,但是没有显示def create(self, rect_left_x, rect_left_y, alpha=0.4):self.rect_left_x_list.append(rect_left_x)self.rect_left_y_list.append(rect_left_y)self.alpha_list.append(alpha)self.square_count += 1# 更新位置def display(self, class_obj):for i in range(0, self.square_count):x = self.rect_left_x_list[i]y = self.rect_left_y_list[i]alpha = self.alpha_list[i]overlay = class_obj.image.copy()if (i == self.active_index):cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 255), -1)else:cv2.rectangle(overlay, (x, y), (x + self.rect_width, y + self.rect_width), (255, 0, 0), -1)# Following line overlays transparent rectangle over the self.imageclass_obj.image = cv2.addWeighted(overlay, alpha, class_obj.image, 1 - alpha, 0)# 判断落在哪个方块上,返回方块的IDdef checkOverlay(self, check_x, check_y):for i in range(0, self.square_count):x = self.rect_left_x_list[i]y = self.rect_left_y_list[i]if (x < check_x < (x + self.rect_width)) and (y < check_y < (y + self.rect_width)):# 保存被激活的方块IDself.active_index = ireturn ireturn -1# 计算与指尖的距离def setLen(self, check_x, check_y):# 计算距离self.L1 = check_x - self.rect_left_x_list[self.active_index]self.L2 = check_y - self.rect_left_y_list[self.active_index]# 更新方块 def updateSquare(self, new_x, new_y):# print(self.rect_left_x_list[self.active_index])self.rect_left_x_list[self.active_index] = new_x - self.L1self.rect_left_y_list[self.active_index] = new_y - self.L2# 识别控制类
class HandControlVolume:def __init__(self):# 初始化medialpipeself.mp_drawing = mp.solutions.drawing_utilsself.mp_drawing_styles = mp.solutions.drawing_stylesself.mp_hands = mp.solutions.hands# 中指与矩形左上角点的距离self.L1 = 0self.L2 = 0# image实例,以便另一个类调用self.image = None# 主函数def recognize(self):# 计算刷新率fpsTime = time.time()# OpenCV读取视频流cap = cv2.VideoCapture(0)# 视频分辨率resize_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))resize_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))# 画面显示初始化参数rect_percent_text = 0# 初始化方块管理器squareManager = SquareManager(150)# 创建多个方块for i in range(0, 5):squareManager.create(200 * i + 20, 200, 0.6)with self.mp_hands.Hands(min_detection_confidence=0.7,min_tracking_confidence=0.5,max_num_hands=2) as hands:while cap.isOpened():# 初始化矩形success, self.image = cap.read()self.image = cv2.resize(self.image, (resize_w, resize_h))if not success:print("空帧.")continue# 提高性能self.image.flags.writeable = False# 转为RGBself.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)# 镜像self.image = cv2.flip(self.image, 1)# mediapipe模型处理results = hands.process(self.image)self.image.flags.writeable = Trueself.image = cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR)# 判断是否有手掌if results.multi_hand_landmarks:# 遍历每个手掌for hand_landmarks in results.multi_hand_landmarks:# 在画面标注手指self.mp_drawing.draw_landmarks(self.image,hand_landmarks,self.mp_hands.HAND_CONNECTIONS,self.mp_drawing_styles.get_default_hand_landmarks_style(),self.mp_drawing_styles.get_default_hand_connections_style())# 解析手指,存入各个手指坐标landmark_list = []# 用来存储手掌范围的矩形坐标paw_x_list = []paw_y_list = []for landmark_id, finger_axis in enumerate(hand_landmarks.landmark):landmark_list.append([landmark_id, finger_axis.x, finger_axis.y,finger_axis.z])paw_x_list.append(finger_axis.x)paw_y_list.append(finger_axis.y)if landmark_list:# 比例缩放到像素ratio_x_to_pixel = lambda x: math.ceil(x * resize_w)ratio_y_to_pixel = lambda y: math.ceil(y * resize_h)# 设计手掌左上角、右下角坐标paw_left_top_x, paw_right_bottom_x = map(ratio_x_to_pixel,[min(paw_x_list), max(paw_x_list)])paw_left_top_y, paw_right_bottom_y = map(ratio_y_to_pixel,[min(paw_y_list), max(paw_y_list)])# 给手掌画框框cv2.rectangle(self.image, (paw_left_top_x - 30, paw_left_top_y - 30),(paw_right_bottom_x + 30, paw_right_bottom_y + 30), (0, 255, 0), 2)# 获取中指指尖坐标middle_finger_tip = landmark_list[12]middle_finger_tip_x = ratio_x_to_pixel(middle_finger_tip[1])middle_finger_tip_y = ratio_y_to_pixel(middle_finger_tip[2])# 获取食指指尖坐标index_finger_tip = landmark_list[8]index_finger_tip_x = ratio_x_to_pixel(index_finger_tip[1])index_finger_tip_y = ratio_y_to_pixel(index_finger_tip[2])# 中间点between_finger_tip = (middle_finger_tip_x + index_finger_tip_x) // 2, (middle_finger_tip_y + index_finger_tip_y) // 2# print(middle_finger_tip_x)thumb_finger_point = (middle_finger_tip_x, middle_finger_tip_y)index_finger_point = (index_finger_tip_x, index_finger_tip_y)# 画指尖2点circle_func = lambda point: cv2.circle(self.image, point, 10, (255, 0, 255), -1)self.image = circle_func(thumb_finger_point)self.image = circle_func(index_finger_point)self.image = circle_func(between_finger_tip)# 画2点连线self.image = cv2.line(self.image, thumb_finger_point, index_finger_point, (255, 0, 255), 5)# 勾股定理计算长度line_len = math.hypot((index_finger_tip_x - middle_finger_tip_x),(index_finger_tip_y - middle_finger_tip_y))# 将指尖距离映射到文字rect_percent_text = math.ceil(line_len)# 激活模式,需要让矩形跟随移动if squareManager.drag_active:# 更新方块squareManager.updateSquare(between_finger_tip[0], between_finger_tip[1])if (line_len > 100):# 取消激活squareManager.drag_active = FalsesquareManager.active_index = -1elif (line_len < 100) and (squareManager.checkOverlay(between_finger_tip[0],between_finger_tip[1]) != -1) and (squareManager.drag_active == False):# 激活squareManager.drag_active = True# 计算距离squareManager.setLen(between_finger_tip[0], between_finger_tip[1])# 显示方块,传入本实例,主要为了半透明的处理squareManager.display(self)# 显示距离cv2.putText(self.image, "Distance:" + str(rect_percent_text), (10, 120), cv2.FONT_HERSHEY_PLAIN, 3,(255, 0, 0), 3)# 显示当前激活cv2.putText(self.image, "Active:" + ("None" if squareManager.active_index == -1 else str(squareManager.active_index)), (10, 170),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)# 显示刷新率FPScTime = time.time()fps_text = 1 / (cTime - fpsTime)fpsTime = cTimecv2.putText(self.image, "FPS: " + str(int(fps_text)), (10, 70),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)# 显示画面cv2.imshow('virtual drag and drop', self.image)if cv2.waitKey(5) & 0xFF == 27:breakcap.release()control = HandControlVolume()
control.recognize()
相关文章:

OpenCV实现手势虚拟拖拽
前言: Hello大家好,我是Dream。 今天来学习一下如何使用OpenCV实现手势虚拟拖拽,欢迎大家一起前来探讨学习~ 一、主要步骤及库的功能介绍 1.主要步骤 要实现本次实验,主要步骤如下: 导入OpenCV库。通过OpenCV读取摄…...
深圳市宝安区委常委、宣传部部长周学良一行莅临联诚发考察调研
11月9日,深圳市宝安区组织开展主题教育“大走访、大座谈、大起底”行动和调查研究、“基层调研服务日”活动。当日上午,区委常委、宣传部部长周学良率调研组莅临联诚发LCF总部考察调研。区委宣传部副部长孙箫韵,区文化广电旅游体育局党组成员…...

Presentation Prompter 5.4.2(mac屏幕提词器)
Presentation Prompter是一款演讲辅助屏幕提词器软件,旨在帮助演讲者在公共演讲、主持活动或录制视频时更加流畅地进行演讲。以下是Presentation Prompter的一些特色功能: 提供滚动或分页显示:可以将演讲稿以滚动或分页的形式显示在屏幕上&a…...

9 网关的作用
1、总结: 1.如果离开本局域网,就需要经过网关,网关是路由器的一个网口。 2.路由器是一个三层设备,里面有如何寻找下一跳的规则 3.经过路由器之后 MAC 头要变,如果 IP 不变,相当于不换护照的欧洲旅游&#…...

计算机网络实验
计算机网络实验 使用软件PT7.0按照上面的拓扑结构建立网络,进行合理配置,使得所有计算机之间能够互相通信。并且修改各交换机的系统名称为:学号_编号,如你的学号为123,交换机Switch0的编号为0,则系统名称为…...
九凌网络分享外贸快车实现迅速出口的目标
随着全球化进程的不断加速,外贸行业也越来越繁荣。但如何在这个竞争激烈的市场中迅速出口,成为了很多外贸企业家们面临的难题。为此,很多企业开始寻求新的帮助,其中一种办法就是通过专业的外贸快车来提高自己的出口速度。九凌网络…...

分享66个Python管理系统源代码总有一个是你想要的
分享66个Python管理系统源代码总有一个是你想要的 源码下载链接:https://pan.baidu.com/s/1FGmE9Q_NE1-cjjoxU540BQ?pwd8888 提取码:8888 项目名称 automobile-sales-management-system汽车销售管理系统 Python Vue BNUZ教务系统认证爬虫Python语言…...

python 删除特定字符所在行
嗨喽,大家好呀~这里是爱看美女的茜茜呐 查询文件中含有特殊字符串的行 #!/usr/bin/python # -*- coding:utf-8 -*- import re file1 open(test.txt,r) istxt re.compile(r.*if.*,re.I) for line in file1.readlines():line line.strip()ifstr re.findall(istxt…...

邮箱哪家强?哪个牌子邮箱好用
邮箱在国内外使用情况不太一样,国内一般都是工作中需要用邮箱,直接使用公司发的企业邮箱就可以了,个人一般自己需要使用邮箱频率比较少,大多是用来注册其他平台信息,接受验证码、电子发票等等,使用不频繁。…...
关于DDD的贫血模型和充血模型到底是什么区别?
贫血模型和充血模型是两种不同的设计模式,用于处理复杂的业务逻辑和数据操作。 贫血模型是指将业务逻辑和数据操作分离,业务逻辑在服务层处理,数据操作在数据访问层处理。这种设计模式的优点是易于维护和测试,但是在处理复杂的业…...

让BI自动生成零售数据分析报表?用模板
不知道BI零售数据分析怎么做?用模板。 没时间去整理数据、计算零售数据分析指标?用模板。 不知道怎么做出炫酷直观的零售数据分析报表?用模板。 …… 奥威BI零售数据分析模板全新上线,数据分析模型、数据可视图表、关键指标以…...

以吉祥物宣传片实力出圈!吉祥物三维动画宣传片怎么制作?
首届学青会吉祥物“壮壮”、“美美”在宣传片中展示了举重、打羽毛球、游泳等运动姿态,靠着可爱的虚拟形象萌出圈! *图片源于网络 在数字化时代,吉祥物三维动画宣传片已成为众多大型活动、品牌宣发、文旅城市宣传的一大途径,如学…...

TensorFlow(1):深度学习的介绍
1 深度学习与机器学习的区别 学习目标:知道深度学习与机器学习的区别 区别:深度学习没有特征提取 1.1 特征提取方面 机器学习的特征工程步骤是要靠手动完成的,而且需要大量领域专业知识深度学习通常由多个层组成,它们通常将更简…...
C# 如何优雅的写代码[进阶篇]
文章目录 前言相关文章如何让代码优雅知识点补充enum枚举类型?null判定 前言 我之前发布过一些篇章,是专门关于代码优化的,距离我上一次[如何优雅的写C#]已经过去半年时间了,最近我又研究出了一些新东西。 相关文章 如何优雅的写C#&#x…...

【JavaEESpring】Spring, Spring Boot 和Spring MVC的关系以及区别
Spring, Spring Boot 和Spring MVC的关系以及区别 Spring:简单来说,Spring 是一个开发应用框架,什么样的框架呢?轻量级、一站式、模块化,其目的是用于简化企业级应用程序开发 Spring的主要功能: 管理对象, 以及对象之…...

【网络编程】传输层——TCP协议
文章目录 TCP协议TCP协议格式窗口大小六个标志位确认应答机制超时重传机制连接管理机制三次握手四次挥手 流量控制滑动窗口拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况TCP小结基于TCP的应用层协议TCP与UDP的对比 TCP相关实验CLOSE_WAIT状态实验TIME_WAIT状态实验TI…...
【数据结构与算法】如何衡量一个算法的好坏?
目录 1. 算法复杂度1.1 时间复杂度1.2 大O渐进表示法1.2.1 例子引入推导大O法1.2.2 举例 1.3 空间复杂度1.3.1 细分1.3.2 举例 2.什么是好的算法?3. 影响程序运行时间的因数 1. 算法复杂度 算法在编写成可执行程序后,运行时需要耗费时间资源和内存资源 。…...

在PostgreSQL中创建和管理数据库
PostgreSQL是一个强大、开源的关系型数据库管理系统,它提供了丰富的功能和灵活的配置选项,使得它成为许多开发者和组织的首选数据库之一,接下来我会介绍如何在PostgreSQL中创建和管理数据库。 一、安装和配置PostgreSQL 第一步,…...

从哪些方面做好电商系统的网站建设?
电子商务的迅猛发展,建设一款成功的电商系统网站成为企业取得竞争优势的重要一环。下面将从用户体验、网站设计、安全性和性能优化等方面,介绍如何打造一款优秀的电商系统网站。 一、用户体验 一款成功的电商系统网站必须注重用户体验,确保用…...

C++的Odyssey之旅——STL
W...Y的主页 😊 代码仓库分享💕 🍔前言:我们已经将基本语法了解的差不多了,现在我们就该进入C中最重要也是最富有特点的一部分——STL。在学习C语言中我们想要使用顺序表、链表等一些数据结构进行做题时都需要进行这…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验
2024年初,人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目(一款融合大型语言模型能力的云端AI编程IDE)时,技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力,TRAE在WayToAGI等…...
算法250609 高精度
加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...
CentOS 7.9安装Nginx1.24.0时报 checking for LuaJIT 2.x ... not found
Nginx1.24编译时,报LuaJIT2.x错误, configuring additional modules adding module in /www/server/nginx/src/ngx_devel_kit ngx_devel_kit was configured adding module in /www/server/nginx/src/lua_nginx_module checking for LuaJIT 2.x ... not…...