Unreal 에서 Opencv를 이용한 webcam 화면 불러오기

2023. 8. 23. 17:37unreal engine

구우우우우우욷이 언리얼에서 기본으로 제공하는 웹캠 연결자가 있음에도 opencv를 쓰는 이유는

 

이후에 이미지에 postprocessing을 하기 위해서는 opencv가 훠어어어얼씬 편하기 때문이다...

 

 

 

#1. opencv 깔기

 

이미 언리얼에서는 opencv를 플러그인으로 제공하고 있다.

 

제공하는 기능이 좀 많이 허접♡ 해서 그렇지만, 

 

opencv 홈페이지에 가서 다운받고 언리얼용으로 새로 맞춰서 빌드하고 임포트 과정에서 수많은 에러와 싸워가면서 겨우 성공하고... 이럴 필요가 없다는 이야기다.

 

opencv플러그인은 대충 여기 있다.

 

 

이걸 내 프로젝트로 들고 온다.

 

 

그리고 집어넣는다.

 

 

 

#2. webcam 읽어주는 widget

 

만들 때 https://github.com/VegetableWithChicken/OpenCVForUnreal/tree/5.0 를 많이 참고했다.

 

 

widget을 쓸 거기 때문에 UMG 모듈을 들고 와야 하고,

Texture에 덮어쓰는 부분이 있어서 RHI, RenderCore를 들고왔다...

 

 

*** OpenCVWidget.h

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Runtime/UMG/Public/Blueprint/UserWidget.h"
#include "Runtime/Engine/Classes/Engine/Texture2D.h"
#include "Runtime/Core/Public/HAL/RunnableThread.h"
#include "Runtime/Core/Public/HAL/Runnable.h"

#include "OpenCVHelper.h"

#if WITH_OPENCV

#include "PreOpenCVHeaders.h"

#include "opencv2/core.hpp"
#include "opencv2/highgui.hpp"    
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"

#include "PostOpenCVHeaders.h"

#endif    // WITH_OPENCV

#include "OpenCVWidget.generated.h"

class FRunnable;
class FCameraReadThread;

UCLASS(BlueprintType)
class OPENCVHELPER_API UOpenCVWidget : public UUserWidget
{
    GENERATED_BODY()

public:
    UOpenCVWidget(const FObjectInitializer& ObjectInitializer);
    virtual void NativeConstruct() override; //init
    virtual void NativeDestruct() override; //destroy

    // The device ID opened by the Video Stream
    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OpenCV")
        int32 CameraID;

    // The webcam size width and height (width, height)
    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "OpenCV")
        FVector2D CameraSize;

    // Camera and read thread
    cv::VideoCapture cap;
    FCameraReadThread* ReadThread;

    // Camera readframe and cv::Mat to unreal Texture
    void ReadFrame();
    UTexture2D* ConvertMat2Texture2D(const cv::Mat& inMat);

    // Blueprint Event called every time the video frame is updated
    UFUNCTION(BlueprintImplementableEvent, Category = "OpenCV")
        void OnTextureUpdated(UTexture2D* outRGB);

};

class OPENCVHELPER_API FCameraReadThread :public FRunnable
{
public:
    static FCameraReadThread* InitThread(UOpenCVWidget* inWidget)
    {
        if (!ReadInstance && FPlatformProcess::SupportsMultithreading())
        {
            ReadInstance = new FCameraReadThread(inWidget);
        }
        return ReadInstance;
    }

public:

    virtual bool Init() override
    {
        StopThreadCounter.Increment();
        return true;
    }

    virtual uint32 Run() override
    {
        if (!ReadWidget)
        {
            UE_LOG(LogTemp, Warning, TEXT("There is no OpenCV Widget"));
            return 1;
        }
        while (StopThreadCounter.GetValue())
        {
            ReadWidget->ReadFrame();
        }
        return 0;
    }

    virtual void Exit() override
    {

    }

    virtual void Stop() override
    {
        if (ReadInstance)
        {
            ReadInstance->EnsureThread();
            delete ReadInstance;
            ReadInstance = nullptr;
        }

    }
    void EnsureThread()
    {
        StopThreadCounter.Decrement();
        if (ReadImageThread) {
            ReadImageThread->WaitForCompletion();
        }
    }
protected:
    FCameraReadThread(UOpenCVWidget* inWidget)
    {

        ReadWidget = inWidget;
        ReadImageThread = FRunnableThread::Create(this, TEXT("ReadImageThread"));
    }


    ~FCameraReadThread() {

    };


private:
    FRunnableThread* ReadImageThread;
    UOpenCVWidget* ReadWidget;
    static FCameraReadThread* ReadInstance;
    FThreadSafeCounter StopThreadCounter;
};

 

 

 

 

 

*** OpenCVWidget.cpp

#include "OpenCVWidget.h"

//--------------------------------------------------------------------//
// for unreal widget basic functions
//--------------------------------------------------------------------//

UOpenCVWidget::UOpenCVWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
    CameraID = 0;
    CameraSize = FVector2D(320, 240);
    cap = cv::VideoCapture();
}

void UOpenCVWidget::NativeConstruct()
{
    Super::NativeConstruct();

    //open the camera and read thread
    Async<>(EAsyncExecution::Thread, [=]()
        {
            if (cap.open(CameraID))
            {
                UE_LOG(LogTemp, Warning, TEXT("OpenCamera Sucess"));
                cap.set(cv::CAP_PROP_FRAME_WIDTH, CameraSize.X);
                cap.set(cv::CAP_PROP_FRAME_HEIGHT, CameraSize.Y);
                cap.set(cv::CAP_PROP_FPS, 30);
                ReadThread = FCameraReadThread::InitThread(this);
            }
            else
            {
                UE_LOG(LogTemp, Warning, TEXT("OpenCamera Failed"));
            }
            FPlatformProcess::Sleep(0.01);
        });
}
void UOpenCVWidget::NativeDestruct()
{
    //Close camera and read thread
    if (ReadThread)
    {
        ReadThread->Stop();
        ReadThread = nullptr;

    }
    if (cap.isOpened())
    {
        cap.release();
    }
    Super::NativeDestruct();
}

//--------------------------------------------------------------------//
// for opencv camera thread capture 
//--------------------------------------------------------------------//


void UOpenCVWidget::ReadFrame()
{
    if (cap.isOpened())
    {
        cv::Mat frame;
        cap.read(frame);
        if (frame.empty()) return;

        if (frame.channels() == 4)
        {
            cv::cvtColor(frame, frame, cv::COLOR_BGRA2RGB);
        }
        AsyncTask(ENamedThreads::GameThread, [=]()
            {
                UTexture2D* outTexture = ConvertMat2Texture2D(frame);
                OnTextureUpdated(outTexture);
            });
    }
}

UTexture2D* UOpenCVWidget::ConvertMat2Texture2D(const cv::Mat& inMat)
{
    int32 width = inMat.cols;
    int32 height = inMat.rows;
    int32 Channels = inMat.channels();
    cv::Mat out;
    cv::cvtColor(inMat, out, cv::COLOR_RGB2RGBA);
    UTexture2D* NewTexture = UTexture2D::CreateTransient(width, height);
    NewTexture->SRGB = 0;
    //NewTexture >CompressionSettings = TextureCompressionSettings::TC_Displacementmap;
    int DataSize = width * height * 4;
    void* Datas = NewTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
    FMemory::Memmove(Datas, out.data, DataSize);
    NewTexture->PlatformData->Mips[0].BulkData.Unlock();
    NewTexture->UpdateResource();
    return NewTexture;
}


/*Thread Instance*/
FCameraReadThread* FCameraReadThread::ReadInstance = nullptr;

 

 

 

#3. 프로젝트에 적용하기

 

 

아까 만들었던 OpenCVWidget을 부모로 하는 위젯 블루프린트를 하나 만들어 주고,

 

Image를 한 개 박아넣는다.

 

이미지에 사용할 mat는 그냥 단순히 ui용 mat에서 texture하나 달아놓은 거다.

Texture를 Param으로 빼 놔야 material instance에서 갈아끼워 줄 수 있다.

 

 

헤더에서 정의했던

 

    UFUNCTION(BlueprintImplementableEvent, Category = "OpenCV")
        void OnTextureUpdated(UTexture2D* outRGB);

 

는 ReadFrame() 이 끝날 때 마다 호출된다. blueprint에서는 해당 이벤트를 받아서 material의 텍스처를 교환해 준다.

 

 

 

 

 

'unreal engine' 카테고리의 다른 글

외부 DLL 사용하기  (0) 2024.04.12
Tickable Object  (0) 2024.02.23
off-axis projection in unreal  (0) 2023.05.16
Animation이 적용된 Skeletal Mesh  (0) 2023.05.13
Gradient Material  (0) 2023.05.11