树莓派,opencv,Picamera2利用舵机云台追踪特定颜色对象(PID控制)

一、需要准备的硬件

  1. Raspiberry 4b
  2. 两个SG90 180度舵机(注意舵机的角度,最好是180度且带限位的,切勿选360度舵机)
  3. 二自由度舵机云台(如下图)
  4. Raspiberry CSI 摄像头
    组装后的效果:
    组装后的效果

二、项目目标

追踪特定颜色的物体:
当物体移动时,摄像头通过控制两个伺服电机(分别是偏航和俯仰)把该物体放到视界的中心位置,我在这里追踪的是一支黄色的铅笔。

三、具体步骤

3.1 获得被追踪对象的颜色参数

  1. 提前准备一张图片(如下图),可以直接用树莓派的CSI摄像头拍摄并保存,具体方法可以在我之前的文章里找到

示例图片
2. 利用下面的代码并通过调整滑块(Trackbar)获得红色铅笔的HSV颜色参数,为接下来的颜色追踪做准备

import cv2
import json
path='crop_img.jpg'
cv2.namedWindow("TrackBar")def nothing(x):pass
#创建滑块控件
cv2.createTrackbar("Hue Min","TrackBar",0,179,nothing)
cv2.createTrackbar("Hue Max","TrackBar",179,179,nothing)
cv2.createTrackbar("Sat Min","TrackBar",0,255,nothing)
cv2.createTrackbar("Sat Max","TrackBar",255,255,nothing)
cv2.createTrackbar("Val Min","TrackBar",0,255,nothing)
cv2.createTrackbar("Val Max","TrackBar",255,255,nothing)while True:#读取目标图片image=cv2.imread(path)image=cv2.resize(image,(640,480))imgHSV=cv2.cvtColor(image,cv2.COLOR_BGR2HSV)hueLow=cv2.getTrackbarPos("Hue Min","TrackBar")hueHigh=cv2.getTrackbarPos("Hue Max","TrackBar")satLow=cv2.getTrackbarPos("Sat Min","TrackBar")satHigh=cv2.getTrackbarPos("Sat Max","TrackBar")valLow=cv2.getTrackbarPos("Val Min","TrackBar")valHigh=cv2.getTrackbarPos("Val Max","TrackBar")print(hueLow,hueHigh,satLow,satHigh,valLow,valHigh)#创建掩膜mask=cv2.inRange(imgHSV,(hueLow,satLow,valLow),(hueHigh,satHigh,valHigh))image=cv2.bitwise_and(image,image,mask=mask)#显示图像cv2.imshow('Origial',image)data={"hueLow":hueLow,"hueHigh":hueHigh,"satLow":satLow,"satHigh":satHigh,"valLow":valLow,"valHigh":valHigh,}mask_json=json.dumps(data)#按q键保存并退出if cv2.waitKey(1)==ord('q'):#将设置的参数保存到mask.json文件中with open('mask.json','w') as f:f.write(mask_json)break
cv2.destroyAllWindows() 
  1. 运行color_detection.py,并调整滑块(TrackBar)如下图,当然你的被追踪物体的颜色不同,参数也必然不同。
    在这里插入图片描述

  2. 这时你会发现,红色铅笔被显示出来,其它部分被掩膜遮挡,当你在frame窗口按下"q"键后,会自动生成mask.json文件保存相应参数设置
    被掩膜遮挡后的图片

3.2 目标追踪代码

  1. 新建color_tracking_pid.py文件,一级(pan)舵机的信号脚接在GPIO的19脚,二级(tilt)舵机的信号脚接在GPIO的16脚,在运行时可以通过调整main函数里的PID参数,代码如下:
# -*- coding: UTF-8 -*-
# 调用必需库
# color_tracking_pid.py
from multiprocessing import Manager, Process
from pid import PID
from colorcenter import Colorcenter
from servo import Servo
import time
import signal
import sys
import cv2
from picamera2 import Picamera2
import json# 定义舵机
pan = Servo(pin=19)
tilt = Servo(pin=16)# 定义图像尺寸
dispW = 1280
dispH = 720
# 读取掩模配置文件
with open('mask.json') as f:mask = json.load(f)def nothing(x):pass# 键盘终止函数def signal_handler(sig, frame):# 输出状态信息print("[INFO] You pressed `ctrl + c`! Exiting...")# 关闭舵机pan.stop()tilt.stop()# 退出sys.exit()def color_center(objX, objY, centerX, centerY):# ctrl+c退出进程signal.signal(signal.SIGINT, signal_handler)# 启动视频流并缓冲print("[INFO] waiting for camera to warm up...")cv2.startWindowThread()picam2 = Picamera2()picam2.preview_configuration.main.size = (dispW, dispH)picam2.preview_configuration.main.format = "RGB888"picam2.preview_configuration.controls.FrameRate = 10picam2.preview_configuration.align()picam2.configure("preview")picam2.start()fps = 0time.sleep(2.0)# 初始化色块探测器obj = Colorcenter(mask['hueLow'], mask['satLow'], mask['valLow'],mask['hueHigh'], mask['satHigh'], mask['valHigh'])# 进入循环while True:tStart = time.time()# 从视频流抓取图像并旋转frame = picam2.capture_array()frame = cv2.flip(frame, 1)# #在图像上显示帧率fps = 0cv2.putText(frame, "FPS: {:.2f}".format(fps), (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 225), 3)# 找到图像中心(H, W) = frame.shape[:2]centerX.value = W // 2centerY.value = H // 2# 画出图像中心点cv2.circle(frame, (centerX.value, centerY.value), 5, (0, 0, 255), -1)# 找到色块objectLoc = obj.update(frame, (centerX.value, centerY.value))((objX.value, objY.value), rect) = objectLoc# 绘制色块外界矩形if rect is not None:(x, y, w, h) = rectcv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 3)fX = int(x + (w / 2.0))fY = int(y + (h / 2.0))cv2.circle(frame, (fX, fY), 5, (0, 0, 255), -1)cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 3)# 在色块中心和视窗中心画的条连线cv2.line(frame, (centerX.value, centerY.value),(fX, fY), (0, 255, 0), 2)# 显示图像tEnd = time.time()loopTime = tEnd-tStartfps = .9*fps + .1*(1/loopTime)cv2.imshow("Pan-Tilt Face Tracking", 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 = 0output.value = p.update(error)def set_servos(panAngle, tiltAngle):# ctrl+c退出进程signal.signal(signal.SIGINT, signal_handler)# 进入循环while True:# 偏角变号yaw = -1 * panAngle.valuepitch = -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"即为整型integercenterY = 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"即为浮点型floatpanI = 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=color_center, 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. 上述代码中的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:breaksleep(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*100MIN_PW = 250 # 2.5/20*100_freq = 50 # 50 Hz, 20msdef __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 = 0self.max_angle = max_angleself.min_angle = min_angleself.pi.set_PWM_dutycycle(self.pin, 0)def set_angle(self, angle):if angle > self.max_angle:angle = self.max_angleelif angle < self.min_angle:angle = self.min_angleself.angle = angleduty = self.map(angle, -90, 90, 250, 1250)self.pi.set_PWM_dutycycle(self.pin, duty)def get_angle(self):return self.angledef 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):#     passdef map(self, x, in_min, in_max, out_min, out_max):return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_minif __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 = 0tiltAngle = 0pan.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. 运行color_tracking_pid.py,移动黄色铅笔,摄像头就会自动追踪该对象

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

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

相关文章

Python 中如何编写类型提示

哈喽大家好&#xff0c;我是咸鱼 我们知道 Python 是一门具有动态特性的语言&#xff0c;在编写 Python 代码的时候不需要显式地指定变量的类型 这样做虽然方便&#xff0c;但是降低了代码的可阅读性&#xff0c;在后期 review 代码的时候容易对变量的类型产生混淆&#xff0…

解决文件导出过大->java压缩zip文件--封装工具类

阿丹&#xff1a; 在业务逻辑中的数据存在一部分业务场景&#xff0c;在导出文件或者视频的时候需要将文件暂存在服务器上再上传到oss对象存储或者fastdfs中让用户来下载使用。但是出现的问题就是如果目标文件过大&#xff0c;文件的上传云端和下载本地就会时间拉长&#xff0c…

使用python读取EXCEL放假日历并制作订阅文件

前言 不想升级IOS&#xff0c;苦于找不到新的日历订阅url&#xff0c;小菜鸡百度来百度去发现ics这东西可以自己做一个&#xff0c;惊喜于看到了这篇文章--使用python获取日历信息并制作订阅文件_https: //github.com/lk-itween/calendar-CSDN博客 感谢作者大大。就想自己写一…

滑动窗口双指针

力扣 209 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 类似窗口滑动 j代表的是窗口的结束位置 i表示开始位置 在while循环中是寻找最…

服务器数据恢复-昆腾存储StorNext文件系统下raid5数据恢复案例

服务器数据恢复环境&#xff1a; 昆腾某型号存储&#xff0c;StorNext文件存储系统。 共有9个分别配置了24块磁盘的磁盘柜&#xff0c;其中8个磁盘柜存放普通数据&#xff0c;1个磁盘柜存放元数据。 存放元数据的磁盘柜中的24块磁盘组建了8组RAID1阵列和1组4盘RAID10阵列&#…

NCV8460ADR2G在汽车和工业应用中高压侧驱动如何破?

NCV8460ADR2G是一款完全保护的高压侧驱动器&#xff0c;可用于开关各种负载&#xff0c;如灯泡、电磁阀和其他致动器。该器件可以通过有源电流限制和高温关断针对过载情况进行内部保护。 诊断状态输出引脚提供了高温以及开关状态开路负载情况的数字故障指示。 特性&#xff1a;…

Nginx的location路径与proxy_pass匹配规则

location路径与proxy_pass匹配规则 路径替换总结当访问地址是 http://127.0.0.1/api/user/list注意:location后斜杆与proxy_pass后斜杆"/"问题&#xff0c;最好要么两者都加斜杆&#xff0c;要么都不加 路径替换 配置proxy_pass时&#xff0c;可以实现URL路径的部分…

22 Vue3中使用v-for遍历对象

概述 使用v-for遍历对象在真实的开发中比较少见&#xff0c;了解即可。 对象我更喜欢统一称之为字典&#xff0c;假如你哪天发现我在某个前端的教程中把对象叫做字典&#xff0c;请你知道这两个是同一个玩意儿。 所谓字典&#xff0c;就是一种key-value类型的结构的统称。 …

队列(C语言版)

一.队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有 先进先出 FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为 队尾 出队列&#xff1a;进行删除操作的一端称为…

网络安全之Linux环境配置及Linux基础知识讲解<三>

目录 一.下载安装Vmware二.下载安装Kali三.Linux目录结构四.Linux文件属性五.文件目录管理六.vim编辑器 一.下载安装Vmware Vmware官网&#xff1a;https://www.vmware.com 二.下载安装Kali Kali包含数百种工具&#xff0c;可用于各种信息安全任务&#xff0c;例如渗透测试、…

vue中判断应该import 哪个js或css

import Vue from vue import App from ./App.vueif (window.myFlag true) {import(./jsFile1.js).then((module) > {// 执行导入的jsFile1.js模块的代码module.default.init()}) } else {import(./jsFile2.js).then((module) > {// 执行导入的jsFile2.js模块的代码modul…

vue导出element表格,xlsx和xlsx-style生成xlsx文件并修改样式

1.下载依赖 npm install xlsx --save npm install file-saver --save npm install xlsx-style --save2.先修改xlsx-style的源码&#xff0c;一旦引入xlsx-style则会报错 xlsx-style使用中常见问题及解决办法&#xff1a; xlsx-style使用中常见问题及解决办法-CSDN博客 在\n…

SpringBoot 多环境开发配置文件

在开发过程中&#xff0c;往往开发环境和生产环境需要不同的配置。为了兼容两种运行环境&#xff0c;提高开发效率&#xff0c;可以使用多环境开发配置文件。 配置文件结构大概是这样&#xff1a; application.yml -主启动配置文件&#xff08;用于控制使用哪种环境配…

Flink系列之:Apache Kafka SQL 连接器

Flink系列之&#xff1a;Apache Kafka SQL 连接器 一、Apache Kafka SQL 连接器二、依赖三、创建Kafka 表四、可用的元数据五、连接器参数六、特性七、Topic 和 Partition 的探测八、起始消费位点九、有界结束位置十、CDC 变更日志&#xff08;Changelog&#xff09; Source十一…

Java:获取当前线程的线程组

代码示例&#xff1a; package com.thb;public class Demo4 {public static void main(String[] args) {ThreadGroup threadGroup Thread.currentThread().getThreadGroup();System.out.println(threadGroup.getName());} }运行输出&#xff1a;

“2024山西智博会”由中国人工智能学会和省科学技术协会联合主办

近日&#xff0c;山西省政府新闻办近日举行了“山西加快转型发展”系列主题新闻发布会的第六场发布会&#xff0c;同时也是“推动数字经济发展壮大”专场发布会。在发布会上&#xff0c;省委、省政府强调了数字经济的重要性&#xff0c;并将其作为重组要素资源、重塑经济结构、…

【无标题】欢迎使用Markdown编辑器

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

第五节TypeScript 运算符

一、描述 运算符用于执行程序代码运算。 二、运算符主要包括&#xff1a; 算术运算符逻辑运算符关系运算符按位运算符赋值运算符三元/条件运算符字符串运算符类型运算符 1、算术运算符 y5&#xff0c;对下面算术运算符进行解释&#xff1a; 运算符 描述 例子 x 运算结果…

Jtti:Tomcat服务器底层原理是什么

Apache Tomcat 是一个开源的、轻量级的应用服务器&#xff0c;用于执行Java Servlet、JavaServer Pages (JSP) 和其他相关技术的Web应用程序。以下是 Tomcat 服务器底层的主要原理&#xff1a; Servlet容器&#xff1a; Tomcat 是一个Servlet容器&#xff0c;实现了Java Servle…

ros2 humble安装joint_state_publisher功能包

第一步从GitHub下载此功能包&#xff1a;命令 git clone -b ros2 https://github.com/ros/joint_state_publisher.git 然后执行里面的setup.py文件就完成安装&#xff1b;命令是 sudo python setup.py install 最后检查是不是有安装好&#xff0c;输入命令&#xff1a; ro…