计算机视觉 | OpenCV 实现手势虚拟控制亮度和音量

Hi,大家好,我是半亩花海。在当今科技飞速发展的时代,我们身边充斥着各种智能设备,然而,如何更便捷地与这些设备进行交互却是一个不断被探索的课题。本文将主要介绍一个基于 OpenCV 手势识别项目,通过手势来控制电脑屏幕亮度音量大小,为用户提供了一种全新的交互方式。


目录

一、代码拆解

1. 导入必要库

2. 初始化手部关键点

3. 数据格式转换

4. 画手势关键点

5. 手势状态缓冲处理

6. 画直线

7. 屏幕亮度和音量控制

8. 初始化摄像头和手部关键点识别器

9. Pygame 界面初始化和事件监听

二、实战演示

1. 亮度——light

2. 音量——voice

3. 菜单——menu

三、完整代码


一、代码拆解

1. 导入必要库

在开始介绍项目的实现细节之前,我们首先需要导入项目所需的必要库。这些库包括:

  • OpenCV:用于处理图像和视频数据。
  • Mediapipe:提供了对手部关键点的识别和跟踪功能。
  • Pygame:用于创建图形界面和显示摄像头捕获的图像。
  • WMI:用于调节电脑屏幕亮度。
  • pycaw:用于控制电脑的音量。
# 导入必要库
import math
import sys
import numpy as np
import cv2
import pygame
import wmi
import mediapipe as mp
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
import warnings  # 忽略警告
warnings.filterwarnings("ignore")

2. 初始化手部关键点

首先创建一个 HandKeyPoint 类,用于初始化手部关键点检测器,并提供对图像进行处理的方法。

# 手部关键点类
class HandKeyPoint:
    def __init__(self,
                 static_image_mode=False,
                 max_num_hands=2,
                 model_complexity=1,
                 min_detection_confidence=0.5,
                 min_tracking_confidence=0.5):
        # 手部识别api
        self.mp_hands = mp.solutions.hands
        # 获取手部识别类
        self.hands = self.mp_hands.Hands(static_image_mode=static_image_mode,
                                         max_num_hands=max_num_hands,
                                         model_complexity=model_complexity,
                                         min_detection_confidence=min_detection_confidence,
                                         min_tracking_confidence=min_tracking_confidence)

    def process(self, image):
        # 将BGR转换为RGB
        img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # 识别图像中的手势,并返回结果
        results = self.hands.process(img)
        # numpy格式的数据
        np_arr = landmarks_to_numpy(results)
        return results, np_arr

3. 数据格式转换

手部关键点的检测结果(将 landmarks 格式的数据)转换为 numpy 数组,以便后续的处理和分析。

# 将landmarks格式的数据转换为numpy格式的数据
def landmarks_to_numpy(results):
    """
    将landmarks格式的数据转换为numpy格式的数据
    numpy shape:(2, 21, 3)
    :param results:
    :return:
    """
    shape = (2, 21, 3)
    landmarks = results.multi_hand_landmarks
    if landmarks is None:
        # 没有检测到手
        return np.zeros(shape)
    elif len(landmarks) == 1:
        # 检测出一只手,先判断是左手还是右手
        label = results.multi_handedness[0].classification[0].label
        hand = landmarks[0]
        # print(label)
        if label == "Left":
            return np.array(
                [np.array([[hand.landmark[i].x, hand.landmark[i].y, hand.landmark[i].z] for i in range(21)]),
                 np.zeros((21, 3))])
        else:
            return np.array([np.zeros((21, 3)),
                             np.array(
                                 [[hand.landmark[i].x, hand.landmark[i].y, hand.landmark[i].z] for i in range(21)])])
    elif len(landmarks) == 2:
        # print(results.multi_handedness)
        lh_idx = 0
        rh_idx = 0
        for idx, hand_type in enumerate(results.multi_handedness):
            label = hand_type.classification[0].label
            if label == 'Left':
                lh_idx = idx
            if label == 'Right':
                rh_idx = idx

        lh = np.array(
            [[landmarks[lh_idx].landmark[i].x, landmarks[lh_idx].landmark[i].y, landmarks[lh_idx].landmark[i].z] for i
             in range(21)])
        rh = np.array(
            [[landmarks[rh_idx].landmark[i].x, landmarks[rh_idx].landmark[i].y, landmarks[rh_idx].landmark[i].z] for i
             in range(21)])
        return np.array([lh, rh])
    else:
        return np.zeros((2, 21, 3))

4. 画手势关键点

# 画手势关键点
def draw_landmark(img, results):
    if results.multi_hand_landmarks:
        for hand_landmark in results.multi_hand_landmarks:
            mp.solutions.drawing_utils.draw_landmarks(img,
                                                      hand_landmark,
                                                      mp.solutions.hands.HAND_CONNECTIONS,
                                                      mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
                                                      mp.solutions.drawing_styles.get_default_hand_connections_style())

    return img

5. 手势状态缓冲处理

为了平滑处理手势状态的变化,我们实现了一个 Buffer 类,用于缓存手势状态的变化,并提供了添加正例和负例的方法。

# 缓冲区类
class Buffer:
    def __init__(self, volume=20):
        self.__positive = 0
        self.state = False
        self.__negative = 0
        self.__volume = volume
        self.__count = 0

    def add_positive(self):
        self.__count += 1
        if self.__positive >= self.__volume:
            # 如果正例个数大于容量,将状态定为True
            self.state = True
            self.__negative = 0
            self.__count = 0
        else:
            self.__positive += 1

        if self.__count > self.__volume:
            # 如果大于容量次操作后还没有确定状态
            self.__positive = 0
            self.__count = 0

    def add_negative(self):
        self.__count += 1
        if self.__negative >= self.__volume:
            # 如果负例个数大于容量,将状态定为False
            self.state = False
            self.__positive = 0
        else:
            self.__negative += 1

        if self.__count > self.__volume:
            # 如果大于容量次操作后还没有确定状态
            self.__positive = 0
            self.__count = 0
        # print(f"pos:{self.__positive} neg:{self.__negative} count:{self.__count}")

    def clear(self):
        self.__positive = 0
        self.state = False
        self.__negative = 0
        self.__count = 0

6. 画直线

# 画线函数
def draw_line(frame, p1, p2, color=(255, 127, 0), thickness=3):
    """
    画一条直线
    :param p1:
    :param p2:
    :return:
    """
    return cv2.line(frame, (int(p1[0] * CAM_W), int(p1[1] * CAM_H)), (int(p2[0] * CAM_W), int(p2[1] * CAM_H)), color,
                    thickness)

7. 屏幕亮度和音量控制

# 控制屏幕亮度
def screen_change(percent):  # percent/2即为亮度百分比
    SCREEN = wmi.WMI(namespace='root/WMI')
    a = SCREEN.WmiMonitorBrightnessMethods()[0]
    a.WmiSetBrightness(Brightness=percent, Timeout=500)

# 初始化音量控制
def init_voice():
    devices = AudioUtilities.GetSpeakers()
    interface = devices.Activate(
        IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
    volume = cast(interface, POINTER(IAudioEndpointVolume))
    volume.SetMute(0, None)
    volume_range = volume.GetVolumeRange()
    min_volume = volume_range[0]
    max_volume = volume_range[1]
    return (min_volume, max_volume), volume

8. 初始化摄像头和手部关键点识别器

在项目的初始化阶段,我们需要加载摄像头实例和手部关键点识别实例,以便后续对手势进行识别和处理。

# 加载摄像头实例
cap = cv2.VideoCapture(0)
CAM_W = 640
CAM_H = 480
CAM_SCALE = CAM_W / CAM_H

# 加载手部关键点识别实例
hand = HandKeyPoint()

9. Pygame 界面初始化和事件监听

为了展示手势控制效果,并提供交互界面,我们使用了 Pygame 库。在初始化阶段,我们创建了一个窗口,并设置了标题。同时,我们实现了事件监听功能,以便在需要时退出程序

具体来说,我们使用 Pygame 创建了一个窗口,并将摄像头捕获的图像显示在窗口中。同时,我们利用 Pygame 的事件监听功能,监听用户的键盘事件,例如按下"q"键时退出程序。这样,用户就可以通过手势控制屏幕亮度和音量大小,同时在 Pygame 窗口中观察手势识别效果。

# 初始化pygame
pygame.init()
# 设置窗口全屏
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("virtual_control_screen")
# 获取当前窗口大小
window_size = list(screen.get_size())

# 主循环
while True:
······
    # 事件监听 若按q则退出程序
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                sys.exit(0)

二、实战演示

1. 亮度——light

如果 20 < angle < 90,那么“light ready”即手势控制亮度

2. 音量——voice

如果 -20 > angle > -50,那么“voice ready”即手势控制音量

3. 菜单——menu

上述两种情况除外,那么处于“menu”状态即进入菜单

通过演示可以发现,食指与大拇指在屏幕中的距离越远,亮度越高(音量越大),反之越小,实现了通过手势对亮度和音量的控制。


三、完整代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@Project : virtual
@File    : virtual_control.py
@IDE     : PyCharm
@Author  : 半亩花海
@Date    : 2024:02:06 18:01
"""
# 导入模块
import math
import sys
import numpy as np
import cv2
import pygame
import wmi
import mediapipe as mp
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
import warnings  # 忽略警告
warnings.filterwarnings("ignore")


# 手部关键点类
class HandKeyPoint:
    def __init__(self,
                 static_image_mode=False,
                 max_num_hands=2,
                 model_complexity=1,
                 min_detection_confidence=0.5,
                 min_tracking_confidence=0.5):
        # 手部识别api
        self.mp_hands = mp.solutions.hands
        # 获取手部识别类
        self.hands = self.mp_hands.Hands(static_image_mode=static_image_mode,
                                         max_num_hands=max_num_hands,
                                         model_complexity=model_complexity,
                                         min_detection_confidence=min_detection_confidence,
                                         min_tracking_confidence=min_tracking_confidence)

    def process(self, image):
        # 将BGR转换为RGB
        img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # 识别图像中的手势,并返回结果
        results = self.hands.process(img)
        # numpy格式的数据
        np_arr = landmarks_to_numpy(results)
        return results, np_arr


# 将landmarks格式的数据转换为numpy格式的数据
def landmarks_to_numpy(results):
    """
    将landmarks格式的数据转换为numpy格式的数据
    numpy shape:(2, 21, 3)
    :param results:
    :return:
    """
    shape = (2, 21, 3)
    landmarks = results.multi_hand_landmarks
    if landmarks is None:
        # 没有检测到手
        return np.zeros(shape)
    elif len(landmarks) == 1:
        # 检测出一只手,先判断是左手还是右手
        label = results.multi_handedness[0].classification[0].label
        hand = landmarks[0]
        # print(label)
        if label == "Left":
            return np.array(
                [np.array([[hand.landmark[i].x, hand.landmark[i].y, hand.landmark[i].z] for i in range(21)]),
                 np.zeros((21, 3))])
        else:
            return np.array([np.zeros((21, 3)),
                             np.array(
                                 [[hand.landmark[i].x, hand.landmark[i].y, hand.landmark[i].z] for i in range(21)])])
    elif len(landmarks) == 2:
        # print(results.multi_handedness)
        lh_idx = 0
        rh_idx = 0
        for idx, hand_type in enumerate(results.multi_handedness):
            label = hand_type.classification[0].label
            if label == 'Left':
                lh_idx = idx
            if label == 'Right':
                rh_idx = idx

        lh = np.array(
            [[landmarks[lh_idx].landmark[i].x, landmarks[lh_idx].landmark[i].y, landmarks[lh_idx].landmark[i].z] for i
             in range(21)])
        rh = np.array(
            [[landmarks[rh_idx].landmark[i].x, landmarks[rh_idx].landmark[i].y, landmarks[rh_idx].landmark[i].z] for i
             in range(21)])
        return np.array([lh, rh])
    else:
        return np.zeros((2, 21, 3))


# 画手势关键点
def draw_landmark(img, results):
    if results.multi_hand_landmarks:
        for hand_landmark in results.multi_hand_landmarks:
            mp.solutions.drawing_utils.draw_landmarks(img,
                                                      hand_landmark,
                                                      mp.solutions.hands.HAND_CONNECTIONS,
                                                      mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
                                                      mp.solutions.drawing_styles.get_default_hand_connections_style())

    return img


# 缓冲区类
class Buffer:
    def __init__(self, volume=20):
        self.__positive = 0
        self.state = False
        self.__negative = 0
        self.__volume = volume
        self.__count = 0

    def add_positive(self):
        self.__count += 1
        if self.__positive >= self.__volume:
            # 如果正例个数大于容量,将状态定为True
            self.state = True
            self.__negative = 0
            self.__count = 0
        else:
            self.__positive += 1

        if self.__count > self.__volume:
            # 如果大于容量次操作后还没有确定状态
            self.__positive = 0
            self.__count = 0

    def add_negative(self):
        self.__count += 1
        if self.__negative >= self.__volume:
            # 如果负例个数大于容量,将状态定为False
            self.state = False
            self.__positive = 0
        else:
            self.__negative += 1

        if self.__count > self.__volume:
            # 如果大于容量次操作后还没有确定状态
            self.__positive = 0
            self.__count = 0
        # print(f"pos:{self.__positive} neg:{self.__negative} count:{self.__count}")

    def clear(self):
        self.__positive = 0
        self.state = False
        self.__negative = 0
        self.__count = 0


# 画线函数
def draw_line(frame, p1, p2, color=(255, 127, 0), thickness=3):
    """
    画一条直线
    :param p1:
    :param p2:
    :return:
    """
    return cv2.line(frame, (int(p1[0] * CAM_W), int(p1[1] * CAM_H)), (int(p2[0] * CAM_W), int(p2[1] * CAM_H)), color,
                    thickness)


# 控制屏幕亮度
def screen_change(percent):  # percent/2即为亮度百分比
    SCREEN = wmi.WMI(namespace='root/WMI')
    a = SCREEN.WmiMonitorBrightnessMethods()[0]
    a.WmiSetBrightness(Brightness=percent, Timeout=500)


# 初始化音量控制
def init_voice():
    devices = AudioUtilities.GetSpeakers()
    interface = devices.Activate(
        IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
    volume = cast(interface, POINTER(IAudioEndpointVolume))
    volume.SetMute(0, None)
    volume_range = volume.GetVolumeRange()
    min_volume = volume_range[0]
    max_volume = volume_range[1]
    return (min_volume, max_volume), volume


# 加载摄像头实例
cap = cv2.VideoCapture(0)
CAM_W = 640
CAM_H = 480
CAM_SCALE = CAM_W / CAM_H

# 加载手部关键点识别实例
hand = HandKeyPoint()

# 初始化pygame
pygame.init()
# 设置窗口全屏
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("virtual_control_screen")
# 获取当前窗口大小
window_size = list(screen.get_size())

# 设置缓冲区
buffer_light = Buffer(10)
buffer_voice = Buffer(10)

last_y = 0
last_2_y = 1
last_2_x = 0

# 初始化声音控制
voice_range, volume = init_voice()

# 设置亮度条参数
bright_bar_length = 300
bright_bar_height = 20
bright_bar_x = 50
bright_bar_y = 100

# 设置音量条参数
vol_bar_length = 300
vol_bar_height = 20
vol_bar_x = 50
vol_bar_y = 50

# 主循环 每次循环就是对每帧的处理
while True:
    img_menu = None
    lh_index = -1
    # 读取摄像头画面
    success, frame = cap.read()

    # 将opencv中图片格式的BGR转换为常规的RGB
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    # 镜面反转
    frame = cv2.flip(frame, 1)

    # 处理图像
    res, arr = hand.process(frame)
    frame = draw_landmark(frame, res)

    scale = math.hypot((arr[0, 7, 0] - arr[0, 8, 0]),
                       (arr[0, 7, 1] - arr[0, 8, 1]),
                       (arr[0, 7, 2] - arr[0, 8, 2]))

    # 计算tan值
    tan = (arr[0, 0, 1] - arr[0, 12, 1]) / (arr[0, 0, 0] - arr[0, 12, 0])
    # 计算角度
    angle = np.arctan(tan) * 180 / np.pi
    # print(angle)

    if 20 < angle < 90:
        path = 'resources/menu/light.png'
        buffer_light.add_positive()
        buffer_voice.add_negative()
        # 显示亮度条和亮度刻度值
        show_brightness = True
        show_volume = False
    elif -20 > angle > -50:
        path = 'resources/menu/voice.png'
        buffer_voice.add_positive()
        buffer_light.add_negative()
        # 显示音量条和音量刻度值
        show_brightness = False
        show_volume = True
    else:
        path = 'resources/menu/menu.png'
        buffer_light.add_negative()
        buffer_voice.add_negative()
        # 不显示刻度值和百分比
        show_brightness = False
        show_volume = False

    # 计算拇指与食指之间的距离
    dis = math.hypot(int((arr[1, 4, 0] - arr[1, 8, 0]) * CAM_W), int((arr[1, 4, 1] - arr[1, 8, 1]) * CAM_H))
    # 右手映射时的缩放尺度
    s = math.hypot((arr[1, 5, 0] - arr[1, 9, 0]), (arr[1, 5, 1] - arr[1, 9, 1]), (arr[1, 5, 2] - arr[1, 9, 2]))

    # 调节亮度
    if buffer_light.state:
        frame = cv2.putText(frame, 'light ready', (10, 35), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 127, 0))
        frame = draw_line(frame, arr[1, 4], arr[1, 8], thickness=5, color=(255, 188, 66))
        if dis != 0:
            # 线性插值,可以理解为将一个区间中的一个值映射到另一区间内
            light = np.interp(dis, [int(500 * s), int(3000 * s)], (0, 100))
            # 调节亮度
            screen_change(light)
    # 调节声音
    elif buffer_voice.state:
        frame = cv2.putText(frame, 'voice ready', (10, 35), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 127, 0))
        frame = draw_line(frame, arr[1, 4], arr[1, 8], thickness=5, color=(132, 134, 248))
        if dis != 0:
            vol = np.interp(dis, [int(500 * s), int(3000 * s)], voice_range)
            # 调节音量
            volume.SetMasterVolumeLevel(vol, None)

    # 将图片改为与窗口一样的大小
    frame = cv2.resize(frame, (int(window_size[1] * CAM_SCALE), window_size[1]))
    frame = cv2.transpose(frame)
    # 渲染图片
    frame = pygame.surfarray.make_surface(frame)
    screen.blit(frame, (int(0.5 * (CAM_W - CAM_H * CAM_SCALE)), 0))

    img_menu = pygame.image.load(path).convert_alpha()
    img_w, img_h = img_menu.get_size()
    img_menu = pygame.transform.scale(img_menu, (int(img_w * scale * 5), int(img_h * scale * 5)))
    x = (arr[0][9][0] + arr[0][13][0] + arr[0][0][0]) / 3
    y = (arr[0][9][1] + arr[0][13][1] + arr[0][0][1]) / 3
    x = int(x * window_size[0] - window_size[0] * scale * 3.5)
    y = int(y * window_size[1] - window_size[1] * scale * 12)
    # print(x, y)
    screen.blit(img_menu, (x, y))

    # 绘制音量条和亮度条的外框
    if show_volume:
        pygame.draw.rect(screen, (255, 255, 255), (vol_bar_x, vol_bar_y, vol_bar_length, vol_bar_height), 3)
    elif show_brightness:
        pygame.draw.rect(screen, (255, 255, 255), (bright_bar_x, bright_bar_y, bright_bar_length, bright_bar_height),
                         3)

    # 计算当前音量和亮度在条上的位置和大小,并绘制已填充的条
    if show_volume:
        vol = volume.GetMasterVolumeLevel()
        vol_range = voice_range[1] - voice_range[0]
        vol_bar_fill_length = int((vol - voice_range[0]) / vol_range * vol_bar_length)
        pygame.draw.rect(screen, (0, 255, 0), (vol_bar_x, vol_bar_y, vol_bar_fill_length, vol_bar_height))
        # 显示音量刻度值和当前音量大小
        vol_text = f"Volume: {int((vol - voice_range[0]) / vol_range * 100)}%"
        vol_text_surface = pygame.font.SysFont(None, 24).render(vol_text, True, (255, 255, 255))
        screen.blit(vol_text_surface, (vol_bar_x + vol_bar_length + 10, vol_bar_y))
    elif show_brightness:
        brightness = wmi.WMI(namespace='root/WMI').WmiMonitorBrightness()[0].CurrentBrightness
        bright_bar_fill_length = int(brightness / 100 * bright_bar_length)
        pygame.draw.rect(screen, (255, 255, 0), (bright_bar_x, bright_bar_y, bright_bar_fill_length, bright_bar_height))
        # 显示亮度刻度值和当前亮度大小
        bright_text = f"Brightness: {brightness}%"
        bright_text_surface = pygame.font.SysFont(None, 24).render(bright_text, True, (255, 255, 255))
        screen.blit(bright_text_surface, (bright_bar_x + bright_bar_length + 10, bright_bar_y))

    pygame.display.flip()

    # 事件监听 若按q则退出程序
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                sys.exit(0)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Java学生管理系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

解锁阿里巴巴面试题:创建线程的几种方式?

大家好,我是小米!今天我们来聊一个热门话题——阿里巴巴面试题:创建线程的几种方式。在技术的海洋中,线程是我们编程航程中的一艘不可或缺的船,驶向程序的未知领域。那么,究竟有哪些方式可以创建线程呢?让我们一起揭开这个技术的神秘面纱! 实现Runnable接口 首先,我…

最好的方式来预测未来是去创造它。

在辅导企业的过程中&#xff0c;对于「建设性的冲突」持开放态度&#xff0c;这背后反映了一种深刻的系统思考和变革管理的理念。在许多传统工作环境中&#xff0c;「和谐」往往被高度重视&#xff0c;但这种表面的和谐有时会掩盖问题的真相&#xff0c;阻碍组织的深层次变革和…

C语言:整形存储

#include<stdio.h> int main() {char a -1;signed char b -1;unsigned char c -1;printf("a%d,b%d,c%d", a, b, c);return 0; } b与a都是有符号数结果一样。a的signed相当于省略了。 运行结果 整形提升&#xff1a;整形算术运算总是以至少以缺省整型类型的精…

时序预测 | Matlab实现基于LSTM长短期记忆神经网络的电力负荷预测模型

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 时序预测 | Matlab实现基于LSTM长短期记忆神经网络的电力负荷预测模型 LSTM(长短期记忆)是一种递归神经网络(RNN)的变体,它在序列数据建模方面表现出色。电力负荷预测是一项重要的任务,可以利用LSTM神经网络…

Golang 学习(二)进阶使用

二、进阶使用 性能提升——协程 GoRoutine go f();一个 Go 线程上&#xff0c;可以起多个协程&#xff08;有独立的栈空间、共享程序堆空间、调度由用户控制&#xff09;主线程是一个物理线程&#xff0c;直接作用在 cpu 上的。是重量级的&#xff0c;非常耗费 cpu 资源。协…

从零开始手写mmo游戏从框架到爆炸(六)— 消息处理工厂

就好像门牌号一样&#xff0c;我们需要把消息路由到对应的楼栋和楼层&#xff0c;总不能像菜鸟一样让大家都来自己找数据吧。 首先这里我们参考了rabbitmq中的topic与tag模型&#xff0c;topic对应类&#xff0c;tag对应方法。 新增一个模块&#xff0c;专门记录路由eternity-…

Mac上几款好用的MacBook视频播放器

使用Mac电脑时&#xff0c;视频播放器可以说是我们使用频率最高的软件之一了&#xff0c;不管是工作时看视频资料还是在家里看下载好的电影&#xff0c;都需要用到视频播放器&#xff0c;本文中我们就来推荐几款好用的Macbook视频播放器&#xff0c;总有一款适合你&#xff01;…

完全免费,文字转语音、AI语音合成,视频配音就用这两款软件!

最近又有不少小伙伴找我要文字转语音、配音软件&#xff0c;刚好最近我也找了两款还比较不错的免费软件&#xff0c;今天就来分享给大家。最后还推荐了一款我自己一直在用的软件&#xff0c;建议认真看看&#xff01; 01 - Vpot-FREE&#xff08;电脑&#xff09; 它是一款永久…

灵敏可靠的缓激肽(Bradykinin)ELISA检测试剂盒

灵敏可靠的ELISA试剂盒&#xff0c;用于检测血浆、血清和尿液样本中的缓激肽 缓激肽&#xff08;Bradykinin&#xff09;于1949年被发现&#xff0c;由血浆中的球蛋白前体在蛋白酶的作用下生成。它的名字表明它会促使肠道缓慢运动。早在1909年&#xff0c;人们就注意到在尿液中…

Elementplus报错 [ElOnlyChild] no valid child node found

报错描述&#xff1a;ElementPlusError: [ElOnlyChild] no valid child node found 问题复现&#xff08;随机例子&#xff09;&#xff1a; <el-popover placement"right" :width"400" trigger"click"><template #reference><e…

使用navicat导出mysql离线数据后,再导入doris的方案

一、背景 doris本身是支持直接从mysql中同步数据的&#xff0c;但有时候&#xff0c;客户不允许我们使用doris直连mysql&#xff0c;此时就需要客户配合将mysql中的数据手工导出成离线文件&#xff0c;我们再导入到doris中 二、环境 doris 1.2 三、方案 doris支持多种导入…

2024:AI 大冒险

2024&#xff1a;AI 大冒险 2023 年就像一场疯狂的过山车&#xff0c;现在让我们一起系好安全带&#xff0c;来预测一下 2024 年的五大惊心动魄事件吧&#xff01; 一、AI 惹祸升级 嘿&#xff0c;2024 年可要小心了&#xff01;AI 这家伙可能会变得更调皮捣蛋。人们可能会用…

数据结构与算法之美学习笔记:51 | 并行算法:如何利用并行处理提高算法的执行效率?

目录 前言并行排序并行查找并行字符串匹配并行搜索总结引申 前言 本节课程思维导图&#xff1a; 时间复杂度是衡量算法执行效率的一种标准。但是&#xff0c;时间复杂度并不能跟性能划等号。在真实的软件开发中&#xff0c;即便在不降低时间复杂度的情况下&#xff0c;也可以…

re:从0开始的CSS学习之路 5. 颜色单位

0. 写在前面 没想到在CSS里也要再次了解这些颜色单位&#xff0c;感觉回到了大二的数字图像处理&#xff0c;可惜现在已经大四了&#xff0c;感觉并没有学会什么AI的东西 1. 颜色单位 预定义颜色名&#xff1a;HTML和CSS规定了147种颜色名。例如&#xff1a;red yellow green …

数据库管理-第146期 最强Oracle监控EMCC深入使用-03(20240206)

数据库管理145期 2024-02-06 数据库管理-第146期 最强Oracle监控EMCC深入使用-03&#xff08;20240206&#xff09;1 概览2 性能中心3 性能中心-Exadata总结 数据库管理-第146期 最强Oracle监控EMCC深入使用-03&#xff08;20240206&#xff09; 作者&#xff1a;胖头鱼的鱼缸&…

React+Echarts实现数据排名+自动滚动+Y轴自定义toolTip文字提示

1、效果 2、环境准备 1、react18 2、antd 4 3、代码实现 原理&#xff1a;自动滚动通过创建定时器动态更新echar的dataZoom属性startValue、endValue&#xff0c;自定义tooltip通过监听echar的鼠标移入移出事件&#xff0c;判断tooltTip元素的显隐以及位置。 1、导入所需组…

JavaScript流程控制详解之顺序结构和选择结构

流程控制 流程控制&#xff0c;指的是控制程序按照怎样的顺序执行 在JavaScript中&#xff0c;共有3种流程控制方式 顺序结构选择结构循环结构 顺序结构 在JavaScript中&#xff0c;顺序结构是最基本的结构&#xff0c;所谓的顺序结构&#xff0c;指的是代码按照从上到下、…

数据结构之堆排序

对于几个元素的关键字序列{K1&#xff0c;K2&#xff0c;…&#xff0c;Kn}&#xff0c;当且仅当满足下列关系时称其为堆&#xff0c;其中 2i 和2i1应不大于n。 { K i ≤ K 2 i 1 K i ≤ K 2 i 或 { K i ≥ K 2 i 1 K i ≥ K 2 i {\huge \{}^{K_i≤K_{2i}} _{K_i≤K_{2i1}} …

《java 从入门到放弃》1.1 jdk 安装

1.jdk 是啥&#xff1f; jdk&#xff08;Java Development Kit&#xff09;&#xff0c;简单来说&#xff0c;就是java的开发工具。允许java 程序就是用它了。 jre &#xff0c;里面放的是java用的那些公用的包。 2.jdk下载 2.1 官网下载地址&#xff1a;Java Downloads | …
最新文章