手势控制的机器人手臂

将向你展示如何构建机械手臂并使用手势和计算机视觉来控制它。下面有一个在开发阶段的机械手臂的演示视频。

展示开发中的手臂的演示视频:https://youtu.be/KwiwetZGv0s

如图所示,该过程首先用摄像头捕捉我的手及其标志。通过跟踪特定的界标,例如拇指和食指的指尖,可以确定这些界标的相对运动,并将其转化为伺服系统的运动。这是通过处理数据并将整数值发送到控制伺服电机的 Arduino 的 Python 脚本完成的。

操作流程:

相机 —> python 脚本 —> Arduino —> 伺服电机

f6b4ec8ea3d509fd4f1539aa3f9ef856.jpeg

此项目的项目列表

  1. 机械臂套件(仅限框架和伺服电机 (MG996)):

  2. Arduino 板(加 USB 线)

  3. 跳线

  4. 电话充电器

  5. PCA9685 伺服电机驱动器

  6. 笔记本电脑

  7. 抽吸泵套件:

  8. 继电器模块

使用的编程环境:

  1. 开发环境

  2. PyCharm 编辑器

先决条件:

  • OpenCV 知识

    • youtube 上有教程:https://www.youtube.com/watch?v=WQeoO7MI0Bs&t=8774s

  • 了解 C++ 和 Python

需要修改机械臂组件(用于我购买的套件),因为伺服电机在完全组装后无法移动机械臂,导致某些自由度 (DOF) 的损失,并取消了作为末端执行器的抓手。我选择改用抽吸泵机构。

对于这个项目,你可以只购买我使用的机械臂套件,或者只购买底盘并升级到更强大的伺服系统,以与完整的组件一起工作。第二种选择需要你使用本文作为指导,并进行修改以达到你想要的结果。

  1. 组装机械臂:

  • 将 Arduino 代码加载到电路板上,并使用电路图连接伺服电机。

  • 是时候组装机器人手臂了。在开始之前,确保按照此处所述(https://www.electroniclinic.com/pca9685-servo-driver-arduino-circuit-diagram-and-code/#google_vignette),通过下载并安装 HCPCA9685 库来加载 Arduino 代码。使用提供的电路图连接伺服电机,以确保它们处于所需位置。首先将手臂底部的伺服(旋转底座关节)连接到 PCA9865 的引脚 0,然后将手臂中的下一个伺服(肩关节)连接到引脚 3,依此类推,从左到右移动 PCA9685。最后,启动 Arduino 板并等待伺服电机到达其位置。

  1. 手臂关节

  • 将滑轮放在伺服电机上并固定螺钉。

  • 现在是时候组装机器人手臂的其他部分了。使用此处(https://automaticaddison.com/how-to-build-a-diy-aluminium-6-dof-robotic-arm-from-scratch/) 链接的视频作为指南,但你可以随意使用自己的方法来实现图像中描绘的姿势。如果在测试过程中遇到任何问题,例如关节不动,请检查伺服电机是否发热。如果天气很热,则该接头处可能存在装配问题,导致其无法正常工作。

880cd4c1df8635cfd4687f9dd599ebb0.jpeg 8b027964dfa74b78feca5c2c5b0891ef.jpeg

图片显示机器人手臂处于原位。

96a14d68a54124efe26d188f0cbf54f4.png 764ba1038ba341b2071f96e56cbe3d4d.png 5ba7b399be4fe495d7e84a5c13a3a640.png

组装手臂时供参考的附加图片

  1. 连接抽吸泵:

  • 如下图所示连接软管,黑色箭头代表空气的连接和流动。

a0a41d8ba66fd4ffef8e2e44b2a19f83.jpeg 1651a97e39eb8a1d86f88e8247e1b14e.jpeg

抽吸泵机构

  • 将真空泵、继电器模块和电磁阀的负极端子连接到电源的负极端子。

  • 将继电器模块的正极端子连接到电源的正极端子。

  • 将继电器模块的信号线连接到 Arduino 板的数字引脚 3。

  • 将真空泵的正极端子连接到继电器模块的常开 (NO) 端子。

  • 将电磁阀的正极端子连接到继电器模块的常闭 (NC) 端子。

  • 将继电器模块的公共端子连接到电源输入的正极端子。

  1. Arduino代码:

  • 设置和初始化库和变量。

#include <Servo.h>

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();


#define USMIN  1000//600 // This is the rounded 'minimum' microsecond length based on the minimum pulse of 150
#define USMAX  2000//2400 // This is the rounded 'maximum' microsecond length based on the maximum pulse of 600
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

uint16_t Pos1;
uint16_t Pos2;
uint16_t Pos3;
uint16_t Pos0;
 int ang;
 int ang2;
 int ang3;
 int ang0;
 int X;  //variable storing data from the index finger (landmark 8)
 int Y;  //variable storing data from the middle finger(landmark 12)
 int Z;  //variable storing data from the ring finger (landmark 16)
 int J;  //variable storing data from the bottom of the palm (landmark 0)
 int K;  //variable storing data from the thumb finger (landmark )
 int L;  //variable storing data from the thumb finger (landmark )
 const int RELAY_PIN = 3; // auction pump relay pin
 const int RELAY_PIN = 3;
  • “setup”部分在通电后运行一次并定位伺服电机。

void setup() {
  Serial.begin(9600);
  Serial.println("Ready"); // prints ready on the serial port
  pinMode(RELAY_PIN, OUTPUT);  //setting relay pin to output
  digitalWrite(RELAY_PIN, LOW);  //putting off relay pin
  Pos1 = 1500;
  Pos2 = 1500;
  Pos3 = 1500;
  Pos0 = 1500;
  pwm.begin();

  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  for(int i = 0; i < 4; i++) {  // loop to position the servo motors in their initial state after startup
  
    pwm.writeMicroseconds(i, Pos1);
    delay(10);

    }
}
  • 关键组件是“postn”函数,它将“loop”函数中设置的变量转换为运动。它需要四个输入(A、B、C 和 D)

  • A是手掌上被跟踪以控制伺服电机的点,从计算机串行发送到Arduino,由“loop”函数读取。

  • 该函数会验证伺服器的当前位置是否在其最小和最大限制范围内,然后根据它经过的触发器在任一方向上以 20 为增量进行调整。在界标 0(手腕/下手掌)处运行代码时,触发器 B 和 C 会显示在屏幕上。

  • B 和 C 是上/左和下/右触发器,当跟踪的手掌点穿过它们时,分别控制伺服运动的上/右或下/左。

  • “postn”函数的最后一部分根据手部地标 3 和 4 的位置打开和关闭抽吸泵。

void Postn()
{
    
     if ((X < 90) && (Pos1 > USMIN) && (Pos1 < USMAX)) // if the tracked landmark (stored as X) is lower than the lower trigger (90) and
    {                                                  // and the current position (pos1) of the servo is greater than its minimum limit (usmin)
      Pos1 -= 20;                                      //and the current position of the servo is less than its maximum limit (usmax)
    pwm.writeMicroseconds(1, Pos1);                     // the servo position moves in steps of 20
    Serial.print("X = ");                               // a signal is sent to the servo to effect the change in position (pos1)
    Serial.println(Pos1);
      if (Pos1 == USMIN)                            // if the variable storing the position of the servo gets to the min limit (servomax)
      {Pos1 = USMIN + 20;}                          // the servo will get stuck; hence, this line reduces its value at the end of this section of the code. you can comment this line and run a test to better understand 
    }

     if ((X > 150) && (Pos1 > USMIN) && (Pos1 < USMAX))  // if the tracked landmark (stored as X) is higher than the uppwer trigger (150) and
    {                                                     // and the current position (pos1) of the servo is greater than its minimum limit (usmin)
      Pos1 += 20;                                         //and the current position of the servo is less than its maximum limit (usmax)
    pwm.writeMicroseconds(1, Pos1);                         // a signal is sent to the servo to effect the change in position (pos1)
    Serial.print("X = ");
    Serial.println(Pos1);
    if (Pos1 == USMAX)                             // if the variable storing the position of the servo gets to the max limit (servomax)
      {Pos1 = USMAX - 20;}                      // the servo will get stuck; hence, this line reduces its value at the end of this section of the code. you can comment this line and run a test to better understand 
    }

    if ((Y < 70) && (Pos2 > USMIN) && (Pos2 < USMAX))   // the logic above applies to the here and for the lest of this function
    {
      Pos2 -= 20;
    pwm.writeMicroseconds(2, Pos2);
    Serial.print("Y = ");
    Serial.println(Pos2);
      if (Pos2 == USMIN)
      {Pos2 = USMIN + 20;} 
    }

     if ((Y > 130) && (Pos2 > USMIN) && (Pos2 < USMAX))
    {
      Pos2 += 20;
    pwm.writeMicroseconds(2, Pos2);
    Serial.print("Y = ");
    Serial.println(Pos2);
    if (Pos2 == USMAX)
      {Pos2 = USMAX - 20;} 
    }

       if ((Z < 70) && (Pos3 > USMIN) && (Pos3 < USMAX))
      {
      Pos3 += 20;
    pwm.writeMicroseconds(3, Pos3);
    Serial.print("Z = ");
    Serial.println(Pos3);
    if (Pos3 == USMAX)
      {Pos3 = USMAX - 20;} 
    }

     if ((Z > 130) && (Pos3 > USMIN) && (Pos3 < USMAX))
  {
      Pos3 -= 20;
    pwm.writeMicroseconds(3, Pos3);
    Serial.print("Z = ");
    Serial.println(Pos3);
      if (Pos3 == USMIN)
      {Pos3 = USMIN + 20;} 
    }

    if ((J < 220) && (Pos0 > USMIN) && (Pos0 < USMAX))
    {
      Pos0 += 20;
    pwm.writeMicroseconds(0, Pos0);
    Serial.print("J = ");
    Serial.println(Pos0);
    if (Pos0 == USMAX)
      {Pos0 = USMAX - 20;} 
    }

     if ((J > 370) && (Pos0 > USMIN) && (Pos0 < USMAX))
    {
      Pos0 -= 20;
    pwm.writeMicroseconds(0, Pos0);
    Serial.print("J = ");
    Serial.println(Pos0);
      if (Pos0 == USMIN)
      {Pos0 = USMIN + 20;} 
    }

      if (K > L)   //The pump function checks if hand landmark 4 (tip of thumb) is to the right or left side 
    {                 //of hand landmark 3 (IP joint), this in turn switches on and off the suction pump.
     digitalWrite(RELAY_PIN, LOW);
     Serial.print("K =");
    Serial.println(K);
    Serial.print("L =");
    Serial.println(L);
 
    }

     if (K < L)
    {
     digitalWrite(RELAY_PIN, HIGH);
      Serial.print("K");
    Serial.println(K);
    Serial.print("L");
    Serial.println(L);
    }

    }
  • “loop”函数检查串行端口是否有从计算机传输的数据,并相应地调用“postn”函数。

void loop() {//   The 'loop' function checks the serial port for data being transmitted 
                 //from the computer; depending on the data it gets, it calls upon the 'postn' function.
                 // or the pump function

if(Serial.available() > 0)
  {
    if(Serial.read() == 'X')
    {
      X = Serial.parseInt();
      if(Serial.read() == 'Y')
        {
          Y = Serial.parseInt();
          if(Serial.read() == 'Z')
            {
              Z = Serial.parseInt();
              if(Serial.read() == 'J')
                {
                J = Serial.parseInt();
                if(Serial.read() == 'K')
                  {
                  K = Serial.parseInt();
                  if(Serial.read() == 'L')
                    {
                    L = Serial.parseInt();
                    Postn();
                    }
                  }
               }
            }
         }
     }
 }

   
}
  1. python 脚本中的注释解释了你需要了解的所有内容。使用 PyCharm 打开 python 脚本并运行脚本。弹出一个新窗口,显示相机看到的内容,正确放置你的手,并移动适当的地标以使机器人手臂移动。

    你可以在 Arduino IDE 上打开串行监视器以查看传输到 Arduino 的数据,但请确保在你想要重新启动 python 脚本时,关闭串行监视器以防止错误。

import cv2    #import neccesary libraries after adding them as packages to this project
import mediapipe as mp    # go to settings > python interpreter
import serial
import time
import math

Arduino=serial.Serial('/dev/cu.usbmodem14101', 9600, timeout=0.1)  #initialize the serial port(in quotation marks) for communicatin with the arduino

wCam, hCam = 1240, 720   # variable for setting the camera window width and height
cam = cv2.VideoCapture(0)  # start the webcam, use 0 for and inbuilt camera and 1 for an external one
cam.set(3, wCam)     #setting the camera window width and height
cam.set(4, hCam)
smoothV = 2
pVal = 0
cVal = 0

color = (255, 255, 255)

class mpHands:  #class used to detect hands, hand landmarks, measure distance and angle between hand landmarks
                # watche this video for an explanation of the class; https://www.youtube.com/watch?v=WQeoO7MI0Bs&t=8774s
    def __init__(self, mode=False, modelComplexity=1, maxHands=2, TrackCon=0.5, DetectCon=0.5):
        self.mode = mode
        self.modelComplexity = modelComplexity
        self.maxHands = maxHands
        self.TrackCon = TrackCon
        self.DetCon = DetectCon

        self.mpHands = mp.solutions.hands
        self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.modelComplexity,
                                        self.TrackCon, self.DetCon)
        self.mpDraw = mp.solutions.drawing_utils
    def Marks(self,frame):
        myHands=[]
        handsType=[]
        frameRGB=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
        results=self.hands.process(frameRGB)
        if results.multi_hand_landmarks != None:
            for hand in results.multi_handedness:
                #print(hand)
                #print(hand.classification)
                #print(hand.classification[0])
                handType=hand.classification[0].label
                handsType.append(handType)
            for handLandMarks in results.multi_hand_landmarks:
                myHand=[]
                for landMark in handLandMarks.landmark:
                    h, w, c = frame.shape
                    myHand.append((int(landMark.x*w),int(landMark.y*h)))
                myHands.append(myHand)
        return myHands, handsType

    def findDistance(self, a, b, frame ):
        x1, y1 =a[0], a[1]
        x2, y2 =b[0], b[1]
        cx, cy = (x1 + x2) // 2, (y1 + y2) // 2  # center point between thumb and index finger

        cv2.circle(frame, (x1, y1), 5, (255, 255, 255), cv2.FILLED)  # circle on thumb
        cv2.circle(frame, (x2, y2), 5, (255, 255, 255), cv2.FILLED)  # circle on index finger
        cv2.circle(frame, (cx, cy), 5, (255, 255, 255), cv2.FILLED)  # circle at center point bte thumb and index

        cv2.line(frame, (x1, y1), (x2, y2), (255, 255, 255), 3)  # line btw index and center point


        length = int(math.hypot(x2 - x1, y2 - y1))
        # print(length)
        return length, frame, [x1, y1, x2, y2, cx, cy]

    def findAngle(self, a, b, c, frame ):
        #get the landmarks
        x1, y1 = hand[a]
        x2, y2 = hand[b]
        x3, y3 = hand[c]
        #get the angle
        angle = math.degrees(math.atan2(y3-y2, x3-x2) - math.atan2(y1-y2, x1-x2))
        if angle<0:
            angle= angle+360
        return angle
        #print(angle)

width=1280
height=720
findHands=mpHands(2)

while True:
    ignore,  frames = cam.read()
    frame= cv2.flip(frames, 1) #fliping the camera output sideways, comment this line to see the difference
    handData, handType = findHands.Marks(frame)

    for hand, handType in zip(handData, handType):
        right = 0
        left = 0
        if handType == 'Right':
            handColor = (0, 0, 255)

                  #tracking landmark 0 located at the wrist (lower part of the palm)
            l1, l2 = hand[0]  # x and y coordinates for the point at the bottom of the palm (wrist)
            cv2.circle(frame, (l1, l2), 10, (0, 0, 255), cv2.FILLED)  # draw a red circle at the point
                                                                # this point controlls the rotation of the base of the arm
            cv2.line(frame, (50, l2), (220, l2), (46, 98, 84), 3)   #this lines draw a green, white and green line on the screen
            cv2.line(frame, (220, l2), (370, l2), (255, 255, 255), 3)    #movement of landmark 0 to the green part of the line makes the arm
            cv2.line(frame, (370, l2), (550, l2), (46, 98, 84), 3)          # rotate to the the left or right (when it crosses the limits triggers at the upper and lower points of the line (220 and 370 on the x axis respectively)

            cv2.circle(frame, (50, l2), 5, (255, 255, 255), cv2.FILLED)  #these lines draw a circle at the two ends of the gree, white and green line
            cv2.circle(frame, (550,l2), 5, (255, 255, 255), cv2.FILLED)  # they also draw circles at the two boundaries between the green and white line
            cv2.circle(frame, (220, l2), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (370, l2), 5, (255, 255, 255), cv2.FILLED)

            cv2.putText(frame, 'A', (l1,l2-50), cv2.FONT_HERSHEY_SIMPLEX,     # A is shown on the landmark being tracked
                                0.5, color, 2)
            cv2.putText(frame, 'B', (220, l2+50), cv2.FONT_HERSHEY_SIMPLEX,0.5, color, 2)  # prints B at the location of the leftward triggers
            cv2.putText(frame, 'c', (370, l2 + 50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)  # prints B at the location of the rightward triggers
            cv2.putText(frame, 'servo 0', (280, l2 + 50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) # prints  the pin of servo being controlled

            if (51 <= l1 <= 499):   # the total length of the line created above is 448 (449-51) on the X axis
             J = l1                 # if the landmark being tracked is between the range above then it is stored with the variable J
            elif (l1 < 51):         # if the point being tracked (landmark 0) is at a position less than 51 on the X axis (the min point on the line drawn above
                J = 51          #it is still stored as 51 because that is the lower limit and this being absent would result in errors when transferring data to the arduino( nothing will be sent to the arduino and the python script is coded such that something must be sent, thats why errors will come up)
            elif (l1 > 499):   # the same applies here with the upper limit of 499
                J = 499

                # tracking landmark 8 located at the tip of the index finger, this controlls the shoulder joint
            l1, l2 = hand[8]   # the same logic used to track landmark 0 applies here
            cv2.circle(frame, (l1, l2), 10, (0, 0, 255), cv2.FILLED)

            cv2.line(frame, (l1, 20), (l1, 90), (46, 98, 84), 3)
            cv2.line(frame, (l1, 90), (l1, 150), (255, 255, 255), 3)
            cv2.line(frame, (l1, 150), (l1, 220), (46, 98, 84), 3)

            cv2.circle(frame, (l1, 20), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 220), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 90), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 150), 5, (255, 255, 255), cv2.FILLED)
            if (21 <= l2 <= 219):
              X = l2
            elif (l2 < 21):
                X = 21
            elif (l2 > 179):
                X = 179

                # tracking landmark 12 located at the tip of the middle finger, this controlls the elbow joint
            l1, l2 = hand[12]    # the same logic used to track landmark 0 applies here
            cv2.circle(frame, (l1, l2), 10, (0, 0, 255), cv2.FILLED)

            cv2.line(frame, (l1, 20), (l1, 70), (46, 98, 84), 3)
            cv2.line(frame, (l1, 70), (l1, 130), (255, 255, 255), 3)
            cv2.line(frame, (l1, 130), (l1, 180), (46, 98, 84), 3)

            cv2.circle(frame, (l1, 20), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 180), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 70), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 130), 5, (255, 255, 255), cv2.FILLED)
            if (21 <= l2 <= 179):
                 Y = l2
            elif (l2 < 21):
                Y = 21
            elif (l2 > 179):
                Y = 179

                # tracking landmark 16 located at the tip of the ring finger, this controlls the wrist joint
            l1, l2 = hand[16]    # the same logic used to track landmark 0 applies here
            cv2.circle(frame, (l1, l2), 10, (0, 0, 255), cv2.FILLED)

            cv2.line(frame, (l1, 20), (l1, 70), (46, 98, 84), 3)
            cv2.line(frame, (l1, 70), (l1, 130), (255, 255, 255), 3)
            cv2.line(frame, (l1, 130), (l1, 180), (46, 98, 84), 3)

            cv2.circle(frame, (l1, 20), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 180), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 70), 5, (255, 255, 255), cv2.FILLED)
            cv2.circle(frame, (l1, 130), 5, (255, 255, 255), cv2.FILLED)
            if (21 <= l2 <= 179):
                Z = l2
            elif (l2 < 21):
                Z = 21
            elif (l2 > 179):
                Z = 179


                         # this part id used to controll the suction pump. the suction pump is switched on or off depending on weather the tip of the thumb is positioned
                         # at he left or right of the joint below the tip of the thumb
            l1, l2 = hand[4]   #this represents the X and Y coordinated of tip of the thumb
            l3, l4 = hand[3]   #this represents the X and Y coordinated of joint below the tip of the thumb

            cv2.circle(frame, (l1, l2), 10, color, cv2.FILLED)  #this draws a circle at landmark 4, color was decleared in line 17

            cv2.line(frame, (l3, l4+20), (l3, l4-100), (255, 255, 255), 3)   #draws a white vertical line at landmark 3
            #cv2.line(frame, (65, l2), (80, l2), (255, 0, 0), 3)

            cv2.circle(frame, (l3, l4+20), 5, (255, 255, 255), cv2.FILLED)   # draws circles at the two ends of the line drawn above
            cv2.circle(frame, (l3, l4-100), 5, (255, 255, 255), cv2.FILLED)
            #cv2.circle(frame, (80, l2), 5, (255, 255, 255), cv2.FILLED)
            K = l1
            L = l3
            if ( l1 < l3):   # the color of the circle at the tip of the thumb changes to red or green
                color = (46, 98, 84)   # depending on its position (to the right or left of the joint below the thumb (landmark 3))
            elif (l1 > l3):           # this signifies weather the pupmp is powered on or off
                color = (0, 0, 255)


            values = 'X{0}Y{1}Z{2}J{3}K{4}L{5}'.format(X, Y, Z, J, K, L)   # all the variables used to store data are here
            Arduino.write(values.encode('utf-8'))    # the data containing the position of the landmarks being tracked are sent to the arduino

            print(values)  # the data being transferred is displayed




    cv2.imshow('my WEBcam', frame)
    #cv2.moveWindow('my WEBcam',0,0)
    if cv2.waitKey(1) & 0xff ==ord('q'):   # press the Q button on the keyboard to stop the camera
        break

完成后,手的演示视频:https://youtu.be/XSMEVKlM3js

现在就这些了,你可以随时改进这个项目的伺服电机,并调整代码。此外,可以嵌入深度视觉相机,通过跟踪整个手指,而不是一个手指的运动,来更精确地控制手臂手指上的单个地标。

☆ END ☆

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。

扫描二维码添加小编↓

282372c89e3a71e7f98787664756834b.jpeg

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

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

相关文章

C++语法(16)---- 多态

https://blog.csdn.net/m0_63488627/article/details/130106690?spm1001.2014.3001.5501https://blog.csdn.net/m0_63488627/article/details/130106690?spm1001.2014.3001.5501 目录 1. 多态的概念 2.多态的实现 1.虚函数 2.多态条件 得到的多态条件 特殊条件 3.虚函…

数据结构入门-10-AVL

文章目录 一、AVL的性质1.2 平衡二叉树定义 二、添加需达到平衡2.1 平衡因子2.1.2 平衡因子的实现 2.2 判断该二叉树是否为平衡二叉树2.3 左旋右旋2.3.1 左旋LL右旋RR基本原理2.3.2 LR RLLRRL 三、AVL中删除 一、AVL的性质 平衡二叉树 AVL树得名于它的俄罗斯发明者G. M. Adels…

被裁员了,要求公司足额补缴全部公积金,一次补了二十多万!网友兴奋了,该怎么操作?...

被裁员后&#xff0c;能要求公司补缴公积金吗&#xff1f; 一位网友问&#xff1a; 被裁员了&#xff0c;要求公司把历史公积金全部足额缴纳&#xff0c;现在月薪2.3万&#xff0c;但公司每个月只给自己缴纳300元公积金&#xff0c;结果一次补了二十多万&#xff0c;一次性取出…

Node 【Buffer 与 Stream】

文章目录 &#x1f31f;前言&#x1f31f;Buffer&#x1f31f; Buffer结构&#x1f31f; 什么时候用Buffer&#x1f31f; Buffer的转换&#x1f31f; Buffer使用&#x1f31f; 创建Buffer&#x1f31f; 字符串转Buffer&#x1f31f; Buffer转字符串&#x1f31f; 拼接Buffer&am…

Java每日一练(20230417)

目录 1. N 皇后 &#x1f31f;&#x1f31f;&#x1f31f; 2. 搜索二维矩阵 &#x1f31f;&#x1f31f; 3. 发奖金问题 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 …

权限控制_SpringSecurity

认证-授权 认证&#xff1a;系统提供的用于识别用户身份的功能&#xff0c;通常提供用户名和密码进行登录其实就是在进行认证&#xff0c;认证的目的是让系统知道你是谁。 授权&#xff1a;用户认证成功后&#xff0c;需要为用户授权&#xff0c;其实就是指定当前用户可以操作…

Node 内置模块 【fs模块】

文章目录 &#x1f31f;前言&#x1f31f;fs模块&#x1f31f; 使用fs模块&#x1f31f; 异步编程和同步编程&#x1f31f; 异步编程&#x1f31f; 同步编程 &#x1f31f;常用操作&#x1f31f; 文件操作&#x1f31f; readFile异步读取文件&#x1f31f; readFileSync同步读取…

YOLOv8 更换主干网络之 GhostNetV2

《GhostNetV2:Enhance Cheap Operation with Long-Range Attention》 轻量级卷积神经网络(CNN)是专门为在移动设备上具有更快推理速度的应用而设计的。卷积操作只能捕捉窗口区域内的局部信息,这防止了性能的进一步提高。将自注意力引入卷积可以很好地捕捉全局信息,但这将大…

【系统集成项目管理工程师】项目进度管理

&#x1f4a5;十大知识领域&#xff1a;项目进度管理 主要考计算题 项目进度管理包括以下 7 个过程: 规划进度管理过程定义活动过程排列活动顺序过程估算活动资源过程估算活动持续时间过程制定进度计划过程控制进度过程 一、规划进度管理过程 制定政策、程序和文档以管理项目进…

JeecgBoot 3.5.1 版本发布,开源的企业级低代码平台

项目介绍 JeecgBoot是一款企业级的低代码平台&#xff01;前后端分离架构 SpringBoot2.x&#xff0c;SpringCloud&#xff0c;Ant Design&Vue3&#xff0c;Mybatis-plus&#xff0c;Shiro&#xff0c;JWT 支持微服务。强大的代码生成器让前后端代码一键生成! JeecgBoot引领…

苹果电容笔值得买吗?ipad电容笔推荐平价

在当今时代&#xff0c;高科技已经成为推动数字产品发展的重要推动力。无论是在工作上&#xff0c;还是在学习上&#xff0c;大屏幕都能起到很好的作用。IPAD将会更好地融入我们的生活&#xff0c;不管是现在还是未来。而ipad配上一支简单的电容笔&#xff0c;不仅可以提高工作…

几分种学会React Router v6使用

React路由可以实现页面间的切换。 传送门&#xff1a;英文文档 中文教程&#xff1a; https://www.reactrouter.cn/docs/getting-started/tutorial 1.基础使用 1.安装react-router npm i react-router-dom62.配置根组件app.js import { React, lazy, Suspense } from "…

C++ -3- 类和对象 (中) | 构造函数与析构函数(一)

文章目录 1.类的6个默认成员函数2.构造函数3.析构函数构造函数与析构函数应用场景缺省值初始化 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自…

【文章学习系列之模型】FEDformer

本章内容 文章概况模型流程主要结构Frequency Enhanced Decomposition Architecture&#xff08;频率增强分解结构&#xff09;Fourier enhanced blocks and Wavelet enhanced blocks&#xff08;傅里叶增强模块和小波增强模块&#xff09;Fourier Enhanced Structure&#xff…

【Java 数据结构】优先级队列 (堆)

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

快速精简软件,如何让软件缩小到原来的5%大小,从删除文件入手,到修改C++引用库,合规解决存储问题

Hi~大家好&#xff0c;今天制作一个简单的精简软件的教学~ 事先说明下&#xff0c;精简软件并不违反任何规定&#xff0c;尤其是开源软件&#xff0c;这里也仅讨论开源软件的修改&#xff0c;根据几乎所有开源软件的开源规则&#xff0c;精简软件&#xff0c;本质也就是修改软件…

戴尔G3 Ubuntu18.04双系统安装

ROS学习需要使用Linux系统&#xff0c;首先就是Ubuntu&#xff0c;我选择的是18.04.6这个版本&#xff0c;因为后面我要使用以Jetson Nano为主控的Jetbot进行ROS编程&#xff0c;Jetbot所带的出厂镜像就是18.04&#xff0c;为了方便程序移植&#xff0c;以及减少不必要的麻烦。…

【消息队列】聊一下Kafka副本机制

副本机制的好处 副本在分布式系统下&#xff0c;不同的网络互联的机器保存同一份数据。我们知道在分布式系统中&#xff0c;都会通过数据镜像、数据冗余的方式来提升高可用性。 提供数据冗余&#xff1a;这点比较好理解&#xff0c;说白了就是通过数据冗余在不同的服务器上&a…

大家副业都在做什么?csgo搬砖靠谱的副业推荐给你

从来没想过&#xff0c;以前只会玩CSGO的男孩子&#xff0c;现在居然能借助游戏赚到钱了&#xff01;甚至不需要什么专业的技巧&#xff0c;简简单单 在steam平台选择有利润的道具后&#xff0c;再上架到国内网易BUFF平台&#xff0c;赚取“信息差”差价而已&#xff01; 谁大…

SpringCloud学习(六)——Feign的简单使用

文章目录 1. Feign 的使用1.1 引入依赖1.2 添加注解1.3 编写Feign客户端1.4 测试 2. Feign中的自定义配置2.1.配置文件方式2.2.Java代码方式 3. Feign 性能优化4. Feign的抽取式使用4.1 抽取配置4.2 引入依赖4.3 指明Client 在此之前&#xff0c;我们服务之间需要进行调用的时候…