[41호]최대 흡기상태 측정 모듈
2016 ICT 융합 프로젝트 공모전 입선
최대 흡기상태 측정 모듈
글 | 고려대학교 강병욱, 김도형, 송명근, 원준현, 장재현
심사평
뉴티씨 실제로 병원에서 X-ray 사진을 찍을 때에 좋은 위치에서 찍기 위한 실질적인 아이디어가 반영된 작품으로 판단되며, 좋은 어플리케이션이다. 잘 만들면 실용적으로 적용하는 것도 좋으리라 생각된다. 난이도는 어렵지 않으나, 실제 동작이 완료된 점, 실용적인 점 등은 좋은 점수를 받은 이유이다.
칩센 상세한 관찰로 아이디어가 도출되어 높게 평가한다. 구현이 쉬울 수는 있어도 어려운 게 뛰어난 것은 아니다.
위드로봇 X-ray 검사 시 호흡을 멈추는 상태를 검출한다는 아이디어는 매우 재미있습니다. 단, 이 상태를 검출하기 위해 PSD를 이용하여 측정자의 어깨의 흔들림을 측정하는 부분은 조금 아쉽습니다. 최대 흡기 상태를 정의할 수 있는 다른 지표를 찾고, 그 지표를 검출할 수 있는 시스템을 개발하는 과정이 상세하게 드러나면 좋은 작품이 될 것 같습니다.
작품 개요
다소 생소할 수 있는 이 작품은 방사선사의 고충으로 고안되어 나온 작품으로, X-ray검사 중 가장 많이 촬영되어지는 흉부부위는 다른 부위와 다르게 방사선사와 피검자의 신호 교환으로 촬영 된다. 보통의 흉부선 X-ray 촬영은 아래와 같이 이루어지고 있다.
이 순간 방사선사는 피검자의 호흡 상태를 알지 못한 채로 X-ray 촬영버튼인 shoot 버튼을 누르며 촬영이 종료된다. 방사선사가 피검자에게 이러한 지시를 하는 이유는 피검사자의 최대 흡기 상태, 즉 최대한 숨을 들이마셔 폐의 부피를 가장 크게 하였을 때 횡격막의 양끝이 아래로 잘 내려가 흉강 내 구조물이 엑스선 영상에 이상적으로 나타나기 때문이다.
물론 대부분의 X-ray 사진이 잘 나오는 편이지만, 그렇지 않을 경우 재촬영이 필요하게 되며, 이는 피검자에게 불필요한 방사선 피폭을 안겨주고 시간, 비용적인 문제 역시 따르게 된다. 또한 청력이 약한 노인이나 장애인분들 같은 경우 방사선사와의 신호 소통이 잘 이루어지지 않아 발생되는 문제점들이 생기는데 있어 구체적인 해결방법을 제시하고자 한다.
인체가 호흡을 할 때 흡기상태에서는 쇄골이 위로 올라가고, 횡격막은 내려가며 외관상으로는 가슴이 내밀어지고 어깨가 올라가게 된다. 즉, 가슴둘레가 커지고 어깨 높이가 높아지게 되는 것이다. 실제로 디텍터를 끌어안은 피검자는 가슴은 내밀지 못하므로 어깨가 더욱 상승하는 것을 관찰할 수 있지만 먼 거리에서 X-ray 버튼을 누르는 방사선사의 눈으로는 식별하가 쉽지 않다. 실제로 대부분의 경험 많은 방사선사는 피검자의 호흡 상태를 알아차리는 것보다 오랜 기간 숙달된 감각으로 X-ray 촬영을 한다고 한다.
우리는 인체가 흡기중일 때 어깨가 상승한다는 것을 이용하여, 어깨 높이에 따른 최대 흡기 상태가 측정이 가능하다면, 보다 정확한 측정 방식이 될 것으로 예상하여 보다 객관적으로, 그리고 정량적으로 최대 흡기 상태를 측정 할 수 있도록 본 모듈을 고안하였다.
작품 설명
프로젝트 진행 중 문득 X-ray가 모듈에 영향을 미치지 않을까? 의문점이 들었다. 하지만 잠깐의 조사로 괜찮다는 것을 금방 알 수 있었는데. X-ray는 공항검색, 화물검색, 전자장비 오류 검출 등으로 많은 분야에서 사용되고 있다. 여기서 전자장비의 검출은 X-ray가 전자장비에 영향을 미치지 않는다고 판단할 수 있으므로 다른 장비의 기기를 추가적으로 사용 가능하다는 것을 알 수 있다. 실제로 방사선사의 휴대폰 또한 온종일 X-ray에 노출되지만 오랜 시간 사용가능한 것 역시 확인할 수 있었다. 피검자의 최대 흡기 상태의 측정 방법은 어깨높이를 측정함으로써 간단히, 간접적으로 알 수 있다.
[그림 5]와 같이 만들어진 모듈은 어깨의 상단에 배치된다. 모듈에서는 적외선을 쏘고 반사되어진 빛을 감지하여 시간을 측정하여 그 거리를 가늠하는 것이다. 실제 흉부 X-ray 상황을 가정해 보자. 모듈이 없을 때와 비슷한 상황으로 가겠지만 모듈을 사용한다면 방사선사는 피검자의 호흡 상태를 알 수 있는 상황이 될 것이다.방사선사는 최대 흡기 상태 측정모듈의 LED의 ON/OFF로 피검자의 상태를 알 수 있다. 모듈의 마지막 Green_Led에 불이 들어오면 피검자의 최대 흡기 상태라고 판단하여 shoot 버튼을 누름으로써 보다 정확하게 흉부 X-ray 촬영이 가능하다.
3.1. 주요 동작 및 특징
● 피검자의 유/무 검출
● 피검자의 어깨 높이 측정
● 피검자의 어깨 높이에 따른 LED 변화
적외선 거리 감지기의 Data에 의해서 각색의 LED로 상황을 알려주게 된다. [그림 9]는 이상적인 적외선 센서 그래프로, 피검자 감지를 하면 [그림 7]과 같이 노란색 LED에 불이 들어오게 되고, 호흡을 측정하여 최대 흡기 상태라고 판단되면 [그림 8]과 같이 녹색 LED까지 불이 들어오게 된다.
3.2. 전체 시스템 구성
3.2.1. Hardware
하드웨어 구성은 비교적 간단하다. 거리를 측정할 적외선 감지모듈과 제어할 수 있는 마이크로컨트롤러 보드 그리고 System 상태를 알려주는 LED로 구성되어 있다. 적외선 거리 측정센서 모듈은 SHARP사의 GP2YOAO2YKOF로 20~150cm의 거리 측정이 가능하며 [그림 11]에서의 노란색 구간인 20~30cm의 전압 레벨의 차가 심하여, Data 값 분석에 있어서 보다 명확하게 판단할 수 있다. [그림 11]의 초록색 구간은 피검자 유무 검출 구간으로 사용되었다.
3.2.2. Software
Software System의 전체 구성은 크게 2가지로 나뉘어진다. 노이즈 제거를 위한 Filtering System과 최대 흡기 상태를 찾는 Detecting System이다. Filtering System은 적외선 거리센서로부터 받아오는 Data의 노이즈를 줄여주는 System이고, 최대 흡기 상태 측정은 Detecting System이 담당한다.
가. Filtering System
Filtering System은 [그림 14]와 같이 적외선 거리측정 센서의 Data 값이 연속적인 노이즈로 제어하기 힘들어 소프트웨어 필터링을 추가로 사용하였다. 부속적인 시스템이지만 기존에 사용되는 Filtering과는 조금 차별화를 두고 무엇보다 작은 시스템으로 큰 효과를 보는 후처리방식이며, 노이즈라 판단될 경우 보정치환을 하여 노이즈를 제거하게 된다.
후처리 System이기 때문에 Data를 Buffer에 잠시 묶어두어 노이즈를 찾아내게 된다. 한 번 Filtering 하는데 3개의 Buffer가 필요하다. 현재 5번의 Filtering을 하여 총 11개의 Data_buffer가 필요하며 하나의 Filtering영역에서 노이즈 신호라고 판단될 경우 이전의 정상적인 값을 치환하여 Data 값을 보정하게 된다. 후처리 방식으로 진행되는 Filtering이지만 실제 시간차이는 System이 동작하는 속도로 움직이기 때문에 사람의 체감으로는 잘 구분하지 못한다. Filtering Data는 Detecting System으로 활용된다.
나. Detecting System
Detection System은 크게 네 가지 구간으로 나누어지는데 그 중 첫 번째 상태로 피검자를 감지하지 못한다면 System은 대기상태에 머물게 된다. 두 번째 상태로 피검자를 감지하였다면 이를 System 사용자에게 알려주게 된다. 세 번째 상태로는 최대 흡기 상태 측정상태로 방사선사에게 Shoot을 해도 좋다는 신호를 주는 상태이다. 네 번째 상태는 shoot 버튼을 누르게 되면 대기상태로 돌아간다. 하지만 이 네 번째 상태는 Shoot 버튼을 누른 상태로 가상의 상태를 시험하기 위해 만든 상태이다. 실제 시스템 구성에서는 사용되지 않고 대기상태로 돌아가게 된다.
3.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)
· C언어 개발
· arduino sketch Tool 사용
4. 단계별 제작 과정
· 문제점을 발견하고 모듈을 구상 거리측정센서 및 보드 선정
· 하드웨어 케이스제작 및 부착
· 소프트웨어제작 1달 동안 Filtering 고안 및 Test.
· 2주 Detecting system 제작 및 수치 값 조정
5. 소스코드
/***********************************************************
1. micros() 함수를 이용한 dt 생성!
2. dt를 이용한 미분값 확인!
3. 배열 10개로의 평균값 구하기.
************************************************************/
#include <TimerOne.h>
#define ts 0.001*0.001
#define FlagOn 1
#define FlagOff 0
#define R_LED 9 // Red LED!!
#define Y_LED 8 // Yellow LED!!
#define Y_LED1 10 // Yellow LED!!
#define Y_LED2 12 // Yellow LED!!
#define G_LED 11 // Green LED!!
#define Filtering_y 4
#define Filtering_diff_y 4
#define matrix_SizeFory 99
#define matrix_SizeForDiffy 99
#define Average_Value 10
#define entry_value 0
#define high_filter 0×00
#define low_filter 0×08
#define filter_buffer_number 4 // 필터링 개수 정하는 곳 <<<< 4가 적당한듯
#define Low_number 1
#define DatsFilterBufferNumber 5
float tCount = entry_value;
float tCountPre = entry_value;
unsigned char i = 0, j = 0, k = 0, l = 0;
unsigned char DetectionFlag = 0×00;
unsigned char reference_Flag = 0×00;
unsigned char System_stats = 0×00; // System Flag
int SumCheckFlag = 0;
int Count = 0;
int refer_filter_value = 0;
int band_pass_value = 0;
int sum_y, sum_diff_y, y_filtering , value_filtering, diff_value, integ_value, integ_y, integ_y_filter;
float t;
int diff_y, diff_y_filtering , dt, diff_diff_y; // time, y_value, diff_Y, △t, diff_diff_Y
int ch_t_value = entry_value;
int diff_y_pre = entry_value;
int y_filtering_pre = entry_value;
int filter_value = entry_value;
int pre_filter_value = entry_value;
int diff_filter_value = entry_value;
int mov_avr_value = entry_value;
int reference_value = entry_value; // 사람이 들어왔을시 기준값 저장
int Neates = entry_value;
int Neates_ = entry_value;
int Breath_Check_Time = entry_value;
int detection_H_Check_Time = entry_value;
float Breath_Ch_tCountPre = entry_value;
float detection_H_Check_tCountPre = entry_value;
int y;
int Start_Value = entry_value;
int Replace_Out = entry_value;
int BreathFlag = entry_value;
int TimeCheckValue = entry_value;
int Data_buffer[2 * filter_buffer_number];
int y_filter_buffer[matrix_SizeFory];
int diff_filter_buffer[matrix_SizeForDiffy];
int reference_buffer[0];
int StartF = 1000;
int DatsFilterBuffer[DatsFilterBufferNumber * 2];
int HumenCHeckFlag[DatsFilterBufferNumber * 2];
int out_value = 0;
unsigned int IR_output = entry_value; // ANALOG IN 0 PIN!!
void PinModeSetup()
{
pinMode(R_LED, OUTPUT);
pinMode(Y_LED, OUTPUT);
pinMode(Y_LED1, OUTPUT);
pinMode(Y_LED2, OUTPUT);
pinMode(G_LED, OUTPUT);
digitalWrite(R_LED, HIGH);
digitalWrite(Y_LED, HIGH);
digitalWrite(Y_LED1, HIGH);
digitalWrite(Y_LED2, HIGH);
digitalWrite(G_LED, HIGH);
}
void saveStatesForNextStep() {
tCountPre = tCount;
diff_y_pre = diff_y;
y_filtering_pre = y_filtering;
pre_filter_value = filter_value;
saveDataBufferForNextStep(filter_buffer_number); // Cation!! this funtion is macro
}
void saveDataBufferForNextStep( int filter_num) { // 5번
for (int i = filter_num – 1 ; i >= 0; i–) {
Data_buffer[(2 * i) + 2] = Data_buffer[(2 * i) + 1];
Data_buffer[(2 * i) + 1] = Data_buffer[(2 * i)];
}
}
void RealtimeProcessingForNextStep() {
for (int i = DatsFilterBufferNumber – 1 ; i >= 0; i–) {
DatsFilterBuffer[(2 * i) + 2] = DatsFilterBuffer[(2 * i) + 1];
DatsFilterBuffer[(2 * i) + 1] = DatsFilterBuffer[(2 * i)];
}
}
void printStates(int t) {
Serial.print(t);
Serial.print(“, “);
}
int mov_avr_filter(int y_value) {
sum_y = 0;
y_filter_buffer[0] = y_value;
for (int i = Filtering_y; i >= 0; i–) {
y_filter_buffer[i] = y_filter_buffer[i - 1];
sum_y += y_filter_buffer[i];
}
value_filtering = sum_y / Filtering_y + 1;
return value_filtering;
}
int diff_buffer_filtering(int y_value) {
sum_diff_y = 0;
for (int i = Filtering_diff_y; i >= 0; i–) {
diff_filter_buffer[i + 1] = diff_filter_buffer[i];
sum_diff_y += diff_filter_buffer[i];
}
diff_filter_buffer[0] = y_value;
value_filtering = sum_diff_y / Filtering_diff_y;
return value_filtering;
}
int diff_funtion(int value_pre, int value) {
diff_value = (((value – value_pre) / dt));
return diff_value;
}
int integ_cacul(int value, int value_pre)
{
int tringle = 0;
integ_value = (((value – value_pre) * dt));
return integ_value;
}
/*
compare_funtion 함수 (상 하위 필터 선택, 필터횟수, 과거의 과거의 값, 과거의 값, 현재 값)으로 구성, 필터횟수가 크면 클수록 급격히 변화되는 변화량에 영향을 미칠 수 있음!! 현재의 값과 과거의 값을 비교하여 과거의 값을 교정하는 방식의 필터링. 순간적인 noise에 적합하다.
*/
int compare_function( unsigned char high_low_filter, unsigned char HWNUM, int y, int after_y, int after_after_y) {
unsigned char high_after_flag = 0×00;
unsigned char high_after_after_flag = 0×00;
unsigned char low_after_flag = 0×00;
unsigned char low_after_after_flag = 0×00;
switch (high_low_filter) {
case 0×00 :
if ((y > after_after_y) && (y > after_y)) // y 의심
{
return after_after_y;
}
else {
return y;
}
break;
case 0×08 :
if ((y < after_after_y) && (y < after_y)) // y 의심
{
return after_after_y;
}
else {
return y;
}
break;
case 0×02 : if (high_low_filter == 0×02 && high_after_flag > high_after_after_flag) {} break;
case 0×04 : if (high_low_filter == 0×04 && high_after_flag < high_after_after_flag) {} break;
}
}
int ReplaceFuction(int Pre_Pre_y, int Pre_y, int y) {
if (Pre_Pre_y < Pre_y) {
return Pre_y;
} else {
return Pre_Pre_y;
}
}
void High_Compare_operation() {
for (int i = 0 ; i <= filter_buffer_number ; i++) {
i = 2 * i;
j = (2 * i) + 1;
k = (2 * i) + 2;
Data_buffer[k] = compare_function(high_filter, filter_buffer_number, Data_buffer[k], Data_buffer[j], Data_buffer[i]);
}
}
void Low_Compare_operation() {
for (int i = (filter_buffer_number – (filter_buffer_number – Low_number)) ; i <= filter_buffer_number ; i++) {
i = 2 * i;
j = (2 * i) + 1;
k = (2 * i) + 2;
Data_buffer[k] = compare_function(low_filter, filter_buffer_number, Data_buffer[k], Data_buffer[j], Data_buffer[i]);
}
}
void Time_Count() {
tCount = micros();
t = (float)(tCount) * ts;
dt = (float)(tCount – tCountPre) * ts;
}
void Serial_out_put() {
/* Serial out put*/
// for(int j = 0 ; j <= filter_buffer_number*2 ; j++){
// printStates(Data_buffer[j]);
// }
printStates(Data_buffer[filter_buffer_number * 2]);
printStates(Data_buffer[filter_buffer_number * 2]);
printStates(mov_avr_value);
Serial.print(“\r\n”);
}
void System_opeation() {
switch (System_stats) {
case 0×00 :
digitalWrite(R_LED, LOW);
digitalWrite(Y_LED, HIGH);
digitalWrite(Y_LED1, HIGH);
digitalWrite(Y_LED2, HIGH);
digitalWrite(G_LED, HIGH);
// Serial.println(” System operation… “);
break;
case 0×02 :
digitalWrite(R_LED, LOW);
digitalWrite(Y_LED, LOW);
digitalWrite(Y_LED1, HIGH);
digitalWrite(Y_LED2, HIGH);
digitalWrite(G_LED, HIGH);
// Serial.println(” Dection_humen… Take brethe…”);
break;
case 0×04 :
digitalWrite(R_LED, LOW);
digitalWrite(Y_LED, LOW);
digitalWrite(Y_LED1, LOW);
digitalWrite(Y_LED2, LOW);
digitalWrite(G_LED, LOW);
// Serial.println(” Ok. ready_shot “);
break;
}
}
void System_timer(void)
{
System_opeation();
}
void setup() {
// put your setup code here, to run once:
PinModeSetup();
Serial.begin(115200); // error rate -3.5%
Timer1.initialize(12000); // System_timer to run every 0.2 seconds
Timer1.attachInterrupt(System_timer);
delay(100);
}
void loop() {
Data_buffer[0] = analogRead(A0);
Time_Count();
High_Compare_operation();
// mov_avr_value = mov_avr_filter(Data_buffer[filter_buffer_number*2]);
// Serial_out_put(); //화면 출력 for Test
/* 아래에 검출 이 들어와야함.*/
if (Data_buffer[0] < 265) {
System_stats = 0×00;
}
/****************** System_stats = 0×00 ******************/
if (System_stats == 0×00) {
if ((Data_buffer[filter_buffer_number * 2]) – 50 > 300) {
detection_H_Check_tCountPre = tCount;
System_stats = 0×02;
}
}
/****************** System_stats = 0×02 ******************/
if (System_stats == 0×02) {
Breath_Check_Time = 0;
detection_H_Check_Time = (float)(tCount – detection_H_Check_tCountPre) * ts;
if (detection_H_Check_Time == 1) {
Neates = Data_buffer[filter_buffer_number * 2]; // 첫번째 조건 초기값 : Neates
Neates_ = Data_buffer[filter_buffer_number * 2]; // 두번째 조건 초기값 : Neates_
}
if ((Data_buffer[filter_buffer_number * 2]) > Neates + 2) { //Neates보다 큰 값이 들어오면 Neates는 더 커진다.
Neates = Data_buffer[filter_buffer_number * 2];
Breath_Ch_tCountPre = tCount;
} else { // 위의 조건이 맞지 않으면 Breath_Ch_tCountPre 는 1초 .. 2초 세게된다.
if (Neates < Neates_ + 20) { // Nestes_ 보다 들어오는 값이 작다면 Breath_Ch_tCountPre 변동된다.
Breath_Ch_tCountPre = tCount;
}
Breath_Check_Time = (float)(tCount – Breath_Ch_tCountPre) * ts;
}
if (Breath_Check_Time == 1) { // Breath_Check_Time이 1초가 되면 shoot 상태로 가고 0×02 상태에서 벗어난다.
System_stats = 0×04;
}
}
saveStatesForNextStep(); // 항상 마지막에 위치해야 함
}