0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

jf_72402704 来源:jf_72402704 作者:jf_72402704 2023-03-03 17:05 次阅读

1. 任务描述

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

pYYBAGQBuqyAKr0iAAAnlrKhp_M591.jpg

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路由器接口上。

poYBAGQBuqyANV06AAAxAGkw5eM599.jpgpYYBAGQBuq2AUsRrAAB42LtiKvQ675.jpg

3.2示例程序

编程环境:Arduino 1.8.19

① 下位机例程:

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

参考例程代码(car.ino)如下:【详细例程源代码详见https://www.robotway.com/h-col-113.html】

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

  版权说明: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

{

    /// 

    /// Tracking_Car

    /// 

    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";

        }



        /// 

        /// MatToBitmap(Mat image)

        /// 

        public static Bitmap MatToBitmap(Mat image)

        {

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

        }



        /// 

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

        /// 

        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();

            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. 资料内容


​①视觉循迹-程序源代码
​②视觉循迹-样机3D文件

审核编辑黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 机器人
    +关注

    关注

    210

    文章

    28201

    浏览量

    206519
  • 编程
    +关注

    关注

    88

    文章

    3592

    浏览量

    93594
收藏 人收藏

    评论

    相关推荐

    小型平行履带底盘的制作分享

    小型平行履带底盘可以通过两侧履带的 运动 来实现前进、后退、原地转向、大半径转向等基本行驶功能
    的头像 发表于 01-12 14:50 3079次阅读
    <b class='flag-5'>小型</b>平行履带<b class='flag-5'>底盘</b>的制作分享

    第13章-循迹功能 STM32智能小车循迹教程 PID循迹算法分析

    视觉控制、电磁循迹、RTOS等功能。 讲解一下我们小车里面的循迹部分,包括红外基础使用,无PID循迹和有PID
    的头像 发表于 08-21 16:27 1122次阅读
    第13章-<b class='flag-5'>循迹</b><b class='flag-5'>功能</b> STM32智能小车<b class='flag-5'>循迹</b>教程 PID<b class='flag-5'>循迹</b>算法分析

    如何搭建实体机器人ros底盘

    目录介绍一、底盘主控板二、嵌入式开发板1. 与上位机pc的关系2. 与STM32主控板的关系介绍自下而上的分析实体机器人(分轮机器人)搭建中的关键过程。一、底盘主控板本部分搭建实体
    发表于 01-20 07:36

    底盘的设计资料分享

    SLAM+语音机器人DIY系列:(四)底盘设计——1.stm32主控硬件设计摘要 运动底盘是移动机器人的重要组成部分,不像激光雷达、IMU、麦克风、音响、摄像头这些通用部件可以直接买到,很难
    发表于 02-14 06:29

    使用以双灰度传感器实现的曲线循迹以及矩形循迹的资料说明

    在这次课程中,我们小组选定的课题是循迹小车,在长达数周的辛苦付出中,我组小车已能实现功能有1.红遥控小车2.以灰度传感器实现矩形循迹3.以
    发表于 12-23 08:00 12次下载
    使用以双灰度传感器<b class='flag-5'>实现</b>的曲线<b class='flag-5'>循迹</b>以及矩形<b class='flag-5'>循迹</b>的资料说明

    小型双节履带底盘的制作

    1.运动功能说明 双节履带车可以通过两个驱动轮的运动来实现前进、后退、原地转向、大半径转向等基本行驶功能,并可通过舵机关节模块进行小臂的
    的头像 发表于 01-07 10:56 813次阅读
    <b class='flag-5'>小型</b>双节履带<b class='flag-5'>底盘</b>的制作

    小型双轮底盘的制作

    小型双轮底盘可以通过两个驱动轮的 运动 来
    的头像 发表于 01-13 15:17 2812次阅读
    <b class='flag-5'>小型</b><b class='flag-5'>双轮</b><b class='flag-5'>差</b><b class='flag-5'>速</b><b class='flag-5'>底盘</b>的制作

    小型三节履带底盘的制作

    双节履带车可以通过两个驱动轮的运动来实现前进、后退、原地转向、大半径转向等基本行驶功能,并可通过舵机关节模块进行小臂的抬起和落下。通过底盘
    的头像 发表于 01-29 09:15 1146次阅读
    <b class='flag-5'>小型</b>三节履带<b class='flag-5'>底盘</b>的制作

    如何实现双轮底盘躲避悬崖的功能

    1. 功能说明        本实验使用的样机为R023样机小型双轮底盘。在样机前方安装3个
    的头像 发表于 03-03 17:08 366次阅读
    如何<b class='flag-5'>实现</b><b class='flag-5'>双轮</b><b class='flag-5'>差</b><b class='flag-5'>速</b><b class='flag-5'>底盘</b>躲避悬崖的<b class='flag-5'>功能</b>?

    如何让小型双轮底盘实现双灰度循迹功能?

    1. 功能说明    在机器人车体上安装2个 灰度传感器 ,实现机器人按照下图所指定的路线进行导航运动,来模拟仓库物流机器人按指定路线行进的工作过程。   2. 使用样机     本实验使用的样机为
    的头像 发表于 03-03 17:14 445次阅读
    如何让<b class='flag-5'>小型</b><b class='flag-5'>双轮</b><b class='flag-5'>差</b><b class='flag-5'>速</b><b class='flag-5'>底盘</b><b class='flag-5'>实现</b>双灰度<b class='flag-5'>循迹</b><b class='flag-5'>功能</b>?

    双轮底盘三灰度循迹功能实现

    1. 功能说明     在小型双轮底盘样机前方安装3个 灰度传感器 ,
    的头像 发表于 03-03 17:29 712次阅读
    <b class='flag-5'>双轮</b><b class='flag-5'>差</b><b class='flag-5'>速</b><b class='flag-5'>底盘</b>三灰度<b class='flag-5'>循迹</b><b class='flag-5'>功能</b>的<b class='flag-5'>实现</b>

    小型平行履带底盘实现双灰度循迹功能

    1. 功能说明    在小型平行履带底盘样机上安装2个 灰度传感器 ,实现机器人沿指定路线进行运动的效果。 2. 使用样机    本实验使用的样机为R026a样机。 3.
    的头像 发表于 03-09 08:52 408次阅读
    <b class='flag-5'>小型</b>平行履带<b class='flag-5'>底盘</b><b class='flag-5'>实现</b>双灰度<b class='flag-5'>循迹</b><b class='flag-5'>功能</b>

    电机与无刷电机的区别是什么 电机和无刷电机哪个好

    电机与无刷电机的区别是什么?结构上,无刷电机和电机有相似之处,也有转子和定子,只不过和
    发表于 03-27 15:07 1.1w次阅读

    DGT-01M四轮移动机器人底盘

    DGT-01M 机器人底盘是一款多功能行业应用的实用新型产品,是一种四轮运载平台,采用四轮结构,原地转向,体型小,灵活性强,并且采用模块化设计,具有相对独立性、互换性、通用性,模块
    发表于 12-23 09:37 2次下载

    第19.2章-星曈科技 openmv H7 plus openmv视觉循迹功能-完成视觉识别指定区域 OpenMV视觉模块循迹 STM32F103C8T6视觉巡线

    第19.2章-【星曈科技】openmv H7 plus openmv视觉循迹功能-完成视觉识别指定区域 OpenMV视觉模块
    的头像 发表于 08-20 11:28 446次阅读