2024-10-10 08:47:28 [显示全部楼层]
3728浏览
查看: 3728|回复: 1

[ESP8266/ESP32] 做一只由 OpenCV 控制的仿生手

[复制链接]
本帖最后由 RRoy 于 2024-10-10 08:47 编辑

这个项目介绍了如何制作和控制一只仿生手。作者最初受到Instagram上一个视频的启发,该视频展示了使用MPU6050传感器追踪手部动作并在屏幕上显示3D模型。作者决定将这个想法进一步发展,使用OpenCV来控制一只真实的仿生手。

做一只由 OpenCV 控制的仿生手图1

大家好,在这篇教程中,我想和大家分享一下如何制作并控制一只自己的仿生手。这个想法源于我在无意中刷Instagram时,看到一段短视频:一个人通过MPU6050传感器来跟踪手部运动,并在屏幕上显示手的3D模型。因为我之前也使用过这个传感器,所以觉得这个我也能完成。我一直喜欢将编程与现实世界结合起来,于是我想,为什么不将这些测量数据传输到一个真实的仿生手上呢?后来我决定,使用OpenCV代替MPU6050会更加高效,部分原因也是我想借此机会学习另一种技能。

特别感谢Gaël Langevin,他在InMoov项目[1]中设计了这个手的模型,并慷慨地分享了出来。

效果展示



所需材料InMoov手及前臂
  • 3D打印
  • 焊接工具
  • 约1公斤的耗材(PETG 或 ABS 或 PLA)
  • 3米钓鱼线(能承重约20公斤)
  • 5根扩展弹簧(3/16″ x 1-3/4)
  • RTV硅胶 Ecoflex™ 00-10
螺丝、螺母和螺栓
  • 10个M2x4平头木螺丝
  • 10个M3x4mm平头螺丝
  • 4个M3x12mm平头木螺丝
  • 20个M3x12mm平头螺丝
  • 25个M3x16mm平头螺丝
  • 10个M3x20mm平头螺丝
  • 35个M3螺母
电子元件
  • 1块ESP32 38-pin 开发模块
  • 1根micro USB数据线
  • 5个线性霍尔传感器(49E)
  • 5个直径2.5mm x 1mm的磁盘磁铁
  • 1根16芯彩排线
  • 5个1k电阻
  • 5个2k电阻
  • 6个伺服电机(JX PDI-6225MG-300)
  • 1块定制PCB(可选)
  • 1个电源(理想情况下为6V或5V,功率约100W,因为每个伺服电机的电流可达3A)


步骤1:3D打印手部

做一只由 OpenCV 控制的仿生手图2

做一只由 OpenCV 控制的仿生手图3

3D打印文件见文末。

做一只由 OpenCV 控制的仿生手图4

打印时,建议使用稍高的填充率(约30%),以提高部件的耐用性。关于材料,InMoov使用的是ABS,不过如果你没有稳定打印ABS的设备,PETG或PLA同样可以使用。

步骤2:3D打印前臂
做一只由 OpenCV 控制的仿生手图5

同样地,手部所需的文件如下,并且也在inmoov STL零件库[2]中。请注意,在inmoov零件库中有原版inmoov机器人的文件。这个手是i2版本,因此你只需要前臂部分的一些零件。另外一个需要注意的是,当打印Bolt_entretoise7时,你只需要中间的螺栓和夹子(其他部分是为旧版手设计的)。

做一只由 OpenCV 控制的仿生手图6

3D打印文件文末下载。

你还可以打印一个我自己在Fusion 360中设计的小展示支架。

做一只由 OpenCV 控制的仿生手图7

链接:https://www.printables.com/model/593999-inmoov-hand-stand?lang=cs

步骤3:组装
做一只由 OpenCV 控制的仿生手图8

在组装时,可以参考InMoov提供的hand i2[3]与前臂[4]的教程,这些教程非常详细,提供了所有必要的信息。

做一只由 OpenCV 控制的仿生手图9

做一只由 OpenCV 控制的仿生手图10

做一只由 OpenCV 控制的仿生手图11

初始部件的组装相对简单,只需用螺丝将整个设计固定在一起。稍微复杂的部分是确保钓鱼线的布置不打结,以及将霍尔传感器正确安装在指尖。

步骤4:硅胶指尖
做一只由 OpenCV 控制的仿生手图12

做一只由 OpenCV 控制的仿生手图13

做一只由 OpenCV 控制的仿生手图14

对于指尖来说,使用非常柔软的硅胶是很重要的,因为霍尔传感器的读取有一定的不确定性。硅胶越软,内部的磁铁运动幅度越大,从而更容易从数据中识别。将硅胶部分粘到3D打印出的部件上之后,可以用它来调整霍尔传感器的突出程度。

做一只由 OpenCV 控制的仿生手图15

在这一切设置好之后,强烈建议将霍尔传感器固定在手指的末端,否则在手指运动过程中,霍尔传感器可能会稍微移动,从而影响测量结果。

步骤5:电路
做一只由 OpenCV 控制的仿生手图16

做一只由 OpenCV 控制的仿生手图17

电路方面,使用16路舵机驱动模块会带来显著的效果,但也存在一些缺点。该驱动模块有两种不同的版本,虽然它们几乎相同,但在反极性保护电路(用于电容)所使用的晶体管上有区别,一个版本可承受约8A电流,而另一个版本仅可承受约0.5A,这远低于舵机实际需要的电流。因此,最好不要让伺服电机通过驱动模块供电,或者按照视频[5]中所述进行小改动,并在使用电容时要格外小心。

关于霍尔传感器,我们需要使用一个电压分压器,因为它输出的电压范围在0V到5V之间,而ESP32只能正确读取0V到3.3V的ADC值。

对于整个电路,可以选择使用面包板,或者更好的是使用定制PCB(作者版本的GitHub链接[6])。

步骤6:测试





▲ 在仿生手上测试霍尔传感器





▲ 在仿生手上测试电机

做一只由 OpenCV 控制的仿生手图18

做一只由 OpenCV 控制的仿生手图19

由于每个伺服电机和霍尔传感器都略有不同,所以需要对它们进行测试。

最重要的是测试霍尔传感器,因为它们测量的值将决定仿生手是否施加了足够的压力。我建议使用Arduino IDE的绘图功能来绘制数据,以观察数值何时超过自然不确定性范围。

为此,我们可以使用这个非常简单的代码片段:

  1. int hall = "Pin number your hall sensor is connected to";
  2. void setup() {
  3. Serial.begin(115200);
  4. pinMode(hall, INPUT);
  5. }
  6. void loop() {
  7. Serial.println(analogRead(hall));
  8. delay(10);
  9. }
复制代码


步骤7:代码OpenCV(在VSCode中运行的Python代码)
就运行在带有网络摄像头的PC上的代码而言,我们需要完成两个主要任务:

第一个任务是使用OpenCV追踪手部及其元素。基于这些元素我们可以计算每根手指的位置。

第二个任务是通过串口将数据发送到ESP32,以便控制伺服电机。这些数据可以相对简化,因为我们不需要发送精确的角度值,而只需发送每个手指是否弯曲的信息。因此,我们可以发送五个0或1,并在末尾加一个符号以便后续识别每个数字的索引。

这种方法将手部追踪和数据传输简化为一个二进制状态系统,使得数据处理和传输更加高效,同时仍能提供足够的信息来控制仿生手的动作。

首先,我们需要为Python代码导入以下库:
  1. import cv2
  2. import mediapipe as mp
  3. import time
  4. import serial
复制代码

然后,我们需要创建一个用于处理摄像头数据的类:

  1. class HandDetector():
  2.     # Constructor of the class with parameters for the measurement
  3.     def __init__(self, mode=False, maxHands=1, detectionCon=0.5, trackCon=0.5):
  4.         self.mode = mode
  5.         self.maxHands = maxHands
  6.         self.detectionCon = detectionCon
  7.         self.trackCon = trackCon
  8.         self.mpHands = mp.solutions.hands
  9.         self.hands = self.mpHands.Hands()
  10.         self.mpDraw = mp.solutions.drawing_utils
  11.     # Function for finding and drawing the hand
  12.     def findHands(self, frame, draw=True):
  13.         imgRGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
  14.         self.results = self.hands.process(imgRGB)
  15.         
  16.         if self.results.multi_hand_landmarks:
  17.             for handLms in self.results.multi_hand_landmarks:
  18.                 if draw:
  19.                     self.mpDraw.draw_landmarks(frame, handLms, self.mpHands.HAND_CONNECTIONS)
  20.         return frame
  21.    
  22.     # Function for finding each hand landmark and drawing its position
  23.     def findPosition(self, frame, handNo=0, draw=False):
  24.         lmList = []
  25.         if self.results.multi_hand_landmarks:
  26.             myHand = self.results.multi_hand_landmarks[handNo]
  27.             for id, lm in enumerate(myHand.landmark):
  28.                 h, w, c = frame.shape
  29.                 cx, cy = int(lm.x * w), int(lm.y * h)
  30.                 lmList.append([id, cx, cy])
  31.                 if draw and id == 0:
  32.                     cv2.circle(frame, (cx, cy), 15, (255, 0, 255), -1)
  33.         return lmList
复制代码

接下来定义主函数:

  1. def main():
  2.     # The prevTime and currentTime are used to calculate the FPS later
  3.     prevTime = 0
  4.     currentTime = 0
  5.     # Array for storing the info about the hand
  6.     hand = [["Wrist", False], ["Index", False], ["Middle", False],
  7.             ["Ring", False], ["Thumb", False], ["Pinky", False]]
  8.     # Initializing the Serial and opencv
  9.     ser = serial.Serial(port="The name of the port the ESP32 is connected to")
  10.     # I had to include the "cv2.CAP_DSHOW" because I had issues with the webcam loading on my linux machine
  11.     cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
  12.     detector = HandDetector()
  13.     #MAIN LOOP OF THE CODE#
  14.     # Releasing the stuff allocated for opencv
  15.     cap.release()
  16.     cv2.destroyAllWindows()
  17. main()
复制代码


以及代码的主循环:

  1.     while (True):
  2.       # Finding the hands and reading the position of hte landmarks
  3.       ret, frame = cap.read()
  4.       frame = detector.findHands(frame)
  5.       lmList = detector.findPosition(frame)
  6.         
  7.       if len(lmList) > 0:
  8.             j = 1
  9.             change = False
  10.             # Loop which checks if the top of the finger is below the second most top
  11.             for i in range(1, 6):
  12.                 if i == 1 and lmList[4][1] < lmList[3][1] and not hand[4][1]:
  13.                     # In case that it is true it changes all the needed data
  14.                     hand[4][1] = True
  15.                     change = True
  16.                     print(hand[4][0], hand[4][1])
  17.                 elif i == 1 and lmList[4][1] > lmList[3][1] and hand[4][1]:
  18.                     hand[4][1] = False
  19.                     change = True
  20.                     print(hand[4][0], hand[4][1])
  21.                 elif i != 1:
  22.                     if lmList[i*4][2] > lmList[(i*4)-2][2] and not hand[j][1]:
  23.                         hand[j][1] = True
  24.                         change = True
  25.                         print(hand[j][0], hand[j][0])
  26.                     elif lmList[i*4][2] < lmList[(i*4)-2][2] and hand[j][1]:
  27.                         hand[j][1] = False
  28.                         change = True
  29.                         print(hand[j][0], hand[j][0])
  30.                     if j == 3:
  31.                         j += 2
  32.                     else:
  33.                         j += 1
  34.             # If there has been any change in the state of the hand this code block will run
  35.             if change:
  36.                 msg = ""
  37.                 # Converts the boolean values to 0s and 1s
  38.                 for i in range(6):
  39.                     if hand[i][1]:
  40.                         msg += "1"
  41.                     else:
  42.                         msg += "0"
  43.                 # Adds the ending symbol and sends the data over to the ESP32
  44.                 msg += '\n'
  45.                 print(msg)
  46.                 ser.write(msg.encode("Ascii"))
  47.         # Calculates the FPS and displays it on the frame
  48.         currentTime = time.time()
  49.         fps = 1/(currentTime-prevTime)
  50.         prevTime = currentTime
  51.         cv2.putText(frame, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 0, 255), 3)
  52.         # Shows what the webcam sees on a frame
  53.         cv2.imshow("frame", frame)
  54.         # If we press "q" it quits running the program
  55.         if cv2.waitKey(1) & 0xFF == ord("q"):
  56.             break
复制代码


整个代码 OpenCV:
  1. import cv2
  2. import mediapipe as mp
  3. import time
  4. import serial
  5. class HandDetector():
  6.     def __init__(self, mode=False, maxHands=2, detectionCon=0.5, trackCon=0.5):
  7.         self.mode = mode
  8.         self.maxHands = maxHands
  9.         self.detectionCon = detectionCon
  10.         self.trackCon = trackCon
  11.         self.mpHands = mp.solutions.hands
  12.         self.hands = self.mpHands.Hands()
  13.         self.mpDraw = mp.solutions.drawing_utils
  14.     def findHands(self, frame, draw=True):
  15.         imgRGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
  16.         self.results = self.hands.process(imgRGB)
  17.         
  18.         if self.results.multi_hand_landmarks:
  19.             for handLms in self.results.multi_hand_landmarks:
  20.                 if draw:
  21.                     self.mpDraw.draw_landmarks(frame, handLms, self.mpHands.HAND_CONNECTIONS)
  22.         return frame
  23.    
  24.     def findPosition(self, frame, handNo=0, draw=False):
  25.         lmList = []
  26.         if self.results.multi_hand_landmarks:
  27.             myHand = self.results.multi_hand_landmarks[handNo]
  28.             for id, lm in enumerate(myHand.landmark):
  29.                 h, w, c = frame.shape
  30.                 cx, cy = int(lm.x * w), int(lm.y * h)
  31.                 lmList.append([id, cx, cy])
  32.                 if draw and id == 0:
  33.                     cv2.circle(frame, (cx, cy), 15, (255, 0, 255), -1)
  34.         return lmList
  35. def main():
  36.     prevTime = 0
  37.     currentTime = 0
  38.     hand = [["Wrist", False], ["Index", False], ["Middle", False],
  39.             ["Ring", False], ["Thumb", False], ["Pinky", False]]
  40.     ser = serial.Serial(port="COM3")
  41.     cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
  42.     detector = HandDetector()
  43.     while (True):
  44.         ret, frame = cap.read()
  45.         frame = detector.findHands(frame)
  46.         lmList = detector.findPosition(frame)
  47.         
  48.         if len(lmList) > 0:
  49.             j = 1
  50.             change = False
  51.             for i in range(1, 6):
  52.                 if i == 1 and lmList[4][1] < lmList[3][1] and not hand[4][1]:
  53.                     hand[4][1] = True
  54.                     change = True
  55.                     print(hand[4][0], hand[4][1])
  56.                 elif i == 1 and lmList[4][1] > lmList[3][1] and hand[4][1]:
  57.                     hand[4][1] = False
  58.                     change = True
  59.                     print(hand[4][0], hand[4][1])
  60.                 elif i != 1:
  61.                     if lmList[i*4][2] > lmList[(i*4)-2][2] and not hand[j][1]:
  62.                         hand[j][1] = True
  63.                         change = True
  64.                         print(hand[j][0], hand[j][0])
  65.                     elif lmList[i*4][2] < lmList[(i*4)-2][2] and hand[j][1]:
  66.                         hand[j][1] = False
  67.                         change = True
  68.                         print(hand[j][0], hand[j][0])
  69.                     if j == 3:
  70.                         j += 2
  71.                     else:
  72.                         j += 1
  73.             if change:
  74.                 msg = ""
  75.                 for i in range(6):
  76.                     if hand[i][1]:
  77.                         msg += "1"
  78.                     else:
  79.                         msg += "0"
  80.                 msg += '\n'
  81.                 print(msg)
  82.                 ser.write(msg.encode("Ascii"))
  83.         currentTime = time.time()
  84.         fps = 1/(currentTime-prevTime)
  85.         prevTime = currentTime
  86.         cv2.putText(frame, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 0, 255), 3)
  87.         cv2.imshow("frame", frame)
  88.         if cv2.waitKey(1) & 0xFF == ord("q"):
  89.             break
  90.     cap.release()
  91.     cv2.destroyAllWindows()
  92. main()
复制代码

ESP32(Arduino IDE)
我们可以充分利用ESP32是双核这一特点,类似于PC的代码,我们同样需要完成两项主要工作。

首先是接收来自PC的数据。正如前面提到的,数据基本上是一个带有六位二进制数和结束符的字符串。此外,由于只有在状态变化时才会传输数据,我们可以立即将这些值(转换为true或false)分配给相应的变量。将这个任务分配给核心0,而主循环则在核心1上运行。

第二项工作就是控制手部运动。为此,我们需要不停地检查这些变量的状态是否发生变化,一旦有变化,伺服电机就会按小步长进行线性移动。在每一步后,首先需要检查变量是否没有再次变化,并且还要测量霍尔传感器读取的值。如果霍尔传感器的值过高,意味着磁铁距离手指核心太近,此时也要停止伺服电机的运动。

最初,我们需要用于伺服驱动的库,并且还将包含用于I2C通信的Wire库:

  1. #include <Wire.h>
  2. #include <Adafruit_PWMServoDriver.h>
复制代码


之后,我们需要定义脉冲长度的值,这些值因伺服类型而异,所以强烈建议查找特定伺服的信息或者像这样测试[7]它们。

  1. // Operating Speed of my Servo (6V): 0.21 sec/60°
  2. #define SERVOMIN "Your value (mine was 70)" // This is the 'minimum' pulse length count (out of 4096)
  3. #define SERVOMAX "Your value (mine was 510)" // This is the 'maximum' pulse length count (out of 4096)
  4. #define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates
复制代码


现在我们必须定义其余要使用到的变量:

  1. // Initializing servo driver object
  2. Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
  3. // Index, Middle, Ring, Thumb, Pinky
  4. // "state0" is the state the hand on the webcam is in and "state"
  5. // is the stuff happening on the actual hand
  6. bool state0[6] = {false, false, false, false, false, false};
  7. bool state[6] = {false, false, false, false, false, false};
  8. // Variable which indicates if there has been any change made to the state
  9. bool change = false;
  10. // Variables needed for reading the data from Serial
  11. char sData;
  12. String state;
  13. // Variable for the hall sensor
  14. // Index, Middle, Ring, Thumb, Pinky
  15. // {pin, measured value, maximum value}
  16. // ALL OF THE MAX VALUES WERE MEASURED BY ME THUS THEY WILL MOST LIKELY NOT BE SAME FOR YOU
  17. int hall[5][3] = {{26, 0, 2200}, {27, 0, 2400}, {14, 0, 2300}, {25, 0, 2200}, {12, 0, 2300}};
  18. // Setting the index numbers of each motor
  19. int wrist = 0;
  20. int thumb = 4;
  21. int index = 1;
  22. int middle = 2;
  23. int ring = 3; // IMPORTANT this motor will rotate in the oposite direction
  24. int pinky = 5; // IMPORTANT this motor will rotate in the oposite direction
  25. // Function for calculating the PWM based on the degree you want
  26. int degToPwm(int degree) {
  27. return map(degree, 0, 320, SERVOMIN, SERVOMAX);
  28. }
  29. // Setting the degree thresholds used
  30. int deg = degToPwm(75);
  31. int deg1 = degToPwm(95);
  32. int deg2 = degToPwm(85);
  33. int startDeg = degToPwm(180);
复制代码


接下来,需要定义我们将要使用的函数:

  1. // Initialization of the task
  2. TaskHandle_t recieveData;
  3. // Function which reads the data from Serial
  4. void recieveDataCode(void * parameter) {
  5. for(;;) {
  6.   // Loop which runs when there is a message sent
  7.   while(Serial.available()) {
  8.    // Reading by each character
  9.    sData = Serial.read();
  10.    // If the character is the line ending symbol we know it is the end of the message
  11.    if(sData == '\n') {
  12.     // Loop for converting the string 0s and 1s to boolean
  13.     for(int i = 0; i < 6; i++) {
  14.      state0[i] = state.substring(i, i+1).toInt();
  15.     }
  16.     // Reseting the state temporary variable
  17.     state = "";
  18.     // Showing a change in state happened
  19.     change = true;
  20.     break;
  21.    } else { // If the character is not the line ending symbol we add it to the temporary state
  22.     state += sData;
  23.    }
  24.   }
  25.   delay(10);
  26. }
  27. }
  28. // Function for actually moving the servos
  29. void moveFinger(int fingerId, bool flex, int iteration) {
  30. // Because the ring and pinky motors move in opposite direction
  31. // we have to check which motors we are moving
  32. if(fingerId != ring && fingerId != pinky) {
  33.   // We also need to check if we want the finger to flex or straighten
  34.   if(flex) {
  35.    // Moreover the thumb moves a little less so we also check for that
  36.    if(fingerId == thumb) {
  37.     // Because we want to be able to control the movement throughout we have to
  38.     // divide it into smaller parts
  39.     float fPwm = SERVOMIN + (float(103)*float(iteration))/float(130);
  40.     // But we also have to make sure to convert back to int because float would
  41.     // not be accepted by pwm function
  42.     int iPwm = round(fPwm);
  43.     pwm.setPWM(fingerId, 0, iPwm);
  44.    } else { // If the finger is not the thumb we just move it
  45.     pwm.setPWM(fingerId, 0, SERVOMIN + iteration);
  46.    }
  47.   } else { // For the case that is retracting we have to just do the opposite
  48.    if(fingerId == thumb) {
  49.     float fPwm = deg - (float(103)*float(iteration))/float(130);
  50.     int iPwm = round(fPwm);
  51.     pwm.setPWM(fingerId, 0, iPwm);
  52.    } else {
  53.     pwm.setPWM(fingerId, 0, deg1 - iteration);
  54.    }
  55.   }
  56. } else if(fingerId == ring || fingerId == pinky) {
  57.   // In the case of the ring or pinky finger we do again the same
  58.   if(flex) {
  59.    pwm.setPWM(fingerId, 0, startDeg - iteration);
  60.   } else {
  61.    pwm.setPWM(fingerId, 0, deg2 + iteration);
  62.   }
  63. }
  64. }
复制代码

补上设置和循环功能:

  1. void setup() {
  2. // Starting Serial on the same frequency as on the PC
  3. Serial.begin(9600);
  4.    
  5. // Assigning the pinMode to all pins connected to hall sensor
  6. for(int i = 0; i < 5; i++) {
  7.   pinMode(hall[i][0], INPUT);
  8. }
  9.   
  10. // Setup and starting the servo driver
  11. pwm.begin();
  12. pwm.setOscillatorFrequency(27000000);
  13. pwm.setPWMFreq(SERVO_FREQ);
  14. delay(10);
  15.   
  16. // Pinning the created task to core 0
  17. xTaskCreatePinnedToCore(
  18.   recieveDataCode,
  19.   "recieveData",
  20.   10000,
  21.   NULL,
  22.   0,
  23.   &recieveData,
  24.   0);
  25. delay(500);
  26. }
  27. void loop() {
  28. // Once there has been a change in the state this code block will run
  29. if(change) {
  30.   // Looping firstly through the total steps of the servos
  31.   for(int i = 5; i < 135; i += 5) {
  32.    // Secondly through all of the hall sensors and reading the values
  33.    for(int k = 0; k < 5; k++) {
  34.     hall[k][1] = analogRead(hall[k][0]);
  35.     // If the measured value is greater than maximum value we stop the movement
  36.     if(hall[k][1] > hall[k][2]) {
  37.      state1[k+1] = state0[k+1];
  38.     }
  39.    }
  40.    // Thirdly through all the servo motors
  41.    for(int j = 0; j < 6; j++) {
  42.     if(state0[j] != state1[j]) {
  43.      // If the state on the PC does not match the one on the esp32 we
  44.      // call the function for moving the respective finger
  45.      moveFinger(j, state0[j], i);
  46.     }
  47.    }
  48.    // This delay is very important as it sets the speed of the movements
  49.    delay(17);
  50.   }
  51.   // At the and we make the state variables equal again
  52.   for(int i = 0; i < 6; i++) {
  53.    state1[i] = state0[i];
  54.   }
  55. }
  56.   
  57. delay(100);
  58. }
复制代码



ESP32的完整代码:


  1. #include <Wire.h>
  2. #include <Adafruit_PWMServoDriver.h>
  3. #define SERVOMIN "Your value"
  4. #define SERVOMAX "Your value"
  5. #define SERVO_FREQ 50
  6. Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
  7. bool state0[6] = {false, false, false, false, false, false};
  8. bool state1[6] = {false, false, false, false, false, false};
  9. bool change = false;
  10. char sData;
  11. String state;
  12. int hall[5][3] = {{26, 0, 2200}, {27, 0, 2400}, {14, 0, 2300}, {25, 0, 2200}, {12, 0, 2300}};
  13. int wrist = 0;
  14. int thumb = 4;
  15. int index = 1;
  16. int middle = 2;
  17. int ring = 3;
  18. int pinky = 5;
  19. int degToPwm(int degree) {
  20. return map(degree, 0, 320, SERVOMIN, SERVOMAX);
  21. }
  22. int deg = degToPwm(75);
  23. int deg1 = degToPwm(95);
  24. int deg2 = degToPwm(85);
  25. int startDeg = degToPwm(180);
  26. TaskHandle_t recieveData;
  27. void recieveDataCode(void * parameter) {
  28. for(;;) {
  29.   while(Serial.available()) {
  30.    sData = Serial.read();
  31.    if(sData == '\n') {
  32.     for(int i = 0; i < 6; i++) {
  33.      state0[i] = state.substring(i, i+1).toInt();
  34.     }
  35.     state = "";
  36.     change = true;
  37.     break;
  38.    } else {
  39.     state += sData;
  40.    }
  41.   }
  42.   delay(10);
  43. }
  44. }
  45. void moveFinger(int fingerId, bool flex, int iteration) {
  46. if(fingerId != ring && fingerId != pinky) {
  47.   if(flex) {
  48.    if(fingerId == thumb) {
  49.     float fPwm = SERVOMIN + (float(103)*float(iteration))/float(130);
  50.     int iPwm = round(fPwm);
  51.     pwm.setPWM(fingerId, 0, iPwm);
  52.    } else {
  53.     pwm.setPWM(fingerId, 0, SERVOMIN + iteration);
  54.    }
  55.   } else {
  56.    if(fingerId == thumb) {
  57.     float fPwm = deg - (float(103)*float(iteration))/float(130);
  58.     int iPwm = round(fPwm);
  59.     pwm.setPWM(fingerId, 0, iPwm);
  60.    } else {
  61.     pwm.setPWM(fingerId, 0, deg1 - iteration);
  62.    }
  63.   }
  64. } else /*if(fingerId == ring || fingerId == pinky)*/ {
  65.   if(flex) {
  66.    pwm.setPWM(fingerId, 0, startDeg - iteration);
  67.   } else {
  68.    pwm.setPWM(fingerId, 0, deg2 + iteration);
  69.   }
  70. }
  71. }
  72. void setup() {
  73. Serial.begin(9600);
  74.    
  75. for(int i = 0; i < 5; i++) {
  76.   pinMode(hall[i][0], INPUT);
  77. }
  78.   
  79. pwm.begin();
  80. pwm.setOscillatorFrequency(27000000);
  81. pwm.setPWMFreq(SERVO_FREQ);
  82. delay(10);
  83.   
  84. xTaskCreatePinnedToCore(
  85.   recieveDataCode,
  86.   "recieveData",
  87.   10000,
  88.   NULL,
  89.   0,
  90.   &recieveData,
  91.   0);
  92. delay(500);
  93. }
  94. void loop() {
  95. if(change) {
  96.   for(int i = 5; i < 135; i += 5) {
  97.    for(int k = 0; k < 5; k++) {
  98.     hall[k][1] = analogRead(hall[k][0]);
  99.     if(hall[k][1] > hall[k][2]) {
  100.      state1[k+1] = state0[k+1];
  101.     }
  102.    }
  103.    for(int j = 0; j < 6; j++) {
  104.     if(state0[j] != state1[j]) {
  105.      moveFinger(j, state0[j], i);
  106.     }
  107.    }
  108.    delay(17);
  109.   }
  110.   for(int i = 0; i < 6; i++) {
  111.    state1[i] = state0[i];
  112.   }
  113. }
  114.   
  115. delay(100);}
复制代码



参考资料

[1]InMoov项目: https://inmoov.fr/?doing_wp_cron ... 7699527740478515625
[2]inmoov STL零件库: https://inmoov.fr/inmoov-stl-parts-viewer/
[3]i2手: https://inmoov.fr/hand-i2/
[4]前臂: https://inmoov.fr/hand-and-forarm/
[5]视频: https://youtu.be/EqVhpilm3sw?si=hGODLQO_kjNtr1q0
[6]作者版本的GitHub链接: https://github.com/Bloudakm/Blou ... hand_Gerber_PCB.zip
[7]测试: https://www.youtube.com/watch?v=y8X9X10Tn1k&t=941s

原文地址:https://www.instructables.com/Bionic-Hand-Controlled-by-OpenCV/

项目作者:bloudakm

译文首发于:DF创客社区

转载请注明来源信息






Bionic Hand.zip

9.55 MB, 下载次数: 17

罗罗罗  中级技师

发表于 2024-10-12 21:54:25

太牛了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail