C++에서 Boost ASIO를 사용하여 TCP 동기화(Blocked) 통신 프로그래밍

공대생의 팁 2019. 7. 21. 19:58

 지난 포스팅에서 C++에서 제공하는 Boost 라이브러리의 통신 라이브러리인 ASIO를 사용하여 비동기 통신 프로그래밍을 하는 방법에 대해 소개를 하였던 적이 있었습니다.



C++에서 Boost ASIO를 사용하여 TCP 비동기(Unblocked) 통신 프로그래밍

https://elecs.tistory.com/314


 서버-클라이언트 통신 시스템을 구축하던 도중 Java에서 사용하던 객체 통신 프로그래밍 방식이 상당히 편했던 기억이 있어 이를 C++에서도 편하게 사용할 수 있는 방법을 찾았던 것이어서 이를 정리하였던 글이었는데 생각보다 많은 분들께서 도움을 받으셨다는 답변을 받았습니다.


 그래서 이번에는 ASIO 통신 프로그래밍에서 다룰 만한 내용으로 동기화(Synchronization, Blocked) 프로그래밍 방식에 대해 이야기 해보고자 합니다.





 위 그림은 ASIO 프로그래밍의 동기화 통신 방식의 흐름도를 나타낸 것입니다. 이전 포스팅에서 다루었던 비동기화 통신 방식과는 달리 동기화 통신은 상대로부터 response를 대기하는 과정이 추가됩니다.

 비동기 모드의 경우 Handler를 등록해두면 response의 수신 여부와 관계 없이 서버는 프로그램의 동작을 멈추지 않고 별개의 동작을 수행할 수 있습니다. 반면 동기화 통신의 경우 상대방의 response가 들어오고 나서 동작하는 방식이므로 서로가 통신이 확인이 되었는지의 여부를 확인할 수 있다는 장점이 있습니다.


 먼저 프로세스는 socket에 자신의 IP 정보를 기록한 후 이를 io_service(Boost 1.66 버전 이후는 io_context)를 통해 운영체제 커널에 정보를 등록합니다. 이후 운영체제가 다른 컴퓨터로부터 응답을 수신하게 되면 이를 프로세스의 socket에게 전달하고 프로세스는 전달 받은 정보를 가공하는 방식으로 통신이 이루어집니다.


자세한 내용은 아래의 소스코드를 통해 알아보도록 하겠습니다.



 프로그램을 실행하기에 앞서 자신의 컴퓨터에 Boost 라이브러리를 설치합니다. Ubuntu를 기준으로 다음과 같이 실행합니다.


$ sudo apt install libboost-all-dev



 이제 어러분의 컴퓨터에는 Boost 라이브러리가 설치되었습니다. 다음으로 아래의 소스코드를 작성해줍니다.


sync_server.cc


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<cstdlib>
#include<iostream>
#include<boost/asio.hpp>
 
using namespace std;
using boost::asio::ip::tcp;
 
class session
{
public:
    session(tcp::socket& sock)
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //session(boost::asio::io_context& io_service)
    : socket_(sock){
        start();
    }
 
    void start(){
        try{
            while(1){
                boost::system::error_code error;
                size_t length = socket_.read_some(boost::asio::buffer(data_), error);
                if(error == boost::asio::error::eof){
                    //클라이언트로 부터 정보를 모두 받았으므로 종료한다.
                    break;
                }else if(error){
                    throw boost::system::system_error(error);
                }
                cout << "Message from client: " << data_ << endl;
                boost::asio::write(socket_, boost::asio::buffer(data_, length));
            }
        }catch(exception& e){
            cerr << "Exception in server: " << e.what() << endl;
        }
    }
 
    tcp::socket& socket(){return socket_;}
 
private:
    tcp::socket& socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};
 
class server
{
public:
    server(boost::asio::io_service& io_service, short port)
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //server(boost::asio::io_context& io_service, short port)
    : io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
    {
        while(1){
            //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
            //new session(acceptor_.accept());
 
            //sever의 소켓 생성
            tcp::socket sock(io_service);
            //client로부터 접속 대기
            acceptor_.accept(sock);
            new session(sock);
        }
    }
private:
    boost::asio::io_service& io_service_;
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //boost::asio::io_context &io_service_;
    tcp::acceptor acceptor_;
};
 
int main(int argc, char* argv[]){
    try{
        if(argc != 2){
            cerr << "Usage: server <port>" << endl;
            return 1;
        }
        boost::asio::io_service io_service;
        //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
        //boost::asio::io_context io_service;
        server s(io_service, atoi(argv[1]));
 
    }catch(exception& e){
        cerr << "Exception: " << e.what() << endl;
    }
 
    return 0;
}
cs



sync_client.cc


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<cstdlib>
#include<iostream>
#include<boost/asio.hpp>
 
using boost::asio::ip::tcp;
using namespace std;
 
class client
{
public:
    client(boost::asio::io_service& io_service,
    //client(boost::asio::io_context& io_service,
    const string& host, const string& port) : socket_(io_service)
    {
        tcp::resolver resolver(io_service);
        //서버에 접속을 시도한다.
        boost::asio::connect(socket_, resolver.resolve({host, port}));
 
        string msg = "Hello, world!";
        size_t msg_length = msg.length();
 
        //서버로 데이터를 전송
        boost::asio::write(socket_, boost::asio::buffer(msg, msg_length));
 
        char reply[max_length];
        //서버로부터 데이터를 수신
        size_t reply_length = boost::asio::read(socket_, boost::asio::buffer(reply, msg_length));
        cout << "Reply is: " << reply << endl;
        
    }
 
private:
    tcp::socket socket_;
    enum { max_length = 1024 };
};
 
int main(int argc, char* argv[]){
    try{
        if(argc != 3){
            cerr << "Usage: client <host> <port>" << endl;
            return 1;
        }
 
        //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
        //boost::asio::io_context &io_service;
        boost::asio::io_service io_service;
        client c(io_service, argv[1], argv[2]);
 
    }catch(exception& e){
        cerr<<"Exception: " << e.what() << endl;
    }
 
    return 0;
}
cs



CMakeLists.txt


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmake_minimum_required(VERSION 3.0)
add_compile_options(-std=c++11)
 
project(asio_sync)
find_package(Boost REQUIRED system)
find_package(Threads)
 
include_directories(${Boost_INCLUDE_DIR})
 
add_executable(client
    sync_client.cc
)
 
add_executable(server
    sync_server.cc
)
 
target_link_libraries(client
    ${Boost_LIBRARIES}
    ${CMAKE_THREAD_LIBS_INIT}
)
 
target_link_libraries(server
    ${Boost_LIBRARIES}
)
cs



Linux 환경에서 다음과 같이 프로그램을 컴파일합니다.


$ mkdir build

$ cd build

$ cmake ..

$ make


 이제 우리가 만든 프로그램을 실행하여봅니다.



Server

1
2
$ ./server 2222
Message from client: Hello, world!
cs



Client


1
2
$./client 127.0.0.1 2222
Reply is: Hello, world!
cs



 축하합니다! 이제 여러분들도 Boost 라이브러리의 ASIO를 사용하여 동기화 통신 프로그래밍을 만들 수 있게 되었습니다!

300x250

리눅스(우분투)에서 USB가 동작하지 않을 때 수동으로 연결하기(Failed to open the USB device!)

공대생의 팁 2019. 7. 5. 14:45


 PrimeSense사에서 제작한 RD 1.09 RGB-D 카메라를 USB 3.0에 꽃아서 openni2 환경에서 실행을 해보려 하던 도중 다음과 같은 문제가 발생하였습니다.


No matching device found.... waiting for devices. Reason: openni2_wrapper::OpenNI2Device::OpenNI2Device(const string&) @ /tmp/buildd/ros-indigo-openni2-camera-0.2.2-0trusty-20141015-0837/src/openni2_device.cpp @ 74 : Initialize failed


    Could not open "1d27/0609@1/12": Failed to open the USB device!


보아하니 RD 1.09 버전에서는 USB 2.0 버전만 지원이 되서 발생하는 문제로 추측됩니다. 즉, 디바이스 노드를 생성하지 못하는 상황으로 추측할 수 있습니다. 이 경우 USB 기기를 수동으로 설정하기 위해서는 다음과 같이 진행해줍니다.


 1. 먼저 현재 리눅스(우분투)에 연결된 USB 드라이버의 리스트를 출력합니다.


$ lsusb


 위의 명령어를 입력하시면 다음과 같이 현재 컴퓨터에 연결된 USB 기기들의 리스트가 나타납니다.



 위 목록을 보았을 때 자신의 기기가 어떤 것인지를 파악합니다. 저의 경우 1d27:0609로 써있는 부분으로 추정됩니다.


 2. udev를 사용하여 USB 드라이버를 수동으로 연결하는 규칙을 만들어줍니다. 여기서 .rules의 이름은 자신이 파악하기 쉬운 이름으로 붙여줍니다. (ex: 40-libopenni2-0.rules)


$ sudo vi /etc/udev/rule.d/my_rule.rules


3. 다음과 같이 입력해 주시고 ':wq'를 입력하여 저장해줍니다. ATTR의 'idProduct'와 'idVendor'는 위에서 lsusb를 입력하였을 때 나왔던 ID를 각각 입력해주시고 GROUP의 경우 자신에 맞는 그룹을 설정해줍니다. 저의 경우 카메라를 사용하므로 'video'로 설정하였습니다.


SUBSYSTEM=="usb", ATTR{idProduct}=="0609", ATTR{idVendor}=="1d27", MODE:="0666", OWNER:="root", GROUP:="video"


4. 여기까지 진행하신 후 다시 기기를 사용해보신다면 정상적으로 연결되는 것을 확인하실 수 있습니다.


 혹시 udev에 대해 자세히 알고 싶으신 분은 아래의 블로그 글을 읽어보시기를 추천드립니다!

https://mokga.tistory.com/54

300x250

C++에서 Graphviz로 그래프 이미지 그리기

공대생의 팁 2019. 7. 4. 01:04


 자료구조를 공부하거나 특정한 환경을 그림으로 표현하고자 하는 경우가 있습니다. 특히 위상수학 같이 이산수학이 사용되는 분야에서는 각 객체들의 상호관계를 나타내야 하는 것이 중요하지요. 이러한 특성을 이미지로 나타낸 것을 흔히 그래프(Graph)라고 부릅니다.



 위의 그림은 같은 Depth를 유지하는 것을 목적으로 설계된 B+ 트리를 나타냅니다. 각 노드(혹은 Vertex)가 화살표(혹은 Edge)로 연결되어 있습니다. 특정한 프로그램의 알고리즘을 위와 같이 그림으로 표현한 것을 그래프라고 합니다.


 그렇다면 과연 우리들이 직접 설계한 프로그램을 바로 그림으로 나타낼 수 있는 방법이 있을까요? C++의 Boost library에서는 그래프를 생성할 수 있는 Graphviz를 지원합니다. 이산수학의 그래프 이론을 잘 알고 계시다면 Graphviz를 사용 하는 것이 상당히 쉬워질 것입니다.


 Graphviz를 사용하기에 앞서서 간단한 그래프 이론에 대해 설명을 드리겠습니다. 예를들어 다음과 같은 그래프가 있다고 가정합니다.




 위 그래프는 Graphviz로 생성한 4개의 Vertex와 4개의 Edge를 가지고 있는 그래프입니다. 한 눈으로 봐도 각 vertex인 A, B, C와 D가 각각 edge로 어떻게 연결되었는지 한 번에 알 수 있습니다. 이를 인접 행렬(Adjacency matrix)로 나타내면 다음과 같습니다.



 각 행과 열의 첫 번째 부터 A, B, C, D로 보았을 때 나타낸 인접 행렬의 모습입니다. 첫째 열의 경우 [0 1 0 1] 로 나타내어졌는데 각각 A는 두 번째인 B와 4 번째인 D와 연결된 부분을 1로 나타낸 것입니다. 나머지 열의 경우는 직접 위의 그림과 비교해 보신다면 쉽게 아실 수 있으실 겁니다!


 지금까지 우리는 무방향 그래프(Undirectional graph)를 설명하였습니다. 다음으로는 Edge에 방향성이 포함되어 있는 방향 그래프(Directional graph)를 보도록 하겠습니다.



 위에서 보았던 그래프에 화살표로 방향이 추가된 그래프입니다. 이 그래프 또한 인접 행렬로 다음과 같이 나타낼 수 있습니다.



 위에서 보았던 인접 행렬과 다른 점은 각 열은 출발 vertex, 각 행은 도착 vertex를 나타낸 것입니다. 즉 A의 경우 B와 D의 방향으로 향하고 있기 때문에 첫 번째 열의 경우 [0 1 0 1] 순서로 나타나는 것입니다. 반면, C의 경우 무방향 그래프의 경우 A와 같은 [0 1 0 1]이었으나 B로는 연결 방향이 존재하지 않기 때문에 [0 0 0 1]로 나타내는 것입니다. 또한 방향 그래프는 인접 리스트(Adjacency list)로 아래와 같이 나타낼 수 있습니다.



 위에서 보았던 방향 그래프를 생각하시면 이해하기 쉬우실 겁니다. 각각의 Vertex인 A와 B, C, D를 생성한 후 각 Vertex가 향하는 방향의 Vertex를 연결 리스트(Linked list)로 이어주는 것이지요. 연결되는 순서는 상관 없습니다. 즉 [A → B → C] 로 표기하거나 [A → C → B]로 표기하여도 모두 같은 값을 나타낸다고 보시면 되겠습니다. 여기서 D로부터 시작하는 방향은 존재하지 않으므로 D는 그 어떤 Vertex와 연결되있지 않음을 위에서 보실 수 있습니다.


 그러면 이번에는 위에서 설명한 그래프들을 직접 소스코드로 구현해봅시다. 먼저 Boost library를 컴퓨터에 설치해 줍니다.


$ sudo apt install libboost-all-deb graphviz


 다음으로 아래와 같은 소스코드를 입력합니다.


graphviz.cc


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <cstdio>
 
using namespace std;
 
struct VertexP { string tag; };
struct EdgeP { string symbol; };
struct GraphP { string orientation; };
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexP, EdgeP, GraphP, boost::listS> ListGraph;
 
int main()
{
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
 
    ofstream dot_file("graph.dot");
    string dcmd = "dot -Tpng graph.dot -o graph.png";
 
    // Create the vertexes
    ListGraph::vertex_descriptor a = add_vertex(VertexP{"A"},g);
    ListGraph::vertex_descriptor b = add_vertex(VertexP{"B"},g);
    ListGraph::vertex_descriptor c = add_vertex(VertexP{"C"},g);
    ListGraph::vertex_descriptor d = add_vertex(VertexP{"D"},g);
 
    // Create the edges
    // A → B
    add_edge(a, b, g);
    // B → D
    add_edge(a, d, g);
    // B → C
    add_edge(b, c, g);
    // C → D
    add_edge(c, d, g);
 
    // Print graph
    write_graphviz_dp(dot_file, g, dp);
 
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(dcmd.c_str(), "r"), pclose);
    if (!pipe) {
        throw runtime_error("popen() failed!");
    }
 
    return 0;
}
 
cs



 여기서 소스코드의 중요한 부분을 하나씩 설명해 드리겠습니다.



typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, VertexP, EdgeP, GraphP, boost::listS> ListGraph;


 위에서 설명드렸던 인접 리스트(Adjacency list)를 만들수 있도록 해주는 Boost library입니다. Graphviz로 그래프를 만들어주기 위해 사용됩니다.


14
15
16
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
cs


하나의 인접 리스트 변수 g를 선언합니다. 인접 리스트의 이름은 "Example" 입니다. dynamic_properties는 Graphviz에서 Adjacency list의 내용을 설정해 주기 위해 사용되는 변수입니다. 각 vertex의 이름은 VertexP 구조에 저장되므로 16번 줄을 작성하여 Adjacency list 변수 g에 선언해줍니다.


18
19
    ofstream dot_file("graph.dot");
    string dcmd = "dot -Tpng graph.dot -o graph.png";
cs


 Graphviz를 생성해 주기 위해 파일을 생성합니다. 파일의 이름은 "graph.dot"입니다. Graphviz를 이미지로 생성시켜주는 과정은 Terminal의 명령어로 이루어지므로 명령어를 설정해 줍니다. 명령어는 다음과 같습니다.


$ dot -Tpng graph.dot -o graph.png


 위 명령어는 생성되는 이미지의 확장명은 png로 설정하며 파일명은 graph.png로 저장하겠다는 의미입니다.


21
22
23
24
25
    // Create the vertexes
    ListGraph::vertex_descriptor a = add_vertex(VertexP{"A"},g);
    ListGraph::vertex_descriptor b = add_vertex(VertexP{"B"},g);
    ListGraph::vertex_descriptor c = add_vertex(VertexP{"C"},g);
    ListGraph::vertex_descriptor d = add_vertex(VertexP{"D"},g);
cs


 Vertex들을 생성해줍니다. vertex_descriptor에 해당 vertex를 저장해 줍니다.


27
28
29
30
31
32
33
34
35
// Create the edges
// A → B
add_edge(a, b, g);
// B → D
add_edge(a, d, g);
// B → C
add_edge(b, c, g);
// C → D
add_edge(c, d, g);
cs


 바로 이 부분이 제가 위에서 말씀드렸던 인접 리스트의 방식을 나타낸 것입니다. 각 vertex와 연결하고자 하는 vertex를 위의 순서대로 입력하시면 우리가 원하는 방식대로 edge가 연결됩니다.


37
38
    // Print graph
    write_graphviz_dp(dot_file, g, dp);
cs


 지금까지 우리들이 만들었던 그래프를 Graphviz로 만듭니다. 이를 실행하면 dot 파일이 생성됩니다.


1
2
3
4
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(dcmd.c_str(), "r"), pclose);
    if (!pipe) {
        throw runtime_error("popen() failed!");
    }
cs


 dot 파일로 생성된 Graphviz를 이미지로 만들기 위해 실행되는 부분입니다. 이 과정은 Graphviz 프로그램에서 직접 실행되기 때문에 위와 같이 설정해 주시면 직접 사용자가 dot 명령어를 입력하지 않고도 이미지가 생성됩니다.


여기까지 완성되었다면 이제 컴파일을 해줍니다. 다음과 같은 파일을 만들어줍니다.


Makefile

1
2
3
4
5
CC = g++
CFLAGS = -g -Wall -std=c++11
 
output:graphviz.cc
    $(CC) $(CFLAGS) -o output graphviz.cc
cs


make로 컴파일을 해줍니다.


$ make


이제 컴파일이 실행된 폴더를 확인해보시면 'output' 이라는 이름의 실행파일이 생성됩니다. 이제 실행해봅니다.


$ ./output


 그러면 다음과 같은 이미지 파일이 생성되 있는 것을 확인하실 수 있습니다.



 다음으로 이번에는 방향 그래프를 만들어 보겠습니다. 위 코드에서 다음 부분만 바꿔주시면 되겠습니다.




typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, VertexP, EdgeP, GraphP, boost::listS> ListGraph;


 그러면 다음과 같은 방향 그래프가 생성됩니다.



 이번에는 생성된 그래프를 회전 시켜보겠습니다. 다음과 같은 코드를 추가시켜주세요.


14
15
16
17
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
    dp.property("rankdir", boost::make_constant_property<ListGraph*>(string("LR")));
cs


그러면 다음과 같이 그래프가 90도 회전합니다.



만약 위의 vertex의 순서를 반대로 하고자 하시는 분이라면 소스코드를 다음과 같이 고쳐주세요.


14
15
16
17
    ListGraph g(GraphP{"Example"});
    boost::dynamic_properties dp;
    dp.property("node_id", get(&VertexP::tag,g));
    dp.property("rankdir", boost::make_constant_property<ListGraph*>(string("RL")));
cs


 그러면 vertex의 순서가 좌우 반전으로 다음과 같이 출력됩니다.




300x250

Extreme Learning Machine(극학습기계)

공대생의 팁 2019. 5. 15. 02:57


 인공지능 분야가 발전하면서 다양한 구조의 알고리즘들이 발굴되고 있으며 이는 조금씩 발전되고 있습니다. 물론 각 알고리즘들이 제시하는 다양한 제안들이 하나둘씩 쌓여가다 보면 언젠가는 알파고를 뛰어넘는 새로운 알고리즘이 나타나리라 기대합니다.

 이번 포스팅에서는 2006년 난양공대(NTU)의 Guang-Bin Huang이 투고한 2019년 5월 15일 현재 피인용수 7044회의 논문인 Extreme Learning machine에 대해 보도록 하겠습니다.



 Neural Network(인공신경망)의 구조는 거의 대부분이 다음과 같은 모습을 가지고 있습니다.


 위의 그림에서 x는 input, b는 bias, w는 input layer와 hidden layer를 연결해주는 weight vector, N는 hidden layer의 node, β는 hidden layer와 output layer를 이어주는 weight vector, t는 output을 의미합니다. 위 그림의 neural network를 식으로 다음과 같이 나타낼 수 있습니다.



 이 때 함수 g()는 무한으로 미분이 가능한 함수로 흔히 알려진 삼각함수 sin() 혹은 cos()와 자연함수 e^x가 있습니다.


 위 구조의 Neural Network를 기존의 방식대로 학습을 시킬때는 다음과 같은 방법으로 흔히 사용합니다.



 흔히 사용되는 알고리즘은 forward propagation으로 계산을 하여 기존의 출력값과 비교하여 오차를 구한 후 back propagation 과정을 통해 weight값을 조정합니다. 가장 보편적으로 사용되는 알고리즘이지만 다음과 같은 이슈가 존재합니다.


 1) learning rate인 η의 값이 너무 작으면 학습 속도가 매우 느려진다. 그러나 η의 값이 너무 클 경우 학습 속도가 불안전해 지면서 심할 경우 값이 수렴되지 않고 발산하는 경우가 발생한다.

 2) back propagation을 하는 과정에서 값이 local minima에 빠질 가능성이 존재한다. 이 경우 global minimum에 도달하지 못하고 local minima에 갇혀버릴 수 있다.

 3) back propagation에 의해 오버트래이닝이 되면 over-fitting이 되어 학습 결과물이 더 좋아지지 않을 가능성이 있다.

 4) 위의 알고리즘 자체에서 가지고 있는 핵심 수단인 gradient-base 학습은 시간이 매우 많이 걸리는 방법이다.


 가장 많이 사용되는 gradient-base 알고리즘의 단점들을 좀 더 효율적인 방법을 찾아보기 위해 만들어진 알고리즘이 이번에 소개하는 Extreme Learning Machine입니다.


 그럼 위에서 보았떤 Neural Network의 구조를 다시 한 번 보도록 하겠습니다.



 Neural Network의 입력에서 hidden layer까지의 과정을 H로 가정하고 이를 식으로 나타내면 다음과 같이 나타낼 수 있습니다.



이 때 위의 식에서 나타낸 부분들은 아래와 같이 나타낼 수 있습니다. 





 위에서 주어진 행렬 H를 사용하여 최적의 값을 찾기 위해 다음과 같은 식으로 나타낼 수 있습니다.



 기존의 출력값 T를 행렬 H와 β의 최소값을 찾아 결과값이 최소로 나오게 한다면 우리는 최적의 weight vector를 얻을 수 있습니다. 위 식에서 β의 최소값은 다음과 같은 식으로 찾을 수 있겠습니다.



 이 식을 사용함으로서 기존의 알고리즘에서 사용하던 back propagation 과정을 거치지 않고 적합한 값을 빠르게 찾을 수 있는 방법입니다. 여기서 †는 Dagger(칼표)로서 Moore-Penrose generalized inverse(Pseudo inverse, 의사역행렬)로서 행렬 H가 정사각행렬이 아닐 때 유사 역행렬을 구하는 방식입니다. pseudo inverse에 대해 자세히 알고 싶으신 분들께서는 아래의 사이트를 참조 하시면 큰 도움이 될 것입니다.


https://darkpgmr.tistory.com/108


 이러한 방식의 Extreme Learning Machine을 좀 더 발전시켜 auto encoding을 사용하는 OS-ELM알고리즘과 OR-ELM알고리즘으로 발전하게 됩니다. OR-ELM의 알고리즘에 대해 다루는 사이트를 소개하면서 글을 마치도록 하겠습니다.


Online Recurrent Extreme Learning Machine

https://github.com/chickenbestlover/Online-Recurrent-Extreme-Learning-Machine




출저 : HUANG, Guang-Bin; ZHU, Qin-Yu; SIEW, Chee-Kheong. Extreme learning machine: theory and applications. Neurocomputing, 2006, 70.1-3: 489-501

https://www.sciencedirect.com/science/article/pii/S0925231206000385



300x250

LSD-SLAM에서 Semi-Dense의 의미

공대생의 팁 2019. 3. 19. 20:56


 최근 자동차의 자율주행에 대한 연구가 지속됨으로 인해 자율주행과 관련된 분야들에서 각종 기술들일 발전하고 있는 오늘날입니다. 자동 제어와 자동차 역학을 기본으로 주행중 다양한 위험물들을 감지해 낼 수 있는 센서들을 사용함으로서 자율주행 기술은 한 걸음씩 나아가고 있습니다.


 특히 기존 로봇공학에서 연구되어온 SLAM(Simultaneous Localization and Mapping)으로 지도를 만드는 기술을 사용하여 자율주행중인 자동차가 위험물을 감지해내 회피하는 알고리즘 또한 연구가 진행되고 있습니다. 특히 카메라의 경우 Rader나 Lidar 센서에 비해 가격이 압도적으로 저렴하고 상대적으로 많은 동영상 정보를 수집할 수 있으나 이를 처리하기에는 상당히 많은 변수가 존재하기 때문에 이와 관련된 연구 또한 다양하게 진행되고 있습니다. 이러한 카메라를 사용하여 Visual SLAM또한 자율주행의 연구 대상이기도 합니다.


 이번 포스팅에서는 카메라로 SLAM을 사용하는 알고리즘 중 하나인 LSD-SLAM(Large-Scale Direct SLAM)에서 Semi-Dense의 의미에 대해 파악해보고자 합니다.


1. LSD-SLAM이란?



 LSD-SLAM은 카메라로 들어오는 이미지에서 Feature point를 검출하는 과정을 거치지 않고 바로 사진 자체에서 Semi-Dense를 사용하여 카메라에 감지된 물체의 거리를 가늠하여 이를 SLAM에 적용하는 과정입니다.


사진 출저 : https://medium.com/@j.zijlmans/lsd-slam-vs-orb-slam2-a-literature-based-comparison-20732df431d


 쉽게 설명드리기 위하여 보조자료를 가져와 보았습니다. 위에서 보시는 바와 같이 Feature-Based 방식을 사용하면 카메라로 들어오는 이미지에서 Feature를 추출하는 과정이 추가로 존자하여 LSD-SLAM보다 느릴 수 있다는 단점을 가지고 있습니다.

 물론 ORB-SLAM는 Feature-Based 방식으로 ORB 방식으로 Feature point를 추출하는 과정이 존재하지만 빠른 속도록 Feature를 감지할 수 있어 최근 각광받는 기술이 존재하기도 합니다.


2. Semi-Dense의 의미


 그렇다면 LSD-SLAM의 핵심 기술인 Semi-dense는 어떤 의미일까요? 밀도를 의미하는 dense와 semi의 접두어가 붙었을 때의 의미가 이해되기 쉽지 않으시리라 생각됩니다. 아래 예제를 통해 말씀드리도록 하겠습니다.



만약 다음과 같은 이미지가 있다고 가정합니다. 위 사진을 통해 Dense를 추측하면 아래와 같이 나타낼 수 있습니다.



위 사진에서 파란색의 경우 화면에서 상대적으로 가깝다는 의미이고 빨간색의 경우 화면에서 상대적으로 멀다는 의미로 생각하시면 됩니다. 이를 통해 사진에서 보이는 물체들의 거리를 짐작할 수 있습니다. 이 결과물을  dense depth map이라 하겠습니다.


 하지만 SLAM에서 dense depth를 모두 사용하기에는 상당히 많은 시간이 걸리는 데다가 결정적으로 모든 dense depth가 사용되지 않기 때문에 이를 SLAM에 사용하기엔 매우 부적절하다고 할 수 있습니다.


 그렇다면 이를 좀 더 가볍게 사용할 수 있는 방법은 없을까요? 이 때 semi-depth 라는 개념을 적용해봅니다.



 위 그림은 dense depth map에서 필요하지 않은 부분을 Gaussian 확률 분포를 사용하여 처리하고 남은 부분들을 나타낸 것입니다. Feature-based 방식에서 Feature를 검출하였을 때와 유사한 부분들이 남아있는 것을 볼 수 있습니다.


 즉 모든 dense depth map을 사용하지 않고 일부분만을 사용하기 때문에 (full) dense depth map의 방식에서 일부분만 사용한다는 의미로 semi-dense depth map으로 이름을 붙인 것이지요.


 이러한 특성을 가지고 있는 LSD-SLAM을 사용한다면 좀 더 빠른 방법으로 SLAM을 사용할 수 있을 것으로 기대할 수 있겠습니다.


 - Reference -

https://github.com/tum-vision/lsd_slam

https://ieeexplore.ieee.org/document/6751290

300x250

C++ 클래스 객체를 stream으로 통신 및 전달방법 - Boost Serialization(2) [Class를 TCP 소켓 통신으로 전송]

공대생의 팁 2019. 3. 8. 00:09
 이전에 Java를 사용하여 Object(객체)를 소켓 통신으로 전송하는 방법에 다루었던 적이 있었습니다. 확실히 Java는 이러한 기능이 구현되어 있어 프로그래밍을 하는 데 있어 상당히 훌륭한 기능을 수행합니다.

안드로이드 - Java 서버간 Object 객체 소켓 프로그래밍
https://elecs.tistory.com/147


 그러나 C++의 경우 아직까지 Java처럼 객체를 전송함에 있어 불편한 점이 있습니다. C를 사용할 경우 전송하기 위해 사용되는 Struct의 크기를 직접 확인하여야 하며 대용량 파일의 경우 한정된 버퍼의 크기에 맞추어 분리하여 전송하여야 하기 때문에 고려해야할 점이 상당히 많습니다.

 C++ 라이브러리 중 하나인 Boost에서는 향상된 소켓 프로그래밍인 ASIO와 Class를 직렬화 할 수 있는 Serialization을 제공합니다. 이름에서 볼 수 있듯이 Serialization를 사용하여 C++의 클래스를 직렬화 하여 Byte 형태로 변환할 수 있기 때문에 이를 다른 컴퓨터에 전송한 후 이를 역직렬화(De-serialization)하여 전송 받은 컴퓨터에서도 똑같은 Class 변수를 생성할 수 있게 되는 것입니다. 자세한 이야기는 아래의 링크를 참조해주시기 바랍니다.

C++ 클래스 객체를 stream으로 통신 및 전달방법 - Boost Serialization(1) [직렬화된 클래스 만들기]
https://elecs.tistory.com/289



 또한 이번 포스팅에서는 TCP 비동기 통신을 사용하여 class 객체값을 전달받는 과정이 구현되어 있습니다. 이와 관련된 정보에 대해 자세히 알고자 하시는 분들께 아래 링크를 참조해주시길 바랍니다.

C++에서 Boost ASIO를 사용하여 TCP 비동기(Unblocked) 통신 프로그래밍
https://elecs.tistory.com/314


 이번 프로그램의 경우 소스코드의 양이 크기 때문에 일부분을 설명하는 방식으로 진행하겠습니다. 먼저 아래에 자신의 환경에 맞는 소스코드를 다운로드 받습니다.

Ubuntu 18.04 버전 이전(Boost 1.65 버전 이전)

asio_serialization_1804.zip


Ubuntu 18.10 버전 이후(Boost 1.66 버전 이후)

asio_serialization_2004.zip


 위 소스코드의 알고리즘은 다음과 같이 구성되어 있습니다.



 서버측에서 stock라는 struct 타입의 object를 가지고 있으며 이를 Serialization하여 Binary 파일로 변환한 후 Server는 Client의 접속을 accept할 준비를 합니다. 서버에서 이와 같은 과정이 완료되면 client에서 connect를 요청합니다.

 client로부터의 connect 요청을 받은 server는 write를 하여 client에게 직렬화 된 object를 수신받습니다. 이후 handler에 등록된 handle_read 함수가 실행되어 수신된 Binary 파일을 역직렬화 한 후 이를 server에서 사용하던 대로의 파일 형식으로 되돌려놓는 방식입니다.



위 소스코드를 다운로드 받으신 후 해당 폴더에서 다음과 같은 명령어를 입력합니다.


$ mkdir build

$ cd build

$ cmake ..

$ make


 위와 같이 실행하셨을 경우 server와 client라는 이름의 실행 파일이 생성됩니다. 해당 파일들은 각각 다음과 같은 명령어로 실행하실 수 있습니다.

$ ./server 포트번호
$ ./client 서버IP 포트번호

 이와 같이 실행하면 client 화면에서 다음과 같은 결과를 확인하실 수 있습니다.






300x250

C++에서 Boost ASIO를 사용하여 TCP 비동기(Unblocked) 통신 프로그래밍

공대생의 팁 2019. 3. 7. 23:04
 C++에서 소켓 통신을 할 때 동기식(Blocked, sync)과 비동기식(Unblocked, async)방식으로 TCP 통신을 구현합니다. 그러나 Java에 비교하였을 때 C에서 기본으로 제공하는 socket 프로그래밍을 사용하는 것은 여간 불편한 일이 아닙니다.

 이러한 환경에서 Boost 라이브러리에서 제공하는 ASIO 소켓 통신 프로그램을 사용하게 되면 이전보다 코딩 환경이 매우 쾌적함을 경험할 수 있습니다.

 이번 포스팅에서는 Boost 라이브러리에서 제공하는 ASIO를 사용하여 Unblock(Asynchronization)방식의 소켓 통신 프로그램을 구현해 보았습니다.


 위 그림은 Boost library에서 ASIO의 비동기(Asynchronization)방식의 소켓 통신 프로그래밍의 알고리즘을 나타낸 것입니다.

 프로그램을 실행하게 되었을 때 Socket에 IP주소와 Port 번호를 등록하고 이를 io_service(1.66 버전 이후에서는 io_context)에 이 때 handle_connect, handle_read, handle_write와 같은 handler 또한 함께 등록합니다. 이 때 등록된 handler는 server 혹은 client에서 실행되었을 때 바로 handler를 실행하는 방법으로 어느 한 쪽으로부터 데이터 전달을 기다리지 않고 있다가 요청이 들어왔을 때 실행하는 것이지요.

 시작하기에 앞서 Ubuntu 환경에서 다음과 같이 Boost 라이브러리를 설치합니다.

$ sudo apt install libboost-all-dev

자세한 내용은 소스코드의 주석을 통해 설명을 달아보았습니다.

server.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
 
using boost::asio::ip::tcp;
using namespace std;
 
class session
{
public:
  session(boost::asio::io_service& io_service)
  //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
  //session(boost::asio::io_context& io_service)
    : socket_(io_service)
  {
  }
 
  tcp::socket& socket()
  {
    return socket_;
  }
 
  void start()
  {
    //client로부터 연결됨
    cout << "connected" << endl;
    //client로부터 비동기 read 실행
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        boost::bind(&session::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }
 
private:
  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      cout << data_ << endl;
    }
    else
    {
      delete this;
    }
  }
 
  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};
 
class server
{
public:
  server(boost::asio::io_service& io_service, short port)
  //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
  //server(boost::asio::io_context& io_service, short port)
    : io_service_(io_service),
      //PORT 번호 등록
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    start_accept();
  }
 
private:
  void start_accept()
  {
    session* new_session = new session(io_service_);
    //client로부터 접속될 때 까지 대기한다.
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }
 
  //client로부터 접속이 되었을 때 해당 handler 함수를 실행한다.
  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }
    else
    {
      delete new_session;
    }
    //client로부터 접속이 끊겼을 대 다시 대기한다.
    start_accept();
  }
 
  boost::asio::io_service& io_service_;
  //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
  //boost::asio::io_context &io_service_;
  tcp::acceptor acceptor_;
};
 
int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }
 
    boost::asio::io_service io_service;
    //boost 1.66이후 (Ubuntu 18.10 이후) 버전의 경우 io_context를 사용
    //boost::asio::io_context io_service;
 
    server s(io_service, atoi(argv[1]));
    //asio 통신을 시작한다.
    io_service.run();
  }
  catch (exception& e)
  {
    cerr << "Exception: " << e.what() << "\n";
  }
 
  return 0;
}
cs

client.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<boost/asio.hpp>
#include<boost/bind.hpp>
 
using boost::asio::ip::tcp;
using namespace std;
 
enum { max_length = 1024 };
 
class client
{
public:
  client(boost::asio::io_service& io_context,
  //client(boost::asio::io_context& io_context,
      const string& host, const string& port) : socket_(io_context)
  {
    boost::asio::ip::tcp::resolver resolver(io_context);
    boost::asio::ip::tcp::resolver::query query(host, port);
    boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
 
    //비동기(Unblock) 상태로 서버와 접속한다.
    boost::asio::async_connect(socket_, endpoint_iterator,
        boost::bind(&client::handle_connect, this, boost::asio::placeholders::error));
  }
 
  void handle_connect(const boost::system::error_code& e){
    if(!e){
      cout << "Connected!" << endl;
      string msg = "Hello! Server!";
      //Server로부터 비동기 write를 시도한다.
      boost::asio::async_write(socket_, boost::asio::buffer(msg, msg.length()),
          boost::bind(&client::handle_write,this,boost::asio::placeholders::error)); 
    }
  }
 
  void handle_write(const boost::system::error_code& e){
    if(!e){
      cout << "Done!" << endl;
    }
  }
 
private:
  tcp::socket socket_;
  char data_[max_length];
};
 
int main(int argc, char* argv[])
{
  try
  {
    // Check command line arguments.
    if (argc != 3)
    {
      std::cerr << "Usage: client <host> <port>" << std::endl;
      return 1;
    }
 
    boost::asio::io_service io_context;
    //boost::asio:io_context io_context;
    client c(io_context, argv[1], argv[2]);
    io_context.run();
  }
  catch (std::exception& e)
  {
    cerr << e.what() << endl;
  }
 
  return 0;
}
cs

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 3.0)
 
project(asio_async)
find_package(Boost REQUIRED system)
find_package(Threads)
 
include_directories(${Boost_INCLUDE_DIR})
 
add_executable(client
    client.cpp
)
 
add_executable(server
    server.cpp
)
 
target_link_libraries(client
    ${Boost_LIBRARIES}
    ${CMAKE_THREAD_LIBS_INIT}
)
 
target_link_libraries(server
    ${Boost_LIBRARIES}
)
cs


Linux 환경에서 위의 3개의 소스코드를 한 폴더에 넣으신 후 다음과 같이 실행합니다.


$ mkdir build

$ cd build

$ cmake ..

$ make


 위와 같은 과정을 거치면 client와 server라는 실행파일이 build 폴더 안에 생성됩니다. 이 두 프로그램을 실행하면 다음과 같은 결과가 나옵니다.


1
2
3
4
$ ./server 2580
 
Connected!
Hello! Server!
cs



1
2
3
4
./client 127.0.0.1 2580
 
Connected!
Done!
cs


관련 자료

C++에서 Boost ASIO를 사용하여 TCP 동기화(Blocked) 통신 프로그래밍

https://elecs.tistory.com/332

300x250

https 차단을 우회하는 방법 - GoodbyeDPI [2019.02.13]

공대생의 팁 2019. 2. 13. 16:07


 블로그를 운영하면서 생각하였던 목표 중 하나는 방문하는 분들께서 조금이나마 도움을 드리고자 하는 방향으로 글을 쓰는 것이었습니다. 비록 제가 공부하는 분야들 중에서 인터넷에서 자세히 나오지 않는 부분에 대한 글들이 대부분이라 방문하는 모든 분들께 만족할 수 없기에 조회수는 제가 쓴 글들이 하루에 한 개씩 읽히는 정도로 생각해왔지요.


 그러던 어느날 정말로 깜짝 놀랄 만한 일이 벌어졌습니다.



 무려 3년 전에 작성하였던 글의 조회수가 갑자기 폭등하는 상황이 벌어진 것입니다. 제가 작성하였던 글이지만 대체 무슨 논란이 벌어져서 이런 현상이 일어났는지 생각해보니 최근 몇몇 해외 사이트가 접속되지 않는 현상이 나타난다는 이야기를 친구로부터 들었던 것이 기억이 났습니다.


 도대체 무슨 상황이길래 우리나라의 인터넷 세상에서 이토록 난리가 난 것일까요?


 우선 이를 설명하기 위해 우리는 https의 원리에 대해 알아보도록 하겠습니다.



 기존의 http는 컴퓨터 간의 정보를 교환하는 프로토콜(Protocol) 역할을 합니다. http는 초창기에 만들어진 연결이다 보니 암호화가 되어 있지 않아 보안이 매우 취약합니다.




 반면 https의 경우 기존 http에 TLS(SSL) 암호화가 적용되어 데이터 전송의 매체인 패킷(Packet)을 암호화하여 이를 도중에 감청을 시도하더라도 암호를 해독하기 위한 key가 없으면 내용을 알아낼 수 없도록 설계되어 있습니다. 그렇기에 특정 사이트를 차단을 시도한다 하더라도 이를 막을 수 있는 방법이 없었던 것입니다.


 그런데 이러한 https 프로토클을 막을 수 있는 방법이 하나 있습니다. 바로 DNS Spoofing을 사용하는 것이지요.



 이번 https 차단 사태에서 사용된 방법으로 우리가 인터넷으로 접속할 때엔 해당 서버의 IP주소를 사용해야 하지만 숫자로 되어 있는 주소를 외우기엔 어렵기 때문에 이를 도메인 (예를 들어 https://elecs.tistory.com) 을 입력하면 DNS 서버가 해당 도메인의 IP 주소를 사용자에게 가르쳐 줌으로서 원하는 사이트에 접속할 수 있는 것이지요.

 그런데 이러한 역할을 해주는 DNS 서버를 변조하여 다른 사이트로 바꿔버리면 사용자는 해당 홈페이지에 접속할 수 없는 상황이 벌어집니다. 만약 해당 사이트가 인터넷뱅킹 사이트였는데 해커가 보이스피싱 사이트로 DNS를 변조한다면 막대한 피해가 생기는 것이지요.


 


 문제는 앞으로 이보다 더 나아가 SNI(Server Name Indication) 필드를 감청하여 차단하는 방식을 적용하게 되면 이는 통신보호법 제 5조 2항이 '헌법불합치' 판정으로 인해 헌법에 위배되는 행위가 됩니다. 만약 이를 허용하게 된다면 타인이 자신이 접속하는 사이트를 알 수 있으며 이는 매우 심각한 인권 침해가 될 수 있다는 것이지요.

 이러한 상황이 염려되기 때문에 많은 사람들이 국민청원으로 목소리를 내고 있는 것이지요. 그렇다면 지금 상황에서 https가 차단된 해외 사이트 접속은 불가능한 것일까요?


 현재의 상황에서 차단된 해외 사이트에 접속할 수 있는 방법으로 러시아 개발자가 만든 GoodbyeDPI를 사용하여 심층 패킷 감청(Deep Packet Inspection)을 피하는 것입니다. 흔히 우리나라에서 불법 사이트에 접속하였을 때 나타나는 Warning 사이트의 원리가 DPI를 사용하는 것인데 이를 우회하는 것이지요


 먼저 GoodbyeDPI 공식 사이트에 접속하여 프로그램을 다운로드 받습니다.


https://github.com/Include-sys/GUI-for-GoodbyeDPI


 접속하시면  아래와 같은 화면이 나타나는데 자신의 컴퓨터 환경(32비트 or 64비트)에 해당되는 링크를 클릭하여 다운로드 받습니다.



 프로그램이 인터넷 접속에 영향을 주기 때문에 보안경고가 나타납니다. '자세히' 버튼을 눌러 실행 버튼을 누르면 실행 전 경고 화면이 나타납니다. 해당 화면에서도 실행 버튼을 눌러주면 GoodbyeDPI 프로그램을 실행하실 수 있습니다.




 위와 같은 화면이 나오면 빨간색으로 표시된 버튼을 누르면 프로그램이 실행됩니다. 이제 https 차단을 우회하여 해외 사이트에 접속하실 수 있습니다.



 이 프로그램의 문제점으로 창을 닫는다고 해서 프로그램이 종료되지 않습니다. 작업 관리자(ctrl+alt+delete) 버튼을 눌러 직접 프로세스를 선택한 후 '작업 끝내기'를 눌러 프로그램을 완전히 종료시킵니다.



300x250

Co-visibility graph(공가시성 그래프)

공대생의 팁 2019. 1. 22. 01:34


 로보틱스에서 motion planning을 하게 될 때 visibility graph(가시성 그래프)를 사용하는 경우가 있습니다. visibility graph란 로봇이 이동을 할 때 자신의 앞에 보이는 장애물을 어떻게 하면 회피해서 갈 수 있는지에 대해 알 수 있는 방법을 고안하는 단계에서부터 시작하게 됩니다. 설명하기에 앞서 간단한 그림을 사용하여 설명해드리겠습니다.



 위 그림은 두 개의 직사각형 모양의 장애물이 있고, 로봇은 검은 점과 붉은 점을 이동해야 하는 상황을 나타낸 것입니다. 먼저 로봇이 지나갈 수 있는지를 visibility graph를 그려 확인하는 과정으로 시작점과 도착점에서 장애물까지 직선으로 선을 긋습니다. 위 그림에서 파란색으로 그려진 부분에서 시작점과 도착점의 visibility graph가 만나고 있음을 알고 있습니다. visibility graph를 그릴 때 주의할 점은 점에서부터 시작되는 visibility graph는 곡선이 될 수 없으며 도달하는 부분까지 직선으로 이어져 있어야 한다는 점입니다.


 이번 포스팅에서 설명하게 될 co-visibility graph(공가시성 그래프)를 이해하기 위해 사전지식으로 visibility graph에 대해 간단히 설명드렸습니다. 이름에서 알 수 있듯이 co-visibility graph는 두 개 이상의 점에서 나오는 직선이 특정 점에 도달하게 되었을 때 visibility graph의 갯수를 나타냅니다.


 Vision 분야에서의 visibility graph를 먼저 설명드리도록 하겠습니다.



 이 그림에서 파란 삼각형은 카메라를 나타낸 것이며 초록색 도형은 카메라에 비치는 물체를 나타낸 것이며 노란 점은 카메라에서 해당 도형에 도달하는 visibility graph를 나타낸 것입니다. 실제 점은 훨신 더 촘촘하게 나타낼 수 있으나 보기 쉽게 설명드리기 위해 일부 점만을 나타낸 것입니다.


 다음 그림은 두 개의 카메라로 도형을 바라보게 된 경우입니다.



 두 대의 카메라를 사용하여 특정 물체를 바라보게 되었을 때 보시는 바와 같이 붉은 점으로 표현된 부분은 두 대의 카메라의 시야에 모두 들어오는 부분입니다. 이렇게 두 대 이상의 카메라에서 영사되어 겹치게 되는 부분의 갯수를 표현하는 것을 co-visibility graph라 합니다.

 위의 그림을 보았을 때 노란점의 경우 한 대의 카메라에서만 해당 부분이 보이며 붉은점의 경우 두 대의 카메라에서 해당 부분을 볼 수 있음을 알 수 있습니다.


 이러한 co-visibility graph의 특성은 Visual-SLAM에서 시각정보로 지도를 그리게 되었을 때 주로 사용됩니다. 각 카메라의 영사 정보를 사용하여 상관관계를 판단하여 실제와 최대한 비슷한 지도를 만들 수 있는 것이지요.

300x250

Pixel Intensity(화소 강도)

공대생의 팁 2019. 1. 13. 01:57


 이미지 처리에 있어 해당 이미지의 기초 단위인 화소는 매우 중요한 요소라 할 수 있습니다. Pixel intensity란 특정 화소의 밝은 정도를 나타낸다고 할 수 있겠습니다. 직관적인 예제를 보도록 합시다.



 위 그림은 Grayscale 관점에서 Pixel intensity를 나타낸 것입니다. 검은색의 경우 low값을 나타내고 흰색의 경우 high값을 나타내고 있으며 그 중간인 회색의 경우 middle값을 가지고 있음을 볼 수 있습니다.

 만약 각 픽셀이 0에서 255 사이의 값을 가진다 하였을 때 검은색은 0을, 흰색은 255, 회색은 0~255 사이의 값을 가질 것으로 예상할 수 있습니다.


 아래의 그림은 15×15 크기의 이미지입니다.



 각 픽셀의 intensity는 아래와 같은 숫자로 나타내게 됩니다. 위에서 보았듯이 검은색에 가까울수록 값은 0에 가깝고 흰색에 가까울수록 255에 가까운 값이 나타남을 알 수 있습니다. 



 여기까지 설명을 들으셨다면 pixel intensity의 개념에 대해 어느 정도 감이 잡혔으리라 생각합니다. 이번에는 grayscale과 같은 단색이 아닌 흔히 쓰이는 RGB 컬러 사진을 보도록 하겠습니다.



 우리들이 흔히 사용하는 이미지는 빨강,초록,파랑의 3개의 색을 조합하여 색을 만듭니다. 각각의 색상 또한 grayscale처럼 색깔의 강도를 조절함으로서 다양한 색을 나타낼 수 있게 되는 것이지요.

 RGB 이미지는 마치 3개의 grayscale로 이루어져 있다고 생각할 수 있습니다. 즉 픽셀 1개당 3개의 값을 가진 벡터 (r, g, b)로 표현할 수 있으며 각 값은 0에서 255 사이의 값을 갖고 있는 것입니다.


 그렇다면 위 이미지의 pixel intensity를 histogram을 통해 확인해보도록 하겠습니다. 이를 위해 Matlab을 사용하여 histogram을 그려보았습니다.


1
2
3
4
5
6
7
8
9
10
11
img = imread('sample.png')
 
Red = imag(:,:,1);
Green = img(:,:,2);
Blue = img(:,:,3);
 
[yRed, x] = imhist(Red);
[yGreen, x] = imhist(Green);
[yBlue, x] = imhist(Blue);
 
plot(x, yRed, 'Red', x, yGreen, 'Green', x, yBlue, 'Blue');
cs


 위 코드를 Matlab에서 실행하면 다음과 같은 그래프를 얻을 수 있습니다.


 위 이미지와 만들어진 histogram을 비교해본다면 각 column에서 가장 많이 차지하는 색상을 어느정도 비슷하게 나타내고 있음을 확인할 수 있습니다.


 특정 이미지에서의 pixel intensity는 의학 분야에서 상당히 중요하게 사용되고 있습니다.



 신체검사 등을 할 때 컴퓨터단층촬영을 한 모습입니다. 각 단면의 pixel intensity의 차이값으로 환자의 몸에 어느 부분에서 문제가 발생하였는지 쉽게 확인할 수 있습니다.

 또한 최근에는 Vision 분야가 발전함으로서 카메라에 들어오는 image를 처리하여 정보를 얻는 등의 방법으로 유용하게 사용되고 있습니다.


300x250

Nonparametric methods(비모수적 접근법)

공대생의 팁 2018. 12. 31. 15:17


 우리가 통계 자료를 사용함에 있어서 평균 μ과 표준편차 σ를 사용합니다. 여기서 μ와 σ라는 두 개의 parameter(모수, 매개변수) 를 사용하여 특정 분포에서 원하는 결과를 추론하는 과정을 Parametric method(모수적 접근법)이라 할 수 있습니다. 즉 μ와 σ는 우리들이 원하는 결과값을 얻음에 있어 사용되는 도구라 할 수 있겠습니다.


 반면 Nonparameteric method(비모수적 접근법)은 어떠한 parameter를 사용하지 않거나 매우 제한된 조건을 사용하여 원하는 결과를 얻는 과정이라 할 수 있습니다. 만약 주어진 데이터가 정규분포와 같은 모양을 하고 있지 않아 parametric method를 사용함에 적절하지 않을 경우 Nonparametric method는 이를 대체할 수 있는 좋은 방법이라고 할 수 있겠습니다.


 Nonparametric method를 사용함에 있어 다음과 같은 장점들이 있습니다.


1. 엄격한 모수적 추론을 요구하지 않는다.

2. 모수적 추론을 거의 사용하지 않아 분포를 추론함에 있어 더 많은 수단을 사용할 수 있다.

3. 평균과 표준편차와 같은 파라미터를 사용하지 않는다.

4. 최소한의 추론만 사용하므로 부적절하게 사용될 경우가 적다.

5. 데이터가 약한 측정척도로 측정되더라도 적용이 가능하다.

6. 이해하기 쉽다.

7. 계산이 빠르고 쉽게 수행된다.

 

반면, 다음과 같은 단점을 가지고 있습니다.


1. Parametric method를 사용하는 것이 더 효율적인 데이터의 경우 Nonparametric method가 더 비효율적일 수 있다.

2. 샘플이 커질 경우 직접 계산하는 것이 어려워진다.

3. 상당히 중요한 값이 항상 계산되지 못한다.


 최근 AI의 요소 중 하나인 Neural Network의 경우 Parametric method와 Nonparametric method가 모두 사용되는 경우라 할 수 있겠습니다. 따라서 AI를 연구함에 있어 두 요소를 모두 고려해야 하는 것이 상당히 중요하며 이를 적절히 해결할 수 있다면 좋은 결과를 만들어낼 수 있을 것으로 기대할 수 있겠습니다.



출저 : https://www.researchgate.net/publication/322677728_INTRODUCTION_TO_NONPARAMETRIC_STATISTICAL_METHODS

300x250

Model bias(모형 편향)

공대생의 팁 2018. 12. 30. 18:15


 모형편향(Model bias)란 관측된 출력에서 시뮬레이터 결과로부터 나오는 시스템에서의 편차를 의미합니다. 이 현상은 모형 불충분성(Model inadequacy) 또는 모형 불일치(Model discrepancy)라 하며 대다수 환경 모델에 영향을 줍니다.


 모형편향의 원인을 이해하는 것은 모델을 예측함에 있어 이를 평가하는 데에 필수적인 요소라 할 수 있습니다. 모델을 예측함으로서 더 적합한 모델을 만들거나 가장 대포적인 입력 정보를 얻음으로서 좀 더 좋은 모형을 만들 수 있다는 것입니다.

 




출저 : https://www.sciencedirect.com/science/article/pii/S1364815214003247

300x250