esp32cam视频流传输到yolov5遇到了问题

跟着视频学了用thonny,利用udp协议将开发版拍摄的视频通过WiFi传到pc端
需要在pc端输入ip和端口才能进行连接
但是在设计的pyqt代码中,视频流需要通过LoadWebcam(self.source, img_size=imgsz, stride=stride)进行操作,LoadWebcam()又来自官方代码中datasets.py文件,我不敢乱动,只能输入source = 0进行本地视频流输入
请问怎么通过udp协议进行视频流输入

img

对于视频流通过UDP协议传输,可以通过OpenCV库中的cv2.VideoCapture()函数实现,在调用该函数时将传输地址填入即可。

以接收端(PC端)为例,先使用socket库创建一个UDP连接。代码如下所示:

import cv2
import socket
import numpy as np

# 创建UDP连接
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', 9876))    # 填入接收端口号

# 循环接收视频流
while True:
    # 接收数据和发送端IP
    data, addr = sock.recvfrom(64000)
    # 二进制数据解码
    img = np.frombuffer(data, dtype=np.uint8)
    # 解码成图片
    img = cv2.imdecode(img, flags=cv2.IMREAD_COLOR)
    cv2.imshow('Video Stream', img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cv2.destroyAllWindows()

运行代码后即可开启PC端接受数据的服务,此时需要修改yolo代码文件(dataset.py)的视频源部分。替换0为udp协议的传输地址,代码示例如下:

def init_dataset(name):
    global img_size
    if ("coco" in name):
        dataset = COCODataset(name)
    elif ("VOC" in name) or ("pascal" in name):
        dataset = PascalVOCDataset(name)
    elif ("ytvos" in name):
        dataset = YTVOSDataset(name)
    elif re.match("^image-[0-9]+$", name) is not None:
        dataset = ImageFolderDataset(name)
    elif re.match("^video-[0-9]+$", name) is not None:
        # cap = cv2.VideoCapture(int(name[6:]))
        cap = cv2.VideoCapture('udp://<UDP传输地址>:<端口号>')
        dataset = VideoDataset(name, cap)
    else:
        ...

将代码中的UDP传输地址和端口号修改为实际使用的IP和端口号即可在本地运行yolo模型对接收到的视频流进行目标检测。

提供参考实例:https://blog.csdn.net/theArcticOcean/article/details/52658748?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-5-52658748-blog-121043693.235%5Ev36%5Epc_relevant_default_base3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-5-52658748-blog-121043693.235%5Ev36%5Epc_relevant_default_base3&utm_relevant_index=8

请采纳
要在 Thonny 中使用 UDP 协议将开发版拍摄的视频通过 WiFi 传输到 PC 端,您需要执行以下步骤:

在 Thonny 中安装 Python 的 udp 模块,以便您可以在 Python 中使用 UDP 协议。您可以使用以下命令安装 udp 模块:

pip install udp
在 Thonny 中创建一个新的 Python 文件,例如 send_video.py。

在 send_video.py 中,导入必要的模块:

import udp  
import cv2

定义一个函数,该函数将使用 UDP 协议将视频流发送到指定的 IP 地址和端口:

def send_video(ip, port, video_source):  
    # 创建 UDP socket  
    sock = udp.socket(ip, port)  
  
    # 打开视频源并获取帧  
    cap = cv2.VideoCapture(video_source)  
    while True:  
        ret, frame = cap.read()  
        if not ret:  
            break  
  
        # 编码视频帧以发送给服务器  
        encoded_frame = cv2.VideoEncodeFormat(cv2.VideoWriter_fourcc(*'mp4v'), int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(cap.get(cv2.CAP_PROP_FPS)), frame.tobytes())  
        sock.sendto(encoded_frame, (ip, port))

在 send_video.py 的主函数中,调用 send_video 函数,并将必要的参数传递给它:

if __name__ == '__main__':  
    send_video('192.168.0.100', 5000, 0)  # 将视频源设为本地摄像头(source=0)并发送到 IP 地址为 192.168.0.100、端口号为 5000 的服务器。

在 PC 端运行一个 Python 程序,该程序将接收来自 Thonny 的视频流,并将其显示在窗口中。例如,您可以使用以下代码创建一个简单的窗口,并在其中显示接收到的视频流:

import cv2  
import socket  
import threading  
  
# 创建 UDP socket 并绑定到本地端口号  
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
sock.bind(('localhost', 5000))  
  
# 创建窗口以显示视频流  
cv2.namedWindow('Video')  
cv2.resizeWindow('Video', 800, 600)  
cv2.setWindowProperty('Video', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)  
while True:  
    # 从 UDP socket 中接收数据并解码帧以显示在窗口中  
    data, addr = sock.recvfrom(1024)  
    frame = cv2.imdecode(bytearray(data), cv2.IMREAD_UNCHANGED)  
    cv2.imshow('Video', frame)  
    if cv2.waitKey(1) & 0xFF == ord('q'):  # 按 'q' 键退出循环并关闭窗口  
        break  # 关闭窗口并退出循环

将esp-32相机流式传输到YouTube等RTMP服务器
可以借鉴下
https://www.it1352.com/1681205.html

跟着视频学了用thonny,利用udp协议将开发版拍摄的视频通过WiFi传到pc端
需要在pc端输入ip和端口才能进行连接
但是在设计的pyqt代码中,视频流需要通过LoadWebcam(self.source, img_size=imgsz, stride=stride)进行操作,LoadWebcam()又来自官方代码中datasets.py文件,我不敢乱动,只能输入source = 0进行本地视频流输入
请问怎么通过udp协议进行视频流输入


```c
import cv2
import socket
 cap = cv2.VideoCapture(0)  # 打开本地摄像头
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 创建UDP客户端
ip = '192.168.1.100'  # 开发版的IP
port = 10001  # 开发版的端口
 while True:
    ret, frame = cap.read()
    # 对视频数据进行编码
    img_encode = cv2.imencode('.jpg', frame)[1]
    data = img_encode.tostring()
    # 发送UDP数据包
    sock.sendto(data, (ip, port))
 # 接收方
import cv2
import socket
from PyQt5.QtGui import QPixmap
 def receive_udp_video(ip, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 创建UDP服务端
    sock.bind((ip, port))  # 绑定IP和端口
     while True:
        # 接收UDP数据包
        data, addr = sock.recvfrom(65536)
        # 对视频数据进行解码
        img_decode = cv2.imdecode(np.fromstring(data, dtype=np.uint8), cv2.IMREAD_COLOR)
        # 向UI界面显示视频帧
        pixmap = QPixmap.fromImage(QImage(img_decode, img_decode.shape[1], img_decode.shape[0], QImage.Format_RGB888))
        self.label.setPixmap(pixmap)

```

要通过UDP协议进行视频流输入,需要进行以下步骤:

  1. 在拍摄视频的设备上,使用UDP协议将视频数据发送给PC端。

  2. 在PC端使用Python的socket模块创建一个UDP socket,指定业务端口和IP地址(由用户在界面上输入)。

  3. 在PC端,通过socket.recv()函数接收来自拍摄视频设备发送的视频数据。

  4. 将接收到的视频数据解码并显示在PC端的界面上。

下面是Python示例代码:

import cv2
import numpy as np
import socket

# 定义UDP地址和端口
UDP_IP = "192.168.0.1"
UDP_PORT = 12345

# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

# 创建窗口并加载视频流
window_name = "Video Stream"
cv2.namedWindow(window_name)

# 不断循环读取和显示视频流
while True:
    # 接收UDP数据包
    data, addr = sock.recvfrom(65536)
    # 解码数据包
    nparr = np.frombuffer(data, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
    # 显示视频流
    cv2.imshow(window_name, img)
    # 检测键盘按键
    key = cv2.waitKey(1)
    if key == ord('q'):
        break

# 释放资源
cv2.destroyAllWindows()
sock.close()

可以借鉴下

/*
网络调试助手
https://soft.3dmgame.com/down/213757.html
*/
#include <Arduino.h>
#include <WiFi.h>
#include "esp_camera.h"
#include <vector>
 
const char *ssid = "dsx_zj";
const char *password = "dsxbs725";
const IPAddress serverIP(192,168,0,2); //欲访问的地址
uint16_t serverPort = 8080;         //服务器端口号
 
#define maxcache 1430
 
WiFiClient client; //声明一个客户端对象,用于与服务器进行连接
 
//CAMERA_MODEL_AI_THINKER类型摄像头的引脚定义
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
 
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22
 
static camera_config_t camera_config = {
    .pin_pwdn = PWDN_GPIO_NUM,
    .pin_reset = RESET_GPIO_NUM,
    .pin_xclk = XCLK_GPIO_NUM,
    .pin_sscb_sda = SIOD_GPIO_NUM,
    .pin_sscb_scl = SIOC_GPIO_NUM,
    
    .pin_d7 = Y9_GPIO_NUM,
    .pin_d6 = Y8_GPIO_NUM,
    .pin_d5 = Y7_GPIO_NUM,
    .pin_d4 = Y6_GPIO_NUM,
    .pin_d3 = Y5_GPIO_NUM,
    .pin_d2 = Y4_GPIO_NUM,
    .pin_d1 = Y3_GPIO_NUM,
    .pin_d0 = Y2_GPIO_NUM,
    .pin_vsync = VSYNC_GPIO_NUM,
    .pin_href = HREF_GPIO_NUM,
    .pin_pclk = PCLK_GPIO_NUM,
    
    .xclk_freq_hz = 20000000,
    .ledc_timer = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,
    
    .pixel_format = PIXFORMAT_JPEG,
    .frame_size = FRAMESIZE_VGA,
    .jpeg_quality = 12,
    .fb_count = 1,
};
void wifi_init()
{
    WiFi.mode(WIFI_STA);
    WiFi.setSleep(false); //关闭STA模式下wifi休眠,提高响应速度
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
    Serial.println("WiFi Connected!");
    Serial.print("IP Address:");
    Serial.println(WiFi.localIP());
}
esp_err_t camera_init() {
    //initialize the camera
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.println("Camera Init Failed");
        return err;
    }
    sensor_t * s = esp_camera_sensor_get();
    //initial sensors are flipped vertically and colors are a bit saturated
    if (s->id.PID == OV2640_PID) {
    //        s->set_vflip(s, 1);//flip it back
    //        s->set_brightness(s, 1);//up the blightness just a bit
    //        s->set_contrast(s, 1);
    }
    Serial.println("Camera Init OK!");
    return ESP_OK;
}
 
void setup()
{
    Serial.begin(115200);
    wifi_init();
    camera_init();
}
 
void loop()
{
    Serial.println("Try To Connect TCP Server!");
    if (client.connect(serverIP, serverPort)) //尝试访问目标地址
    {
        Serial.println("Connect Tcp Server Success!");
        //client.println("Frame Begin");  //46 72 61 6D 65 20 42 65 67 69 6E // 0D 0A 代表换行  //向服务器发送数据
        while (1){       
          camera_fb_t * fb = esp_camera_fb_get();
          uint8_t * temp = fb->buf; //这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复
          if (!fb)
          {
              Serial.println( "Camera Capture Failed");
          }
          else
          { 
            //先发送Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送1430 余数最后发送 
            //完毕后发送结束标志 Frame Over 表示一张图片发送完毕 
            client.print("Frame Begin"); //一张图片的起始标志
            // 将图片数据分段发送
            int leng = fb->len;
            int timess = leng/maxcache;
            int extra = leng%maxcache;
            for(int j = 0;j< timess;j++)
            {
              client.write(fb->buf, maxcache); 
              for(int i =0;i< maxcache;i++)
              {
                fb->buf++;
              }
            }
            client.write(fb->buf, extra);
            client.print("Frame Over");      // 一张图片的结束标志
            Serial.print("This Frame Length:");
            Serial.print(fb->len);
            Serial.println(".Succes To Send Image For TCP!");
            //return the frame buffer back to the driver for reuse
            fb->buf = temp; //将当时保存的指针重新返还
            esp_camera_fb_return(fb);  //这一步在发送完毕后要执行,具体作用还未可知。        
          }
          delay(20);//短暂延时 增加数据传输可靠性
        }
        /*
        while (client.connected() || client.available()) //如果已连接或有收到的未读取的数据
        {
            if (client.available()) //如果有数据可读取
            {
                String line = client.readStringUntil('\n'); //读取数据到换行符
                Serial.print("ReceiveData:");
                Serial.println(line);
                client.print("--From ESP32--:Hello Server!");    
            }
        }
        Serial.println("close connect!");
        client.stop(); //关闭客户端
        */
    }
    else
    {
        Serial.println("Connect To Tcp Server Failed!After 10 Seconds Try Again!");
        client.stop(); //关闭客户端
    }
    delay(10000);
}

答案由GPT-4Plus大模型(语音)与博主@晓码自在合作编写(完整代码在最后):

根据您的描述,现在遇到的问题是:

需要通过UDP协议获取开发板发送的视频流,但是LoadWebcam()函数只接受来自本地摄像头的输入,无法直接使用UDP视频流。

解决思路如下:

  1. 理解UDP视频流的原理

UDP视频流的基本原理是:

  • 开发板将视频画面编码为图片数据,通过UDP socket发送出去。
  • PC端开启一个UDP socket,绑定位端口,等待接收开发板发送的图片数据。
  • PC端接收到图片数据后,解码并显示,通过帧率重构出视频画面。

所以关键是在PC端接收UDP socket,得到图片数据。

  1. 编写一个函数接收UDP视频流

可以编写一个函数,如receive_img_from_udp(),其基本逻辑为:

  • 建立UDP socket,绑定端口port(与开发板设置的发送端口一致)
  • 循环接收来自该端口的数据
  • 获得图片数据后,解码为OpencV格式的图片
  • 返回图片,以供后续显示

该函数的代码大致如下:

python
import socket
import cv2

def receive_img_from_udp(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('localhost', port))
    while True:
        data, addr = sock.recvfrom(4096)  # buffer size is 4096 bytes
        img = cv2.imdecode(np.fromstring(data, dtype=np.uint8), cv2.IMREAD_COLOR)
        return img

  1. 调用该函数,传入LoadWebcam()

在pyqt代码中,调用该函数获得图片,然后传入LoadWebcam()的source参数,即可显示UDP视频流:

python
img = receive_img_from_udp(port)   # 获得一帧图片
self.source = img                   # 传入LoadWebcam
LoadWebcam(self.source, ......)     # 显示视频流

完整代码加完美注释参考如下:

python
import socket
import cv2
from PyQt5.QtCore import QThread, pyqtSignal

# 定义信号,用于更新视频帧
frame_signal = pyqtSignal(np.ndarray)

# 视频流接收线程
class VideoReceive(QThread):
    def __init__(self, port):
        super().__init__()
        self.port = port

    def run(self):
        # 建立socket,监听port端口
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.bind(('127.0.0.1', self.port))  
        
        while True:
            # 不断接收数据
            data, addr = sock.recvfrom(4096)  
            
            # 将数据解码为图片
            img = cv2.imdecode(np.fromstring(data, dtype=np.uint8), cv2.IMREAD_COLOR)
            
            # 发出信号更新视频帧
            frame_signal.emit(img)   

# PyQt界面    
class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        ...
        
    def start_receive(self, port): 
        # 创建并启动视频接收线程
        self.receive_thread = VideoReceive(port)
        self.receive_thread.start()
        
        # 连接信号槽,更新视频帧
        frame_signal.connect(self.update_frame)
        
    def update_frame(self, frame):
        # 将新的视频帧显示
        self.source = frame
        self.LoadWebcam(self.source, ...)

# 程序入口            
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyApp()
    window.show()
    
    # 输入IP和端口,启动接收线程
    ip = input('Input IP: ') 
    port = int(input('Input port: '))
    window.start_receive(port)  

    sys.exit(app.exec_())

注释说明:

  1. 定义VideoReceive线程,接收UDP数据并解码为图片
  2. 定义frame_signal信号,用于发送新的视频帧
  3. VideoReceive线程接收UDP数据,发出信号更新视频帧
  4. pyqt界面连接信号槽,获得新的视频帧并显示
  5. 输入IP和端口,启动VideoReceive线程开始接收视频流
  6. 添加完整注释,详细解释代码功能与实现过程

这段代码实现了通过UDP协议获取视频流并显示在pyqt界面上的效果。

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
首先,你需要在PC端编写一个UDP server端,用于接收从ESP32-CAM发送的视频流数据,然后将这些数据传递给LoadWebcam()函数。下面是一个简单的UDP server端代码示例:

import socket
import cv2
import numpy as np

UDP_IP = "0.0.0.0" # 设置为0.0.0.0表明可以接收任意IP地址发送过来的数据
UDP_PORT = 5005    # 设置UDP端口号
sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))

img_size = (640, 480) # 设置图像大小,需要和ESP32-CAM发送的图像大小一致
stride = 32           # 设置stride,默认为32
window = 'camera'     # 设置窗口名称

while True:
    data, addr = sock.recvfrom(1024)  # 接收数据,接收缓冲区大小为1024字节
    if not data:
        break
    nparr = np.frombuffer(data, np.uint8) # 将数据转化为numpy数组
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 解码图像数据
    img = cv2.resize(img, img_size) # 将图像大小调整为设定大小
    cv2.imshow(window, img) # 将图像显示到窗口
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cv2.destroyAllWindows()

上面的代码会接收从UDP端口5005发送过来的数据,将其解码为图像数据,并显示在名为'camera'的窗口中。你可以根据需要修改图像大小。此外,如果你想要使用YoloV5进行目标检测,你需要将图像转换为适合YoloV5模型输入的格式。

在ESP32-CAM开发板中,你可以使用以下代码将摄像头拍摄的视频流通过UDP发送到PC端:

import sensor
import time
import network
import socket
import ujson
import gc

# 初始化摄像头配置,详细配置可以参考开发板官方文档
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))  # 将图像大小调整为224x224,需要和UDP server端的大小一致
sensor.set_vflip(True)
sensor.set_hmirror(True)
sensor.run(1)

# 连接WiFi网络
ssid = "Your_SSID"
key = "Your_Password"
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect(ssid, key)
while not sta_if.isconnected():
    pass
print("WiFi connected")

# 创建UDP客户端
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
dst_addr = ("Your_PC_IP", 5005) # 将PC的IP地址和UDP server的端口号作为目标地址
udp_socket.connect(dst_addr)

# 利用UDP协议传输视频流
while True:
    try:
        img = sensor.snapshot() # 拍摄一帧图像
        img_bytes = ujson.dumps(img).encode('utf-8') # 将图像数据转化为json字符串,并将其编码为utf-8格式
        udp_socket.send(img_bytes) # 发送数据
        gc.collect() # 手动垃圾回收,释放内存
    except OSError as ex:
        print(ex)

udp_socket.close() # 关闭UDP socket

上面的代码会将拍摄的图像转化为json字符串,并通过UDP协议发送到指定的IP地址和端口号。你可以根据需要修改图像大小。

最后,在你的PyQt代码中,你需要修改LoadWebcam()函数,将其接口修改为:

def LoadWebcam(self, source, img_size=(640,480), stride=0):
    self.stride = stride
    self.cap = cv2.VideoCapture(source)
    self.img_size = img_size

source参数是用来指定视频流来源,可以是本地摄像头、本地视频文件或者从网络接收的视频流。在这里,你可以传递一个空的source参数,在UDP server端中将视频流数据传递给LoadWebcam()函数即可。

如果你使用的是YoloV5预训练模型,你可以使用以下代码将图像转换为适合YoloV5输入的格式:

from PIL import Image
import numpy as np

def yolov5_input(img):
    img = Image.fromarray(img) # 将OpenCV图像转换为PIL图像
    img = img.convert('RGB') # 将图像转化为RGB模式
    img = img.resize((640, 640), Image.BICUBIC) # 将图像大小调整为640x640
    img = np.array(img).astype(np.float32) / 255.0 # 将图像数据转化为numpy数组,并将像素值缩放到[0, 1]的范围内
    img = np.transpose(img, (2, 0, 1)) # 转置图像维度,符合YoloV5模型输入的格式
    img = np.expand_dims(img, 0) # 添加一个维度,符合YoloV5模型输入的格式
    return img

将上面的代码添加到你的PyQt代码中,然后在处理视频流时,将每一帧图像传递给yolov5_input()函数,然后将返回的结果传递给YoloV5模型即可。

希望这些代码能够对你有所帮助!
如果我的回答解决了您的问题,请采纳!