캡스톤 디자인 결과보고서

캡스톤 디자인 번호	2022-04
캡스톤 디자인 분야	딥러닝 기반 영상처리 앱
캡스톤 디자인 지도교수	노기섭 교수님
캡스톤 디자인 작품명	(국문) 알약서치 앱
	(영문) Pill search app
프로젝트 책임자	팀 명		불4조
	학 과		인공지능소프트웨어 학과
	성 명					학 번
	휴대폰					E-mail
프로젝트 세부현황	번 호	세부역할			성 명		전 공		학 번
	1	소스코드 작성
	2	소스코드 작성
	3	소스코드 작성
프로젝트 수행기간	2020년 3월 02일부터 2020년 06월 20일 까지(15주)
2020도 캡스톤 디자인 프로젝트를 성실히 수행하여 이에 결과보고서를 제출합니다. 2022 . 06. 15. 과제책임자 서명 인공지능소프트웨어 담당교수 귀하

1. 요약서

캡스톤디자인 번호	2022-04
캡스톤디자인 명	알약서치 앱
캡스톤디자인 책임자

○ 캡스톤디자인 목표

개인이 집에서 가지고있는 알약을 간편하게 휴대폰으로 찍어서 구분할 수 있는 어플

○ 캡스톤디자인 내용

알약을 구분하는 방법에는 모양과 문자, 마크등을 이용하여 구하는 방법이 있다,

휴대폰으로 찍은 알약사진을 영상처리와 학습 모델을 이용하여 모양, 색, 문자와 마크를 분류하고 알약의 효능과 복용방법을 알려줄 수 있도록한다.

○ 캡스톤디자인 개발에 따른 기대성과

약을 처방받고 복용후에 남은 약들을 가지도 있는 경우가 많을것이라 생각되는데 가지고 있는 약을 분류하는 어플을 만듬으로서 약의 오용 및 남용문제를 줄일수 있을것이라 생각된다.

캡스톤디자인 계획서

캡스톤디자인 명 : 알약서치 앱

캡스톤디자인 책임자 :

1. 캡스톤 디자인의 필요성

약을 처방받고 복용후에 남은 약들을 가지도 있는 경우가 많을것이라 생각되는데 가지고 있는 약을 분류하는 어플을 만듬으로서 약의 오용 및 남용문제를 줄일수 있을것이라 생각된다. 현대에 있어서 약의 종류가 많기 때문에 약의 모양,문자,마크 등을 보고 식별하기 어렵다 또한 눈이 좋지못한 사람들의 경우 작은 글씨의 약의 문자를 보고 약의종류를 판단하기 어렵다. 약의 종류를 알기 어려운 사람들을 위해 약의 사진을 찍어 약의 정보를 쉽게 제공해주는 앱을 만들고자 한다.

2. 캡스톤 디자인의 목표 및 내용

가. 캡스톤 디자인 목표

타이레놀	모양	색	마크	문자	복용법
	타원형	하얀색	X	TYLENOL500	1회 1~2정/1일 3~4회
	빠른 통증완화가 필요한 두통, 생리통, 치통에 효과적인 해열 진통제

약을 처방받을 때 봉투에 복용방법과 효능을 적어주는 것이 일반적인데 약을 복용한 후에 남은 약들의 경우에봉투도 같이 보관하는 경우는 드물다. 또 약을 처방받을 때 한 봉투 안에 여러종류의 약이 같이 들어있는데 자신이 진통제나 소화제 같이 필요한 약만 먹을 수 있도록 한다. 기존에 있던 알약을 구분해주는 어플들은 약의 모양, 색, 마크, 식별번호를 직접 알아보고 검색해야 알아볼 수 있는 반면에 이 어플은 휴대폰으로 사진을 찍는 것 만으로 간단하게 약의 정보를 알아볼 수 있다.

나. 캡스톤 디자인 내용

UI 이미지	휴대폰에 있는 카메라로 찍은 사진을 분석해 약의 정보를 받아들이고 무슨 효능을 가지고 있는지, 언제 먹어야 하는지 등을 알려준다. 가능하다면 알림으로 알려주는 등 여러 가지 기능을 추가할 예정이다. 완성 후에 국내에서 제일 수요가 많은 알약 10개로 실험해보고 제대로 작동하는지 알아본다.

3. 캡스톤 디자인의 추진전략 및 체계

1. 영상처리로 받아들인 알약 정보들을 학습시키고 학습 시킨 결과물을 모바일에 적용한다.

2. 알약 서치 앱을 개발하고, 개발이 완성되면 담당 교수님께 피드백을 받아서 추가적인 수정 및 보완을 한다.

3.PPT에 일부코드 및 시연영상을 보여주여 작품의 완성도를 전달한다.

4. 캡스톤 디자인 개발의 기대효과 및 활용방안

약을 처방받을 때 봉투에 복용방법과 효능을 적어주는 것이 일반적인데 약을 복용한 후에 남은 약들의 경우에봉투도 같이 보관하는 경우는 드물다. 또 약을 처방받을 때 한 봉투 안에 여러종류의 약이 같이 들어있는데 자신이 진통제나 소화제 같이 필요한 약만 먹을 수 있도록 한다. 기존에 있던 알약을 구분해주는 어플들은 약의 모양, 색, 마크, 식별번호를 직접 알아보고 검색해야 알아볼 수 있는 반면에 이 어플은 휴대폰으로 사진을 찍는 것 만으로 간단하게 약의 정보를 알아볼 수 있다. 따라서 이 앱을 통해 약의 정보를 몰라 곤란한 상황에 대한 해결방안이 될 것이다. 또한, 모바일 어플을 사용하여 사용자가 쉽게 접근가능하며 사용자의 편의성을 높인다

5. 작업분할 구조도(WBS, Work Breakdown Structure)

가. 캡스톤디자인 작업분할 구조도

7. 소요장비 및 재료

장비 및 재료 명	규격	수량	용 도	비 고
개인 PC	H/W	3	종합 작업
Chrome	S/W	3	정보 및 자료 검색
한컴오피스 한글 2010	S/W	3	문서 작성
Microsoft Power Point	S/W	3	PPT 제작 및 이미지 제작
스마트폰	H/W	3	팀원 간 연락 및 자료 공유
VSCode	S/W	3	소스 코드 개발
Groomide	S/W	3	소스 코드 개발
Yolo	S/W	1	객체 인식 및 자르기
Android studio	S/W	1	어플리케이션 개발

8. 캡스톤디자인 추진일정 계획

	세부 추진 내 용				추 진 일 정 (월)
					3	4	5	6
알약서치 앱	1	알약인식	Yolov5	알약 객체 인식
	2			알약 특징 추출
	3		딥러닝	알약 이미지 학습
	4			알약 이미지 테스트
	5	알약인식 앱 제작	앱 제작	서버,DB 앱 연동
	6			앱을 통한 알약인식
		:
		:
		:

9. 필요 기자재

물품명 및 가격	스펙 및 부품	주소
타이레놀정 (3000원)	해열, 진통, 소염제 제조업:한국얀센	의약품 인터넷 판매 제공 x
모드콜에스연질캡슐 (3000원)	해열, 진통, 소염제 제조업:종근당	의약품 인터넷 판매 제공 x
탁센 연질캡슐 (3000원)	해열, 진통, 소염제 제조업:녹십자	의약품 인터넷 판매 제공 x
텐텐츄정 (10개입 2500원)	영양제 제조업:한미업체	의약품 인터넷 판매 제공 x
게보린정 (3000원)	해열, 진통, 소염제 제조:삼진제약	의약품 인터넷 판매 제공 x
총액	14500원

캡스톤 디자인 결과보고서(結果報告書)

목  차

1. 시스템 모식도 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	15
2. 딥러닝 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	16
2.1. Imgaug 설치 및 데이터증강 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	16
2.1.1 Imgaug 설치 및 데이터증강∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	16
2.1.2 이미지 증강 소스코드∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	16
2.1.3. imgaug 경로지정 및 데이터 증강∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	19
2.1.4. imgaug 증강된 이미지 예시∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	20
2.2 Resnet34∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	21
2.2.1. groomide GPU 설정∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	21
2.2.2 resnet34 학습∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	22
2.2.3. resnet34 train 소스코드∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	23
2.3.4. resnet34 test 소스코드∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	26
2.3.5. model test∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	28
2.3.6. Flask로 모델 배포 및 테스트∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	29
3. YOLO ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	32
3.1. Roboflow를 활용한 데이터셋 제작∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	32
3.2. Yolov5 커스텀 데이터 학습 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	33
4. 안드로이드 앱 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	34
4.1 안드로이드 소스코드 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	34
4.2. 안드로이드 UI ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	37
5.0. 향후 추진 계획 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	39

1.0. 시스템 모식도

해당 프로젝트의 구성 요소를 크게 3가지로 나누자면, 딥러닝 모델 학습, 서버 구축, 앱 제작으로 나눌 수 있습니다.

딥러닝 모델 학습 과정은 학습에 필요한 데이터를 수집하고, 수집한 데이터를 증강 및 전처리 작업을 거쳐 모델을 학습합니다.

서버 구축 과정은 서비스를 구현하기 위해 데이터를 받고, 데이터의 전처리 및 분석하고 결과를 반환하는 기능이 필요하기에. 해당 기능을 구현하기 위해 서버 환경을 구축합니다.

앱 제작 과정은 서버로 데이터를 업로드하고, 서버로부터 분석된 결과를 받기 위한 과정으로, 앱UI를 구상하고, 서버로 데이터를 업로드하고, 분석된 결과를 출력하는 등의 필요한 기능을 개발합니다.

다음은 알약을 구분하는 앱의 서비스가 동작하는 과정을 순서대로 나열한 것입니다.

사용자가 알약을 구분하기 위해 앱을 사용할 때, 앱의 카메라로 알약의 외향의 사진을 찍은 후에 저장합니다.

저장한 사진을 서버로 보내어 yolov5로 인식된 객체부분만 잘라 전처리 작업과 전처리 작업을 통해 알약을 분류하기 쉽게 만들고, 학습된 딥러닝 모델을 통해 알약의 분류 작업을 진행합니다.

분류 작업이 끝난 후에 생긴 결과를 바탕으로 데이터베이스를 검색하여 해당하는 알약에 대한정보를  앱으로 출력하는 순으로 진행됩니다.

2.0. 딥러닝

2.1.1. Imgaug 설치 및 데이터증강

터미널 에서 pip을 이용한 패키지 설치

터미널에서 다음과 같은 명령어로 패키지를 추가한다.

-> pip install --no-dependencies imgaug

2.1.2. 이미지 증강 소스코드

imgaug.py
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110	importnumpy asnp importimgaug asia importimgaug.augmenters asiaa # random example images images =np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8) # Self.sometimes(0.5, ...) applies the given augmenter in 50% of all cases, # e.g. Self.sometimes(0.5, GaussianBlur(0.3)) would blur roughly every second image. class Img_aug: def __init__(self) : self.sometimes = lambda aug: iaa.Sometimes(0.5, aug) # Define our sequence of augmentation steps that will be applied to every image # All augmenters with per_channel=0.5 will sample one value _per image_ # in 50% of all cases. In all other cases they will sample new values # _per channel_. self.seq =iaa.Sequential( [ # apply the following augmenters to most images iaa.Fliplr(0.5), # horizontally flip 50% of all images iaa.Flipud(0.2), # vertically flip 20% of all images # crop images by -5% to 10% of their height/width self.sometimes(iaa.CropAndPad( percent=(-0.05, 0.1), pad_mode=ia.ALL, pad_cval=(0, 255) )), self.sometimes(iaa.Affine( scale={"x": (0.8, 1.2), "y": (0.8, 1.2)}, # scale images to 80-120% of their size, individually per axis translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)}, # translate by -20 to +20 percent (per axis) rotate=(-45, 45), # rotate by -45 to +45 degrees shear=(-16, 16), # shear by -16 to +16 degrees order=[0, 1], # use nearest neighbour or bilinear interpolation (fast) cval=(0, 255), # if mode is constant, use a cval between 0 and 255 mode=ia.ALL # use any of scikit-image's warping modes (see 2nd image from the top for examples) )), # execute 0 to 5 of the following (less important) augmenters per image # don't execute all of them, as that would often be way too strong iaa.SomeOf((0, 5), [ self.sometimes(iaa.Superpixels(p_replace=(0, 1.0), n_segments=(20, 200))), # convert images into their superpixel representation iaa.OneOf([ iaa.GaussianBlur((0, 3.0)), # blur images with a sigma between 0 and 3.0 iaa.AverageBlur(k=(2, 7)), # blur image using local means with kernel sizes between 2 and 7 iaa.MedianBlur(k=(3, 11)), # blur image using local medians with kernel sizes between 2 and 7 ]), iaa.Sharpen(alpha=(0, 1.0), lightness=(0.75, 1.5)), # sharpen images iaa.Emboss(alpha=(0, 1.0), strength=(0, 2.0)), # emboss images # search either for all edges or for directed edges, # blend the result with the original image using a blobby mask iaa.SimplexNoiseAlpha(iaa.OneOf([ iaa.EdgeDetect(alpha=(0.5, 1.0)), iaa.DirectedEdgeDetect(alpha=(0.5, 1.0), direction=(0.0, 1.0)), ])), iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5), # add gaussian noise to images iaa.OneOf([ iaa.Dropout((0.01, 0.1), per_channel=0.5), # randomly remove up to 10% of the pixels iaa.CoarseDropout((0.03, 0.15), size_percent=(0.02, 0.05), per_channel=0.2), ]), iaa.Invert(0.05, per_channel=True), # invert color channels iaa.Add((-10, 10), per_channel=0.5), # change brightness of images (by -10 to 10 of original value) iaa.AddToHueAndSaturation((-20, 20)), # change hue and saturation # either change the brightness of the whole image (self.sometimes # per channel) or change the brightness of subareas iaa.OneOf([ iaa.Multiply((0.5, 1.5), per_channel=0.5), iaa.FrequencyNoiseAlpha( exponent=(-4, 0), first=iaa.Multiply((0.5, 1.5), per_channel=True), second=iaa.LinearContrast((0.5, 2.0)) ) ]), iaa.LinearContrast((0.5, 2.0), per_channel=0.5), # improve or worsen the contrast iaa.Grayscale(alpha=(0.0, 1.0)), self.sometimes(iaa.ElasticTransformation(alpha=(0.5, 3.5), sigma=0.25)), # move pixels locally around (with random strengths) self.sometimes(iaa.PiecewiseAffine(scale=(0.01, 0.05))), # self.sometimes move parts of the image around self.sometimes(iaa.PerspectiveTransform(scale=(0.01, 0.1))) ], random_order=True ) ], random_order=True )

1) https://imgaug.readthedocs.io/en/latest/index.html (imgaug docs)

Imgaug docs에서 사용하려는 증강코드 함수명을 확인한 후에 sequensial 안에 선언해주어 사용이 가능하다 class로 선언하였기 떄문엔 사용하고자하는 python스크립트에서 간단하게 쓸수 있다

2.1.3. imgaug 경로지정 및 데이터 증강

Imgaug_path.py
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020	Import cv2 Import numpy as np From img_aug import Img_aug Import os a=1 aug =Img_aug() augment_num = 50 save_path = 'C:/Users/SD/Desktop/pill_aug/Mode_Cough_S/' file_llst = os.listdir('C:/Users/SD/Desktop/pill_img/Mode_Cough_S') forfile_name infile_llst: img =cv2.imread('C:/Users/SD/Desktop/pill_img/Mode_Cough_S/{}'.format(file_name)) images_aug =aug.seq.augment_images([img fori in range(augment_num)]) fornum,aug_img in enumerate(images_aug): cv2.imwrite(save_path+'Mode_Cough_S_{}.jpg'.format(a),aug_img) a+=1 print('Complete augmenting images')

7번 // augment_num

- augment_num 변수에 원본이미지 1장당 늘리고자하는 증강 개수를 정해준다

8번 // save_path

- 저장될 위치의 경로를 지정해준다

9~12번 // 원본이미지 읽기

- 경로에 있는 모든 원본이미지를 읽어 img변수에 넣어준다

13~14번 // 데이터 증강

- augment_num 변수에 지정해준 값으로 for문을 돌면서 img_aug.py 파일의 코드를 받아와 이미지데이터를 증강시켜준다

15~17번 // save_path 지정

- save_path에 지정된 경로로 Mode_Cough_S_{}.jpg 와 같이 이름을 정해준뒤 파일에 저장시켜준다

2.1.4. imgaug 증강된 이미지 예시

↓↓↓

2.2 Resnet34

2.2.1. groomide GPU 설정

클라우드 통합 개발환경으로 GPU(Tesla T4) 환경에서 개발 가능하다

https://ide.goorm.io/my/dashboard 로 접속하여 새 컨테이너를 생성한다

개발환경의 이름과 설명을 작성하고 GPU는 NVIDIA TESLA T4로 설정해주고 컨테이너 생성하기를 누른다

컨테이너가 생성되면 GPU환경 구성하기 버튼을 눌러 개발환경에 접속가능하다

하루 최대12시간 1주에 최대 30시간까지 GPU사용이 가능하다

2.2.2 resnet34 학습

Dataset 풀더안에 test,train 풀더를 작성하고 class별로 풀더를 작성해준뒤 이미지를 넣어준다

2.2.3. resnet34 train 소스코드

capstone_train.py
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092	import torch import torch.nn as nn import torch.optim as optim import os import torchvision from torchvision import datasets, models, transforms import numpy as np import time device = 'cuda' if torch.cuda.is_available() else 'cpu' # device 객체 print(device) # 데이터셋을 불러올 때 사용할 변형(transformation) 객체 정의 transforms_train = transforms.Compose([ transforms.Resize((224, 224)), # transforms.RandomHorizontalFlip(), # 데이터 증진(augmentation) transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 정규화(normalization) ]) transforms_test = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) data_dir = '/workspace/capstone/dataset' train_datasets = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'), transforms_train) test_datasets = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'), transforms_test) train_dataloader = torch.utils.data.DataLoader(train_datasets, batch_size=4, shuffle=True, num_workers=4) test_dataloader = torch.utils.data.DataLoader(test_datasets, batch_size=4, shuffle=True, num_workers=4) print('학습 데이터셋 크기:', len(train_datasets)) print('테스트 데이터셋 크기:', len(test_datasets)) class_names = train_datasets.classes print('클래스:', class_names) model = models.resnet34(pretrained=True) num_features = model.fc.in_features # 전이 학습(transfer learning): 모델의 출력 뉴런 수를 3개로 교체하여 마지막 레이어 다시 학습 model.fc = nn.Linear(num_features, 5) model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) num_epochs = 10 model.train() start_time = time.time() # 전체 반복(epoch) 수 만큼 반복하며 for epoch in range(num_epochs): running_loss = 0. running_corrects = 0 # 배치 단위로 학습 데이터 불러오기 for inputs, labels in train_dataloader: inputs = inputs.to(device) labels = labels.to(device) # 모델에 입력(forward)하고 결과 계산 optimizer.zero_grad() outputs = model(inputs) _, preds = torch.max(outputs, 1) loss = criterion(outputs, labels) # 역전파를 통해 기울기(gradient) 계산 및 학습 진행 loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) running_corrects += torch.sum(preds == labels.data) epoch_loss = running_loss / len(train_datasets) epoch_acc = running_corrects / len(train_datasets) * 100. # 학습 과정 중에 결과 출력 print('#{} Loss: {:.4f} Acc: {:.4f}% Time: {:.4f}s'.format(epoch, epoch_loss, epoch_acc, time.time() - start_time)) PATH = './pill_train.pth' torch.save(model.state_dict(),PATH)

1~9번 // 라이브러리

- 필요한 라이브러리를 import해준다 또한 torchvision에서 이미지를 전처리 할 수 있는 transforms을 사용한다

12_13번 // cuda

- cuda를 사용하는 코드를 작성한다 cuda를 사용하지 못할 경우 cpu로 전환된다

15~26번 // 데이터 전처리

- 이미지를 전처리합니다. transforms.Compose를 통해 Resize, ToTensor, Normalize를 시켜줍니다. 이미지의 높이와 너비가 조금씩 다를 수 있고, 이미지 데이터를 정규화 해주기위해 전처리를 진행한다.

ToTensor를 통해 이미지를 텐서형태로 바꿔줍니다.

28~30번 // 이미지 경로 지정

- train과 test를 ImageFolder를 사용하여 저장합니다. 경로는 클래스 폴더들이 들어있는 상위폴더로 경로를 지정한다

또한 앞서서 선언했던 trans를 transform 파라미터 값으로 넣어 데이터가 전처리 될 수 있도록 한다

32~33번 // dataloader

- DataLoader를 통해 train,test을 감싸주고 batch_size는 4개로 설정. shuffle을 사용하여 섞어주고 사용할 CPU core 갯수는 4개로 지정했다

35~39번 // 데이터 확인

- train,test,class_names 크기를 print함수를 이용하여 확인한다

41번 // resnet34모델 로드

- 사전학습된 resnet34를 불러온다

42~45번 // 마지막 레이어 교체

- 마지막 레이어를 우리가 해결하려는 문제에 맞게, 새로운 클래스 개수를 갖는 Linear 레이어를 만들어 대체하려고 하기 때문에 class 분류 개수인 5를 지정해준다

47~48번 // 손실함수와 옵티마이저

- CrossEntropyLoss 와  optimizer를 지정해준다

50번 // epochs

- num_epochs 몇 번 돌릴건지 지정해준다

51~81번 // 학습시작

- 학습을 시작하며 역전파를 사용하여 정확도를 높인다

83~84번 // 모델저장

- 학습을 끝낸 모델을 저장한다

2.3.4. resnet34 test 소스코드

capstone_test.py
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062	import torch import torch.nn as nn import torch.optim as optim import os import torchvision from torchvision import datasets, models, transforms from PIL import Image import numpy as np import time device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model = models.resnet34(pretrained=True) num_features = model.fc.in_features # 전이 학습(transfer learning): 모델의 출력 뉴런 수를 3개로 교체하여 마지막 레이어 다시 학습 model.fc = nn.Linear(num_features, 5) model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) PATH = '/workspace/capstone/pill_train.pth' model.load_state_dict(torch.load(PATH)) model.eval() start_time = time.time() transforms_train = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 정규화(normalization) ]) transforms_test = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) data_dir = '/workspace/capstone/dataset' train_datasets = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'), transforms_train) class_names = train_datasets.classes #print('클래스:', class_names) image = Image.open('/workspace/capstone/image/cold2.jpg') images = transforms_test(image).unsqueeze(0).to(device) with torch.no_grad(): outputs = model(images) _, preds = torch.max(outputs, 1) print(class_names[preds[0]])

1~52번 // 전처리 코드

- 이미지를 테스트 하기위해서 앞 코드와 같이 전처리를 진행해준다

22~23번 // 모델 불러오기

- 앞서서 학습시킨 모델을 불러온다

54~57번 // 이미지 테스트

- 모델에 돌려 가장 높은 확률의 class이름을 내보낸다

2.3.5. model test

테스트할 이미지의 경로를 지정한뒤에 capstone_test.py 를 실행한다

(테스트 이미지 :Cefalexin)

↓↓↓

(모델 추론 정답: Cefalexin)

2.3.6. Flask로 모델 배포 및 테스트

터미널에서 다음과 같은 명령어로 Flask를 설치한다.

-> pip install flask-ngrok

Flask-ngrok서비스를 이용하여 공인주소를 개방하여 외부에서 이미지를 보낼수 있게한다

app.py
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086	import io from flask import Flask, jsonify, request import torch import torch.nn as nn import torch.optim as optim import os import torchvision from torchvision import datasets, models, transforms from PIL import Image import numpy as np import time from flask_ngrok import run_with_ngrok device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model = models.resnet34(pretrained=True) num_features = model.fc.in_features # 전이 학습(transfer learning): 모델의 출력 뉴런 수를 3개로 교체하여 마지막 레이어 다시 학습 model.fc = nn.Linear(num_features, 5) model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) PATH = '/workspace/capstone/pill_train.pth' model.load_state_dict(torch.load(PATH)) model.eval() start_time = time.time() transforms_train = transforms.Compose([ transforms.Resize((224, 224)), #transforms.RandomHorizontalFlip(), # 데이터 증진(augmentation) transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 정규화(normalization) ]) transforms_test = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) data_dir = '/workspace/capstone/dataset' train_datasets = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'), transforms_train) class_names = train_datasets.classes #print('클래스:', class_names) def get_prediction(image_bytes): image = Image.open(io.BytesIO(image_bytes)) image = transforms_test(image).unsqueeze(0).to(device) with torch.no_grad(): outputs = model(image) _, preds = torch.max(outputs, 1) return class_names[preds[0]] app = Flask(__name__) @app.route('/', methods=['POST']) def predict(): if request.method == 'POST': # 이미지 바이트 데이터 받아오기 file = request.files['file'] image_bytes = file.read() # 분류 결과 확인 및 클라이언트에게 결과 반환 class_name = get_prediction(image_bytes=image_bytes) print("결과:", {'class_name': class_name}) return jsonify({'class_name': class_name}) run_with_ngrok(app) app.run()

57~65번 // 모델 예측 함수

- image_byte가 있을떄 이것을 이미지형태로 읽어와서 파이토치의 텐서로 바꾼다 그후 모델에 넣어 예측결과를  돌려주는 함수

71~81번 // 클라이언트에게 결과 반환

- POST 방식으로 root경로에 하나의 파일이 도착한 경우 읽어서 모델의 판단결과를 구한뒤에 클라이언트에게 돌려준다

Flask run 명령어를 사용하여 서버를 열어준다

공인주소를 확인한다

클라이언트에서 테스트 방법은 다음명령어를 통해 작성해준다

curl -X POST -F file=@{이미지 파일명} {Ngrok 서버 주소}

cmd에서 테스트할 이미지 파일명과 서버 주소를 작성하여 서버로 보내면 파일이 도착해 모델에 넣어준뒤 추론 결과를 반환해준다

3. Yolov5

3.1. Roboflow를 활용한 데이터셋 제작

무료 데이터셋 제작을 지원하는 https://app.roboflow.com/ 에 접속하여 로그인 후에 새 프로젝트 생성을 누른 후에 프로젝트 명과 타입 등을 설정해 새 프로젝트를 생성한다.

해당 프로젝트 안에 Upload를 통해 데이터셋을 만들기 위한 이미지 자료들을 업로드한다.

업로드 된 이미지들을 Annotate에서 업로드 된 이미지를 바운딩박스 처리하고 데이터셋에 추가시킨다.

추가된 이미지들을 Generate에서 데이터 셋을 제작한다

3.2. Yolov5 커스텀 데이터 학습

Yolov5_Custom_Train.ipynb
001 002 003 004 005 006 007 008 009	!git clone https://github.com/ultralytics/yolov5 %cd yolov5 !pip install –r requirements.txt !python train.py --data "/content/yolov5/capstone-3/data.yaml" --epochs 100 !python val.py --data "/content/yolov5/capstone-3/data.yaml" --weights "/content/yolov5/runs/train/exp/weights/yolov5s.pt“ !python detect.py --weights "/content/best.pt" --source "/content/yolov5/capstone-3/test/images" --save-crop

1~4번// Yolov5 학습 환경

-clone 명령어로 Yolov5 프로젝트를 로컬로 가져오고, pip install에 –r 옵션을 통해 필요 패키지 목록을 설치한다.

6번// 학습

-data.yaml을 통해 학습시킬 이미지의 위치를 지정하고 학습 횟수를 지정하여 학습을 진행한다.

8번// 검증

-학습된 데이터의 정확도를 확인하기 위해 검증하는 작업으로, 학습시키고 나온 가중치의 정확도를 검증한다.

9번// 검출

-학습 결과의 가중치와 검출한 이미지를 지정하여 객체 검출을 하고, --save-crop으로 검출한 이미지를 잘라 추출한다.

학습된 가중치는 content/yolov5/runs/train/exp/weights에 저장되며, 학습된 가중치로 검출한 이미지는 content/yolov5/runs/detect/exp에 저장된다.

4. 안드로이드 앱

4.1 안드로이드 소스코드

안드로이드 스튜디오 카메라

MainActivity.java

package org.pp.myapplication3;

import android.app.Activity;
import android.os.Bundle;

import com.google.android.material.bottomnavigation.BottomNavigationView;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;

import org.pp.myapplication3.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

private final String TAG = this.getClass().getSimpleName();
ImageView imageview;
private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

BottomNavigationView navView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);

setContentView(R.layout.fragment_dashboard);

imageview = findViewById(R.id.imageview);

imageview.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
launcher.launch(intent);
});
}
ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>()
{
@Override
public void onActivityResult(ActivityResult result)
{
if (result.getResultCode() == RESULT_OK)
{
Log.e(TAG, "result : " + result);
Intent intent = result.getData();
Log.e(TAG, "intent : " + intent);
Uri uri = intent.getData();
Log.e(TAG, "uri : " + uri);
imageview.setImageURI(uri);

}
}
});

}

mainactivity.java 전체코드입니다

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

BottomNavigationView navView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);

네비게이션 뷰를 구현한 코드입니다.

setContentView(R.layout.fragment_dashboard);

imageview = findViewById(R.id.imageview);

imageview.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
launcher.launch(intent);
});
}
ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>()
{
@Override
public void onActivityResult(ActivityResult result)
{
if (result.getResultCode() == RESULT_OK)
{
Log.e(TAG, "result : " + result);
Intent intent = result.getData();
Log.e(TAG, "intent : " + intent);
Uri uri = intent.getData();
Log.e(TAG, "uri : " + uri);
imageview.setImageURI(uri);

}

startactivityforresult함수가 작동하지 않는 관계로 대체할수 있는  registarForActivityResult를 사용하여 카메라를 구현하려 하였지만 오류로 인해 구현하지 못했습니다.

4.2. 안드로이드 UI

UI구상은 기본페에지, 카메라, 라이브러리로 구성되있습니다.

문제점.

가장 중요한 알약을 인식하는 부분의 디자인을 설정하지 않았다.

2. 디자인을 계속하여 개선하다 최근에서야 디자인을 다 잡았기 때문에 구현하지 못한 부분이 많습니다.

라이브러리

메인페이지. 아래의 네비게이션 바를 클릭시 해당하는 페이지로 이동합니다.

5.0. 향후 추진 계획

구상한 UI에 맞게 기능들을 추가하고, 분류 가능한 약을 추가하기 위해 더 많은 종류의 약을 학습시키는 것과 분류하는 데에 정확도를 향상 시키고, 해결하지 못한 사진을 전송하기 위한 카메라 및 전송 기능을 구현을 완성해야합니다.