You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过UDP将Python图像发送至Unreal Engine并生成Texture?

问题:UDP传输图像到Unreal Engine并转为Texture的接收处理问题

我希望通过UDP从Python脚本向Unreal Engine发送图像,在Unreal中加载并创建为Texture,但目前在接收图像环节遇到了问题。

Python UDP发送脚本

import cv2, imutils, socket
import numpy as np
import time
import base64

BUFF_SIZE = 65536
server_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF,BUFF_SIZE)
host_name = socket.gethostname()
host_ip = '127.0.0.1'#  socket.gethostbyname(host_name)
print(host_ip)
port = 54001
socket_address = (host_ip,port)
server_socket.bind(socket_address)
print('Listening at:',socket_address)

vid = cv2.VideoCapture(0) #  replace 'rocket.mp4' with 0 for webcam
fps,st,frames_to_count,cnt = (0,0,20,0)

while True:
    msg,client_addr = server_socket.recvfrom(BUFF_SIZE)
    print('GOT connection from ',client_addr)
    WIDTH=400
    while(vid.isOpened()):
        _,frame = vid.read()
        frame = imutils.resize(frame,width=WIDTH)
        encoded,buffer = cv2.imencode('.jpg',frame,[cv2.IMWRITE_JPEG_QUALITY,80])
        #message = base64.b64encode(buffer)
        server_socket.sendto(bytes(buffer),client_addr)
        frame = cv2.putText(frame,'FPS: '+str(fps),(10,40),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0,0,255),2)
        cv2.imshow('TRANSMITTING VIDEO',frame)
        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            server_socket.close()
            break
        if cnt == frames_to_count:
            try:
                fps = round(frames_to_count/(time.time()-st))
                st=time.time()
                cnt=0
            except:
                pass
        cnt+=1

Unreal Engine C++ UDP接收代码

void Audp_module::BeginPlay()
{
    Super::BeginPlay();
    SocketSubsystem = nullptr;
    if (SocketSubsystem == nullptr) SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);

    SendSize = 2 * 1024 * 1024;
    BufferSize = 2 * 1024 * 1024;
    FIPv4Address iPAdress;
    FIPv4Address::Parse("127.0.0.1",iPAdress);
    LocalEndpoint = FIPv4Endpoint(iPAdress, LocalPort);

    FIPv4Address::Parse(IP, RemoteAddress);
    RemoteEndpoint = FIPv4Endpoint(RemoteAddress, RemotePort);

    Socket = nullptr;

    if (SocketSubsystem != nullptr)
    {
        if (Socket == nullptr)
        {
            Socket = FUdpSocketBuilder(SocketDescription)
                .AsNonBlocking()
                .AsReusable()
                .BoundToEndpoint(LocalEndpoint)
                .WithReceiveBufferSize(SendSize)
                .WithReceiveBufferSize(BufferSize)
                .WithBroadcast();
        }
    }
}

void Audp_module::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
    SocketSubsystem->DestroySocket(Socket);
    Socket = nullptr;
    SocketSubsystem = nullptr;
}

void Audp_module::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    FString t = "Hello";
    sendMessage(t); // Send Message Test
    Listen(); // Listen for messages
}

void Audp_module::Listen()
{
    TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
    uint32 Size;
    if(Socket != nullptr)
    {
        while (Socket->HasPendingData(Size))
        {
            uint8* Recv = new uint8[Size];
            int32 BytesRead = 0;

            ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
            Recv = ReceivedData.GetData();
            Socket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);
            
            char ansiiData[1024];
            memcpy(ansiiData, ReceivedData.GetData(), BytesRead);
            ansiiData[BytesRead] = 0;
            
            FString data = ANSI_TO_TCHAR(ansiiData);
            //FString data = FString::FromInt(BytesRead);
            const auto encoded = FBase64::Encode(*data);
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, encoded);
            UE_LOG(LogTemp, Verbose, TEXT("Received: %s"), *encoded);
            //FString data = ANSI_TO_TCHAR(ansiiData);
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, "Message by UDP: " + data);
        }
    }
    else
    {
        Socket = FUdpSocketBuilder(SocketDescription)
                .AsNonBlocking()
                .AsReusable()
                .BoundToEndpoint(LocalEndpoint)
                .WithReceiveBufferSize(SendSize)
                .WithReceiveBufferSize(BufferSize)
                .WithBroadcast();
    }
}

问题分析与解决方案

当前接收端逻辑存在核心错误:Python实际发送的是原始JPEG二进制数据(代码中已注释base64编码),但Unreal端错误地将二进制数据当作ANSI字符串处理,还额外执行了一次base64编码,完全偏离了正确流程。以下是修正步骤:

1. 统一传输格式(二选一)

选项A:保持原始JPEG二进制传输(用当前Python代码)

无需修改Python发送逻辑,直接传输bytes(buffer)即可。

选项B:改用base64编码传输

取消Python中注释的代码,发送base64字符串:

message = base64.b64encode(buffer)
server_socket.sendto(message, client_addr)

2. 重构Unreal接收与转换逻辑

修正后的Listen函数

void Audp_module::Listen()
{
    TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
    uint32 Size;
    if(Socket != nullptr)
    {
        while (Socket->HasPendingData(Size))
        {
            ReceivedData.SetNumUninitialized(Size);
            int32 BytesRead = 0;

            // 接收原始二进制数据
            Socket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);
            
            TArray<uint8> JpegData;
            // 根据传输格式选择处理方式
            if (/* 使用base64传输 */ false)
            {
                FString Base64Str = FString(ANSI_TO_TCHAR(reinterpret_cast<const char*>(ReceivedData.GetData())));
                FBase64::Decode(Base64Str, JpegData);
            }
            else
            {
                // 直接使用原始JPEG二进制数据
                JpegData = ReceivedData;
            }

            // 转换为Texture2D
            ConvertJpegToTexture(JpegData);
        }
    }
    else
    {
        Socket = FUdpSocketBuilder(SocketDescription)
                .AsNonBlocking()
                .AsReusable()
                .BoundToEndpoint(LocalEndpoint)
                .WithReceiveBufferSize(SendSize)
                .WithReceiveBufferSize(BufferSize)
                .WithBroadcast();
    }
}

添加JPEG转Texture2D的工具函数

void Audp_module::ConvertJpegToTexture(const TArray<uint8>& JpegData)
{
    if (JpegData.Num() == 0) return;

    // 加载JPEG为图像数据
    TSharedPtr<FImage> Image = MakeShareable(new FImage());
    if (!FImageUtils::LoadImageFromRawData(Image, JpegData.GetData(), JpegData.Num()))
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Failed to load JPEG data"));
        return;
    }

    // 创建临时Texture2D
    UTexture2D* Texture = UTexture2D::CreateTransient(Image->SizeX, Image->SizeY, PF_B8G8R8A8);
    if (!Texture) return;

    // 复制图像数据到纹理内存
    void* TextureData = Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
    FMemory::Memcpy(TextureData, Image->GetData(), Image->GetDataSize());
    Texture->PlatformData->Mips[0].BulkData.Unlock();

    // 更新纹理资源
    Texture->UpdateResource();

    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Texture created successfully"));
    // 此处可将Texture赋值给材质、UI等
    // Example: YourMaterialInstance->SetTextureParameterValue("InputTexture", Texture);
}

3. 额外注意事项

  • UDP单包最大传输限制为64KB左右,若图像过大需在Python端分片,Unreal端重组
  • 确保Unreal的Socket接收缓冲区足够大,避免丢包
  • 测试时先单帧传输验证流程,再扩展到视频流

内容的提问来源于stack exchange,提问作者potu1304

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:05:58