树莓派,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 = 10
    picam2.preview_configuration.align()
    picam2.configure("preview")
    picam2.start()
    fps = 0
    time.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 = 0
        cv2.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 // 2
        centerY.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) = rect
            cv2.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-tStart
            fps = .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 = 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=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:
        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. 运行color_tracking_pid.py,移动黄色铅笔,摄像头就会自动追踪该对象

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

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

相关文章

STM32 使用ARM仿真器设置

STM32单片机程序下载到单片机芯片中有两种方式&#xff0c;①编译生成HEX&#xff0c;使用程序烧录软件刷到单片机芯片里。②使用ARM仿真器下载程序。使用ARM仿真器的优势是&#xff0c;在工程编译没问题直接在Keil软件里就可以将程序下载到单片机里&#xff0c;并且程序可以在…

6. 行为模式 - 观察者模式

亦称&#xff1a; 事件订阅者、监听者、Event-Subscriber、Listener、Observer 意图 观察者模式是一种行为设计模式&#xff0c; 允许你定义一种订阅机制&#xff0c; 可在对象事件发生时通知多个 “观察” 该对象的其他对象。 问题 假如你有两种类型的对象&#xff1a; ​ 顾…

如何在 openKylin 上使用 ONLYOFFICE 桌面编辑器

文章作者&#xff1a;ajun ONLYOFFICE 桌面编辑器是一款基于依据 AGPL v.3 许可进行分发的开源办公套件。使用这款应用&#xff0c;您无需保持网络连接状态即可处理存储在计算机上的文档。 本文章基于中国根操作系统 openKylin 操作系统&#xff0c;使用软件商店快速安装与手…

IPC之九:使用UNIX Domain Socket进行进程间通信的实例

socket 编程是一种用于网络通信的编程方式&#xff0c;在 socket 的协议族中除了常用的 AF_INET、AF_RAW、AF_NETLINK等以外&#xff0c;还有一个专门用于 IPC 的协议族 AF_UNIX&#xff0c;IPC 是 Linux 编程中一个重要的概念&#xff0c;常用的 IPC 方式有管道、消息队列、共…

深入了解Spring MVC工作流程

目录 1. MVC架构简介 2. Spring MVC的工作流程 2.1. 客户端请求的处理 2.2. 视图解析和渲染 2.3. 响应生成与返回 3. Spring MVC的关键组件 3.1. DispatcherServlet 3.2. HandlerMapping 3.3. Controller 3.4. ViewResolver 4. 结论 Spring MVC&#xff08;Model-Vi…

Node.js-模块化(二)

1. 模块化的基本概念 1.1 什么是模块化 模块化是指解决一个复杂问题时&#xff0c;自顶向下逐层将系统拆分成若干模块的过程。对于整个系统来说&#xff0c;模块是可组合、分解和更换的单元。 1.2 编程领域中的模块化 编程领域中的模块化&#xff0c;就是遵守固定的规则&…

ubuntu下docker安装,配置python运行环境

参考自: 1.最详细ubuntu安装docker教程 2.使用docker搭建python环境 首先假设已经安装了docker&#xff0c;卸载原来的docker 在命令行中运行&#xff1a; sudo apt-get updatesudo apt-get remove docker docker-engine docker.io containerd runc 安装docker依赖 apt-get…

flink使用sql-client-defaults.yml无效

希望在flink sql脚本启动时自动选择catalog&#xff0c;减少麻烦。于是乎配置sql-client-defaults.yaml&#xff1a; catalogs:- name: hive_catalogtype: icebergcatalog-type: hiveproperty-version: 1cache-enabled: trueuri: thrift://localhost:9083client: 5warehouse: …

【Docker-5】镜像编排

Dockerfile语法 制作apache镜像 httpd.service 文件路径&#xff1a;/lib/systemd/system/httpd.service [rootdocker-0002 ~]# mkdir apache [rootdocker-0002 ~]# cd apache拷贝动态页面到docker-0002的/root/apache/ [rootecs-proxy ~]# scp /root/5/public/info.php 192.…

Unity中的导航系统

NavMesh(导航网格)是3D游戏世界中用于实现动态物体自动寻路的一种技术&#xff0c;将游戏中复杂的结构组织关系简化为带有一定信息的网格&#xff0c;在这些网格的基础上通过一系列的计算来实现自动寻路。导航时&#xff0c;只需要给导航物体挂载导航组建&#xff0c;导航物体便…

华为安防监控摄像头

华为政企42 华为政企 目录 上一篇华为政企城市一张网研究报告下一篇华为全屋wifi6蜂鸟套装标准

uniapp中使用封装步骤条组件

针对步骤条封装完终于清清楚楚啦 先看效果&#xff1a; 附上代码&#xff1a;使用可直接复用&#xff1a;数据是写在了当前组件中&#xff0c;如有必须&#xff0c;可以使用其中的props传值stepInfos传递相应的数据&#xff0c;根据steps步数就可以控制走到哪一步啦 <temp…

AOP springboot

1. 2. Around(“execution(* com.example.demo.controller..(…))”) 代表所有的类下面所有的方法任意参数 3.

【IntelliJ IDEA】打开项目Git突然无法识别解决方案

这个问题也是我今天突然偶尔遇到的&#xff0c;当时没在意&#xff0c;项目打开之后又关闭&#xff0c;后来很久才又打开&#xff0c;发现项目明明有git版本控制的&#xff0c;咋突然开发工具右下角没有标识了&#xff0c;然后检查了一下git配置还报错了。 其实从图上我们可以看…

腾讯云发布升级版金融音视频解决方案,提供全新架构、安全和特性

远程银行、视频尽调、全媒体客服、路演直播……近年来&#xff0c;音视频技术支撑下的非接触式金融服务&#xff0c;成为了金融机构数字化转型和探索服务创新的重要方向。 12月21日&#xff0c;腾讯云正式发布升级版金融级音视频解决方案。新方案在架构、安全和特性上进行全面…

11 Vue3中v-bind绑定动态样式和动态样式类

概述 v-bind指令可以说是Vue3中最常用的指令之一&#xff0c;使用v-bind&#xff0c;我们几乎能够给任何实现动态的绑定比值。 这里&#xff0c;我们主要演示以下&#xff0c;通过v-bind动态绑定CSS样式。 基本用法 我们创建src/components/Demo11.vue&#xff0c;在这个组…

CSS:元素显示模式与背景

CSS&#xff1a;元素显示模式与背景 元素显示模式什么是元素显示模式块级元素 block行内元素 inline行内块元素 inline-block元素显示模式对比元素显示模式转换 display 背景背景颜色 background-color背景图片 background-image背景平铺 background-repeat背景图片位置 backgr…

当下流行的直播技术demo演示

nginx-http-flv-module&#xff08;更新不是很频繁&#xff09; SRS: https://ossrs.net/lts/zh-cn/&#xff08;独立官网&#xff0c;目前最新稳定版version5&#xff09; 基于SRS搭建直播demo演示&#xff1a; 一、搭建流媒体服务器 参见官网&#xff1a;https://ossrs.ne…

【已解决】vs2015下c++对sqlite的操作

本博文源于笔者操作sqlite3&#xff0c;借鉴了很多文章的思路&#xff0c;这里并整理了c常用的对数据库的操作供大家点赞收藏以后备用。包含了&#xff1a;c对sqlite3的创建数据库、创建数据表、写入数据表、读取数据表、删除数据表。也包括了最基础的让c运行sqlite3.内容供读者…

OV通配符

OV通配符主要功能是可以保护主域名及下一级域名无限制的使用&#xff0c;所以通配符可以省钱而且方便管理&#xff0c;适用于各种环境及服务器&#xff0c;凡主流的三大机构签发的证书都可以符合兼容性99%&#xff0c;并且可以支持等保认证&#xff0c;分别是&#xff1a;DigiC…