2~3달간 언리얼 공부하면서 프로젝트 발표했던것들이 있어

여기에 링크 남긴다.

 

1차프로젝트 기획 - 손검출을이용한 자동차시뮬레이터

https://docs.google.com/presentation/d/1c_5qoX-Va1VqvE6wc0jZqaYnp4DzvrmP/edit#slide=id.p1

1차프로젝트 프로토타입 - 손검출을이용한 자동차시뮬레이터

https://docs.google.com/presentation/d/1LW0pUNbyc7b5z3Wp8OHd9OvmSf9EkkGR/edit#slide=id.p1

1차프로젝트 알파 - 손검출을이용한 자동차시뮬레이터

https://docs.google.com/presentation/d/1WS2-zUlpS_MkMcmRUssAwGekLn9cIfnP/edit#slide=id.p1

1차프로젝트 베타 - 손검출을이용한 자동차시뮬레이터

https://docs.google.com/presentation/d/1PsUrccbHe4Myl8WK3wAMrnlad7Db2SBl/edit#slide=id.p1

 

 

2차프로젝트 기획 - 버추얼 피아노

https://docs.google.com/presentation/d/1GVN8IPpeQm4J18yRnm5Fh2AIEu4S9xjA/edit#slide=id.p1

2차프로젝트 프로토타입 - 버추얼 피아노

https://docs.google.com/presentation/d/1bkyBLjFY6aWnxru6j455r26aSTKrrahf/edit#slide=id.p1

2차프로젝트 프로토타입 - 버추얼 칼림바

https://docs.google.com/presentation/d/1ajEUDPyJJkSeywkgIDZRUJMIftPbPQJ0/edit#slide=id.p1

 

원래 버추얼 피아노를 하려했다가 손 잡기 문제가 있어 데스크탑으로 변경

 

2차프로젝트 알파 - 버추얼 데스크탑

https://docs.google.com/presentation/d/1eqiJEY-xM-Y5gBGS7s-QilQf6SxJkxAs/edit#slide=id.p1

2차프로젝트 베타 - 버추얼 데스크탑

https://docs.google.com/presentation/d/1_Vrt8yKb-K48xkh1Iu9uwGp3V7cAMx4Q/edit#slide=id.p1

 

 

 

3차프로젝트 기획 - 관성 자세

https://docs.google.com/presentation/d/1YyUrZmpN605Wa3vn5tbPP9gpS3zDR_MF/edit#slide=id.p1

 

 

 

 

 

 

우선 먼저 웹캠 스트리밍부터 다시해보자

BP_Screen 생성하고 이름을 BP_ScreenCam, 좀 작게 보도록 스케일을 줄여 배치

 

 

 

 

 

 

 

웹캠 영상을 위한 이미지와 택스처, 준비하고

 */
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 = 1280;
	int webcamHeight = 720;

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

};

 

 

 

이전에 주석처리한 코드 고쳐서 수정

* 노트북 웹캠이 아니라 따로 연결한 웹캠쓸꺼라 비디오캡처 1

* 웹캠 set으로 1280x720 받아오도록 설정.

* 하지만 스크린이 FHD 사이즈라 텍스처 크기를 모니터 기준으로함



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

	capture = cv::VideoCapture(1);

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

}


void ADesktopGameModeBase::ReadFrame()
{

	if (!capture.isOpened())
	{
		return;
	}
	capture.read(webcamImage);
	MatToTexture2D(webcamImage);



	ScreensToCVMats();
	CVMatsToTextures();
}

 

 

 

MatToTexture2D가 기존에 imageTexture로 되있던걸 webcamTexture로 수정

* 웹캠 영상이 1280 x 720이라 FHD로 리사이징하여 처리

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

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

}

 

 

웹캠 영상 띄우기 위해

데스크톱 게임모드 베이스의 변수에

StrScreenWebcam 디폴트값은 BP_ScreenWebcam과

ScreenCam 추가

 

 

스크린웹캠을 위한 이벤트 그래프 추가

 

 

이벤트 틱쪽도 추가

 

 

 

웹캠 영상이 잘나오는걸 확인

 

 

 

영상 추론, 전처리, 후처리 코드를 모아둘 

None타입 클래스 Blaze 생성

 

 

 

 

opencv를 사용할것이므로 Blaze.h에 와서 opencv 헤더부터 추가

 

 

#pragma once

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

#include "CoreMinimal.h"

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

 

 

먼저 모델로드부터 작성

#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;
};

 

 

최근 blazepam은 256 x 256 입력받으나

tflite2onnx로 변환을 못해서 구버전 blazepalm으로 변환시켜 사용

이 구버전 blazepalm은 128 x 128 입력받음

blazepalm_old.onnx
3.70MB
blazehand.onnx
7.72MB

// 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()
{
}

 

 

 

이제 리사이징, 패딩 파이썬 코드를 cpp로 작성

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))
    
    // img256을 srcimg의 (0, 0) 좌표에 그리기
    cv::Mat roi1 = srcimg(cv::Rect(0, 0, img256.cols, img256.rows));
    img256.copyTo(roi1);

    // img128를 srcimg의 (300, 0) 좌표에 그리기
    cv::Mat roi2 = srcimg(cv::Rect(300, 0, img128.cols, img128.rows));
    img128.copyTo(roi2);
    return img1, img2, scale, pad

 

 

리사이즈엔 패드 cpp 코드

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

    // img256을 srcimg의 (0, 0) 좌표에 그리기
    cv::Mat roi1 = srcimg(cv::Rect(0, 0, img256.cols, img256.rows));
    img256.copyTo(roi1);

    // img128를 srcimg의 (300, 0) 좌표에 그리기
    cv::Mat roi2 = srcimg(cv::Rect(300, 0, img128.cols, img128.rows));
    img128.copyTo(roi2);
}

 

 

 

 

 

 

데스크탑게임모드베이스로 돌아와

read frame 중에 동작하도록 약간 고쳐주자

리사이즈 앤 패드 후 스케일과 패드도 출력


void ADesktopGameModeBase::ReadFrame()
{

	if (!capture.isOpened())
	{
		return;
	}
	capture.read(webcamImage);


	Blaze::ResizeAndPad(webcamImage, img256, img128, scale, pad);
	UE_LOG(LogTemp, Log, TEXT("scale value: %f, pad value: (%f, %f)"), scale, pad[0], pad[1]);


	MatToTexture2D(webcamImage);


	ScreensToCVMats();
	CVMatsToTextures();
}

 

 

256x256, 128 x 128 이미지가 패드 붙여서 잘나온다.

 

 

 

 

 

스케일은 5, 패드는 280, 0이 나오는데 왜이렇게 나오는지 잠깐보면

 

지금 입력되는 이미지는 1280 x 720 해상도로

 

리사이즈 패드 루틴에서

위스가 기므로 아래 조건에 들어간다.

 

스케일을 구하기위해 h1은 256 x 720 / 1280 = 144

scale = 720 / 144 = 5

기존의 폭이 720이었으나 256 x 256이미지에서 패딩을 제외하면 144가 되므로 5배가 줄어든게 맞음

 

    int h1, w1, padw, padh;

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

 

 

위스가 길기때문에 패딩은 h방향으로 붙는데

padh = 256 - h1 = 256 - 144 = 112

padh1 = 112 / 2 = 56

pad = (padh1 * scale, 0) = (56 x 5 = 280, 0)

 

패딩 길이가 280이여야 위아래로 붙였을때 원본 이미지의 종횡 길이가 일치함

일단 리사이즈 앤 패드로 패딩처리된 256, 128 이미지, 스케일, 패딩(h길이, w길이) 값 가져옴

 

 

 

 

 

 

잠깐 파이썬으로 돌아와 보면

이제 팜 디텍션을 할 차례

이전에 차 시뮬레이터 만들면서 팜 디텍터만 이미 사용해봤었는데

(아래의 블라즈팜 오닉스 추론 예제 참고해서 c++ 코드로만들었음)

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

 

BlazePalm ONNX Inference Test

Colaboratory notebook

colab.research.google.com

 

mediapipepytorch는 파이썬, 토치 기준으로 작성되어있지만

여기선 c++, opencv dnn으로 블라즈팜 추론 수행해야함

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

    if back_detector:
        normalized_face_detections = face_detector.predict_on_image(img1)
    else:
        normalized_face_detections = face_detector.predict_on_image(img2)
    normalized_palm_detections = palm_detector.predict_on_image(img1)

 

 

 

 

 

일단  블라즈팜 디텍터 코드를 들어가서보면

전처리 -> 추론 -> 텐서투 디텍션 -> NMS 필터링 순으로 동작된다.

먼저 전처리 코드부터 보자

    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

 

 

 

전처리 코드는 /255 뿐인데 옆에 주석으로 # 127.5 - 1.0으로 되어있다.

cvdnn으로 팜디텍터 전처리할때는 /127.5 - 1했었는데 여긴 왜 /255한진 모르겠다.

    def _preprocess(self, x):
        """Converts the image pixels to the range [-1, 1]."""
        return x.float() / 255.# 127.5 - 1.0

 

 

 

일단 전에 만들어둔 전처리 + 추론 코드는 이런식이다.

전처리로 정규화후

W x H x C 이미지를

N C W H 블롭으로 만들고

출력레이어 이름 지정, 출력 레이어 결과 받아올 outputs 준비

net.forward(출력결과받을행렬, 출력레이어이름들) 로 출력결과 받아옴

    cv::Mat tensor;
    inputImg.convertTo(tensor, CV_32F, 1 / 127.5, -1.0);
    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";

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

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

 

 

 

 

파이썬에서 텐서즈 투 디텍션을보면

꽤 복잡하다

스코어 텐서를 시그모이드해서 점수가져오고

디코드 박스로 박스가져오고

최소 스코어 임계치로 필터링하는 내용

 

    def _tensors_to_detections(self, raw_box_tensor, raw_score_tensor, anchors):
        """The output of the neural network is a tensor of shape (b, 896, 16)
        containing the bounding box regressor predictions, as well as a tensor 
        of shape (b, 896, 1) with the classification confidences.

        This function converts these two "raw" tensors into proper detections.
        Returns a list of (num_detections, 17) tensors, one for each image in
        the batch.

        This is based on the source code from:
        mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.cc
        mediapipe/calculators/tflite/tflite_tensors_to_detections_calculator.proto
        """
        assert raw_box_tensor.ndimension() == 3
        assert raw_box_tensor.shape[1] == self.num_anchors
        assert raw_box_tensor.shape[2] == self.num_coords

        assert raw_score_tensor.ndimension() == 3
        assert raw_score_tensor.shape[1] == self.num_anchors
        assert raw_score_tensor.shape[2] == self.num_classes

        assert raw_box_tensor.shape[0] == raw_score_tensor.shape[0]
        
        detection_boxes = self._decode_boxes(raw_box_tensor, anchors)
        
        thresh = self.score_clipping_thresh
        raw_score_tensor = raw_score_tensor.clamp(-thresh, thresh)
        detection_scores = raw_score_tensor.sigmoid().squeeze(dim=-1)
        
        # Note: we stripped off the last dimension from the scores tensor
        # because there is only has one class. Now we can simply use a mask
        # to filter out the boxes with too low confidence.
        mask = detection_scores >= self.min_score_thresh

        # Because each image from the batch can have a different number of
        # detections, process them one at a time using a loop.
        output_detections = []
        for i in range(raw_box_tensor.shape[0]):
            boxes = detection_boxes[i, mask[i]]
            scores = detection_scores[i, mask[i]].unsqueeze(dim=-1)
            output_detections.append(torch.cat((boxes, scores), dim=-1))

        return output_detections

    def _decode_boxes(self, raw_boxes, anchors):
        """Converts the predictions into actual coordinates using
        the anchor boxes. Processes the entire batch at once.
        """
        boxes = torch.zeros_like(raw_boxes)

        x_center = raw_boxes[..., 0] / self.x_scale * anchors[:, 2] + anchors[:, 0]
        y_center = raw_boxes[..., 1] / self.y_scale * anchors[:, 3] + anchors[:, 1]

        w = raw_boxes[..., 2] / self.w_scale * anchors[:, 2]
        h = raw_boxes[..., 3] / self.h_scale * anchors[:, 3]

        boxes[..., 0] = y_center - h / 2.  # ymin
        boxes[..., 1] = x_center - w / 2.  # xmin
        boxes[..., 2] = y_center + h / 2.  # ymax
        boxes[..., 3] = x_center + w / 2.  # xmax

        for k in range(self.num_keypoints):
            offset = 4 + k*2
            keypoint_x = raw_boxes[..., offset    ] / self.x_scale * anchors[:, 2] + anchors[:, 0]
            keypoint_y = raw_boxes[..., offset + 1] / self.y_scale * anchors[:, 3] + anchors[:, 1]
            boxes[..., offset    ] = keypoint_x
            boxes[..., offset + 1] = keypoint_y

        return boxes

 

 

 

이전에 스코어랑, 박스 좌표를 가져오는 후처리 코드 만들어둔게 있긴한데

여기서는 키포인트까지 담지는 않고, (스코어,x,y,w,h) 순으로 만들었엇다.

 

파이썬 코드는  (ymin,xmin,ymax,xmax,kp1x,kp1y, ...., kp6x, pk6y, score)로 17개인 형태


struct DetectResult {
    float score;
    int x;
    int y;
    int w;
    int h;
};

    
std::vector<ASimulatorGameModeBase::DetectResult> ASimulatorGameModeBase::getDetectResults(cv::Mat frame, cv::dnn::Net net, cv::Size detectInputSize, cv::Size detectOutputSize)
{
    std::vector<DetectResult> beforeNMSResults;
    std::vector<DetectResult> afterNMSResults;
    std::vector<float> scores;
    std::vector<int> indices;
    std::vector<cv::Rect> boundingBoxes;

    cv::Mat inputImg;
    cv::resize(frame, inputImg, detectInputSize);
    cv::cvtColor(inputImg, inputImg, cv::COLOR_BGR2RGB);

    cv::Mat tensor;
    inputImg.convertTo(tensor, CV_32F, 1 / 127.5, -1.0);
    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";

    net.setInput(blob);
    std::vector<cv::Mat> outputs;
    net.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) {
                DetectResult res = getDetectResult(frame, regressor, classificator, 8, 2, x, y, a, 0, detectInputSize, detectOutputSize);
                if (res.score != 0)
                {
                    beforeNMSResults.push_back(res);
                    boundingBoxes.push_back(cv::Rect(res.x, res.y, res.w, res.h));
                    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) {
                DetectResult res = getDetectResult(frame, regressor, classificator, 16, 6, x, y, a, 512, detectInputSize, detectOutputSize);
                if (res.score != 0)
                {
                    beforeNMSResults.push_back(res);
                    boundingBoxes.push_back(cv::Rect(res.x, res.y, res.w, res.h));
                    scores.push_back(res.score);
                }
            }
        }
    }

    cv::dnn::NMSBoxes(boundingBoxes, scores, 0.5, 0.3, indices);

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

    return afterNMSResults;
}

 

 

 

 

 

일단 blaze.h로 돌아와 팜 디텍션에 필요한 변수 함수 구조체 선언

팜 디텍션에 키포인트 좌표도 필요하긴 한데 일단 동작되는지만 보기위해 기존 코드 비슷하게 감


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

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


	//for resize and pad
	static void ResizeAndPad(
		cv::Mat& srcimg, cv::Mat& img256,
		cv::Mat& img128, float& scale, cv::Scalar& pad
	);



	// var and funcs for blazepalm
	struct PalmDetection {
		int x;
		int y;
		int w;
		int h;
		float score;
	};


	float palmMinScoreThresh = 0.5;
	float palmMinNMSThresh = 0.3;
	int palmMinNumKeyPoints = 6;

	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<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 inputImg;
    cv::cvtColor(img, inputImg, cv::COLOR_BGR2RGB);

    cv::Mat tensor;
    inputImg.convertTo(tensor, CV_32F, 1 / 127.5, -1.0);
    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, detectInputSize, detectOutputSize);
                if (res.score != 0)
                {
                    beforeNMSResults.push_back(res);
                    boundingBoxes.push_back(cv::Rect(res.x, res.y, res.w, res.h));
                    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, detectInputSize, detectOutputSize);
                if (res.score != 0)
                {
                    beforeNMSResults.push_back(res);
                    boundingBoxes.push_back(cv::Rect(res.x, res.y, res.w, res.h));
                    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{0, 0, 0, 0, 0.0f};

    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.score = score;
    res.x = int(x);
    res.y = int(y);
    res.w = int(w);
    res.h = int(h);
    return res;
}

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

 

 

 

반정규화 과정은

패딩 처리된 256 x 256 이미지 기준 정규화된 좌표들을

패딩 처리되어 1280 x 1280 형태로 돌린 후 패딩을 제거해

1280 x 720이미지에 맞게 위치를 조정하는 내용

 

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

 

 

 

일단 내가 가지고 있는 팜디텍션 모델은 구버전이라 256 x 256이 아닌 128 x 128 사용하므로 조금 다름

코드 작성하다보니 xmin, ymin, xmax, ymax로 다루고 있었는데 내코드에는 xywh를 쓰고 있었네

잠깐 기존 PalmDetection 내용을 (ymin,xmin,ymax,xmax, score) 형태로 수정

 

 

블라즈 헤더

	struct PalmDetection {
		int ymin;
		int xmin;
		int ymax;
		int xmax;
		float score;
	};

블라드 cpp

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{0, 0, 0, 0, 0.0f};

    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 = int(y);
    res.xmin = int(x);
    res.ymax = int(y + h);
    res.xmax = int(x + w);
    res.score = score;
    return res;
}

 

 

 

 

블라즈팜 구버전은 128 사이즈 사용하므로

검출결과들 반정규화 코드는 이런식으로 작성

 

	// var and funcs for blazepalm
	struct PalmDetection {
		int ymin;
		int xmin;
		int ymax;
		int xmax;
		float score;
	};

	int blazePalmSize = 128;
	float palmMinScoreThresh = 0.5;
	float palmMinNMSThresh = 0.3;
	int palmMinNumKeyPoints = 6;

	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, float scale, cv::Scalar pad);
};

 

std::vector<Blaze::PalmDetection> Blaze::DenormalizePalmDetections(std::vector<Blaze::PalmDetection> detections, float scale, cv::Scalar pad)
{

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

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

 

 

 

 

 

이제 시각화 코드도 작성

 


	// var and funcs for blazepalm
	struct PalmDetection {
		int ymin;
		int xmin;
		int ymax;
		int xmax;
		float score;
	};

	int blazePalmSize = 128;
	float palmMinScoreThresh = 0.5;
	float palmMinNMSThresh = 0.3;
	int palmMinNumKeyPoints = 6;

	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, float scale, cv::Scalar pad);
	void DrawPalmDetections(cv::Mat& img, std::vector<Blaze::PalmDetection> 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);
    }
}

 

 

일단 여기까지 기본적인 블라즈쪽 코드 구현은 다된듯하다.

 

 

하다가 꼬인게 데스크탑 게임모드베이스에서 블라즈 생성해서 쓰도록 수정

블라즈쪽에는 스태틱 함수를 그냥 함수로 변경

 

데스크탑 게임모드베이스 해더

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

};

 

블라즈 헤더

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

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


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



	// var and funcs for blazepalm
	struct PalmDetection {
		int ymin;
		int xmin;
		int ymax;
		int xmax;
		float score;
	};

	int blazePalmSize = 128;
	float palmMinScoreThresh = 0.5;
	float palmMinNMSThresh = 0.3;
	int palmMinNumKeyPoints = 6;

	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, float scale, cv::Scalar pad);
	void DrawPalmDetections(cv::Mat& img, std::vector<Blaze::PalmDetection> denormDets);

};

데스크탑 게임모드베이스 cpp - 비긴플레이에 블라즈 생성

#include "DesktopGameModeBase.h"


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

	capture = cv::VideoCapture(1);
	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(webcamImage);


	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, scale, pad);
	blaze.DrawPalmDetections(webcamImage, denormDets);


	std::string dets_str = "norm dets : " + std::to_string(normDets.size()) + ", denorm dets : " + std::to_string(denormDets.size());
	cv::putText(webcamImage, dets_str, cv::Point(0, 50), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(125, 125, 125), 2);

	MatToTexture2D(webcamImage);


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

 

 

 

 

다 된줄 알았는데

프레딕트 팜 디텍션즈에서 PalmDetection 바꾼걸 반영안시켜놔서 에러났다.

바뀐 PalmDetection에 맞게 수정

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 inputImg;
    cv::cvtColor(img, inputImg, cv::COLOR_BGR2RGB);

    cv::Mat tensor;
    inputImg.convertTo(tensor, CV_32F, 1 / 127.5, -1.0);
    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, res.ymin);
                    cv::Point2d endPt = cv::Point2d(res.xmax, res.ymax);

                    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, res.ymin);
                    cv::Point2d endPt = cv::Point2d(res.xmax, res.ymax);

                    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.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, scale, pad);
	blaze.DrawPalmDetections(webcamImage, denormDets);


	std::string dets_size_str = "norm dets : " + std::to_string(normDets.size()) + ", denorm dets : " + std::to_string(denormDets.size());
	cv::putText(webcamImage, dets_size_str, cv::Point(30, 50), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(255, 0, 0), 2);

	int i = 1;
	for (auto& det : denormDets)
	{

		std::ostringstream oss;
		oss << "denorm dets : (" << det.xmin << ", " << det.ymin << "),(" << det.xmax << ", " << det.ymax << ")";

		std::string det_str = oss.str();
		cv::putText(webcamImage, det_str, cv::Point(30, 50 + 50 * i), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 255), 2);
		i++;
	}

 

denormDets에 들어간 값이 50000넘는걸봐서 원래 반정규화된 체로 들어가는 느낌이라

normDets도 출력시켜봤더니 정상적인 좌표값이 나온다

이 모델 검출 결과가 처음부터 반정규화 된체로 나오는듯?

 

 

 

전에는 차시뮬레이터 만들때는 가능했는데 지금은 왜 이상한가 계속 찾다보니

좌표 데이터가 128 x 128 입력 이미지 기준으로 나오고 있었고 

여기 반정규화 코드와 맞지 않았다.

 

 

 

잠깐 내가 blazehand.onnx를 어디서 얻었는지 생각 안나서 잠깐 찾아봤는데 링크남김

blazepalm.onnx도 다운 가능한데 옛날 모델이 아니라 내가 작성한 코드는 못쓴다.

https://storage.googleapis.com/ailia-models/blazepalm/blazepalm.onnx

https://storage.googleapis.com/ailia-models/blazehand/blazehand.onnx

 

 

다시 반정규화 문제 해결하는걸로 돌아와서

구버전 블라즈팜은 128 x 128 기준 좌표를 반환하므로 /128로 정규화

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{0, 0, 0, 0, 0.0f};

    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;
    res.score = score;
    return res;
}

 

 

반정규화 코드에서는 width, height 받아서 긴쪽으로 스케일링하여 1280 x 1280 크기 좌표들 만든뒤

패딩 만큼 -하여 조정

 

블라즈 해더 cpp

	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);
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;
        denormDets.push_back(denormDet);
    }
    return denormDets;
}

 

 

 

 

손없을때 오탐하긴 하지만 대충 쫒아가긴 한다.

 

 

 

기존에 만든 블라즈팜 코드에는 박스 검출만하고 키포인트는 뭔지 몰라서 구현안했는데

키포인트가 손관절인걸 알았고 필요하니 팜디텍션에 추가

 

구버전 블라즈 핸드 파이썬 코드에서 키포인트는 이렇게 구하였다.

def draw_patch(regressor, classificator, stride, anchor_count, column, row, anchor, offset, color):
  index = (int(row * 128 / stride) + column) * anchor_count + anchor + offset

  score = sigmoid(regressor[index][0])
  if score < 0.5: return

  x, y, w, h = classificator[index][:4]

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

  rect = patches.Rectangle((x, y), w, h, color = color, fill = None)
  ax.add_patch(rect)

  for key in range(7):
    x = classificator[index][4 + key * 2]
    y = classificator[index][5 + key * 2]
    
    x += (column + 0.5) * stride
    y += (row    + 0.5) * stride
    
    rect = patches.Rectangle((x, y), 1, 1, facecolor = color)
    ax.add_patch(rect)

 

 

 

 

팜디텍션 구조체는 이렇게 고치고

 

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

 

 

 

 

키포인트 들은 이렇게 뽑아냄

* 이상한 값 필터링을위해 중간에 /blazemPalmSize 한뒤에 0보다 작거나 1보다 큰경우 나와서 조건줌

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;
    res.score = score;
    
    if ((res.ymin < 0) || (res.xmin < 0) || (res.xmax > 1) || (res.ymax > 1)) return res;

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

 

 

 

 

키포인트 반정규화 코드도 추가

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

    }
}

 

 

 

원래 1280 x 720 이미지 사용했는데

종횡비도 너무 틀어지고

128 x 128 모델 입력으로 넣기엔 패딩까지 포함해서 너무 작아져서인지

스코어 임계치(0.35) 낮춰도 오판은 무시하더라도 손을 잘 찾질 못했다.

웹캠 해상도를 640 x 480으로 변경하여 사용하니 좀 나아진것같긴한데 여전하다.

 

차 시뮬레이터에서도 이정도로 못잡지는 않았는데 키포인트 코드 추가한뒤로 영잡질못한다

 

있다보니 괜찬아졋는데, 카메라 조명 조정되면서 뭔가 바뀐듯하기도하고

 

 

지금보니 블라즈팜이 왜 바뀐지 알겠다.

조금만 돌려도 너무 못찾음

 

 

 

 

블라즈 해더의 임계치 설정값은 이런식

	int blazePalmSize = 128;
	float palmMinScoreThresh = 0.4;
	float palmMinNMSThresh = 0.2;
	int palmMinNumKeyPoints = 7;

 

 

 

 

+ Recent posts