환자를 위한 비접촉식 제스처 인터페이스 및 모니터링 시스템
프로젝트 개요
Gesture Interface and Monitoring system for Patient(;GIMP)
비접촉식 동작 인식 기술을 이용한 NUI(Natural User Interface)를 구축함으로써 환자들이 간단한 손동작만으로 PC를 직관적으로 제어할 수 있습니다. 의료 담당자는 실시간 모니터링 시스템을 통해서 수신되는 응급 메시지를 확인하고 원격 카메라 모니터링 기능을 이용하여 환자의 상태를 확인할 수 있습니다.
프로젝트 개발
- 개발 기간 : 2018. 03. 02 - 2018. 11. 15
- 개발 인원 : 4명
- 역할
- PMS(환자 모니터링 시스템) 원격 카메라 모니터링 구현
- PMS UI 추가 설계
개발 환경
개발 환경
- 언어 : C++11
- Raspbian Stretch with Desktop
- Qt Creator 4.6.0 for Linux, Qt 5.9.0 for Linux
- Kinect Windows SDK v2.0, C# WPF(Windows Presentation Foundation)
- OpenCV(; OpenCVSharp)
실행 화면
(:편집된 이미지이며 프로그램이 사람을 식별하고 모자이크를 해주지 않습니다.)
클라이언트에서 환자는 응급 메시지 발송을 위해 제스처 명령을 할 수 있습니다.
서버는 클라이언트에서 보낸 신호를 수신받고 Alert 창이 활성화 됩니다.
개발 후기
어려웠던 부분
개발에 필요한 하드웨어 부족
프로젝트 진행을 위해 팀원들과 미리 주문을 해둔 Kinect 재고가 없다는 연락을 받았고, Kinect가 부족하여 각자 담당한 부분을 원활히 개발할 수 없었습니다. 2대의 Kinect를 3명이 사용해야 하는 상황을 해결하기 위해 처음에는 Kinect가 필요한 팀원들끼리 개발 시간을 나누고 로테이션으로 한 명씩 새벽에 개발하였으나 이 방법으로는 프로젝트 마감 기간을 지키기 어려웠습니다.
다른 팀원들이 담당하고 있는 제스처 인식 기능에는 Kinect가 필수적이었기 때문에 저는 제가 담당한 모니터링 기능에서 서버와 클라이언트의 구조를 먼저 생각해보았습니다. 그리고 구현할 부분에서 영상 데이터의 송수신 로직은 Kinect를 사용하지 않아도 크게 달라지지 않을 것이라는 생각이 들어서, 저는 키넥트 대신 웹캠을 사용하여 임의의 클라이언트가 되는 Stub을 만들어 개발했습니다.
이후 팀원으로부터 Kinect를 받아 모니터링을 수행했을 때 초기에는 영상이 계획대로 출력되지 않았지만 Stub을 통해 테스트를 해두었기 때문에 비교적 빠른 시간 이내에 통신 로직 이외의 문제라는 판단이 가능했습니다.
클라이언트는 Kinect SDK에서 제공하는 함수를 사용하여 Kinect를 통해 얻어온 영상 데이터의 타입(ColorFrame)을 byte 형태로 변환했었는데, 이때 변환된 데이터가 서버에서 사용하는 cv::Mat 타입으로 제대로 변환될 수 없음을 확인하였고 이 부분은 별도로 OpenCVSharp 라이브러리를 통해 byte 형태로 변환해주었습니다. 모니터링 기능이 잘 작동되는 것을 확인한 후에는 서버가 다수의 클라이언트와 연결되어 모니터링을 수행할 수 있도록 블로킹 소켓을 스레드와 함께 사용하여 구현을 마무리 했습니다.
구현해야 할 기능이 하드웨어(Kinect)에 종속적인 상황을 해결하기 위하여 소프트웨어 공학적으로 접근하였고, 제가 구현해야 할 부분의 설계 구조를 고려하여 Stub을 만들어 문제를 해결할 수 있었습니다.
모니터링 기능 구현 중 발생한 문제
클라이언트가 Kinect에서 얻은 영상 데이터를 서버로 전송하면 서버에서 해당 데이터를 수신하고 그 영상을 볼 수 있도록 출력하는 기능을 구현하고 있었습니다. 이 과정에서 recv 함수를 사용했는데, recv 함수가 기본적으로 블로킹 모드로 동작하기 때문에 클라이언트로부터 모든 데이터를 받아온 이후에 다음 흐름으로 넘어갈 것으로 생각하였습니다.
하지만 클라이언트에서 send 함수를 통해 영상 데이터를 전송하고 서버에서 recv 함수를 통해 데이터를 받는 형태로 구현하였으나 서버가 데이터를 제대로 수신하지 못하는 문제가 있었습니다. 이 부분을 확인해보니 recv 함수가 한 번 호출될 때, 클라이언트가 보내는 모든 데이터를 소켓 버퍼에 한 번에 받을 수 있다는 보장이 없다는 것을 알게 되었습니다.
결과적으로 서버에서 클라이언트가 보내려고 하는 모든 데이터를 수신했는지 확인하려면 수신해야 할 데이터의 크기를 알고 있어야 했으며, 모니터링 기능에 사용되는 영상 데이터의 형태를 클라이언트와 서버에서 모두 동일하게 맞추어 아래의 코드를 통해 수신할 데이터 크기를 미리 정의하였습니다.
img = Mat::zeros(height, width, CV_8UC3); // BGR 3채널
imgSize = img.total() * img.elemSize(); // 수신해야 할 데이터 크기
sockData = new uchar[imgSize];
이처럼 수신하려는 영상 데이터의 크기를 미리 정의하고, recv 함수 호출을 정의된 바이트(imgSize)만큼 수신이 완료될 때까지 반복하는 구조로 변환하여 기존의 의도대로 동작하게끔 구현하였습니다.
모니터링 기능을 구현한 이후에는 클라이언트가 기존에 비해 제스처의 인식 속도가 늦어지는 것 같다는 팀원의 피드백을 받았습니다. 확인해보니 픽셀에 접근하기 위해 At(row, col) 메소드를 사용했던 부분이 성능 저하의 원인이었습니다. C++의 경우 이 부분을 포인터 방식으로 접근하도록 변경하여 성능을 개선할 수 있는데, C#에서는 포인터를 활용하려면 unsafe 키워드를 사용해야하기 때문에 혹시 다른 방안이 있는지 찾아보았으나 다른 해결책을 찾지는 못하였기 때문에 포인터 접근 방식으로 변경하여 성능 저하 이슈를 해결하였습니다.
라즈베리파이, 키넥트 등 하드웨어를 활용해본 프로젝트였고 팀원들과 함께 Git을 활용했습니다.
프로젝트 초반부터 Kinect 장비의 부족으로 인하여 예상하지 못했던 상황이 발생했지만 해결할 수 있었고, 팀원들과 협업하며 프로젝트를 완수하여 캡스톤 디자인 경진대회에서 동상을 수상하였습니다.