概要
我们一般使用yolo检测之后会得到目标的目标框坐标,检测是比较方便的,但用于工程的话,检测肯定是不够的,需要结合对象进行运动,这就需要传输信息了。今天记录使用socket将YOLOv5检测到的坐标信息传输出去。
我需要做一个云台的视觉控制程序,目前的方案是yolov5检测输出坐标信息,使用socket发送到客户端后,再打包成字节发送出去,发送时使用struct包发送给串口即可,也可在发送前处理数据,比如云台的话是将检测到的目标框中心坐标信息跟图像中心做比较,这个误差输入pid进行修正稳定,之后转换数据格式为二进制字节,变为电机可接收的数据,即可通过串口传出去。实现云台转动。主要难点在于:
1、socket传输坐标信息。2、数据传输要匹配电机通讯协议。3、pid调参。
1. Socket介绍
Socket是一种通信模式,它提供了一种标准化的接口,允许应用程序通过网络进行通信。Socket是网络通信的基本构建块,它封装了网络协议的复杂性,使得应用程序可以方便地进行数据传输。
发送条件: 在Socket通信中,每台计算机都有一个唯一的IP地址,而每个正在运行的网络应用程序都需要绑定到一个特定的端口号。IP地址用于标识计算机,端口号用于标识应用程序。
通信协议: Socket通信可以基于不同的网络协议,如TCP(传输控制协议)和UDP(用户数据报协议)。TCP提供了可靠的、面向连接的通信,而UDP提供了不可靠的、面向无连接的通信。
关于TCP:
主要是需要三次握手。自行百度,面试常考。大概可理解为建立连接需要至少三次握手,发送数据需要校验。
使用TCP协议的套接字,提供可靠的、面向连接的通信。流套接字通过建立连接,实现了数据的有序传输和错误检测,适用于需要可靠性的应用程序,如文件传输、HTTP等。
关于UDP:
使用UDP协议的套接字,提供不可靠的、面向无连接的通信。数据报套接字将数据分割成小的数据包(数据报)进行传输,适用于实时性要求高的应用程序,如音频、视频流等。
2. Socket编程的基本步骤:
Socket编程通常包括以下步骤:
- 创建Socket:使用socket()函数创建一个Socket对象,指定通信协议(TCP或UDP)和套接字类型(流套接字或数据报套接字)。
- 绑定地址和端口:如果是服务器,需要将Socket绑定到一个特定的IP地址和端口号上,以便监听来自客户端的连接请求。
- 建立连接:如果是客户端,可以使用connect()方法连接到远程服务器。如果是服务器,使用listen()方法监听客户端的连接请求,一旦有连接请求,使用accept()方法接受连接。
- 数据传输:通过Socket对象的send()和recv()方法进行数据的发送和接收。在TCP中,数据传输是可靠的;在UDP中,数据传输是不可靠的。
- 关闭连接:通信完成后,使用close()方法关闭Socket连接,释放资源。
3. 服务端发送检测坐标:
主要是将检测的中心点坐标发送出去,然后客户端再接收,接收后处理数据,之后通过struct发送给串口。
现在yolo检测后会有坐标信息。可以处理为中心点坐标,一般是左上角x坐标加宽的一半,左上角y坐标加高的一半。
我的代码是跟踪的,所以已经处理好了中心点,所以下面演示中心店如何通过socket发送出去。
在输出中心点xy之后,即可发送,使用多线程发送,需要提前导入ThreadPoolExecutor,并提前创建好线程。
print(f"Center points: ", x_center,y_center)
# 在线程池中提交发送XY坐标的任务*/
thread_pool.submit(send_xy, x_center, y_center) # 传递 x 和 y 参数
提前导入并设置好线程。
from concurrent.futures import ThreadPoolExecutor # 导入 ThreadPoolExecutor
# 创建一个线程池,最多可以同时运行4个线程
thread_pool = ThreadPoolExecutor(max_workers=4)
提交坐标的任务中有涉及一个send_xy,提前定义好,这个部分主要负责发送信息到这个地址上。地址写客户接收端的地址,端口号相同就行,随便写,但一次只能用一个。
# 定义一个函数,用于在单独的线程中发送XY坐标
def send_xy(x, y):
# 创建一个新的套接字并连接到目标 IP 和端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
target_ip = '192.168.1.131'
target_port = 456
s.connect((target_ip, target_port))
# 发送坐标数据
data = f"{x},{y}".encode()
s.send(data)
s.close()
这就是我发送的数据,红色框。
4. 客户端接收检测坐标:
代码同样使用了多线程,而且,接收数据支持小数 。
接收端注意要在局域网中,那种需要账号密码验证的公开网没用,普通局域网都可以用。如果你的服务端和客户端都在一台电脑上也可以的,只是注意ip写成本机ip就行。
import socket
import threading
import re
# 定义处理连接的函数
def handle_connection(conn):
try:
while True:
data = conn.recv(4096) # 接收数据(最大字节)
if not data:
break # 如果没有数据,跳出循环
message = data.decode() # 解码收到的数据
# print("接收到的数据:", message)
# 使用正则表达式解析数据(匹配包含小数的数字)
match = re.match(r'(\d+\.\d+),(\d+\.\d+)', message)
if match:
X = float(match.group(1))
Y = float(match.group(2))
print('X:', X, 'Y:', Y,)
except Exception as e:
print("处理连接时发生错误:", e)
finally:
conn.close() # 关闭连接
# 创建Socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 指定IP地址和端口号
host = '192.168.1.133' # 监听所有接口
port = 456
# 绑定Socket到地址和端口
server_socket.bind((host, port))
# 开始监听连接
server_socket.listen(1)
print("等待连接...")
try:
while True:
# 等待连接请求
conn, addr = server_socket.accept()
# print('连接来自:', addr)
# 创建并启动新线程处理连接
thread = threading.Thread(target=handle_connection, args=(conn,))
thread.start()
except KeyboardInterrupt:
print('手动关闭连接')
server_socket.close()
这是我接收到的地址,暂时没有处理数据,也没有添加串口发送。后面的处理数据和串口发送,以及pid调节才是重头戏。
小结
暂时实现了使用socket将yolov5检测到的坐标信息发送出去,并在另一台客户端可接收,且在一个局域网内实时接收。