树莓派,mediapipe,Picamera2利用舵机云台追踪人手(PID控制)

news2025/2/7 4:50:44

一、项目目标

追踪人手大拇指指尖:
当人手移动时,摄像头通过控制两个伺服电机(分别是偏航和俯仰)把大拇指指尖放到视界的中心位置,本文采用了PID控制伺服电机

  • Mediapipe Hand简介

MediaPipe 手部标志任务可检测图像中手部的标志。 您可以使用此任务来定位手的关键点并在其上渲染视觉效果。 该任务使用机器学习(ML)模型作为静态数据或连续流对图像数据进行操作,并输出图像坐标中的手部标志、世界坐标中的手部标志以及多个检测到的手的惯用手(左/右手)。
在这里插入图片描述

二、 需要准备的软、硬件

  1. Raspiberry Pi 4b
  2. 两个SG90 180度舵机(注意舵机的角度,最好是180度且带限位的,切勿选360度舵机)
  3. 二自由度舵机云台(如下图)
  4. Raspiberry CSI 摄像头
  5. mediapipe库, 安装方法可以参照此链接
    组装后的效果:
    组装后的效果

三、具体步骤

  1. 创建“hand_tracking_PID.py”文件,代码如下,我在本文中追踪的是大拇指指尖,如果你想追踪其它部位,只须将fingerID参数设置成你想追踪的数字即可。具体数字分布如下图。

hand landmark模型

#-*- coding: UTF-8 -*-	
# 调用必需库
#hand_tracking_PID.py
from multiprocessing import Manager
from multiprocessing import Process
from handobj import HandObj
from pid import PID
from servo import Servo
import signal
import time
import sys
import cv2
import mediapipe as mp
from picamera2 import Picamera2


# 定义舵机
pan=Servo(pin=19)
tilt=Servo(pin=16)

#定义图像尺寸
dispW=1280
dispH=720

# 定义手指ID
fingerID=4

# 键盘终止函数
def signal_handler(sig, frame):
    # 输出状态信息
    print("[INFO] You pressed `ctrl + c`! Exiting...")

    # 关闭舵机
    pan.stop()
    tilt.stop()

    # 退出
    sys.exit()

def hand_obj(objX,objY,centerX,centerY):
    # ctrl+c退出进程
    signal.signal(signal.SIGINT, signal_handler)

    # 启动视频流并缓冲
    print("[INFO] waiting for camera to warm up...")
    cv2.startWindowThread()
    picam2 = Picamera2()
    preview_config = picam2.create_preview_configuration(main={"size": (dispW, dispH),"format":"RGB888"})
    picam2.configure(preview_config)
    picam2.start()
    time.sleep(2.0)

    #初始化手掌对象探测器
    hand=HandObj(fingerID)

    #进入循环
    while True:
        # 从视频流抓取图像并旋转
        frame = picam2.capture_array()
        frame = cv2.flip(frame, 1)

        # 找到图像中心
        (H, W) = frame.shape[:2]
        centerX.value = W // 2
        centerY.value = H // 2

        # 画出图像中心点
        cv2.circle(frame, (centerX.value, centerY.value), 5, (0, 0, 255), -1)

        # 找到手指对象点
        objectLoc = hand.update(frame, (centerX.value, centerY.value))
        ((objX.value, objY.value), handlms) = objectLoc
        # 画出手指关注的对象点,这是里前面定义的ID:4,即大拇指指尖
        if handlms is not None:      
            cv2.circle(frame, (objX.value, objY.value), 15, (255, 0, 255), cv2.FILLED)
        cv2.imshow('Hand', frame)
        cv2.waitKey(1)

def pid_process(output, p, i, d, objCoord, centerCoord):
    # ctrl+c退出进程
    signal.signal(signal.SIGINT, signal_handler)
    
     # 创建一个PID类的对象并初始化
    p = PID(p.value, i.value, d.value)
    p.initialize()

    # 进入循环
    while True:
        # 计算误差
        error = centerCoord.value - objCoord.value
        # 更新输出值,当error小于50时,误差设为0,以避免云台不停运行。
        if abs(error) < 50:
            error = 0
        output.value = p.update(error)

def set_servos(panAngle, tiltAngle):
    # ctrl+c退出进程
    signal.signal(signal.SIGINT, signal_handler)

    #进入循环
    while True:
        # 偏角变号
        yaw = -1 * panAngle.value
        pitch = -1 * tiltAngle.value

        # 设置舵机角度。
        pan.set_angle(yaw)
        tilt.set_angle(pitch)

# 启动主程序
if __name__ == "__main__":

    # 启动多进程变量管理
    with Manager() as manager:  # 相当于manager=Manager(),with as 语句操作上下文管理器(context manager),它能够帮助我们自动分配并且释放资源。
        # 舵机角度置零
        pan.set_angle(0)
        tilt.set_angle(0)

        # 为图像中心坐标赋初值
        centerX = manager.Value("i", 0)  # "i"即为整型integer
        centerY = manager.Value("i", 0)

        # 为人脸中心坐标赋初值
        objX = manager.Value("i", 0)
        objY = manager.Value("i", 0)

        # panAngle和tiltAngle分别是两个舵机的PID控制输出量
        panAngle = manager.Value("i", 0)
        tiltAngle = manager.Value("i", 0)

       # 设置一级舵机的PID参数
        panP = manager.Value("f", 0.015)  # "f"即为浮点型float
        panI = manager.Value("f", 0.01)
        panD = manager.Value("f", 0.0008)

        # 设置二级舵机的PID参数
        tiltP = manager.Value("f", 0.025)
        tiltI = manager.Value("f", 0.01)
        tiltD = manager.Value("f", 0.008)

        # 创建4个独立进程
        # 1. objectCenter  - 探测人脸
        # 2. panning       - 对一级舵机进行PID控制,控制偏航角
        # 3. tilting       - 对二级舵机进行PID控制,控制俯仰角
        # 4. setServos     - 根据PID控制的输出驱动舵机

        processObjectCenter = Process(
            target=hand_obj, args=(objX, objY, centerX, centerY))
        processPanning = Process(target=pid_process, args=(
            panAngle, panP, panI, panD, objX, centerX))
        processTilting = Process(target=pid_process, args=(
            tiltAngle, tiltP, tiltI, tiltD, objY, centerY))
        processSetServos = Process(
            target=set_servos, args=(panAngle, tiltAngle))

        # 开启4个进程
        processObjectCenter.start()
        processPanning.start()
        processTilting.start()
        processSetServos.start()

        # 添加4个进程
        processObjectCenter.join()
        processPanning.join()
        processTilting.join()
        processSetServos.join()

  1. 创建“handobj.py”,代码如下:
#handobj.py
#-*- coding: UTF-8 -*-
# 调用必需库
import mediapipe as mp

class HandObj:
	def __init__(self,fingerID):
		# 初始化手掌关键点坐标
		self.myHands=mp.solutions.hands
		# 初始化手掌关键点坐标和手掌关键点连接情况
		self.hands=self.myHands.Hands()
		# 初始化手掌关键点绘制库
		self.mpDraw=mp.solutions.drawing_utils
		# 初始化手掌关键点ID
		self.fingerID=fingerID


	def update(self, frame, frameCenter):
		# 处理视频流
		results = self.hands.process(frame)
		if results.multi_hand_landmarks:
			for handLms in results.multi_hand_landmarks:
				# 绘制手掌关键点
				self.mpDraw.draw_landmarks(frame, handLms, self.myHands.HAND_CONNECTIONS)
				for id, lm in enumerate(handLms.landmark):
					h, w, c = frame.shape
					cx, cy = int(lm.x * w), int(lm.y * h)
					if id == self.fingerID:
						#绘制手掌关键点并返回手掌关键点坐标
						return ((cx, cy), handLms)
		return(frameCenter,None)
					

  1. 创建“pid.py”,代码如下:
#-*- coding: UTF-8 -*-
# 调用必需库
import time

class PID:
	def __init__(self, kP=1, kI=0, kD=0):
		# 初始化参数
		self.kP = kP
		self.kI = kI
		self.kD = kD

	def initialize(self):
		# 初始化当前时间和上一次计算的时间
		self.currTime = time.time()
		self.prevTime = self.currTime

		# 初始化上一次计算的误差
		self.prevError = 0

		# 初始化误差的比例值,积分值和微分值
		self.cP = 0
		self.cI = 0
		self.cD = 0

	def update(self, error, sleep=0.5):
		# 暂停
		time.sleep(sleep)

		# 获取当前时间并计算时间差
		self.currTime = time.time()
		deltaTime = self.currTime - self.prevTime

		# 计算误差的微分
		deltaError = error - self.prevError

		# 比例项
		self.cP = error

		# 积分项
		self.cI += error * deltaTime

		# 微分项
		self.cD = (deltaError / deltaTime) if deltaTime > 0 else 0

		# 保存时间和误差为下次更新做准备
		self.prevTime = self.currTime
		self.prevError = error

		# 返回输出值
		return sum([
			self.kP * self.cP,
			self.kI * self.cI,
			self.kD * self.cD])
  1. 上述代码中的from servo import Servo导入servo,这个库是没有的,我们要手动创建这个库,在object_tracking.py所在的目录下新建servo.py文件,复制下面的代码到文件中
#!/usr/bin/env python3
import pigpio
from time import sleep
# Start the pigpiod daemon
import subprocess
result = None
status = 1
for x in range(3):
    p = subprocess.Popen('sudo pigpiod', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    result = p.stdout.read().decode('utf-8')
    status = p.poll()
    if status == 0:
        break
    sleep(0.2)
if status != 0:
    print(status, result)
'''
> Use the DMA PWM of the pigpio library to drive the servo
> Map the servo angle (0 ~ 180 degree) to (-90 ~ 90 degree)

'''

class Servo():
    MAX_PW = 1250  # 0.5/20*100
    MIN_PW = 250 # 2.5/20*100
    _freq = 50 # 50 Hz, 20ms
 
    def __init__(self, pin, min_angle=-90, max_angle=90):

        self.pi = pigpio.pi()
        self.pin = pin 
        self.pi.set_PWM_frequency(self.pin, self._freq)
        self.pi.set_PWM_range(self.pin, 10000)      
        self.angle = 0
        self.max_angle = max_angle
        self.min_angle = min_angle
        self.pi.set_PWM_dutycycle(self.pin, 0)

    def set_angle(self, angle):
        if angle > self.max_angle:
            angle = self.max_angle
        elif angle < self.min_angle:
            angle = self.min_angle
        self.angle = angle
        duty = self.map(angle, -90, 90, 250, 1250)
        self.pi.set_PWM_dutycycle(self.pin, duty)


    def get_angle(self):
        return self.angle
    
    def stop(self):
        self.pi.set_PWM_dutycycle(self.pin, 0)
        self.pi.stop()

    # will be called automatically when the object is deleted
    # def __del__(self):
    #     pass

    def map(self, x, in_min, in_max, out_min, out_max):
        return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min


if __name__ =='__main__':
    from vilib import Vilib
    # Vilib.camera_start(vflip=True,hflip=True) 
    # Vilib.display(local=True,web=True)

    pan = Servo(pin=13, max_angle=90, min_angle=-90)
    tilt = Servo(pin=12, max_angle=30, min_angle=-90)
    panAngle = 0
    tiltAngle = 0
    pan.set_angle(panAngle)
    tilt.set_angle(tiltAngle)
    sleep(1)

    while True:
        for angle in range(0, 90, 1):
            pan.set_angle(angle)
            tilt.set_angle(angle)
            sleep(.01)
        sleep(.5)
        for angle in range(90, -90, -1):
            pan.set_angle(angle)
            tilt.set_angle(angle)
            sleep(.01)
        sleep(.5)
        for angle in range(-90, 0, 1):
            pan.set_angle(angle)
            tilt.set_angle(angle)
            sleep(.01)
        sleep(.5)

  1. 运行效果如下图,如果不想在运行过程中显示网格与关注的手指节点,可以把相应的代码注释掉

在这里插入图片描述

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

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

相关文章

状态管理概述

ArkTS UI的状态管理到这里就叙述完了&#xff0c;现在做一个概述&#xff0c;也可以认为是一个总结。 在声明式UI编程框架中&#xff0c;UI是程序状态的运行结果&#xff0c;用户构建了一个UI模型&#xff0c;其中应用的运行时的状态是参数。当参数改变时&#xff0c;UI作为返回…

向华为学习:IPD运作-PDP产品开发流程-开发阶段的关键活动

前面几天&#xff0c;华研荟为您分享了IPD体系中产品开发流程&#xff08;严格来说是PDP流程&#xff0c;也是狭义的IPD流程&#xff09;前两个阶段&#xff1a;概念阶段和计划阶段的主要内容和关键活动。 今天我们继续来介绍PDP流程的第三个阶段&#xff1a;开发阶段的主要内容…

鸿蒙原生应用/元服务开发-Stage模型能力接口(十)上

ohos.app.form.FormExtensionAbility (FormExtensionAbility) FormExtensionAbility为卡片扩展模块&#xff0c;提供卡片创建、销毁、刷新等生命周期回调。 本模块首批接口从API version 9开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。本模块接…

ArkUI动画概述

目录 1、按照页面分类 2、按照功能分类 3、显示动画 4、属性动画 动画的原理是在一个时间段内&#xff0c;多次改变UI外观&#xff0c;由于人眼会产生视觉暂留&#xff0c;所以最终看到的就是一个“连续”的动画。UI的一次改变称为一个动画帧&#xff0c;对应一次屏幕刷新&a…

C#实现串口通讯

1、官网下载Launch Virtual Serial Port Driver Virtual Serial Port Driver - create and emulate virtual COM port&#xff0c;开个虚拟串口&#xff1a; Pair模式&#xff08;一对&#xff0c;成双成对的意思&#xff0c;就是COM1向COM2传或者COM2向COM1,好比两台机器的CO…

Unity | 渡鸦避难所-4 | 镜头跟随角色移动

1 Cinemachine 简介 在第一人称视角的游戏中&#xff0c;摄像机需要时刻跟随角色移动。除了手动计算摄像机的位置、旋转外&#xff0c;也可以使用 Unity 提供的 Cinemachine 插件来轻松实现摄像机的控制 Cinemachine 是一套用于操作 Unity 相机的模块&#xff0c;解决了跟踪目…

【AI】阿里云免费GPU服务资源领取方法

首先&#xff0c;直接点击链接&#xff1a;阿里云免费试用 也可以复制链接到浏览器进行跳转&#xff1a;https://free.aliyun.com?userCodernbj0c1o 页面如下所示&#xff1a;这里的免费试用期限是3个月&#xff0c;给的资源点够我们试用V100 16G显存服务器300个小时&#xff…

【Java】智慧工地云平台管理系统源码

智慧工地平台-系统架构 •统一数据标准、规范数据接口 •决策支持&#xff1a;数据挖掘、全文搜索引擎、OLAP分析、统计报表 •智慧工地平台&#xff1a;项目人员管理、视频监控管理、安全隐患管理、现场物料管理、危大工程监测、绿色文明施工。 •物联网采集&#xff1a;人脸识…

Linux入门——环境 基本指令 基本工具 权限的初步认识

目录 1.Linux的历史 2.利用云服务器设置Linux 3.使用Xshell连接阿里云服务器 4.Linux常见的指令 5.什么是操作系统&#xff1f; 6.命令解释器 7. linux的权限的初步认识 8.相关习题的练习 1.Linux的历史 1991 年 10 月 5 日&#xff0c;赫尔辛基大学的一名研究生 Linus B…

解决企业TB或者PB级大文件传输速度和安全问题

随着企业数据不断增加&#xff0c;TB或PB级大文件的传输成为企业信息共享和数据备份的重要手段之一。然而&#xff0c;这些大文件的传输速度和安全问题成为制约企业发展的瓶颈&#xff0c;也是企业需要解决的重要问题。本文将探讨如何解决这些问题&#xff0c;并从以下几个方面…

基于Java SSM框架实现水果销售网站系统项目【项目源码+论文说明】

基于java的SSM框架实现水果销售网站系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&a…

Maven仓库依赖导入‘.lastUpadate‘问题解决

1. 依赖导入不进去先检查 当你开一个新的项目导入maven仓库的时候&#xff0c;发现本地有这个依赖&#xff0c;但是pom.xml文件里的依赖老是爆红&#xff0c;然后无论怎么样去reload->clean->install还是不行&#xff0c; 这时可以先去检查下maven的setting.xml文件和m…

CentOS8+宝塔面板+cpolar内网穿透搭建可公网访问的Typecho个人站点

文章目录 前言1. 安装环境2. 下载Typecho3. 创建站点4. 访问Typecho5. 安装cpolar6. 远程访问Typecho7. 固定远程访问地址8. 配置typecho 前言 Typecho是由type和echo两个词合成的&#xff0c;来自于开发团队的头脑风暴。Typecho基于PHP5开发&#xff0c;支持多种数据库&#…

SpringBoot整合JWT+Spring Security+Redis实现登录拦截(一)

一、JWT简介 JWT 全称 JSON Web Token&#xff0c;JWT 主要用于用户登录鉴权&#xff0c;当用户登录之后&#xff0c;返回给前端一个Token&#xff0c;之后用户利用Token进行信息交互。 除了JWT认证之外&#xff0c;比较传统的还有Session认证&#xff0c;如何选择可以查看之前…

2024年【道路运输企业安全生产管理人员】考试题及道路运输企业安全生产管理人员报名考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业安全生产管理人员考试题考前必练&#xff01;安全生产模拟考试一点通每个月更新道路运输企业安全生产管理人员报名考试题目及答案&#xff01;多做几遍&#xff0c;其实通过道路运输企业安全生产管理人员…

web功能实例 - Canvas裁剪工具

嗯,手撸官方文档2天&#xff0c;发现没啥用&#xff0c;尤其是动画,那种计算出来的&#xff0c;根本想不到。因此学着学了抱着要做个东西的想法,去网上找相关案例,最终做出了这个裁剪工具。 PS :先说一下思路: 核心实现有3个canvas图层, 其中一个负责图片的预览。另外2个叠加到…

【深度学习】使用ffmpg及gstreamer进行视频拉流及编解码(一):ffmpg

目录 为什么要进行视频编解码网络带宽常见的视频编码格式视频分辨率及其占用的经验带宽千兆网口及百兆网口 硬件编解码和软件编解码的区别拉流工具简介安装ffmpg库安装必要的依赖库安装ffmpg库 代码 为什么要进行视频编解码 视频流需要编解码的主要原因是视频文件的数据量很大…

【深度学习】DataComp论文,数据集介绍,大数据模型的数据集介绍

参考&#xff1a; https://laion.ai/blog/datacomp/ 论文&#xff1a;https://arxiv.org/abs/2304.14108 文章目录 论文报告的一些内容datacomp-1B 数据质量比lainon2B要好不同规模数据有多少数据数据处理数据来源 论文报告的一些内容 摘要 多模态数据集是近期如CLIP、Stable …

python 安装django 构建django项目

背景 项目需要&#xff0c;构建一个可视化平台&#xff0c;在参与技术调研后决定选用django作为主要技术栈。 内容 通过Python安装django&#xff0c;我这里的pycharm和Python版本有点低&#xff0c;所有没有通过pycharm页面入口进行创建django项目。 pip install django 安装…

MYSQL一一函数一一流程函数

咱今天讲的是MySQL函数中的流程函数&#xff0c;会有3小题和一个综合案例帮助大家理解 流程函数是很常用的一类函数&#xff0c;可以在SQL语句中实现条件筛选&#xff0c;从而提高语句的效率 小题&#xff1a; ①if语句&#xff1a; select if(flash,ok,error); //如果…