내가 구현하면서 실수한게

블루프린트에서 사용하던 ScreenX, Y는

C++에서 써야되서

 

C++에서 먼저 만들어서 가져와써야됫다.

근데 생각못하고 BP에 만들어서 사용

 

그래서 기존 ScreenX, ScreenY앞에 BP_를 붙여 이름을 구분하고

 

 

CameraPawn 헤더에 따로변수추가

윈API 쓸것이므로 

헤더 추가

커서 이동, 클릭을 위한 함수도 추가.

// 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 <GameFramework/SpringArmComponent.h>
#include <Camera/CameraComponent.h>

#include "Components/PoseableMeshComponent.h"

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CameraPawn.generated.h"

UCLASS()
class HANDDESKTOP_API ACameraPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	ACameraPawn();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PoseableMesh)
	UPoseableMeshComponent* HandRight;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PoseableMesh)
	UPoseableMeshComponent* HandLeft;


	UPROPERTY(VisibleAnywhere, Category = Camera)
	class USpringArmComponent* springArmComp;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
	class UCameraComponent* camComp;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = ScreenCoord)
	int ScreenX = 0;
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = ScreenCoord)
	int ScreenY = 0;

	UFUNCTION(BlueprintCallable, Category=MouseControl)
	void MouseMove();
	UFUNCTION(BlueprintCallable, Category = MouseControl)
	void MouseClick(int status);

};

 

마우스 무브 구현

void ACameraPawn::MouseMove()
{
	SetCursorPos(ScreenX, ScreenY);
}

 

 

 

 

BP스크린xY계산과 커서 이동 중간에

ScreenXY 설정, 마우스 무브도 추가

 

 

 

마우스 커서 이동 확인

 

마지막으로 마우스 클릭 구현 차례인데

 

 

일단 3이하받으면 마우스 이벤트 수행

아니면 안하고

void ACameraPawn::MouseClick(int status)
{
	INPUT input = { 0 };
	input.type = INPUT_MOUSE;

	if (status == 0)
		input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
	else if(status == 1)
		input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
	else if(status == 2)
		input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
	else if(status == 3)
		input.mi.dwFlags = MOUSEEVENTF_RIGHTUP;
	
	if (status <= 3)
		SendInput(1, &input, sizeof(INPUT));
}

 

 

 

 

 

마우스 클릭 이벤트 처리 시점을 생각해봤는데

커서 이동 체크 루틴에서

 

1. 커서 이동 체크시 아닐 겨우 확인 직후 바로

2. 커서 이동 체크시 이동하는 경우 ScreenXY 설정, 마우스 이동이후 바로

다뤄야 할듯

 

 

 

좌클릭의 경우 오른손 검지 중지 붙였을때

우클릭의경우 오른손 약지 소지를 접었을때로 처리

 

상태 다루기위한 ClickStatus, ClickStatusPrev int타입으로 추가

위 C++코드대로

0 : 좌마우스 다운

1 : 좌마우스 업

2 : 우마우스 다운

3 : 우마우스 업

4 : None

으로 5가지로 다룸

 

 

우선 좌클릭은 커서 이동여부 체크때 썻던 내용 활용

해서 하려고하는데

손 겹침과 마우스 입력 경우가 섞여있어서 햇갈린다.

 

나눠서 다시정리하자

 

 

손가락붙임상태

0 검지중지붙음

1 약지소지접힘

2 아무것도 안됨(디폴트)

 

마우스입력상태

0 : 좌마우스 다운

1 : 좌마우스 업

2 : 우마우스 다운

3 : 우마우스 업

4 : None(디폴트)

 

 

손가락붙임상태의 경우 이전, 현재를 따로 놓고 구분하자

 

이전 현재 값이 동일한 경우 4로 mouseClick함수 호출해서

SendInput을 안보내려고하는데

좌마우스 다운상태에서 SendInput시 드래그가 되는건진 모르겟다.

손가락붙임상태_이전 손가락붙임상태_현재 마우스입력상태
2(안붙음) 0(검지중지붙음) 0(좌마우스다운)
0(검지중지붙음) 2(안붙음) 1(좌마우스업)
2(안붙음) 1(약지소지접힘) 2(우마우스 다운)
1(약지소지접힘) 2(안붙음) 3(우마우스 업)
이전 현재 동일한경우 이전 현재 동일한경우 4(None)

 

이를 위해 마우스 스테이터스 변수 추가

 

 

 

 

 

오른손을보고 손가락붙임 상태 보는 BP

0, 1, 2 구분할수 있도록 만들었다.

 

 

 

 

 

 

 

 

그리고 이 표대로 작성 후

마우스 클릭 호출 하는 블루프린트

 

마우스 클릭 동작은 편의를 위해서 커서 움직이지 않는 경우에만 진입

손가락붙임상태_이전 손가락붙임상태_현재 마우스입력상태
2(안붙음) 0(검지중지붙음) 0(좌마우스다운)
0(검지중지붙음) 2(안붙음) 1(좌마우스업)
2(안붙음) 1(약지소지접힘) 2(우마우스 다운)
1(약지소지접힘) 2(안붙음) 3(우마우스 업)
이전 현재 동일한경우 이전 현재 동일한경우 4(None)

 

 

 

 

 

 

 

결과 영상

 

언리얼에서 윈도우 화면 커서 이동을 구현하려고함.

이 다음엔 윈도우 커서따라 언리얼 커서도 따라가야할것

 

 

 

지금까지 제어로직은 이런식

조건 1. 양손 존재 확인

조건 2. 엄지 검지 붙음 : 줌인아웃회전제어

조건 3. 왼손 엄지/검지3이 엄지/검지3과 가까움 : 초기점 설정

 

 

조건1, 2 true : 줌인아웃 회전제어

조건1, 3 true : 초기점 설정

 

 

마우스 커서 이동 조건은 이런식으로 할 예정

1.양손 보임

2. 왼손 검지, 중지 붙음 

 

 

 

 

간단하게 만들어서 확인하니

손 펴서 엄지검지 떨어질땐 30이상

엄지 검지 붙였을땐 20이하

 

손펼때 손가락붙일때

 

 

손가락 붙은 상황

중지끝 diff 계산해서 따라 마우스 이동 시키면 될듯

이를 위해 

LeftMidVecForCursor 벡터 변수 추가

 

0 체크해주고

0아니면 diff 계산

 

 

 

언리얼 전역계 Y diff는 스크린 X

언리얼 전역계  diffZ는 스크린 Z로 해주면 될것같긴하지만

스케일링 필요하지 않은지 먼저 출력해서 확인해봐야할듯해서

 

따로 diff 출력 추가

 

 

확인하려다가 잠깐 빠트린게 마우스 커서 이동 상태인지 체크 안했다.

 

cursor move 추가

 

 

보니까 손 땟다 붙였을때 이전 위치 사용해서 때졋을때 0초기화로 수정

 

 

 

 

diff 출력한거 보니 굳이 스케일링 따로 안해도될듯

 

 

 

이렇게 이동한 위치 따라 커서 이동 구현해야하는데

 

ScreenX/Y 좌표를 월드 좌표계로 변환시켜야한다.

 

 

위아래 이동인 ScreenY부터 생각해보자

 

handdesktop 23번 글에서 Screen_Y 계산하는 식을 이런식으로 정리했었는데

---

Screen_Y = -LT_Z + 1180

---

지금은 라인 트레이싱을 하는게 아니니 LT_Z 대신 Coord_Z로 표기해서 이 값에 대해 수정하면

 

 

Coord_Z = 1180 - Screen_Y가 된다.

 

그러고보니 Screen XY 임계치도 설정해줘야할듯

 

 

 

임계화 시킨건 좋은데 Y를 반대로 하는걸 잊었다.

손이 위로 올라가면 커서가 올라가는 것이므로 Y는 낮아져야함

아무튼 임계화도 정상 동작 확인

 

 

다시 아까 하던걸로 돌아와

스크린 Y로 Coord_Z 지금보니 World_Z가 적당할듯하지만

 

Coord_Z = 1180 - Screen_Y

 

그냥 WorldCursorTF로 트렌스폼 변수 추가

먼저 커서 위치 설정하는 부분에 월드 커서 TF의 LoC와 Rot 설정

(스케일은 0.5)

 

 

다시 돌아와서

 

Coord_Z = 1180 - Screen_Y를 블루프린트로 만들고

 

월드 커서 TF로 커서액터 이동하도록 만들어 테스트하니 정상동작 확인

 

 

 

 

월드커서Z는 했으니 스크린X로 월드커서XY를 구해야한다.

스크린 좌표와 월드 화면의 위치를 같이 보면 이런식이 된다.

 

스크린 X값에 따라 현재 스크린이 무엇인지 판단하고

그에 따라 다르게 동작하도록 하면 될것같다.

 

 

2번 스크린의 경우 간단하다.

ScreenX가 1920 ~ 3840 일때 2번 스크린인데

2번 화면은 회전되어있지 않으므로 

또, 화면과 동일 위치에 있으면 가려지므로 -10 해줌

 

World_X = 1470 - 10

World_Y = ScreenX - 1920 

 

이렇게 동작하도록 BP만들었는데 생긴 문제가

 

커서 초기화 루틴이랑 커서 이동 루틴이랑 엄지, 검지가 까까워지면서 오해한다

 

커서 초기화 루틴 손동작을 엄지검지 대신

약지소지로 변경

 

 

또 이상한게 커서 초기화 후 제어 시작할때

중심점이 아닌 우측 화면 시작점에서 부터 커서가 나온다.

 

World_Y = ScreenX - 1920 을 하면 0으로 가는데 문제는 스크린 2는 Y가 -960부터 시작하기 때문으로보임.

World_Y = ScreenX - 1920 - 960 = ScreenX - 2880 으로 수정

 

 

 

정상동작 하나 옆화면으로는 못넘어간다.

 

 

 

이제 ScreenX로 좌우 스크린 XY를 계산해야한다.

먼저 좌측 스크린부터 생각해보자.

 

내가 라인트레이싱 충돌점으로 ScreenX계산한걸 생각하면 벡터의 놈을 사용했었다.

반대로 ScreenX는 놈값이니

 

좌측스크린 좌측시작점에서 좌측끝점을 향하는 벡터를 구해서

ScreenX(0~1920)를 곱해주면 되지않을까 싶다.

 

 

근데 좌측 스크린의 경우도 겹치는걸 방지하기 위해

BP_Screen2는 World_X = 1470 - 10 해서 -10으로 떨어트렸지만

 

BP_Screen1은 

시작점을 (-185 x -1915) 끝점은 (1475 x -955) 쯤으로 사용하면 될듯

 

 

 

 

요 60도 포워드벡터 가져와출력해보니 0.5, 0.866 나오는데

이걸 사용하면 될것같다.

 

 1920 / 2 = 960 이므로

이 포워드벡터 x 960을 한뒤 -190, -1920 하면 640 x -1440 나올듯 하다

 

아까 실수한게

위에서 보면 첫스크린이 y과 각이 60도 정도 벌어진것 처럼보이는데

 

위에있는 분홍 큐브를 사용해보니 z축에 대해 30도 만 회전하면 됨

요 30의 포워드 벡터 만들어서 계산해보니 정상적으로 BP_Screen1의 중심점이 나온다.

 

 

 

 

Start Point Left 를 xy로 +5해준뒤

요 30도 포워드 벡터와 스크린X를 곱해준것을 더해서

만든결과 정상적으로 스크린1,2를 이동가능해진다.

 

 

아직 안한게

스크린 X에 따라 커서를 회전처리도 필요하다.

 

z축 회전각을

정면에는 0도

스크린1의 경우 -60

스크린2는 60

 

일단 기존 구현에 이런식으로 로테이터 추가했더니

커서가 정상 회전하는걸 확인함.

 

 

 

 

 

 

 

 

이번엔 우측 화면을 하자

 

우측 화면 시작점 X축 기준으로

150도 회전하니 이걸로 포워드 벡터얻으면 될것같고.

 

그 포워드벡터 x 960 + 우측시작점 = 640 x 1440이 되는지 보면된다.

 

 

 

블루프린트로 만든결과

원하던데로 BP스크린3 중심점이 생각한데로 640x1440가 나온다.

 

 

 

 

 

BP_Screen3을 위한 WorldCursorTF 도 만듬

 

 

최종 커서 제어 결과

이제 언리얼에서 마우스 다룰 대부분의 준비는 다된것같다.

마지막으로 윈 API로 마우스 다루기 시작

 

 

먼저 윈도우 위치 가져오는 코드

#include <Windows.h>
#include <iostream>

void GetCursorPosition()
{
    POINT cursorPos;
    if (GetCursorPos(&cursorPos))
    {
        int x = cursorPos.x;
        int y = cursorPos.y;
        // 마우스 커서 위치를 사용하여 원하는 작업 수행
        // 예: 좌표 출력
        std::cout << "Cursor position: x = "<< x << " , y = " << y << std::endl;
    }
}

int main()
{
    int duration = 5; // 프로그램 실행 시간 (초)
    int interval = 500; // 마우스 커서 위치 갱신 간격 (밀리초)

    // 일정 시간 동안 주기적으로 마우스 커서 위치 얻기
    for (int i = 0; i < duration * 1000 / interval; i++)
    {
        GetCursorPosition();
        Sleep(interval);
    }

    return 0;
}

 

 

 

실행 결과 커서 위치 잘가져옴

 

 

 

마우스 커서 이동 시키는 코드

 

#include <Windows.h>
#include <iostream>

void GetCursorPosition()
{
    POINT cursorPos;
    if (GetCursorPos(&cursorPos))
    {
        int x = cursorPos.x;
        int y = cursorPos.y;
        // 마우스 커서 위치를 사용하여 원하는 작업 수행
        // 예: 좌표 출력
        std::cout << "Cursor position: x = " << x << " , y = " << y << std::endl;
    }
}

void MoveCursorTo(int x, int y)
{
    SetCursorPos(x, y);
}

int main()
{
    int startX = 80; // 시작 x 좌표
    int startY = 80; // 시작 y 좌표
    int duration = 5; // 프로그램 실행 시간 (초)
    int interval = 500; // 좌표 갱신 간격 (밀리초)
    int step = 20; // x 좌표 증가량

    int targetX = startX;
    int targetY = startY;

    // 일정 시간 동안 주기적으로 x 좌표를 증가시켜 마우스 커서 이동
    for (int i = 0; i < duration * 1000 / interval; i++)
    {
        targetX += step;
        MoveCursorTo(targetX, targetY);
        GetCursorPosition();
        Sleep(interval);
    }

    return 0;
}

 

 

보조 모니터(2번)에서 마우스 좌클릭 우클릭 반복하며 이동하는 코드

 

#include <Windows.h>
#include <iostream>


void MoveCursorTo(int x, int y)
{
    SetCursorPos(x, y);
}

void MouseClick(int button)
{
    INPUT input = { 0 };
    input.type = INPUT_MOUSE;
    input.mi.dwFlags = button;

    SendInput(1, &input, sizeof(INPUT));
}

int main()
{
    int startX = 2500; // 시작 x 좌표
    int startY = 80; // 시작 y 좌표
    int duration = 8; // 프로그램 실행 시간 (초)
    int interval = 500; // 좌표 갱신 간격 (밀리초)
    int step = -20; // x 좌표 감소량

    int targetX = startX;
    int targetY = startY;

    // 일정 시간 동안 주기적으로 x 좌표를 감소시켜 마우스 커서 이동 후 클릭
    for (int i = 0; i < duration * 1000 / interval; i++)
    {
        targetX += step;

        MoveCursorTo(targetX, targetY);
        if (i%2 == 0)
            MouseClick(MOUSEEVENTF_RIGHTDOWN);
        else
            MouseClick(MOUSEEVENTF_RIGHTUP);
        Sleep(interval);
    }

    return 0;
}

 

 

 

 

 

 

 

 

 

 

 

커서 이동하기전에

일단 화면 중심점을따라 라인트레이싱해서

충돌된 스크린 좌표 가져오는 내용을 했었는데

이렇게 구한 YZ 좌표를 스크린의 XY계로 변경할수 있어야함.

 

정면 스크린의 경우

1470, 0, 640에 위치하고 있으니

 

1920x1080의 절반인 960 x540이 화면 중점 좌표고

아래의 사진의 경우 Y=533이므로

 

저 빨간점의 좌표는 (960 + 533) x 540 -> 1493 x 540쯤 될것이다.

 

 

 

 

 

하지만 좌우에 위치한 기울어진 화면의 경우 문제가 된다.

충돌 지점의 위치가 1064, 1182, 640인데

이걸 어떻게 할까.

 

 

 

 

 

스크린 바로 위에 큐브를 배치해서 대강 중점과 코너 위치를 정리했다.

큐브 z는 1200이라 생략하고 XY만 작성

 

 

 

 

위에 스크린 3에 라인트레이싱 한 결과는

충돌 지점의 위치가 1064, 1182, 640였으므로

 

대충 파란색 지점쯤 될것인데 이걸 어떻게 계산시킬까

영상처리에서 어파인변환 같은느낌으로 해야할것같기도하고.

 

 

 

그냥 가장 간단하게 생각하면

먼저 시작점과 파란점 사이 각도 세타를 구하고

위 그림의 파란점을 - 시작점 한뒤

파란점을 세타만큼 역방향 회전시키면 진짜 위치를 구할수 있을것같다.

 

 

 

오른쪽스크린, 왼쪽스크린 시작점 벡터 추가

각 스크린 시작점은 맨 왼쪽에 있는 지점 사용

 

 

 

 

 

일단 BP_Screen2부터 생각해보자.

 

아까 이사진의 충돌점을 화면상 좌표를 계산했었는데

----

1920x1080의 절반인 960 x540이 화면 중점 좌표고

아래의 사진의 경우 Y=533이므로

저 빨간점의 좌표는 (960 + 533) x 540 -> 1493 x 540쯤 될것이다.

---

스크린X는

Screen_X = (960 + LT_Y)

LT_Y = 533

=> Screen_X = 1493

 

스크린Y는

540 - (LT_Z - 540) = 1080 - LT_Z 

LT_Z  는 640이므로 

=> Screen_Y = 440

?

 

인가 싶은데 좀햇갈린다.

 

 

 

LT_Z=640  일때 Screen_Y = 540

LT_Z= 1180 일땐 Screen_Y = 0

LT_Z = 0    이면 Screen_Y = 1080

이 되어야 한다.

 

이에 대한 방정식은

 

Screen_Y = -LT_Z + 1180이 된다.

스크린 X는 아까 구했고

Screen_X = 960 + LT_Y

 

 

이제 좌표 계산하려고하는데

가져오기 힘들어서 따로 HitLoc 벡터 추가해서사용

 

 

ScreenX, ScreenY int 변수 추가해서 하면 잘동작하는데

 

 

라인트레이싱에서 월드 다이나믹스로 충돌 감지하도록하다보니

BP_Cursor 충돌을 NoCollision으로 해야함.

 

 

 

 

이제 아까 하던 오른쪽 화면을 해보자.

먼저 충돌점 - 시작점 한후 세타부터 계산

해봣는데

 

어딜가든 -60이 나온다.

당연한게 스크린을 -120(뒤집혀서 이럼) 회전시켜서 스크린상 충돌지점은 어딜가나 똑같음

 

 

 

그래서 atan2할필요없이 그냥 60도 회전하면 될줄 알았는데 뭔가 잘안된다.

충돌점, 시작점 차벡터를 구해서 z축 60도 로테이터로 벡터 회전만 시켜주면 끝인줄 알았는데

전과 후나 차이가 없음.

 

그래서 손계산 하다가 생각난 방법은

 

충돌-시작점 차벡터의 놈을 Screen_X로 쓰면될듯하다.

 

벡터 랭스로 놈을 구해서 쓰니 원하던 X축 좌표가 잘나온다.

 

 

 

 

이번에는 좌측 화면 좌표를 잡으려하는데

좌측 화면 중심이 충돌점이라 치자

 

그러면 좌측화면 시작점을 0,0으로 보내서 놈 계산하려면

여기도 충돌점 - 시작점하면 될듯

 

 

 

 

 

좌측 화면도 정상 동작 확인해서

최종 정리하면 이런식이 될듯하다

 

정면 스크린은 x로 +1920

우측스크린은 x로 + 3840

 

 

중간에 추가한 스크란 좌표 출력

 

 

 

실제 스크린에서 비치할 좌표가 정상적으로 나온다.

 

 

 

일단 지난 글까지 카메라 중심으로 부터 간단하게 라인트레이싱하는것까지만 봤는데

윈 api로 마우스 커서 이동시키기전에 먼저 커서 매시 만들어서 위치시키려고한다.

 

 

히트인포를 분할시켰더니 이런식으로 나오는데

히트 거리, 로케이션 정도면 충분할듯

 

 

일단 거리, 위치는 잘 나오는듯 한데

 

커서를 dist -20 지점쯤에 띄워주면 될듯

 

 

 

 

 

블렌더로 대충 커서로 쓸 삼각형 만들고

게임에 대충 추가

 

 

커서를 배치할때

스크린1은 120도

스크린2는 0도

스크린3은 -120도 회전시켜야 하므로

 

 

충돌 여부와 충돌 액터이름 출력 추가

 

 

 

무슨 액터와 충돌했는지 나오니

커서도 잘 회전시킬수 있을듯

 

 

 

 

 

 

근데 저 커서를 가져오려고하니 액터가 아니라 bp에서 접근이 안되니 엑터로 만들어 사용

 

 

 

 

커서가 스크린에 따라 회전되니 커서 위치도 바꿔줄 차례

 

라인 트레이싱 지점에서 살짝 스크린 앞으로 나온 지점으로 커서 이동

 

 

중심점으로 게임 커서 이동, 스크린에 따라 회전 결과

 

 

 

 

이제 대강 카메라 제어도 만들었겠다.

손으로 마우스 제어를 하려고한다.

 

그런데 우선 두손을 주먹쥐면

 

카메라 방향 라인트레이싱해서

화면과 충돌된 지점의 위치를 가져와서

그 지점으로 마우스 커서 이동시키려고 함.

 

하다보니 

 

윈api DC에선 마우스 커서가 보이질 않는다.

따로 커서 매시를 화면에 띄워줘야 될것같은느낌

 

 

 

주먹을 완전히 쥐었더니

카메라 제어상황과 오해해서

 

 

엄지는 쥐지않고 이정도 자세가 적당한듯

 

b_l_index1 은 b_l_index3과

b_l_middle1 은 b_l_middle3과

b_l_ring1 은 b_l_ring3과

b_l_pinky1 은 b_l_pinky3과

 

근데 이 4손가락 다 보는건 너무 내용이 많아지므로

 

index, middle 두손가락이 1, 3이 서로 가까운지 보면 될듯.

근데 생각해보니 양손다 이상황을 판단시키기엔 양이 많아질것같아

왼손만 시켜야할듯하다.

 

 

상태들은 따로 불리언 변수로 빼고

상태, 검지 중지 거리 출력 bp

 

위 루틴으로는 두손 존재 + 줌인아웃,좌우회전이 아닐때 넘어간다.

 

 

 

 

검지 거리가 안나오고, 

출력이 줌인아웃 아닐때만 출력됨.

 

보니 index1, index1 가져오도록했었음.

 

이 루틴에는 거리만 출력하고

 

상태 초기화 전에 

기존에 저장된 상태값 출력하도록 수정

 

 

이상한게 엄지검지 붙은 상태에서 줌인아웃, 안할때만 출력된다.

원래 의도는 아닌데

생각해보니 attach false때하면 되는걸 잊음

attach false때 들어가도록 수정

 

 

 

쥐었을때 거리가 아주 커도 40이하는 되고

평소 손을 폈을때 5~60쯤 된다.

 

 

 

 

일단 커서 상태 저장하는 부분까지하고

라인트레이싱 시작

 

 

 

 

일단 대충 라인 트레이싱 

 

 

 

 

 

 

 

이렇게 글이 많아질줄 몰랐는데

벌써 20번째 글

 

이번에는 손 재스처로 카메라 줌인줌아웃, 좌우회전을 제어하도록 구현하려고한다.

 

 

 

 

양손의

index3, thumb3 이 가까이(붙을때) 있을때

 

시작 

 

시작 시점 기준으로

좌우로 멀어지면 줌인

좌우로 가까워지면 줌아웃 

 

 

 

일단 양손의 저 지점(월드계) 사이 거리 출력

 

 

대충 딱 붙이면 커도 40이하 정도 나오고

손 안붙이면 50 이상은 나온다.

 

 

 

 

두손의 엄지 검지가 붙었을때 구분하도록할때 잘동작은 하지만

손이 존재하지 않을때도 관절상 붙어있으면 붙은걸로 오해하고 있어 수정 필요

 

 

이 상황을 생각하고 코드작성을 못해서 좀 지저분해졌지만 일단 진행

 

 

블라즈 헤더

	bool isExistLeft;
	bool isExistRight;

반정규화 본 위치 함수에

처음엔 false하되

중간에 찾으면 true 하도록 추가

 

std::vector<cv::Mat> Blaze::DenormalizeHandLandmarksForBoneLocation(std::vector<cv::Mat> imgs_landmarks, std::vector<cv::Rect> rects)
{

    std::vector<cv::Mat> denorm_imgs_landmarks;
    isExistLeft = false;
    isExistRight = false;

.....



        if (isLeft)
        {
            for (int j = 0; j < squeezed_for_bone.size[0]; j++)
            {
                squeezed_for_bone.at<float>(j, 0) = (squeezed_for_origin.at<float>(j, 1) - squeezed_for_origin.at<float>(0, 1)) * skeletonXRatio * 2 * -1;
                squeezed_for_bone.at<float>(j, 1) = (squeezed_for_origin.at<float>(j, 0) - 0.5) * skeletonYRatio * 2;
                squeezed_for_bone.at<float>(j, 2) = squeezed_for_origin.at<float>(j, 2) * skeletonYRatio * 3 * -1;
                //UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone %d : %f, %f, %f"), j, squeezed_for_bone.at<float>(j, 1), squeezed_for_bone.at<float>(j, 2), squeezed_for_bone.at<float>(j, 3));

            }
            handLeft = squeezed_for_bone.clone();
            handLeftImg = squeezed_for_img.clone();
            isExistLeft = true;
        }
        else
        {
            for (int j = 0; j < squeezed_for_bone.size[0]; j++)
            {
                squeezed_for_bone.at<float>(j, 0) = (squeezed_for_origin.at<float>(j, 1) - squeezed_for_origin.at<float>(0, 1)) * skeletonXRatio * 2;
                squeezed_for_bone.at<float>(j, 1) = (squeezed_for_origin.at<float>(j, 0) - 0.5) * skeletonYRatio * 2;
                squeezed_for_bone.at<float>(j, 2) = squeezed_for_origin.at<float>(j, 2) * skeletonYRatio * 3;
                //UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone %d : %f, %f, %f"), j, squeezed_for_bone.at<float>(j, 1), squeezed_for_bone.at<float>(j, 2), squeezed_for_bone.at<float>(j, 3));
            }

            handRight = squeezed_for_bone.clone();
            handRightImg = squeezed_for_img.clone();
            isExistRight = true;
        }
        ....

 

 

 

 

게임모드 헤더

	UPROPERTY(BlueprintReadWrite, Category = "HandDetected")
	bool isExistLeft;
	UPROPERTY(BlueprintReadWrite, Category = "HandDetected")
	bool isExistRight;

	void check_hand_exist(std::vector<cv::Mat>& imgs_landmarks);

게임모드 구현

 

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

	/*
	get filtered detections
	*/
	blaze.ResizeAndPad(webcamImage, img256, img128, scale, pad);
	//UE_LOG(LogTemp, Log, TEXT("scale value: %f, pad value: (%f, %f)"), scale, pad[0], pad[1]);
	std::vector<Blaze::PalmDetection> normDets = blaze.PredictPalmDetections(img128);
	std::vector<Blaze::PalmDetection> denormDets = blaze.DenormalizePalmDetections(normDets, webcamWidth, webcamHeight, pad);
	std::vector<Blaze::PalmDetection> filteredDets = blaze.FilteringDets(denormDets, webcamWidth, webcamHeight);





	std::vector<cv::Rect> handRects = blaze.convertHandRects(filteredDets);
	std::vector<cv::Mat> handImgs;
	blaze.GetHandImages(webcamImage, handRects, handImgs);
	std::vector<cv::Mat> imgs_landmarks = blaze.PredictHandDetections(handImgs);
	std::vector<cv::Mat> denorm_imgs_landmarks = blaze.DenormalizeHandLandmarksForBoneLocation(imgs_landmarks, handRects);

	check_hand_exist(imgs_landmarks);

 

void ADesktopGameModeBase::check_hand_exist(std::vector<cv::Mat>& imgs_landmarks)
{
	if (imgs_landmarks.size() == 0)
	{
		isExistLeft = false;
		isExistRight = false;
	}
	else
	{
		isExistLeft = blaze.isExistLeft;
		isExistRight = blaze.isExistRight;
	}
}

 

 

 

두손이 존재하고,

두손의 엄지검지가 가까울때 찾기

 

 

추론 결과 손을 놓칠때가 있어 아쉽긴하지만 

일단 동작은가능

 

 

 

 

 

일단 여기까지 했으니

줌인 줌아웃 부터 해보자

 

두손을 밖으로 밀면 줌인

두손을 안으로 밀면 줌아웃

 

 

 

어캐 구현할까 고민되는게

컴포넌트계로 가져오면 두 손사이 거리가 안되서

월드계 가져오긴해야하는데

 

단순 y거리로 하자니 회전된 상태때문에 고민이된다.

 

 

그래서 월드계 위치 가져와서

 

두손 엄지

줌인아웃의 경우 z를 제외한 xy축값만으로 거리 계산

회전의 경우 xy를 제외한 z축 값만으로 거리 계산하면 될듯

 

 

변수로

현재붙음, 이전붙음

현재xy거리, 시작시점xy거리

현재xy거리, 시작시점z거리

 

추가

 

 

두손 존재여부확인과

 

두손가락 붙음여부 확인 

 

 

양손다 두손가락 붙으면

 

엄지검지간 거리 설정

xy, z거리

 

 

떨어졌다가붙을때 시작 거리설정

계속 붙어있을때 줌, 회전 여부 판단

 

 

xy거리가 늘면 줌인, 줄면 줌아웃

 

오른손 왼손엄지 z 큰것 찾아 좌회전우회전판단

 

 

손 놓치면서 오동작 하긴하는데 대충 되긴한다.

 

그러면 손을 카메라 앞에 놓아야 하는데 어디다 놓는게 적당할까?

 

지금까지는 왼손과 오른손 클래스를 따로 만들어서 했는데

 

그냥 카메라 폰에다가 생성시켜서 쓰는게 편할것같다.

 

그전에 잠깐

로컬 좌표로 카메라 이동시키기 테스트

 

 

 

대충 이렇게 셋 렐레티브 로케이션으로 하면

 

 

 

손 오일러각, 컴포넌트 공간 다룰때 했던것 처럼

월드 좌표계가 아닌 로컬 좌표계로 이동됨

 

카메라 폰 전역계 -65 도 회전시킨 상태에서

위 상대 좌표로 전진시켰을때 나오는 화면 

 

 

 

 

이제 양 손을 카메라 폰에 추가시키자

각 손에 포저블 컴포넌트 만들어서 스켈레탈 메시 등록하기

 

카메라폰 

#pragma once
#include "Components/PoseableMeshComponent.h"

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "CameraPawn.generated.h"

UCLASS()
class HANDDESKTOP_API ACameraPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	ACameraPawn();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PoseableMesh)
	UPoseableMeshComponent* HandRight;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PoseableMesh)
	UPoseableMeshComponent* HandLeft;

};

 

#include "CameraPawn.h"

// Sets default values
ACameraPawn::ACameraPawn()
{
 	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;



	// 스켈레탈 메시 컴포넌트 생성 및 부착
	ConstructorHelpers::FObjectFinder<USkeletalMesh> TempHandLeftMesh(
		TEXT("SkeletalMesh'/Game/MyContent/Hand_L.Hand_L'")
	);
	HandLeft = CreateDefaultSubobject<UPoseableMeshComponent>(TEXT("HandLeft"));

	HandLeft->SetSkeletalMesh(TempHandLeftMesh.Object);
	HandLeft->SetRelativeLocation(FVector(0, 0, 0));


	// 스켈레탈 메시 컴포넌트 생성 및 부착
	ConstructorHelpers::FObjectFinder<USkeletalMesh> TempHandRightMesh(
		TEXT("SkeletalMesh'/Game/MyContent/Hand_R.Hand_R'")
	);
	HandRight = CreateDefaultSubobject<UPoseableMeshComponent>(TEXT("HandRight"));

	HandRight->SetSkeletalMesh(TempHandRightMesh.Object);
	HandRight->SetRelativeLocation(FVector(0, 0, 0));
}

// Called when the game starts or when spawned
void ACameraPawn::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void ACameraPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void ACameraPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}

 

 

 

 

이 상태로 빌드하면

일단 손이 추가된걸 볼수있다.

 

손 위치를 정리해야하는데

 

이전에 카메라 0,0,0을 했을때 손 x를 200으로 준걸로 기억하는데 이쯤에 일단 대충 놓으면 될거같고

 

 

 

왼손, 오른손 메시는 서로 반대로 뒤집혀있기때문에

아래만큼 회전시켜줘야한다.

 

왼손 오른손

 

 

 

 

카메라가 이상하게나와서 보니

bp에서 만든 카메라 컴포넌트가 손 하위로 내려간다.

 

 

카메라 컴포넌트도 그냥 c++로 추가

해서 해봣는데

루트컴포넌트가 아니어서그런지 컴포넌트계 기준으로 가지 않고 

원점기준으로 가버린다.

 

 

 

아쉬운대로 액터로케이션 + 포워드 벡터

이용해서

손 앞에 배치하고

 

 

회전 같은 경우에는 set rotation 2번해서는 마지막껏만 적용됨

찾아보니 컴바인 로테이터가 존재하여 사용

 

 

 

회전시 손 따라가기는 해결했으나. 

반정규화된 블라즈 핸드 xy와 월드 공간상 좌표 매칭이 문제

 

 

 

 

 

 

잡기너무힘들다

 

 

 

 

 

잡기 너무힘들어서 정면에 놓고 테스트

정면은 이런식으로 좌표가 되는데

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

회전 재정리

 

https://ghebook.blogspot.com/2020/08/blog-post.html

 

get actor forward vector -> 1, 0, 0

 

yaw -30도 -> 0.866, -0.5, 0

yaw -60도 -> 0.5, -0.86

 

yaw 30도 -> 0.866, 0.5, 0

ywa 60도 -> 0.5, 0.866, 0

 

 

회전하다보니 z축 이동은 지금도 가능하다싶네

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

게임폰은 -341,0,641

 

641 - 450 = 191

 

100은 오프셋 - 해서 위아래 이동 수정

 

 

 

 

 

 

 

 

 

 

잠깐 좌표 상황 정리하고

 

 

 

 

 

 

 

 

 

갯 포워드 백터니 라이트 백터니 대신

그냥 xyz 다합친뒤 rotate vector로 처리

 

 

 

 

 

손따라오는게 이상한데 회전을 원점 중심으로 회전해서 그런듯

이걸 해결할 방법은 대충 이해는 하고있는데 생각만큼 안되서 포기하고

그냥 전체 요소들을 플레이어 시작점을 원점이 되도록 밀어 해결

 

양손 추적 확인 

 

 

 

이제 거의다 구현된듯 하지만 카메라 전 후진에따라 위치 반영을 시켜주지 않았다.

 

카메라와 액터가 원점에 있지만

줌인 줌아웃 에따라 카메라를 전후진 시켜줄것이므로 이에 따라 손위치도 따라가야함

 

 

지금 손은 원점 기준 400 만큼 앞에가있도록 되어있어

원점 대신 카메라 기준 400만큼 앞에 가도록 고쳐야함

 

 

 

 

 

 

카메라 컴포넌트 위치에 따라 손 따라가도록 추적

카메라 폰 자체 위치는  원점 그대로

 

 

 

 

 

 

 

 

 

손 방향 설정

 

 

 

왼손 - 관절 위치 설정, 손 위치 설정

 

오른손 - 관절 위치 설정, 손 위치 설정

 

 

 

 

 

 

 

 

 

마우스 제어, 카메라 제어 뭘 할까 고민하다가

카메라 제어 먼제 시도

 

 

기존에 손 제어는 시작점을 200, 650, 600 으로 설정하고

반정규화 결과에 때라 y, z 로 변화를 주었음

 

 

 

 

 

 

 

세 스크린 위치는 

 

 

 

 

카메라 springarm 000에 놓고

카메라폰을 150, 0, 640에 위치시키면

정면 화면이 꽉차보임

-> 플레이어 스타트를 150, 0, 640 배치

 

 

 

 

 

카메라를 액터 000에 놓진 않을거고

카메라 액터 시작점을 정해야함

다른 화면도 잘 보여야 하므로

다른 화면은 어느 위치 각도여야 최대 화면이 되는지 찾아야함

 

일단 정면의 경우 970 일때 최대화면

옆화면도 중심점 접선과 970 지점을 찾아야함

 

 

 

 

그리다보니 생각난게 두 직선의 접점을 카메라 원점으로 사용하면된다.

 

 

 

이거 바로 계산할 방법이 없어서 gpt 한태 도움

 

 

 

 

x 290, y -1440 점을 지나는 기울기가 60도인 직선의 방정식을 알려줘

 

 

주어진 정보를 바탕으로 기울기 60도인 직선의 방정식을 구해보겠습니다.

주어진 점 (x1, y1) = (290, -1440)과 기울기 60도를 이용하여 직선의 방정식을 구할 수 있습니다. 기울기 60도는 라디안으로 변환하여 계산해야 합니다. 60도는 π/3 라디안과 같습니다.

직선의 방정식은 일반적으로 y = mx + c 형태로 나타낼 수 있습니다. 여기서 m은 기울기, c는 y 절편을 나타냅니다.

먼저, 주어진 기울기를 라디안으로 변환합니다:

60도 * π / 180 = 1.047 라디안

기울기가 1.047 라디안이므로, 방정식은 다음과 같습니다:

y = tan(1.047) * x + c

점 (290, -1440)를 대입하여 c를 구할 수 있습니다:

-1440 = tan(1.047) * 290 + c

이를 통해 c를 구하면 방정식을 완성할 수 있습니다.

계산 결과를 위해 적절한 프로그래밍 언어나 계산기를 사용하여 수식을 계산할 수 있습니다.

 

 

 

 

 

답을 받앗는데 tan안에 라디안을 넣는게 맞는지 가물가물하다

삼각함수 공식은 이런식이었는데

 

https://johnleeedu.tistory.com/33

 

 

 

 

 

 

 

 

60도는 1.047 라디안인데

 

tan(60)은 약 1.732입니다.

tan(1.047)은 약 1.732입니다.

 

둘다 같은걸봐선

대충

y = 1.732 x - 1942 일듯하지만

 

x에 290을 넣으면 y = 1439.72가 나오긴한데

 

y = tan(60)x - 1942.68이 대충 맞을듯싶다.

 

 

 

가 아니라 법선을 계산해야되는걸 잊엇는데

 

tan(-30)이며 290, -1440를 지나는 직선으로 찾아야할듯

 

 

 

 

 

 

 

----

주어진 방정식 y = tan(-30)x + c에서 x = 290, y = -1440일 때, c를 계산해보겠습니다.

우선, -30도는 -π/6 라디안입니다. tan(-π/6)은 -√3 / 3입니다. 이를 이용하여 방정식을 정리합니다.

-1440 = (-√3 / 3) * 290 + c

-1440 = (-290√3 / 3) + c

c = -1440 + (290√3 / 3)

c ≈ -197.29

따라서, 직선 y = tan(-30)x + c가 점 (290, -1440)을 지날 때, c는 약 -197.29입니다.

-----

 

tan(30) = root(3) / 3 이므로 -30도일땐 - root(3)/3이 맞는듯

 

계산한 직선 방정식은

y = tan(-30)x - 197.29

 

y=0을 대입하면

 

---

주어진 방정식 y = tan(-30)x - 197.29에서 y = 0일 때, x의 값을 계산해보겠습니다.

먼저, 방정식을 정리하여 x에 대한 식을 도출합니다:

tan(-30)x = 197.29

x = 197.29 / tan(-30)

-30도는 -π/6 라디안이므로, tan(-30)은 -√3 / 3입니다. 이를 이용하여 계산합니다.

x = 197.29 / (-√3 / 3)

x = -197.29√3

따라서, 주어진 방정식에서 y = 0일 때, x의 값은 약 -341.16입니다

----

 

 

대충 -341, 0을 원점으로 사용하면 될듯

 

 

 

 

 

 

 

 

카메라 엑터

341,0,640에 놓고 -60도 회전 시키면 이런식으로 나옴

 

 

 

로컬 좌표계로 바꿔서 970만큼 x전진시켜주면

전진 시키려했는데 뷰포트에서는 스켈레톤에디터와 다르게

로컬계 x 970 전진이 안된다.

 

 

 

 

아쉬운데로 카메라 폰에서 970 전진시켜서보니

너무 크게 나온다.

 

 

 

 

 

 

 

카메라 폰 내 카메라 위치랑

뷰포트 좌표에서 이동시켯을때랑 좀 다른데

 

 

 

 

 

카메라를 480, 0, 0에놓고

 

 

 

 

0도

 

 

-65도

 

 

 

65도

 

 

 

 

했을때 가장 잘 보이므로

 

 

 

월드에서 카매라 폰 위치는 -341, 0, 640으로 잡고

 

카메라 액터내 최대 전진거리는 500

최소 전진거리는 0으로 잡는게 적당할듯

 

게임 실행중 카메라 폰 회전시켰을대 나오는 화면

 

 

 

이전 글에서 파이썬으로 왼손 랜드마크 데이터를 어떻게 반정규화 시킬지를 다루어서

이제 c++로 구현하려고 한다.

 

근데 먼저할께 블라즈 핸드로 찾은 손이 왼손인지 오른손인지 먼저 판단할 필요가 있어보인다.

 

지난글에 만든 반정규화 , 시각화 코드

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd


%matplotlib widget

# 3차원 산점도 그리기
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

denormalized_landmark = normalized_landmark.copy()
denormalized_landmark[:,0] = 16.5 * (normalized_landmark[:, 1] - normalized_landmark[0, 1]) * -1
denormalized_landmark[:,1] = (normalized_landmark[:, 0] - 0.5) * 11.6 * -1
denormalized_landmark[:,2] = normalized_landmark[:, 2] * 11.6 * -1

x = denormalized_landmark[:, 0]
y = denormalized_landmark[:, 1]
z = denormalized_landmark[:, 2]

# 색상 지정
colors = ['black'] + ['red'] * 4 + ['orange'] * 4 + ['yellow'] * 4 + ['green'] * 4 + ['blue'] * 4
ax.scatter(x, y, z, c=colors)


#손가락
colors = ['red', 'orange', 'yellow', 'green', 'blue']
groups = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18, 19, 20]]
for i, group in enumerate(groups):
    for j in range(len(group)-1):
        plt.plot([x[group[j]], x[group[j+1]]], [y[group[j]], y[group[j+1]]], [z[group[j]], z[group[j+1]]], color=colors[i])
#손등
lines = [[0, 1], [0, 5], [0, 17], [5, 9], [9, 13], [13, 17]] 
for line in lines:
    ax.plot([x[line[0]], x[line[1]]], [y[line[0]], y[line[1]]], [z[line[0]], z[line[1]]], color='gray')



ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')


ax.set_xlim(-1, 17)
ax.set_ylim(7, -7)
ax.set_zlim(0, 2)


plt.show()

 

 

 

 

 

기존의 블라즈에 만든 반정규화 코드는

추론에 사용한 상자 폭/너비을 곱해준다음 위치 조정을 위해 xy를 더해주는 식으로 만들었는데

 

std::vector<cv::Mat> Blaze::DenormalizeHandLandmarks(std::vector<cv::Mat> imgs_landmarks, std::vector<cv::Rect> rects)
{
    std::vector<cv::Mat> denorm_imgs_landmarks;

    for (int i = 0; i < imgs_landmarks.size(); i++)
    {
        cv::Mat landmarks = imgs_landmarks.at(i);
        cv::Rect rect = rects.at(i);
        cv::Mat squeezed = landmarks.reshape(0, landmarks.size[1]);

        //std::cout << "squeezed size: " << squeezed.size[0] << "," << squeezed.size[1] << std::endl;

        for (int j = 0; j < squeezed.size[0]; j++)
        {
            squeezed.at<float>(j, 0) = squeezed.at<float>(j, 0) * rect.width + rect.x;
            squeezed.at<float>(j, 1) = squeezed.at<float>(j, 1) * rect.height + rect.y;
            squeezed.at<float>(j, 2) = squeezed.at<float>(j, 2) * rect.height * -1;

        }
        denorm_imgs_landmarks.push_back(squeezed);
    }

    //std::cout << std::endl;
    return denorm_imgs_landmarks;
}

 

 

 

블라즈 헤더에 이렇게 추가하고

	// var, funs for left, right lms and bone location
	float skeletonXRatio = 16.5;
	float skeletonYRatio = 11.6;
	cv::Mat handLeft;
	cv::Mat handRight;
	void DenormalizeHandLandmarksForBoneLocation(std::vector<cv::Mat> imgs_landmarks);
};

 

21행, 3열로 초기화할때 설정

Blaze::Blaze()
{
	this->blazePalm = cv::dnn::readNet("c:/blazepalm_old.onnx");
	this->blazeHand = cv::dnn::readNet("c:/blazehand.onnx");


    handLeft = cv::Mat(21, 3, CV_32FC1);
    handRight = cv::Mat(21, 3, CV_32FC1);
}

 

 

 

 

구현은 이런식으로 하고 

중간에 좌측 손인지 우측손인지 판단해야하는데

 

void Blaze::DenormalizeHandLandmarksForBoneLocation(std::vector<cv::Mat> imgs_landmarks)
{
    for (int i = 0; i < imgs_landmarks.size(); i++)
    {
        cv::Mat landmarks = imgs_landmarks.at(i);
        cv::Mat squeezed = landmarks.reshape(0, landmarks.size[1]);

        //std::cout << "squeezed size: " << squeezed.size[0] << "," << squeezed.size[1] << std::endl;

        for (int j = 0; j < squeezed.size[0]; j++)
        {
            squeezed.at<float>(j, 0) = (squeezed.at<float>(j, 1) - squeezed.at<float>(0, 1)) * skeletonXRatio;
            squeezed.at<float>(j, 1) = (squeezed.at<float>(j, 0) - 0.5) * skeletonYRatio * -1;
            squeezed.at<float>(j, 2) = squeezed.at<float>(j, 2) * skeletonYRatio * -1;

        }
    }
}

 

 

전혀 조정하지 않은 정규화된 데이터 기준으로 생각하자

 

왼손의 경우 엄지손끝 4번 x가 새끼손끝 20번 x보다 크므로

 

 

 

 

 

 

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd


%matplotlib widget

# 3차원 산점도 그리기
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

x = normalized_landmark[:, 0]
y = normalized_landmark[:, 1]
z = normalized_landmark[:, 2]

# 색상 지정
colors = ['black'] + ['red'] * 4 + ['orange'] * 4 + ['yellow'] * 4 + ['green'] * 4 + ['blue'] * 4
ax.scatter(x, y, z, c=colors)


#손가락
colors = ['red', 'orange', 'yellow', 'green', 'blue']
groups = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18, 19, 20]]
for i, group in enumerate(groups):
    for j in range(len(group)-1):
        plt.plot([x[group[j]], x[group[j+1]]], [y[group[j]], y[group[j+1]]], [z[group[j]], z[group[j+1]]], color=colors[i])
#손등
lines = [[0, 1], [0, 5], [0, 17], [5, 9], [9, 13], [13, 17]] 
for line in lines:
    ax.plot([x[line[0]], x[line[1]]], [y[line[0]], y[line[1]]], [z[line[0]], z[line[1]]], color='gray')



ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')


plt.gca().invert_xaxis()
plt.show()

 

 

 

 

 

isLefthand 함수로 정규화된 랜드마크 받아서

결과에 따라 왼손맷 오른손맷에 저장

 

오른손의 경우 컴포넌트 공간에서 왼손이랑 다른탓에 좀다르게 반정규화해야하지만

일단 넘어가자.

void Blaze::DenormalizeHandLandmarksForBoneLocation(std::vector<cv::Mat> imgs_landmarks)
{
    for (int i = 0; i < imgs_landmarks.size(); i++)
    {
        cv::Mat landmarks = imgs_landmarks.at(i);
        cv::Mat squeezed = landmarks.reshape(0, landmarks.size[1]);

        bool isLeft = IsLeftHand(squeezed);

        //std::cout << "squeezed size: " << squeezed.size[0] << "," << squeezed.size[1] << std::endl;

        for (int j = 0; j < squeezed.size[0]; j++)
        {
            squeezed.at<float>(j, 0) = (squeezed.at<float>(j, 1) - squeezed.at<float>(0, 1)) * skeletonXRatio;
            squeezed.at<float>(j, 1) = (squeezed.at<float>(j, 0) - 0.5) * skeletonYRatio * -1;
            squeezed.at<float>(j, 2) = squeezed.at<float>(j, 2) * skeletonYRatio * -1;

        }
        if (isLeft)
            squeezed.copyTo(handLeft);
        else
            squeezed.copyTo(handRight);
    }
}

bool Blaze::IsLeftHand(cv::Mat normalizedLandmarks)
{
    if (normalizedLandmarks.at<float>(4, 0) > normalizedLandmarks.at<float>(20, 0))
        return true;
    return false;
}

 

 

 

 

근데 언리얼에선 cv::Mat을 가져올 방법도없으니 대신

벡터맵 사용

데스크탑게임모드베이스에 이런식으로 추가

 

	// bone location
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FVector> MapBoneLocationLeft;
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FVector> MapBoneLocationRight;
	void make_map_for_location();

};

 

void ADesktopGameModeBase::make_map_for_location()
{
	cv::Mat HandLeftLocMat = blaze.handLeft;
	cv::Mat HandRightLocMat = blaze.handRight;

	for (int j = 0; j < HandLeftLocMat.size[0]; j++)
	{
		FVector vec(HandLeftLocMat.at<float>(j, 0), HandLeftLocMat.at<float>(j, 1), HandLeftLocMat.at<float>(j, 2));
		MapBoneLocationLeft.Add(j, vec);

	}
	for (int j = 0; j < HandRightLocMat.size[0]; j++)
	{
		FVector vec(HandRightLocMat.at<float>(j, 0), HandRightLocMat.at<float>(j, 1), HandRightLocMat.at<float>(j, 2));
		MapBoneLocationRight.Add(j, vec);
	}

}

 

 

리드프레임에서 로테이터 주석하고 로케이션 맵만들기 추가

	//make_map_for_rotators(denorm_imgs_landmarks);
	make_map_for_location();

 

 

 

 

 

 

 

 

맵 차제를 못가져오길래

따로 맵을 생성해놓고

맵값 갱신하는 식으로 수정

 

 

	// bone location
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FVector> MapBoneLocationLeft;
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FVector> MapBoneLocationRight;
	void set_map_for_location();
	void make_map_for_location();
};

 

 

void ADesktopGameModeBase::set_map_for_location()
{
	cv::Mat HandLeftLocMat = blaze.handLeft;
	cv::Mat HandRightLocMat = blaze.handRight;

	for (int j = 0; j < HandLeftLocMat.size[0]; j++)
	{
		FVector vec(HandLeftLocMat.at<float>(j, 0), HandLeftLocMat.at<float>(j, 1), HandLeftLocMat.at<float>(j, 2));
		MapBoneLocationLeft.Add(j, vec);

	}
	for (int j = 0; j < HandRightLocMat.size[0]; j++)
	{
		FVector vec(HandRightLocMat.at<float>(j, 0), HandRightLocMat.at<float>(j, 1), HandRightLocMat.at<float>(j, 2));
		MapBoneLocationRight.Add(j, vec);
	}

}



void ADesktopGameModeBase::make_map_for_location()
{

	for (int j = 0; j < 21; j++)
	{
		MapBoneLocationLeft.Add(j, FVector(0, 0, 0));
		MapBoneLocationRight.Add(j, FVector(0, 0, 0));
	}
}

 

 

 

값은 출력이 되지만 0,0,0만 나온다.

 

 

 

 

행렬 값을 확인해보니 둘다 제대로 가져오지 못하는 상황

 

void ADesktopGameModeBase::set_map_for_location()
{
	cv::Mat HandLeftLocMat = blaze.handLeft;
	cv::Mat HandRightLocMat = blaze.handRight;


	UE_LOG(LogTemp, Log, TEXT("rows : %d, cols : %df"), HandLeftLocMat.size[0], HandLeftLocMat.size[1]);

 

 

 

 

왜 저런문제가 있나 찾아보다가

 

추론 결과가 아까 썻던 정규화 랜드마크랑 좀다른식으로 나왔다.

 

 

 

 

 

 

아무래도 저장해둔 랜드마크가 오른손이었나보다.

 

import cv2
import time
import numpy as np
import traceback


def get_color_filtered_boxes(image):
    # 이미지를 HSV 색 공간으로 변환
    hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    # 살색 영역을 마스크로 만들기
    skin_mask = cv2.inRange(hsv_image, lower_skin, upper_skin)
    # 모폴로지 연산을 위한 구조 요소 생성
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9))
    # 모폴로지 열림 연산 적용
    skin_mask = cv2.morphologyEx(skin_mask, cv2.MORPH_OPEN, kernel)

    # 마스크를 이용하여 살색 영역 추출
    skin_image = cv2.bitwise_and(image, image, mask=skin_mask)

    # 살색 영역에 대한 바운딩 박스 추출
    contours, _ = cv2.findContours(skin_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    bounding_boxes = [cv2.boundingRect(cnt) for cnt in contours]
        
    # 크기가 작은 박스와 큰 박스 제거
    color_boxes = []
    for (x, y, w, h) in bounding_boxes:
        if w * h > 100 * 100:
            # 약간 박스 더크게
            #color_boxes.append((x - 30, y - 30, w + 60, h + 60))
            center_x = int((x + x + w) / 2)
            center_y = int((y + y + h) / 2)

            large = 0
            if w > h:
                large = w                
            if w < h:                
                large = h
            
            large = int(large * 0.7)
            color_boxes.append((center_x - large, center_y - large, 2 * large,  2 * large))


    return color_boxes


def landmark_inference(img):
    #tensor = img / 127.5 - 1.0
    tensor = img / 256
    blob = cv2.dnn.blobFromImage(tensor.astype(np.float32), swapRB=False, crop=False)
    net.setInput(blob)
    preds = net.forward(outNames)

    return preds

def denormalize_landmarks(landmarks):
    landmarks[:,:,:2] *= 256
    return landmarks


def draw_landmarks(img, points, connections=[], color=(0, 255, 0), size=2):
    points = points[:,:2]
    for point in points:
        x, y = point
        x, y = int(x), int(y)
        cv2.circle(img, (x, y), size, color, thickness=size)

    for connection in connections:
        x0, y0 = points[connection[0]]
        x1, y1 = points[connection[1]]
        x0, y0 = int(x0), int(y0)
        x1, y1 = int(x1), int(y1)
        cv2.line(img, (x0, y0), (x1, y1), (0,0,0), size)




HAND_CONNECTIONS = [
    (0, 1), (1, 2), (2, 3), (3, 4),
    (5, 6), (6, 7), (7, 8),
    (9, 10), (10, 11), (11, 12),
    (13, 14), (14, 15), (15, 16),
    (17, 18), (18, 19), (19, 20),
    (0, 5), (5, 9), (9, 13), (13, 17), (0, 17)
]






# 살색 영역의 범위 지정 (HSV 색 공간)
lower_skin = np.array([0, 20, 70], dtype=np.uint8)
upper_skin = np.array([20, 255, 255], dtype=np.uint8)

img_width = 640
img_height = 480
lm_infer_width = 256
lm_infer_height = 256

net = cv2.dnn.readNet('blazehand.onnx')
#net = cv2.dnn.readNet('hand_landmark.onnx')

outNames = net.getUnconnectedOutLayersNames()
print(outNames)


cap = cv2.VideoCapture(0)
norm_landmarks = None
while True:
    time_start = time.time()
    try:
        roi = None
        ret, frame = cap.read()
        frame = cv2.resize(frame, (img_width, img_height))

        skin_image = frame.copy()
        # 크기가 작은 박스와 큰 박스 제거
        color_boxes = get_color_filtered_boxes(skin_image)

        # 바운딩 박스를 이미지에 그리기
        for (x, y, w, h) in color_boxes:
            cv2.rectangle(skin_image, (x, y), (x + w, y + h), (0, 255, 0), 2)


        for idx, color_box in enumerate(color_boxes):
            x, y, w, h = color_box
            cbox_ratio_width = w / lm_infer_width
            cbox_ratio_height = h / lm_infer_height

            roi = frame[y:y+h, x:x+w]
            roi = cv2.resize(roi, (lm_infer_width, lm_infer_height))
            lm_input = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)


            preds = landmark_inference(lm_input)

            landmarks = preds[2]
            flag = preds[0]
            norm_landmarks = landmarks.copy()
            denorm_landmarks = denormalize_landmarks(landmarks)

            for i in range(len(flag)):
                landmark, flag = denorm_landmarks[i], flag[i]
                
                if flag>.5:
                    draw_landmarks(roi, landmark[:,:2], HAND_CONNECTIONS, size=2)




        if cv2.waitKey(1) == ord('q'):
            break
        time_cur = time.time()
        cv2.putText(frame, f"time spend: {time_cur - time_start}", (0, 50), cv2.FONT_HERSHEY_SIMPLEX, 2, (125, 125, 125), 2)
        
        #frame = cv2.resize(frame, (320, 240))
        if roi is not None:
            cv2.imshow('roi', roi)
        cv2.imshow('Camera Streaming', frame)
        cv2.imshow('Skin Extraction', skin_image)
    except Exception as e:
        traceback.print_exc()

cap.release()
cv2.destroyAllWindows()

 

 

 

 

y축 뒤집힌건 그렇다쳐도

엄지가 작은데가있다.

 

 

오른손의 경우 반대로 엄지가 오른쪽에감

 

 

 

 

좌우 오계산한거 해결한건 좋은데 값이 좀이상하게 나온다.

 

 

 

 

 

 

 

 

 

 

 

 

반정규화 전에는 분명 0 ~ 1로 출력되던 값이 왜이렇게 됫을까

 

 

싶었는데 파이썬 넘파이로 행렬 연산으로 한번에 처리한것과 다르게

단순 루프로 하면서  서로 교차하는 부분이 문제된듯함

        for (int j = 0; j < squeezed_for_bone.size[0]; j++)
        {
            squeezed_for_bone.at<float>(j, 0) = (squeezed_for_bone.at<float>(j, 1) - squeezed_for_bone.at<float>(0, 1)) * skeletonXRatio;
            squeezed_for_bone.at<float>(j, 1) = (squeezed_for_bone.at<float>(j, 0) - 0.5) * skeletonYRatio * -1;
            squeezed_for_bone.at<float>(j, 2) = squeezed_for_bone.at<float>(j, 2) * skeletonYRatio * -1;
            UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone %d : %f, %f, %f"), j, squeezed_for_bone.at<float>(j, 1), squeezed_for_bone.at<float>(j, 2), squeezed_for_bone.at<float>(j, 3));

        }

 

 

기준 행렬을 따로 빼서 쓰면

 

        for (int j = 0; j < squeezed_for_bone.size[0]; j++)
        {
            squeezed_for_bone.at<float>(j, 0) = (squeezed_for_origin.at<float>(j, 1) - squeezed_for_origin.at<float>(0, 1)) * skeletonXRatio;
            squeezed_for_bone.at<float>(j, 1) = (squeezed_for_origin.at<float>(j, 0) - 0.5) * skeletonYRatio * -1;
            squeezed_for_bone.at<float>(j, 2) = squeezed_for_origin.at<float>(j, 2) * skeletonYRatio * -1;
            UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone %d : %f, %f, %f"), j, squeezed_for_bone.at<float>(j, 1), squeezed_for_bone.at<float>(j, 2), squeezed_for_bone.at<float>(j, 3));

        }

 

 

잘 나온것 같지만

xy값 부호가 잘못된것처럼보인다

 

컴포넌트 좌표계상에서

 

검지 끝은 20번으로 12.6, 4.9    

중지 끝은 16번으로 16.5, 0.56 

엄지 끝은 4번으로 8.3, -6.7

 

이나

 

아래 얻은 반정규화 좌표는 으로 xy둘다 뒤집어진 상태

검지 끝 20번 -7.8, -3.3

중지 끝 16번 -10, -1.6

엄지 끝 4번  -4.6, 3.6

 

x좌표는 아까 오른손 데이터를가지고 왼손으로 정리한게 문젠거같고

y축은 왜뒤집힌건지 모르겟네

 

 

 

 

 

 

 

 

 

 

 

이제 본위치를 설정해야되는데 본 갯수와 랜드마크 수가 맞지않는다.

랜드마크는 21개

본은 4 + 3 + 3 + 3 + 4 + 1 = 18개로 갯수 매칭이 안된다.

 

일단 

매칭할만한것만 정리하면

블라즈핸드 블라즈핸드
5 b_l_index1 17 b_l_pinky1
6 b_l_index2 18 b_l_pinky2
7 b_l_index3 19 b_l_pinky3
9 b_l_middle1 1 b_l_thumb0
10 b_l_middle2 2 b_l_thumb1
11 b_l_middle3 3 b_l_thumb2
13 b_l_ring1 4 b_l_thumb3
14 b_l_ring2 0 b_l_wrist
15 b_l_ring3    

로 정리할듯하나

b_l_pinky0는 pinky1과 손목 사이로 만들어야할듯.

 

 

 

 

 

 

 

 

 

대충 동작은 하는대 크기가 좀 아쉬워서 배율 추가

위치는 잘 매칭시키긴한거같은데 rot 설정이 없어서 손이 회전없이 어그러진건 좀 아쉽

 

 

 

 

 

손등 정면에서보면 그래도 나쁘진 않은데

옆에서보면 아쉽

 

 

 

 

 

 

 

 

 

 

 

 

이제 화면에서 이동 추가하자

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

왼손 처럼 오른손 클래스 생성

#pragma once

#include "Components/PoseableMeshComponent.h"
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HandRight.generated.h"

UCLASS()
class HANDDESKTOP_API AHandRight : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AHandRight();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PoseableMesh)
	UPoseableMeshComponent* HanRight;

};
#include "HandRight.h"

// Sets default values
AHandRight::AHandRight()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// 스켈레탈 메시 컴포넌트 생성 및 부착
	ConstructorHelpers::FObjectFinder<USkeletalMesh> TempHandMesh(
		TEXT("SkeletalMesh'/Game/MyContent/Hand_R.Hand_R'")
	);
	HanRight = CreateDefaultSubobject<UPoseableMeshComponent>(TEXT("HandRight"));

	HanRight->SetSkeletalMesh(TempHandMesh.Object);
	HanRight->SetRelativeLocation(FVector(0, 0, 0));
}

// Called when the game starts or when spawned
void AHandRight::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AHandRight::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

 

 

 

 

 

BP 오른손 생성후 게임 내 배치

 

 

 

 

 

 

 

 

 

 

 

오른손도 이동하도록 bp랑 코드도 수정했는데

잘 움직이긴하지만 방향이 반대로됨

만든 반정규화 코드가 왼손 기준으로 만듬

오른손이랑 왼손 모델은 좌우대칭뿐만 아니라 상하반전도 되어있기때문

 

 

 xy부호만 반대로하면 될줄알았는데

정리좀 하고싶은데 잘안되네

 

 

 

왼손은 위를 향하고 있는 상태며 좌표는 이랬는데

 

 

오른손은 손바닥이 아래를 향하면서 원점 기준으로 반대로 되어있다.

 

 

 

 

 

 

블라즈핸드 추론 결과는 x축 반전만 되어있는데

모델은 x,y축 둘다 반전되어있으니 곤란하다.

 

왼손 추론결과 오른손 추론결과

 

 

 

 

 

 

 

 

 

근데 보다보니 

손 대부분은 정상적으로 나오지만

뼈하나가 엉뚱한데 간게 아닐까싶더라

 

 

 

 

 

 

 

전체 뼈 컴포넌트 공간 좌표 찍어봤는데

의심되던 pinky 0 도 정상이고

 

 

 

 

 f1로보면 왼손은 이쁘게 잘나오는데

 

 

 

 

 

아무리봐도 

index1이 이상한대 가서그런거같은데

 

 

 

 

 

 

따로봐야될거같아서 0~4까지만 로케이션 설정했을때 결과

 

17 ~ 19 핑키123 만 걸었을때 결과

 

 

이렇게 보니 y좌표 자체가 반대로되서 이런듯

 

이제 손이 구불구불하지만 제대로 나온다.

 

 

 

 

 

 

정리된 블루프린트

 

 

컴포넌트 공간에서만 위치잡도록했으니 

월드공간에서 방향만맞춰주자

 

 

 

 

인게임에서 제어결과

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

게임모드

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

#pragma once


#include "Blaze.h"

#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:
	UFUNCTION(BlueprintCallable)
	void ReadFrame();




	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();



	int webcamWidth = 640;
	int webcamHeight = 480;

	cv::VideoCapture capture;
	cv::Mat webcamImage;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UTexture2D* webcamTexture;
	void MatToTexture2D(const cv::Mat InMat);


	//var and functions with blaze
	Blaze blaze;
	cv::Mat img256;
	cv::Mat img128;
	float scale;
	cv::Scalar pad;





	// vars and funcs for rotator
	int hand_conns_indexes[14] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
	void get_pitch_yaw(cv::Point3f pt_start, cv::Point3f pt_end, float& pitch, float& yaw);
	//void calculateRotation(const cv::Point3f& pt1, const cv::Point3f& pt2, float& roll, float& pitch, float& yaw);

	void make_map_for_rotators(std::vector<cv::Mat> denorm_imgs_landmarks);
	void make_map_bone();

	UPROPERTY(BlueprintReadWrite, Category = "RotatorMap")
	TMap<int32, float> MapRoll;
	UPROPERTY(BlueprintReadWrite, Category="RotatorMap")
	TMap<int32, float> MapPitch;
	UPROPERTY(BlueprintReadWrite, Category = "RotatorMap")
	TMap<int32, float> MapYaw;

	UPROPERTY(BlueprintReadWrite, Category = "RotatorMap")
	TMap<int32, FString> MapBoneLeft;
	UPROPERTY(BlueprintReadWrite, Category = "RotatorMap")
	TMap<int32, FString> MapBoneRight;

	UPROPERTY(BlueprintReadWrite, Category = "HandCoord")
	float HandLeftX;
	UPROPERTY(BlueprintReadWrite, Category = "HandCoord")
	float HandLeftY;


	// bone location
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FVector> MapBoneLocationLeft;
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FVector> MapBoneLocationRight;
	void set_map_for_location();
	void make_map_for_location();

	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FString> MapBoneLocationNameLeft;
	UPROPERTY(BlueprintReadWrite, Category = "LocationMap")
	TMap<int32, FString> MapBoneLocationNameRight;

	void make_map_bone_name();
	void set_hand_pos_world();


	UPROPERTY(BlueprintReadWrite, Category = "HandCoord")
	float HandRightX;
	UPROPERTY(BlueprintReadWrite, Category = "HandCoord")
	float HandRightY;

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

#include "DesktopGameModeBase.h"


void ADesktopGameModeBase::BeginPlay()
{
	Super::BeginPlay();
	blaze = Blaze();

	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"));
	}
	capture.set(cv::CAP_PROP_FRAME_WIDTH, webcamWidth);
	capture.set(cv::CAP_PROP_FRAME_HEIGHT, webcamHeight);


	webcamTexture = 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);
	
	make_map_bone();
	make_map_for_location();
	make_map_bone_name();
}


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

	/*
	get filtered detections
	*/
	blaze.ResizeAndPad(webcamImage, img256, img128, scale, pad);
	//UE_LOG(LogTemp, Log, TEXT("scale value: %f, pad value: (%f, %f)"), scale, pad[0], pad[1]);
	std::vector<Blaze::PalmDetection> normDets = blaze.PredictPalmDetections(img128);
	std::vector<Blaze::PalmDetection> denormDets = blaze.DenormalizePalmDetections(normDets, webcamWidth, webcamHeight, pad);
	std::vector<Blaze::PalmDetection> filteredDets = blaze.FilteringDets(denormDets, webcamWidth, webcamHeight);





	std::vector<cv::Rect> handRects = blaze.convertHandRects(filteredDets);
	std::vector<cv::Mat> handImgs;
	blaze.GetHandImages(webcamImage, handRects, handImgs);
	std::vector<cv::Mat> imgs_landmarks = blaze.PredictHandDetections(handImgs);
	std::vector<cv::Mat> denorm_imgs_landmarks = blaze.DenormalizeHandLandmarksForBoneLocation(imgs_landmarks, handRects);

	//make_map_for_rotators(denorm_imgs_landmarks);
	set_map_for_location();
	set_hand_pos_world();

	//draw hand rects/ plam detection/ dets info/ hand detection
	blaze.DrawRects(webcamImage, handRects);
	blaze.DrawPalmDetections(webcamImage, filteredDets);
	blaze.DrawDetsInfo(webcamImage, filteredDets, normDets, denormDets);
	blaze.DrawHandDetections(webcamImage, denorm_imgs_landmarks);

	//cv::mat to utexture2d
	MatToTexture2D(webcamImage);


	/*
	모니터 시각화
	*/
	ScreensToCVMats();
	CVMatsToTextures();
}


void ADesktopGameModeBase::MatToTexture2D(const cv::Mat InMat)
{
	if (InMat.type() == CV_8UC3)//example for pre-conversion of Mat
	{
		cv::Mat resizedImage;
		cv::resize(InMat, resizedImage, cv::Size(monitorWidth, monitorHeight));
		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(resizedImage, bgraImage, cv::COLOR_BGR2BGRA);

		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = webcamTexture->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();
		webcamTexture->PostEditChange();
		webcamTexture->UpdateResource();
	}
	else if (InMat.type() == CV_8UC4)
	{
		//actually copy the data to the new texture
		FTexture2DMipMap& Mip = webcamTexture->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();
		webcamTexture->PostEditChange();
		webcamTexture->UpdateResource();
	}
	//if the texture hasnt the right pixel format, abort.
	webcamTexture->PostEditChange();
	webcamTexture->UpdateResource();
}



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);

}


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();
		}


	}
}





void ADesktopGameModeBase::get_pitch_yaw(cv::Point3f pt_start, cv::Point3f pt_end, float& pitch, float& yaw) {
	float dx = pt_end.x - pt_start.x;
	float dy = pt_end.y - pt_start.y;
	dy *= -1;
	yaw = std::atan2(dy, dx) * 180 / CV_PI;
	yaw = yaw - 90;

	float dz = pt_end.z - pt_start.z;
	float xy_norm = std::sqrt(dx * dx + dy * dy);
	pitch = std::atan2(dz, xy_norm) * 180 / CV_PI;
	pitch *= -1;
}

/*
void ADesktopGameModeBase::calculateRotation(const cv::Point3f& pt1, const cv::Point3f& pt2, float& roll, float& pitch, float& yaw) {
	cv::Point3f vec = pt2 - pt1;  // 두 벡터를 연결하는 벡터 계산

	roll = atan2(vec.y, vec.x) * 180 / CV_PI;  // 롤(Roll) 회전 계산
	pitch = asin(vec.z / cv::norm(vec)) * 180 / CV_PI;  // 피치(Pitch) 회전 계산

	cv::Point2f vecXY(vec.x, vec.y);  // xy 평면으로 투영
	yaw = (atan2(vec.y, vec.x) - atan2(vec.z, cv::norm(vecXY))) * 180 / CV_PI;  // 요(Yaw) 회전 계산
}
*/



void ADesktopGameModeBase::make_map_for_rotators(std::vector<cv::Mat> denorm_imgs_landmarks)
{
	for (auto& denorm_landmarks : denorm_imgs_landmarks)
	{
		std::vector<std::array<int, 2>> HAND_CONNECTIONS = blaze.HAND_CONNECTIONS;
		for (auto& hand_conns_index : hand_conns_indexes)
		{
			std::array<int, 2> hand_conns = HAND_CONNECTIONS.at(hand_conns_index);
			float roll, pitch, yaw;
			cv::Point3f pt_start, pt_end;

			pt_start.x = denorm_landmarks.at<float>(hand_conns.at(0), 0);
			pt_start.y = denorm_landmarks.at<float>(hand_conns.at(0), 1);
			pt_start.z = denorm_landmarks.at<float>(hand_conns.at(0), 2);

			pt_end.x = denorm_landmarks.at<float>(hand_conns.at(1), 0);
			pt_end.y = denorm_landmarks.at<float>(hand_conns.at(1), 1);
			pt_end.z = denorm_landmarks.at<float>(hand_conns.at(1), 2);


			//calculateRotation(pt_start, pt_end, roll, pitch, yaw);

			get_pitch_yaw(pt_start, pt_end, pitch, yaw);
			MapRoll.Add(hand_conns_index, roll);
			MapPitch.Add(hand_conns_index, pitch);
			MapYaw.Add(hand_conns_index, yaw);

		}
		HandLeftX = denorm_landmarks.at<float>(9, 0);
		HandLeftY = denorm_landmarks.at<float>(9, 1);

	}
}


/*
	std::vector<std::array<int, 2>> HAND_CONNECTIONS = {
	{0, 1}, {1, 2}, {2, 3}, {3, 4},
	{5, 6}, {6, 7}, {7, 8},
	{9, 10}, {10, 11}, {11, 12},
	{13, 14}, {14, 15}, {15, 16},
	{17, 18}, {18, 19}, {19, 20},
	{0, 5}, {5, 9}, {9, 13}, {13, 17}, {0, 17}
	};
*/


void ADesktopGameModeBase::make_map_bone()
{
	MapBoneLeft.Add(2, FString("b_l_thumb2"));
	MapBoneLeft.Add(3, FString("b_l_thumb3"));
	MapBoneLeft.Add(4, FString("b_l_index1"));
	MapBoneLeft.Add(5, FString("b_l_index2"));
	MapBoneLeft.Add(6, FString("b_l_index3"));
	MapBoneLeft.Add(7, FString("b_l_middle1"));
	MapBoneLeft.Add(8, FString("b_l_middle2"));
	MapBoneLeft.Add(9, FString("b_l_middle3"));
	MapBoneLeft.Add(10, FString("b_l_ring1"));
	MapBoneLeft.Add(11, FString("b_l_ring2"));
	MapBoneLeft.Add(12, FString("b_l_ring3"));
	MapBoneLeft.Add(13, FString("b_l_pinky1"));
	MapBoneLeft.Add(14, FString("b_l_pinky2"));
	MapBoneLeft.Add(15, FString("b_l_pinky3"));


	MapBoneRight.Add(2, FString("b_r_thumb2"));
	MapBoneRight.Add(3, FString("b_r_thumb3"));
	MapBoneRight.Add(4, FString("b_r_index1"));
	MapBoneRight.Add(5, FString("b_r_index2"));
	MapBoneRight.Add(6, FString("b_r_index3"));
	MapBoneRight.Add(7, FString("b_r_middle1"));
	MapBoneRight.Add(8, FString("b_r_middle2"));
	MapBoneRight.Add(9, FString("b_r_middle3"));
	MapBoneRight.Add(10, FString("b_r_ring1"));
	MapBoneRight.Add(11, FString("b_r_ring2"));
	MapBoneRight.Add(12, FString("b_r_ring3"));
	MapBoneRight.Add(13, FString("b_r_pinky1"));
	MapBoneRight.Add(14, FString("b_r_pinky2"));
	MapBoneRight.Add(15, FString("b_r_pinky3"));



	MapBoneLeft.Add(16, FString("b_l_wrist"));
	MapBoneLeft.Add(17, FString("b_l_thumb0"));
	MapBoneLeft.Add(18, FString("b_l_thumb1"));
	MapBoneLeft.Add(19, FString("b_l_pinky0"));
	MapBoneRight.Add(16, FString("b_r_wrist"));
	MapBoneRight.Add(17, FString("b_r_thumb0"));
	MapBoneRight.Add(18, FString("b_r_thumb1"));
	MapBoneRight.Add(19, FString("b_r_pinky0"));
}





void ADesktopGameModeBase::set_map_for_location()
{
	cv::Mat HandLeftLocMat = blaze.handLeft;
	cv::Mat HandRightLocMat = blaze.handRight;


	//UE_LOG(LogTemp, Log, TEXT("rows : %d, cols : %d"), HandLeftLocMat.size[0], HandLeftLocMat.size[1]);

	for (int j = 0; j < HandLeftLocMat.size[0]; j++)
	{
		FVector vec(HandLeftLocMat.at<float>(j, 0), HandLeftLocMat.at<float>(j, 1), HandLeftLocMat.at<float>(j, 2));
		MapBoneLocationLeft.Add(j, vec);

	}
	for (int j = 0; j < HandRightLocMat.size[0]; j++)
	{
		FVector vec(HandRightLocMat.at<float>(j, 0), HandRightLocMat.at<float>(j, 1), HandRightLocMat.at<float>(j, 2));
		MapBoneLocationRight.Add(j, vec);
	}
	
	//UE_LOG(LogTemp, Log, TEXT("HandRightLocMat.size[0] : %d"), HandRightLocMat.size[0]);

}



void ADesktopGameModeBase::make_map_for_location()
{

	for (int j = 0; j < 21; j++)
	{
		MapBoneLocationLeft.Add(j, FVector(0, 0, 0));
		MapBoneLocationRight.Add(j, FVector(0, 0, 0));
	}
}



void ADesktopGameModeBase::make_map_bone_name()
{
	MapBoneLocationNameLeft.Add(0, FString("b_l_wrist"));
	MapBoneLocationNameLeft.Add(1, FString("b_l_thumb0"));
	MapBoneLocationNameLeft.Add(2, FString("b_l_thumb1"));
	MapBoneLocationNameLeft.Add(3, FString("b_l_thumb2"));
	MapBoneLocationNameLeft.Add(4, FString("b_l_thumb3"));
	MapBoneLocationNameLeft.Add(5, FString("b_l_index1"));
	MapBoneLocationNameLeft.Add(6, FString("b_l_index2"));
	MapBoneLocationNameLeft.Add(7, FString("b_l_index3"));
	MapBoneLocationNameLeft.Add(9, FString("b_l_middle1"));
	MapBoneLocationNameLeft.Add(10, FString("b_l_middle2"));
	MapBoneLocationNameLeft.Add(11, FString("b_l_middle3"));
	MapBoneLocationNameLeft.Add(13, FString("b_l_ring1"));
	MapBoneLocationNameLeft.Add(14, FString("b_l_ring2"));
	MapBoneLocationNameLeft.Add(15, FString("b_l_ring3"));
	MapBoneLocationNameLeft.Add(17, FString("b_l_pinky1"));
	MapBoneLocationNameLeft.Add(18, FString("b_l_pinky2"));
	MapBoneLocationNameLeft.Add(19, FString("b_l_pinky3"));


	MapBoneLocationNameRight.Add(0, FString("b_r_wrist"));

	MapBoneLocationNameRight.Add(1, FString("b_r_thumb0"));
	MapBoneLocationNameRight.Add(2, FString("b_r_thumb1"));
	MapBoneLocationNameRight.Add(3, FString("b_r_thumb2"));
	MapBoneLocationNameRight.Add(4, FString("b_r_thumb3"));

	MapBoneLocationNameRight.Add(5, FString("b_r_index1"));
	MapBoneLocationNameRight.Add(6, FString("b_r_index2"));
	MapBoneLocationNameRight.Add(7, FString("b_r_index3"));

	MapBoneLocationNameRight.Add(9, FString("b_r_middle1"));
	MapBoneLocationNameRight.Add(10, FString("b_r_middle2"));
	MapBoneLocationNameRight.Add(11, FString("b_r_middle3"));

	MapBoneLocationNameRight.Add(13, FString("b_r_ring1"));
	MapBoneLocationNameRight.Add(14, FString("b_r_ring2"));
	MapBoneLocationNameRight.Add(15, FString("b_r_ring3"));

	MapBoneLocationNameRight.Add(17, FString("b_r_pinky1"));
	MapBoneLocationNameRight.Add(18, FString("b_r_pinky2"));
	MapBoneLocationNameRight.Add(19, FString("b_r_pinky3"));
}


void ADesktopGameModeBase::set_hand_pos_world()
{
	cv::Mat handLeft = blaze.handLeftImg;
	cv::Mat handRight = blaze.handRightImg;

	if (handLeft.size[0] == 21)
	{
		HandLeftX = handLeft.at<float>(9, 0);
		HandLeftY = handLeft.at<float>(9, 1);
	}
	if (handRight.size[0] == 21)
	{
		HandRightX = handRight.at<float>(9, 0);
		HandRightY = handRight.at<float>(9, 1);
	}


}

 

 

 

블라즈

// 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"

/**
 * 
 */
class HANDDESKTOP_API Blaze
{
public:
	Blaze();
	~Blaze();

	cv::dnn::Net blazePalm;
	cv::dnn::Net blazeHand;



	// var and funcs for blazepalm
	struct PalmDetection {
		float ymin;
		float xmin;
		float ymax;
		float xmax;
		cv::Point2d kp_arr[7];
		float score;
	};


	int blazeHandSize = 256;
	int blazePalmSize = 128;
	float palmMinScoreThresh = 0.7;
	float palmMinNMSThresh = 0.4;
	int palmMinNumKeyPoints = 7;

	void ResizeAndPad(
		cv::Mat& srcimg, cv::Mat& img256,
		cv::Mat& img128, float& scale, cv::Scalar& pad
	);

	// funcs for blaze palm
	std::vector<PalmDetection> PredictPalmDetections(cv::Mat& img);
	PalmDetection GetPalmDetection(cv::Mat regressor, cv::Mat classificator,
		int stride, int anchor_count, int column, int row, int anchor, int offset);
	float sigmoid(float x);
	std::vector<PalmDetection> DenormalizePalmDetections(std::vector<PalmDetection> detections, int width, int height, cv::Scalar pad);
	void DrawPalmDetections(cv::Mat& img, std::vector<Blaze::PalmDetection> denormDets);
	void DrawDetsInfo(cv::Mat& img, std::vector<Blaze::PalmDetection> filteredDets, std::vector<Blaze::PalmDetection> normDets, std::vector<Blaze::PalmDetection> denormDets);
	std::vector<PalmDetection> FilteringDets(std::vector<PalmDetection> detections, int width, int height);




	// vars and funcs for blaze hand
	std::vector<std::array<int, 2>> HAND_CONNECTIONS = {
	{0, 1}, {1, 2}, {2, 3}, {3, 4},
	{5, 6}, {6, 7}, {7, 8},
	{9, 10}, {10, 11}, {11, 12},
	{13, 14}, {14, 15}, {15, 16},
	{17, 18}, {18, 19}, {19, 20},
	{0, 5}, {5, 9}, {9, 13}, {13, 17}, {0, 17}
	};
	float blazeHandDScale = 3;
	int webcamWidth = 640;
	int webcamHeight = 480;



	std::vector<cv::Rect> convertHandRects(std::vector<PalmDetection> filteredDets);
	void DrawRects(cv::Mat& img, std::vector<cv::Rect> rects);
	void GetHandImages(cv::Mat& img, std::vector<cv::Rect> rects, std::vector<cv::Mat>& handImgs);
	std::vector<cv::Mat> PredictHandDetections(std::vector<cv::Mat>& imgs);
	std::vector<cv::Mat> DenormalizeHandLandmarks(std::vector<cv::Mat> imgs_landmarks, std::vector<cv::Rect> rects);
	void DrawHandDetections(cv::Mat img, std::vector<cv::Mat> denorm_imgs_landmarks);



	// var, funs for left, right lms and bone location
	float skeletonXRatio = 16.5;
	float skeletonYRatio = 11.6;
	cv::Mat handLeft;
	cv::Mat handRight;
	cv::Mat handLeftImg;
	cv::Mat handRightImg;

	std::vector<cv::Mat> DenormalizeHandLandmarksForBoneLocation(std::vector<cv::Mat> imgs_landmarks, std::vector<cv::Rect> rects);
	bool IsLeftHand(cv::Mat normalizedLandmarks);

};

 

 

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


#include "Blaze.h"

Blaze::Blaze()
{
	this->blazePalm = cv::dnn::readNet("c:/blazepalm_old.onnx");
	this->blazeHand = cv::dnn::readNet("c:/blazehand.onnx");

}

Blaze::~Blaze()
{
}


void Blaze::ResizeAndPad(
    cv::Mat& srcimg, cv::Mat& img256,
    cv::Mat& img128, float& scale, cv::Scalar& pad
)
{
    float h1, w1;
    int padw, padh;

    cv::Size size0 = srcimg.size();
    if (size0.height >= size0.width) {
        h1 = 256;
        w1 = 256 * size0.width / size0.height;
        padh = 0;
        padw = static_cast<int>(256 - w1);
        scale = size0.width / static_cast<float>(w1);
    }
    else {
        h1 = 256 * size0.height / size0.width;
        w1 = 256;
        padh = static_cast<int>(256 - h1);
        padw = 0;
        scale = size0.height / static_cast<float>(h1);
    }

    //UE_LOG(LogTemp, Log, TEXT("scale value: %f, size0.height: %d, size0.width : %d, h1 : %f"), scale, size0.height, size0.width, h1);

    int padh1 = static_cast<int>(padh / 2);
    int padh2 = static_cast<int>(padh / 2) + static_cast<int>(padh % 2);
    int padw1 = static_cast<int>(padw / 2);
    int padw2 = static_cast<int>(padw / 2) + static_cast<int>(padw % 2);
    pad = cv::Scalar(static_cast<float>(padh1 * scale), static_cast<float>(padw1 * scale));


    cv::resize(srcimg, img256, cv::Size(w1, h1));
    cv::copyMakeBorder(img256, img256, padh1, padh2, padw1, padw2, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));

    cv::resize(img256, img128, cv::Size(128, 128));
    cv::cvtColor(img256, img256, cv::COLOR_BGR2RGB);
    cv::cvtColor(img128, img128, cv::COLOR_BGR2RGB);
}



std::vector<Blaze::PalmDetection> Blaze::PredictPalmDetections(cv::Mat& img)
{
    std::vector<Blaze::PalmDetection> beforeNMSResults;
    std::vector<Blaze::PalmDetection> afterNMSResults;
    std::vector<float> scores;
    std::vector<int> indices;
    std::vector<cv::Rect> boundingBoxes;


    cv::Mat tensor;
    img.convertTo(tensor, CV_32F);
    tensor = tensor / 127.5 - 1;
    cv::Mat blob = cv::dnn::blobFromImage(tensor, 1.0, tensor.size(), 0, false, false, CV_32F);
    std::vector<cv::String> outNames(2);
    outNames[0] = "regressors";
    outNames[1] = "classificators";

    blazePalm.setInput(blob);
    std::vector<cv::Mat> outputs;
    blazePalm.forward(outputs, outNames);

    cv::Mat classificator = outputs[0];
    cv::Mat regressor = outputs[1];


    for (int y = 0; y < 16; ++y) {
        for (int x = 0; x < 16; ++x) {
            for (int a = 0; a < 2; ++a) {
                PalmDetection res = GetPalmDetection(regressor, classificator, 8, 2, x, y, a, 0);
                if (res.score != 0)
                {
                    beforeNMSResults.push_back(res);

                    cv::Point2d startPt = cv::Point2d(res.xmin * blazePalmSize, res.ymin * blazePalmSize);
                    cv::Point2d endPt = cv::Point2d(res.xmax * blazePalmSize, res.ymax * blazePalmSize);

                    boundingBoxes.push_back(cv::Rect(startPt, endPt));
                    scores.push_back(res.score);
                }
            }
        }
    }

    for (int y = 0; y < 8; ++y) {
        for (int x = 0; x < 8; ++x) {
            for (int a = 0; a < 6; ++a) {
                PalmDetection res = GetPalmDetection(regressor, classificator, 16, 6, x, y, a, 512);
                if (res.score != 0)
                {
                    beforeNMSResults.push_back(res);
                    cv::Point2d startPt = cv::Point2d(res.xmin * blazePalmSize, res.ymin * blazePalmSize);
                    cv::Point2d endPt = cv::Point2d(res.xmax * blazePalmSize, res.ymax * blazePalmSize);

                    boundingBoxes.push_back(cv::Rect(startPt, endPt));
                    scores.push_back(res.score);
                }
            }
        }
    }


    cv::dnn::NMSBoxes(boundingBoxes, scores, palmMinScoreThresh, palmMinNMSThresh, indices);

    for (int i = 0; i < indices.size(); i++) {
        int idx = indices[i];
        afterNMSResults.push_back(beforeNMSResults[idx]);
    }

    return afterNMSResults;
}

Blaze::PalmDetection Blaze::GetPalmDetection(cv::Mat regressor, cv::Mat classificator,
    int stride, int anchor_count, int column, int row, int anchor, int offset) {

    Blaze::PalmDetection res;

    int index = (int(row * 128 / stride) + column) * anchor_count + anchor + offset;
    float origin_score = regressor.at<float>(0, index, 0);
    float score = sigmoid(origin_score);
    if (score < palmMinScoreThresh) return res;

    float x = classificator.at<float>(0, index, 0);
    float y = classificator.at<float>(0, index, 1);
    float w = classificator.at<float>(0, index, 2);
    float h = classificator.at<float>(0, index, 3);

    x += (column + 0.5) * stride - w / 2;
    y += (row + 0.5) * stride - h / 2;

    res.ymin = (y) / blazePalmSize;
    res.xmin = (x) / blazePalmSize;
    res.ymax = (y + h) / blazePalmSize;
    res.xmax = (x + w) / blazePalmSize;

    //if ((res.ymin < 0) || (res.xmin < 0) || (res.xmax > 1) || (res.ymax > 1)) return res;

    res.score = score;

    std::vector<cv::Point2d> kpts;
    for (int key_id = 0; key_id < palmMinNumKeyPoints; key_id++)
    {
        float kpt_x = classificator.at<float>(0, index, 4 + key_id * 2);
        float kpt_y = classificator.at<float>(0, index, 5 + key_id * 2);
        kpt_x += (column + 0.5) * stride;
        kpt_x  = kpt_x / blazePalmSize;
        kpt_y += (row + 0.5) * stride;
        kpt_y  = kpt_y / blazePalmSize;
        //UE_LOG(LogTemp, Log, TEXT("kpt id(%d) : (%f, %f)"), key_id, kpt_x, kpt_y);
        res.kp_arr[key_id] = cv::Point2d(kpt_x, kpt_y);

    }
    return res;
}

float Blaze::sigmoid(float x) {
    return 1 / (1 + exp(-x));
}


std::vector<Blaze::PalmDetection> Blaze::DenormalizePalmDetections(std::vector<Blaze::PalmDetection> detections, int width, int height, cv::Scalar pad)
{

    std::vector<Blaze::PalmDetection> denormDets;

    int scale = 0;
    if (width > height)
        scale = width;
    else
        scale = height;

    for (auto& det : detections)
    {
        Blaze::PalmDetection denormDet;
        denormDet.ymin = det.ymin * scale - pad[0];
        denormDet.xmin = det.xmin * scale - pad[1];
        denormDet.ymax = det.ymax * scale - pad[0];
        denormDet.xmax = det.xmax * scale - pad[1];
        denormDet.score = det.score;

        for (int i = 0; i < palmMinNumKeyPoints; i++)
        {
            cv::Point2d pt_new = cv::Point2d(det.kp_arr[i].x * scale - pad[1], det.kp_arr[i].y * scale - pad[0]);
            //UE_LOG(LogTemp, Log, TEXT("denorm kpt id(%d) : (%f, %f)"), i, pt_new.x, pt_new.y);
            denormDet.kp_arr[i] = pt_new;
        }
        denormDets.push_back(denormDet);
    }
    return denormDets;
}

void Blaze::DrawPalmDetections(cv::Mat& img, std::vector<Blaze::PalmDetection> denormDets)
{
    for (auto& denormDet : denormDets)
    {
        cv::Point2d startPt = cv::Point2d(denormDet.xmin, denormDet.ymin);
        cv::Point2d endPt = cv::Point2d(denormDet.xmax, denormDet.ymax);
        cv::rectangle(img, cv::Rect(startPt, endPt), cv::Scalar(255, 0, 0), 1);

        for (int i = 0; i < palmMinNumKeyPoints; i++)
            cv::circle(img, denormDet.kp_arr[i], 5, cv::Scalar(255, 0, 0), -1);

        std::string score_str = std::to_string(static_cast<int>(denormDet.score * 100))+ "%";
        cv::putText(img, score_str, cv::Point(startPt.x, startPt.y - 20), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 2);

    }
}

void Blaze::DrawDetsInfo(cv::Mat& img, std::vector<Blaze::PalmDetection> filteredDets, std::vector<Blaze::PalmDetection> normDets, std::vector<Blaze::PalmDetection> denormDets)
{
    //puttext num of filtered/denorm dets
    std::string dets_size_str = "filtered dets : " + std::to_string(filteredDets.size()) + ", norm dets : " + std::to_string(normDets.size()) + ", denorm dets : " + std::to_string(denormDets.size());
    cv::putText(img, dets_size_str, cv::Point(30, 30), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 0, 0), 2);

    for (int i = 0; i < filteredDets.size(); i++)
    {
        auto& det = filteredDets.at(i);

        std::ostringstream oss;
        oss << "filteredDets : (" << det.xmin << ", " << det.ymin << "),(" <<
            det.xmax << ", " << det.ymax << ")";
        std::string det_str = oss.str();
        cv::putText(img, det_str, cv::Point(30, 50 + 20 * i), cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 255), 2);
    }

}




std::vector<Blaze::PalmDetection> Blaze::FilteringDets(std::vector<Blaze::PalmDetection> detections, int width, int height)
{
    std::vector<Blaze::PalmDetection> filteredDets;

    for (auto& denormDet : detections)
    {
        cv::Point2d startPt = cv::Point2d(denormDet.xmin, denormDet.ymin);
        if (startPt.x < 10 || startPt.y < 10)
            continue;
        if (startPt.x > width || startPt.y > height)
            continue;
        int w = denormDet.xmax - denormDet.xmin;
        int y = denormDet.ymax - denormDet.ymin;
        if ((w * y < 40 * 40 )|| (w * y  > (width * 0.7) * (height * 0.7)))
            continue;
        filteredDets.push_back(denormDet);
    }
    return filteredDets;
}







std::vector<cv::Rect> Blaze::convertHandRects(std::vector<PalmDetection> filteredDets)
{
    std::vector<cv::Rect> handRects;
    for (auto& det : filteredDets)
    {
        int width = det.xmax - det.xmin; 
        int height = det.ymax - det.ymin;
        int large_length = 0;

        int center_x = static_cast<int>((det.xmax + det.xmin) / 2);
        int center_y = static_cast<int>((det.ymax + det.ymin) / 2);

        if (width > height)
            large_length = height * blazeHandDScale;
        else
            large_length = height * blazeHandDScale;

        int start_x = center_x - static_cast<int>(large_length / 2);
        int start_y = center_y - static_cast<int>(large_length / 2) - large_length * 0.1;

        int end_x = center_x + static_cast<int>(large_length / 2);
        int end_y = center_y + static_cast<int>(large_length / 2) - large_length * 0.1;


        start_x = std::max(start_x, 0);
        start_y = std::max(start_y, 0);
        end_x = std::min(end_x, webcamWidth);
        end_y = std::min(end_y, webcamHeight);

        cv::Point2d startPt = cv::Point2d(start_x, start_y);
        cv::Point2d endPt = cv::Point2d(end_x, end_y);

        cv::Rect handRect(startPt, endPt);
        handRects.push_back(handRect);
    }

    return handRects;
}

void Blaze::DrawRects(cv::Mat& img, std::vector<cv::Rect> rects)
{
    for (auto& rect : rects)
    {
        cv::rectangle(img, rect, cv::Scalar(255, 255, 255), 1);
    }
}

void Blaze::GetHandImages(cv::Mat& img, std::vector<cv::Rect> rects, std::vector<cv::Mat>& handImgs)
{
    for (auto& rect : rects)
    {

        //std::cout << "img size : " << img.size() << ", rect : " << rect << std::endl;

        //cv::Mat roi(cv::Size(rect.width, rect.height), img.type(), cv::Scalar(0, 0, 0))

        cv::Mat roi = img(rect).clone();
        cv::cvtColor(roi, roi, cv::COLOR_BGR2RGB);
        cv::resize(roi, roi, cv::Size(blazeHandSize, blazeHandSize));
        handImgs.push_back(roi);
    }
}



std::vector<cv::Mat> Blaze::PredictHandDetections(std::vector<cv::Mat>& imgs)
{
    std::vector<cv::Mat> imgs_landmarks;


    for (auto& img : imgs)
    {
        cv::Mat tensor;
        img.convertTo(tensor, CV_32F);
        tensor = tensor / blazeHandSize;
        cv::Mat blob = cv::dnn::blobFromImage(tensor, 1.0, tensor.size(), 0, false, false, CV_32F);
        std::vector<cv::String> outNames(3);
        outNames[0] = "hand_flag";
        outNames[1] = "handedness";
        outNames[2] = "landmarks";



        blazeHand.setInput(blob);
        std::vector<cv::Mat> outputs;
        blazeHand.forward(outputs, outNames);

        cv::Mat handflag = outputs[0];
        cv::Mat handedness = outputs[1];
        cv::Mat landmarks = outputs[2];


        imgs_landmarks.push_back(landmarks);
    }
    return imgs_landmarks;
}

std::vector<cv::Mat> Blaze::DenormalizeHandLandmarks(std::vector<cv::Mat> imgs_landmarks, std::vector<cv::Rect> rects)
{
    std::vector<cv::Mat> denorm_imgs_landmarks;

    for (int i = 0; i < imgs_landmarks.size(); i++)
    {
        cv::Mat landmarks = imgs_landmarks.at(i);
        cv::Rect rect = rects.at(i);
        cv::Mat squeezed = landmarks.reshape(0, landmarks.size[1]);

        //std::cout << "squeezed size: " << squeezed.size[0] << "," << squeezed.size[1] << std::endl;

        for (int j = 0; j < squeezed.size[0]; j++)
        {
            squeezed.at<float>(j, 0) = squeezed.at<float>(j, 0) * rect.width + rect.x;
            squeezed.at<float>(j, 1) = squeezed.at<float>(j, 1) * rect.height + rect.y;
            squeezed.at<float>(j, 2) = squeezed.at<float>(j, 2) * rect.height * -1;

        }
        denorm_imgs_landmarks.push_back(squeezed);
    }

    //std::cout << std::endl;
    return denorm_imgs_landmarks;
}

void Blaze::DrawHandDetections(cv::Mat img, std::vector<cv::Mat> denorm_imgs_landmarks)
{
    for (auto& denorm_landmarks : denorm_imgs_landmarks)
    {
        for (int i = 0; i < denorm_landmarks.size[0]; i++)
        {
            float x = denorm_landmarks.at<float>(i, 0);
            float y = denorm_landmarks.at<float>(i, 1);

            cv::Point pt(x, y);

            cv::circle(img, pt, 5, cv::Scalar(0, 255, 0), -1);

        }

        for (auto& connection : HAND_CONNECTIONS)
        {
            int startPtIdx = connection[0];
            int endPtIdx = connection[1];

            float startPtX = denorm_landmarks.at<float>(startPtIdx, 0);
            float startPtY = denorm_landmarks.at<float>(startPtIdx, 1);
            float endPtX = denorm_landmarks.at<float>(endPtIdx, 0);
            float endPtY = denorm_landmarks.at<float>(endPtIdx, 1);
            cv::line(img, cv::Point(startPtX, startPtY), cv::Point(endPtX, endPtY), cv::Scalar(255, 255, 255), 3);


        }
    }
}


std::vector<cv::Mat> Blaze::DenormalizeHandLandmarksForBoneLocation(std::vector<cv::Mat> imgs_landmarks, std::vector<cv::Rect> rects)
{

    std::vector<cv::Mat> denorm_imgs_landmarks;

  
    for (int i = 0; i < imgs_landmarks.size(); i++)
    {
        /*
        denrom lms for img drawing
        */
        cv::Mat landmarks = imgs_landmarks.at(i);
        cv::Rect rect = rects.at(i);
        cv::Mat squeezed_for_origin = landmarks.reshape(0, landmarks.size[1]).clone();

        cv::Mat squeezed_for_img = landmarks.reshape(0, landmarks.size[1]).clone();

        //std::cout << "squeezed size: " << squeezed.size[0] << "," << squeezed.size[1] << std::endl;

        for (int j = 0; j < squeezed_for_img.size[0]; j++)
        {
            squeezed_for_img.at<float>(j, 0) = squeezed_for_img.at<float>(j, 0) * rect.width + rect.x;
            squeezed_for_img.at<float>(j, 1) = squeezed_for_img.at<float>(j, 1) * rect.height + rect.y;
            squeezed_for_img.at<float>(j, 2) = squeezed_for_img.at<float>(j, 2) * rect.height * -1;

        }
        denorm_imgs_landmarks.push_back(squeezed_for_img);




        /*
        denrom lms for bone location
        */
        cv::Mat squeezed_for_bone = landmarks.reshape(0, landmarks.size[1]).clone();

        bool isLeft = IsLeftHand(squeezed_for_bone);


        //UE_LOG(LogTemp, Log, TEXT("isLeft : %d"), isLeft);
        //UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone 4 xy : %f, %f"), squeezed_for_bone.at<float>(4, 0), squeezed_for_bone.at<float>(4, 1));
        //UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone 20 xy : %f, %f"), squeezed_for_bone.at<float>(20, 0), squeezed_for_bone.at<float>(20, 1));



        if (isLeft)
        {
            for (int j = 0; j < squeezed_for_bone.size[0]; j++)
            {
                squeezed_for_bone.at<float>(j, 0) = (squeezed_for_origin.at<float>(j, 1) - squeezed_for_origin.at<float>(0, 1)) * skeletonXRatio * 2 * -1;
                squeezed_for_bone.at<float>(j, 1) = (squeezed_for_origin.at<float>(j, 0) - 0.5) * skeletonYRatio * 2;
                squeezed_for_bone.at<float>(j, 2) = squeezed_for_origin.at<float>(j, 2) * skeletonYRatio * 3 * -1;
                //UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone %d : %f, %f, %f"), j, squeezed_for_bone.at<float>(j, 1), squeezed_for_bone.at<float>(j, 2), squeezed_for_bone.at<float>(j, 3));

            }
            handLeft = squeezed_for_bone.clone();
            handLeftImg = squeezed_for_img.clone();
        }
        else
        {
            for (int j = 0; j < squeezed_for_bone.size[0]; j++)
            {
                squeezed_for_bone.at<float>(j, 0) = (squeezed_for_origin.at<float>(j, 1) - squeezed_for_origin.at<float>(0, 1)) * skeletonXRatio * 2;
                squeezed_for_bone.at<float>(j, 1) = (squeezed_for_origin.at<float>(j, 0) - 0.5) * skeletonYRatio * 2;
                squeezed_for_bone.at<float>(j, 2) = squeezed_for_origin.at<float>(j, 2) * skeletonYRatio * 3;
                //UE_LOG(LogTemp, Log, TEXT("squeezed_for_bone %d : %f, %f, %f"), j, squeezed_for_bone.at<float>(j, 1), squeezed_for_bone.at<float>(j, 2), squeezed_for_bone.at<float>(j, 3));
            }

            handRight = squeezed_for_bone.clone();
            handRightImg = squeezed_for_img.clone();
        }
        //UE_LOG(LogTemp, Log, TEXT("handLeft rows : %d, cols : %d"), handLeft.size[0], handLeft.size[1]);
    }

    //std::cout << std::endl;
    return denorm_imgs_landmarks;

}

bool Blaze::IsLeftHand(cv::Mat normalizedLandmarks)
{
    if (normalizedLandmarks.at<float>(4, 0) < normalizedLandmarks.at<float>(20, 0))
        return true;
    return false;
}

 

 

 

 

 

 

+ Recent posts