블록체인 소개 - (1) 분산 장부

|

블록체인이 요새 뜨거운 이슈입니다. 그래서 블록체인 과제를 수행했던 경험을 기반으로 간단한 블록체인 소개글을 적어봅니다.

블록체인을 쉽게 이해하려면 ‘분산 장부(Distributed Ledgers)’를 먼저 이해하는 편이 좋습니다.


분산 장부

‘장부’란 말이 생소하게 느껴질 수도 있는데, 장부는 우리가 흔히 생각하는 돈거래 등을 할 때 기록하는 그 장부가 맞습니다. 분산 장부는 이 장부를 모든 사람들이 갖고 있자는 말입니다.

이 개념이 등장하게 된 배경을 조금 이해하기 쉽게 ‘돈거래’를 예시로 들겠습니다.

‘철수’와 ‘영희’가 돈거래를 한다고 생각합시다. 철수가 영희에게 돈 100원을 빌렸습니다.


Image

처음에는 서로의 기억에 의존해서 돈을 빌린 사실을 기억했습니다. 그러다보니 오래되니 기억도 희미해지고 누군가 거짓말을 해도 확인하기가 어려워졌습니다.


Image

그래서 영희는 장부를 적기 시작했습니다. 그랬더니 이번엔 영희가 장부에 사실만 기록했는지 확인하기가 어려워졌고, 장부가 분실되기도 했습니다.


Image

그래서 철수와 영희 모두 같은 내용의 장부를 기록하기 시작했습니다. 하지만, 철수와 영희의 장부에 서로 다른 내용이 기록되기 시작하자 누구의 장부가 진짜인지 확인하기가 어려웠고, 낮은 확률이지만 장부를 둘 다 잊어버리는 경우도 발생했습니다.


Image

그래서 철수와 영희 외 제 3의 대리인을 증인으로 해서 같은 내용의 장부를 3명이 나누어서 관리하기 시작했습니다. 부동산 거래를 생각하시면 됩니다. 매수자, 매도자, 중개인의 구도가 이와 같습니다.

물론, 이 방법도 위험 요소가 있습니다. 대리인을 100% 신뢰할 수 없다거나 등의 문제가 발생할 수 있습니다.


Image

그래서 이제 대리인을 ‘믿을 수 있는 기관’으로 대신했습니다. 은행이나 법원 등을 생각하시면 됩니다.

이게 현재의 금융 시스템의 큰 형태입니다. 모든 것은 은행이라는 중앙 서버에서 관리를 하고 사실 확인을 해줍니다.

하지만, 하나의 중앙 서버에서 모든 것을 해결하다보니 신경써야 할 점이 엄청나게 많습니다.

해커들이 수시로 노리고 있기 때문에 보안에 엄청나게 신경을 써야 합니다. 또한 수천, 수만명 그 이상의 동시 접속자가 발생하더라도 다운되지 않고 안정적으로 돌아가야 합니다. 자료들의 백업, 관리는 철저해야 합니다.

현재의 방식은 해커들로부터 정보를 보호해야하기 때문에 사용자들의 송금 정보나 계좌 정보 등은 철저하게 숨겨야 합니다.

그런데, 분산 장부는 이러한 패러다임을 완전히 바꾼 아이디어입니다. 정보를 숨기기보다는 완전히 오픈을 해버렸습니다.


Image

철수와 영희간의 거래 기록을 아예 모든 사람들이 다 같이 공유하도록 해버렸습니다. 그래서 누군가 해킹을 당해 위변조가 되더라도 나머지 장부들을 조회해서 사실 여부를 확인할 수 있도록 했습니다. 해커가 해킹을 하려면 적어도 과반수가 넘는 사람들의 장부를 전부 해킹해야 하기 때문에 보안에 안전하도록 했습니다.

여기까지가 ‘분산 장부’입니다. 블록 체인은 여기에 각 이체 내역들을 블록으로 쌓아서 체인 형태로 더 견고하고 단단하게 만든 개념입니다.

다음 번에는 블록체인에 대해서 포스팅해보도록 하겠습니다.

Visual Studio 2017 기준 C++ 프로그램 실행 후 콘솔창 안 닫히도록 하기

|

과거에는 프로그램을 작성한 후 Ctrl + F5를 누르면 콘솔 창이 닫히지 않아서 결과를 확인하기가 용이했습니다.

그런데 언제부턴지 몰라도 Ctrl + F5 키를 누르더라도 콘솔 창이 닫혀서 불편한 경우가 많이 생겼네요.

콘솔창이 안 닫히도록 하는 방법은 다음과 같습니다. (Visual Studio 2017 버전 기준입니다.)

C++ 콘솔창 안 닫히도록 하는 방법

먼저 프로젝트에 마우스 오른 버튼을 눌러 속성 메뉴를 엽니다. 주의할 점은 ‘솔루션’을 선택하고 마우스 오른 버튼을 누르는게 아니라 ‘프로젝트’를 선택해야 한다는 점입니다.

그리고 구성 속성에서 링커를 선택합니다.

image

하위 시스템콘솔(/SUBSYSTEM:CONSOLE)으로 선택합니다.

이제 Ctrl + F5 키를 누르면 결과 콘솔창이 닫히지 않고 유지되는 것을 확인할 수 있을 것입니다.

OpenCV 카메라 이미지를 Subscribe 하는 예제

|

카메라 이미지 Subscriber

여기 소스 코드를 참고했습니다.

main.cpp

#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"

#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <cstdio>
#include <memory>
#include <string>

using namespace std;
using namespace cv;

#define ROS_NODE_NAME "usb_camera_subscriber"
#define ROS_CAMERA_TOPIC_NAME "snowdeer_camera_topic"


void (*breakCapture)(int);
void signalingHandler(int signo) {
  printf("'Ctrl + C'(%d) processing...\n", signo);

  exit(1);
}

int encoding2mat_type(const string & encoding) {
  if (encoding == "mono8") {
    return CV_8UC1;
  } else if (encoding == "bgr8") {
    return CV_8UC3;
  } else if (encoding == "mono16") {
    return CV_16SC1;
  } else if (encoding == "rgba8") {
    return CV_8UC4;
  } else if (encoding == "bgra8") {
    return CV_8UC4;
  } else if (encoding == "32FC1") {
    return CV_32FC1;
  } else if (encoding == "rgb8") {
    return CV_8UC3;
  } else {
    throw std::runtime_error("Unsupported encoding type");
  }
}

string mat_type2encoding(int mat_type) {
  switch (mat_type) {
    case CV_8UC1:
      return "mono8";
    case CV_8UC3:
      return "bgr8";
    case CV_16SC1:
      return "mono16";
    case CV_8UC4:
      return "rgba8";
    default:
      throw std::runtime_error("Unsupported encoding type");
  }
}

void show_image(const sensor_msgs::msg::Image::SharedPtr msg) {
  printf("Received image #%s\n", msg->header.frame_id.c_str());

  cv::Mat frame(msg->height, msg->width, encoding2mat_type(msg->encoding),
      const_cast<unsigned char *>(msg->data.data()), msg->step);

  CvMat cvframe;
  if (msg->encoding == "rgb8") {
    cv::Mat frame2;
    cv::cvtColor(frame, frame2, cv::COLOR_RGB2BGR);
    cvframe = frame2;
  } else {
    cvframe = frame;
  }

  cvShowImage("Received Image", &cvframe);
  if( waitKey(10) == 27 ) exit(1);
}

void callback_from_image_topic(const sensor_msgs::msg::Image::SharedPtr msg) {
  show_image(msg);
}

int main(int argc, char** argv) {
  breakCapture = signal(SIGINT, signalingHandler);

  rclcpp::init(argc, argv);

  rmw_qos_reliability_policy_t reliability_policy = RMW_QOS_POLICY_RELIABILITY_RELIABLE;
  rmw_qos_history_policy_t history_policy = RMW_QOS_POLICY_HISTORY_KEEP_ALL;

  size_t depth = 10;
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);

  auto node = rclcpp::Node::make_shared(ROS_NODE_NAME);
  rmw_qos_profile_t custom_qos_profile = rmw_qos_profile_default;
  custom_qos_profile.depth = depth;
  custom_qos_profile.reliability = reliability_policy;
  custom_qos_profile.history = history_policy;

  auto sub = node->create_subscription<sensor_msgs::msg::Image>(
      ROS_CAMERA_TOPIC_NAME, callback_from_image_topic, custom_qos_profile);

  rclcpp::spin(node);

  rclcpp::shutdown();

  return 0;
}

OpenCV 카메라 이미지를 Publish 하는 예제

|

카메라 이미지 Publisher

여기 소스 코드를 참고했습니다.

main.cpp

#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"

#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <cstdio>
#include <memory>
#include <string>

using namespace std;
using namespace cv;

#define ROS_NODE_NAME "usb_camera_publisher"
#define ROS_CAMERA_TOPIC_NAME "snowdeer_camera_topic"

void (*breakCapture)(int);
void signalingHandler(int signo) {
  printf("'Ctrl + C'(%d) processing...\n", signo);

  exit(1);
}

string mat_type2encoding(int mat_type) {
  switch (mat_type) {
    case CV_8UC1:
      return "mono8";
    case CV_8UC3:
      return "bgr8";
    case CV_16SC1:
      return "mono16";
    case CV_8UC4:
      return "rgba8";
    default:
      throw std::runtime_error("Unsupported encoding type");
  }
}

void convert_frame_to_message(const cv::Mat & frame, size_t frame_id, 
  sensor_msgs::msg::Image::SharedPtr msg) {
  
  msg->height = frame.rows;
  msg->width = frame.cols;
  msg->encoding = mat_type2encoding(frame.type());
  msg->step = static_cast<sensor_msgs::msg::Image::_step_type>(frame.step);
  size_t size = frame.step * frame.rows;
  msg->data.resize(size);
  memcpy(&msg->data[0], frame.data, size);
  msg->header.frame_id = std::to_string(frame_id);
}

int main(int argc, char** argv) {
  breakCapture = signal(SIGINT, signalingHandler);

  rclcpp::init(argc, argv);

  rmw_qos_reliability_policy_t reliability_policy = RMW_QOS_POLICY_RELIABILITY_RELIABLE;
  rmw_qos_history_policy_t history_policy = RMW_QOS_POLICY_HISTORY_KEEP_ALL;

  size_t depth = 10;
  double freq = 30.0;
  size_t width = 320;
  size_t height = 240;
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);

  
  auto node = rclcpp::Node::make_shared(ROS_NODE_NAME);
  rmw_qos_profile_t custom_camera_qos_profile = rmw_qos_profile_default;
  custom_camera_qos_profile.depth = depth;
  custom_camera_qos_profile.reliability = reliability_policy;
  custom_camera_qos_profile.history = history_policy;

  auto publisher1 = node->create_publisher<sensor_msgs::msg::Image>(
    ROS_CAMERA_TOPIC_NAME, custom_camera_qos_profile);

  rclcpp::WallRate loop_rate(freq);

  VideoCapture cap;
  if(!cap.open(0)) return 0;
  
  cap.set(CV_CAP_PROP_FRAME_WIDTH, static_cast<double>(width));
  cap.set(CV_CAP_PROP_FRAME_HEIGHT, static_cast<double>(height));

  if (!cap.isOpened()) {
    printf("Could not open video stream!!\n");
    return 1;
  }
  
  Mat frame;
  auto msg = std::make_shared<sensor_msgs::msg::Image>();
  msg->is_bigendian = false;

  size_t i = 1;

  while (rclcpp::ok()) {
    cap >> frame;

    if (!frame.empty()) {
      convert_frame_to_message(frame, i, msg);
    }

    if (!frame.empty()) {
      CvMat cvframe = frame;
      cvShowImage("Camera", &cvframe);
      if( waitKey(10) == 27 ) break;

      printf("Publishing image #%zd\n", i);
      publisher->publish(msg);
      ++i;

      if(i > 10000000) i = 0;
    }

    rclcpp::spin_some(node);
    loop_rate.sleep();
  }

  rclcpp::shutdown();

  return 0;
}

OpenCV 라이브러리를 사용하는 ROS 2.0 빌드용 CMakeFileList.txt

|

ROS 2.0 with OpenCV

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(usb_camera_publisher)

if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(OpenCV REQUIRED)
find_package(sensor_msgs REQUIRED)

include_directories(include)

add_executable(${PROJECT_NAME} src/main.cpp)

ament_target_dependencies(${PROJECT_NAME} 
  "rclcpp" 
  "sensor_msgs"
  "OpenCV")

install(TARGETS ${PROJECT_NAME}
  DESTINATION bin/${PROJECT_NAME})

ament_package()

위에서 sensor_msgs 라이브러리는 ROS 2.0에서 사용하는 이미지의 데이터 타입 Image를 사용하기 위해서 추가했습니다.