Photo_classifier / index.html
sh5959's picture
Update index.html
a41925b verified
Raw History Blame Contribute Delete
11.6 kB
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๋‚ด ๋ฐ๋ชจ์˜ ์ œ๋ชฉ -->
<title>StreetCLIP ์‚ฌ์ง„์œผ๋กœ ๊ตญ๊ฐ€ ๋งžํžˆ๊ธฐ</title>
<style>
body {
font-family: "๋ง‘์€ ๊ณ ๋”•", sans-serif;
max-width: 720px;
margin: 24px auto;
padding: 0 16px;
color: #202124;
}
h1 { font-size: 24px; margin-bottom: 4px; }
.sub { color: #5f6368; margin-top: 0; }
#status {
display: block;
margin: 12px 0;
padding: 8px 12px;
background: #f1f3f4;
border-radius: 6px;
}
#preview {
max-width: 100%;
max-height: 320px;
display: none;
margin: 12px 0;
border-radius: 6px;
}
.row {
display: flex;
align-items: center;
gap: 8px;
margin: 4px 0;
}
.name {
width: 90px;
flex-shrink: 0;
font-size: 14px;
}
.track {
flex: 1;
background: #e8edf5;
border-radius: 3px;
}
/* โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๋ง‰๋Œ€ ์ƒ‰์ƒ์„ ํŒŒ๋ž€์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ */
.bar {
height: 14px;
background: #2563eb;
border-radius: 3px;
}
.score { font-size: 13px; color: #5f6368; }
button { padding: 6px 14px; }
</style>
</head>
<body>
<!-- โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๊ตญ๊ฐ€ ํ›„๋ณด๋ฅผ ๋น„๊ตํ•˜๋Š” ๋ฐ๋ชจ๋กœ ๋ณ€๊ฒฝ -->
<h1>์‚ฌ์ง„์œผ๋กœ ๊ตญ๊ฐ€ ๋งžํžˆ๊ธฐ</h1>
<p class="sub">
๊ฑฐ๋ฆฌยทํ’๊ฒฝ ์‚ฌ์ง„์„ ์˜ฌ๋ฆฌ๊ณ , ์–ด๋А ๋‚˜๋ผ์™€ ๊ฐ€์žฅ ๋น„์Šทํ•œ์ง€ ํ™•์ธํ•ด ๋ณด์„ธ์š”.
</p>
<p>101๊ฐœ ๊ตญ๊ฐ€ยท์ง€์—ญ์„ ๋น„๊ตํ•ด ์ƒ์œ„ 5๊ฐœ ํ›„๋ณด๋ฅผ ๋ณด์—ฌ ์ค๋‹ˆ๋‹ค.</p>
<p>
์ ์ˆ˜๋Š” ์ „์ฒด 101๊ฐœ ํ›„๋ณด ์‚ฌ์ด์˜ ์ƒ๋Œ€์ ์ธ ์˜ˆ์ธก ์ ์ˆ˜์ด๋ฉฐ,
์ƒ์œ„ 5๊ฐœ์˜ ํ•ฉ์€ 100%๊ฐ€ ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์‹ค์ œ ์ •๋‹ต๋ฅ ์ด๋‚˜ ์ •ํ™•ํ•œ ์ดฌ์˜ ์œ„์น˜๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.
</p>
<details>
<summary>๋น„๊ต ํ›„๋ณด 101๊ฐœ ๋ณด๊ธฐ</summary>
<p id="countryList"></p>
</details>
<p class="sub">
๋…ผ๋ฌธ ๋ถ€๋ก์— ๋‚˜์—ด๋œ 92๊ฐœ ๊ตญ๊ฐ€ยท์ง€์—ญ์— 9๊ฐœ ๊ตญ๊ฐ€๋ฅผ ์ถ”๊ฐ€ํ•œ ๋น„๊ต ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค.
์ •ํ™•ํ•œ ํ•™์Šต ๊ตญ๊ฐ€ ๋ชฉ๋ก์€ ์•„๋‹ˆ๋ฉฐ,
ํ›„๋ณด ๋ฐ– ์ง€์—ญ์˜ ์‚ฌ์ง„๋„ ์ด ์ค‘ ํ•˜๋‚˜๋กœ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</p>
<!-- โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๋ชจ๋ธ ์ค€๋น„ ์ „์—๋Š” ์ž…๋ ฅ์„ ๋น„ํ™œ์„ฑํ™” -->
<input
id="upload"
type="file"
accept="image/*"
aria-label="๋ถ„๋ฅ˜ํ•  ์‚ฌ์ง„ ์„ ํƒ"
disabled
/>
<button id="example" disabled>์˜ˆ์‹œ ๊ฑฐ๋ฆฌ ์‚ฌ์ง„์œผ๋กœ ํ•ด ๋ณด๊ธฐ</button>
<span id="status" role="status">
๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
์ฒ˜์Œ์—๋Š” ์•ฝ 434MB์˜ ๋ชจ๋ธ์„ ๋ฐ›์•„ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</span>
<img id="preview" alt="๋ถ„๋ฅ˜ํ•  ์‚ฌ์ง„" />
<div id="result"></div>
<p class="sub">
์˜ˆ์‹œ ์‚ฌ์ง„: ๋ชจ๋ธ ์ œ์ž‘์ž๊ฐ€ ๊ณต๊ฐœํ•œ ์ƒŒํ”„๋ž€์‹œ์Šค์ฝ” ์‚ฌ์ง„.
๊ฒฐ๊ณผ๋Š” ์‹คํ–‰ ํ›„ ํ™•์ธํ•˜์„ธ์š”.
</p>
<p class="sub">
๋ชจ๋ธ:
<a href="https://huggingface.co/onnx-community/StreetCLIP-ONNX">
StreetCLIP ONNX
</a>
ยท ์›๋ณธ:
<a href="https://huggingface.co/geolocal/StreetCLIP">
geolocal/StreetCLIP
</a>
ยท
<a href="https://creativecommons.org/licenses/by-nc/4.0/">
CC BY-NC 4.0
</a>
</p>
<script type="module">
import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.6";
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ์‚ฌ์ง„๊ณผ ๊ตญ๊ฐ€ ์ด๋ฆ„์„ ๋น„๊ตํ•˜๋Š” ๋ชจ๋ธ
const TASK = "zero-shot-image-classification";
const MODEL_NAME = "onnx-community/StreetCLIP-ONNX";
const EXAMPLE_URL = "https://huggingface.co/geolocal/StreetCLIP/resolve/main/sanfrancisco.jpeg";
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๋ชจ๋ธ์—๋Š” ์˜์–ด ์ด๋ฆ„, ํ™”๋ฉด์—๋Š” ํ•œ๊ตญ์–ด ์ด๋ฆ„ ์‚ฌ์šฉ
const countries = {
"Albania": "์•Œ๋ฐ”๋‹ˆ์•„",
"Andorra": "์•ˆ๋„๋ผ",
"Argentina": "์•„๋ฅดํ—จํ‹ฐ๋‚˜",
"Australia": "ํ˜ธ์ฃผ",
"Austria": "์˜ค์ŠคํŠธ๋ฆฌ์•„",
"Bangladesh": "๋ฐฉ๊ธ€๋ผ๋ฐ์‹œ",
"Belgium": "๋ฒจ๊ธฐ์—",
"Bermuda": "๋ฒ„๋ฎค๋‹ค",
"Bhutan": "๋ถ€ํƒ„",
"Bolivia": "๋ณผ๋ฆฌ๋น„์•„",
"Botswana": "๋ณด์ธ ์™€๋‚˜",
"Brazil": "๋ธŒ๋ผ์งˆ",
"Bulgaria": "๋ถˆ๊ฐ€๋ฆฌ์•„",
"Cambodia": "์บ„๋ณด๋””์•„",
"Canada": "์บ๋‚˜๋‹ค",
"Chile": "์น ๋ ˆ",
"China": "์ค‘๊ตญ",
"Colombia": "์ฝœ๋กฌ๋น„์•„",
"Croatia": "ํฌ๋กœ์•„ํ‹ฐ์•„",
"Czech Republic": "์ฒด์ฝ”",
"Denmark": "๋ด๋งˆํฌ",
"Dominican Republic": "๋„๋ฏธ๋‹ˆ์นด ๊ณตํ™”๊ตญ",
"Ecuador": "์—์ฝฐ๋„๋ฅด",
"Estonia": "์—์Šคํ† ๋‹ˆ์•„",
"Finland": "ํ•€๋ž€๋“œ",
"France": "ํ”„๋ž‘์Šค",
"Germany": "๋…์ผ",
"Ghana": "๊ฐ€๋‚˜",
"Greece": "๊ทธ๋ฆฌ์Šค",
"Greenland": "๊ทธ๋ฆฐ๋ž€๋“œ",
"Guam": "๊ดŒ",
"Guatemala": "๊ณผํ…Œ๋ง๋ผ",
"Hungary": "ํ—๊ฐ€๋ฆฌ",
"Iceland": "์•„์ด์Šฌ๋ž€๋“œ",
"India": "์ธ๋„",
"Indonesia": "์ธ๋„๋„ค์‹œ์•„",
"Ireland": "์•„์ผ๋žœ๋“œ",
"Israel": "์ด์Šค๋ผ์—˜",
"Italy": "์ดํƒˆ๋ฆฌ์•„",
"Japan": "์ผ๋ณธ",
"Jordan": "์š”๋ฅด๋‹จ",
"Kenya": "์ผ€๋ƒ",
"Kyrgyzstan": "ํ‚ค๋ฅด๊ธฐ์Šค์Šคํƒ„",
"Laos": "๋ผ์˜ค์Šค",
"Latvia": "๋ผํŠธ๋น„์•„",
"Lesotho": "๋ ˆ์†Œํ† ",
"Lithuania": "๋ฆฌํˆฌ์•„๋‹ˆ์•„",
"Luxembourg": "๋ฃฉ์…ˆ๋ถ€๋ฅดํฌ",
"North Macedonia": "๋ถ๋งˆ์ผ€๋„๋‹ˆ์•„",
"Madagascar": "๋งˆ๋‹ค๊ฐ€์Šค์นด๋ฅด",
"Malaysia": "๋ง๋ ˆ์ด์‹œ์•„",
"Malta": "๋ชฐํƒ€",
"Mexico": "๋ฉ•์‹œ์ฝ”",
"Monaco": "๋ชจ๋‚˜์ฝ”",
"Mongolia": "๋ชฝ๊ณจ",
"Montenegro": "๋ชฌํ…Œ๋„ค๊ทธ๋กœ",
"Netherlands": "๋„ค๋œ๋ž€๋“œ",
"New Zealand": "๋‰ด์งˆ๋žœ๋“œ",
"Nigeria": "๋‚˜์ด์ง€๋ฆฌ์•„",
"Norway": "๋…ธ๋ฅด์›จ์ด",
"Pakistan": "ํŒŒํ‚ค์Šคํƒ„",
"Palestine": "ํŒ”๋ ˆ์Šคํƒ€์ธ",
"Peru": "ํŽ˜๋ฃจ",
"Philippines": "ํ•„๋ฆฌํ•€",
"Poland": "ํด๋ž€๋“œ",
"Portugal": "ํฌ๋ฅดํˆฌ๊ฐˆ",
"Puerto Rico": "ํ‘ธ์—๋ฅดํ† ๋ฆฌ์ฝ”",
"Romania": "๋ฃจ๋งˆ๋‹ˆ์•„",
"Russia": "๋Ÿฌ์‹œ์•„",
"Rwanda": "๋ฅด์™„๋‹ค",
"Senegal": "์„ธ๋„ค๊ฐˆ",
"Serbia": "์„ธ๋ฅด๋น„์•„",
"Singapore": "์‹ฑ๊ฐ€ํฌ๋ฅด",
"Slovakia": "์Šฌ๋กœ๋ฐ”ํ‚ค์•„",
"Slovenia": "์Šฌ๋กœ๋ฒ ๋‹ˆ์•„",
"South Africa": "๋‚จ์•„ํ”„๋ฆฌ์นด ๊ณตํ™”๊ตญ",
"South Korea": "ํ•œ๊ตญ",
"Spain": "์ŠคํŽ˜์ธ",
"Sri Lanka": "์Šค๋ฆฌ๋ž‘์นด",
"Eswatini": "์—์Šค์™€ํ‹ฐ๋‹ˆ",
"Sweden": "์Šค์›จ๋ด",
"Switzerland": "์Šค์œ„์Šค",
"Taiwan": "๋Œ€๋งŒ",
"Thailand": "ํƒœ๊ตญ",
"Tunisia": "ํŠ€๋‹ˆ์ง€",
"Turkey": "ํŠ€๋ฅดํ‚ค์˜ˆ",
"Uganda": "์šฐ๊ฐ„๋‹ค",
"Ukraine": "์šฐํฌ๋ผ์ด๋‚˜",
"United Arab Emirates": "์•„๋ž์—๋ฏธ๋ฆฌํŠธ",
"United Kingdom": "์˜๊ตญ",
"United States": "๋ฏธ๊ตญ",
"Uruguay": "์šฐ๋ฃจ๊ณผ์ด",
"Vietnam": "๋ฒ ํŠธ๋‚จ",
"Nepal": "๋„คํŒ”",
"Kazakhstan": "์นด์žํ์Šคํƒ„",
"Costa Rica": "์ฝ”์Šคํƒ€๋ฆฌ์นด",
"Panama": "ํŒŒ๋‚˜๋งˆ",
"Morocco": "๋ชจ๋กœ์ฝ”",
"Egypt": "์ด์ง‘ํŠธ",
"Saudi Arabia": "์‚ฌ์šฐ๋””์•„๋ผ๋น„์•„",
"Tanzania": "ํƒ„์ž๋‹ˆ์•„"
};
const candidateLabels = Object.keys(countries);
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๊ตญ๊ฐ€ยท์ง€์—ญ 101๊ฐœ๋ฅผ ๋น„๊ตํ•˜๊ณ  ๊ฒฐ๊ณผ๋Š” ์ƒ์œ„ 5๊ฐœ๋งŒ ํ‘œ์‹œ
document.getElementById("countryList").textContent =
Object.values(countries).join(" ยท ");
const statusBox = document.getElementById("status");
const uploadInput = document.getElementById("upload");
const exampleButton = document.getElementById("example");
const previewImage = document.getElementById("preview");
const resultBox = document.getElementById("result");
let classifier = null;
let loadSeconds = "";
try {
const loadStart = performance.now();
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ์›๋ณธ๋ณด๋‹ค ์ž‘์€ q8 ONNX ํŒŒ์ผ ์‚ฌ์šฉ, ํŒŒ์ผ๋ณ„ ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ
classifier = await pipeline(TASK, MODEL_NAME, {
dtype: "q8",
progress_callback: (data) => {
if (data.status === "progress") {
statusBox.textContent =
`ํŒŒ์ผ ๋ฐ›๋Š” ์ค‘: ${data.file} ${Math.round(data.progress)}%`;
}
if (data.status === "done") {
statusBox.textContent =
"ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์™„๋ฃŒ ยท ๋ชจ๋ธ ์ค€๋น„ ์ค‘...";
}
}
});
loadSeconds =
((performance.now() - loadStart) / 1000).toFixed(1);
statusBox.textContent =
`์ค€๋น„ ์™„๋ฃŒ ยท ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ${loadSeconds}์ดˆ`;
uploadInput.disabled = false;
exampleButton.disabled = false;
} catch (error) {
statusBox.textContent =
`๋ชจ๋ธ์„ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ์ƒˆ๋กœ ๊ณ ์นจํ•˜์„ธ์š”. (${error.message})`;
}
async function classifyImage(imageUrl) {
if (classifier === null) return;
previewImage.src = imageUrl;
previewImage.style.display = "block";
resultBox.innerHTML = "";
statusBox.textContent =
"101๊ฐœ ๊ตญ๊ฐ€ยท์ง€์—ญ์„ ๋น„๊ตํ•˜๋Š” ์ค‘... ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.";
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๋ถ„๋ฅ˜ ์ค‘์—๋Š” ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€
uploadInput.disabled = true;
exampleButton.disabled = true;
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ๋ถ„๋ฅ˜ ์‹คํŒจ๋„ ํ™”๋ฉด์— ์•ˆ๋‚ด
try {
const start = performance.now();
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ์‚ฌ์ง„๊ณผ ํ•จ๊ป˜ ๋น„๊ตํ•  ๊ตญ๊ฐ€ ํ›„๋ณด๋ฅผ ์ „๋‹ฌ
const results = await classifier(imageUrl, candidateLabels, {
hypothesis_template: "A street view photo taken in {}."
});
const seconds =
((performance.now() - start) / 1000).toFixed(1);
statusBox.textContent =
`๋ถ„๋ฅ˜ ${seconds}์ดˆ ยท (๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ${loadSeconds}์ดˆ)`;
// ์ „์ฒด ํ›„๋ณด ์ ์ˆ˜๋ฅผ ์œ ์ง€ํ•œ ์ฑ„ ์ ์ˆ˜์ˆœ์œผ๋กœ ์ •๋ ฌํ•ด 5๊ฐœ๋งŒ ํ‘œ์‹œ
results.sort((a, b) => b.score - a.score);
results.slice(0, 5).forEach(({ label, score }) => {
resultBox.innerHTML += `
<div class="row">
<span class="name">${countries[label]}</span>
<div class="track">
<div
class="bar"
style="width:${(score * 100).toFixed(1)}%"
></div>
</div>
<span class="score">
${(score * 100).toFixed(1)}%
</span>
</div>`;
});
} catch (error) {
statusBox.textContent =
`๋ถ„๋ฅ˜ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ์ง„์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”. (${error.message})`;
}
uploadInput.disabled = false;
exampleButton.disabled = false;
}
exampleButton.addEventListener(
"click",
() => classifyImage(EXAMPLE_URL)
);
uploadInput.addEventListener("change", (event) => {
const file = event.target.files[0];
// ํŒŒ์ผ ์„ ํƒ์„ ์ทจ์†Œํ•˜๋ฉด ์ข…๋ฃŒ
if (!file) return;
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹Œ ํŒŒ์ผ์„ ๊ณ ๋ฅธ ๊ฒฝ์šฐ ์•ˆ๋‚ด
if (!file.type.startsWith("image/")) {
statusBox.textContent =
"JPG ๋˜๋Š” PNG ๊ฐ™์€ ์‚ฌ์ง„ ํŒŒ์ผ์„ ์„ ํƒํ•˜์„ธ์š”.";
return;
}
const reader = new FileReader();
reader.onload = (e) => classifyImage(e.target.result);
// โ˜… ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„: ํŒŒ์ผ ์ฝ๊ธฐ ์‹คํŒจ ์•ˆ๋‚ด
reader.onerror = () => {
statusBox.textContent =
"ํŒŒ์ผ์„ ์ฝ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์ง„์„ ๋‹ค์‹œ ์„ ํƒํ•˜์„ธ์š”.";
};
reader.readAsDataURL(file);
});
</script>
</body>
</html>