极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 2300|回复: 0

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

[复制链接]
发表于 2023-3-6 09:42:12 | 显示全部楼层 |阅读模式
本帖最后由 机器谱 于 2023-3-6 09:42 编辑

1. 任务描述
       在机器人小车上搭载摄像头,摄像头采集图像信息并通过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)如下:
  1. /*------------------------------------------------------------------------------------

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

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

  4.            https://opensource.org/licenses/MIT

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

  6.   -----------------------------------------------------------------------------------

  7. /*

  8.   wift car:

  9.   2019/08/19:

  10.   JN

  11.   left: 9, 5;

  12.   right: 10, 6;  

  13. */


  14. const String FORWARD = "F";

  15. const String BACK = "B";

  16. const String LEFT = "L";

  17. const String RIGHT = "R";

  18. const String STOP = "S";


  19. int speed_left = 41;

  20. int speed_right = 41;


  21. void setup() {

  22.   Serial.begin(9600);

  23.   pinMode(5, OUTPUT);

  24.   pinMode(6, OUTPUT);







  25.   pinMode(9, OUTPUT);

  26.   pinMode(10, OUTPUT);

  27.   Stop();

  28.   delay(1000);

  29. }


  30. void loop() {

  31.   String data = SerialRead();



  32.   //if(data != ""){   

  33.     if(data == FORWARD)

  34.       Forward();

  35.     else if(data == BACK)

  36.       Back();

  37.     else if(data == LEFT)

  38.       Left();

  39.     else if(data == RIGHT)

  40.       Right();   

  41.     else if(data == STOP)

  42.       Stop();

  43. // }

  44. }


  45. String SerialRead(){

  46.   String str;

  47.   while(Serial.available()){

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

  49.   }

  50.   return str;

  51. }


  52. void Forward(){

  53.   analogWrite(9, speed_left);

  54.   analogWrite(5, 0);

  55.   analogWrite(6, 0);

  56.   analogWrite(10, speed_right);

  57. }


  58. void Back(){

  59.   analogWrite(9, 0);

  60.   analogWrite(5, speed_left);

  61.   analogWrite(6, speed_right);

  62.   analogWrite(10, 0);

  63. }


  64. void Left(){

  65.   analogWrite(9, 0);

  66.   analogWrite(5, speed_left);

  67.   analogWrite(6, 0);

  68.   analogWrite(10, speed_right);

  69. }


  70. void Right(){

  71.   analogWrite(9, speed_left);

  72.   analogWrite(5, 0);

  73.   analogWrite(6, speed_right);

  74.   analogWrite(10, 0);

  75. }


  76. void Stop(){

  77.   analogWrite(9, speed_left);

  78.   analogWrite(5, speed_left);

  79.   analogWrite(6, speed_right);

  80.   analogWrite(10,speed_right);

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

  1. /*******************************************************************************************

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

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

  4.            https://opensource.org/licenses/MIT

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

  6. ---------------------------------------------------------------------------------------

  7. using System;

  8. using System.IO;

  9. using System.Collections.Generic;

  10. using System.Linq;

  11. using System.Text;

  12. using System.Threading.Tasks;

  13. using System.Windows;

  14. using System.Windows.Controls;

  15. using System.Windows.Data;

  16. using System.Windows.Documents;

  17. using System.Windows.Input;

  18. using System.Windows.Media;

  19. using System.Windows.Media.Imaging;

  20. using System.Windows.Navigation;

  21. using System.Windows.Shapes;

  22. using System.Windows.Media.Animation;

  23. using System.Threading;

  24. using OpenCvSharp;

  25. using System.Drawing;

  26. using System.Drawing.Imaging;

  27. using System.Net;

  28. using System.Net.Sockets;



  29. namespace Tracking_Car

  30. {

  31.     /// <summary>

  32.     /// Tracking_Car

  33.     /// </summary>

  34.     public partial class MainWindow : System.Windows.Window

  35.     {

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

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

  38.         static string ControlIp = "192.168.8.1";

  39.         static string Port = "2001";



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

  41.         //定义命令变量

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



  43.         /*

  44.          * 指针角度对应各颜色

  45.          * 25 -> 红色

  46.          * 90 -> 绿色

  47.          * 150 -> 蓝色

  48.          */

  49.         int ANGLE_LEFT = 0;

  50.         int ANGLE_GO = 0;

  51.         int ANGLE_RIGHT = 0;



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

  53.         double numOfleft = 0.0;

  54.         double numOfright = 0.0;



  55.         //创建视频图像实例

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

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

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



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

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



  61.         //图像中心线坐标

  62.         int x1, y1, x2, y2;

  63.         //窗口面积

  64.         float area;



  65.         //视频显示切换变量

  66.         Boolean isChange = false;

  67.         //循迹开始开关变量

  68.         Boolean isBegin = false;



  69.         public MainWindow()

  70.         {

  71.             InitializeComponent();

  72.         }



  73.         private void Window_Loaded(object sender, RoutedEventArgs e)

  74.         {

  75.             Assignment();

  76.         }



  77.         //变量赋值函数

  78.         private void Assignment()

  79.         {

  80.             ANGLE_LEFT = 25;

  81.             ANGLE_GO = 90;

  82.             ANGLE_RIGHT = 150;



  83.             rateLeft.Height = 10;

  84.             rateRight.Height = 10;



  85.             x1 = 80;

  86.             y1 = 0;

  87.             x2 = x1;

  88.             y2 = 120;

  89.             area = 160 * 120 / 2;



  90.             CMD_FORWARD = "F";

  91.             CMD_TURN_LEFT = "L";

  92.             CMD_TURN_RIGHT = "R";

  93.             CMD_STOP = "S";

  94.         }



  95.         /// <summary>

  96.         /// MatToBitmap(Mat image)

  97.         /// </summary>

  98.         public static Bitmap MatToBitmap(Mat image)

  99.         {

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

  101.         }



  102.         /// <summary>

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

  104.         /// </summary>

  105.         public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)

  106.         {

  107.             using (MemoryStream stream = new MemoryStream())

  108.             {

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



  110.                 stream.Position = 0;

  111.                 BitmapImage result = new BitmapImage();

  112.                 result.BeginInit();

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

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

  115.                 result.CacheOption = BitmapCacheOption.OnLoad;

  116.                 result.StreamSource = stream;

  117.                 result.EndInit();

  118.                 result.Freeze();

  119.                 return result;

  120.             }

  121.         }



  122.         //颜色指示动画函数

  123.         int angelCurrent = 0;

  124.         private void ColorIndicate(int where)

  125.         {

  126.             RotateTransform rt = new RotateTransform();

  127.             rt.CenterX = 130;

  128.             rt.CenterY = 200;



  129.             this.indicatorPin.RenderTransform = rt;



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

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

  132.             da.AccelerationRatio = 0.8;

  133.             rt.BeginAnimation(RotateTransform.AngleProperty, da);



  134.             switch (where)

  135.             {

  136.                 case 25:

  137.                     dirDisplay.Content = "左转";

  138.                     break;

  139.                 case 90:

  140.                     dirDisplay.Content = "前进";

  141.                     break;

  142.                 case 150:

  143.                     dirDisplay.Content = "右转";

  144.                     break;

  145.                 default:

  146.                     dirDisplay.Content = "方向指示";

  147.                     break;

  148.             }



  149.             angelCurrent = where;

  150.         }



  151.         //检测函数

  152.         private void ColorDetect() {

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

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

  155.             //进行高斯滤波

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

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

  158.             Cv2.Dilate(binary, binary, null);

  159.             Cv2.Erode(binary, binary, kernel);



  160.             result = binary.Clone();

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



  162.             float rateOfleft = 0, rateOfRight = 0;

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

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

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

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

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

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



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

  170.                         if (j <= x1) {

  171.                             numOfleft++;

  172.                         }

  173.                         else

  174.                         {

  175.                             numOfright++;

  176.                         }

  177.                     }

  178.                 }

  179.             }



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

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

  182.             rateLeft.Height = rateOfleft;

  183.             rateRight.Height = rateOfRight;

  184.             numOfleft = 0;

  185.             numOfright = 0;

  186.         }



  187.         //命令发送函数

  188.         void SendData(string data)

  189.         {

  190.             try

  191.             {

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

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

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



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



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

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

  198.                 c.Close();

  199.             }

  200.             catch (Exception e)

  201.             {

  202.                 MessageBox.Show(e.Message);

  203.             }

  204.         }



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

  206.         private void CommandSend() {

  207.             double l = rateLeft.Height;

  208.             double r = rateRight.Height;



  209.             if (isBegin) {

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

  211.                 {

  212.                     ColorIndicate(ANGLE_GO);

  213.                     SendData(CMD_FORWARD);

  214.                 }

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

  216.                 {

  217.                     ColorIndicate(ANGLE_RIGHT);

  218.                     SendData(CMD_TURN_RIGHT);



  219.                 }

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

  221.                 {

  222.                     ColorIndicate(ANGLE_LEFT);

  223.                     SendData(CMD_TURN_LEFT);

  224.                 }

  225.             }

  226.         }



  227.         //视频显示函数

  228.         private void ThreadCapShow()

  229.         {



  230.             while (true)

  231.             {

  232.                 try

  233.                 {

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

  235.                     if (frame.Empty())

  236.                         break;



  237.                     this.Dispatcher.Invoke(

  238.                         new Action(

  239.                             delegate

  240.                             {

  241.                                 if (isChange)

  242.                                 {

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

  244.                                     ColorDetect();

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

  246.                                     CommandSend();

  247.                                     result = null;

  248.                                 }

  249.                                 else

  250.                                 {

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

  252.                                 }

  253.                             }

  254.                             ));

  255.                     //Cv2.WaitKey(100);

  256.                     //bitimg = null;

  257.                 }

  258.                 catch { }

  259.             }

  260.         }



  261.         //加载视频

  262.         private void loadBtn_Click(object sender, RoutedEventArgs e)

  263.         {

  264.             if (originImage.Source != null) return;

  265.             Thread m_thread = new Thread(ThreadCapShow);

  266.             m_thread.IsBackground = true;

  267.             m_thread.Start();

  268.         }



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

  270.         private void changeBtn_Click(object sender, RoutedEventArgs e)

  271.         {

  272.             if (!isChange)

  273.             {

  274.                 isChange = true;

  275.                 changeBtn.Content = "返回";

  276.             }

  277.             else

  278.             {

  279.                 isChange = false;

  280.                 changeBtn.Content = "切换";

  281.                 //指针角度归零

  282.                 ColorIndicate(0);

  283.                 rateLeft.Height = 10;

  284.                 rateRight.Height = 10;

  285.                 result = null;

  286.             }

  287.         }

  288.         //循迹开始

  289.         private void bgeinBtn_Click(object sender, RoutedEventArgs e)

  290.         {

  291.             isBegin = true;

  292.         }

  293.         //循迹停止

  294.         private void stopBtn_Click(object sender, RoutedEventArgs e)

  295.         {

  296.             isBegin = false;

  297.             SendData(CMD_STOP);

  298.          }

  299.     }

  300. }
复制代码

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

想了解更多机器人开源项目资料请关注 机器谱网站 https://www.robotway.com



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-24 03:44 , Processed in 0.047063 second(s), 18 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表