이번껀 전문가가 아니기에 틀린 정보가 많을 수 있습니다.
이번엔 집중도 구하기!
이번엔 아마도 그나마 짧지 않을까 한다..(글 쓰기 전이라 추측)
그리고 조금 문제가 생겼다.. 당시에는 EEG를 알아내고 brainflow를 C++로도 사용하고 Python으로도 사용해서 집중도나 이런 거 구하는 거를 했었는데.. 학교를 다니면서 기숙사에서 작업하다 보니 정신이 별로 없던 것 같다.. 2~4주 안에 이 모든 걸 다 했으니..
그렇다 보니 지금 다시 확인해 봤는데 코드가 좀 엉망이다.. 잘못된 정보들이 대부분일 수 있다.
최대한으로 찾아서 해보고 있지만 코드적인 실수가 어디선가 있을 수도 있긴 하다..
참고로 대학교 수업 자료로 쓴 것이기에.. 잘못 정보를 전달했다는 걸 지금 느꼈다..
솔직히 어디서부터 써야 될지 감이 안 잡히긴 하는데.. 어떻게든 적어보겠다..
아무튼 시작!
집중도 구하는 코드 먼저!
from time import sleep
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
from brainflow.data_filter import DataFilter, FilterTypes
board_id = BoardIds.MUSE_2_BOARD
params = BrainFlowInputParams()
params.serial_number = "Muse-0465" # Muse 2의 고유 시리얼 넘버
board_shim = BoardShim(board_id, params)
use_data_seconds = 1 # n초 동안 데이터 수집
sampling_rate = BoardShim.get_sampling_rate(board_id)
eeg_channels = BoardShim.get_eeg_channels(board_id)
num_points = use_data_seconds * sampling_rate # 몇 초 동안의 데이터를 가져올지
# Muse 2 연결
board_shim.prepare_session()
# Muse 2 데이터 수집 시작
board_shim.start_stream()
# n초 + 1초 동안 데이터 수집
sleep(use_data_seconds + 1)
# 수집한 데이터를 변수에 저장
data = board_shim.get_current_board_data(num_points)
# 데이터 처리
for count, channel in enumerate(eeg_channels):
DataFilter.perform_bandpass(data[channel], sampling_rate, 2.0, 45.0, 4, FilterTypes.BESSEL_ZERO_PHASE, 0) # 분석할 파형 밖의 범위 제거
DataFilter.perform_bandstop(data[channel], sampling_rate, 48.0, 52.0, 4, FilterTypes.BUTTERWORTH_ZERO_PHASE, 0) # 50Hz의 주변 전기 노이즈 제거
DataFilter.perform_bandstop(data[channel], sampling_rate, 58.0, 62.0, 4, FilterTypes.BUTTERWORTH_ZERO_PHASE, 0) # 60Hz의 주변 전기 노이즈 제거
# 예측에 필요한 데이터 추출
bands = DataFilter.get_avg_band_powers(data, eeg_channels, sampling_rate, False)
feature_vector = bands[0]
# 집중도 예측
print(f"Attention: {feature_vector[3] / feature_vector[2]}") # Beta / Alpha
# Muse 2 데이터 수집 정지
board_shim.stop_stream()
# Muse 2 연결 해제
board_shim.release_session()
이번 코드는 이것이다.
최대한 이해하기 쉽게 하기 위해 이전 코드에서 데이터 처리 부분만 추가해서 작동하도록 했다.
그렇다 보니 코드 설명에선 이전에 한 부분은 제외하고 하겠다..
참고로 저번에 데이터를 확인했기 때문에 여기에선 집중도만 구할 것이기에 데이터를 저장하는 코드는 넣지 않았다.
넣고 싶으면 이전 코드에서 가져와서 넣길..
그리고 이번에는 n초로 주석이 되어있다. 이유라면 집중도를 측정하기 때문에 1초 정도로는 부족한 것 같아서 5초로 했다. 하지만 저건 변경할 수도 있으니 n초로 한 것
코드 설명 전 이해할 것
뇌파로 분석을 할 때 보통 사용되는 특정 주파수의 파형이 있다. (바로 아래 사진)
(바로 아래 사진 말고 그 사진 아래 있는 사진은 한국어로 되어있으니 그걸 먼저 보는 것을 추천합니다.)
그 파형은 Delta(델타), Theta(세타), Alpha(알파), Beta(베타), Gamma(감마)인데 위의 Muse 공식 사이트 안에 설명되어 있는 내용 중 사진에서 가져온 걸 보면 된다.
수집된 데이터에서 FFT(고속 푸리에 변환) 방법으로 저렇게 특정 파형을 뽑아낼 수 있다.
각 파형은 각각의 특색이 있다. 그 특색은 사진의 오른쪽에 적혀있다. 그냥 현재 뇌의 상태를 알 수 있는 파형이라 생각하면 된다.
뭐 잠든 건지 깨어있는지 집중도가 강한지 등을 알 수 있게 된다.
아무튼 저런 파형은 적혀있는 대로 Delta(0.5, 4.0), Theta(4.0, 8.0), Alpha(8.0, 13.0), Beta(13.0, 32.0), Gamma(32.0, 100.0) 이런 범위로 되어있다. 저 범위에 있는 걸 추출하면 현재 뇌 상태를 알 수 있다는 의미? 가 된다.
참고로 아래 링크로 가면 자세히 설명되어 있다. 그걸 보는 걸 추천한다..
https://choosemuse.com/blogs/news/a-deep-dive-into-brainwaves-brainwave-frequencies-explained-2
Understanding Brainwaves and Frequencies: A Comprehensive Guide | Muse™ EEG-Powered Meditation & Sleep Headband
Uncover the science behind brainwave frequencies, explore types of brainwaves, and harness their potential for heightened perception, creativity, and problem-solving. Discover innovative tools like the Muse 2 Headband and the Muse S Headband that revolutio
choosemuse.com
https://choosemuse.com/pages/muse-research
Research | Muse™ EEG-Powered Meditation & Sleep Headband
Buy Muse: the brain sensing headband in USD and receive free and fast US delivery with a money back guarantee. Worldwide Shipping available. Muse is the world's most popular consumer EEG device providing real-time neurofeedback to learn, track and evolve y
choosemuse.com
https://www.etri.re.kr/webzine/20170630/sub04.html
Technology
Technology
www.etri.re.kr
참고로 바로 위인 etri.re.kr 링크에 있는 내용 중에 한국어로 된 사진 있는데..
이걸 보면 될 것 같긴 하다.
머.. 파형은 잘은 모르겠지만 개발하는 곳마다 다 다르게 적혀있다..
어디서는 0.5에서 델타가 시작하고 어디에서는 3.5에서 시작하고 어디에서는 2로 시작하고 그런다..
암튼 저 범위는 무시하고 그냥 각각 파형에 따라 그 파형이 무슨 역할을 하는지만 이해하면 될 것 같다.
참고로 아래에서 말하겠지만 집중도 구할 때 사용하는 파형은 베타파와 알파파이다.
코드 설명 및 이해하기
use_data_seconds = 1 # n초 동안 데이터 수집
sampling_rate = BoardShim.get_sampling_rate(board_id)
eeg_channels = BoardShim.get_eeg_channels(board_id)
num_points = use_data_seconds * sampling_rate # 몇 초 동안의 데이터를 가져올지
이전 글에서 설명한 코드에 추가적으로 eeg_channels에 대한게 있어서 그것만 추가로 적어보겠다.
변수에 적힌 말 그대로 그냥 EEG 채널을 출력하는 것이다.
{'eeg_channels': [1, 2, 3, 4], 'eeg_names': 'TP9,AF7,AF8,TP10', 'marker_channel': 7, 'name': 'Muse2', 'num_rows': 8, 'other_channels': [5], 'package_num_channel': 0, 'sampling_rate': 256, 'timestamp_channel': 6}
이전 글에서 언급했던 Muse 2 기기에 대한 정보값에서 'eeg_channels' 저거 [1, 2, 3, 4] 저거를 반환하는 함수가 get_eeg_channels() 이다.
다른 eeg_names나 이런 거 가져오는 함수들도 있다.
암튼 eeg_channels에는 [1, 2, 3, 4] 값이 저장되어 있다.
for count, channel in enumerate(eeg_channels):
DataFilter.perform_bandpass(data[channel], sampling_rate, 2.0, 45.0, 4, FilterTypes.BESSEL_ZERO_PHASE, 0) # 분석할 파형 밖의 범위 제거
DataFilter.perform_bandstop(data[channel], sampling_rate, 48.0, 52.0, 4, FilterTypes.BUTTERWORTH_ZERO_PHASE, 0) # 50Hz의 주변 전기 노이즈 제거
DataFilter.perform_bandstop(data[channel], sampling_rate, 58.0, 62.0, 4, FilterTypes.BUTTERWORTH_ZERO_PHASE, 0) # 60Hz의 주변 전기 노이즈 제거
솔직히 제일 자신 없는 부분이다..
당시에도 어떻게든 찾아서 수업 자료로 만든 적도 있는데.. 결국엔 교수님이 나도 제대로 이해하지 못하고 쓰고 학생들도 이해 못 할 것 같아서 제외된 설명이다..
원래는 Detrend라는 함수도 사용했는데 굳이 중요할 것 같진 않아서 제외했다. 수업 자료에도 제외했었는데.. 넣는 게 나았을지도..?
그리고 당시에는 저거 데이터 처리 코드를 가져올 때 딱히 이해는 안 하고 가져와야 될 것 같아서 문서에서 찾아서 가져왔던 것.. 수업 자료(PPT) 만들게 되면서 그나마 이해했던 것..
암튼 여기가 좀 길 듯하다..
일단 저 코드에서는 Muse 2 기기에서 수집된 데이터들로 집중도를 구하기 전 전처리를 하는 단계이다.
for 문은 각 전극에 해당하는 채널 번호를 가져오게 하는 건데 data에는 eeg 전극 채널 외에 다른 채널들도 있기에 그것 중 전극으로 수집한 값이 들은 채널만 가져올 수 있도록 하는 역할이다.
그리고 이제 for문 안에 있는 걸 설명해야 하는데.. 제일 자신 없다.. 최대한 당시에 이해한 내용으로 설명해 보겠다..
일단 여기에서 사용하는 데이터 처리 함수는 perform_bandpass와 perform_bandstop이 있다.
함수에 들어갈 파라미터는 대충 이렇다.
DataFilter.perform_band[pass|stop](<적용할 데이터>, <샘플링 속도(256)>, <범위 시작 값>, <범위 끝 값>, <오더>, <적용할 필터 방식>, 0)
[pass|stop] <- 이건 pass와 stop 둘 다 된다는 의미
오더는 세기를 생각하면 될 것 같다. 이후에 나옴
필터는 이후에 설명..
그리고 각각의 역할은 이렇다.
BandPass
- 필터에 따라 특정 주파수 범위 사이의 신호를 통과 시킴
- 필터에 따라 특정 주파수 범위 밖의 나머지 신호를 감소 시킴
BandStop
- 필터에 따라 특정 주파수 범위 사이의 신호를 감소 시킴
- 필터에 따라 특정 주파수 범위 밖의 나머지 신호를 통과 시킴
당시에 수업 자료(PPT)에 적은걸 그대로 적은 거긴 한데.. 솔직히 내가 적어도 이해가 안 된다.
간단하게 말하면 BandPass는 범위 안의 데이터는 놔두고 나머지 신호는 감소시킨다. BandStop은 그냥 반대로 생각하면 된다.
암튼 이제 필터에 대해서 볼 건데 필터의 종류는 대충 이렇다. (CHEBYSHEV_TYPE_1 이것도 있긴 한데 처음 보는 거라서 패스!)
- BUTTERWORTH
- BESSEL
- BUTTERWORTH_ZERO_PHASE
- BESSEL_ZERO_PHASE
이제 각각을 설명해 볼 건데 ZERO_PHASE는 위상 왜곡을 최소화하는 거라는데 필터 먼저 설명하고 따로 설명하겠다.
Butterworth : 신호를 부드럽게 처리하며 범위 밖 주파수를 제거 (여기서는 BandStop에 사용)
Bessel : 파형의 원래 모양을 유지하며 신호 왜곡이 적음 (여기서는 BandPass에 사용)
최대한 간단하게 적었다.. 수업 자료(PPT)에 적은 것 보다도 더 간단하게..
솔직히 간단하게 적었다 하더라도 아무리 봐도 이해하기는 어려울 거다.. 그렇기에 시각화! (시각화 Python 코드 비밀~)
솔직히 부드러워 보이는 건 Bessel 같아 보이긴 하는데 뭐.. 설명으론 저렇다. 암튼 저런 차이가 있는데.. 나는 뭐 시각화한 걸 봐도 잘은 모르겠다.
그냥 코드에서 사용하는 것하고 같이 말해보면 주석에 적힌 것처럼
분석할 파형 밖의 범위 제거하는 건 파형 원래의 모양을 유지하는 것이 좋으니 BandPass - Bessel 사용
50/60Hz 주변의 전기 노이즈 제거는 노이즈 같은 이상한 신호를 제거해야 하니 부드럽게 처리해야 해서 BandStop - Butterworth 사용
이라고 생각하면 편할 것 같다. (갑자기 이해되면서 생각난 거라 적어본 거)
이제 필터 바로 뒤에 붙는 Zero Phase에 대해 설명할 건데.. 이건 그냥 간단하다.
위상 왜곡을 최소화하는 것이다. 그냥 데이터의 왜곡을 적게 만드는 것이다.
또한 Zero Phase를 사용 안 하면 데이터 신호가 지연된 것처럼 보인다.
그냥 시각화해서 보자! (시각화 Python 코드 비밀~)
여기에서 분홍색이 원본, 초록 파랑이 Zero Phase 적용 안 한 거 보라 빨강이 Zero Phase 적용한 거인데
BandPass - line이라고 되어있는 게.. 그냥 그래프 2개 똑같은 거 그린 후에 그림판으로 잘 보이는 기준점을 선정해서 선으로 그려 수정한 거다. 잘 보이게 하려고 2개 넣은 거
암튼 그림을 보면 알 수 있듯이..
확대해서 보면 알 수 있다.
빨간색과 보라색은 Zero Phase를 적용한 건데 원본 파형과 기준선의 위치 차이가 없다고 봐도 될 정도이다.
그에 반면 Zero Phase를 적용 안 한 초록색과 파란색은 뒤로 밀려난 것을 확인할 수 있다. 조금 밀려난 건 회색, 좀 더 밀려난 건 검은색으로 표시했다. (참고로 그릴 때 그림판으로 확대해서 맞춰서 그린 거라 내가 봐도 잘 그린 듯)
추가적으로 Butterworth가 더 많이 지연된 것처럼 보이기도 한다.
암튼.. 이걸로 끝이 아니라 하나 더..! 오더가 있다.
위에 설명하는데 힘을 다 빼버려서 오더는 적당히 설명하겠다. 어차피 이건 이미지로 보는 게 제일 이해가 쉬운 거라..
그냥 Order은 필터의 강도라고 생각하면 된다.
Order은 숫자가 커질수록 필터의 세기가 강해진다고 생각하면 된다. 아래 시각화를 보면 이해가 될지도? (시각화 Python 코드 비밀~)
![]() |
![]() |
강도가 세질수록 좀 더 가파르게 변한다고 생각하면 된다. 그리고 원본은 표시를 안 해놓긴 했지만 그냥 직선이다.
여기에서는 필터에 따라 직선이 휘어진 것으로 생각하면 된다.
참고로 Order은 1 ~ 8까지만 설정 가능하다.
마지막으로 각 데이터 처리 코드를 지나면 어떻게 변하는지도 시각화해 봤다.
아무튼 이걸로 데이터 처리 부분 설명은 끝! 휴..
bands = DataFilter.get_avg_band_powers(data, eeg_channels, sampling_rate, False)
feature_vector = bands[0]
코드에 대해서 설명하기 전에 말할 것이 있는데..
수업 자료 만들 때 잘못 썼던 부분이 여기에 하나 있다..
지금은 코드를 정상적으로 고쳤는데.. 저기 뒤에 get_avg_band_powers 함수의 뒤에 False인 부분을 처음에는 True로 했었다. True로 하게 되면 저게 Apply Filter인데 바로 위에 데이터 처리 코드 부분을 수행한 뒤에 데이터를 반환해 준다..
즉 지금은 False로 해서 위에서 전처리한 게 소용이 있는데.. True로 하면 전처리를 하고 또 전처리를 하게 되어서 좀 별로다.. 암튼 그럼..
이 코드는 수집된 데이터에서 Delta(델타), Theta(세타), Alpha(알파), Beta(베타), Gamma(감마) 데이터(band_powers)를 뽑아서 그중 평균값만 뽑아 feature_vector에 저장하는 코드이다. (위에 파형에 대한 설명을 적어놓았으니 델타 세타파 저런 게 뭔지 모르겠다면 위에 적은 걸 보시길..)
그리고 feature_vector에는 bands[0]을 저장하는데 위에 적혀있는 것처럼 평균값만 뽑는다.
그럼 bands에 저장되어 있는 다른 건 무엇이냐.. 하면.. get_avg_band_powers가 뭘 리턴하는지 보면 된다.
# 코드 설명을 하기 위한 코드
def get_avg_band_powers(cls, data, channels: List, sampling_rate: int, apply_filter: bool) -> Tuple:
"""calculate avg and stddev of BandPowers across all channels, bands are 1-4,4-8,8-13,13-30,30-50
:param data: 2d array for calculation
:type data: NDArray[Shape["*, *"], Float64]
:param channels: channels - rows of data array which should be used for calculation
:type channels: List
:param sampling_rate: sampling rate
:type sampling_rate: int
:param apply_filter: apply bandpass and bandstop filtrers or not
:type apply_filter: bool
:return: avg and stddev arrays for bandpowers
:rtype: tuple
"""
bands = [(2.0, 4.0), (4.0, 8.0), (8.0, 13.0), (13.0, 30.0), (30.0, 45.0)]
return cls.get_custom_band_powers(data, bands, channels, sampling_rate, apply_filter)
이게 그 함수의 코드이다.
아래 bands에 정의되어 있는 건 Delta, Theta, Alpha, Beta, Gamma 파형의 범위 값이다. 위에서 말했듯이 여기에서 사용하는 파형의 범위도 약간 다르다.
아무튼 코드 보단 주석을 보면 되는데..
주석을 보면 return이 뭐를 반환하는지 적혀있다.
avg는 평균값을 의미하고 stddev는 표준편차를 의미한다. 배열로 2가지 값들을 반환하기 때문에 0번 인덱스인 평균값만 불러오도록 코드를 제작한 것이다.
참고로 분석에는 보통 평균값을 이용하고 데이터의 안전성이나 변동성을 확인할 때는 표준편차를 이용한다 한다.
그리고 리턴되는건 Delta, Theta, Alpha, Beta, Gamma 각각의 평균 값과 표준편차 값이다. 즉 입력된 모든 데이터의 5개 뇌파가 나오는게 아니라 모든 전극 뇌파에서 Delta, Theta, Alpha, Beta, Gamma를 추출하고 그것으로 평균과 표준편차 값을 구해서 반환한 것이다. 그래서 리턴되는 것에서 평균과 표준편차 각각은 5개의 실수가 리스트로 담겨서 리턴된다.
추가적으로 원하는 범위를 사용하여 추출하고 싶으면 저기 return 부분에 적혀있는 것처럼 get_custom_band_powers 이 함수에 원하는 범위를 담은 리스트를 넘겨서 받아내면 된다. (난 잘 모르기에 brainflow에서 사용하는 걸 사용)
추가적으로 위에 왜 함수 사용할 때 False로 해놓았냐에 대한 설명이 있는데.. 그걸 좀 더 자세하게 설명해 보면..
저 apply_filter가 어디에서 사용되는지 따라가 봤다. 따라가 봤는데 DataHandlerDLL의 함수에 사용된다고 한다.. DLL를 본 순간.. 이 brainflow는 C++로 제작된 거니깐 C++ 코드에 있겠다 생각하고 C++ 코드를 찾아보았다..
그리고 찾아냈는데.. 아래 C++ 코드가 있었다.
# 코드 설명을 하기 위한 코드
if (apply_filters)
{
exit_codes[i] = detrend (thread_data, cols, (int)DetrendOperations::CONSTANT);
if (exit_codes[i] == (int)BrainFlowExitCodes::STATUS_OK)
{
exit_codes[i] = perform_bandstop (thread_data, cols, sampling_rate, 48.0, 52.0, 4,
(int)FilterTypes::BUTTERWORTH_ZERO_PHASE, 0.0);
}
if (exit_codes[i] == (int)BrainFlowExitCodes::STATUS_OK)
{
exit_codes[i] = perform_bandstop (thread_data, cols, sampling_rate, 58.0, 62.0, 4,
(int)FilterTypes::BUTTERWORTH_ZERO_PHASE, 0.0);
}
if (exit_codes[i] == (int)BrainFlowExitCodes::STATUS_OK)
{
exit_codes[i] = perform_bandpass (thread_data, cols, sampling_rate, 2.0, 45.0, 4,
(int)FilterTypes::BUTTERWORTH_ZERO_PHASE, 0.0);
}
}
봤을 때 바로 알 수 있을 것이다.. 이 코드 설명 바로 위 코드 설명에 있는 코드하고 닮았다..
즉 apply_filter를 켜게 되면 위에서 했던 데이터 처리 코드가 작동한다는 것..
그 때문에 apply_filter를 켜면 데이터 처리를 했는데 또 처리를 하게 된다는 의미..
뭐 그래도 약간은 달라서 괜찮을지도..?
(근데 함수와 괄호 사이가 왜 띄어져 있는 거지? brainflow 코드 살펴보면서 제일 신경 쓰이는 것..)
print(f"Attention: {feature_vector[3] / feature_vector[2]}") # Beta / Alpha
이제 여기에서 집중도를 구하게 된다..
원래는 저걸 Attention이라 안 적고 Mindfulness 라 적었는데.. 이유는 다음 글에서 설명할 인공지능으로 집중도 구하는 것과 관련되어 있는데.. 거기에서는 Mindfulness를 사용한다.. 그런데 저 Beta / Alpha 이게.. 집중도를 구하는데 Mindfulness 보단 Attention으로 번역하는 게 더 맞아서 수정했다. 그리고 찾아보니 다음 글에서 할 Mindfulness는 현재의 집중도와 약간 다르긴 하다.. 뭐 딱히 중요하진 않으니 넘어가도 괜찮지 않을까라는.. 생가..ㄱ
암튼 저거 위에서 가져온 feature_vector 변수에는 어쨌든 평균값의 델타 베타 이런 값들이 가져와지게 되는데
거기에서 순서대로 델타부터 해서 해보면..
- Delta(델타) : feature_vector[0]
- Theta(세타) : feature_vector[1]
- Alpha(알파) : feature_vector[2]
- Beta(베타) : feature_vector[3]
- Gamma(감마) : feature_vector[3]
이렇게 인덱싱할 수 있다고 생각하면 된다.
그렇기에 3번과 2번을 가져와서 나누는 거니깐 암튼 Beta / Alpha를 한다는 소리..
이걸 하면 집중도가 구해진다는 의미가 된다.
저 Beta / Alpha 수식은 보통적으로 집중도를 구할 때 많이 사용되는 수식 같다. 솔직히 나는 저게 집중도를 구할 수 있는 건지는 잘 모르겠다. 뭐 그래도 아래에 Beta / Alpha가 언급된 논문 2개를 걸어놓겠다.. (참고로 영어라서 거의 보진 않았다..)
https://www.mdpi.com/2076-3425/13/1/57
https://typeset.io/questions/what-is-the-relationship-between-eeg-beta-alpha-index-and-8f18vjplgq
좀 더 편한 코드
from time import sleep
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
from brainflow.data_filter import DataFilter
board_id = BoardIds.MUSE_2_BOARD
params = BrainFlowInputParams()
params.serial_number = "Muse-0465" # Muse 2의 고유 시리얼 넘버
board_shim = BoardShim(board_id, params)
use_data_seconds = 5 # n초 동안 데이터 수집
sampling_rate = BoardShim.get_sampling_rate(board_id)
eeg_channels = BoardShim.get_eeg_channels(board_id)
num_points = use_data_seconds * sampling_rate # 몇 초 동안의 데이터를 가져올지
# Muse 2 연결
board_shim.prepare_session()
# Muse 2 데이터 수집 시작
board_shim.start_stream()
# n초 + 1초 동안 데이터 수집
sleep(use_data_seconds + 1)
# 수집한 데이터를 변수에 저장
data = board_shim.get_current_board_data(num_points)
# 예측에 필요한 데이터 추출
bands = DataFilter.get_avg_band_powers(data, eeg_channels, sampling_rate, True)
feature_vector = bands[0]
# 집중도 예측
print(f"Attention: {feature_vector[3] / feature_vector[2]}") # Beta / Alpha
# Muse 2 데이터 수집 정지
board_shim.stop_stream()
# Muse 2 연결 해제
board_shim.release_session()
이 코드는 위에서 말했듯이 get_avg_band_powers에서 자동으로 필터 적용해 주는 apply_filter를 켜서 데이터 전처리를 따로 안 하고 되도록 하는 코드이다.
위에 코드는 잊어버리고 이거 사용하면 될 듯하다..
암튼 바로 실행해 보자! 이번엔 귀찮긴 하지만 착용하고 측정했다!
그리고 위에서도 말했듯이 이번에는 1초가 아니라 5초 측정이다.
1초로도 되기는 하는데.. 조금 부족해 보여서 5초로 했고.. 좀 더 길게 할수록 정확도는 높아질 것 같다 생각은 들긴 한다..
코드 실행!
[2025-02-19 07:46:39.169] [board_logger] [info] incoming json: {
"file": "",
"file_anc": "",
"file_aux": "",
"ip_address": "",
"ip_address_anc": "",
"ip_address_aux": "",
"ip_port": 0,
"ip_port_anc": 0,
"ip_port_aux": 0,
"ip_protocol": 0,
"mac_address": "",
"master_board": -100,
"other_info": "",
"serial_number": "Muse-0465",
"serial_port": "",
"timeout": 0
}
[2025-02-19 07:46:39.170] [board_logger] [info] Use timeout for discovery: 6
[INFO] SimpleBLE: D:\a\brainflow\brainflow\third_party\SimpleBLE\simpleble\src\backends\windows\Utils.cpp:33 in initialize_winrt: CoGetApartmentType: cotype=-1, qualifier=0, result=800401F0
[INFO] SimpleBLE: D:\a\brainflow\brainflow\third_party\SimpleBLE\simpleble\src\backends\windows\Utils.cpp:41 in initialize_winrt: RoInitialize: result=0
[2025-02-19 07:46:39.203] [board_logger] [info] found 1 BLE adapter(s)
[2025-02-19 07:46:39.409] [board_logger] [info] Found Muse device
[2025-02-19 07:46:41.070] [board_logger] [info] Connected to Muse Device
[2025-02-19 07:46:42.072] [board_logger] [info] found control characteristic
Attention: 0.5993206298011964
다른 정보 출력들은 이전 글에서 말했으니 무시하고 Attention만 봐보자!
처음 실행하고 코드의 한 함수를 보면서 집중했을 때 (글 쓰느라 머리 엄청 아픈 상태였음 글 쓰는데 6시간쯤 됐을 때)
Attention: 1.172955634748009
값이 작아지나 머리 흔들거나 집중 안 했을 때 (딴 데 보거나 머리 흔듦.. 집중해서 머리 아픈 상태라 높게 나올까 봐..)
Attention: 0.5993206298011964
도대체 값이 어디까지가 최대인거지? 집중 한번 해볼까? 하다가 한곳에 집중하지 코드를 훑어보고 있었을 때
Attention: 0.7456045268742625
어라? 왜 낮은 거지.. 하고 함수 안을 살펴보고 있었을 때 (함수 들어갈 때 화면이 바뀌면서 집중력 더 떨어짐)
Attention: 0.45490847948134255
그냥 한곳에 집중해 보자 하고 한 함수만 가까이서 집중하며 보고 있었을 때
Attention: 1.2981097661167502
측정하면서 어떤 상황인지 함께 적어봤다..
확실히 그 상황마다의 집중도를 확실하게 캐치하는 것 같다. 정규화는 되어있지 않아서 확인하기는 좀 힘들긴 하지만..
저게 0 ~ 1의 값이 아닌 이유는 뭐 당연하지만 값의 범위가 달라서이다.. 저거를 0 ~ 1로 맞추는 방법이 있긴 하겠지만.. 뭐 있겠지만 생각한다. 참고로 뭐 max 값 min 값 해서 범위 찾아서 그걸로 0 ~ 1로 맞추는 걸 생각해 보긴 했는데 어차피 측정할 때 데이터를 이상하게 하는 방법도 있고 개인마다 다 차이 나서 맞추기가 어려울 것 같긴 하다. 뭐 찾아보면 어딘가에는 0 ~ 1로 맞추는 게 있긴 하겠다만.. 어차피 다음 글에서 인공지능으로 하는 게 0 ~ 1 값이다.
추가로 이전에 말했던 것 같은데 왼쪽은 근육이 발달하지 않은 건지 잘 안되지만 오른쪽은 뭔가 귀를 위로 올린다 생각하며 힘준 상태로 이를 물어버리면 오른쪽 귀 쪽이 뭔가 위로 올라가며 힘을 주는 듯한 느낌이 난다.
이 상태로 측정해 버리면 베타 값인가? 그게 파워가 확 높아져버림..
그렇게 되면 집중도를 조작할 수 있다. 심각하게..
그 상태로 측정하게 되면 이렇게 나온다..
Attention: 4.007621255928729
저거 힘주는 건 기숙사 방에서 해보다가 어쩌다가 집중하려고 힘줬더니만 진짜 돼버려서 알게 된 것..
참고로 기기를 벗은 상태로 측정하면 -1000 같은 결측값과 이상한 값들 때문에 이렇게 나온다.
Attention: 19.663765197669104
암튼 이상하게 나온다. 이래서 0 ~ 1 값으로 맞추기는 좀 어려울 것 같긴 하다.. 뭐 극단적으로 지금 생각나는 방법으로는 범위를 -1000 ~ 1000으로 해서 하면 될 것 같긴 한데, 이러면 집중을 해도 값이 작게 나올 테니 패스
그리고 추가로 실시간으로 측정하는 걸 원한다면.. 아래 코드로 해보는 걸 추천한다.
from time import sleep
import keyboard
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
from brainflow.data_filter import DataFilter
board_id = BoardIds.MUSE_2_BOARD
params = BrainFlowInputParams()
params.serial_number = "Muse-0465" # Muse 2의 고유 시리얼 넘버
board_shim = BoardShim(board_id, params)
use_data_seconds = 5 # n초 동안 데이터 수집
sampling_rate = BoardShim.get_sampling_rate(board_id)
eeg_channels = BoardShim.get_eeg_channels(board_id)
num_points = use_data_seconds * sampling_rate # 몇 초 동안의 데이터를 가져올지
# Muse 2 연결
board_shim.prepare_session()
# Muse 2 데이터 수집 시작
board_shim.start_stream()
# n초 + 1초 동안 데이터 수집 - 안하면 오류남
sleep(use_data_seconds + 1)
while True:
# 나가기 조건
if keyboard.is_pressed('q'):
break
# 수집한 데이터를 변수에 저장
data = board_shim.get_current_board_data(num_points)
# 예측에 필요한 데이터 추출
bands = DataFilter.get_avg_band_powers(data, eeg_channels, sampling_rate, True)
feature_vector = bands[0]
# 집중도 예측
print(f"Attention: {feature_vector[3] / feature_vector[2]}") # Beta / Alpha
# Muse 2 데이터 수집 정지
board_shim.stop_stream()
# Muse 2 연결 해제
board_shim.release_session()
실행하려면 pip install keyboard로 keyboard 라이브러리 설치하고 실행할 수 있다.
간단하게 설명하면 처음에 데이터 수집을 위해 수집 초 + 1초를 기다린 후
while로 반복하면서 반복될 때마다 최근의 5 * 256 데이터를 가져오고 그걸로 집중도를 계산해서 바로 출력하는 코드이다. 그리고 키보드로 q를 누르면 종료된다.
이 코드의 함정이라면 집중을 한 것을 보고 싶다면 집중한 후에 바로 보는 것은 하기 힘들고 집중하고 설정한 초가 지난 후에 봐야 집중도를 잴 수 있다는 점이다. (ㅋㅋ)
뭐 저 초를 1초로 바꿔도 되기는 하지만.. 정확도는 낮아지니 추천은 안 하지만.. 수업 자료(PPT)에서는 1초로 했었기 때문에.. 해봐도 문제 될 건 아마 없지 않을까.. 하는..
암튼 이걸로 Beta / Alpha 수식으로 집중도를 계산해 보는 건 여기에서 끝!
이거 새벽 2시에 작성하기 시작했는데.. 벌써 8시네.. 허허.. 심지어 더 길어 보이네..
암튼 끝!
'개발 > Muse 2' 카테고리의 다른 글
[Muse 2] 게임으로 집중도 확인하기!! (1) | 2025.02.25 |
---|---|
[Muse 2] BrainFlow ML로 집중도 구하기!! (0) | 2025.02.21 |
[Muse 2] BrainFlow에 Muse 2 연결하고 데이터 수집!! (0) | 2025.02.17 |
[Muse 2] BrainFlow Python 설치하기 (0) | 2025.02.16 |
[Muse 2] Muse 2 알아보기!! (0) | 2025.02.16 |