ITS's Dev Story

우리가 흔히 '1부터 n까지 더한 값을 출력하는 프로그램을 작성하라' 는 문제를 보면,


대부분이 이렇게 작성할 것이다.

#include <stdio.h>

int main() { int a, sum=0; scanf("%d", &a); for(int i = 1; i <= a; i++) { sum = sum + i; //물론 sum += i로 쓸 수도 있다. } printf("%d", sum); }


그리고, 출력값은 위와 같이 표시될 것이다.


만약, 이 출력값을 반복문 없이 구현하라고 하면 어떻게 구현할 것인가? 

재귀부터 시작해서 등차수열의 합 공식 등... 다양한 방법이 있을 것이다.


그 중 우리는 재귀함수를 사용해서 점화식으로 구현해 보도록 하겠다.


<풀이방법 1> 


먼저, 함수 f(n)은 1부터 n까지의 합으로 정의할 수 있다.


f(1) = 1

f(2) = 1 + 2


....


f(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9

f(10) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10


뭔가 공통점이 보이지 않는가? [f(n), n=10] 을 구하고자 할 때 f(9)는 1 ~ n-1의 합을 구하는 것이고, 

f(10)은 1부터 n까지의 합을 구하는 것이다. 따라서 f(10)은 아래와 같이 작성할 수 있다.


f(10) = f(9) + 10


이를 일반화하면



으로 쓸 수 있다.


이를 코드로 작성하면


으로 작성할 수 있는 것이다. n에 1이 들어가면 1~1의 합이니 return 1, 

1이 아니라면 함수 내 재귀를 수행하는 것이다.


<풀이방법 2>


f(10) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10일 때,

f(5) = 1 + 2 + 3 + 4 + 5 이다.


이를 f(10) = f(5) + 6 + 7 + 8 + 9 + 10 로 표현할 수 있으며,

f(10) = f(5) + (5 + 1) + (5 + 2) + (5 + 3) + (5 + 4) + (5 + 5) 로 표현 가능하다.


정리하면, 

f(10) = f(5) + 1 + 2 + 3 + 4 + 5 + 5 + 5 + 5 + 5 + 5


이 식에서 굵게 표시한 부분은 또 f(5)로 치환할 수 있다. 

빨간색으로 표현한 부분은 5*5로 5의 제곱으로 표현 가능하다.


따라서 f(10) = 2f(5) + 5^2 로 표현할 수 있으며, 입력한 n은 10이므로


로 나타낼 수 있다.


그러나, 이 식은 홀수에서는 표현할 수 없는 식이다. 홀수를 나누면 소수점이 남기 때문.

따라서, 일반항에 가우스를 씌우고 (0.5 잃음), 가우스에서 잃었던 0.5 + n/2할때 생기는 0.5를 더해준다.


그럼 아래와 같이 정리할 수 있다. 


이를 홀, 짝 구분없이 쓸 수 있도록 정리하면, 아래와 같이 표현 가능하다.


프로그래밍 언어에서는 정수를 나누면 소수점 이하를 버리므로 아래와 같이 프로그램을 작성할 수 있다.



직접 여러 숫자를 대입해 보면서 풀어보면 잘 이해가 될 것이다.

OpenBCI로 마인크래프트의 스티브를 움직이는 방법을 알아보겠다. 



OpenBCI 소프트웨어의 최신 버전은 OSC 통신을 지원, Focus 감지 기능을 지원하므로, 이를 이용하면 스티브를 앞으로 움직이기 위해 키보드로 'W' 키를 누르는 과정을 파이썬 소프트웨어로 대체할 수 있다.


1. Python 3.6 버전을 다운받는다. (2.7 버전 안됨! +_+)

 * 이미 파이썬 2.7 버전을 설치한 경우에는 2.7 버전을 지우고, 3.6 설치, 환경변수 재설정 등 각종 설정을 다시 해야 한다. 

 * 다운로드 링크 : https://www.python.org/downloads/release/python-362/


2. Muselab 다운로드

 - Muselab은 OSC 데이터 분석 등에 유용하게 사용되므로, 다운받을 필요가 있다. 다운받지 않아도 프로그램을 동작하는데 문제는 없지만, 신호를 분석할 때에는 필요가 있다.

 * 다운로드 링크 : https://storage.googleapis.com/ix_downloads/musesdk-3.4.1/musesdk-3.4.1-windows-installer.exe


3. OpenBCI S/W 실행, OSC 프로토콜 설정 및 Focus 측정



OpenBCI에서 위와 같이 Control Panel 설정을 변경한다. (설치 및 실행 관련은 전 포스팅에서 설명했으므로 생략한다.)


[Networking] - 프로토콜 OSC로 변경, Data Type은 Focus, IP (127.0.0.1) Port(12345) 변경



위는 Focus Widget인데, focus를 측정하는 소스코드는 아래와 같다.


<W_focus.pde> 소스코드 중 일부


float alpha_avg = 0, beta_avg = 0; boolean isFocused; float alpha_thresh = 0.7, beta_thresh = 0.7, alpha_upper = 2;

<중략>


void update()

{


<중략>

alpha_avg = alpha_avg / alpha_count; beta_avg = beta_avg / beta_count; if (alpha_avg > alpha_thresh && alpha_avg < alpha_upper && beta_avg < alpha_thresh)

{ isFocused = true; }

else

{ isFocused = false; }

}

if (alpha_avg > 0.7 && alpha_avg < 2 && beta_avg < 0.7)


조건에 걸리는 경우, isFocused = true 를 OSC로 보낸다.


4. 제공하는 Python 소스코드를 이용해서, 마무리한다.


아래 소스코드를 SerialFocusAdvanced.py 로 저장하고 실행한다. 없는 라이브러리는 pip을 이용해 받아 준다.

import argparse
import ctypes
from ctypes import wintypes
import time

user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_MOUSE    = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP       = 0x0002
KEYEVENTF_UNICODE     = 0x0004
KEYEVENTF_SCANCODE    = 0x0008

MAPVK_VK_TO_VSC = 0

# msdn.microsoft.com/en-us/library/dd375731
VK_TAB  = 0x09
VK_MENU = 0x12
VK_W = 87

# C struct definitions

wintypes.ULONG_PTR = wintypes.WPARAM

class MOUSEINPUT(ctypes.Structure):
    _fields_ = (("dx",          wintypes.LONG),
                ("dy",          wintypes.LONG),
                ("mouseData",   wintypes.DWORD),
                ("dwFlags",     wintypes.DWORD),
                ("time",        wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

class KEYBDINPUT(ctypes.Structure):
    _fields_ = (("wVk",         wintypes.WORD),
                ("wScan",       wintypes.WORD),
                ("dwFlags",     wintypes.DWORD),
                ("time",        wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

    def __init__(self, *args, **kwds):
        super(KEYBDINPUT, self).__init__(*args, **kwds)
        # some programs use the scan code even if KEYEVENTF_SCANCODE
        # isn't set in dwFflags, so attempt to map the correct code.
        if not self.dwFlags & KEYEVENTF_UNICODE:
            self.wScan = user32.MapVirtualKeyExW(self.wVk,
                                                 MAPVK_VK_TO_VSC, 0)

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = (("uMsg",    wintypes.DWORD),
                ("wParamL", wintypes.WORD),
                ("wParamH", wintypes.WORD))

class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = (("ki", KEYBDINPUT),
                    ("mi", MOUSEINPUT),
                    ("hi", HARDWAREINPUT))
    _anonymous_ = ("_input",)
    _fields_ = (("type",   wintypes.DWORD),
                ("_input", _INPUT))

LPINPUT = ctypes.POINTER(INPUT)

def _check_count(result, func, args):
    if result == 0:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT, # nInputs
                             LPINPUT,       # pInputs
                             ctypes.c_int)  # cbSize

# Functions

def PressKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

def ReleaseKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode,
                            dwFlags=KEYEVENTF_KEYUP))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

import argparse
import math
import time

from pythonosc import dispatcher
from pythonosc import osc_server

def eeg_handler1(unused_addr, args, ch1):
    print("Blink: ", ch1)

def eeg_handler2(unused_addr, args, ch1):
    print("Jaw Clench: ", ch1)

def eeg_handler3(unused_addr, args, ch1):
    print("Open Bci Focus: ", ch1)
    if(ch1):
        PressKey(VK_W)
        time.sleep(0.25)
        ReleaseKey(VK_W)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="0.0.0.0", help="The ip to listen on")
    parser.add_argument("--port", type=int, default=12345, help="The port to listen on")
    args = parser.parse_args()

    dispatcher = dispatcher.Dispatcher()
    dispatcher.map("/debug", print)
    dispatcher.map("/muse/elements/blink", eeg_handler1, "Blink")
    dispatcher.map("/muse/elements/jaw_clench", eeg_handler2, "Jaw_clench")
    dispatcher.map("/openbci", eeg_handler3, "Jaw_clench")

    server = osc_server.ThreadingOSCUDPServer((args.ip, args.port), dispatcher)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()


4. 마인크래프트를 실행, 스티브를 움직여 본다.



최근 서울대학교 과학영재교육원에서 OpenBCI를 이용한 탐구를 진행하고 있다. 


절차상으로도 굉장히 쉽고, 이 내용이 OpenBCI 사이트에 설명이 잘 되어있긴 하지만, 이 글이 내 개인적으로도 (OpenBCI를 Windows PC에 설치할 때마다 헷갈린다), OpenBCI를 공부하거나 연구하는 사람들에게도 도움이 될 것 같아 방법을 정리하여 올린다.




 

<준비물>

OpenBCI Cython (구. OpenBCI 32Bit Board Kit), 골든 일렉트로드 케이블, USB (BLE) 동글, 배터리 박스(?), 배터리, 

Ten20 (전극 테스트를 할 때 필요), Windows 10(아무 Windows든 사실 큰 상관 없음) 이 설치된 PC, 그리고 차 한잔???

 

* 차 한잔은 OpenBCI를 개발하는데 전혀 아무런 영향을 미치지 않습니다... 자신의 정신건강에 도움이 될 뿐 ㅋㅋㅋㅋ

 

1. OpenBCI USB 동글은 FTDI 드라이버로 이루어져 있다. 아래 사이트에서 FTDI 드라이버를 설치한다.

 * 만약 설치하지 않았다면 USB Serial Port가 장치 관리자에서 찾을 수 없다고 뜰 것이고, OpenBCI 프로세싱 기반 소프트웨어에서 포트를 감지하지 않을 것이다.

 

http://www.ftdichip.com/Drivers/VCP.htm

 

사이트 하단에 있는 드라이버를 다운받는다. 운영체제와 자신의 컴퓨터 비트 수를 확인해서 알맞는 드라이버를 다운받는다.

 * 비트수를 확인하려면 Windows 10에서 [내 PC] - 우클릭/[속성] - 시스템 종류를 확인.

 

2. 배터리 박스에 배터리를 장착하고, OpenBCI 본체의 스위치는 PC로 향하게 한다 [BLE/OFF 안됨]. USB 동글의 스위치는 GPIO_6으로 향하게 한다.

 

 * 잠깐! 일반 배터리를 OpenBCI에 연결하면 보드의 파워 서플라이가 나갈 수 있기 때문에, 배터리 박스를 OpenBCI를 구입 시 사용하도록 같이 넣어준 것이다. 절대로 배터리 박스 없이 배터리를 끼우지 말 것!

 

3. http://openbci.com/index.php/downloads 에서 알맞는 버전을 다운받는다.

난 GUI 1.0.0 [64비트 다운로드 링크 : http://openbci.com//apps/v100/application.windows64.zip] 을 다운받았다.

나처럼 하고 싶다면 OpenBCI GUI 최신버전 밑 [ALL OPENBCI GUI RELEASES...] 를 클릭하면 된다.

 

4. 소프트웨어를 실행시켜, 아래와 같이 따라한다.

 

1. LIVE (최신 버전에서는 Cython으로 보일 것이다.) 를 클릭한다.

2. 동글의 포트를 클릭한다. 그리고 SD 카드 옵션이 있는데 자신의 OpenBCI에 SD카드가 끼워져 있고, 한번 해보고 싶다는 사람은 해봐도 무방하다. 안해도 OpenBCI를 탐구하는데 지장은 크지 않은 것 같다.

3. 그리고 Start System을 누른다.

 

 

 

여기까지 따라해서 이 화면이 떴다? 축하한다. 당신의 OpenBCI랑 PC는 연결된 상태라는 것을 증명해 주는 화면이다.

이제 Start Data Stream을 누르고 OpenBCI의 전극을 만져보자. (금색 부분) 아마 뇌파가 요동치는 걸 떠나 최고점에 닿을 것이다.

 

 

이제 골드컵 일렉트로드 케이블에 Ten20 크림을 바르고, 자신의 머리 뇌파를 측정해 보자.