视觉循迹小车(旭日x3派、摄像头、循迹)

1、旭日x3派(烧录好系统镜像)

2、USB摄像头

3、TB6612

4、小车底盘(直流电机或直流减速电机)

 

视觉循迹原理

x3派读取摄像头图像,转换成灰度图像,从灰度图像中选择第 120 行(图像的一个水平线),遍历第120行的全部320列,根据像素值小于或大于阈值,将相应的值(0 或 1)添加到 date 列表中。最后根据小于阈值的像素个数和它们的总和来判断黑色赛道的位置,以此调节左右电机的转速实现循迹。

 

python代码

import Hobot.GPIO as GPIO

import time

import cv2

 

class EYE():

    def __init__(self):

        self.video = cv2.VideoCapture(8)  #打开索引为8的摄像头

        ret = self.video.isOpened()  #判断摄像头是否打开成功

        if ret:

            print("The video is opened.")

        else:

            print("No video.")

 

        codec = cv2.VideoWriter_fourcc( 'M', 'J', 'P', 'G' )   #设置参数

        self.video.set(cv2.CAP_PROP_FOURCC, codec)

        self.video.set(cv2.CAP_PROP_FPS, 30)

        self.video.set(cv2.CAP_PROP_FRAME_WIDTH, 672)

        self.video.set(cv2.CAP_PROP_FRAME_HEIGHT, 672)

 

        # 创建全屏窗口

        #cv2.namedWindow("Camera Feed", cv2.WND_PROP_FULLSCREEN)

        #cv2.setWindowProperty("Camera Feed", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

 

    def outmiss(self):

        _, img = self.video.read()  #从摄像头读取一帧图像

        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  #将图像转为灰度

        img = img[120]   #选择图像的第120行,一共240行。

 

        date = []

        for i in range(320):    #遍历每一列,一共320列

            if img[i] <= 64:   #如果当前列的像素值小于等于 64,将 1 添加到 date 列表,表示该像素是感兴趣的。

                date.append(1)

            elif img[i] > 64:  #如果当前列的像素值大于 64,将 0 添加到 date 列表,表示该像素不感兴趣。

                date.append(0)

 

        n = 0   #用于计算感兴趣的像素数量。  

        sum = 0   #用于计算感兴趣像素的列索引总和。

        for i in range(320):

            if date[i] == 1:

                sum += i   #如果该列的像素是感兴趣的(即 date[i] 为 1),则更新 sum 和 n。

                n += 1

        if n >= 18:

            return sum / n - 159.5

        else:

            return None

 

    def off(self):

 

        self.video.release()

 

class CTRL():

    def __init__(self, in1, in2, in3, in4, pa, pb):

        GPIO.setmode(GPIO.BOARD)

        GPIO.setwarnings(False)

 

        GPIO.setup(in1, GPIO.OUT)

        GPIO.setup(in2, GPIO.OUT)

        GPIO.setup(in3, GPIO.OUT)

        GPIO.setup(in4, GPIO.OUT)

 

        self.in1 = in1

        self.in2 = in2

        self.in3 = in3

        self.in4 = in4

 

        self.PWMA = GPIO.PWM(pa, 48000)

        self.PWMB = GPIO.PWM(pb, 48000)

 

    def drive(self, FL, FR):

        if FL >= 0:

            GPIO.output(self.in3, GPIO.HIGH)

            GPIO.output(self.in4, GPIO.LOW)

        elif FL < 0:

            GPIO.output(self.in4, GPIO.HIGH)

            GPIO.output(self.in3, GPIO.LOW)

 

        if FR >= 0:

            GPIO.output(self.in1, GPIO.HIGH)

            GPIO.output(self.in2, GPIO.LOW)

        elif FR < 0:

            GPIO.output(self.in2, GPIO.HIGH)

            GPIO.output(self.in1, GPIO.LOW)

 

        self.PWMA.ChangeDutyCycle(abs(FR))

        self.PWMB.ChangeDutyCycle(abs(FL))

        self.PWMA.start(abs(FR))

        self.PWMB.start(abs(FL))

 

    def stop(self):

        GPIO.output(self.in1, GPIO.LOW)

        GPIO.output(self.in2, GPIO.LOW)

        GPIO.output(self.in3, GPIO.LOW)

        GPIO.output(self.in4, GPIO.LOW)

 

        self.PWMA.ChangeDutyCycle(0)

        self.PWMB.ChangeDutyCycle(0)

        self.PWMA.start(0)

        self.PWMB.start(0)

 

    def clean(self):

        self.PWMB.stop()

        self.PWMA.stop()

        GPIO.cleanup()

        

class PID():

    def __init__(self,KP,KI,KD):

        self.KP = KP

        self.KI = KI

        self.KD = KD

        self.p1 , self.p2 = 0 , 0#保留一个帧的误差

        self.i = 0#积累误差初值

        

    def naosu(self,miss):

        if miss != None:

            self.p1 , self.p2 = self.p2 , miss #替换缓存的误差

            self.i += miss

            if self.i > 1000:

                self.i -= 800

            if self.i < -1000:

                self.i += 800#积累误差的限制

            naosu = self.KP * miss + self.KI * self.i + self.KD * (self.p2 - self.p1)

            #按照公式输出

            return naosu

            

        elif miss == None:

        #摄像头读空时,根据上一帧的缓存误差正负,来判断现在应该原地左转还是右转

            if self.p2 >= 0:

                self.p1 , self.p2 = self.p2 , 1

                return "r"

                

            elif self.p2 < 0:

                self.p1 , self.p2 = self.p2 , -1

                return "l"

 

if __name__ == '__main__':

    try:

        Ctrl = CTRL(11, 13, 16, 15, 32, 33)  # 设置管脚

        Eye = EYE()  # 调用视觉模块

        Pid = PID(0.095,0.001,0.52)#调用PID,传入参数

        Ctrl.drive(25, 25)  # 小车的始发运动

        time.sleep(0.5)

        while True:

            ms = Eye.outmiss()  # 获取误差

            ns = Pid.naosu(ms)#获取修正值

            if ns == "r":#原地转弯的情况

                Ctrl.drive(20,-20)

            elif ns == "l":

                Ctrl.drive(-20,20)

            else:#限制修正值,保证不超过PWM上下限

                if ns > 18:

                    ns = 18

                if ns < -18:

                    ns = -18

                    

                Ctrl.drive(25+ns, 25-ns)  # 小车的始发运动

         

            # 添加代码来显示摄像头捕获的图像

            _, frame = Eye.video.read()

            cv2.imshow("Camera Feed", frame)

            time.sleep(0.2)

            if cv2.waitKey(1) & 0xFF == ord('q'):

                break

                

                

    finally:

        Ctrl.stop()

        Ctrl.clean()

        Eye.off()

        cv2.destroyAllWindows()

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

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

相关文章

Stable Diffusion XL之使用Stable Diffusion XL训练自己的AI绘画模型

文章目录 一 SDXL训练基本步骤二 从0到1上手使用Stable Diffusion XL训练自己的AI绘画模型2.1 配置训练环境与训练文件2.2 SDXL训练数据集制作(1) 数据筛选与清洗(2) 使用BLIP自动标注caption(3) 使用Waifu Diffusion 1.4自动标注tag(4) 补充标注特殊tag(5) 训练数据预处理(标注…

neo4j相同查询语句一次查询特慢再次查询比较快。

现象&#xff1a; neo4j相同查询语句一次查询特慢再次查询比较快。 分析&#xff1a; 查询语句 //查询同名方法match(path:Method) where id(path) in [244333030] and NOT path:Constructor//是rpc的方法match(rpc_method:Method)<-[:DECLARES]-(rpc_method_cls:Class) -…

从零开始为香橙派orangepi zero 3移植主线linux——1.uboot

从零开始为香橙派orangepi zero 3移植主线linux——1.uboot 0.前言一、准备二、制作引导文件1.BL312.SCP firmware (Crust)3.uboot 三、烧录四、运行 0.前言 之前买了块香橙派zero3&#xff0c;CPU是全志H618&#xff0c;四核cortex-A53&#xff0c;烧录了官方的ubuntu系统后就…

vscode上编辑vba

安装xvba插件更换vscode的工作目录启动扩展服务器在config.json中添加目标工作簿的名称加载excel文件&#xff08;必须带宏的xlsm&#xff09;这个扩展就会自动提取出Excel文件中的代码Export VBA&#xff08;编辑完成的VBA代码保存到 Excel文件 &#xff09;再打开excel文件可…

了解XSS和CSRF攻击与防御

什么是XSS攻击 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种常见的网络安全漏洞&#xff0c;它允许攻击者在受害者的浏览器上执行恶意脚本。这种攻击通常发生在 web 应用程序中&#xff0c;攻击者通过注入恶意脚本来利用用户对网站的信任&…

centos2anolis

我的centos7原地升级到anolis7记录 注意&#xff1a;如果是桌面版请先卸载firefox&#xff0c;否则so文件冲突。 参考&#xff1a; CentOS 7和8Linux系统迁移到国产Linux龙蜥Anolis OS 8手册_disable pam_pkcs11 module in pam configuration-CSDN博客 关于 CentOS 迁移龙蜥…

使用JMeter的JSON提取器:通过递归下降查找,从接口响应中提取特定字段

在接口测试中&#xff0c;我们经常需要从返回的JSON数据中提取特定字段以便后续使用。JMeter提供了JSON提取器&#xff0c;可以帮助我们实现这一目标。本文将介绍如何使用JMeter的JSON提取器通过递归下降查找的方式从接口响应中提取特定字段&#xff0c;并通过示例解释JSON表达…

【大模型】非常好用的大语言模型推理框架 bigdl-llm,现改名为 ipex-llm

非常好用的大语言模型推理框架 bigdl-llm&#xff0c;现改名为 ipex-llm bigdl-llmgithub地址环境安装依赖下载测试模型加载和优化预训练模型使用优化后的模型构建一个聊天应用 bigdl-llm IPEX-LLM is a PyTorch library for running LLM on Intel CPU and GPU (e.g., local P…

QT中的服务器与客户端

一、前言 本文主要讲讲QT中服务器与客户端的使用方法&#xff0c;QT已经封装好了&#xff0c;调用相应类直接访问即可。本文以QT中的QT中的TCP为例子&#xff0c;讲下使用方法以及线程中使用。 二、正文 2.1 Sever的使用方法 2.1.1 思路 QT中Sever使用的时候大致步骤为&…

网络链路层之(2)PPP协议

网络链路层之(2)PPP协议 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN…

STM32学习笔记(6_8)- TIM定时器的编码器接口代码

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 现在开…

MyEclipse将项目的开发环境与服务器的JDK 版本保持一致

前言 我们使用MyEclipse开发Java项目开发中&#xff0c;偶尔会遇到因项目开发环境不协调&#xff0c;导致这样那样的问题&#xff0c;在这里以把所有环境调整为JDK1.6 为例。 操作步骤 1.Window-->Preferences-->Java-->Installed JRES 修改为 1.6版本 2.Window-->…

【搜索引擎2】实现API方式调用ElasticSearch8接口

1、理解ElasticSearch各名词含义 ElasticSearch对比Mysql Mysql数据库Elastic SearchDatabase7.X版本前有Type&#xff0c;对比数据库中的表&#xff0c;新版取消了TableIndexRowDocumentColumnmapping Elasticsearch是使用Java开发的&#xff0c;8.1版本的ES需要JDK17及以上…

connections on Unix domain socket “/var/run/postgresql/.s.PGSQL.5432“

Postgresql 修改数据存储位置后重启报错/重启服务报错/connections on Unix domain socket “/var/run/postgresql/.s.PGSQL.5432_connections on unix domain socket "/var/run/postgr-CSDN博客 ps -ef | grep post netstat -lnpt | grep 5432 cd /var/lib/postgresql/12…

linux正则表达式之.

1..含义 linux正则表达式.表示“任意一个”字符&#xff0c;一定是一个任意字符 2.样例 正则表达式.样例 命令&#xff1a; grep -n "m.n" anaconda-ks.cfg 搜索的字符串可以是(min)、(m n)&#xff0c;但不能是(mn)&#xff0c;即m与n之间一定仅有一个字符。注…

【unity】解决unity编译器安装中文汉化包失败

如果有的同学中文包安装失败&#xff0c;我们找到相应的编译器版本&#xff0c;点击在资源管理器中显示按钮&#xff0c; 我们点击当前目录的上一级&#xff0c;进入编译器目录。 找到modules.json文件双击打开 我们找到简体中文&#xff0c;复制downloadUrl后面的值到浏览…

云电脑安全性怎么样?企业如何选择安全的云电脑

云电脑在保障企业数字资产安全方面&#xff0c;采取了一系列严谨而全面的措施。随着企业对于数字化转型的深入推进&#xff0c;数字资产的安全问题日益凸显&#xff0c;而云电脑作为一种新兴的办公模式&#xff0c;正是为解决这一问题而生。云电脑安全吗&#xff1f;可以放心使…

Java常见限流用法介绍和实现

目录 一、现象 ​编辑 二、工具 ​​​​​​1、AtomicInteger,AtomicLong 原子类操作 ​​​​​​2、RedisLua ​​​​​​3、Google Guava的RateLimiter 1&#xff09; 使用 2&#xff09; Demo 3&#xff09; 优化demo 4、阿里开源的Sentinel 三、算法 1、计数…

『Apisix进阶篇』动态负载均衡:APISIX的实战演练与策略应用

&#x1f680;『Apisix系列文章』探索新一代微服务体系下的API管理新范式与最佳实践 【点击此跳转】 &#x1f4e3;读完这篇文章里你能收获到 &#x1f3af; 掌握APISIX中多种负载均衡策略的原理及其适用场景。&#x1f4c8; 学习如何通过APISIX的Admin API和Dashboard进行负…

LaMDA: Language Models for Dialog Applications

Abstract LaMDA: Language Models for Dialog Applications. 虽然增大模型可以提高质量,但是在 safety 和 factual grounding 方面的改进较少可以通过使用标注数据微调和查询外部知识源来提升 safety 和 factual grounding Safety: 使用标注数据训练一个分类器用于过滤有害内…