이전에 퀘스트2 virtual desktop인가 immersed인가 사용했었는데

거기서는 핸드 컨트롤러로 했었지만

 

컴퓨터에서는 손 자세 추정을 통해서 만들어 보려고 한다.

그런데 mediapipe의 손 랜드마크 추정을 쓰자니

그냥 파이썬처럼 쉽게 할수가 없다.

 

바젤 없이는 빌드는 물론 실행 조차 못한다하고

 

언리얼에선 파이썬 못쓰나 찾아봤지만

게임 실행중에는 안되고 에디터 상에서만 사용가능해보인다.

 

언리얼 미디어파이프 플러그인도 있긴한데 구버전에서 동작해서 잘 안되다보니

아쉬운데로 손 추정에 사용하는 모델을 cv::dnn 으로 추론해서

직접 사용해보려고한다.

 

아래의 코드는 블라즈팜 오닉스 추론 예제인데

일단 파이썬으로 손 검출부터 해도 실제 손 전체를 못감싸준다.

 

여기서 사용한 블라즈팜이 128 x 128 입력으로받는 옛날 모델인데

미디어 파이프 모델들은 tflite로 올라와있지만 최근건 tflite2onnx로 변환이 불가

 

 

https://colab.research.google.com/drive/1dgKi8btAKu2ihB7XOJbNwBcs7246B79_?usp=sharing#scrollTo=dm-e2eb_X7Q7

 

BlazePalm ONNX Inference Test

Colaboratory notebook

colab.research.google.com

 

 

 

 

 

 

전에는 못찾았는데

찾다보니 미디어파이프 tflite 모델을 파이토치 파일로 변환한걸 찾음

 

https://github.com/vidursatija/BlazePalm/blob/master/ML/ConverterPalmDetector.ipynb

 

 

https://github.com/zmurez/MediaPipePyTorch

 

GitHub - zmurez/MediaPipePyTorch: Port of MediaPipe tflite models to PyTorch

Port of MediaPipe tflite models to PyTorch. Contribute to zmurez/MediaPipePyTorch development by creating an account on GitHub.

github.com

 

 

 

 

 

다행이 이 링크에서 제공하는 데모에서는 미디어파이프 없이 blaze 핸드, 얼굴, 팜 등 코드가 동작한다.

여길 참고해서 c++에서 동작하게 고쳐주면 될듯한데

 

쉽진안지만 잘 모르는 pbtxt 범벅된 미디어파이프 코드보단 그래도 알아볼만하다.

팜 디텍터 결과보면 손바닥 영역만 찾아내는게 맞앗었나보다.



 

그러면 블라즈 핸드의 입력으로 들어가는 이미지가 어떻게 되어있나 싶었는데

회전을 풀어낸 1 x 3 x 256 x 256 이미지였다.

 

 

블라즈 팜은 회전이 고려되지 않은 사각형이 나오는데

어떻게 손 방향대로 얼마만큼 회전된걸 알았길래 손을 위를 향하도록 되돌릴수 있었을까.

 

 

 

 

디텍션2roi 함수에서

xy 센터와 스케일, 세타(방향) 정보를 반환하도록 되어있다.

 

 

 

디텍션 2roi 보기전에 드로 디텍션을 봤다.

디텍션즈에 알고보니 뭔진 모를 키포인트 좌표도 같이 있었는데

손 내에 파란점들로

다시보니 손바닥 점들의 좌표를 나타내는 것으로 보인다.

아마 이 손바닥 키포인트들을 이용해서 방향을 찾은듯

 

 

 

 

앞서 블라즈팜에서도 키포인트를 구해서 드로잉했던것처럼

디텍션2roi에선 이 블라즈팜 키포인트로 방향-세타 를 구해내도록 되어있다.

    def detection2roi(self, detection):
        """ Convert detections from detector to an oriented bounding box.

        Adapted from:
        # mediapipe/modules/face_landmark/face_detection_front_detection_to_roi.pbtxt

        The center and size of the box is calculated from the center 
        of the detected box. Rotation is calcualted from the vector
        between kp1 and kp2 relative to theta0. The box is scaled
        and shifted by dscale and dy.

        """
        if self.detection2roi_method == 'box':
            # compute box center and scale
            # use mediapipe/calculators/util/detections_to_rects_calculator.cc
            xc = (detection[:,1] + detection[:,3]) / 2
            yc = (detection[:,0] + detection[:,2]) / 2
            scale = (detection[:,3] - detection[:,1]) # assumes square boxes

        elif self.detection2roi_method == 'alignment':
            # compute box center and scale
            # use mediapipe/calculators/util/alignment_points_to_rects_calculator.cc
            xc = detection[:,4+2*self.kp1]
            yc = detection[:,4+2*self.kp1+1]
            x1 = detection[:,4+2*self.kp2]
            y1 = detection[:,4+2*self.kp2+1]
            scale = ((xc-x1)**2 + (yc-y1)**2).sqrt() * 2
        else:
            raise NotImplementedError(
                "detection2roi_method [%s] not supported"%self.detection2roi_method)

        yc += self.dy * scale
        scale *= self.dscale

        # compute box rotation
        x0 = detection[:,4+2*self.kp1]
        y0 = detection[:,4+2*self.kp1+1]
        x1 = detection[:,4+2*self.kp2]
        y1 = detection[:,4+2*self.kp2+1]
        #theta = np.arctan2(y0-y1, x0-x1) - self.theta0
        theta = torch.atan2(y0-y1, x0-x1) - self.theta0
        return xc, yc, scale, theta

 

 

 

중심점과 세타는 그런데

스케일 계산에서 dscale이 뭔가 싶었는데

모델별로 기본 스케일 값이 있었다. 

 

 

 

 

 

대충 궁금했던 내용은 여기까지보고

구현 흐름을 정리해보자.

 

일단 아래 그림은 블라즈 핸드 동작 흐름 정리된 그림인데

 

 

 

 

 

다시 코드보면서 정리해보자.

 

가장 먼저 이미지 가져온 후 리사이즈 패드 수행한다.

(중간에 다른 모델 코드는 제외)

if len(sys.argv) > 1:
    capture = cv2.VideoCapture(sys.argv[1])
    mirror_img = False
else:
    capture = cv2.VideoCapture(0)
    mirror_img = True

if capture.isOpened():
    hasFrame, frame = capture.read()
    frame_ct = 0
else:
    hasFrame = False

while hasFrame:
    frame_ct +=1

    if mirror_img:
        frame = np.ascontiguousarray(frame[:,::-1,::-1])
    else:
        frame = np.ascontiguousarray(frame[:,:,::-1])

    img1, img2, scale, pad = resize_pad(frame)

 

 

 

 

이 리사이즈 패드는 

블라즈 모델들은 128 x 128, 256 x 256 크기 이미지를 사용해서

리사이징 + 패딩 처리 한뒤, 두 크기 이미지와 스케일과 패드 스케일을 반환한다.

def resize_pad(img):
    """ resize and pad images to be input to the detectors

    The face and palm detector networks take 256x256 and 128x128 images
    as input. As such the input image is padded and resized to fit the
    size while maintaing the aspect ratio.

    Returns:
        img1: 256x256
        img2: 128x128
        scale: scale factor between original image and 256x256 image
        pad: pixels of padding in the original image
    """

    size0 = img.shape
    if size0[0]>=size0[1]:
        h1 = 256
        w1 = 256 * size0[1] // size0[0]
        padh = 0
        padw = 256 - w1
        scale = size0[1] / w1
    else:
        h1 = 256 * size0[0] // size0[1]
        w1 = 256
        padh = 256 - h1
        padw = 0
        scale = size0[0] / h1
    padh1 = padh//2
    padh2 = padh//2 + padh%2
    padw1 = padw//2
    padw2 = padw//2 + padw%2
    img1 = cv2.resize(img, (w1,h1))
    img1 = np.pad(img1, ((padh1, padh2), (padw1, padw2), (0,0)))
    pad = (int(padh1 * scale), int(padw1 * scale))
    img2 = cv2.resize(img1, (128,128))
    return img1, img2, scale, pad

 

 

 

리사이즈 패드해서 얻은 이미지로 

예측해서 정규화된 팜 디택션 결과들을 가져옴

    img1, img2, scale, pad = resize_pad(frame)

    normalized_palm_detections = palm_detector.predict_on_image(img1)

 

 

 

 

프레딕트 이미지를 보면

프레딕트 온 배치로 넘어가지는데

    def predict_on_image(self, img):
        """Makes a prediction on a single image.

        Arguments:
            img: a NumPy array of shape (H, W, 3) or a PyTorch tensor of
                 shape (3, H, W). The image's height and width should be 
                 128 pixels.

        Returns:
            A tensor with face detections.
        """
        if isinstance(img, np.ndarray):
            img = torch.from_numpy(img).permute((2, 0, 1))

        return self.predict_on_batch(img.unsqueeze(0))[0]

 

 

 

 

 

중간에 전처리 -> 모델 추론 -> 후처리 -> 디텍션 획득 -> 비최대 억제 -> 필터링된 디텍션 반환

의 흐름으로 수행된다.

 

주석 내용을보니

디텍션은 0, 17 형태를 갖고있고

ymin, xmin, ymax, xmax, 키포인트xy 6개, 신뢰도 스코어 로 구성된다고 한다.

    def predict_on_batch(self, x):
        """Makes a prediction on a batch of images.

        Arguments:
            x: a NumPy array of shape (b, H, W, 3) or a PyTorch tensor of
               shape (b, 3, H, W). The height and width should be 128 pixels.

        Returns:
            A list containing a tensor of face detections for each image in 
            the batch. If no faces are found for an image, returns a tensor
            of shape (0, 17).

        Each face detection is a PyTorch tensor consisting of 17 numbers:
            - ymin, xmin, ymax, xmax
            - x,y-coordinates for the 6 keypoints
            - confidence score
        """
        if isinstance(x, np.ndarray):
            x = torch.from_numpy(x).permute((0, 3, 1, 2))

        assert x.shape[1] == 3
        assert x.shape[2] == self.y_scale
        assert x.shape[3] == self.x_scale

        # 1. Preprocess the images into tensors:
        x = x.to(self._device())
        x = self._preprocess(x)

        # 2. Run the neural network:
        with torch.no_grad():
            out = self.__call__(x)

        # 3. Postprocess the raw predictions:
        detections = self._tensors_to_detections(out[0], out[1], self.anchors)

        # 4. Non-maximum suppression to remove overlapping detections:
        filtered_detections = []
        for i in range(len(detections)):
            faces = self._weighted_non_max_suppression(detections[i])
            faces = torch.stack(faces) if len(faces) > 0 else torch.zeros((0, self.num_coords+1))
            filtered_detections.append(faces)

        return filtered_detections

 

 

 

 

하지만 이렇게 얻은 팜 디텍션 결과는 정규화된 값으로

이전에 리사이즈 패드에서 얻은 스케일과 패드값으로 반정규화 시켜준다.

    normalized_palm_detections = palm_detector.predict_on_image(img1)

    palm_detections = denormalize_detections(normalized_palm_detections, scale, pad)

 

 

스케일과 패드를 이용해서 반정규화 코드는 이런식

def denormalize_detections(detections, scale, pad):
    """ maps detection coordinates from [0,1] to image coordinates

    The face and palm detector networks take 256x256 and 128x128 images
    as input. As such the input image is padded and resized to fit the
    size while maintaing the aspect ratio. This function maps the
    normalized coordinates back to the original image coordinates.

    Inputs:
        detections: nxm tensor. n is the number of detections.
            m is 4+2*k where the first 4 valuse are the bounding
            box coordinates and k is the number of additional
            keypoints output by the detector.
        scale: scalar that was used to resize the image
        pad: padding in the x and y dimensions

    """
    detections[:, 0] = detections[:, 0] * scale * 256 - pad[0]
    detections[:, 1] = detections[:, 1] * scale * 256 - pad[1]
    detections[:, 2] = detections[:, 2] * scale * 256 - pad[0]
    detections[:, 3] = detections[:, 3] * scale * 256 - pad[1]

    detections[:, 4::2] = detections[:, 4::2] * scale * 256 - pad[1]
    detections[:, 5::2] = detections[:, 5::2] * scale * 256 - pad[0]
    return detections

 

 

 

 

 

반정규화 이후에는 디텍션을 roi로 바꾸는데, (아까본거) xy 센터점과 스케일, 방향을 얻어내고

extract_roi로 원본 이미지로부터 img, affine2, box2를 가져오는데

 

    palm_detections = denormalize_detections(normalized_palm_detections, scale, pad)



    xc, yc, scale, theta = palm_detector.detection2roi(palm_detections.cpu())
    img, affine2, box2 = hand_regressor.extract_roi(frame, xc, yc, theta, scale)

 

 

 

roi 추출 코드를 보면

img는 정규화된 N x C x W x H 형태의 탠서이고

affines는 기울어진손 -> 정방향 손 어파인 변환을 위한, 역 어파인 변환 행렬

points는 어파인 변환에 사용하던 점들로 보인다.

 

    def extract_roi(self, frame, xc, yc, theta, scale):

        # take points on unit square and transform them according to the roi
        points = torch.tensor([[-1, -1, 1, 1],
                            [-1, 1, -1, 1]], device=scale.device).view(1,2,4)
        points = points * scale.view(-1,1,1)/2
        theta = theta.view(-1, 1, 1)
        R = torch.cat((
            torch.cat((torch.cos(theta), -torch.sin(theta)), 2),
            torch.cat((torch.sin(theta), torch.cos(theta)), 2),
            ), 1)
        center = torch.cat((xc.view(-1,1,1), yc.view(-1,1,1)), 1)
        points = R @ points + center

        # use the points to compute the affine transform that maps 
        # these points back to the output square
        res = self.resolution
        points1 = np.array([[0, 0, res-1],
                            [0, res-1, 0]], dtype=np.float32).T
        affines = []
        imgs = []
        for i in range(points.shape[0]):
            pts = points[i, :, :3].cpu().numpy().T
            M = cv2.getAffineTransform(pts, points1)
            img = cv2.warpAffine(frame, M, (res,res))#, borderValue=127.5)
            img = torch.tensor(img, device=scale.device)
            imgs.append(img)
            affine = cv2.invertAffineTransform(M).astype('float32')
            affine = torch.tensor(affine, device=scale.device)
            affines.append(affine)
        if imgs:
            imgs = torch.stack(imgs).permute(0,3,1,2).float() / 255.#/ 127.5 - 1.0
            affines = torch.stack(affines)
        else:
            imgs = torch.zeros((0, 3, res, res), device=scale.device)
            affines = torch.zeros((0, 2, 3), device=scale.device)

        return imgs, affines, points

 

 

 

이렇게 구한 텐서(블롭) img를 

hand_regressor(랜드마크 추정기)에 넣어서 플래그, 핸드, 정규화된 랜드마크들을 받아내느데 

 

    flags2, handed2, normalized_landmarks2 = hand_regressor(img.to(gpu))
    landmarks2 = hand_regressor.denormalize_landmarks(normalized_landmarks2.cpu(), affine2)

 

 

핸드 랜드마크 추론 코드

    def forward(self, x):
        if x.shape[0] == 0:
            return torch.zeros((0,)), torch.zeros((0,)), torch.zeros((0, 21, 3))

        x = F.pad(x, (0, 1, 0, 1), "constant", 0)

        x = self.backbone1(x)
        y = self.backbone2(x)
        z = self.backbone3(y)
        w = self.backbone4(z)

        z = z + F.interpolate(w, scale_factor=2, mode='bilinear')
        z = self.blaze5(z)

        y = y + F.interpolate(z, scale_factor=2, mode='bilinear')
        y = self.blaze6(y)
        y = self.conv7(y)

        x = x + F.interpolate(y, scale_factor=2, mode='bilinear')

        x = self.backbone8(x)

        hand_flag = self.hand_flag(x).view(-1).sigmoid()
        handed = self.handed(x).view(-1).sigmoid()
        landmarks = self.landmarks(x).view(-1, 21, 3) / 256

        return hand_flag, handed, landmarks

 

 

 

마지막으로 랜드마크 반정규화를보면

랜드마크를 어파인 역변환 행렬과 곱하는 내용

여기서 나오는 래졸루션은 블라즈 핸드에 입력으로 사용하는 256

 

    def denormalize_landmarks(self, landmarks, affines):
        landmarks[:,:,:2] *= self.resolution
        for i in range(len(landmarks)):
            landmark, affine = landmarks[i], affines[i]
            landmark = (affine[:,:2] @ landmark[:,:2].T + affine[:,2:]).T
            landmarks[i,:,:2] = landmark
        return landmarks

 

class BlazeHandLandmark(BlazeLandmark):
    """The hand landmark model from MediaPipe.
    
    """
    def __init__(self):
        super(BlazeHandLandmark, self).__init__()

        # size of ROIs used for input
        self.resolution = 256

 

 

일단 여기까지 파이썬 코드 살펴봤으면

c++로 만들어볼수 있을듯

GetDC로 디바이스 컨텍스트 가져와서

주모니터는 가져와지는데 다른 보조모니터는 어떻게 가져오나 해매다가 좋은 참고자료 찾음

 

https://stackoverflow.com/questions/53329673/c-getdc-all-monitors

 

C++ GetDC All Monitors

Basically, I'm making something that imitates a screen melting effect, but I can only get it working on my primary monitor. I've looked up as much as I could and there was only one forum on GetDC f...

stackoverflow.com

 

위 링크에 따르면 GetDC(0) 하면 전체 모니터 DC를 가져온다고 하는데

BitBlt에서 좌표 설정해야 된다고 하더라

 

 

중앙에 위치한 3번 모니터의 시작점은 1920, 0이므로 

 

 

 

 

 

 

BitBlt에서 x1 자리 매개변수 값을 1920으로 고치면 3번 보조모니터의 화면이 나온다.

cv::Mat ADesktopGameModeBase::GetScreenToCVMat()
{
	HDC hScreenDC = GetDC(NULL);
	HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
	int screenWidth = GetDeviceCaps(hScreenDC, HORZRES);
	int screenHeight = GetDeviceCaps(hScreenDC, VERTRES);

	HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
	BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 1920, 0, SRCCOPY);
	SelectObject(hMemoryDC, hOldBitmap);

	cv::Mat matImage(screenHeight, screenWidth, CV_8UC4);
	GetBitmapBits(hBitmap, matImage.total() * matImage.elemSize(), matImage.data);

	DeleteDC(hScreenDC);
	DeleteDC(hMemoryDC);

	DeleteObject(hBitmap);
	DeleteObject(hOldBitmap);


	return matImage;
}

 

 

중앙 보조모니터(3번)

 

 

이제 세 모니터를 띄우는게 가능할것같다.

일단 스크린부터 3개로 늘리자

 

 

배치후 BP_Screen1, 2, 3으로 이름 변경

 

 

 

BP 데스크탑 게임모드베이스에서 기존 코드는 잠깐때고

한번 디스플레이 이름 출력해보면

 

 

순서는 섞였지만 나오긴 나온다.

이 디스플레이 이름 확인해서 넣어주면될듯

 

 

일단 1, 3, 4화면을 utexture2d로 변환하는 코드 만들어보면

 

 

 

 

 

 

 

 

 

기존 코드는 가능한 나두고

모니터 가로세로길이

이미지텍스처스크린1,2,3

cvmat 이미지스크린1,2,3

함수로 ScreensToCVMats, CVMatsToTextures

 

UCLASS()
class HANDDESKTOP_API ADesktopGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	cv::VideoCapture capture;
	cv::Mat image;

	UFUNCTION(BlueprintCallable)
	void ReadFrame();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTexture;
	void MatToTexture2D(const cv::Mat InMat);

	cv::Mat GetScreenToCVMat();




	int monitorWidth = 1920;
	int monitorHeight = 1080;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTextureScreen1;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTextureScreen2;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTextureScreen3;
	cv::Mat imageScreen1;
	cv::Mat imageScreen2;
	cv::Mat imageScreen3;

	void ScreensToCVMats();
	void CVMatsToTextures();
};

 

 

비긴 플레이에 초기화 코드 추가

#include "DesktopGameModeBase.h"


void ADesktopGameModeBase::BeginPlay()
{
	Super::BeginPlay();

	capture = cv::VideoCapture(0);

	if (!capture.isOpened())
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam failed"));
		return;
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam Success"));
	}
	imageTexture = UTexture2D::CreateTransient(monitorWidth, monitorHeight, PF_B8G8R8A8);



	imageScreen1 = cv::Mat(monitorHeight, monitorWidth, CV_8UC4);
	imageScreen2 = cv::Mat(monitorHeight, monitorWidth, CV_8UC4);
	imageScreen3 = cv::Mat(monitorHeight, monitorWidth, CV_8UC4);
	imageTextureScreen1 = UTexture2D::CreateTransient(monitorWidth, monitorHeight, PF_B8G8R8A8);
	imageTextureScreen2 = UTexture2D::CreateTransient(monitorWidth, monitorHeight, PF_B8G8R8A8);
	imageTextureScreen3 = UTexture2D::CreateTransient(monitorWidth, monitorHeight, PF_B8G8R8A8);

}

 

 

리드 프레임 수정

void ADesktopGameModeBase::ReadFrame()
{
	/*
	if (!capture.isOpened())
	{
		return;
	}
	capture.read(image);

	cv::Mat desktopImage = GetScreenToCVMat();
	MatToTexture2D(desktopImage);
	*/

	ScreensToCVMats();
	CVMatsToTextures();

}

 

모니터 1, 3, 4를 cv::Mat imageScreen1, 2, 3에 저장하는 코드

void ADesktopGameModeBase::ScreensToCVMats()
{
	HDC hScreenDC = GetDC(NULL);
	HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
	int screenWidth = GetDeviceCaps(hScreenDC, HORZRES);
	int screenHeight = GetDeviceCaps(hScreenDC, VERTRES);

	HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);

	//screen 1
	BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY);
	GetBitmapBits(hBitmap, imageScreen1.total() * imageScreen1.elemSize(), imageScreen1.data);

	//screen 2
	BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 1920, 0, SRCCOPY);
	GetBitmapBits(hBitmap, imageScreen2.total() * imageScreen2.elemSize(), imageScreen2.data);

	//screen 3
	BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 3840, 0, SRCCOPY);
	GetBitmapBits(hBitmap, imageScreen3.total() * imageScreen3.elemSize(), imageScreen3.data);
	SelectObject(hMemoryDC, hOldBitmap);


	DeleteDC(hScreenDC);
	DeleteDC(hMemoryDC);

	DeleteObject(hBitmap);
	DeleteObject(hOldBitmap);

}

 

 

 

imageScreen1,2,3을 imageTextureScreen1,2,3에 저장하는 코드

이런 식으로 만들 필요는 없는데

이미지텍스처 스크린을 언리얼 프로퍼티로 설정해서 BP에서 사용하려다가 좀 이상하게 됨

void ADesktopGameModeBase::CVMatsToTextures()
{
	for (int i = 0; i < 3; i++)
	{
		if (i == 0)
		{
			FTexture2DMipMap& Mip = imageTextureScreen1->GetPlatformData()->Mips[0];
			void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
			FMemory::Memcpy(Data, imageScreen1.data, imageScreen1.total() * imageScreen1.elemSize());//copy the data
			Mip.BulkData.Unlock();

			imageTextureScreen1->PostEditChange();
			imageTextureScreen1->UpdateResource();
		}
		else if (i == 1)
		{
			FTexture2DMipMap& Mip = imageTextureScreen2->GetPlatformData()->Mips[0];
			void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
			FMemory::Memcpy(Data, imageScreen2.data, imageScreen2.total() * imageScreen2.elemSize());//copy the data
			Mip.BulkData.Unlock();

			imageTextureScreen2->PostEditChange();
			imageTextureScreen2->UpdateResource();
		}
		else if (i == 2)
		{
			FTexture2DMipMap& Mip = imageTextureScreen3->GetPlatformData()->Mips[0];
			void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
			FMemory::Memcpy(Data, imageScreen3.data, imageScreen3.total() * imageScreen3.elemSize());//copy the data
			Mip.BulkData.Unlock();

			imageTextureScreen3->PostEditChange();
			imageTextureScreen3->UpdateResource();
		}


	}


}

 

 

 

데스크탑 게임모드베이스 블루프린트로 들어와

Screen1,2,3(BP_MainWidget) StrScreen1,2,3(포 이치 루프에서 이름으로 찾아내기 위함) 추가

StrScreen1,2,3의 디폴트 값으로 BP_Screen1, BP_Screen2 , BP_Screen3 으로 설정

 

 

 

BP_Screen 모든 액터 가져와 포 이치 루프

아까 정의한 StrScreen1,2,3으로 액터 확인, BP MainWidget 생성후 Screen 1,2,3에 등록

Screen 1, 2, 3(BP_MainWidget)을 루프로 가져온 Screen(BP_Screen)에 등록

 

 

 

 

이벤트 틱에서 각 Screen 변수의 imageWidget과 c++에서 설정한 imageTextureScreen(UProperty)로
SetBrushFromTexture로 텍스쳐 드로잉

 

 

 

전체 스크린에 대해 수행한 결과

 

 

 

 

정리하면 이런형태

 

 

 

 

 

일단 가상 모니터 추가하는 방법 찾아봤는데

 

아래 링크에서

amyuni 소개해줘서 보고 따라해봄

 

https://quasarzone.com/bbs/qf_sw/views/50718

 

모니터 없이 디스플레이 확장 방법 없을까요?

윈도우7쓸대는 디스플레이 확장 메뉴에서 모니터 없어도 감지눌러서 추가하는게 가능했는데지금 윈도우10 최신버전…

quasarzone.com

 

 

 

설명 글대로

 

 

 

압축 풀고 

cmd 관리자 권한으로 deviceinstaller64.exe install usbmmidd.inf usbmmidd로 드라이버 설치후

deviceinstaller64 enableidd 1을 여러번 하면 가상 모니터가 여러번 추가된다

deviceinstaller64 enableidd 0을 하면 가상모니터가 사라지고

 

 

 

 

 

 

듀얼 모니터(1, 2)를 사용 중인데

가상 모니터(3, 4)를 이렇게 배치시켰다.

 

 

1, 3, 4 모니터 3개를 인게임에 띄우고, 보조보니터 2에는 언리얼로 실행시키기 위함

 

 

가상 모니터는 준비되었고, 언리얼 엔진 실행하고 커서로 다른걸 누르면 화면이 급 느려지는데

찾아보니 에디터 프리퍼런스에서 Use less cpu when in background option이 체크된걸 해제해주면 된다더라

 

 

https://forums.unrealengine.com/t/keeping-editor-running-as-normal-when-not-in-focus/367988

 

Keeping editor running as normal when not in focus

Hey guys, how do you keep a game running as normal when the editor is not in focus? I want to test my multiplayer with two editors open (I know you can have two instances on one editor but I need to run two editors for authentication purposes) but one edit

forums.unrealengine.com

 

 

체크 해재해주면 실행중 다른걸 눌러도 속도가 느려지지않는다.

 

 

 

그리고 계속 텍스처 스트리밍 풀 예산 초과 경고가 뜨는데

 

 

아래 링크대로 해결되지 않는다.

 

https://3dperson1.tistory.com/44

 

언리얼 텍스처 스트리밍 풀이 예산을 초과했습니다 해결 방법

안녕하세요 오랜만입니다. 요즘 언리얼만 공부를 하고 있다보니, 미드저니나 챗지피티 등등에 대해 예전만큼 시간을 쏟지 못하고 있습니다. 거두절미하고 언리얼 텍스쳐 스트리밍 풀이 예산을

3dperson1.tistory.com

 

 

 

 

 

 

 

 

작업 관리자로 보니 메모리가 가득 찼다가 non streaming mips가 해제되면서 내려갔다, 다시 올라갓다를 반복하는듯하다.

생성한 utexture2d들이 제때 제거안되서 그런듯.

 

 

 

 

 

 

 

 

생각해보니 HBITMAP 을 cvMat으로 만드는 코드에서

디바이스 콘텍스트와 비트맵을 해제안시켰었다. 수정

 

cv::Mat ADesktopGameModeBase::GetScreenToCVMat()
{
	HDC hScreenDC = GetDC(NULL);
	HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
	int screenWidth = GetDeviceCaps(hScreenDC, HORZRES);
	int screenHeight = GetDeviceCaps(hScreenDC, VERTRES);

	HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
	BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY);
	SelectObject(hMemoryDC, hOldBitmap);

	cv::Mat matImage(screenHeight, screenWidth, CV_8UC4);
	GetBitmapBits(hBitmap, matImage.total() * matImage.elemSize(), matImage.data);

	DeleteDC(hScreenDC);
	DeleteDC(hMemoryDC);

	DeleteObject(hBitmap);
	DeleteObject(hOldBitmap);


	return matImage;
}

 

 

 

 

DC와 비트맵을 cv::Mat 생성 후 해제시켜주니 액세스 위반으로 꺼지는건 해결됬는데

여전히 Non Streaming Pool이 가득차다 풀렸다 반복하는건 여전하다.

 

 

 

 

이미지 텍스처를 제때 해재안해준게 문젠거같아서 

전역 변수로 뺀뒤 중간에 해재하도록 수정

 

void ADesktopGameModeBase::ReadFrame()
{
	/*
	if (!capture.isOpened())
	{
		return;
	}
	capture.read(image);
	*/

	cv::Mat desktopImage = GetScreenToCVMat();
	MatToTexture2D(desktopImage);
}


void ADesktopGameModeBase::MatToTexture2D(const cv::Mat InMat)
{
	imageTexture->ReleaseResource();
	//create new texture, set its values
	imageTexture = UTexture2D::CreateTransient(InMat.cols, InMat.rows, PF_B8G8R8A8);
	if (InMat.type() == CV_8UC3)//example for pre-conversion of Mat
	{
		cv::Mat bgraImage;
		//if the Mat is in BGR space, convert it to BGRA. There is no three channel texture in UE (at least with eight bit)
		cv::cvtColor(InMat, bgraImage, cv::COLOR_BGR2BGRA);

		//Texture->SRGB = 0;//set to 0 if Mat is not in srgb (which is likely when coming from a webcam)
		//other settings of the texture can also be changed here
		//Texture->UpdateResource();

		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = imageTexture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, bgraImage.data, bgraImage.total() * bgraImage.elemSize());//copy the data
		Mip.BulkData.Unlock();
		imageTexture->PostEditChange();
		imageTexture->UpdateResource();
	}
	else if (InMat.type() == CV_8UC4)
	{
		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = imageTexture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, InMat.data, InMat.total() * InMat.elemSize());//copy the data
		Mip.BulkData.Unlock();
		imageTexture->PostEditChange();
		imageTexture->UpdateResource();
	}
	//if the texture hasnt the right pixel format, abort.
	imageTexture->PostEditChange();
	imageTexture->UpdateResource();
}

 

 

 

public:
	cv::VideoCapture capture;
	cv::Mat image;

	UFUNCTION(BlueprintCallable)
	void ReadFrame();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTexture;
	void MatToTexture2D(const cv::Mat InMat);


	cv::Mat GetScreenToCVMat();
};

 

이랬더니 release Resouce한후에 CreateTransient해서 그런지 또 액세스 위반 크래시 예외발생

 

 

 

 

 

beginPlay에서 imageTexture를 한번 초기화해주고 

mat to texture2d에서 cvmat -> texture 변환 후 imaget texture에 복사하기만 했더니

스트리밍 풀 문제는 해결됬는데

 

void ADesktopGameModeBase::BeginPlay()
{
	Super::BeginPlay();

	capture = cv::VideoCapture(0);

	if (!capture.isOpened())
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam failed"));
		return;
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam Success"));
	}
	imageTexture = UTexture2D::CreateTransient(1080, 1920, PF_B8G8R8A8);
}


void ADesktopGameModeBase::ReadFrame()
{
	/*
	if (!capture.isOpened())
	{
		return;
	}
	capture.read(image);
	*/

	cv::Mat desktopImage = GetScreenToCVMat();
	MatToTexture2D(desktopImage);
}


void ADesktopGameModeBase::MatToTexture2D(const cv::Mat InMat)
{
	if (InMat.type() == CV_8UC3)//example for pre-conversion of Mat
	{
		cv::Mat bgraImage;
		//if the Mat is in BGR space, convert it to BGRA. There is no three channel texture in UE (at least with eight bit)
		cv::cvtColor(InMat, bgraImage, cv::COLOR_BGR2BGRA);

		//Texture->SRGB = 0;//set to 0 if Mat is not in srgb (which is likely when coming from a webcam)
		//other settings of the texture can also be changed here
		//Texture->UpdateResource();

		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = imageTexture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, bgraImage.data, bgraImage.total() * bgraImage.elemSize());//copy the data
		Mip.BulkData.Unlock();
		imageTexture->PostEditChange();
		imageTexture->UpdateResource();
	}
	else if (InMat.type() == CV_8UC4)
	{
		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = imageTexture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, InMat.data, InMat.total() * InMat.elemSize());//copy the data
		Mip.BulkData.Unlock();
		imageTexture->PostEditChange();
		imageTexture->UpdateResource();
	}
	//if the texture hasnt the right pixel format, abort.
	imageTexture->PostEditChange();
	imageTexture->UpdateResource();
}

 

 

 

nonstreamingpool 가득안차는건 좋은데 

imagetexture에 복붙되는게 쌓여서 그런가 겹친듯한 화면이나온다.

 

 

 

인줄 알았는데 

 

createTransient에서 row와 col을 모르고 반대로 적었더라.

원래 순서대로 1920 x 1080 해주면 

void ADesktopGameModeBase::BeginPlay()
{
	Super::BeginPlay();

	capture = cv::VideoCapture(0);

	if (!capture.isOpened())
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam failed"));
		return;
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam Success"));
	}
	imageTexture = UTexture2D::CreateTransient(1920, 1080, PF_B8G8R8A8);
}

 

 

 

액세스 위반도 없고, 스트리밍 풀 문제도 해결

 

이전 글에서는 opencv, 웹캠스트리밍 하는 법 대충 정리했는데 

원래 하려던건 언리얼 상에 가상 데스크톱 같이 만들고 싶었다.

 

듀얼 모니터 사용중에

주모니터 화면을 언리얼 화면상에 띄어보려고 시도하는데

windows.h 인클루드 시켰다가 동작안되서 맨붕왔지만

 

 

다행이 아래 링크 참조해서 문제 해결

 

 

 

https://liiyuulab.tistory.com/29

 

UE4 에서 windows.h 포함 헤더 include 시 주의점

나름 평화롭던? 나날을 보내며 UE4 게임 빌드 중 갑작스럽게 error C4003: not enough arguments for function-like macro invocation 'min' 라는 에러를 보게 되었다. 전에는 이런 에러가 한번도 난 적이 없어서 당황.

liiyuulab.tistory.com

 

 

 

 

게임모드해더에 

 

윈도우 헤더파일 인클루드하고,

GetScreenToCVMat 함수추가

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

#pragma once


#include "Windows/AllowWindowsPlatformTypes.h"
#include <Windows.h>
#include "Windows/HideWindowsPlatformTypes.h"

#include "PreOpenCVHeaders.h"
#include <opencv2/opencv.hpp>
#include "PostOpenCVHeaders.h"

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "DesktopGameModeBase.generated.h"

/**
 * 
 */
UCLASS()
class HANDDESKTOP_API ADesktopGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	cv::VideoCapture capture;
	cv::Mat image;

	UFUNCTION(BlueprintCallable)
	void ReadFrame();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTexture;
	UTexture2D* MatToTexture2D(const cv::Mat InMat);


	cv::Mat GetScreenToCVMat();
};

 

 

 

 

 

기존 리드프레임에선 웹캠 읽을건 아니니 주석처리하고

desktopImage 가져와서 텍스처로 변환

void ADesktopGameModeBase::ReadFrame()
{
	/*
	if (!capture.isOpened())
	{
		return;
	}
	capture.read(image);
	*/

	cv::Mat desktopImage = GetScreenToCVMat();
	imageTexture = MatToTexture2D(desktopImage);
}

 

 

 

윈도우 디바이스 컨텍스트 -> 비트맵 핸들 -> CV:Mat 변환 코드

cv::Mat ADesktopGameModeBase::GetScreenToCVMat()
{
	HDC hScreenDC = GetDC(NULL);
	HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
	int screenWidth = GetDeviceCaps(hScreenDC, HORZRES);
	int screenHeight = GetDeviceCaps(hScreenDC, VERTRES);

	HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
	HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
	BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY);
	SelectObject(hMemoryDC, hOldBitmap);

	cv::Mat matImage(screenHeight, screenWidth, CV_8UC4);
	GetBitmapBits(hBitmap, matImage.total() * matImage.elemSize(), matImage.data);

	return matImage;
}

 

여기서 주의할건

위 코드에서 만든 cv::Mat의 타입은 CV_8UC4 

 

기존 텍스처 변환 코드에서는 3채널에 대해서만 존재하므로 

4채널에 대해서 내용 추가

 

3채널의 경우 bgra 4채널로 변환하던거라

4채널 변환없이 그대로 사용하는 내용

UTexture2D* ADesktopGameModeBase::MatToTexture2D(const cv::Mat InMat)
{
	//create new texture, set its values
	UTexture2D* Texture = UTexture2D::CreateTransient(InMat.cols, InMat.rows, PF_B8G8R8A8);

	if (InMat.type() == CV_8UC3)//example for pre-conversion of Mat
	{
		cv::Mat bgraImage;
		//if the Mat is in BGR space, convert it to BGRA. There is no three channel texture in UE (at least with eight bit)
		cv::cvtColor(InMat, bgraImage, cv::COLOR_BGR2BGRA);

		//Texture->SRGB = 0;//set to 0 if Mat is not in srgb (which is likely when coming from a webcam)
		//other settings of the texture can also be changed here
		//Texture->UpdateResource();

		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = Texture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, bgraImage.data, bgraImage.total() * bgraImage.elemSize());//copy the data
		Mip.BulkData.Unlock();
		Texture->PostEditChange();
		Texture->UpdateResource();
		return Texture;
	}
	else if (InMat.type() == CV_8UC4)
	{
		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = Texture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, InMat.data, InMat.total() * InMat.elemSize());//copy the data
		Mip.BulkData.Unlock();
		Texture->PostEditChange();
		Texture->UpdateResource();
		return Texture;
	}
	//if the texture hasnt the right pixel format, abort.
	Texture->PostEditChange();
	Texture->UpdateResource();
	return Texture;
}

 

 

언리얼 엔진을 보조모니터에 놓고 실행시키면 주모니터 영역만 나온다.

 

잘나오긴 한데 기존 스크린을 640 x 480으로 해서 그런지 

실제 1920 x 1080 이미지와 맞지 않게 글자가 잘려나온다.

 

 

 

 

스크린(위젯 컴포넌트) 드로 사이즈를 FHD로 고치고

메인 위젯의 이미지 위젯도 FHD로 사이즈 수정

 

 

 

아까보다 화면이 커짐

 

 

 

 

 

 

화면 커지니 글자도 잘나온다.

 

 

 

 

한달 전쯤인가 언리얼에서 opencv사용하겠다고 한참 해맸는데 정리차 다시 남김

어디 자료 참고했는지는 ppt에 남겨놓긴 했는데 여기다 다시 정리하긴 귀찬아서 돌아가는것까지만 바로간다

 

새프로젝트 만들고

 

 

 

 

빈레벨 만들고 플러그인 ㄱㄱ

 

 

 

 

 

 

 

opencv 검색한뒤 체크, yes

재시작 하라면 재시작하고

 

 

 

 

 

대충 새로만든 map을 디폴트로 설정하고

 

 

 

 

C++ Class로

게임모드베이스 desktopgamemodebase

메인위젯 mainwidget

액터 screen

어떻게 만들지는 나중에 보고 일단 이렇게 생성

 

메인위젯은 화면 그리기 용도

스크린은 위젯컴포넌트 추가할 액터

 

 

 

c++ 클래스 다만들고 BP도 생성

 

 

 

 

일단 게임모드베이스에서 웹캠읽어오도록 열고

opencv 인클루드 해보자

 

플러그인 추가했는데도 못찾는다하면

 

리프레시 한번 시켜주자

 

 

 

 

그런데 아직도 안된다.

 

 

 

 

 

빌드cs에opencvhelper, opencv 추가하는걸 잊음 

추가하고 VS끈다음에 다시 리프레시 VS프로젝트하자

 

 

 

그래도 여전히 DesktopGameModeBase에서 문제가 생겼는데

 

해더를 이렇게 안하고

#include "PreOpenCVHeaders.h"
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include "PostOpenCVHeaders.h"

 

평소 opencv 쓸때처럼 opencv.hpp만 넣었기 때문

#include <opencv2/opencv.hpp>

 

 

imgproc나 highgui는 필요없는데

 

PreOpencCVHeaders.h와 PostOpenCVHeaders.h가 꼭 있어야 제대로 빌드된다.

ref : https://forums.unrealengine.com/t/using-unreal-engine-5-0-3-or-5-1-0-built-in-opencv-plugin/744951/2

 

이걸 추가하는 이유는 기억 잘 안나는데 

opencv에서 쓰는거랑 unreal에서 opencv 사용할땐 동작 하는게 다르다고한 일본, 중국인 글을 봤었는데

위 링크 말곤 어디엇는지 몰겟다.

 

 

 

아무튼 mat.hpp도 찾아와 들어와짐

 

 

 

 

웹캠 스트리밍할수 있도록

헤더는 이렇게 작성

 

capture, image는 그렇고

블루프린트로 everytick 마다 read frame 호출하도록 UFUNCTION

유저 위젯에 띄우기 위해서 cv::Mat을 UTexture2*D로 바꾸는 함수추가

타 위젯에 쓸수있도록 imageTexutre를 UPROPERTY 등록

 

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

#pragma once
#include "PreOpenCVHeaders.h"
#include <opencv2/opencv.hpp>
#include "PostOpenCVHeaders.h"


#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "DesktopGameModeBase.generated.h"

/**
 * 
 */
UCLASS()
class HANDDESKTOP_API ADesktopGameModeBase : public AGameModeBase
{
	GENERATED_BODY()

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	cv::VideoCapture capture;
	cv::Mat image;

	UFUNCTION(BlueprintCallable)
	void ReadFrame();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* imageTexture;
	UTexture2D* MatToTexture2D(const cv::Mat InMat);
};

 

 

 

 

 

 

구현 내용은 이런식

MatToTexture2D도 어디서 구글링하다 찾은건데 어디서 했던건지 모르겟네

 

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


#include "DesktopGameModeBase.h"


void ADesktopGameModeBase::BeginPlay()
{
	Super::BeginPlay();
	capture = cv::VideoCapture(0);

	if (!capture.isOpened())
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam failed"));
		return;
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT("Open Webcam Success"));
	}
}


void ADesktopGameModeBase::ReadFrame()
{
	if (!capture.isOpened())
	{
		return;
	}
	capture.read(image);

	imageTexture = MatToTexture2D(image);
}


UTexture2D* ADesktopGameModeBase::MatToTexture2D(const cv::Mat InMat)
{
	//create new texture, set its values
	UTexture2D* Texture = UTexture2D::CreateTransient(InMat.cols, InMat.rows, PF_B8G8R8A8);

	if (InMat.type() == CV_8UC3)//example for pre-conversion of Mat
	{
		cv::Mat bgraImage;
		//if the Mat is in BGR space, convert it to BGRA. There is no three channel texture in UE (at least with eight bit)
		cv::cvtColor(InMat, bgraImage, cv::COLOR_BGR2BGRA);

		//Texture->SRGB = 0;//set to 0 if Mat is not in srgb (which is likely when coming from a webcam)
		//other settings of the texture can also be changed here
		//Texture->UpdateResource();

		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = Texture->GetPlatformData()->Mips[0];
		void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);//lock the texture data
		FMemory::Memcpy(Data, bgraImage.data, bgraImage.total() * bgraImage.elemSize());//copy the data
		Mip.BulkData.Unlock();
		Texture->PostEditChange();
		Texture->UpdateResource();
		return Texture;
	}
	//if the texture hasnt the right pixel format, abort.
	Texture->PostEditChange();
	Texture->UpdateResource();
	return Texture;
}

 

 

 

 

빌드 잘되는걸 확인하면 이제 화면을 띄울준비

(핫 리로드 안되면 수동으로 컴파일)

 

 

 

일단 BP_MainWidget에 들어와서

캔버스 판낼에 이미지 넣고 이름 imageWidget, isVariable 설정, 컴파일

 

 

이번엔 BP_Screen으로 들어와

Add로 Widget Component 추가

위젯 컴포넌트 이름을 Screen으로 설정하고 

 

드로 사이즈는 640 x 480

위젯 클레스는 방금 그린 BP_MainWidget

 

 

맵에 BP_Screen 추가하고 플레이어 스타트 앞에 배치

 

 

 

프로젝트 세팅스에서 GameMode를 아까 만든 BP_DesktopGameModeBase로 설정

 

 

 

 

실행해보면 아까 게임보드베이스에 작성했던 Open Webcam Success가 로그로 나온걸 확인 가능

 

 

 

 

 

 

이제 웹캠 영상을 화면에 띄울 준비

 

데스크탑게임모드베이스 블루프린트에서

이벤트 틱에 리드 프레임 연결

 

 

 

 

 

 

리드프레임에선 이미지 가져와서 텍스쳐로 변환함

 

 

 

 

MainScreen 변수 추가하고

Get All Actors of Class로 뷰포트에 있는 BP_Screen들 가져오기

첫번째(인덱스0) BP_Screen 액터로 Set Widget 준비

Create (BP Main Widget ) widget을 생성 후 MainScreen에 등록

이 MainScreen을 BP_Screen  액터의 Set Widget에 등록

 

 

 

 

메인 스크린으로부터 이미지 위젯 가져오기

이미지위젯 -> SetBrushFrom Texture 호출, 리드 프레임과 연결

블루프린트 리드라이트 설정한 imageTexture를 등록

 

 

 

 

 

실행결과

+ Recent posts