이미지 파일이 핫도그인지, 아닌지 판별하는 프로그램이다.
FastAPI를 사용하여 predict api를 생성한다.
$ pdm add fastapi uvicorn
$ pip install jinja2
1단계 : 큰 틀 만들기
[main.py]
서버에 접속하면 이미지 url을 지정해놓은 hotdog , coolcat 사진이 랜덤으로 출력된 화면을 볼 수 있다.
return 부분을 보면 "index.html" 파일에 request와 image_url 변수가 전달된다.
이 HTML 파일은 public 폴더 안에 있어야한다. 왜냐하면 html = Jinja2Templates(directory="public") 이기 때문에.
해당 파일에서 Jinja2 문법을 사용하여 변수를 출력할 수 있다.
from typing import Union
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from fastapi import Request
import random
app = FastAPI()
html = Jinja2Templates(directory="public")
@app.get("/hello")
def read_root():
return {"Hello👋": "hotdog🌭"}
@app.get("/")
async def home(request: Request):
hotdog = "https://www.dailiang.co.kr/news/photo/201111/34714_19009_5246.jpg"
coolcat = "https://image.fmkorea.com/files/attach/new3/20230527/486616/5032171247/5810153990/e5bc995f67dc592ba0121714373baf8c.jpeg"
image_url = random.choice([hotdog, coolcat])
return html.TemplateResponse("index.html",{"request":request, "image_url": image_url})
@app.get("/predict")
def hotdog():
return {"Hello": random.choice(["hotdog", "not hotdog"])}
[index.html]
inja2 템플릿 문법을 사용하여, 특정 조건에 따라 이미지 URL을 동적으로 표시하는 것 이다.
여기서 특정 조건이란 {% if image_url %} 부분이다. main.py에서 index.html로 image_url 변수를 넘겼다. 이 값이 존재할 때만 출력하겠다는 의미이다.
<H1>GIVE ME HOTDOG 👻</H1>
{% if image_url %}
<h2>Here is your image:</h2>
<img src="{{ image_url }}" width="300">
{% endif %}
[결과]
2 단계: 모델 적용하여 예측
[사용 모델]
julien-c/hotdog-not-hotdog · Hugging Face
🦀 NimaBoscarino/hotdog-gradio 👁 deepsynthbody/deepfake-ecg-generator 💻 zzz6519003/hot-girl-or-not 📚 Kinley95/kelly-hot-dog-classifier 📚 NimaBoscarino/hotdog-streamlit 👀 dukecsxu/hotdogclassifier
huggingface.co
먼저 프론트 부분인 index.html은 다음과 같이 구성했다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Classification</title>
<script>
async function uploadImage(event) {
event.preventDefault(); // 폼 제출 방지
// Get the file input element and the file selected by the user
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
// 이미지 미리보기 처리
if (file) {
const imageUrl = URL.createObjectURL(file);
const imagePreview = document.getElementById('imagePreview');
imagePreview.src = imageUrl;
imagePreview.style.display = 'block'; // 미리보기 이미지를 보이게 함
}
// 서버로 파일 업로드
const formData = new FormData(event.target);
const response = await fetch('/uploadfile/', {
method: 'POST',
body: formData
});
// 서버 응답 처리
const result = await response.json();
const label = result.label;
const p_value = result.p;
console.log('result:', JSON.stringify(result, null, 2));
console.log('label:' + label);
// 결과에 따라 이미지 변경
const p_result = document.getElementById('p_result');
p_result.style.display = 'block'; // 결과 이미지를 보이게 함
if (label === 'hot dog') {
p_result.src = "https://cdn.discordapp.com/attachments/1261185494759374900/1288761000039813140/image.png?ex=66f65bb6&is=66f50a36&hm=8b123739ef9158b60715573d54b762ab792163870399137b826e3f11f0406f5f&"; // 핫도그 이미지
} else {
p_result.src = "https://ifh.cc/g/0jy8zj.jpg"; // Not hotdog 이미지
}
// p 값을 형식에 맞게 화면에 출력
const p_display = document.getElementById('p_display');
let output = "";
p_value.forEach(item => {
output += `${item.label}: ${item.score}\n`;
});
p_display.textContent = output; // p 값을 보기 좋게 출력
}
</script>
</head>
<body>
<h1> GIVE ME HOTDOG 👻 </h1>
{% if image_url %}
<img id="resultImage" src="{{ image_url }}" width="300">
{% endif %}
<form id="uploadForm" onsubmit="uploadImage(event)">
<input type="file" name="file" accept="image/*" required>
<button type="submit">Upload</button>
</form>
<!-- Section where the selected image will be previewed -->
<div id="preview">
<h2>Here is your image:</h2>
<img id="imagePreview" src="" alt="No Image" style="display:none; width:300px;">
</div>
<!-- 이미지 분류 결과와 p 값을 표시할 섹션 -->
<div id="result-section">
<img id="p_result" src="" alt="No Image" style="display:none; width:1000px; position:absolute; top:50px; right:20px;">
<pre id="p_display"></pre> <!-- p 값을 출력할 곳 -->
</div>
</body>
</html>
첫 화면은 이렇게 생겼다.
파일을 선택하고 Upload를 클릭하면 사진이 Here is your image: 아래에 출력된다.
사진 출력과 동시에 백엔드 부분인 FASTAPI로 전달된다.
전달된 이미지는 모델에 적용되어 판별이 수행된다.
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
# 파일 저장
img = await file.read()
model = pipeline("image-classification", model="julien-c/hotdog-not-hotdog")
from PIL import Image
img = Image.open(io.BytesIO(img)) # 이미지 바이트를 PIL 이미지로 변환
p = model(img)
#label = get_max_label(p)
label = get_label(p)
return {"label": label, "p": p}
#return {"Tmp" : "tmp" }
get_label()은 다음과 같이 구현되었다.
p는 예시로 이렇게 생겼다. >[{'label': 'hot dog', 'score': 0.787665605545044}, {'label': 'not hot dog', 'score': 0.21233440935611725}]
점수가 더 높은 순서대로 나오기 때문에 만약 첫 번째 label이 not hot dog라면 바로 그 label을 리턴하도록 하였고,
hotdog인 경우에는
그 점수가 0.8 이상이면 최종적으로 핫도그로 판별하기로 하고, 아니라면 핫도그가 아닌 것으로 판별하고자 했다.
def get_label(p):
for item in p:
if item['label'] == 'not hot dog':
return item['label']
elif item['label'] == 'hot dog':
if item['score'] >= 0.8 :
return item['label']
else:
return 'not hot dog'
이렇게 리턴된 값은 index.html 코드의 uploadImage 부분으로 전달되어 처리된다.
다시 그 부분만 적어보자면 아래와 같다.
그리고 이미지 주소가 있는 부분은 리턴된 라벨값이 핫도그라면 원숭이가 핫도그를 서빙하는 사진이 출력될 것 이고
핫도그가 아니라면 절규짤이 출력될 것 이다 ㅋㅋ
<script>
async function uploadImage(event) {
event.preventDefault(); // 폼 제출 방지
// Get the file input element and the file selected by the user
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
// 이미지 미리보기 처리
if (file) {
const imageUrl = URL.createObjectURL(file);
const imagePreview = document.getElementById('imagePreview');
imagePreview.src = imageUrl;
imagePreview.style.display = 'block'; // 미리보기 이미지를 보이게 함
}
// 서버로 파일 업로드
const formData = new FormData(event.target);
const response = await fetch('/uploadfile/', {
method: 'POST',
body: formData
});
// 서버 응답 처리
const result = await response.json();
const label = result.label;
const p_value = result.p;
console.log('result:', JSON.stringify(result, null, 2));
console.log('label:' + label);
// 결과에 따라 이미지 변경
const p_result = document.getElementById('p_result');
p_result.style.display = 'block'; // 결과 이미지를 보이게 함
if (label === 'hot dog') {
p_result.src = "https://cdn.discordapp.com/attachments/1261185494759374900/1288761000039813140/image.png?ex=66f65bb6&is=66f50a36&hm=8b123739ef9158b60715573d54b762ab792163870399137b826e3f11f0406f5f&"; // 핫도그 이미지
} else {
p_result.src = "https://ifh.cc/g/0jy8zj.jpg"; // Not hotdog 이미지
}
// p 값을 형식에 맞게 화면에 출력
const p_display = document.getElementById('p_display');
let output = "";
p_value.forEach(item => {
output += `${item.label}: ${item.score}\n`;
});
p_display.textContent = output; // p 값을 보기 좋게 출력
}
</script>
[결과]
모델의 성능이 그다지 좋은 것 같지는 않다.
첫 번째 사진에서는 핫도그 사진을 0.94 점수로 핫도그로 판별했고
두 번째 사진의 떡볶이를 0.92의 점수로 핫도그로 판별했다 -_-^
세 번째 사진의 짜장면은 핫도그 점수가 더 높긴 하지만 0.8 이하이기 때문에 핫도그가 아니어서 절규한다.
마지막은 다행히 핫도그 점수가 더 낮았다.
오늘 한 내용 넘 재밌다
하지만 오래걸렸다...html 머에요-_-^
'playdata > daily' 카테고리의 다른 글
16주차 : Day 1 (10/21) (1) | 2024.10.21 |
---|---|
12주차 : Day 5 (9/27) (1) | 2024.09.30 |
12주차 : Day 3 (9/25) (1) | 2024.09.30 |
12주차 : Day 2 (9/24) (0) | 2024.09.30 |
12주차 : Day 1 (9/23) (2) | 2024.09.24 |