diff --git a/README.md b/README.md index 9301f3b..5f11be2 100644 --- a/README.md +++ b/README.md @@ -1 +1,313 @@ -# Frontend \ No newline at end of file +# 두피어나 두피어나 +한번의 터치로 두피를 진단하다. +

+ +## **🪮 프로젝트 개요 및 소개** +**두피어나**는 빗으로 두피 상태를 쉽고 빠르게 진단하고, 맞춤형 두피 케어 솔루션을 제공하는 AI 기반의 두피 분석 서비스입니다. **매일 사용하는 빗**을 활용하여 별도의 복잡한 과정 없이 매일 두피 상태를 확인할 수 있도록 도와주는 서비스입니다. + +두피어나 +
+ +--- + +## 💡 개발 배경 및 필요성 +### 1) 프로젝트 개발 동기 및 목적 +프로젝트 개발 동기 및 목적 + +- **건강보험심사평가원**의 조사에 따르면 국내 탈모 환자의 수는 2018년 22만 4,840명에서 2022년 24만 7,915명으로 지속적인 증가 추세로, 현대인들은 두피 염증, 비듬, 지루성 두피염 등 다양한 **두피 문제를 호소**하고 있습니다. +- 그러나 정확한 진단을 위해 병원을 방문해야 하는 등 번거로움과 비용, 시간적 제약으로 인해 꾸준한 관리가 어려운 현실입니다. +- 이러한 문제점을 해결하기 위해 **매일 하는 평범한 빗질**에 **하드웨어**와 **AI**를 결합해집에서도 손쉽게 두피 상태를 진단기록하고 개인 맞춤형 관리 솔루션을 제공하는 를 개발하게 되었습니다. + +### 2) 프로젝트 특·장점 +- 별도의 장비나 병원 방문 없이 두피 상태를 간편하게 진단이 가능합니다. +- 인공지능 분석으로 가성비 높은 맞춤형 두피 관리 솔루션을 제공합니다. +- 올리브영 등 쇼핑몰과 연동하여 최적의 케어 샴푸 등을 추천해줍니다. +- 모바일 앱으로 실시간 진단 결과 확인 및 두피 이력 관리가 가능합니다. + +--- + +## ⚙️ 주요 기능 +### 1) 🪮 SmartBrush를 활용한 두피 진단 +SmartBrush에 내장된 카메라와 센서로 두피 이미지를 촬영하고, AI가 **각질·유분·민감도·밀도·두께·탈모**를 종합적으로 분석하여 **양호, 경증, 중등도, 중증으로 진단**합니다. +이를 통해 사용자는 **가정에서도 자신의 두피 상태를 빠르고 간단하게 알 수 있습니다.** + + + + + +
+ Smartbrush + + 결과페이지 +
+ +### 2) 🧴 두피 MBTI별 제품 추천 +사용자의 두피 진단 결과를 한눈에 이해할 수 있도록 **두피 MBTI(트러블 폭풍형, 지성 민감형, 민감 건조형, 지성 비듬형, 건조 비듬형, 건조 트러블형, 깔끔 지성형, 밸런스형)로 유형을 분류**합니다. 분류된 MBTI 유형에 맞춰 샴푸, 린스, 트리트먼트/팩, 두피 토닉, 헤어 에센스를 **맞춤 추천**합니다. + + + + + +
+ 추천제품 + + 추천제품상세페이지 +
+ +### 3) ✅ 두피 관련 질문과 진단을 통한 습관 챌린지 +두피 관련 질문과 진단을 통해 사용자의 두피 상태와 생활 습관을 분석하고, 결과에 따라 **습관 챌린지**를 매일 다르게 추천합니다. 챌린지는 **LIFESTYLE**(수면·스트레스·활동 습관 개선), **SCALP**(세정·건조·스타일링 루틴), **NUTRITION**(영양 균형·섭취 가이드) 세 가지 카테고리로 구성되며, 하루에 각 2개씩 제공됩니다. + + + + + +
+ 습관챌린지 + + 질문페이지 +
+ +### 4) 📜 두피 월별 레포트 +사용자가 매일 SmartBrush로 진단한 누적 진단 데이터를 기반으로 월별 변화 추이를 시각화하여 보여줍니다. 사용자의 진단 데이터가 매일 저장되고 한 달 단위로 쌓이게 되면, **개인별 기준선이 형성**되어 **더 정확하고 신뢰도 높은 판단이 가능**해집니다. + + + + +
+ 월별레포트 +
+ +### 5) 🗓️ 진단 캘린더 +두피 분석 캘린더를 통해 사용자는 **월별 두피 상태 변화를 한눈에 확인**할 수 있습니다. 또한 원하는 날짜를 클릭하면 해당일의 진단 결과를 상세히 확인할 수 있습니다. + + + + + +
+ 달력 + + 달력_진단후 +
+ +--- + +## ✨ 기대효과 및 활용분야 +- **일상 속 두피 진단** : 빗 하나로 언제 어디서나 간편하게 두피 상태 확인이 가능합니다. +- **전문 서비스 대체** : 고가 장비 없이 정밀 진단 가능, 비용·시간 부담 해소됩니다. +- **산업 연계 확장** : 뷰티샵·클리닉·의료기관과 협업 가능한 플랫폼 확장이 가능합니다. +- **기업 연동 강화** : 쇼핑몰과 연계해 제품 추천 및 구매까지 연결이 가능합니다. +- **개인화 관리 지원** : 데이터 기반 맞춤 케어와 두피 관리 가이드 제공이 가능합니다. + +--- + +## 🛠️ 기술 스택 +- 프론트엔드
+[![My Skills](https://skillicons.dev/icons?i=typescript,tailwind)](https://skillicons.dev) + +- 백엔드
+[![My Skills](https://skillicons.dev/icons?i=java,spring,mysql)](https://skillicons.dev) + +- AI
+[![My Skills](https://skillicons.dev/icons?i=python,flask)](https://skillicons.dev) + +- 하드웨어
+[![My Skills](https://skillicons.dev/icons?i=arduino,cpp)](https://skillicons.dev) + +- 배포 및 관리
+[![My Skills](https://skillicons.dev/icons?i=aws,redis,s3)](https://skillicons.dev) + +--- + +## 👥 팀원 +| 김희원 | 남시윤 | 박효진 | 장다연 | +|:------:|:------:|:------:|:------:| +| || |
| +|
[@heeone1](https://github.com/heeone1)
|
[@nadomola](https://github.com/nadomola)
|
[@phjlia2430](https://github.com/phjlia2430)
|
[@noeyadd](https://github.com/noeyadd)
| + + + +--- +## 💡 시스템 구성도 +### 🔹 아키텍처 구조 +아키텍처 구조 + +### 🔹 하드웨어 구조 + + + + + + + + + +
+ 하드웨어 설계도 + + SmartBrush 내부 +
+ 하드웨어설계도 + + Smartbrush내부 +
+ +--- + +## 🎥 작품 소개 영상 +[![시연 영상 보기](https://img.youtube.com/vi/UiVeHTItPyk/0.jpg)](https://youtu.be/cVYWDxybcYY?si=ZxseDR4PNyukEOeK) + +--- +## 🖥️ 핵심 소스코드 +
+

UV 측정 및 이미지 자동 업로드

+ +```C++ +Adafruit_VEML6070 uvSensor = Adafruit_VEML6070(); +float uvEMA = 0.0f, meanUV = 0.0f, varUV = 0.0f; +uint16_t baseUV = 0; + +void calibrateUV() { + setIT(IT_8T); + uint32_t sum = 0; + for (int i = 0; i < CALI_SAMPLES; ++i) { + sum += uvSensor.readUV(); + delay(20); + } + baseUV = sum / CALI_SAMPLES; + uvEMA = baseUV; + meanUV = baseUV; + varUV = fmaxf(5.0f, (float)baseUV * 0.2f); +} + +String getUVState(uint16_t uv) { + float sigma = sqrtf(fmaxf(1.0f, varUV)); + float z = ((float)uv - (float)baseUV) / fmaxf(1.0f, sigma); + if (z <= 1.0f) return "건성"; + else if (z <= 2.5f) return "보통"; + else return "지성"; +} + +~~ + +void loop() { + uint16_t raw = uvSensor.readUV(); + float sigma = sqrtf(fmaxf(1.0f, varUV)); + float z = ((float)raw - (float)baseUV) / fmaxf(1.0f, sigma); + float deltaRise = (float)raw - uvEMA; + + if (armed && (z >= Z_ON || deltaRise >= DELTA_ON)) { + uint16_t medianUV; float burstEMA; + burstMeasure(medianUV, burstEMA); + String state = getUVState(medianUV); + sendUVToServer(medianUV, state); + captureAndUploadImage(); + armed = false; + lastTriggerMs = millis(); + } else { + sampleAmbientSlow(raw); + } + + delay(IDLE_POLL_MS); +} +``` +
+ + +

두피 진단 AI 코드

+ +```python +from flask import Flask, request, jsonify +import torch +from efficientnet_pytorch import EfficientNet +from torchvision import transforms +from PIL import Image +import numpy as np + +torch.serialization.add_safe_globals({'EfficientNet': EfficientNet}) + +app = Flask(__name__) + +# Load all models +model_paths = { + "미세각질": "model1_full.pt", + "탈모": "model2_full.pt", + "모낭사이홍반": "model3_full.pt", + "모낭홍반농포": "model4_full.pt", + "비듬": "model5_full.pt", + "피지과다": "model6_full.pt", + "모발밀도": "model7_full.pt" +} + +models = {} +for name, path in model_paths.items(): + m = torch.load(path, map_location=torch.device('cpu'), weights_only=False) + m.eval() + models[name] = m + +# Preprocessing +transform = transforms.Compose([ + transforms.Resize([600, 600]), + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225]) +]) + +@app.route("/ai", methods=["POST"]) +def ai(): + # image 필드로 여러 개 업로드 + files = request.files.getlist('image') + if not files: + return jsonify({'error': 'No image provided'}), 400 + + # 질환별 확률 누적 + sum_probs = {disease: None for disease in models.keys()} + valid_count = 0 + + with torch.inference_mode(): + for f in files: + try: + image = Image.open(f.stream).convert('RGB') + except Exception: + # 잘못된 이미지는 평균에서 제외 + continue + + x = transform(image).unsqueeze(0) # [1, 3, H, W] + + for disease, model in models.items(): + logits = model(x) # [1, C] + prob = torch.softmax(logits[0], dim=0).cpu().numpy() # [C] + + if sum_probs[disease] is None: + sum_probs[disease] = prob.copy() + else: + sum_probs[disease] += prob + + valid_count += 1 + + if valid_count == 0: + return jsonify({'error': 'All images were invalid'}), 400 + + # 평균 확률 → 최종 결과 + results = {} + for disease, s in sum_probs.items(): + mean_prob = s / float(valid_count) + pred = int(np.argmax(mean_prob)) + conf = float(mean_prob[pred]) + results[disease] = { + "class_index": pred, + "confidence": round(conf, 3) + } + + return jsonify({ + "count": valid_count, # 평균에 사용된 유효 이미지 수 + "results": results + }) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) + #app.run(host="0.0.0.0", port=8000) +``` + +
+