반응형

내가 대학교에 입학한 후 1학년인데 학생 연구원을 해볼 생각이 없는지 물어보셨었다.

나는 좋다고 한 후 들어갔는데

교수님이 연구를 위해 Tobii Eye Tracker를 빌려주셔서 그 기회에 이 프로젝트를 해보았다.


눈 제어해보기 전에 이것을 만들기 이전에 어떤 과정이 있었는지 말하면..

 

일단 내가 받은 아이트래커는 

이거인데 Tobii Eye Tracker 5이다. 받고난 후 어떻게 할지 검색해보다가 알게 되었다..

내가 받은건 게임용이고 연구용이 아니기에 연구용 SDK가 없다는 것을..

 

아무리 검색해도 이 기기에서는 아이트래킹 정보를 얻을 수가 없었다.

https://developer.tobii.com/product-integration/stream-engine/

 

Stream Engine - Tobii Developer Zone

The Stream Engine SDK is a low-level SDK intended for advanced users wanting to have tight control over the system resources used by Stream Engine and least amount of signal latency. To get started with the StreamEngine SDK, head over to the getting starte

developer.tobii.com

그러다가 이 것이 나와서 찾아봤는데.. 이건 일단 되는 것이다!

게임용으로 지원하는 SDK이고 이 SDK를 사용해서 아이트래킹이 지원되는 게임을 만드는 것 같다.

 

그래서 이걸 쓰기위해 보니 메일을 보내서 요청하라고 적혀있다.. 그래서 보냈더니..

한국어로 답변이 왔는데.. 아이트래커에 해킹을 시도하거나 다르게 이용을 하는 경우가 있어서 SDK를 못 준다는 말만 왔었다.. 개인적으로 사용할 것이라고 얘기해도 불가능이라고 했다.

 

그래서 그냥 포기할까 하고 한가지 생각이 들었다. 이 회사는 유명하니깐 누군가가 Github에 올려놓지 않았을까 하고..

그래서 찾아보니 진짜로 있었다. 32비트, 64비트 모두 Github에 아이트래커 프로젝트를 올리면서 같이 올려진 것들이 있다.

그래서 나는 이걸 다운받아서 시도했더니 아이트래커에 접근이 됬었던 것!

 

(근데 한가지 슬픈건.. 이 SDK가 C++과 C#만 가능한데.. 연구원에서.. 교수님과 나만 C++이 가능했던 것.. 그래서 이 연구와 관련된 프로그램을 제작하는건 모두 내가 맡아서 했었다.. 파이썬으로 실행할 수 있는 것이 github에 있던데.. 더 별로여 보여서 그냥 C++로 하기로 했다..)

 

https://developer.tobii.com/product-integration/stream-engine/tutorial_cplusplus/

 

Stream Engine Tutorial - C++ - Tobii Developer Zone

This policy applies to solutions that store or transfer Tobii eye tracking data, presence or position data, regardless of the purpose for the storing or transferring. End-users care about their data integrity and privacy. Therefore, as a vendor you must ga

developer.tobii.com

아무튼 예제 코드는 여기에 있다. C++ 프로젝트로 사용하는 방법도 나와있고..

뭐 일단 이제 VRChat과 관련된 얘기를 해보겠다!

일단 VRChat에는 OSC 라는 기능이 있다.

https://docs.vrchat.com/docs/osc-overview

 

OSC Overview

Intro to OSC OSC is a way to get different devices and applications to talk to each other. It's a favorite method of creative coders and people making weird interactive things because it's fast, networked, and very open-ended. What does this have to do wit

docs.vrchat.com

여기에 자세히 나와있는데 OSC를 이용하면 VRChat에서 아바타나 VRChat 기능과 소통하면서 여러가지 기능들을 제어할 수가 있는 것 같다.

예전에는 이걸로 번역기도 만들어보긴 했는데.. 굉장히 간단하게 할수가 있었다.

 

아무튼 저 글에서 보면 

https://docs.vrchat.com/docs/osc-eye-tracking

 

OSC Eye Tracking

VRChat now offers support for receiving eye tracking data (eyelook and eyelid) via OSC. Please note: This is an advanced feature! It is NOT plug-and-play. You must create your own program to transmit this data to VRChat using OSC. Hardware manufacturers ma

docs.vrchat.com

 

 

아이트래킹을 할 수 있는 링크가 설명이 되어있는데 대충 이거다.

/tracking/eye/EyesClosedAmount

/tracking/eye/CenterPitchYaw
/tracking/eye/CenterPitchYawDist
/tracking/eye/CenterVec
/tracking/eye/CenterVecFull
/tracking/eye/LeftRightPitchYaw
/tracking/eye/LeftRightVec

(참고로 여기로 9000 포트와 함께 데이터를 보내면 OSC가 작동한다.)

 

일단 Stream Engine을 살펴보니 잘은 모르겠지만 내가 보고 있는 시선의 위치만 가져올 수 있는 것 같다. 뭐.. 게임 용이라 그런 것 같은데.. 아니면 내가 제대로 안 찾아본 것일 수도 있고..

 

아무튼 내가 보고 있는 위치만 가져올 수 있으니깐.. 사용할 수 있는 OSC는 "/tracking/eye/CenterPitchYaw" 이거다. 이거는 문서에 잘 설명 되어있다.

그리고 눈을 감았는지에 대한건 "/tracking/eye/EyesClosedAmount" 이걸 사용하면 될 것 같다.

 

아무튼 이걸 알았으니 이제 예제 코드를 실행해볼껀데, 그냥 실행하면 이런 오류가 난다. 아마도 내꺼가 Github에서 가져와서 버전이 낮은 것 때문에 그런 것 같은데..

// Connect to the first tracker found
tobii_device_t* device = NULL;
result = tobii_device_create(api, url, TOBII_FIELD_OF_USE_STORE_OR_TRANSFER_FALSE, &device);
assert(result == TOBII_ERROR_NO_ERROR);

여기 코드에서 TOBII_FIELD_OF_USE_STORE_OR_TRANSFER_FALSE 이게 오류가 난다.

 

그냥 해결 방법은 그냥 저거 빼고 

사진에 나온거 2개 중에 아무거나 넣으면 된다.

그리고

이런 오류 나면.. 뭐 다르게 해결하는 방법이 있지만 빠르게 테스트 하기 위해 맨 상단에 이걸 넣는다.

#define _CRT_SECURE_NO_WARNINGS

아무튼 이러면 오류가 사라진다.

 

이제 실행해보면 Tobii Eye Tracker이 정상적으로 연결 되어있는 경우 작동하는데 해보면 내가 보고 있는 시선이 x, y로 나올꺼다. 그런데 내가 눈을 감거나 감지할 수 없는 영역을 쳐다보면 아무것도 출력이 안되는데.. 이걸 알 수 있다는 것은 눈을 깜박이는 것을 알 수 있다는 것이다! 확실히 알기 위해 코드를 수정했다!

void gaze_point_callback(tobii_gaze_point_t const* gaze_point, void* /* user_data */)
{
    // Check that the data is valid before using it
    if (gaze_point->validity == TOBII_VALIDITY_VALID)
        printf("Gaze point: %f, %f\n",
            gaze_point->position_xy[0],
            gaze_point->position_xy[1]);
    else // 추가
        printf("Gaze Error\n"); // 추가
}

// 추가 라고 되어있는 것이 내가 추가한건데 그냥 인식 안되면 출력하라는거다..

이렇게 하면 정상적으로 눈을 깜으면 Gaze Error이 표시되고 감지 영역에서 눈을 움직이면 쳐다보는 곳이 잘 출력된다..!

 

그리고 한가지 알아둘 것이 있는데 출력되고 있는 것을 보면

Gaze point: 0.695525, 0.411705
Gaze point: 0.634280, 0.405759
Gaze point: 0.541244, 0.393794
Gaze point: 0.465725, 0.383487
Gaze point: 0.423483, 0.384978
Gaze point: 0.402698, 0.381798
Gaze point: 0.368750, 0.379669
Gaze point: 1.126799, 0.609565
Gaze point: 1.110942, 0.619803
Gaze point: 1.121417, 0.617664
Gaze point: 1.146164, 0.605248
Gaze point: 1.153657, 0.606785
Gaze point: 1.154399, 0.599591
Gaze point: 1.155324, 0.597877

 

 

이런게 있는데.. 이 숫자에 대한 것이다.

이 숫자는 그냥 x와 y의 시선 값인데 0부터 1 사이 값들은 모니터 안에 있는 것이고

1 이상들은 모니터 밖을 쳐다보고 있다는 것이다.

또한 모니터가 1920 * 1080 이면 각각에 x와 y 값을 곱해보면 내가 보고 있던 픽셀을 알수도 있다는 것이다.

 

이번에는 VRChat OSC에 정보를 보내는 방법에 대한 얘기인데

Python이라면 그냥 python-osc 이거 사용해서 로컬 9000 포트로 보내면 끝난다.

근데 C++이기에 OSC 라이브러리를 사용해야한다.

 

일단 난 그냥 간단하게 이걸 사용했다.

https://github.com/CINPLA/oscpack

 

GitHub - CINPLA/oscpack: Automatically exported from code.google.com/p/oscpack

Automatically exported from code.google.com/p/oscpack - CINPLA/oscpack

github.com

 

아무튼 바로 코드!

#include "tobii/tobii.h"
#include "tobii/tobii_streams.h"
#include <iostream>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <conio.h>
#include <Windows.h>
#include <typeinfo>
#include "osc/OscOutboundPacketStream.h"
#include "ip/UdpSocket.h"
#include <opencv2/opencv.hpp>

#pragma comment(lib, "Ws2_32.lib")

#define CanvasW 1920 / 4
#define CanvasH 1080 / 4

const char* ip = "127.0.0.1";
const int port = 9000;

float last_sent_value = -1.0f; // 이전에 보낸 값을 저장할 변수
bool test_mode = true; // 테스트 모드 설정

cv::Point2f current_point(0.0f, 0.0f); // 현재 점의 위치
cv::Point2f target_point(0.0f, 0.0f); // 목표 점의 위치

const float gaze_x_min = -25.0f;
const float gaze_x_max = 25.0f;
const float gaze_y_min = -15.0f;
const float gaze_y_max = 20.0f;

cv::Mat canvas(CanvasH, CanvasW, CV_8UC3, cv::Scalar(255, 255, 255)); // canvas 선언

void send_message(UdpTransmitSocket& transmitSocket, const char* address, float value) {
    if (value == last_sent_value) return; // 이전에 보낸 값과 같다면 보내지 않음

    char buffer[1024];
    osc::OutboundPacketStream p(buffer, 1024);

    p << osc::BeginMessage(address) << value << osc::EndMessage;
    transmitSocket.Send(p.Data(), p.Size());

    last_sent_value = value; // 보낸 값을 저장
}

void send_gaze_message(UdpTransmitSocket& transmitSocket, const char* address, float x, float y) {
    char buffer[1024];
    osc::OutboundPacketStream p(buffer, 1024);

    p << osc::BeginMessage(address) << x << y << osc::EndMessage;
    transmitSocket.Send(p.Data(), p.Size());
}

void gaze_point_callback(tobii_gaze_point_t const* gaze_point, void* user_data)
{
    UdpTransmitSocket* transmitSocket = static_cast<UdpTransmitSocket*>(user_data);
    if (gaze_point->validity == TOBII_VALIDITY_VALID) {
        printf("Gaze point: %f, %f\n",
            gaze_point->position_xy[0],
            gaze_point->position_xy[1]);

        float x = gaze_point->position_xy[0] * (gaze_x_max - gaze_x_min) + gaze_x_min;
        float y = gaze_point->position_xy[1] * (gaze_y_max - gaze_y_min) + gaze_y_min;

        send_gaze_message(*transmitSocket, "/tracking/eye/CenterPitchYaw", y, x);

        send_message(*transmitSocket, "/tracking/eye/EyesClosedAmount", 0.0f);

        if (test_mode) {
            target_point = cv::Point2f(gaze_point->position_xy[0] * CanvasW, gaze_point->position_xy[1] * CanvasH);
            current_point = target_point;
            cv::circle(canvas, current_point, 3, cv::Scalar(255, 0, 0), -1); // 파란색으로 변경
        }
    }
    else {
        std::cout << "Invalid" << std::endl;
        send_message(*transmitSocket, "/tracking/eye/EyesClosedAmount", 1.0f);

        if (test_mode) {
            current_point = target_point;
            cv::circle(canvas, current_point, 3, cv::Scalar(0, 0, 255), -1); // 빨간색으로 변경
        }
    }
}

void url_receiver(char const* url, void* user_data)
{
    char* buffer = (char*)user_data;
    if (*buffer != '\0') return; // only keep first value

    if (strlen(url) < 256)
        strcpy_s(buffer, 256, url); // strcpy_s로 변경
}

int main()
{
    // Create API
    tobii_api_t* api = NULL;
    tobii_error_t result = tobii_api_create(&api, NULL, NULL);
    assert(result == TOBII_ERROR_NO_ERROR);

    // Enumerate devices to find connected eye trackers, keep the first
    char url[256] = { 0 };
    result = tobii_enumerate_local_device_urls(api, url_receiver, url);
    assert(result == TOBII_ERROR_NO_ERROR);
    if (*url == '\0')
    {
        printf("Error: No device found\n");
        //return 1;
    }

    // Connect to the first tracker found
    tobii_device_t* device = NULL;
    result = tobii_device_create(api, url, TOBII_FIELD_OF_USE_ANALYTICAL, &device);
    std::cout << typeid(result).name() << std::endl;
    assert(result == TOBII_ERROR_NO_ERROR);

    // Subscribe to gaze data
    UdpTransmitSocket transmitSocket(IpEndpointName(ip, port));
    result = tobii_gaze_point_subscribe(device, gaze_point_callback, &transmitSocket);
    assert(result == TOBII_ERROR_NO_ERROR);

    // Initialize socket and server address

    while (true) {
        // Optionally block this thread until data is available. Especially useful if running in a separate thread.
        result = tobii_wait_for_callbacks(1, &device);
        assert(result == TOBII_ERROR_NO_ERROR || result == TOBII_ERROR_TIMED_OUT);

        // Process callbacks on this thread if data is available
        result = tobii_device_process_callbacks(device);
        assert(result == TOBII_ERROR_NO_ERROR);

        if (test_mode) {
            cv::imshow("Gaze Point", canvas);
            cv::waitKey(1);
            canvas.setTo(cv::Scalar(255, 255, 255));
        }

        if (_kbhit()) {
            int ch = _getch();
            if (ch == 27) // ESC Key
                break;
        }
    }

    // Cleanup
    result = tobii_gaze_point_unsubscribe(device);
    assert(result == TOBII_ERROR_NO_ERROR);
    result = tobii_device_destroy(device);
    assert(result == TOBII_ERROR_NO_ERROR);
    result = tobii_api_destroy(api);
    assert(result == TOBII_ERROR_NO_ERROR);
    return 0;
}

 

일단 이건 내가 그냥 간단하게 테스트만 해볼려고 만든 코드이다.

그래서 코드가 더러운 것도 있고 굳이 필요 없는 기능이 있기도 하다.

뭐 그래도 대충 이 코드를 실행하면 정상적으로 눈 제어가 가능한 아바타에서는 작동한다!!

 

그리고 코드에 보면 캔버스 설정한게 있는데 그냥 그건 OpenCV로 어디를 보고 있는지 표시하기 위해 놔둔 것이다. test_mode 끄면 나오지 않는다.

 

const float gaze_x_min = -25.0f;
const float gaze_x_max = 25.0f;
const float gaze_y_min = -15.0f;
const float gaze_y_max = 20.0f;

그리고 이런 코드가 있는데.. 이 코드는 VRChat OSC로 데이터를 보낼 때 데이터 범위이다.

각 아바타 마다 데이터의 범위가 정해져있는 것 같다. 일단 내가 사용하는 아바타는 일일이 값을 보내서 테스트 해본 결과 저정도 인 것 같다.

 

아무튼 이렇게 하면 눈 제어와 눈 깜박이는 것을 모두 아이트래커로 가능하다! 심지어 놀라운건 나는 아이트래킹은 VR 유저만 가능한 줄 알았는데 데스크탑에서도 가능하다는 것..

이게 테스트 영상!

 

아무튼 끝!

반응형
반응형

일단 바로 제작된 결과!

음 얼굴을 찾아서 뭔가 하는 프로젝트를 하는데 문제가 생겼다. 얼굴 이미지를 벡터로 변환하는 과정에서 원본 이미지와 얼굴 이미지를 보여줄려 하는데 여기에서 원본 이미지를 보여줄 때 보여주는 곳이 정사각형이다..

근데 OpenCV에서 이미지를 바로 넣을려니깐 이미지를 리사이즈 해서 바로 넣는 것은 좀 불편하고.. 이미지를 정사각형에 맞춰 그냥 리사이즈 하면 이미지가 뭉개지고..

 

그래서 생각한건 흰 배경 중심에 이미지를 넣는 것인데

이걸 해결하기 위해 1시간 반이 걸렸다..

대충 생각을 해본건 

  • 원본 이미지 보여주는 곳과 같은 크기의 배경을 만들어 중앙에 이미지를 배치
  • 원본 이미지의 가장 긴 변의 길이 대로 정사각형 배경을 만들어 중앙에 이미지를 배치

이런거고 중앙에다 배치할땐

  • 비율에 맞춰 원본 이미지를 축소해서 정사각형 배경의 반에 원본 이미지의 짧은 변의 반을 빼서 위치 구하기
  • 정사각형 배경의 반에 원본 이미지의 짧은 변의 반을 빼서 위치 구하기

등을 생각했는데.. 난 여러가지 방법을 생각해보고 여러가지 방법을 해보다가 헷갈리기도 하고 수정하기도 하며 결국엔

원본 이미지의 가장 긴 변의 길이 대로 정사각형 배경을 만들어 중앙에 이미지를 배치할 때 정사각형 배경의 반에 원본 이미지의 짧은 변의 반을 빼서 위치를 구해서 바로 넣는 방법을 하기로 결정했다..

 

그래서 이걸 대충 이미지로 표시하면!

 

이런식으로 

일단 정사각형이면 그냥 패스

가로가 더 길 경우 가로 길이에 대한 정사각형을 만들고 그게 검은색 사각형!

거기에 빨간색이 원본 이미지꺼..

그래서 파란색 동그라미 부분의 위치를 구하는건데

 

검은색 사각형에서 반을 나누고 거기에서 빨간색 사각형의 반을 빼면 저 위치가 나올 것 같다 라는 생각이 들어서 한 것!

파란색은 반대로 생각하면 되는거!

 

대충 식은

(Square / 2) - (OriginalImg / 2)

뭐 어쨌든 이런 방법으로 코드를 구현한 결과!

 

if (I_celeb_img.nc() == I_celeb_img.nr())
    GUICon::putWebcamView(I_celeb_img, preSetImage);
else {
    int squareLen = (I_celeb_img.nc() < I_celeb_img.nr() ? I_celeb_img.nr() : I_celeb_img.nc());

    cv::Mat originalViewImage(squareLen, squareLen, CV_8UC3);
    originalViewImage = cv::Scalar(0xFF, 0xFF, 0xFF);

    //배경 이미지 중앙에 사진 넣기
    //사진의 세로가 가로보다 짧을 경우 squareLen의 길이에서 반을 나누고 이미지 세로의 반 만큼 빼서 좌표 구하기						
    if (I_celeb_img.nr() < I_celeb_img.nc()) { //가로가 더 클 경우 같을 경우 비교 안하는건 위에서 이미 해서
        int ypos = (squareLen / 2) - (I_celeb_img.nr() / 2); //세로가 짧을 경우 정사각형의 한 변의 길에서 이미지 세로 반을 빼서 위치 구하는 것
        CPputImage(I_celeb_img, originalViewImage, cv::Rect(0, ypos, I_celeb_img.nc(), I_celeb_img.nr()));
    }
    else {
        int xpos = (squareLen / 2) - (I_celeb_img.nc() / 2); //위와 반대
        CPputImage(I_celeb_img, originalViewImage, cv::Rect(xpos, 0, I_celeb_img.nc(), I_celeb_img.nr()));
    }
    cv::cvtColor(originalViewImage, originalViewImage, cv::COLOR_RGB2BGR);
    GUICon::putWebcamView(originalViewImage, preSetImage);
}

코드는 이렇다!

여기에서 CPputImage는 그냥 OpenCV 이미지의 특정 위치에 이미지를 넣는 함수고

GUICon::putWebCamView는 원본 이미지 표시하는 쪽에 이미지 넣는 함수다.. 암튼 그럼!

 

그럼 끝!

반응형
반응형

아마 글꼴을 찾는데 문제가 있을 수 있습니다 "맑은 고딕"은 정상적으로 작동되지만 컴퓨터마다 다를 수 있을 것 같습니다. 될 수도 있고 안 될 수도 있다는 점.. 안된다면.. 폰트 쪽 코드를 수정하면 될지도..?

 

일단 음.. 이걸 성공하게 된건.. 한국어나 영어로 찾아볼 땐 아무리 찾아도 안나왔는데.. 일본에는 전문가들이 많으니깐 있지 않을까해서 일본어로 검색해보니.. 역시 있었다. 그래서 해결하게 됨

 

바로 가져갈 분은 맨 아래쪽에 있어요.

 

https://jitaku.work/it/category/image-processing/opencv/write_japanese/

 

C++のOpenCVで日本語をputTextする

Top 作成日: 2020.06.29 C++のOpenCVで日本語をputTextする Pythonの場合比較的容易にできる、日本語を画面描画する方法。 C++はそういったことができないので非常に困っていた。 で、ネットからパク

jitaku.work

일단 나는 여기 것을 사용했는데. 여기에 있는 코드를 바로 쓰면 당연히 안될꺼다.

HFONT hFont = ::CreateFontA(
        fontSize, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE,
        SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
        VARIABLE_PITCH | FF_ROMAN, fontname);

코드를 보면 이런데.. 저기 아래 SHIFTJIS_CHARSET 저것때문에 안된다. 아마 인코딩 문제 같은데 뭐 저거를 다른 대부분 언어도 되게 할려면 DEFAULT_CHARSET으로 하면 아마 될 것 같다. 당연 한국어도 됨!

 

일단 저걸로 하면 되고

추가적으로 수정한 것

일단 내 프로젝트에선 좀 중요하기 때문에 얘를 그냥 출력하면 안티에일리어싱이 결국엔 안된다.

위의 코드에서 안티에일리어싱을 키면 되는데 킨다 하더라도 코드 때문에 결국은 안된다.

 

일단 이 코드의 방식을 보면 먼저 MFC로 어디선가?에서 적을 OpenCV Mat에서의 가로 길이만큼의 텍스트 이미지를 생성한 후 가져와서 비트맵을 Mat 방식대로 바꿔서 적용하는 방식이다. (아마 맞을꺼임)

 

근데 저기 코드를 대충 보면

if (_tmp[0] == 0 && _tmp[1] == 0 && _tmp[2] == 0) {
                    _img[0] = (unsigned char)color.val[0];
                    _img[1] = (unsigned char)color.val[1];
                    _img[2] = (unsigned char)color.val[2];
                }

이런 코드가 있다. 그리고 위쪽에 보면 back_color로 0x99가 정의 되어 있는데.. 이걸 보면 대충 알 수 있다. 일단 MFC에서 텍스트 이미지를 생성하면 텍스트 밖은 기본적으로 흰색(255, 255, 255), 텍스트는 검은색(0, 0, 0)이다.이다. 또한 0x99는 153으로 RGB 153 153 153 뭐 이래보면 회색이다. 최대가 255이니.. 그래서 최종적으로 저 코드는 텍스트외의 배경 (153, 153, 153)과 (255, 255, 255) 부분을 제외한 검은색 부분(텍스트 부분)을 가져와 사전에 정의한 RGB색대로 다시 설정하는 방식이다.

 

이 때문에 이 코드에선 아무리 안티에일리어싱을 한들.. 안티 에일리어싱이 픽셀 사이사이에 텍스트보다 연한 색을 찍는건데 이 것들이 다 제외되면서 결국엔 안티 에일리어싱이 안찍힌다..

 

그래서 내가 생각한 방식은 안티 에일리어싱의 경우 연한 색도 처리해야하기 때문에 배경색이 바뀔때마다 당연히 글자 색도 변경될테니 배경색을 직접 지정하고 텍스트 색도 직접 지정해서 나중에 배경색만 빼는 것이다. 그러면 결국엔 적용되는건 텍스트의 진한 픽셀부터 연한 픽셀까지이다. 그래서 이 방법으로 코딩한건 뭐 아래에 넣을꺼고

 

또한 내가 지정한 것 중에 다른 것들도 있지만 ori가 있다. 내가 당시 했을때 오리엔테이션인가 뭔가 하는걸로 줄여서 적은 것 같은데.. 잘 못 지정한 것 같다.

어쨌든 저 코드를 보면

int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준이 왼쪽이면 그대로, 중간이면 기준점 - 넓이 / 2, 오른쪽이면 기준점 - 넓이

대충 이렇게 적어놨다. 조건연산자로 이리저리 해놨는데 주석문을 보면 대충 알 수 있다. 0이면 왼쪽 기준 1이면 중간 기준 2이면 오른쪽 기준

말 그대로다. 사전에 정의한 글자 위치대로 0이면 텍스트의 맨 왼쪽 기준으로 텍스트가 생성되고 1이면 텍스트의 중간으로 생성 2이면 텍스트의 오른쪽 끝으로 생성된다.

 

그리고 세로 축도 할려했는데.. 음..! 이 때 당시엔 필요가 없었어서.. 하실 분들은 직접 하시길! 내가 기억하기론 세로는 맨 위쪽 기준으로 되는걸로 알고 있다.

 

코드를 보면 뭐 배경색 칠하는 것도 있고 텍스트 색 지정하는 것도 있고 그렇다.. 어쨌든 그런거임!

 

이제 코드를 보여줌!

코드

void _putText(cv::Mat& img, const cv::String& text, const cv::Point& org, const int ori, const char* fontname, const int fontWeight, const double fontScale, const bool anti, const RGBScale textcolor, const RGBScale bkcolor) {
	int fontSize = (int)(10 * fontScale);
	int width = img.cols;
	int height = fontSize * 3 / 2;

	HDC hdc = CreateCompatibleDC(NULL);

	HBRUSH hBrush = CreateSolidBrush(bkcolor.rgb);

	RECT rect;
	rect.left = rect.top = 0;
	rect.right = width;
	rect.bottom = height;

	BITMAPINFOHEADER header;
	ZeroMemory(&header, sizeof(BITMAPINFOHEADER));
	header.biSize = sizeof(BITMAPINFOHEADER);
	header.biWidth = width;
	header.biHeight = height;
	header.biPlanes = 1;
	header.biBitCount = 24;
	BITMAPINFO bitmapInfo;
	bitmapInfo.bmiHeader = header;
	HBITMAP hbmp = CreateDIBSection(NULL, (LPBITMAPINFO)&bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);
	SelectObject(hdc, hbmp);

	FillRect(hdc, &rect, hBrush);

	BITMAP  bitmap;
	GetObject(hbmp, sizeof(BITMAP), &bitmap);

	HFONT hFont = CreateFontA(
		fontSize,
		0,
		0,
		0,
		fontWeight,
		FALSE,
		FALSE,
		FALSE,
		DEFAULT_CHARSET,
		OUT_DEFAULT_PRECIS, //SHIFTJIS_CHARSET -> DEFAULT_CHARSET으로 변경
		CLIP_DEFAULT_PRECIS,
		(anti ? ANTIALIASED_QUALITY : DEFAULT_QUALITY), //안티를 허용하면 해주고 아니면 안해주고
		VARIABLE_PITCH | FF_ROMAN,
		fontname);
	SelectObject(hdc, hFont);
	SetTextColor(hdc, textcolor.rgb);
	SetBkColor(hdc, bkcolor.rgb);

	//넓이 높이 구하기
	SIZE size;
	GetTextExtentPoint32A(hdc, text.c_str(), (int)text.length(), &size);

	TextOutA(hdc, 0, height / 3 * 1, text.c_str(), (int)text.length());
	int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준이 왼쪽이면 그대로, 중간이면 기준점 - 넓이 / 2, 오른쪽이면 기준점 - 넓이
	int posY = org.y - (size.cy / 2 + 5);

	unsigned char* _tmp;
	unsigned char* _img;
	for (int y = 0; y < bitmap.bmHeight; y++) {
		if (posY + y >= 0 && posY + y < img.rows) {
			_img = img.data + (int)(3 * posX + (posY + y) * (((bitmap.bmBitsPixel / 8) * img.cols) & ~3));
			_tmp = (unsigned char*)(bitmap.bmBits) + (int)((bitmap.bmHeight - y - 1) * (((bitmap.bmBitsPixel / 8) * bitmap.bmWidth) & ~3));
			for (int x = 0; x < bitmap.bmWidth; x++) {
				if (x + posX >= img.cols) {
					break;
				}

				if (_tmp[0] != bkcolor.b || _tmp[1] != bkcolor.g || _tmp[2] != bkcolor.r) { //순서를 bgr를 써서 bgr로 한거, rgb 순서이면 그걸로 해야함
					_img[0] = (unsigned char)_tmp[0];
					_img[1] = (unsigned char)_tmp[1];
					_img[2] = (unsigned char)_tmp[2];
				}
				_img += 3;
				_tmp += 3;
			}
		}
	}

	DeleteObject(hFont);
	DeleteObject(hbmp);
	DeleteDC(hdc);
}

주석문 보고 뭐 대충 하면 될 것 같다. 난 OPENCV의 기본 MAT의 설정인 BGR때문에 아래가 BGR 기준이고 RGB면 순서를 바꿔서 작성해야 할 것이다.

 

cv::Mat& img, const cv::String& text, const cv::Point& org, const int ori, const char* fontname, const int fontWeight, const double fontScale, const bool anti, const RGBScale textcolor, const RGBScale bkcolor

파라미터를 대충 설명하면

  • cv::Mat& img -> 텍스트를 적용할 이미지
  • cv::String& text -> 출력할 텍스트
  • cv::Point& org -> OpenCV 이미지에서 텍스트를 지정할 위치
  • int ori -> 텍스트 위치를 지정할 때 왼쪽 중앙 오른쪽으로 어느 위치로 정렬해서 출력할지
  • char* fontname -> 폰트 이름 (예: 맑은 코딕)
  • int fontWeight -> 글자의 굵기 (예: FW_BOLD 이런거 하면 됨.. 그냥 숫자로 해도 되고)
  • double fontScale -> 글자 크기 (예: 이건 좀 보정을 안했는데 그냥 무조건 큰 숫자 적으면 진짜 커지므로 기본 생각했던 것보다 반 작게 줄여서 적으면 아마 맞을꺼다)
  • bool anti -> 안티에일리어싱을 할꺼냐 말꺼냐
  • RGBScale textcolor -> 텍스트의 컬러 지정 (예: RGBScale(255, 0, 0))
  • RGBScale bkcolor -> 배경색 지정 (예: RGBScale(255, 255, 255))

어쨌든 이정도다.. 뭐 쓰다보니 파라미터가 너무 많아지긴 했지만.. 뭐 내가 쓰는건데 뭐 어때!

_putText(outImg, "나", cvPoint(50, 50), 1, "맑은 고딕", FW_BOLD, 6, true, RGBScale(0, 0, 0), RGBScale(255, 255, 255));

사용 예시는 위와 같다.

 

어쨌든 그렇다. 이거 글 쓰는 것도 힘들 구 생각하는 것도 힘들었는데..

나중에 자기 글에다 쓰고 싶다면... 적어도 링크라도.. 부탁.. (이거 그럼 만든게 2022년 11월 쯤에 만들었으니.. 고등학교 2학년 때 만든거군.. 아 여기 학교 C/C++과목 전혀 없음)

 

뭐 어쨌든 끄읕!

 

얼마나 사용해줄진 모르겠지만.. C++은 겁나 복잡해!!

 

(파이썬은 짧고 쉽던데.. 방식은 이거와 비슷)

 

2023-03-01 추가

코드에서 RGBScale에 대한 구조체 내용이 없어서 다시 정리하고 구조체에 대한 것도 새로 적습니다.

struct RGBScale {
    int r = 0;
    int g = 0;
    int b = 0;
    int rgb = 0;

    RGBScale(int r, int g, int b) {
        this->r = r;
        this->g = g;
        this->b = b;

        this->rgb = RGB(r, g, b);
    }
};

RGBScale(0xFF, 0xFF, 0xFF)로 적으면 되는데 0xFF는 그냥 16진수 이기 때문에 0부터 255 숫자중 원하는 색깔에 맞춰 적으시면 됩니다! 그리고.. 기본적으로 cv::Mat 생성할 때 RGB가 아닌 BGR로 생성하기 때문에 아마 RGB 이미지를 넣어서 적용시키면 색깔이 다르게 나올꺼기 때문에.. 인풋엔 BGR로 넣으시면 됩니다. 수정할려면 아래 적혀있는 코드에서 비트맵을 OpenCV로 바꿔주는 곳에서 b, g, r 적혀있는 것을 r, g, b로 변경하시면 될겁니다. RGBScale를 따로 만든 이유는 그냥 r, g, b도 추출하고 rgb도 출력하고 하기 위함!

 

아래는 다시 정리해본 코드!

void CPputText(cv::Mat& O_image, cv::String text, cv::Point org, int ori, const char* fontName, int fontWeight, double fontScale, RGBScale textColor, RGBScale bkColor) {
	int fontSize = (int)(10 * fontScale);
	int width = O_image.cols;
	int height = fontSize * 3 / 2;

	HDC hdc = CreateCompatibleDC(NULL); //텍스트 이미지를 만들어두는 곳 같은거

	HBRUSH hBrush = CreateSolidBrush(bkColor.rgb); //채우는 방식인데 bkColor로 단색으로 채우는거

	//텍스트 이미지 크기 정하는거
	RECT rect;
	rect.left = rect.top = 0;
	rect.right = width;
	rect.bottom = height;

	//비트맵의 구조를 사전에 정의하는 것 크기나 색
	BITMAPINFOHEADER header;
	ZeroMemory(&header, sizeof(BITMAPINFOHEADER));
	header.biSize = sizeof(BITMAPINFOHEADER);
	header.biWidth = width;
	header.biHeight = height;
	header.biPlanes = 1;
	header.biBitCount = 24;
	BITMAPINFO bitmapInfo;
	bitmapInfo.bmiHeader = header;
	HBITMAP hbmp = CreateDIBSection(NULL, (LPBITMAPINFO)&bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);
	SelectObject(hdc, hbmp); //hdc에 적용? 하는 거

	FillRect(hdc, &rect, hBrush); //지정한 크기만큼 완전하게 채우는거 (다 채움)

	BITMAP bitmap;
	GetObject(hbmp, sizeof(BITMAP), &bitmap);

	//텍스트 이미지 만들 때 사용할 수 있는 폰트를 생성? 하는 그런거
	HFONT hFont = CreateFontA(
		fontSize,
		0,
		0,
		0,
		fontWeight,
		FALSE,
		FALSE,
		FALSE,
		DEFAULT_CHARSET, //한국어나 일본어나 해주게 하는거 (아마)
		OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS,
		ANTIALIASED_QUALITY, //안티 에일리어싱을 켜주는거
		VARIABLE_PITCH | FF_ROMAN,
		fontName);
	SelectObject(hdc, hFont);
	SetTextColor(hdc, textColor.rgb);
	SetBkColor(hdc, bkColor.rgb);

	//계산을 위해 미리 텍스트의 사이즈 구하는거
	SIZE size;
	GetTextExtentPoint32A(hdc, text.c_str(), (int)text.length(), &size);

	TextOutA(hdc, 0, height / 3 * 1, text.c_str(), (int)text.length()); //이미지에 텍스트 적는거
	int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준 정하는거 0은 텍스트의 왼쪽 1은 텍스트의 중간 2는 텍스트의 오른쪽
	int posY = org.y - (size.cy / 2 + 5);

	//비트맵 사진을 OpenCV이미지에 삽입해주는거
	unsigned char* _tmp;
	unsigned char* _img;
	for (int y = 0; y < bitmap.bmHeight; y++) {
		if (posY + y >= 0 && posY + y < O_image.rows) {
			_img = O_image.data + (int)(3 * posX + (posY + y) * (((bitmap.bmBitsPixel / 8) * O_image.cols) & ~3));
			_tmp = (unsigned char*)(bitmap.bmBits) + (int)((bitmap.bmHeight - y - 1) * (((bitmap.bmBitsPixel / 8) * bitmap.bmWidth) & ~3));
			for (int x = 0; x < bitmap.bmWidth; x++) {
				if (x + posX >= O_image.cols)
					break;

				if (_tmp[0] != bkcolor.b || _tmp[1] != bkcolor.g || _tmp[2] != bkcolor.r) { //텍스트 이미지의 배경 컬러는 없애기 위한 것, bgr 순서로 하는 이유는 Mat 이미지를 처음에 만들 때 BGR 순이여서
					_img[0] = (unsigned char)_tmp[0]; //B
					_img[1] = (unsigned char)_tmp[1]; //G
					_img[2] = (unsigned char)_tmp[2]; //R
				}
				_img += 3;
				_tmp += 3;
			}
		}
	}

	//메모리에서 삭제해주는거 이거 안하면 메모리 계속 사용함
	DeleteObject(hBrush);
	DeleteObject(hFont);
	DeleteObject(hbmp);
	DeleteObject(hdc);
}
cv::Mat inputMat(500, 500, CV_8UC3);
CPputText(inputMat, "안녕!", cvPoint(inputMat.cols / 2, inputMat.rows), 1, "맑은 고딕", FW_BOLD, 6, RGBScale(0xFF, 0xFF, 0xFF), RGBScale(0, 0, 0));

아아마 이렇게 하면 500 * 500 으로 이미지를 생성하고 그 중간에 텍스트를 넣는거니 완전 중간은 아니고.. 세로가 좀 아래로 쳐져있긴 하지만.. 저렇게 하면 잘 작동은 합니다!

반응형
반응형

https://youtu.be/Mm2eYfj0SgA

예에에에전에 3Blue1Brown분의 푸리에 급수 영상을 보고

갑자기 저런건 도데체 어떻게 만들까.. 라는 생각이 들었다.

 

그래서 여러 곳을 찾아보다가 일단 제일 기본적인 것을 해보고 싶어서 해보았었다.

그래서 위 영상을 찾았고 JS로 되어있어서 C++로 변경을 하고 작동을 하게 새로 만들어서 결국엔 성공했다.

예전에 작게 테스트로 작성해본거라 잘 작성하진 못해서 오래 계산하면 느려지기도 하고 그렇지만.. 어쨌든 된다!

 

언젠간 코드를 다시 짜봐야지

 

https://github.com/cheongpark/Fourier-Series_CPP

 

GitHub - cheongpark/Fourier-Series_CPP: graphics.h

graphics.h. Contribute to cheongpark/Fourier-Series_CPP development by creating an account on GitHub.

github.com

https://youtube.com/shorts/pZXLSCzHu3I?feature=share 

 

 

반응형
반응형

밤 11시쯤에 할꺼 없어서 그냥 프로그래머스 보다가

예전부터 스킬체크를 봤었는데..

다른 공개 문제의 1단계가.. 어렵다 보니 하다가 못 풀까봐 걱정했는데..

이 참에 할꺼 없어서 한번 도전해봤다.

 

결과

!통과!

해본 소감

1단계에 있던 공개문제보다.. 훨씬 쉬웠다.

문제도 별로 안되고 약간의 지식만 있으면 아주 쉽게 풀 수 있는 문제였다.

 

단순한 방법만 생각하면 풀 수 있는 문제였다.

그리고 시간은 40분이였는데.. 너무나도 충분했었다.

15분? 걸렸나? 이것보다도 빠르게 풀 수 있었는데 주석도 달고 오류도 좀 해결하느라.. 좀 걸리긴 했다.

 

다음에 다른 문제도 도전해야지~

반응형
반응형

이 글은 그냥 제가 했던 것을 올리는 것 입니다! 튜토리얼 X

(언젠간 도움이 되지 않을까..)

시간이 있거나 누군가 이 게시글에 강좌글 좀 이라 하면 시간날 때 해보겠습니다! (근데 보는 사람이 있나?)

 

일단 YOLO V5가 C++에서 돌아갈까? 라는 생각은 그냥 학교 가다가 생각난 것..

그래서 검색해보니 YOLO V5가 Pytorch로 모델을 저장하는데, 이 Pytorch가 C++에서 돌릴 수 있는 모델로 변환할 수 있다는 말에 잠시 글을 좀 찾고 해볼 다짐을 했었습니다!

 

정확히는.. (2022-7-18-월) 생각해보니 방학 전이네

 

처음에 찾은 글은 Pytorch를 C++에서 돌리는 방법을 찾아봤더니 LibTorch라는게 있어 그것 부터 해볼려고 해서

먼저 C++로 돌릴 수 있는 파일을 찾아보기로 했습니다.

 

https://chowdera.com/2021/04/20210410171748944y.html

 

Libtorch 6: deployment of C + + version yolov5.4 - 文章整合

One 、 Environment configuration win10 vs2017 libtorch-win-shared-with-deps-debug-1.8.1+cpu opencv349    because yolov5 Code , The author is still updating ( When I write this blog , What's the latest 5.4), The structure of the model may change , So w

chowdera.com

처음엔 이걸 찾았었습니다.

 

그러다가 방학 한 후에 (드론을 날리자 였나?) 이거 프로그램에 신청해서 방학 한 후에 갔다오기도 하고

2022-7-25에, 저어어번에 신청했던 한국데이터산업진흥원 체험하러 갈려 했는데.. 갑자기 아침에 몸이 심각하게 않좋았어서 (아파도 참고 가는 성격) 그냥 쉬었다.. (긴장해서 그런 듯..)

 

그 다음 날 가서 데이터에 대한거 설명 듣고 쓰는 법 배우고 pandas도 해보고 대회(전날 안와서 pandas도 모르는데 선생님이.. 동아리 학생이라고 혼자 팀으로.. (뭐 다른 애들도지만, 3등 안에 못들면 방학 끝나고 발표라던데.. 뭐 하라는 말은 안하셨습니다~)) 도 했었는데 3등도 못들고 시간 안에 한 문제도 못풀고.. 뭐 암튼 그렇게 해서 그 다음 날 다시 드론 가고

 

드론이 다 끝난 수요일에 본격적인 시작을 했습니다.

 

일단 처음에 한 작업은 Visual Studio에서 Pytorch를 해보기 위해 Pytorch로 변환하거나 하는 파이토치 한국 문서(공식 문서 같던데..)를 찾아서 해볼려 했는데 그 전에 그냥 유튜브에 치면 되지 않을까? 해서 봤더니

https://youtu.be/6eTVqYGIWx0

이런 영상이 있어서 바로 해봤지만.. OpenCV를 넣어야 하는데 넣지 못해서(하는 방법을 몰름..) 그래서 바로 포기하고 일반 빈 프로젝트를 만들어서 아까 그 문서대로 따라했는데도.. 안됬습니다.. 보니깐 리눅스에서 돌린 것 같길래..

 

이리저리 해보다가 일단 먼저 변환부터 해보자 해서..

Visual Studio Code 켜서 내가 학습한 것 보단 이미 학습된거 사용할려고 detect.py 실행해서 파일 다운받고 (yolov5x.pt) export.py 로 pt -> torchscript로 변환했습니다. (둘다 pt로 끝나네 우연이겠지?)

 

그리고 실행해볼려는데 계속 안되서.. 그냥 포기하자 하고 포기했습니다.. 하하하 그러다가 낮잠이라도 잘까 할려다가

잠시 누웠더니 누군가 이미 다 파일까지 올려놓지 않았을까? 하는 마음에 구글에 검색해보니

 

https://github.com/yasenh/libtorch-yolov5

 

GitHub - yasenh/libtorch-yolov5: A LibTorch inference implementation of the yolov5

A LibTorch inference implementation of the yolov5. Contribute to yasenh/libtorch-yolov5 development by creating an account on GitHub.

github.com

이걸 찾았습니다.. ㄷㄷ

 

ㄱㅇㄷ이라는 생각을 하고 바로 해보자 해서 Visual Studio Code에서 돌릴려고 CMake 파일도 있어서 CMake로 해보자 라는 생각에 도전했는데 쉽게 안됬습니다.. 하하하 (뭐 당연하지만)

 

CMake도 제대로 해본건 이번이 처음이였고.. 해서 CMake로 Libtorch도 새로 빌드하고 OpenCV도 새로 빌드 하면서 해봤지만.. 실패.. (빌드하다가 알게 됬는데 vcpkg를 사용하면 C++에서 사용가능한 걸로 빌드 해주는걸 찾아서 신기하고 여기서 포기해도 되겠다 라는 생각이 들 정도로.. 좋았다)

 

계속 실패를 하다가 (대략 4시간 이상)

바로 GitBash로 해봤는데도 당연히 실패다..

패키지도 새로 깔고 하다가.. cmake 파일에서 Makefile이 생성되어야 하는데.. 계속 안되더니 보니깐..

대충 윈도에선 Makefile이 생성 안되고 리눅스에선 되는 것 같아보였다.

 

그러다가 리눅스니깐.. 저어어번에 깔았던 Cygwin이 생각나서 바로 몇시간 동안 했었는데.. 음! 당연한 결과 실패

그래서 여기에서의 문제도 Makefile이 생성이 안되는 문제였어서..

 

진짜 리눅스로 돌려야 되는군아.. 생각해서 윈도우에서 우분투를 까는 법을 찾아보니 WSL을 사용하면 된다고 해서 바로 블로그 찾고 해서 깔아서 했습니다. 음 (예전에 Windows 10을 처음에 살 때 Home 버전이 진짜고 Pro는 짝퉁이라고 누가 유튜브에서 유명한 누군가가 그래서 Home 버전을 샀었는데.. 나중에 Pro로 살껄이라는 후회를..) 그래서 Windows Home이여서 WSL를 깔거나 했는데도 안되서 그냥 포기하자 하고.. VR을 새벽까지 하다가 자고 12시에 일어나서 다시 해보자라는 생각으로.. 바이오스에서 SVM모드를 활성화를 하고 좀더 찾아보고 해봤는데도.. 안되서 문제점을 확인하고 Hyper-V가 윈도우 기능에 없어서 안된다는 문제점을 알고 인터넷을 찾아보니 https://forbes.tistory.com/542

 

윈도우10 Home Hyper-V 설치 및 활성화 (Docker Desktop 설치)

HowTo - Windows 10 Home Hyper-V 활성화 방법  Hyper-V는 x64 시스템을 위한 하이퍼바이저 기반의 가상화 시스템으로 Linux, FreeBSD 등 다양한 운영체제를 Windows에서 가상 머신을 이용해 실행할 수 있습니다..

forbes.tistory.com

이런 글이 있길래 바로 해서 결국 까는데에 성공했다..

 

그리고 계속 도전했다. 뭐 45분 정도 게임을 하거나 낮잠을 자긴 했는데 그 시간 빼고는 전부다 이걸 해내는 일에만 계속 도전했다.

 

약 8시간 이상은 쓴듯? 6시간인가? 뭐 암튼

 

우분투 기본 설정도 좀 하고 (리눅스 쓸 줄은 잘 모른다..)

하다가 위에 올린 LibTorch YoloV5 깃헙 그걸 가져와서

설정하는데.. 진짜 짜증나게도 cmake가 없다고 해서 cmake 다시 빌드하고.. 1시간 이상.. 찾아보고 하면서 설치..

cuda도 깔고 하는데도.. 이미 깔려있는데 계속 안된다고 했다..

 

대충 이런느낌..? 사진은 못 찍었는데..

대충 cudnn이 설치 안되어있다고 하거나 뭐가 없다고 하거나..

 

계속 빨간줄이 보였다..

계속 안되다가 좀더 검색하면서 번역도 해가면서 뭐가 문제인지 찾고

경로 설정이 제대로 안되어 있는 것 같아서.. 조금만 바꿔보니.. 뚜둔

뭐가 안되던게 넘어가졌다.. 포기할 까 생각했었지만.. 이 때부터 더 해보는 걸로 했다.

 

된다고 하더라도 그 뒤에 오류도 있어서 대충 찾은건 CUDA_TOOLKIT_ROOT_DIR, CMAKE_CUDA_COMPILER 이 2개가 경로 설정이 안되어서 생긴 오류 였던 것 같다.

 

어찌저찌 해서 했는데 갑자기 Makefile이 생기면서 진행이 되었고

깃헙에 있던 GPU 명령어로 돌렸더니.. 처음엔 안됬다... torchscript도 넣고 다 해보니 안됬는데..

이유가 이건 터미널 창이고 GUI도 없는데 사진 창이 나올리 없었다... (main.cpp코드 Demo 함수에서 저장이 아닌 보여주는걸 확인했었음) 그래서 imwrite를 찾아서 해볼려다가 (이건 왜 안되는지 지금도 잘 모르겠다.) 이것도 안되서 GUI로 변경해볼려 했지만 안되서 그러다가 XLaunch라는걸 찾아서 깔고 하다가 또 안되서.. 또 계속 하다가 블로그 찾아서 또 하고..

그러면서 다 되었다... 테스트로 텍스트 파일을 여니 성공했고...

 

그 후에 돌려보니 정상적으로 작동!

처음에 돌려졌을 때 찍은 사진이다...

와.. 신기하긴 하다.. 이게 파이썬이 아닌 C++로 돌아가다니..

 

그래서 바로 다른 사진도 해보았다.

그냥 구글에서 street 검색해서 나오는거 아무거나 다운받아서 시도!

 

진짜 잘된다.. 너무나도 신기했다..

 

그래서 뭔가 좀 박스 때문에 잘 안보여서..

코드를 좀 수정했다. 뭐 if문 한개만 바꿨지만..

 

분석을 대충 해보니 

옵션에서 view-img를 넣으면 원래는 비활성화 였던거가 활성화로 변경되어서 Demo 함수를 실행

Demo 함수에서는 이렇게 나오는데 대충 보니 

cv::rectangle(img, box, cv::Scalar(204, 243, 238), 2);

이 코드는 박스 색을 지정하는거여서 내가 원하는 색으로 지정했다. (원래는 0, 0, 255 로 빨간색 이였음)

그리고 또 보니 나는 그 위에 Person 상자를 없애고 싶어서 더 확인해보니 putText가 보여서 텍스트 박스를 띄우는거라 생각하고 그 코드를 보니 if문으로 되어있었다. 그리고 label의 bool로 실행되거나 안되는 거였는데 

2번째 위에 올렸던 사진을 보면 Demo 함수를 호출할 때 class_names 값으로 결정한다. (그 곳엔 Demo함수에서 label에 대한걸 확인 후)

그걸 보고 그 위를 보니

대충 코드를 보니 ../weights/coco.names 파일이 비어있다면 -1로 꺼버리는 것 같았다.

뭐 그래서 if문으로 작동하는 것 같아서 0으로 바꿨다.

 

그리고 해보니 아주 이쁘게 나왔다.

어쨌든 성공해서 기뻐서 이 글을 쓰는데.. 지금 4시 11분이네.. 새벽.. (분명 여기까지 끝냈을 때가.. 2022-07-28 새벽 2시 39분 이였다..) 2일 동안 겁나 빡센거 했었네.. 3~4일은 지난 줄 알았는데.. 암막커튼 때문에 시간 개념도 약간 사라졌었고..

 

뭐 어쨌든 글도 다 썼으니.. 오늘 바로 10시에 약속 있어서 자야겠다.. (VR 할려 했는데...)

 

난 파이썬을 잘 몰라서 C++로 해보고 싶어서 시작한건데.. 안해본 것 까지 다 해봐서 좋았다.. 그리고 C++이 파이썬보다 분석이 난 익숙해서 코드 분석이 좀더 쉬웠던 것 같다.

 

이번 걸로 처음 도전해보거나 예전에 해봤지만 제대로 이번에 해본건 : VCPKG, WSL, Ubuntu, CMake, Makefile, LibTorch, (Cuda, Cudnn 우분투 설치), 등등

뭐.. 이 것도 내일되면 거의 다 까먹는다.. 그래도 했다는게 중요한거니 다음에 다시 할때 안되도 오늘 했던 마음을 생각하며 다시 계속 다시 해봐야겠다.. 언젠간 CMakeLists 파일도 만들고 빌드도 다 직접하고 블로그를 최대한 안 찾아보고 할 수 있을 날이 올려나..

 

어쨌든 이번 작업은 예전에 몇주 동안 하던 것 보다 빡쌔서 중간에 포기할까? 포기할까? 생각했지만.. 계속 시도해보고 하니.. 되는걸 보니 간단한 것도 나한테 알려달라 하는 그런 반애들은.. 도데체 뭘까하는 생각이 든다.. 나 처럼 계속 찾아보지도 않고 열심히 해보지도 않고 안된다고.. 나한테 계속 말하는게 좀 짜증나기도 한다..

 

나도 하고 싶어도 안되면 어떻게든 찾아보고 하는데 그런 마음을 다른 애들도 가지면 좋겠다 생각된다..

 

난 안되면 밤새서라도 엄청 계속 하는데 왜 다른 애들은 계속 안해보고 포기해서 나한테 묻는걸까.. (다른 애한테 묻기도 하겠지만..) 뭐 내가 수학을 엄청 모르는거 때문에 대충 마음은 알지만.. 자신의 끈기로 누가 시키지도 않고 도전해보는건데.. 실패하고 포기해서 남을 짜증나게까지 해서 알려달라고 하는게 정말.. 지금도 잘 이해는 안된다.. (나중에는 되겠지..?)

 

싫었던 것

알려주기 싫다고, 혼자 한번 해보라고, 계속 검색해보라고, 난 되는데 넌 왜 안되냐고, 난 엄청 힘들게 했는데 넌 왜 그렇게 안할려하는건데, 여기까지만 알려주고 나머지 자잘한건 직접 찾아보라고 해도

나한테 돌아오는 답변은 안돼, 그냥 좀 알려주라, 너가 재능이 있잖아, 넌 해봤잖아, 넌 알고 있잖아 이미 되잖아, 귀찮으니깐 알려줘 이런 것이다..

내가 진짜 짜증나는 말들은 난 잘 못하는데 계속 남한테 소개하거나 나한테 알려달라 할 때 넌 이거 잘하잖아 하거나 그게 너 재능이라고 하면 짜증난다..

난 분명 못하는데 계속 그렇게 말하니.. 

 

내가 최대한 알려줄 수 있는데까지도 알려주지만.. 알려주기 힘들거나 못 알려주겠거나 안알려주고 싶을 때는 나에게 근처 애들은 점점 사라져간다.. 내가 들은 말 중에 어떤 말은 "내가 얘와 친구 하는건 나중에 도움되거나, 얘가 알려주겠지" 라 내 앞에서 했었다. 내가 앞에 있는데도 그 말을 듣는 순간.. 그 애와 친하게 대화하던 것들이 생각나면서 싫어졌다.. 그 이후로는 대화를 만히 하진 않았다..

 

그래서 그러한 생각들 때문이거나 다른 것들로 난 친구들을 만들지 않거나 회피하기로 했었다. 뭐 지금도 비슷하거나 다르기도 하지만.. 도와달라는 걸로 계속 싸움이 몇십번 일어났었다.. 계속 해보라고 하는데도.. 어떤 애와는 완전 말을 끊기도 했다.. 우xx 난 계속 이러한 생각을 하면서 내가 생각하던 친구들은 내가 제일 잘한다고 생각해서 친구로 있는걸까..? 하고..

요즘에 진행하는 다른 애들의 프로젝트에서도.. 그런 것 때문에 나하고 같이 하자는건가..? 계속 그러다보니.. 현재는 대화는 해도 친구라는 말은 안쓴다.. 그래서 누군하 한테 말할 때도 친구라 하진 않고 친구라 말한다. 

 

그래도 하지만 정xx 같은 그런걸로 만난 친구가 아닌 진짜 서로 잘 맞아서 친구로 있는 친구는 진짜 친구라고 생각된다. 코딩도 딱히 관심없고 해서 나는 그런 친구와는 진짜로 친구라고 한다. 그래서 나에게 진짜 친구는 2022년 7월 29일로 오프라인 친구는 2명이다. (현재와 계속 통화하거나 만나는 친구)

 

뭐 어쨌든 난 그게 싫었고 뭐 이 글을 읽어주셔서 감사합니다!

분명 검색한건 YOLO V5나 C++때문에 검색했겠지만.. 이상한 글이 있어서 의아하셨겠지만..

뭐 나중에 YOLO V5를 C++로 돌리는걸 시간이 되면 올려보겠습니다!

 

교훈 : 안된다 생각하지 말고 어떻게든 방법을 찾아서 끝까지 도전해보자! 

몇 주씩 진짜 해보다가 안되면 그 때 누군가에게 물어보고 도움을 받아보자!

반응형
반응형

 

유튜브

어느 날 알고리즘에 이 영상이 올라왔다.

수학을 못하는 나.. 코딩은 자신 있으니 엑셀이 아닌 C++로 만들어보고 싶었다.

(근데 수업 때 이런걸로 수업해주셨으면.. 수학에 관심 있었을텐데..)

 

준비

일단 제작 방법을 생각해보자!

2중 포문으로 100 * 100의 블럭을 출력하면 될 것 같다.

2중 포문에선 먼저 계산을 해서 배열에 저장하고 나중에 출력하면 될 것 같다.

 

그리고 색까지 넣어서 이쁘게 꾸미는 것을 목표로 한다.

 

제작

#include <iostream>
#include <math.h>
#include <windows.h>
#include <algorithm>

#define x 101 //가로 캔버스 크기
#define y 101 //세로 캔버스 크기
#define outline 15 //아웃라인 색깔

using namespace std;

void textcolor(int color); //텍스트 컬러 지정
void top_bottom_block(int color); //위쪽 아래쪽 아웃라인 블럭 지정
void left_right_blck(int color); //왼쪽 오른쪽 아웃라인 블럭 지정

double a[x][y] = {};

int main() {
	ios::sync_with_stdio(false);
	cout.tie(NULL);

	for (int i = 0; i < x; i++)
		for (int j = 0; j < y; j++)
			a[i][j] = sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2)); //식 입력

	top_bottom_block(outline); //15는 흰색

	for (int i = 0; i < x; i++) {
		left_right_blck(15); //15는 흰색
		for (int j = 0; j < y; j++) {
			textcolor(a[i][j]);
			cout << "■";
		}
		left_right_blck(15); //15는 흰색
		cout << endl;
	}

	top_bottom_block(outline); //15는 흰색
}


void textcolor(int color) {
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}

void top_bottom_block(int color) {
	textcolor(color);

	for (int i = 0; i < x + 2; i++)
		cout << "■";
	cout << endl;
}

void left_right_blck(int color) {
	textcolor(color);
	cout << "■";
}

일단 기본 적인 구조는 만들어 보았다.

캔버스의 크기가 101로 되어있는 이유는 100으로 하게 되면 원의 중심점이 제대로 잡혀지지 않기 때문에 101 홀수로 맞춰서 잡아주었다.

 

 

sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))

식 입력 부분에 위 코드를 집어넣으니 원이 출력되었다.

어떤 원리인진 자세하게는 모르겠지만 어쨌든 신기하다!

 

그 다음은 영상에서 나온 원과 배경을 구분하여 출력하는 것이다.

min((double)1, max((double)0, 25 - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))))

나는 일단 색은 그대로 두었다. 만약 이 색이 마음에 안들면 출력 2중 포문 안쪽 textcolor(a[i][j]); 에서 a[i][j] 뒤에 + 5 이런걸로 해서 색을 지정하든지 일정 값마다 색을 다르게 하는 코드를 추가해서 해보면 될 것 같다.

 

어쨌든 다음!

min((double)1, max((double)0, 25 + 15 * cos(5 * atan2(j - (x / 2), i - (y / 2))) - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))))

신기한건 수학의 식은 한줄이며 이 식만 길어지는 것 뿐인데 그냥 코딩으로 하나씩 포문 여러번 돌리면서 문제를 해결하는 것보다 짧게 되서 수학에 약간 관심이 있어지는 것 같기도..??

 

또 다음!

min((double)1, max((double)0, 20 + 20 * pow((0.5 + 0.5 * cos(5 * atan2(j - (x / 2), i - (y / 2)))), 0.3) - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))))

계속 식을 적으면서 불편한 것은.. 영상에서 나온 엑셀의 식을 C++에 적용을 하기 위해 제곱을 pow로 하고 하는 과정들이 조금 불편하지만.. 오우 너무 근데.. 신기하다..

 

다음!

min((double)1, max((double)0, 20 + 20 * pow((0.5 + 0.5 * cos(5 * atan2(j - (x / 2), i - (y / 2)))), 0.3) - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2)))) + min((double)1, max((double)0, 19 - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))))

으어어어... 길다..

 

다음!

min((double)1, max((double)0, 20 + 20 * pow((0.5 + 0.5 * cos(5 * atan2(j - (x / 2), i - (y / 2)))), 0.3) - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2)))) + min((double)1, max((double)0, 19 - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2)))) - 2 * min((double)1, max((double)0, 2 - abs(4 - sqrt(pow(abs(j - 50) - 6, 2) + pow(i - 45, 2)) * min((double)1, max((double)0, 46 - i))))) - 2 * min((double)1, max((double)0, 2 - abs(8 - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))))) * min(max(i - 50, 0), 1)

드디어 완성!!

영상과는 꽃잎의 회전이 조금 다르지만.. 어쨌든! 성공했다..

으어ㅓㅓㅓ 코드가 너무 길어..!!

 

글을 쓰면서 코드를 적는데 너무 보기도 힘들고 어렵지만.. 다행히 식은 나와있어서.. 다행이였던 것 같다..

내가 혼자 식 만들어서 하라하면.. 절대 못할 듯..

 

어쨌든 추가로 테스트를 더 해봤다.

아래 것 들은 수학을 잘 모르는 상태에서 아무 값이나 이리저리 넣어보면서 테스트 한 것이니.. 뭐라하지 마시길...

 

sqrt(i * j)
tan(i * j)

 

tan(i + j)
20 + 20 * pow((0.5 + 0.5 * cos(5 * atan2(j - (x / 2), i - (y / 2)))), 0.3) - sqrt(pow(j - (x / 2), 2) + pow(i - (y / 2), 2))
cos(i * j)
cos(i + j) * sin(i * j)

내가 테스트로 해본 식들은 그냥 내가 아무거나 식을 적어서 넣어서 출력해본 것이다.

굳이 따지진 말기~

 

근데 진짜 수업시간에 매번 어떻게 쓰는지도 잘 모르는 수학 식으로 수업하는 것보다.. 이렇게 눈으로 그려보면서 수업하는 것도 재밌을 것 같다..

우리 학교는 안해주나...

 

어쨌든 끝입니다!

감사합니다! (2022-05-26 오후 9시 20분 작성완료)

반응형

+ Recent posts