本帖最后由 机器谱 于 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)如下:
- /*------------------------------------------------------------------------------------
- 版权说明: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. 资料下载 资料内容:
① 视觉循迹-程序源代码
② 视觉循迹-样机3D文件 资料下载地址:https://www.robotway.com/h-col-113.html
想了解更多机器人开源项目资料请关注 机器谱网站 https://www.robotway.com
|