본문 바로가기
카테고리 없음

두피 이미지 분류 시스템을 활용한 두피 진단 및 케어 추천 서비스

by zhsus 2023. 2. 24.
반응형

📒 작품 소개 ☆

 

헬스 케어와 관련된 주제로 AIhub에서 데이터 셋을 살펴보던 중, 유형별 두피 이미지 데이터 셋을 찾아볼 수 있었다.

 

아래 링크에서 데이터 셋을 확인해 볼 수 있는데, 

 

유형별 두피 이미지 데이터 셋

 

AI-Hub

샘플 데이터 ? ※샘플데이터는 데이터의 이해를 돕기 위해 별도로 가공하여 제공하는 정보로써 원본 데이터와 차이가 있을 수 있으며, 데이터에 따라서 민감한 정보는 일부 마스킹(*) 처리가 되

aihub.or.kr

 

이번 프로젝트는 이 데이터 셋을 활용하여 두피 증상에 대한 두피 유형을 찾아주는 인공지능 모델을 만들어 보고자 한다.

 

또한, 해당되는 유형에 맞는 맞춤형 두피 케어를 추천해 주는 기능을 추가하여 만든 모델 결과물을 활용한 서비스를 제작해 보았다.

 

위와 더해서 실시간으로 두피 영상을 촬영해서 증상 바로바로 확인할 수 있는 기능 또한 추가해보고자 한다.

 

https://drive.google.com/drive/folders/16w_G1dtGKbUNC5J1ic9c66D3OmZU2dHR?usp=share_link 

 

scalp_project - Google Drive

이 폴더에 파일이 없습니다.이 폴더에 파일을 추가하려면 로그인하세요.

drive.google.com


목차
1. 개발 환경
2. 목표
3. 나의 역할
4. 활동 내용
5. 활동 결과
6. 마무리

#3 두피 이미지 분류 시스템을 활용한 두피 진단 및 케어 추천 서비스

작품 기간 : 2022.10.17 ~ 2022.11.23 (약 5주 간)

1. 개발 환경

  • 활용한 모델 : EfficientNet B0 (EfficientNet B7), YOLOv7, YOLOv5
  • 개발 언어 : Python
  • 개발 도구 : Google Colab, labelImg, jupyter notebook

2. 목표

  • 사람들이 어렵게 병원을 찾지않아도 집에서 사진 한 장으로 자가진단을 통해 확인해 볼 수 있는 두피 진단 모델을 생성하고, 해당 두피 증상에 맞는 유형을 찾아서 두피 케어를 위한 제품을 추천해 주는 시스템을 만드는 것이다.
  • 또한, 실시간 영상 촬영을 통해 두피 증상을 바로 눈에 확인해 볼 수 있는 기능을 포함한다.

3. 나의 역할

  • 프로젝트 필요성을 살펴보기 위한 LDA 토픽 모델링 작업 수행
  • AIhub 유형별 두피 이미지 데이터 셋의 이해
  • 실시간 탐지 기능을 구현하기 위해 YOLO 모델 학습 데이터 라벨링 작업 수행
  • EfficientNet 모델 구조 이해
  • EfficientNet 모델 학습
  • 메타 데이터 분석 및 시각화
  • 웹 어플리케이션 제작

4. 활동 내용

>> 프로젝트 필요성을 살펴보기 위한 LDA 토픽 모델링 작업 수행

  • 두피 관련 문제로 사람들이 얼마나 관심을 가지는 지를 확인해 보기 위해 네이버 지식인에서 질문들을 크롤링하고, 관련 토픽을 찾아내보았다.
  • 네이버 지식인 페이지에서 두피를 검색하였을 때 올라온 질문들을 크롤링하였다.
  • 먼저, 네이버 지식인에 올라온 글의 제목만 따서 리스트에 저장해 준다.
  • ※ 크롬 브라우저로 실행하여 작업하였다. 야생 동물 모델에서 크롬 브라우저를 실행하기 위한 내용을 작성하였기 때문에 여기서는 작성된 코드 내용은 스킵하였다. 
import urllib.request
from bs4 import BeautifulSoup

title_lst = []

for page in range(1, 11) :
    # 두피를 검색하였을 때 나온 질문들이 페이지 당 10개씩 있다.
    # 10페이지의 제목을 크롤링하여 100개의 텍스트를 생성하였다.
    url = 'https://kin.naver.com/search/list.naver?query=%EB%91%90%ED%94%BC&page=' + str(cnt)
    html = urllib.request.urlopen(url).read()
    soup = BeautifulSoup(html, 'html.parser')
    
    # 한 페이지의 제목 글 저장
    title = soup.find_all(class_='_nclicks:kin.txt _searchListTitleAnchor')
    
    for i in title :
    	# href 속성에 있는 모든 값을 문자열로 리턴해준다.
    	title_lst.append(i.attrs['href'])
        
    time.sleep(3)
    
 print(len(title_lst))
  • 위에서 저장한 title_lst에 html 클래스로 묶인 제목 속성을 다시 제목 글만 분류하여 저장해주어야 한다.
    (print로 저장된 리스트를 확인해보자!!)
  • 제목 글을 가진 html 속성명은 c-heading__content와 title 두 개의 속성값이 있었다.
  • 또한, 속성에서 제목 텍스트를 가져왔을 때 개행 문자를 포함해서 가져오게 되는데,
    이를 제거해 주는 작업을 하여 새로운 리스트로 다시 저장시켜 주었다.
  • 코드는 아래와 같다.
txt_lst = []

for title_txt in title_lst :
	try :
    	html = requests.get(title_txt).content
        soup = BeautifulSoup(html, 'html.parser')
        txt = soup.find(class_ = 'c-heading__content').get_text()
        text = str(txt)
        text = title_txt.replace('\n', '')
        text = title_txt.replace('\t', '')
        txt_lst.append(text)
    
    except :
    	txt = soup.find(class_ = 'title').get_text()
        text = str(txt)
        text = title_txt.replace('\n', '')
        text = title_txt.replace('\t', '')
        txt_lst.append(text)
    
print(txt_lst)
  • 여기까지 네이버 지식인에서 질문들을 크롤링한 리스트를 csv 파일로 저장해준다.
import pandas as pd

df = pd.DataFrame({'Q' : txt_list})
df.to_csv('저장 경로/두피.csv', index=False)
  • 이제 저장된 두피.csv 파일을 LDA 토픽 모델링 작업을 진행해본다.

LDA analysis.ipynb
1.10MB

LDA 분석 코드 정리 및 주석 설명

 

  • LDA 내용을 다 작성하기에 글의 내용이 길어질 것 같아, LDA 수행과 워드 클라우드 내용은 위 파일로 기록하여 남긴다.

>> AIhub 유형별 두피 이미지 데이터 셋의 이해

  • 이 데이터 셋에 대한 정보는 미세각질, 피지과다, 모낭사이홍반, 모낭홍반/농포, 비듬, 탈모 6가지 증상에 대하여 4가지 중증도 단계로 나누어 놓은 이미지와 라벨 데이터가 포함되어져 있다.
  • 그리고 사람들의 두피에 대한 설문조사 내용인 메타 데이터도 함께 포함되어진다.
  • 이 프로젝트에서는 탈모를 제외한 5가지 증상을 활용할 것이다.
    (탈모를 제외하는 이유는 탈모의 경우는 이미 진행된 증상으로 개선하기 위한 케어 용품을 추천해주기 어려울 것으로 판단하여 제외하였다.)
  • 또한, 이 데이터 셋에서는 증상별로 두피 유형을 구분해주는 기준 표를 제시해주었다.
구분 미세각질 피지과다 모낭사이홍반 모낭홍반/농포 비듬
양호 - - - - -
건성 + - - - -
지성 - + - - -
민감성 +- - + - -
지루성 +- + + - +-
염증성 +- +- - + +-
비듬성 +- +- - - +
  • -는 미포함, +는 포함, +-는 미포함 또는 포함 을 의미한다.
  • 위의 표는 복합적으로 두피 증상을 포함하는 경우도 있기 때문에, +- 부분의 내용은 포함할 수도 있고, 아닐 수도 있다는 의미가 된다.
  • 이 표를 기준으로 아래와 같이 증상별로 두피 유형을 분류해주는 코드를 작성하였다.
if m1p == 0 and m2p == 0 and m3p == 0 and m4p == 0 and m5p == 0 :
        d1 = '정상입니다.'
        d_list.append(d1)
    elif m1p != 0 and m2p == 0 and m3p == 0 and m4p == 0 and m5p == 0 :
        d2 = '건성 두피입니다.'
        d_list.append(d2)
    elif m1p == 0 and m2p != 0 and m3p == 0 and m4p == 0 and m5p == 0 :
        d3 = '지성 두피입니다.' 
        d_list.append(d3)
    elif m2p == 0 and m3p != 0 and m4p == 0 and m5p == 0 :
        d4 = '민감성 두피입니다.'
        d_list.append(d4)
    elif m2p != 0 and m3p != 0 and m4p == 0 :
        d5 = '지루성 두피입니다.'
        d_list.append(d5)
    elif m3p == 0 and m4p != 0 :
        d6 = '염증성 두피입니다.'
        d_list.append(d6)
    elif m3p == 0 and m4p == 0 and m5p != 0 :
        d7 = '비듬성 두피입니다.'
        d_list.append(d7)
    else :
        d8 = '복합성 두피입니다.' 
        d_list.append(d8)
  • 이후에 설명하겠지만, EfficientNet 모델를 활용하여 각 증상마다 학습시킨 모델로 증상의 포함 여부를 판단하고,
    그에 맞는 두피 유형을 분류해줄 수 있게 된다.

>> 실시간 탐지 기능을 구현하기 위해 YOLO 모델 학습 데이터 라벨링 작업 수행

  • YOLO 모델의 경우, 두가지의 클래스로 통합하여 라벨링을 진행하였다. (비듬과 홍반)
  • 그 이유는, YOLO 모델은 비슷한 증상에 있어 구분하는 데 좋은 모델은 아니라고 생각하였다.
  • 그렇기 때문에, 비슷한 증상으로 미세각질과 피지과다는 비듬의 주 요인이므로 비듬으로 통합하고,
    모낭홍반/농포의 경우 초기 증상이 모낭사이홍반이므로 홍반으로 통합하여 라벨링 해주었다.

>> EfficientNet 모델 구조 이해

  • EfficientNet 모델의 구조를 간단하게 설명하면,
    모델의 Layer 층의 깊이와 필터의 개수, 입력 이미지의 해상도 이 3가지의 요소를 동시에 적절하게 스케일링하여 효율적인 모델을 만드는 것이다.
  • 결과적으로 기존의 모델들보다 더 가볍지만 정확도는 높은 모델이 만들어진 것이다.
  • 핵심적으로 알아야 할 것은 Compound Scaling 기법이었다.
    Compound Scaling 기법를 설명하기 전에
    1. 깊이(depth)를 깊게 만들어서 이미지의 특징을 더 잘 찾아낼 수 있게한다.
    2. 입력 이미지의 해상도를 넓혀서 큰 이미지일수록 정보를 많이 담고 있다.
    3. 큰 이미지일수록 필터 개수(width)를 증가시켜 포함한 정보를 잘 잡아내 줄 수 있게 한다.
  • 이 요소를 적절한 비율을 설정하여 비례식을 세워 최적의 값을 찾을 수 있게 하는 기법이다.
  • EfficientNet 모델의 자세한 설명은 아래 링크에서 확인해 볼 수 있다.
  • https://arxiv.org/abs/1905.11946
 

EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks

Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing n

arxiv.org

  • 5가지 증상을 각각 학습하여 돌려야하기 때문에 기존에 있던 모델로 돌려본다면 많이 무거울 것이라고 생각하였고,
    EfficientNet 모델을 활용한다면, 모델을 더 경량화할 수 있고 더 높은 정확도를 가질 수 있을 것이라고 판단하여 활용해보기로 하였다.

>> EfficientNet 모델 학습

  • EfficientNet 모델 코드는 아래 파일에 정리하여 설명하였다.

EfficeintNet.ipynb
0.43MB

efficientNet 모델 학습 코드 및

주석 설명

>> 메타 데이터 분석 및 시각화

  • 사람들이 설문 조사한 두피 관리 정보를 분석하여 두피 관리에 대한 실태 조사를 파악하고, 그래프로 확인하여 어떤 케어 제품을 추천해줄 수 있을지 분석해보았다.
  • 먼저, 메타 데이터의 압축을 해제한다.
!unzip /content/drive/MyDrive/META_DATA.zip
  • 압축을 해제하면 scalp_meta 폴더가 생성되는데, 이 폴더 안에 있는 모든 json 파일 이름을 리스트에 넣어준다.
# 데이터 확인 및 이동

from glob import glob

json_list = glob('/content/scalp_meta/*.json')
print(len(json_list))
  • 약 10만개의 파일이 존재하여 해당 파일을 반복문을 통해 읽어온 후, 데이터 프레임화 시켰다.
from tqdm import tqdm

import json
import pandas as pd

df = pd.read_json(json_list[0], orient ='index')

cnt = 0

for i in tqdm(json_list[70001:75001]):  
    df_concat = pd.read_json(i, orient ='index')
    if cnt == 0 :
        df_final = pd.concat([df, df_concat], axis=1)
    else :
        df_final = pd.concat([df_final, df_concat], axis=1)
    cnt+=1
  • 문제점 : 약 10만개의 파일을 한번에 처리하려고 시도했으나 너무 오랜 시간을 기다려야했다.
  • 해결방법 : 리스트의 슬라이싱을 통해 일정 개수만큼 데이터를 나누어 데이터 프레임을 생성하였다.
  • 나눈 데이터 프레임을 .csv 파일로 저장해주었다.
df.to_csv("meta_data_json_75000.csv",encoding = 'utf-8')
  • 메타 데이터에 대한 csv 파일을 모두 붙여준다.
import pandas as pd
from glob import glob

file_names = glob("/content/drive/MyDrive/*.csv") #폴더 내의 모든 csv파일 목록을 불러온다
total = pd.DataFrame() #빈 데이터프레임 하나를 생성한다

for file_name in file_names:
    temp = pd.read_csv(file_name, sep=',', encoding='utf-8') #csv파일을 하나씩 열어 임시 데이터프레임으로 생성한다
    total = pd.concat([total, temp]) #전체 데이터프레임에 추가하여 넣는다

total.to_csv("/content/drive/MyDrive/total.csv")
  • 이제부터 데이터 분석을 하기위해 한글 설정을 먼저 해주었다.
  • 아래는 나눔 폰트를 설치해주는 명령어들이다.
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
  • 설치가 다 끝났다면, 시각화할 때 한글이 적용되도록 하기 위해 초기 설정을 해주었다.
import matplotlib.pyplot as plt

plt.rc('font', family='NanumBarunGothic')
  • 데이터 분석을 통해 알아낸 정보는 아래 파일에서 확인해볼 수 있다.

EDA
7.15MB

데이터 분석 과정

 

  • 최종적으로 분석한 내용을 정리하면, 
    현재 사람들이 사용하고 있는 두피 모발용 제품에서는 샴푸가 압도적으로 많았으며, 트리트먼트와 헤어 에센스가 그 다음으로 높았다.
    또한, 맞춤 두피 케어 제품 사용에 대한 선호도는 약 85%로 매우 긍정적이었다.
    그래서 성별로 가장 많이 사용하는 샴푸를 구매할 때 중요하게 고려하는 부분을 분석해보았고, 이 점을 고려하여 두피 케어 제품을 조사할 수 있었다.

>> 웹 어플리케이션 제작

  • 웹 구현은 python의 flask를 활용하였다.
  • ngrok으로 flask 서버를 외부에서 접속 가능한 로컬 네트워크 환경을 구성하였다.
#설치, ngrok read
!pip install flask-ngrok > /dev/null 2>&1
!pip install pyngrok > /dev/null 2>&1
!ngrok authtoken 2Ey1rbDgPf8ySVvby295BDxNL5i_5pZq1md5x8WaHUGZkdjbh
!pip install efficientnet_pytorch
  • EfficientNet 모델을 웹 어플리케이션에 적용해보았다.
  • 아래 코드는 EfficientNet 모델로 5가지 증상을 학습시켜 나온 가중치 파일을 efficientnet-pytorch라이브러리를 활용해서 불러오는 코드이다.
# PATH
PATH1 = '/content/drive/MyDrive/scalp_project/scalp_weights/'+'aram_model1.pt'  # 모델1
PATH2 = '/content/drive/MyDrive/scalp_project/scalp_weights/'+'aram_model2.pt'  # 모델2
PATH3 = '/content/drive/MyDrive/scalp_project/scalp_weights/'+'aram_model3.pt'  # 모델3
PATH4 = '/content/drive/MyDrive/scalp_project/scalp_weights/'+'aram_model4.pt'  # 모델4
PATH5 = '/content/drive/MyDrive/scalp_project/scalp_weights/'+'aram_model5.pt'  # 모델5

# cuda
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# model load
model1 = torch.load(PATH1, map_location=device)
model2 = torch.load(PATH2, map_location=device)
model3 = torch.load(PATH3, map_location=device)
model4 = torch.load(PATH4, map_location=device)
model5 = torch.load(PATH5, map_location=device)

# 아웃풋, 로스, 프레딕, 아큐러시
# 모델 평가모드로 전환 # 평가모드와 학습모드의 layer 구성이 다르다
model1.eval() 
model2.eval()
model3.eval()
model4.eval()
model5.eval()
  • 두피 이미지 한 장을 입력으로 넣어 각 증상의 포함 여부를 판단해서 유형을 분류해주어야한다.
  • 이미지 파일 업로드하는 코드와 위에서 증상별로 유형을 분류해주는 코드를 결합시켜준다.
@app.route('/menu_res', methods=['POST'])
def menu_res():
    menu = {'home':0, 'menu':1, 'statistics':0, 'yolo':0}
    #########################################################################
    ## model.test code
    # test 이미지파일 전처리, 텐서화
    # 전처리-트랜스폼 규칙 선언 # model1_train 코드의 validation set 의 트랜스폼 규칙과 동일하게 
    transforms_test = transforms.Compose([  transforms.Resize([int(600), int(600)], interpolation=transforms.InterpolationMode.BOX),
                                            transforms.ToTensor(),
                                            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])    ])
                                       
    # root 경로 폴더 속 jpg를 전처리, 텐서화 (rood 속에 폴더를 하나 더 만들어서 jpg를 묶어야 함)
    testset = torchvision.datasets.ImageFolder(root = '/content/drive/MyDrive/scalp_project/static/upload' ,
                        transform = transforms_test)
    # DataLoader를 통해 네트워크에 올리기 
    testloader = DataLoader(testset, batch_size=2, shuffle=False, num_workers=0)
    # if __name__ == '__main__':
    with torch.no_grad(): # 평가할 땐  gradient를 backpropagation 하지 않기 때문에 no grad로 gradient 계산을 막아서 연산 속도를 높인다
        for data, target in testloader:                                   
            data, target  = data.to(device), target.to(device) 
            output1 = model1(data)   # model1에 데이터를 넣어서 아웃풋 > [a,b,c,d] 각 0,1,2,3 의 확률값 리턴 가장 큰 것이 pred
            output2 = model2(data) 
            output3 = model3(data) 
            output4 = model4(data) 
            output5 = model5(data) 

    # predict # # 0~3값만 뽑기 
    m1p = output1.argmax(dim=1, keepdim=True)[0][0].tolist()
    m2p = output2.argmax(dim=1, keepdim=True)[0][0].tolist()
    m3p = output3.argmax(dim=1, keepdim=True)[0][0].tolist()
    m4p = output4.argmax(dim=1, keepdim=True)[0][0].tolist()
    m5p = output5.argmax(dim=1, keepdim=True)[0][0].tolist()
    # 진단
    d_list = [] # 두피유형진단결과
    # 두피 유형 진단법

    if m1p == 0 and m2p == 0 and m3p == 0 and m4p == 0 and m5p == 0 :
        d1 = '정상입니다.'
        d_list.append(d1)
    elif m1p != 0 and m2p == 0 and m3p == 0 and m4p == 0 and m5p == 0 :
        d2 = '건성 두피입니다.'
        d_list.append(d2)
    elif m1p == 0 and m2p != 0 and m3p == 0 and m4p == 0 and m5p == 0 :
        d3 = '지성 두피입니다.' 
        d_list.append(d3)
    elif m2p == 0 and m3p != 0 and m4p == 0 and m5p == 0 :
        d4 = '민감성 두피입니다.'
        d_list.append(d4)
    elif m2p != 0 and m3p != 0 and m4p == 0 :
        d5 = '지루성 두피입니다.'
        d_list.append(d5)
    elif m3p == 0 and m4p != 0 :
        d6 = '염증성 두피입니다.'
        d_list.append(d6)
    elif m3p == 0 and m4p == 0 and m5p != 0 :
        d7 = '비듬성 두피입니다.'
        d_list.append(d7)
    else :
        d8 = '복합성 두피입니다.' 
        d_list.append(d8)
    #########################################################################
    ## Web Server Code
    # 모델 실행후 결과를 돌려줌
    final = d_list[0] # 두피유형판단
    result = {'미세각질':m1p, '피지과다':m2p,'모낭사이홍반':m3p,'모낭홍반농포':m4p,'비듬':m5p} # result=result
    final2 = '0:양호, 1:경증, 2:중등도, 3:중증' 

    filename = request.args['filename']  #html에서 넘겨준 파일네임을 리퀘스트로 불러오기

    mtime = int(os.stat(filename).st_mtime) # 업로드한 시간값불러오기 > 큐변경 > 화면갱신 4
    return render_template('menu_res.html',  final2=final2, final=final, menu=menu, result=result, 
                            m1p=m1p,m2p=m2p,m3p=m3p,m4p=m4p,m5p=m5p, mtime=mtime)
  • 이제 실시간 증상 탐지 모델로 학습시킨 YOLO 모델을 적용시켜본다.
  • YOLO 모델 학습의 경우, 다른 팀원이 역할을 맡아 수행해주었고,
    모델 결과를 바탕으로 YOLO 모델의 detect.py를 응용하여 웹에 연동시켜주었다.
  • 아래 코드는 구글 코랩 환경에서 실시간 촬영이 어려워 영상으로 대체하여 짜여진 코드이다.
@app.route('/yolo_result', methods=['POST'])
def yolo_result():
    menu = {'home':0, 'menu':0, 'statistics':0, 'yolo':1}
    # yolo model 불러오기
    SOURCE = '/content/drive/MyDrive/scalp_project/static/video/video/' + 'upload.mp4'
    WEIGHTS = './yolov5/runs/train/exp/weights/best.pt'
    IMG_SIZE = 640
    DEVICE = ''
    AUGMENT = False
    CONF_THRES = 0.25
    IOU_THRES = 0.45
    CLASSES = None
    AGNOSTIC_NMS = False
    hide_labels=False
    hide_conf=False
    source, weights, imgsz = SOURCE, WEIGHTS, IMG_SIZE
    line_thickness=3

    path = '/content/drive/MyDrive/scalp_project/static/video/video/output.mp4'
    cap = cv2.VideoCapture(source)
    fourcc = cv2.VideoWriter_fourcc(*'DIVX')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    
    out = cv2.VideoWriter(path, fourcc, fps, (int(width), int(height)))

    # Initialize
    device = select_device(DEVICE)
    half = device.type != 'cpu'  # half precision only supported on CUDA
    print('device:', device)

    # Load model
    dnn=False
    data='/content/drive/MyDrive/scalp_project/yolov5/MNHBNP_4_custom/data.yaml'
    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)  # load FP32 model
    stride, names, pt = model.stride, model.names, model.pt  # model stride
    imgsz = check_img_size(imgsz, s=stride)  # check img_size
    if half:
        model.half()  # to FP16

    # Get names and colors
    # names = model.module.names if hasattr(model, 'module') else model.names

    # Run inference
    if device.type != 'cpu':
        model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())))  # run once

    # Load image
    while True :
        ret, img0 = cap.read()
        # img0 = cv2.imread(source)  # BGR
        # assert img0 is not None, 'Image Not Found ' + source

        if ret :
            # Padded resize
            img = letterbox(img0, imgsz, stride=stride)[0]

            # Convert
            img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
            img = np.ascontiguousarray(img)

            img = torch.from_numpy(img).to(device)
            img = img.half() if half else img.float()  # uint8 to fp16/32
            img /= 255.0  # 0 - 255 to 0.0 - 1.0
            if img.ndimension() == 3:
                img = img.unsqueeze(0)

            # Inference
            t0 = smart_inference_mode()
            pred = model(img, augment=AUGMENT)[0]
            print('pred shape:', pred.shape)

            # Apply NMS
            pred = non_max_suppression(pred, CONF_THRES, IOU_THRES, classes=CLASSES, agnostic=AGNOSTIC_NMS)

            # Process detections
            det = pred[0]
            print('det shape:', det.shape)

            s = ''
            s += '%gx%g ' % img.shape[2:]  # print string
            annotator = Annotator(img0, line_width=line_thickness, example=str(names))
            if len(det):
                # Rescale boxes from img_size to img0 size
                det[:, :4] = scale_boxes(img.shape[2:], det[:, :4], img0.shape).round()

                # Print results
                for c in det[:, -1].unique():
                    n = (det[:, -1] == c).sum()  # detections per class
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "  # add to string

                # Write results
                for *xyxy, conf, cls in reversed(det):
                    c = int(cls)  # integer class
                    label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                    annotator.box_label(xyxy, label, color=colors(c, True))
                    # label = f'{names[int(cls)]} {conf:.2f}'
                    # annotator.box_label(xyxy, label, color=colors(c, True))
            img0 = annotator.result()
                # print(f'Inferencing and Processing Done. ({time.time() - t0:.3f}s)')
            # Stream results
            print(s)
            frame = cv2.flip(img0, 1)
            out.write(frame)
            cv2.waitKey(0)  # 1 millisecond
        else:
            break
    out.release()
    cap.release()
    return render_template('yolo_result.html', menu=menu, )
  • 모든 코드 내용은 아래 파일에서 확인해 볼 수 있다.

web.ipynb
0.03MB

web 어플리케이션 전체 소스 코드

 

 

Bootstrap

강력하고 확장 가능하며 기능이 풍부한 프론트엔드 툴킷. Sass로 빌드 및 커스터마이징하고, 사전 빌드된 그리드 시스템 및 구성 요소를 활용하고, 강력한 JavaScript 플러그인으로 프로젝트에 생기

getbootstrap.kr

  • 그리고 html에 대한 templates 폴더는 맨 위 구글 공유 드라이브에서 확인해볼 수 있다.

5. 활동 결과

  • EfficientNet 모델 결과 분석
모델 데이터 수 Epoch 정확도
Model1
미세각질
훈련 : 12,739 검증 : 3,639 15 63.3
Model2
피지과다
훈련 : 56,826 검증 : 16,236 5 63.7
Model3
모낭사이홍반
훈련 : 47,726 검증 : 13,635 7 78.3
Model4
모낭홍반/농포
훈련 : 3,750 검증 : 1,070 40 71.5
Model5
비듬
훈련 : 28,873 검증 : 8,248 10 73.2
  • review 에포크 횟수를 다르게 한 이유는 각 증상마다 정확도를 비슷하게 맞춰주기 위함이었다. 결과적으로 GPU 자원이 여유롭지 않아서 많은 시도는 하지 못했지만, 꽤 비슷한 정확도 결과를 얻을 수 있었다.
  • YOLO 모델 결과 분석
  YOLOv5 YOLOv7
데이터 개수 훈련 이미지 : 1,929개 훈련 이미지 : 1,929개
정확도 mAP0.5 : 0.741
mAP0.95 : 0.305
mAP0.5 : 0.893
mAP0.95 : 0.546
Epoch 100 100
Total Instances 34,410 개 34,410 개
Instance 개수 홍반 : 3,315 개
비듬 : 31,095 개
홍반 : 3,315 개
비듬 : 31,095 개
  • review : YOLOv7이 YOLOv5보다 더 좋은 정확도를 가진 결과를 확인해 볼 수 있다. 하지만, 여기서는 YOLOv5를 채택하였다. 그 이유는, 결과 분석 과정에서 사용 사례 & 사용자의 인식에 의해 결정되는 부분도 있기에 YOLOv5가 이 프로젝트에 맞게 적용할 수 있다고 생각하였기 때문이다.
  • 아래 영상에서 웹 동작 영상을 확인해 볼 수 있다.
웹 어플리케이션 동작 영상

6. 마무리

이번 프로젝트에서 수행한 내용이 많아서 모든 내용을 한 글에 다 작성하기 어려웠다. 

 

그래도 최대한 모든 내용을 기록하기 위해서 실제 코드 파일에 주석으로 설명을 달아서 내용을 정리하여 업로드 하였다.

 

그리고 이 프로젝트를 더 서비스화 시켜본다면, 두피 케어 제품을 통해 셀프로 관리하면서 두피 개선 과정을 기록한다. 

 

개발 블로그처럼 개발 경험을 기록에 남겨 다른 사람과 공유하듯이, 두피 개선을 위한 과정들을 다른 사람과 공유하고 새로운 고객이 들어올 때 비슷한 두피 유형의 기록을 추천해주는 서비스로 제공해주어도 좋을 것 같다. 🤔

반응형