mobileNet/废弃/原版KNN/knn-classifier.html

551 lines
17 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KNN 图像分类器 - TensorFlow.js</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/knn-classifier@latest"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.main-container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.grid-container {
grid-template-columns: 1fr;
}
}
.card {
background: white;
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.card h2 {
color: #333;
margin-bottom: 20px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.class-input {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
}
.class-input h3 {
color: #555;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.class-number {
background: #667eea;
color: white;
width: 25px;
height: 25px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
input[type="text"] {
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 5px;
margin-bottom: 10px;
font-size: 16px;
transition: border-color 0.3s;
}
input[type="text"]:focus {
outline: none;
border-color: #667eea;
}
input[type="file"] {
display: none;
}
.file-label {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
margin-right: 10px;
}
.file-label:hover {
background: #5a67d8;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
margin: 5px;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a67d8;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-success {
background: #48bb78;
color: white;
}
.btn-success:hover {
background: #38a169;
}
.btn-danger {
background: #f56565;
color: white;
}
.btn-danger:hover {
background: #e53e3e;
}
.btn:disabled {
background: #cbd5e0;
cursor: not-allowed;
transform: none;
}
#webcam-container {
position: relative;
width: 100%;
max-width: 640px;
margin: 20px auto;
}
#webcam {
width: 100%;
border-radius: 10px;
background: #000;
}
.samples-count {
display: inline-block;
background: #edf2f7;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
color: #4a5568;
margin-left: 5px;
}
.image-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
max-height: 150px;
overflow-y: auto;
}
.preview-img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 5px;
border: 2px solid #e0e0e0;
}
.status-message {
padding: 15px;
border-radius: 5px;
margin: 10px 0;
text-align: center;
font-weight: 500;
}
.status-success {
background: #c6f6d5;
color: #22543d;
border: 1px solid #9ae6b4;
}
.status-error {
background: #fed7d7;
color: #742a2a;
border: 1px solid #fc8181;
}
.status-info {
background: #bee3f8;
color: #2c5282;
border: 1px solid #90cdf4;
}
.button-group {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
.full-width {
grid-column: 1 / -1;
}
.prediction-results {
margin-top: 20px;
padding: 20px;
background: #f7fafc;
border-radius: 10px;
}
.prediction-item {
padding: 15px;
margin: 10px 0;
background: white;
border-radius: 8px;
border-left: 4px solid #667eea;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.prediction-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.prediction-label {
font-weight: 600;
color: #2d3748;
font-size: 16px;
}
.prediction-confidence {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
min-width: 60px;
text-align: center;
}
.confidence-bar-container {
width: 100%;
height: 24px;
background: #e2e8f0;
border-radius: 12px;
overflow: hidden;
position: relative;
}
.confidence-bar {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 12px;
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
min-width: 0;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
}
.confidence-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.confidence-bar.high {
background: linear-gradient(90deg, #48bb78, #38a169);
}
.confidence-bar.medium {
background: linear-gradient(90deg, #ed8936, #dd6b20);
}
.confidence-bar.low {
background: linear-gradient(90deg, #f56565, #e53e3e);
}
.confidence-percentage {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: white;
font-weight: 600;
font-size: 12px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
z-index: 1;
}
.top-tags {
margin: 20px 0;
padding: 15px;
background: #edf2fe;
border-radius: 10px;
}
.tag-item {
display: inline-block;
background: white;
padding: 5px 12px;
margin: 5px;
border-radius: 15px;
font-size: 14px;
border: 1px solid #cbd5e0;
}
.tag-weight {
color: #667eea;
font-weight: bold;
margin-left: 5px;
}
.k-selector {
margin: 15px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.k-selector label {
display: block;
margin-bottom: 10px;
color: #555;
font-weight: 500;
}
.k-value-display {
display: inline-block;
background: #667eea;
color: white;
padding: 2px 8px;
border-radius: 5px;
margin-left: 10px;
}
input[type="range"] {
width: 100%;
margin: 10px 0;
}
.model-info {
margin-top: 20px;
padding: 15px;
background: #f0f4f8;
border-radius: 8px;
font-size: 14px;
}
.info-item {
display: flex;
justify-content: space-between;
margin: 5px 0;
}
.info-label {
color: #718096;
}
.info-value {
color: #2d3748;
font-weight: 500;
}
</style>
</head>
<body>
<div class="main-container">
<h1>🤖 KNN 图像分类器(基于特征标签)</h1>
<div class="grid-container">
<!-- 数据采集卡片 -->
<div class="card">
<h2>📸 数据采集</h2>
<div class="class-input">
<h3><span class="class-number">1</span> 第一类</h3>
<input type="text" id="class1Name" placeholder="输入类别名称(如:猫)" value="类别1">
<label class="file-label" for="class1Images">
选择图片
</label>
<input type="file" id="class1Images" multiple accept="image/*">
<span class="samples-count" id="class1Count">0 张图片</span>
<button class="btn btn-primary" onclick="captureFromWebcam(0)">从摄像头采集</button>
<div class="image-preview" id="class1Preview"></div>
</div>
<div class="class-input">
<h3><span class="class-number">2</span> 第二类</h3>
<input type="text" id="class2Name" placeholder="输入类别名称(如:狗)" value="类别2">
<label class="file-label" for="class2Images">
选择图片
</label>
<input type="file" id="class2Images" multiple accept="image/*">
<span class="samples-count" id="class2Count">0 张图片</span>
<button class="btn btn-primary" onclick="captureFromWebcam(1)">从摄像头采集</button>
<div class="image-preview" id="class2Preview"></div>
</div>
<div class="class-input">
<h3><span class="class-number">3</span> 第三类(可选)</h3>
<input type="text" id="class3Name" placeholder="输入类别名称(可选)" value="">
<label class="file-label" for="class3Images">
选择图片
</label>
<input type="file" id="class3Images" multiple accept="image/*">
<span class="samples-count" id="class3Count">0 张图片</span>
<button class="btn btn-primary" onclick="captureFromWebcam(2)">从摄像头采集</button>
<div class="image-preview" id="class3Preview"></div>
</div>
<div class="button-group">
<button class="btn btn-success" id="addDataBtn">训练KNN模型</button>
<button class="btn btn-danger" id="clearDataBtn">清空数据</button>
</div>
<div id="dataStatus"></div>
</div>
<!-- KNN模型信息卡片 -->
<div class="card">
<h2>🎯 KNN 模型设置</h2>
<div class="k-selector">
<label>
K值最近邻数量
<span class="k-value-display" id="kValueDisplay">3</span>
</label>
<input type="range" id="kValue" min="1" max="20" value="3"
oninput="document.getElementById('kValueDisplay').textContent = this.value">
<small style="color: #718096;">K值越大预测越保守K值越小对局部特征越敏感</small>
</div>
<div class="k-selector">
<label>
滤波器系数 (α)
<span class="k-value-display" id="filterAlphaDisplay">0.3</span>
</label>
<input type="range" id="filterAlpha" min="0.05" max="1.0" step="0.05" value="0.3"
oninput="document.getElementById('filterAlphaDisplay').textContent = this.value">
<small style="color: #718096;">低通滤波器系数:值越小输出越平滑(0.1-0.3推荐),值越大响应越快</small>
</div>
<div class="top-tags" id="topTags">
<h3 style="margin-bottom: 10px;">📊 特征标签提取预览</h3>
<div id="tagsList">等待数据...</div>
</div>
<div class="model-info">
<h3 style="margin-bottom: 10px;"> 模型信息</h3>
<div class="info-item">
<span class="info-label">预训练模型:</span>
<span class="info-value">MobileNet v2</span>
</div>
<div class="info-item">
<span class="info-label">特征维度:</span>
<span class="info-value">1000个标签</span>
</div>
<div class="info-item">
<span class="info-label">分类器类型:</span>
<span class="info-value">K-最近邻 (KNN)</span>
</div>
<div class="info-item">
<span class="info-label">总样本数:</span>
<span class="info-value" id="totalSamples">0</span>
</div>
</div>
</div>
</div>
<!-- 预测卡片 -->
<div class="card full-width">
<h2>📹 实时预测</h2>
<div class="button-group">
<button class="btn btn-primary" id="startWebcamBtn">启动摄像头</button>
<button class="btn btn-danger" id="stopWebcamBtn" disabled>停止摄像头</button>
<button class="btn btn-success" id="saveModelBtn">保存模型</button>
<button class="btn btn-primary" id="loadModelBtn">加载模型</button>
</div>
<div id="webcam-container">
<video id="webcam" autoplay playsinline muted></video>
</div>
<div class="prediction-results" id="predictionResults">
<h3>预测结果</h3>
<div id="predictions">等待预测...</div>
</div>
<div id="predictionStatus"></div>
</div>
</div>
<script src="knn-classifier.js"></script>
</body>
</html>