1. 功能说明
在样机舵机云台上安装一个摄像头,本文示例将实现舵机云台追踪球形物体的功能。
2. 电子硬件
在这个示例中,我们采用了以下硬件,请大家参考:
主控板 | Basra主控板(兼容Arduino Uno) |
扩展板 | Bigfish2.1扩展板 |
电池 | 7.4V锂电池 |
通信 | 2510通信转接板 |
WiFi路由器 | |
其它 | 摄像头 |
配置OpenCV的Visual Studio 2015.net环境 的计算机一台 |
电路连接说明:
① 将2510通信转接板连接到Bigfish扩展板的扩展坞上面;
② 用3根母对母杜邦线将2510通信转接板与WiFi路由器连接起来:GND-GND、RX-RX、TX-TX;
③ 找到1根USB线,一端连接到2510通信转接板接口上,另一端连接到WiFi路由器USB接口上;
④ 将摄像头线连接到WiFi路由器接口上。
3. 功能实现
实现思路:实现舵机云台追踪蓝色小球。
3.1 工作原理
① 摄像头采集图像信息;
② 通过WiFi将图像信息传递给PC端(VS2015配置的OpenCV环境);
③ 在PC端使用OpenCV对图像转化为灰度图像;
④ 检测圆形,并且计算出圆中心坐标;
⑤ 采用九宫格方式对摄像显示图像进行分割;
⑥ 确定目标物体在显示图像的所处九宫格位置;
⑦ 如果目标图像超出九宫格位置的中心,调整摄像头矫正偏移使目标物体在屏幕中心位置;
⑧ 调整摄像头需要上位机通过WiFi给下位机发送矫正指令,下位机需要接收信号,并且让安装了摄像头的舵机云台做出相应的矫正动作。
3.2 示例程序
编程环境:Arduino 1.8.19
① 下位机例程
将参考例程(example.ino)下载到主控板,开启路由器,将路由器与主控板TX、Rx串口进行连接,同时将PC连接至路由器WIFI网络。下位机接收上位机处理的图像信息结果控制舵机云台相应运动,云台跟随目标物体运动。
/*------------------------------------------------------------------------------------
版权说明: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-04-24 https://www.robotway.com/
------------------------------*/
#include <Servo.h>
#define BOTTOM_SERVO_MIN 10
#define BOTTOM_SERVO_MAX 175
#define TOP_SERVO_MIN 85
#define TOP_SERVO_MAX 175
const String CMD_UP = "U";
const String CMD_DOWN = "D";
const String CMD_LEFT = "L";
const String CMD_RIGHT = "R";
Servo myServo[2];
int servo_value[2] = {85, 105};
int port0 = 4;
int port1 = 7;
int servo_move_angle = 1;
void setup()
{
Serial.begin(9600);
ServoInit();
delay(1000);
}
void loop()
{
String s = SerialRead();
if(s != "")
{
if(s == CMD_UP)
ServoAdjust(port1, -servo_move_angle);
else if(s == CMD_DOWN)
ServoAdjust(port1, servo_move_angle);
else if(s == CMD_LEFT)
ServoAdjust(port0, -servo_move_angle);
else if(s == CMD_RIGHT)
ServoAdjust(port0, servo_move_angle);
}
}
String SerialRead()
{
String str = "";
while(Serial.available())
{
str += char(Serial.read());
}
return str;
}
void ServoInit()
{
myServo[0].attach(port0);
myServo[1].attach(port1);
for(int i=0;i<2;i++)
{
myServo[i].write(servo_value[i]);
}
}
void ServoAdjust(int which, int where)
{
switch(which)
{
case 4:
servo_value[0] += where;
if(servo_value[0] <= BOTTOM_SERVO_MIN)
servo_value[0] = BOTTOM_SERVO_MIN;
else if(servo_value[0] >= BOTTOM_SERVO_MAX)
servo_value[0] = BOTTOM_SERVO_MAX;
myServo[0].write(servo_value[0]);
break;
case 7:
servo_value[1] += where;
if(servo_value[1] <= TOP_SERVO_MIN)
servo_value[1] = TOP_SERVO_MIN;
else if(servo_value[1] >= TOP_SERVO_MAX)
servo_value[1] = TOP_SERVO_MAX;
myServo[1].write(servo_value[1]);
break;
default:
break;
}
}
② 上位机例程
下面提供一个可以实现舵机云台追踪球形物体的参考例程(MainWindow.xaml.cs),大家可参考演示视频完成该实验。
using System;
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.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Net;
using System.Net.Sockets;
namespace Project
{
/// <summary>
/// 形状跟踪,球
/// </summary>
public partial class MainWindow : Window
{
//导入 HoughCircles.dll 动态链接库
[DllImport("HoughCircles_DLL")]
public static extern void HoughCircles([MarshalAs(UnmanagedType.LPStr)]string ip_address, //视频地址
ref int xpos, //跟踪物体中心X坐标
ref int ypos); //跟踪物体中心Y坐标
//定义窗口大小
int cap_w = 320, cap_h = 240;
//跟踪物体中心 x, y 坐标值,物体轮廓半径r
int x = 0, y = 0;
//定义命令变量
string CMD_UP = "", CMD_DOWN = "", CMD_TURN_LEFT = "", CMD_TURN_RIGHT = "";
//结构体
public struct Boundaries
{
public int x_left;
public int x_right;
public int y_up;
public int y_down;
}
Boundaries boundaries = new Boundaries();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
GetIni();
SetPosition();
CmdInit();
StructInit();
}
//变量初始化
private void CmdInit()
{
CMD_UP = "U";
CMD_DOWN = "D";
CMD_TURN_LEFT = "L";
CMD_TURN_RIGHT = "R";
}
//结构体初始化
private void StructInit()
{
boundaries.x_left = 100;
boundaries.x_right = 200;
boundaries.y_up = 80;
boundaries.y_down = 160;
}
//获取ini配置文件信息
private void GetIni()
{
ini_RW.FileName = System.Windows.Forms.Application.StartupPath + "\\Config.ini";
this.videoAddress.Text = ini_RW.ReadIni("VideoUrl", "videourl", "");
this.ipAddress.Text = ini_RW.ReadIni("ControlUrl", "controlUrl", "");
this.portBox.Text = ini_RW.ReadIni("ControlPort", "controlPort", "");
}
//修改配置
private void setBtn_Click(object sender, RoutedEventArgs e)
{
ini_RW.WriteIni("VideoUrl", "videourl", this.videoAddress.Text);
ini_RW.WriteIni("ControlUrl", "controlUrl", this.ipAddress.Text);
ini_RW.WriteIni("ControlPort", "controlPort", this.portBox.Text);
System.Windows.MessageBox.Show("配置成功!请重启程序以使配置生效。", "配置信息", MessageBoxButton.OK, MessageBoxImage.Information);
//this.Close();
}
//命令发送函数
void SendData(string data)
{
try
{
IPAddress ips = IPAddress.Parse(ipAddress.Text.ToString());//("192.168.8.1");
IPEndPoint ipe = new IPEndPoint(ips, Convert.ToInt32(portBox.Text.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)
{
System.Windows.Forms.MessageBox.Show(e.Message);
}
}
//跟踪物体位置界限判断
private void LineDetect(int _x, int _y)
{
if (_x > 0 && _x <= boundaries.x_left)
{
SendData(CMD_TURN_LEFT);
}
else if (x > boundaries.x_right && x < cap_w)
{
SendData(CMD_TURN_RIGHT);
}
else if (_y > 0 && _y <= boundaries.y_up)
{
SendData(CMD_DOWN);
}
else if (_y > boundaries.y_down && _y < cap_h)
{
SendData(CMD_UP);
}
}
//物体位置初始化#FFACAAAA
private void SetPosition()
{
var color = new SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#00008B"));
objEllipse.Height = 30;
objEllipse.Width = 30;
objEllipse.Fill = color;
var left_distance = (cap_w - objEllipse.Width) / 2;
var top_distance = (cap_h - objEllipse.Height) / 2;
Canvas.SetLeft(objEllipse, left_distance);
Canvas.SetTop(objEllipse, top_distance);
}
//跟踪物体位置更新函数
private void PositionUpdate(int x, int y)
{
LineDetect(x, y);
Canvas.SetLeft(objEllipse, x);
Canvas.SetTop(objEllipse, y);
posLable.Content = x + " , " + y;
}
//线程函数
private void ThreadCapShow()
{
try
{
while (true)
{
this.Dispatcher.Invoke(
new Action(
delegate
{
string ip = this.videoAddress.Text;
HoughCircles(ip, ref x, ref y);
PositionUpdate(x - 15, y - 15);
}
));
}
}
catch { };
}
//打开跟踪窗口
private void openBtn_Click(object sender, RoutedEventArgs e)
{
try
{
Thread m_thread = new Thread(ThreadCapShow);
m_thread.IsBackground = true;
m_thread.Start();
}
catch { };
}
}
}
例程源代码、样机3D文件资料内容详见 舵机云台-追踪球形目标