# 영상 보행분석 프로젝트 > 경북대학교병원 / 2019.05 ~ 2021.01 (1년 9개월) <img src="https://i.imgur.com/zHJ7WEE.gif" width="100%" style="boarder:1px solid gray"> ## 프로젝트 요약 - 경북대학교병원 신경과 전문의와 협업하여 진행한 프로젝트로, 카메라를 활용하여 보행 변수(속도, 보폭 등)을 측정하는 딥러닝 모델을 개발하였습니다. - 통제되지 않은 보행분석 환경(보행자 이외의 사람이 등장)에서 노이즈를 최소화하기 위한 3D CNN 모델을 개발하였습니다. - External validation을 통해, 상용 보행분석 장비 [GAITRite](https://www.gaitrite.com/) 시스템과 높은 agreement를 확인 및 검증하였습니다. - Flask 기반 보행분석 inference API를 구현하였습니다. - 저널 논문 1편, 컨퍼런스 논문 1편의 학술적 성과를 이루었습니다. - 저널 논문 (Scientific Reports, 2021) : [링크](https://www.nature.com/articles/s41598-021-90524-9) - 컨퍼런스 논문 (ICONIP, 2019) : [링크](https://link.springer.com/chapter/10.1007/978-3-030-36808-1_69) **Code** : [link](https://github.com/youhs4554/gaitanalysis); **Notion** : [link](https://checker-zinnia-239.notion.site/Camera-based-Human-Gait-Analysis-cf86be898416489eb654f3bbdef8386f) ## 개발 목표 - 딥러닝 기반 영상 보행분석 모델 개발 - WEB 기반 병원 정보 시스템에서 보행 분석결과 조회 기능을 위한 Flask 기반 서빙 파이프라인 구현 ## 기술 스택 - PyTorch, Skorch - Numpy, OpenCV - Pandas, Matplotlib, Seaborn - Flask, sqlalchemy - Video Understanding, Vision-based Gait Analysis, 3D CNNs, Yolo v3 ## 개발 내용 ### 환자 영역 선택 > 환자의 보행에 집중하기 위해, [Yolo v3](https://pjreddie.com/darknet/yolo/)를 활용하여 사람 검출 및 트래킹을 수행하였습니다. 환자 영역은 규칙 기반 방법을 적용하여 선택하였습니다. - **트래킹 결과** : 인접 프레임간의 IOU를 계산하고 그리디한 방법으로 트래킹을 진행하였습니다. <table> <tr> <td> <b>IOU 트래커</b> <img src="https://i.imgur.com/A61mQUA.png"> </td> <td> <b>결과물</b> <img src="https://i.imgur.com/VBIJ7I0.gif"> </td> </tr> </table> ![](https://i.imgur.com/VBIJ7I0.gif =x240) <!-- <iframe src="https://drive.google.com/file/d/1D0zcotC6aGFW3PreE9VyhK0rvPX_Nl2G/preview" width="50%" height="240" allow="autoplay"></iframe> --> - **환자 영역 선택 결과** : 박스의 중심점을 기준으로 x축 대비 y축 변화가 가장 큰 트래킹 ID를 선택하였습니다. <table> <tr> <td> <b>x,y 변위 그래프 비교</b> <br> (환자의 track id : <font color="orange">주황색</font>) <img src="https://i.imgur.com/oaAXrev.png"> </td> <td> <b>결과물</b> <img src="https://i.imgur.com/rriwG1M.gif"> </td> </tr> </table> > ![](https://i.imgur.com/oaAXrev.png) ![](https://i.imgur.com/rriwG1M.gif =x240) <!-- <iframe src="https://drive.google.com/file/d/1Gt_y5Dn1S3qnx7LLBbZ1I2jda5CQwpe3/preview" width="50%" height="240" allow="autoplay"></iframe> --> ### Mask Supervision - 환자 영역을 나타내는 바이너리 **마스크 예측 레이어**를 추가하였습니다. - 마스크 예측 결과와 CNN feature를 곱해줘서 환자가 아닌 영역의 **CNN activation을 억제**하였습니다. <br> <img src="https://i.imgur.com/PVhZDTf.png" height="300px"> <br> <br> 코드는 다음과 같습니다. ```python= # i번째 백본 네트워크 레이어에서 output을 얻습니다. (x는 i-1번째 레이어 output) out = backbone_layer(x) # seg_layer를 통해 바이너리 마스크를 예측합니다. seg_layer는 1x1x1 conv 레이어 입니다. seg = torch.sigmoid(seg_layer(out)) # x의 shape를 백본 output과 동일하게 맞춰줍니다. x = max_pool3d(x, [int(x.size(i)/out.size(i)) for i in range(2, 5)]) # max poolling x = matching_layer(x) # 1x1x1 conv -> BN -> ReLU; conv를 통해 채널 갯수를 맞춰줍니다. # 첫째 항 : mask supervision을 적용합니다. i번째 백본 출력 out과 예측된 마스크 seg를 곱해줍니다. # 둘째 항 : mask noise로 인한 blackout을 방지하기 위해, seg를 inverting하여 (i-1) 번째 output인 x와 곱한 결과를 출력에 반영합니다. x = out * seg + x * (1-seg) ``` ### 모델 아키텍처 > torchvision에서 제공하는 [r2plus1d-18](https://arxiv.org/abs/1711.11248) 모델의 각 레이어마다 mask supervision을 반복적으로 수행하여 전체 모델을 구성하였습니다. 각 레이어 마다 $L^{mask}$를 계산하여 $L^{main}$와 함께 최적화 하였습니다. ($L^{mask}$: BCE, $L^{main}$: SmoothL1Loss) 전체 모델 구조는 다음과 같습니다. (모델의 전체 코드는 [여기](https://github.com/youhs4554/gaitanalysis/blob/afc1bab4fd1f3b68d70d2107234bda3a8d833bd7/src/dev/models/regression_model.py#L566)에서 확인할 수 있습니다.) <br> <img src="https://i.imgur.com/PwYudE1.png" height="400px"> ### 결과 > Mask supervision의 효과를 CNN activation 시각화를 통해 확인해 보았습니다. 보행자 이외의 사람이 있더라도 **환자 주변에서 activation이 높게 형성**되는 것을 확인하였습니다. ![](https://i.imgur.com/vWski2L.gif =x240) <br> > Scatter plot을 그려본 결과, mask supervision 적용 후 **scatter의 모양이 더 linear해 지는 것을 확인**하였습니다. (x 축 : ground truth, y축 : predicted) ![](https://i.imgur.com/VajcBM3.jpg) > 또한, **성능 향상**에도 도움이 되는 것을 확인하였습니다. <table style="text-align:center"> <tr> <td rowspan="2">Method</td> <td colspan="3">Mean Absolute Error (MAE)</td> <td colspan="3">R-squared</td> </tr> <tr> <td>Temporal</td> <td>Spatial</td> <td>Summarized</td> <td>Temporal</td> <td>Spatial</td> <td>Summarized</td> </tr> <tr> <td>baseline</td> <td>0.046</td> <td>4.15</td> <td>5.33</td> <td>0.53</td> <td>0.70</td> <td>0.70</td> </tr> <tr> <td>+mask supervision</td> <td><b>0.037</b></td> <td><b>2.74</b></td> <td><b>3.81</b></td> <td><b>0.79</b></td> <td><b>0.82</b></td> <td><b>0.90</b></td> </tr> </table> ### 상용 보행분석 장비 GAITRite 시스템과 agreement 검증 > Scatter plot, Bland-Altman plot, Intraclass correlation 등의 분석을 통해 GAITRite와 **높은 agreement**를 보이는 것을 확인 및 검증하였습니다. **[:arrow_right: 자세한 분석 결과는 논문에서 확인할 수 있습니다. :arrow_left:](https://www.nature.com/articles/s41598-021-90524-9)** Correlation 분석 테이블은 다음과 같습니다. <img src="https://i.imgur.com/4f5oiVL.png" height="300px"> :::success - 학습 데이터에 포함되지 않았던 특정 질환군(INPH;특발성 정상압수두증)에 대한 external validation 실험을 진행하였습니다. - [ICC](https://en.wikipedia.org/wiki/Intraclass_correlation) 0.8 이상의 우수한 agreement를 확인하였습니다. 📗 Excellent: ICC > 0.75, Good: 0.40<ICC<0.75, Poor: ICC<0.40 [[출처](https://www.sciencedirect.com/science/article/abs/pii/S0966636216307111?via%3Dihub)] ::: ### 보행분석 모델 서빙 > 웹 페이지를 통해 업로드 된 보행 비디오를 분석하는 Flask 기반 inference API를 구현하였습니다. - sqlalchemy의 `execute()` 를 사용하여 DB Insert 기능을 구현하였습니다. - 여러 요청이 동시에 들어왔을 때, 쓰레드 간의 race condition이 일어나지 않도록 `scoped_session()`을 사용하였습니다. Inference API는 다음과 구현하였습니다. (전체 코드는 [여기](https://github.com/youhs4554/gaitanalysis/blob/master/src/dev/demo/app.py)에서 확인할 수 있습니다.) ```python= from sqlalchemy.orm import scoped_session, sessionmaker @app.route('/api') def api_call(): ''' 병원정보 시스템(HIS) 웹 클라이언트에서 '분석' 버튼 클릭시 분석이 시작됩니다. (실시간 분석이 아닌, 업로드된 비디오파일을 분석) ''' # ... 중략 ... engine = VariableInterface.db._engine # 엔진으로는 Oracle DB를 사용하였습니다. session = scoped_session(sessionmaker( autocommit=False, autoflush=False, bind=engine)) sess = session() VariableInterface.sess = sess try: for n in range(len(VariableInterface.query_res.filepath.values)): VariableInterface.VIDEO_PATH = f'./demo/static/tmp_{n}.avi' # inference 서버에 업로드 된 비디오 파일입니다. # fetch video file! fetch_file(fileseq=n) # run model! run_model() # write result to DB! write_result() # callback of success, notifying task is done! success_callback() sess.commit() except BadRequestException: sess.rollback() return VariableInterface.BadResponse finally: sess.close() return VariableInterface.GreetingResponse ``` ### 보행분석 결과 조회 > Inference API를 통해 분석된 결과가 병원 정보 시스템에서 조회될 수 있도록 하였습니다. - **테이블 뷰** ![](https://i.imgur.com/ZJha0Z6.png) - **그래프 뷰** ![](https://i.imgur.com/NQ5opX4.png)