如何让小型双轮差速底盘实现视觉循迹功能

news2024/12/24 6:10:07

1. 任务描述

      在机器人小车(R023d)上搭载摄像头,摄像头采集图像信息并通过WiFi将信息传递给PC端,然后PC端使用OpenCV对摄像头读取到的视频进行灰度化、高斯滤波、腐蚀、膨胀等处理,使图像分为黑白两色。PC端进行图像信息处理并将处理结果传递为下位机,下位机接收上位机处理的图像信息结果后便会控制小车相应运动,小车运动包含前进、左转、右转、停止。

2. 电子硬件

在这个示例中,我们采用了以下硬件,请大家参考:

主控板Basra(兼容Arduino Uno)
扩展板Bigfish2.1
电池7.4V锂电池

通信

2510通信转接板
WiFi路由器
其它摄像头x1、计算机x1

3. 功能实现

视觉小车巡黑线工作原理:

      (1) 摄像头采集图像信息;

      (2) 通过 WiFi 将图像信息传递给 PC 端(VS2015 配置的 OpenCV 环境);

      (3) 在 PC 端使用 OpenCV 对摄像头读取到的视频进行灰度化、高斯滤波、腐蚀、膨胀等处理,使图像分为黑白两色,采用 RGB 颜色模型作为黑白颜色判断;

      (4) 将图像对称分成左右两半,分别判断左、右计算检测在显示的摄像范围内的黑色像素区域所占比例=黑色像素范围/显示的摄像范围;

      (5) 比较两侧黑色像素区域所占比例大小确定前进方向,如果左侧比例大于右侧,则小车左偏离,进行右转;

      (6) PC端进行图像信息处理,将处理结果传递为下位机,下位机控制小车进行相应的运动;

3.1硬件连接

接线说明:

      ① 将2510通信转接板连接到扩展板的扩展坞上面;

      ② 用3根母对母杜邦线将2510通信转接板与WiFi路由器连接起来,GND-GND、RX-RX、TX-TX;

      ③ 找到1根USB线,一端连接到2510通信转接板接口上,另一端连接到WiFi路由器USB接口上;

      ④ 将摄像头线连接到WiFi路由器接口上。

3.2示例程序

编程环境:Arduino 1.8.19

① 下位机例程:

      下位机接收上位机处理的图像信息结果控制小车相应运动,小车运动包含前进、左转、右转、停止。

参考例程代码(car.ino)如下:

/*------------------------------------------------------------------------------------

  版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.

           Distributed under MIT license.See file LICENSE for detail or copy at

           https://opensource.org/licenses/MIT

           by 机器谱 2023-02-02 https://www.robotway.com/

  -----------------------------------------------------------------------------------

/*

  wift car:

  2019/08/19:

  JN

  left: 9, 5;

  right: 10, 6;  

*/



const String FORWARD = "F";

const String BACK = "B";

const String LEFT = "L";

const String RIGHT = "R";

const String STOP = "S";



int speed_left = 41;

int speed_right = 41;



void setup() {

  Serial.begin(9600);

  pinMode(5, OUTPUT);

  pinMode(6, OUTPUT);

 

 

 

  pinMode(9, OUTPUT);

  pinMode(10, OUTPUT);

  Stop();

  delay(1000);

}



void loop() {

  String data = SerialRead();

 

  //if(data != ""){   

    if(data == FORWARD)

      Forward();

    else if(data == BACK)

      Back();

    else if(data == LEFT)

      Left();

    else if(data == RIGHT)

      Right();   

    else if(data == STOP)

      Stop();

// }

}



String SerialRead(){

  String str;

  while(Serial.available()){

    str += char(Serial.read());

  }

  return str;

}



void Forward(){

  analogWrite(9, speed_left);

  analogWrite(5, 0);

  analogWrite(6, 0);

  analogWrite(10, speed_right);

}



void Back(){

  analogWrite(9, 0);

  analogWrite(5, speed_left);

  analogWrite(6, speed_right);

  analogWrite(10, 0);

}



void Left(){

  analogWrite(9, 0);

  analogWrite(5, speed_left);

  analogWrite(6, 0);

  analogWrite(10, speed_right);

}



void Right(){

  analogWrite(9, speed_left);

  analogWrite(5, 0);

  analogWrite(6, speed_right);

  analogWrite(10, 0);

}



void Stop(){

  analogWrite(9, speed_left);

  analogWrite(5, speed_left);

  analogWrite(6, speed_right);

  analogWrite(10,speed_right);

}

 ② 上位机例程:

     上位机(Visual Studio 2015.net下配置OpenCV环境)进行图像信息处理。下面提供一个参考例程(MainWindow.xaml.cs),大家可尝试根据实验效果改写。

/*******************************************************************************************

版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.

           Distributed under MIT license.See file LICENSE for detail or copy at

           https://opensource.org/licenses/MIT

           by 机器谱 2023-02-02 https://www.robotway.com/

---------------------------------------------------------------------------------------

using System;

using System.IO;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.Windows.Media.Animation;

using System.Threading;

using OpenCvSharp;

using System.Drawing;

using System.Drawing.Imaging;

using System.Net;

using System.Net.Sockets;



namespace Tracking_Car

{

    /// <summary>

    /// Tracking_Car

    /// </summary>

    public partial class MainWindow : System.Windows.Window

    {

        //定义视频,控制地址以及控制端口变量

        static string CameraIp = "http://192.168.8.1:8083/?action=stream";

        static string ControlIp = "192.168.8.1";

        static string Port = "2001";



        //定义上位机发送的控制命令变量

        //定义命令变量

        string CMD_FORWARD = "", CMD_TURN_LEFT = "", CMD_TURN_RIGHT = "", CMD_STOP = "";



        /*

         * 指针角度对应各颜色

         * 25 -> 红色

         * 90 -> 绿色

         * 150 -> 蓝色

         */

        int ANGLE_LEFT = 0;

        int ANGLE_GO = 0;

        int ANGLE_RIGHT = 0;



        //黑色像素在左右两侧所占比例

        double numOfleft = 0.0;

        double numOfright = 0.0;



        //创建视频图像实例

        VideoCapture capture = new VideoCapture(CameraIp); //图像大小:宽度 X 长度 = 160 X 120;

        Mat frame = new Mat();   //存储视频每一帧图像像素

        Mat result = new Mat(); //存储二值化图像



        static byte[] kernelValues = { 0, 1, 0, 1, 1, 1, 0, 1, 0 }; // cross (+)

        Mat kernel = new Mat(3, 3, MatType.CV_8UC1, kernelValues);



        //图像中心线坐标

        int x1, y1, x2, y2;

        //窗口面积

        float area;



        //视频显示切换变量

        Boolean isChange = false;

        //循迹开始开关变量

        Boolean isBegin = false;



        public MainWindow()

        {

            InitializeComponent();

        }



        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            Assignment();

        }



        //变量赋值函数

        private void Assignment()

        {

            ANGLE_LEFT = 25;

            ANGLE_GO = 90;

            ANGLE_RIGHT = 150;



            rateLeft.Height = 10;

            rateRight.Height = 10;



            x1 = 80;

            y1 = 0;

            x2 = x1;

            y2 = 120;

            area = 160 * 120 / 2;



            CMD_FORWARD = "F";

            CMD_TURN_LEFT = "L";

            CMD_TURN_RIGHT = "R";

            CMD_STOP = "S";

        }



        /// <summary>

        /// MatToBitmap(Mat image)

        /// </summary>

        public static Bitmap MatToBitmap(Mat image)

        {

            return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(image);

        }



        /// <summary>

        /// BitmapToBitmapImage(System.Drawing.Bitmap bitmap)

        /// </summary>

        public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)

        {

            using (MemoryStream stream = new MemoryStream())

            {

                bitmap.Save(stream, ImageFormat.Png); //格式选Bmp时,不带透明度



                stream.Position = 0;

                BitmapImage result = new BitmapImage();

                result.BeginInit();

                // According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."

                // Force the bitmap to load right now so we can dispose the stream.

                result.CacheOption = BitmapCacheOption.OnLoad;

                result.StreamSource = stream;

                result.EndInit();

                result.Freeze();

                return result;

            }

        }



        //颜色指示动画函数

        int angelCurrent = 0;

        private void ColorIndicate(int where)

        {

            RotateTransform rt = new RotateTransform();

            rt.CenterX = 130;

            rt.CenterY = 200;



            this.indicatorPin.RenderTransform = rt;



            double timeAnimation = Math.Abs(angelCurrent - where) * 5;

            DoubleAnimation da = new DoubleAnimation(angelCurrent, where, new Duration(TimeSpan.FromMilliseconds(timeAnimation)));

            da.AccelerationRatio = 0.8;

            rt.BeginAnimation(RotateTransform.AngleProperty, da);



            switch (where)

            {

                case 25:

                    dirDisplay.Content = "左转";

                    break;

                case 90:

                    dirDisplay.Content = "前进";

                    break;

                case 150:

                    dirDisplay.Content = "右转";

                    break;

                default:

                    dirDisplay.Content = "方向指示";

                    break;

            }



            angelCurrent = where;

        }



        //检测函数

        private void ColorDetect() {

            //将摄像头RGB图像转化为灰度图,便于后续算法处理

            Mat gray = frame.CvtColor(ColorConversionCodes.BGR2GRAY);

            //进行高斯滤波

            Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);

            //闭运算,先膨胀后腐蚀,消除小型黑洞

            Cv2.Dilate(binary, binary, null);

            Cv2.Erode(binary, binary, kernel);



            result = binary.Clone();

            result.Line(new OpenCvSharp.Point(x1, y1), new OpenCvSharp.Point(x2, y2), new Scalar(255, 255, 255), 1);



            float rateOfleft = 0, rateOfRight = 0;

            var indexer = result.GetGenericIndexer<Vec3b>();

            for (int i = 0; i < result.Rows; i++) {

                for (int j = 0; j < result.Cols; j++) {

                    int B = indexer[i, j][0];

                    int G = indexer[i, j][1];

                    int R = indexer[i, j][2];



                    if (B == 0 && G == 0 && R == 0) {

                        if (j <= x1) {

                            numOfleft++;

                        }

                        else

                        {

                            numOfright++;

                        }

                    }

                }

            }



            rateOfleft = (float)(numOfleft) / area * 100;

            rateOfRight = (float)(numOfright) / area * 100;

            rateLeft.Height = rateOfleft;

            rateRight.Height = rateOfRight;

            numOfleft = 0;

            numOfright = 0;

        }



        //命令发送函数

        void SendData(string data)

        {

            try

            {

                IPAddress ips = IPAddress.Parse(ControlIp.ToString());//("192.168.8.1");

                IPEndPoint ipe = new IPEndPoint(ips, Convert.ToInt32(Port.ToString()));//把ip和端口转化为IPEndPoint实例

                Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个Socket



                c.Connect(ipe);//连接到服务器



                byte[] bs = Encoding.ASCII.GetBytes(data);

                c.Send(bs, bs.Length, 0);//发送测试信息

                c.Close();

            }

            catch (Exception e)

            {

                MessageBox.Show(e.Message);

            }

        }



        //方向指示更新及命令发送

        private void CommandSend() {

            double l = rateLeft.Height;

            double r = rateRight.Height;



            if (isBegin) {

                if (Math.Abs(l - r) < 20)   //两侧黑色轨迹基本相同,前进

                {

                    ColorIndicate(ANGLE_GO);

                    SendData(CMD_FORWARD);

                }

                else if ((l - r) < -50)     //左侧黑色轨迹小于右侧,右转

                {

                    ColorIndicate(ANGLE_RIGHT);

                    SendData(CMD_TURN_RIGHT);



                }

                else if ((l - r) > 50)      //右侧黑色轨迹小于左侧,左转

                {

                    ColorIndicate(ANGLE_LEFT);

                    SendData(CMD_TURN_LEFT);

                }

            }

        }



        //视频显示函数

        private void ThreadCapShow()

        {



            while (true)

            {

                try

                {

                    capture.Read(frame); // same as cvQueryFrame

                    if (frame.Empty())

                        break;



                    this.Dispatcher.Invoke(

                        new Action(

                            delegate

                            {

                                if (isChange)

                                {

                                    //检测图像左右两侧黑色像素所占的比例,并显示图像

                                    ColorDetect();

                                    originImage.Source = BitmapToBitmapImage(MatToBitmap(result));

                                    CommandSend();

                                    result = null;

                                }

                                else

                                {

                                    originImage.Source = BitmapToBitmapImage(MatToBitmap(frame));

                                }

                            }

                            ));

                    //Cv2.WaitKey(100);

                    //bitimg = null;

                }

                catch { }

            }

        }



        //加载视频

        private void loadBtn_Click(object sender, RoutedEventArgs e)

        {

            if (originImage.Source != null) return;

            Thread m_thread = new Thread(ThreadCapShow);

            m_thread.IsBackground = true;

            m_thread.Start();

        }



        //切换视频显示,显示检测结果

        private void changeBtn_Click(object sender, RoutedEventArgs e)

        {

            if (!isChange)

            {

                isChange = true;

                changeBtn.Content = "返回";

            }

            else

            {

                isChange = false;

                changeBtn.Content = "切换";

                //指针角度归零

                ColorIndicate(0);

                rateLeft.Height = 10;

                rateRight.Height = 10;

                result = null;

            }

        }


        //循迹开始

        private void bgeinBtn_Click(object sender, RoutedEventArgs e)

        {

            isBegin = true;

        }

        //循迹停止

        private void stopBtn_Click(object sender, RoutedEventArgs e)

        {

            isBegin = false;

            SendData(CMD_STOP);

        }

    }

}

4. 资料下载

​资料内容:
​①【R023】-视觉循迹-程序源代码
​②【R023】-视觉循迹-样机3D文件
​资料下载地址:
https://www.robotway.com/h-col-113.html


想了解更多机器人开源项目资料请关注 机器谱网站

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

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

相关文章

【设计模式】策略模式和责任链模式

策略模式 任何程序都离不开算法&#xff0c;我们需要通过算法去解决特定的问题 策略模式将算法的实现分别封装起来&#xff0c;让他们之间可以方便的进行替换&#xff0c;而不需要去改动代码。属于行为型模式。 举个例子:拼多多现在有促销活动&#xff0c;其优惠策略可能是拼…

2023 年 Java 面试正确姿势(1000+ 面试题附答案解析)

几年前&#xff0c;你只需要简单的 ssm 框架&#xff0c;就能轻松找到一份 Java 的工作&#xff0c;但现在不一样了&#xff0c;随着涌入这个行业的人越来越多&#xff0c;同一个岗位需要筛选掉更多人&#xff0c;要求自然水涨船高&#xff0c;这也就是现在越来越多 Java 程序员…

若依学习——BaseController类(PageHelper)

若依的许多controller都继承了BaseController类&#xff0c;学习一下里面的几个方法。1、initBinder&#xff08;&#xff09;方法查了一下&#xff0c;好像是用来解决前端出传来的属性与后端绑定的&#xff0c;不常用&#xff0c;知道个大概就行&#xff0c;详情&#xff1b;(…

基于自抗扰控制ADRC的主动悬架控制

目录 前言 1. 悬架系统 2.ADRC流程图 3.仿真分析 3.1 性能指标和观测效果对比 3.2控制输入对比 3.3 性能指标均方根对比 4.总结 前言 之前通过4篇文章介绍了ADRC&#xff0c;并且在最后一篇文章中进行了总结和应用&#xff0c;本篇文章继续将其应用于悬架对象上&am…

Mysql高级之索引结构详解

Mysql的索引详解1.索引定义2.索引结构2.1数据结构分析2.1.1熟知的数据结构2.1.2分析为什么这么多的数据结构不全适用于索引结构2.2Hash结构2.3B tree结构3.索引分类3.1聚集索引&#xff08;聚簇索引&#xff09;3.2非聚集索引&#xff08;稀疏索引&#xff09;3.3联合索引3.4主…

用于健康医疗的AI计算机视觉:使提供者能够增强患者护理

通过模仿和充当人力的力量&#xff0c;人工智能驱动的计算机视觉技术正在帮助医疗保健行业的医生、护士和企业更好地满足患者的需求。例如通过实时视频分析新见解可帮助他们节省时间、金钱和生命&#xff0c;同时为从患者入院到手术室等各个方面开发更安全、更高效的流程。用于…

java面试八股文之------Redis夺命连环25问

java面试八股文之------Redis夺命连环25问&#x1f468;‍&#x1f393;1.为什么redis这么快&#x1f468;‍&#x1f393;2.redis的应用场景&#xff0c;为什么要用&#x1f468;‍&#x1f393;3.redis6.0之前为什么一直不使用多线程&#xff0c;6.0为甚么又使用多线程了&…

Spring Cloud融合Nacos实现服务配置中心 | Spring Cloud 7

一、服务配置中心 先我们来看一下,微服务架构下关于配置文件的一些问题&#xff1a; 配置文件相对分散。在一个微服务架构下&#xff0c;配置文件会随着微服务的增多变的越来越多&#xff0c;而且分散在各个微服务中&#xff0c;不好统一配置和管理。 配置文件无法区分环境&a…

jQuery和ajax案例练习

jQuery和ajax案例练习1.使用jquery修改div元素的背景色(随意颜色)2.使用jquery修改div的子元素p的内容为"我是子元素"3.使用jquery修改第二个p元素的背景色为"orange"4.使用jQuery添加文本的方式将“添加的文本”追加到p标签的后方5.删除列表元素中最后一个…

理解依赖注入(DI – Dependency Injection)

文章目录依赖注入1.provide(提供)1.1 在选项式 API 中&#xff0c;可通过provide选项为后代提供数据1.2 如果想访问到组件的实例this&#xff0c;provide必须采用函数的方式&#xff08;不能用箭头函数&#xff09;&#xff0c;为保证注入方和供给方之间的响应性链接&#xff0…

K8s:开源安全平台 kubescape 实现 Pod 的安全合规检查/镜像漏洞扫描

写在前面 生产环境中的 k8s 集群安全不可忽略&#xff0c;即使是内网环境容器化的应用部署虽然本质上没有变化&#xff0c;始终是机器上的一个进程但是提高了安全问题的处理的复杂性分享一个开源的 k8s 集群安全合规检查/漏洞扫描 工具 kubescape博文内容涉及&#xff1a; kube…

工控机如何安装Python

钡铼技术BL302基于arm架构工控机&#xff0c;采用NXP的高性能处理器I.MX6ULL 运行速度高达800MHz&#xff0c;并配有8GFlash空间和512M RAM&#xff0c;硬件接口有2个网口、2个串口、1个USB口、1个SD卡卡槽、1个HDMI显示接口&#xff0c;可运行LINUX、Ubuntu、Debian等OS&#…

项目管理:如何做到项目信息透明?

项目管理中&#xff0c;管理者最担心的问题是什么&#xff1f;方项目进度不透明&#xff0c;项目的进展不明确&#xff0c;使用项目管理工具让项目进度一目了然。 1、项目管理必须目标明确。 明确的目标能够更好地指导接下来的一系列项目管理工作。 2、项目管理必须资源配置…

Redis缓存一致

背景介绍&#xff1a; redis是一个key-value存储系统。它支持存储的value类型相对更多&#xff0c;包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash&#xff08;哈希类型&#xff09;。这些数据类型都支持push/pop、add/remove及取交集并集和差…

2023 工业互联网平台:智慧制硅厂 Web SCADA 生产线

我国目前是全球最大的工业硅生产国、消费国和贸易国&#xff0c;且未来该产业的主要增量也将来源于我国。绿色低碳发展已成为全球大趋势和国际社会的共识&#xff0c;随着我国“双碳”目标的推进&#xff0c;光伏产业链快速发展&#xff0c;在光伏装机需求的带动下&#xff0c;…

干货分享!PCBA元器件间距的可焊性设计

“ SMT贴片加工逐步往高密度、细间距的设计发展&#xff0c;元器件的最小间距设计需考虑smt厂家的经验程度和工艺是否完善。元器件最小间距的设计除了保证smt焊盘间不易短接的安全间距外&#xff0c;还应考虑元器件的可维护性。 ” 元器件布局为什么要保证安全间距&#xff…

喜讯!华秋电子荣获第六届“高新杯”十大优秀企业奖

2月22日&#xff0c;由中科为集团与广东高科技产业商会联合举办的“第六届‘高新杯’十大优秀高新企业颁奖典礼暨2023年高新企业高质量发展春茗会”&#xff0c;在深圳隆重举行。 中科为集团自2014年举办首届“高新杯”以来&#xff0c;已主办了5届&#xff0c;共计600多家企业…

如何在CSDN上赚钱:CSDN收益渠道分析

文章目录说明哪些人&#xff0c;哪些资源赚到了钱&#xff1f;上传付费资源的策略一些经验说明 在CSDN平台上要想获得回报&#xff0c;主要的渠道&#xff1a; 付费资源付费专栏文章打赏 这三个都有门槛&#xff0c;上传付费资源需要原力等级达到Lv5&#xff0c;粉丝数500&a…

微信怎么发文字朋友圈?简单快捷的方法,只需1分钟

微信是一个非常受欢迎的聊天工具&#xff0c;也是一个交友&#xff0c;分享生活的平台。其中&#xff0c;朋友圈是最受欢迎的功能之一&#xff0c;让我们可以与好友分享自己的生活点滴&#xff0c;也能够关注他人的动态。那么微信怎么发文字朋友圈呢&#xff1f;下面跟着小编一…

tensorflow lite简介-移动设备端机器学习

TensorFlow Lite 是一组工具&#xff0c;可帮助开发者在移动设备、嵌入式设备和 loT 设备上运行模型&#xff0c;以便实现设备端机器学习。 支持多平台 支持多种平台&#xff0c;涵盖 Android 和 iOS 设备、嵌入式 Linux 和微控制器。 原理/流程 工作原理或者使用流程就是上面…