树莓派应用--AI项目实战篇来啦-12.OpenCV摄像头云台物体追踪

news/2024/10/22 16:49:56/

1. 介绍

        本项目主要是实现OpenCV识别物体,找出中心位置,根据中心位置的偏离情况来修正二维云台,让物体的中心位置始终处于图像的中心位置,要保证追踪的流畅性,这里引入了 PID算法来抑制云台的抖动。

2. PID算法

        在实际使用舵机追踪小球时,由于小球的中心位置在图像中会不停的抖动,导致舵机云台在追踪的效果中,需要进行误差的处理,追踪的原理其实就是移动舵机云台的x方向和y方向,始终保持小车处于图像的中心位置,在实际测试中,结合树莓派的性能,这个采用的窗口大小为(320,240)像素的窗口大小,那么中心位置则为(160,120)位置上,误差的处理我们这里需要使用到PID算法。

2.1 什么是PID 控制器

        常见的闭环控制算法是所谓的PID及比例-积分-微分控制器。
        PID通常用于自动化,使得机械装置可以快速且准确地达到最佳值(由反馈传感器读取)。
        它们用于制造,自动化过程控制,机器人等。
        PID 控制器计算误差项(所需设定点和传感器读数之间的差值)并且具有补偿误差的目
标。
        PID 计算输出一个值,该值用作“过程”的输入(机电过程,而不是计算机科学/软件工程师类型认为的“计算机过程”)。
        传感器输出称为“过程变量”,并作为等式的输入。在整个反馈回路中,捕获定时并将其输入到等式中。

图:比例积分微分(PID)控制回路将用于我们的每个平移和倾斜过程

        注意输出如何循环回输入。还要注意如何计算和求和比例,积分和微分值。
        该图可以用等式形式写成:


        P(比例):如果当前误差很大,输出将成比例增大,以引起显着的校正。
        I(积分):误差的历史值随时间积分。进行微弱的校正以减少误差。如果错误被消除,该项将不会增大。
        D(微分):这个微分项预示着未来。实际上,它是一种阻尼方法。如果P或|将导致值过冲(即舵机转过了一个物体或方向盘转得太多),D将在到达输出之前抑制效果。
        简单来说:
        P-比例,直接调节幅度
        I-积分,修正累计误差
        D-微分,阻尼(预测未来)

        本节将以追踪黄色物体为例进行实验,通过滑条可以随时调节HSV的各种分量的值。

2.2总线舵机零点位置校准

        通过 python 程序来调节舵机位置,使得初始位置力中间位置,也就是说,两个舵机的角度都为90度的时候,为零点状态,这样子舵机的左右角度和倾斜角度是等分的。

import cv2
import numpy as np
from adafruit_servokit import ServoKit
import timekit = ServoKit(channels=16)# 舵机调零
pan = 90
tilt = 90#初始化位置
kit.servo[0].angle=pan
kit.servo[1].angle=tilt

        “Pan”舵机将“水平”移动摄像头(“方位角”),而“Tilt” 舵机将其“垂直”移动(仰角)。

        在我们的开发过程中,我们不会进行“极限” 操作,而只能使用30至150度的 “平移/倾斜”机制。此范围足以用于摄像头。
        当平移/倾斜角度旋转到90度位置后,而实际上舵机云台不在中间位置,这个时候,我们就需要重新安装一下舵机云台,使舵机云台的零点位置处于正确位置上即可。

3.建立电路

树莓派T型转接板PCA9685 舵机驱动模块
SCLSCLSCL
SDASDASDA
3.3V3.3VVCC
5V5VV+
GNDGNDGND
舵机PCA9685 舵机驱动模块
水平方向舵机(pan)PWM0
倾斜舵机(tilt)PWM1

下图显示了电路图连接:

4.源程序代码

        

# 载入必要的库
import cv2
import numpy as np
from adafruit_servokit import ServoKit
import timekit = ServoKit(channels=16)# 舵机调零
pan =  90
tilt = 90
# 初始化位置
kit.servo[0].angle=pan
kit.servo[1].angle=tiltdef bgr8_to_jpeg(value, quality=75):return bytes(cv2.imencode('.jpg', value)[1])import traitlets
import ipywidgets.widgets as widgets
from IPython.display import displayFGmaskComp_img = widgets.Image(format='jpeg', width=320, height=240)
frame_img = widgets.Image(format='jpeg', width=320, height=240)dispaly_img = widgets.HBox([FGmaskComp_img,frame_img])
display(dispaly_img)# 线程函数操作库
import threading # 线程
import ctypes
import inspect# 线程结束代码
def _async_raise(tid, exctype):tid = ctypes.c_long(tid)if not inspect.isclass(exctype):exctype = type(exctype)res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))if res == 0:raise ValueError("invalid thread id")elif res != 1:ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)raise SystemError("PyThreadState_SetAsyncExc failed")def stop_thread(thread):_async_raise(thread.ident, SystemExit)import libcamera
from picamera2 import Picamera2picamera = Picamera2()
config = picamera.create_preview_configuration(main={"format": 'RGB888', "size": (320, 240)},raw={"format": "SRGGB12", "size": (1920, 1080)})
config["transform"] = libcamera.Transform(hflip=0, vflip=1)
picamera.configure(config)
picamera.start()width  = 320
height = 240hueLower = widgets.IntSlider(min=3,max=179,step=1,description='hueLower:',value=3)
hueUpper = widgets.IntSlider(min=23,max=179,step=1,description='hueUpper:',value=23)hue2Lower = widgets.IntSlider(min=3,max=179,step=1,description='hue2Lower:',value=3)
hue2Upper = widgets.IntSlider(min=0,max=179,step=1,description='hue2Upper:',value=0)satLow = widgets.IntSlider(min=100,max=255,step=1,description='satLow:',value=100)
satHigh = widgets.IntSlider(min=255,max=255,step=1,description='satHigh:',value=255)valLow = widgets.IntSlider(min=100,max=255,step=1,description='valLow:',value=100)
valHigh = widgets.IntSlider(min=255,max=255,step=1,description='valHigh:',value=255)slider_img=widgets.VBox([widgets.HBox([hueLower,hueUpper]),widgets.HBox([hue2Lower,hue2Upper]),widgets.HBox([satLow,satHigh]),widgets.HBox([valLow,valHigh])])
display(slider_img)def Video_display():global panglobal tiltwhile True:   frame = picamera.capture_array()hsv=cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)hueLow=hueLower.value hueUp=hueUpper.valuehue2Low=hue2Lower.valuehue2Up=hue2Upper.valueLs= satLow.valueUs = satHigh.valueLv=valLow.valueUv=valHigh.valuel_b=np.array([hueLow,Ls,Lv])u_b=np.array([hueUp,Us,Uv])l_b2=np.array([hue2Low,Ls,Lv])u_b2=np.array([hue2Up,Us,Uv])FGmask=cv2.inRange(hsv,l_b,u_b)FGmask2=cv2.inRange(hsv,l_b2,u_b2)FGmaskComp=cv2.add(FGmask,FGmask2)FGmaskComp_img.value = bgr8_to_jpeg(FGmaskComp)contours,_=cv2.findContours(FGmaskComp,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)contours=sorted(contours,key=lambda x:cv2.contourArea(x),reverse=True)for cnt in contours:area=cv2.contourArea(cnt)(x,y,w,h)=cv2.boundingRect(cnt)if area>=50:cv2.rectangle(frame,(x,y),(x+w,y+h),(255,0,0),3)objX=x+w/2objY=y+h/2errorPan=objX-width/2errorTilt=objY-height/2            if abs(errorPan)>15:pan=pan-errorPan/75    if abs(errorTilt)>15:tilt=tilt-errorTilt/75if pan>180:pan=180print("Pan Out of  Range")           if pan<0:pan=0print("Pan Out of  Range")              if tilt>180:tilt=180print("Tilt Out of  Range") if tilt<0:tilt=0print("Tilt Out of  Range")kit.servo[0].angle=180-pankit.servo[1].angle=180-tiltbreak frame_img.value = bgr8_to_jpeg(frame)cam.release()t = threading.Thread(target=Video_display)
t.setDaemon(True)
t.start()# 结束线程
stop_thread(t)

        可以通过滑块调节具体的HSV值 :

 


        基于(x,y)坐标,使用kit.servo[0].angle=pankit. servo[1].angle=tilt函数生成舵机位置命令。例如,假设y位置“50”,这意味着我们的对象几乎在屏幕的顶部,可以将其转换为“摄像头视线”为“低”(假设倾斜角度为120度)因此,我们必须“减小”倾斜角度(假设为100度),这样摄像头的视线将“向上”并且对象在屏幕上将“向下”(y会增加到 190)。

        上图显示了几何示例。
        想想平移摄像头将如何操作。请注意,摄像头实际图像和显示的图像正好处于一个镜像状态,这意味着如果您将对象移到“您的左侧”,则在与摄像机相对时,它将在屏幕上以“您的右侧”移动。


http://www.ppmy.cn/news/1538973.html

相关文章

自然语言处理 (NLP) 的 5 个步骤

自然语言处理 (NLP) 的 5 个步骤 引言 如今&#xff0c;我们的世界在数字化连接方面达到了前所未有的水平。信息、见解和数据不断争夺我们的注意力&#xff0c;我们不可能全部消化。对于你的企业来说&#xff0c;挑战在于了解客户和潜在客户对你的产品和服务的看法&#xff0c;…

同城搭子怎么找?靠谱同城找搭子交友攻略分享!

在繁华的城市中&#xff0c;我们常常渴望有个搭子相伴&#xff0c;一起分享生活的点滴。无论是寻找一起品尝美食的饭搭子&#xff0c;还是共同挥洒汗水的运动搭子&#xff0c;亦或是畅谈心事的聊天搭子&#xff0c;都能让生活更加丰富多彩。那么&#xff0c;如何才能找到那个与…

【玩转 JS 函数式编程_011】3.2 JS 函数式编程筑基之:以函数式编程的方式活用函数(下)+ 3.3 本章小结

文章目录 3.2.4. 填充脚本 Polyfills1. 检测 Ajax&#xff08;Detecting Ajax&#xff09;2. 替代函数 Adding missing functions 3.2.5. 插入处理 Stubbing3.2.6. 即时调用 Immediate invocation 3.3. 本章小结 Summary 写在前面 不知道看到这里的朋友有没有真正消化 上篇 中介…

探索高效的 PDF 拆分工具及其独特功能

当一份大型的PDF文档包含了多个不同主题或章节的内容时&#xff0c;将其拆分成独立的部分可以更方便我们的阅读、编辑和管理。接下来&#xff0c;让我们一起走进PDF拆分工具的世界&#xff0c;了解它们的功能和价值。 1.福昕PDF编辑器 链接一下>>https://editor.foxits…

Keepalived LVS群集

keepalived群集 Keepalived是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题。 群集具备的特性 1&#xff09;负载均衡 提高群集的性能 2&#xff09;健康检查&#xff08;探针&#xff09; 探测调度器和节点服务器是否在正常运…

商标已拿证,为何无缘无故被撤销?

什么是“撤三”&#xff1f; 《商标法》第四十九条规定&#xff0c;“注册商标成为其核定使用的商品的通用名称或者没有正当理由连续三年不使用的&#xff0c;任何单位或者个人可以向商标局申请撤销该注册商标。”这就是我们通常所说的“撤三”。 那么怎样才算规范、正确的商标…

操作系统笔记---进程的同步与互斥方法汇总

实现互斥的四个原则主要特点空闲让进忙则等待有限等待让权等待软件方法单标志法❌turn 1/0双标志先检查法❌flag[i] true/flase双标志后检查法❌❌flag[i] true/flasePerterson法❌turn 1/0 flag[i] true/flase硬件方法关中断不适用多处理机 关中断权…

Sealos Devbox 发布,珍爱生命,远离 CI/CD

水滴攻击太阳系用的是最原始的攻击方式&#xff1a;撞击&#xff01;却又如此有效率。 当我们搞了一堆容器、编排、CI/CD、DevOps&#xff0c;发明了一大堆没什么用的名词之后&#xff0c;最终发现这些操作都是花里胡哨&#xff0c;让开发者越陷越深。 最终你会发现一个真理&…