bram4627 commited on
Commit
d7c88b9
·
verified ·
1 Parent(s): ad239c3

Upload 7 files

Browse files
README.md CHANGED
@@ -1,10 +1,148 @@
1
- ---
2
- title: Face Recognition
3
- emoji: 🐠
4
- colorFrom: blue
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sistem Pengenalan Wajah Sederhana
2
+
3
+ Website sederhana untuk melakukan pengenalan wajah menggunakan Python, Flask, dan OpenCV.
4
+
5
+ ## Fitur
6
+
7
+ - **Registrasi Wajah**: Mendaftarkan wajah baru ke dalam database
8
+ - **Pengenalan Wajah**: Mengidentifikasi wajah yang sudah terdaftar secara real-time
9
+ - **Manajemen Data**: Melihat dan menghapus data wajah yang terdaftar
10
+ - **Interface Web**: Antarmuka web yang user-friendly dengan Bootstrap
11
+
12
+ ## Teknologi yang Digunakan
13
+
14
+ - Python 3.7+
15
+ - Flask (Web Framework)
16
+ - OpenCV (Computer Vision)
17
+ - NumPy (Numerical Computing)
18
+ - Bootstrap 5 (Frontend Framework)
19
+
20
+ ## Instalasi
21
+
22
+ 1. **Clone atau download proyek ini**
23
+
24
+ 2. **Install Python dependencies:**
25
+
26
+ ```bash
27
+ pip install -r requirements.txt
28
+ ```
29
+
30
+ 3. **Pastikan kamera laptop/PC Anda berfungsi**
31
+
32
+ ## Cara Menjalankan
33
+
34
+ 1. **Jalankan aplikasi:**
35
+
36
+ ```bash
37
+ python app.py
38
+ ```
39
+
40
+ 2. **Buka browser dan akses:**
41
+ ```
42
+ http://localhost:5000
43
+ ```
44
+
45
+ ## Cara Penggunaan
46
+
47
+ ### 1. Mendaftarkan Wajah Baru
48
+
49
+ - Klik tombol "Daftarkan Wajah Baru"
50
+ - Masukkan nama lengkap
51
+ - Posisikan wajah di depan kamera
52
+ - Klik "Ambil Foto" ketika wajah sudah terlihat jelas
53
+ - Pastikan hanya ada satu wajah dalam frame
54
+
55
+ ### 2. Pengenalan Wajah
56
+
57
+ - Klik tombol "Pengenalan Wajah"
58
+ - Sistem akan menampilkan video real-time dengan deteksi wajah
59
+ - Wajah yang dikenali akan ditampilkan dengan nama dan persentase akurasi
60
+ - Wajah yang tidak dikenali akan ditampilkan sebagai "Unknown"
61
+
62
+ ### 3. Mengelola Data Wajah
63
+
64
+ - Di halaman utama, Anda dapat melihat semua wajah yang terdaftar
65
+ - Klik tombol sampah untuk menghapus data wajah tertentu
66
+
67
+ ## Tips untuk Hasil Terbaik
68
+
69
+ ### Saat Registrasi:
70
+
71
+ - Gunakan pencahayaan yang baik dan merata
72
+ - Pastikan wajah menghadap langsung ke kamera
73
+ - Hindari bayangan pada wajah
74
+ - Jangan gunakan kacamata hitam atau topi
75
+ - Pastikan tidak ada orang lain di dalam frame
76
+
77
+ ### Saat Pengenalan:
78
+
79
+ - Jaga jarak yang tepat dari kamera
80
+ - Hindari gerakan yang terlalu cepat
81
+ - Pastikan pencahayaan konsisten dengan saat registrasi
82
+
83
+ ## Struktur Proyek
84
+
85
+ ```
86
+ Pengenalan Wajah/
87
+ ├── app.py # Aplikasi utama Flask
88
+ ├── requirements.txt # Dependencies Python
89
+ ├── README.md # Dokumentasi proyek
90
+ ├── templates/ # Template HTML
91
+ │ ├── index.html # Halaman utama
92
+ │ ├── register.html # Halaman registrasi
93
+ │ └── recognize.html # Halaman pengenalan
94
+ ├── static/ # File statis (CSS, JS, images)
95
+ └── face_data/ # Data wajah (dibuat otomatis)
96
+ ├── names.pkl # Daftar nama terdaftar
97
+ └── [nama_orang]/ # Folder untuk setiap orang
98
+ └── *.jpg # File gambar wajah
99
+ ```
100
+
101
+ ## Cara Kerja Sistem
102
+
103
+ 1. **Deteksi Wajah**: Menggunakan Haar Cascade Classifier dari OpenCV
104
+ 2. **Ekstraksi Fitur**: Menggunakan Local Binary Pattern Histogram (LBPH)
105
+ 3. **Pelatihan Model**: Model dilatih ulang setiap kali ada penambahan wajah baru
106
+ 4. **Pengenalan**: Sistem mencocokkan wajah dengan database yang ada
107
+
108
+ ## Troubleshooting
109
+
110
+ ### Kamera tidak berfungsi:
111
+
112
+ - Pastikan kamera tidak digunakan oleh aplikasi lain
113
+ - Coba restart aplikasi
114
+ - Periksa permission kamera di sistem operasi
115
+
116
+ ### Wajah tidak terdeteksi:
117
+
118
+ - Pastikan pencahayaan cukup
119
+ - Coba ubah posisi atau jarak dari kamera
120
+ - Pastikan wajah menghadap langsung ke kamera
121
+
122
+ ### Akurasi pengenalan rendah:
123
+
124
+ - Daftarkan lebih banyak foto untuk orang yang sama
125
+ - Pastikan kualitas foto registrasi baik
126
+ - Gunakan pencahayaan yang konsisten
127
+
128
+ ## Limitasi
129
+
130
+ - Sistem bekerja optimal dengan 1 wajah per frame
131
+ - Membutuhkan pencahayaan yang cukup
132
+ - Akurasi tergantung pada kualitas kamera dan kondisi pencahayaan
133
+ - Performa menurun dengan jumlah wajah terdaftar yang sangat banyak
134
+
135
+ ## Pengembangan Lanjutan
136
+
137
+ Sistem ini dapat dikembangkan lebih lanjut dengan:
138
+
139
+ - Database yang lebih robust (SQLite/PostgreSQL)
140
+ - Multiple foto per orang untuk akurasi yang lebih baik
141
+ - Logging aktivitas pengenalan
142
+ - API untuk integrasi dengan sistem lain
143
+ - Autentikasi dan authorization
144
+ - Deployment ke cloud platform
145
+
146
+ ## Lisensi
147
+
148
+ Proyek ini dibuat untuk tujuan edukasi dan penelitian.
app.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, Response, jsonify, redirect, url_for
2
+ import cv2
3
+ import os
4
+ import numpy as np
5
+ import pickle
6
+ import base64
7
+ from datetime import datetime
8
+
9
+ app = Flask(__name__)
10
+
11
+ # Konfigurasi
12
+ FACE_DATA_DIR = 'face_data'
13
+ FACE_CASCADE_PATH = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
14
+
15
+ # Pastikan direktori face_data ada
16
+ if not os.path.exists(FACE_DATA_DIR):
17
+ os.makedirs(FACE_DATA_DIR)
18
+
19
+ # Load face cascade
20
+ face_cascade = cv2.CascadeClassifier(FACE_CASCADE_PATH)
21
+
22
+ # Global variables
23
+ camera = None
24
+ face_recognizer = cv2.face.LBPHFaceRecognizer_create()
25
+ is_trained = False
26
+
27
+ def load_face_data():
28
+ """Load data wajah yang sudah disimpan"""
29
+ global is_trained
30
+ faces = []
31
+ labels = []
32
+ names = []
33
+
34
+ if os.path.exists(os.path.join(FACE_DATA_DIR, 'names.pkl')):
35
+ with open(os.path.join(FACE_DATA_DIR, 'names.pkl'), 'rb') as f:
36
+ names = pickle.load(f)
37
+
38
+ for idx, name in enumerate(names):
39
+ face_dir = os.path.join(FACE_DATA_DIR, name)
40
+ if os.path.exists(face_dir):
41
+ for filename in os.listdir(face_dir):
42
+ if filename.endswith('.jpg'):
43
+ img_path = os.path.join(face_dir, filename)
44
+ img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
45
+ faces.append(img)
46
+ labels.append(idx)
47
+
48
+ if faces:
49
+ face_recognizer.train(faces, np.array(labels))
50
+ is_trained = True
51
+ return names
52
+
53
+ return []
54
+
55
+ def get_camera():
56
+ """Inisialisasi kamera"""
57
+ global camera
58
+ if camera is None:
59
+ camera = cv2.VideoCapture(0)
60
+ return camera
61
+
62
+ @app.route('/')
63
+ def index():
64
+ """Halaman utama"""
65
+ names = load_face_data()
66
+ return render_template('index.html', registered_faces=names)
67
+
68
+ @app.route('/register')
69
+ def register():
70
+ """Halaman registrasi wajah baru"""
71
+ return render_template('register.html')
72
+
73
+ @app.route('/recognize')
74
+ def recognize():
75
+ """Halaman pengenalan wajah"""
76
+ return render_template('recognize.html')
77
+
78
+ @app.route('/video_feed')
79
+ def video_feed():
80
+ """Stream video untuk registrasi"""
81
+ def generate():
82
+ camera = get_camera()
83
+ while True:
84
+ success, frame = camera.read()
85
+ if not success:
86
+ break
87
+
88
+ # Convert frame ke format yang bisa dikirim
89
+ ret, buffer = cv2.imencode('.jpg', frame)
90
+ frame = buffer.tobytes()
91
+ yield (b'--frame\r\n'
92
+ b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
93
+
94
+ return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame')
95
+
96
+ @app.route('/recognition_feed')
97
+ def recognition_feed():
98
+ """Stream video untuk pengenalan wajah"""
99
+ def generate():
100
+ camera = get_camera()
101
+ names = load_face_data()
102
+
103
+ while True:
104
+ success, frame = camera.read()
105
+ if not success:
106
+ break
107
+
108
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
109
+ faces = face_cascade.detectMultiScale(gray, 1.3, 5)
110
+
111
+ for (x, y, w, h) in faces:
112
+ cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
113
+
114
+ if is_trained and names:
115
+ roi_gray = gray[y:y+h, x:x+w]
116
+ roi_gray = cv2.resize(roi_gray, (100, 100))
117
+
118
+ id_, confidence = face_recognizer.predict(roi_gray)
119
+
120
+ if confidence < 100: # Threshold untuk confidence
121
+ name = names[id_]
122
+ confidence_text = f"{name} ({round(100-confidence)}%)"
123
+ else:
124
+ confidence_text = "Unknown"
125
+
126
+ cv2.putText(frame, confidence_text, (x, y-10),
127
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
128
+
129
+ ret, buffer = cv2.imencode('.jpg', frame)
130
+ frame = buffer.tobytes()
131
+ yield (b'--frame\r\n'
132
+ b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
133
+
134
+ return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame')
135
+
136
+ @app.route('/capture_face', methods=['POST'])
137
+ def capture_face():
138
+ """Capture wajah untuk registrasi"""
139
+ name = request.json.get('name', '').strip()
140
+
141
+ if not name:
142
+ return jsonify({'error': 'Nama tidak boleh kosong'})
143
+
144
+ camera = get_camera()
145
+ success, frame = camera.read()
146
+
147
+ if not success:
148
+ return jsonify({'error': 'Gagal mengambil gambar dari kamera'})
149
+
150
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
151
+ faces = face_cascade.detectMultiScale(gray, 1.3, 5)
152
+
153
+ if len(faces) == 0:
154
+ return jsonify({'error': 'Tidak ada wajah yang terdeteksi'})
155
+
156
+ if len(faces) > 1:
157
+ return jsonify({'error': 'Terdeteksi lebih dari satu wajah'})
158
+
159
+ # Ambil wajah pertama
160
+ (x, y, w, h) = faces[0]
161
+ face_roi = gray[y:y+h, x:x+w]
162
+ face_roi = cv2.resize(face_roi, (100, 100))
163
+
164
+ # Buat direktori untuk nama ini
165
+ person_dir = os.path.join(FACE_DATA_DIR, name)
166
+ if not os.path.exists(person_dir):
167
+ os.makedirs(person_dir)
168
+
169
+ # Simpan gambar wajah
170
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
171
+ filename = f"{timestamp}.jpg"
172
+ cv2.imwrite(os.path.join(person_dir, filename), face_roi)
173
+
174
+ # Update daftar nama
175
+ names_file = os.path.join(FACE_DATA_DIR, 'names.pkl')
176
+ if os.path.exists(names_file):
177
+ with open(names_file, 'rb') as f:
178
+ names = pickle.load(f)
179
+ else:
180
+ names = []
181
+
182
+ if name not in names:
183
+ names.append(name)
184
+ with open(names_file, 'wb') as f:
185
+ pickle.dump(names, f)
186
+
187
+ # Retrain model
188
+ load_face_data()
189
+
190
+ return jsonify({'success': f'Wajah {name} berhasil didaftarkan'})
191
+
192
+ @app.route('/delete_face/<name>')
193
+ def delete_face(name):
194
+ """Hapus data wajah"""
195
+ person_dir = os.path.join(FACE_DATA_DIR, name)
196
+
197
+ if os.path.exists(person_dir):
198
+ # Hapus semua file di direktori
199
+ for filename in os.listdir(person_dir):
200
+ os.remove(os.path.join(person_dir, filename))
201
+ os.rmdir(person_dir)
202
+
203
+ # Update daftar nama
204
+ names_file = os.path.join(FACE_DATA_DIR, 'names.pkl')
205
+ if os.path.exists(names_file):
206
+ with open(names_file, 'rb') as f:
207
+ names = pickle.load(f)
208
+
209
+ if name in names:
210
+ names.remove(name)
211
+ with open(names_file, 'wb') as f:
212
+ pickle.dump(names, f)
213
+
214
+ # Retrain model
215
+ load_face_data()
216
+
217
+ return redirect(url_for('index'))
218
+
219
+ if __name__ == '__main__':
220
+ app.run(debug=True, host='0.0.0.0', port=5000)
face_data/names.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec0a6ccf9debf1c16781445c4b9106080d00478b0559469336db7c7b7b9711c8
3
+ size 5
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Flask==2.3.3
2
+ opencv-python==4.8.1.78
3
+ opencv-contrib-python==4.8.1.78
4
+ numpy==1.24.3
5
+ Pillow==10.0.1
6
+ fastapi
7
+ uvicorn[standard]
templates/index.html ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Sistem Pengenalan Wajah</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
9
+ <style>
10
+ body {
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
14
+ }
15
+ .card {
16
+ border-radius: 15px;
17
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
18
+ border: none;
19
+ }
20
+ .btn-custom {
21
+ border-radius: 25px;
22
+ padding: 12px 30px;
23
+ font-weight: 600;
24
+ text-transform: uppercase;
25
+ letter-spacing: 1px;
26
+ transition: all 0.3s ease;
27
+ }
28
+ .btn-custom:hover {
29
+ transform: translateY(-2px);
30
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
31
+ }
32
+ .face-list {
33
+ max-height: 300px;
34
+ overflow-y: auto;
35
+ }
36
+ .face-item {
37
+ background: #f8f9fa;
38
+ border-radius: 10px;
39
+ margin-bottom: 10px;
40
+ padding: 15px;
41
+ border-left: 4px solid #667eea;
42
+ }
43
+ .navbar-brand {
44
+ font-weight: bold;
45
+ font-size: 1.5rem;
46
+ }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <nav class="navbar navbar-expand-lg navbar-dark" style="background-color: rgba(0, 0, 0, 0.1)">
51
+ <div class="container">
52
+ <a class="navbar-brand" href="/">
53
+ <i class="fas fa-user-check me-2"></i>
54
+ Sistem Pengenalan Wajah
55
+ </a>
56
+ </div>
57
+ </nav>
58
+
59
+ <div class="container mt-5">
60
+ <div class="row justify-content-center">
61
+ <div class="col-md-8">
62
+ <div class="card">
63
+ <div class="card-header text-center bg-primary text-white">
64
+ <h2 class="mb-0">
65
+ <i class="fas fa-home me-2"></i>
66
+ Dashboard Utama
67
+ </h2>
68
+ </div>
69
+ <div class="card-body p-5">
70
+ <div class="text-center mb-4">
71
+ <p class="lead">Selamat datang di sistem pengenalan wajah sederhana!</p>
72
+ </div>
73
+
74
+ <div class="row mb-4">
75
+ <div class="col-md-6 mb-3">
76
+ <a href="/register" class="btn btn-success btn-custom w-100">
77
+ <i class="fas fa-user-plus me-2"></i>
78
+ Daftarkan Wajah Baru
79
+ </a>
80
+ </div>
81
+ <div class="col-md-6 mb-3">
82
+ <a href="/recognize" class="btn btn-primary btn-custom w-100">
83
+ <i class="fas fa-search me-2"></i>
84
+ Pengenalan Wajah
85
+ </a>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="mt-5">
90
+ <h4 class="mb-3">
91
+ <i class="fas fa-users me-2"></i>
92
+ Wajah Terdaftar ({{ registered_faces|length }})
93
+ </h4>
94
+
95
+ {% if registered_faces %}
96
+ <div class="face-list">
97
+ {% for name in registered_faces %}
98
+ <div class="face-item d-flex justify-content-between align-items-center">
99
+ <div>
100
+ <i class="fas fa-user me-2 text-primary"></i>
101
+ <strong>{{ name }}</strong>
102
+ </div>
103
+ <a href="/delete_face/{{ name }}" class="btn btn-danger btn-sm" onclick="return confirm('Apakah Anda yakin ingin menghapus {{ name }}?')">
104
+ <i class="fas fa-trash"></i>
105
+ </a>
106
+ </div>
107
+ {% endfor %}
108
+ </div>
109
+ {% else %}
110
+ <div class="alert alert-info">
111
+ <i class="fas fa-info-circle me-2"></i>
112
+ Belum ada wajah yang terdaftar. Silakan daftarkan wajah baru terlebih dahulu.
113
+ </div>
114
+ {% endif %}
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- <div class="row justify-content-center mt-4">
122
+ <div class="col-md-8">
123
+ <div class="card">
124
+ <div class="card-body">
125
+ <h5 class="card-title">
126
+ <i class="fas fa-info-circle me-2"></i>
127
+ Cara Penggunaan
128
+ </h5>
129
+ <ol>
130
+ <li><strong>Daftarkan Wajah Baru:</strong> Klik tombol "Daftarkan Wajah Baru" untuk menambahkan wajah ke database</li>
131
+ <li><strong>Pengenalan Wajah:</strong> Klik tombol "Pengenalan Wajah" untuk mengidentifikasi wajah yang sudah terdaftar</li>
132
+ <li><strong>Hapus Data:</strong> Gunakan tombol sampah untuk menghapus data wajah yang tidak diperlukan</li>
133
+ </ol>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div> -->
138
+ </div>
139
+
140
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
141
+ </body>
142
+ </html>
templates/recognize.html ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Pengenalan Wajah - Sistem Pengenalan Wajah</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
9
+ <style>
10
+ body {
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
14
+ }
15
+ .card {
16
+ border-radius: 15px;
17
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
18
+ border: none;
19
+ }
20
+ .video-container {
21
+ position: relative;
22
+ background: #000;
23
+ border-radius: 10px;
24
+ overflow: hidden;
25
+ }
26
+ #video {
27
+ width: 100%;
28
+ height: auto;
29
+ display: block;
30
+ }
31
+ .status-indicator {
32
+ position: absolute;
33
+ top: 10px;
34
+ right: 10px;
35
+ background: rgba(0, 0, 0, 0.7);
36
+ color: white;
37
+ padding: 5px 10px;
38
+ border-radius: 5px;
39
+ font-size: 0.9rem;
40
+ }
41
+ .recognition-info {
42
+ background: #f8f9fa;
43
+ border-radius: 10px;
44
+ padding: 20px;
45
+ margin-top: 20px;
46
+ }
47
+ .navbar-brand {
48
+ font-weight: bold;
49
+ font-size: 1.5rem;
50
+ }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <nav class="navbar navbar-expand-lg navbar-dark" style="background-color: rgba(0, 0, 0, 0.1)">
55
+ <div class="container">
56
+ <a class="navbar-brand" href="/">
57
+ <i class="fas fa-user-check me-2"></i>
58
+ Sistem Pengenalan Wajah
59
+ </a>
60
+ <a href="/" class="btn btn-outline-light">
61
+ <i class="fas fa-arrow-left me-2"></i>
62
+ Kembali
63
+ </a>
64
+ </div>
65
+ </nav>
66
+
67
+ <div class="container mt-5">
68
+ <div class="row justify-content-center">
69
+ <div class="col-md-10">
70
+ <div class="card">
71
+ <div class="card-header text-center bg-primary text-white">
72
+ <h2 class="mb-0">
73
+ <i class="fas fa-search me-2"></i>
74
+ Pengenalan Wajah Real-time
75
+ </h2>
76
+ </div>
77
+ <div class="card-body p-4">
78
+ <div class="row">
79
+ <div class="col-md-8">
80
+ <div class="video-container">
81
+ <img id="video" src="{{ url_for('recognition_feed') }}" alt="Recognition Stream" />
82
+ <div class="status-indicator">
83
+ <i class="fas fa-circle text-success me-2"></i>
84
+ Live Recognition
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <div class="col-md-4">
89
+ <div class="recognition-info">
90
+ <h5>
91
+ <i class="fas fa-info-circle me-2"></i>
92
+ Informasi Pengenalan
93
+ </h5>
94
+ <p>Sistem sedang melakukan pengenalan wajah secara real-time. Wajah yang terdeteksi akan ditampilkan dengan kotak biru dan nama orang (jika dikenali).</p>
95
+
96
+ <div class="mt-4">
97
+ <h6>
98
+ <i class="fas fa-eye me-2"></i>
99
+ Status Deteksi
100
+ </h6>
101
+ <div class="d-flex align-items-center mb-2">
102
+ <div class="badge bg-primary me-2">Kotak Biru</div>
103
+ <small>Wajah terdeteksi</small>
104
+ </div>
105
+ <div class="d-flex align-items-center mb-2">
106
+ <div class="badge bg-success me-2">Nama + %</div>
107
+ <small>Wajah dikenali</small>
108
+ </div>
109
+ <div class="d-flex align-items-center">
110
+ <div class="badge bg-warning me-2">Unknown</div>
111
+ <small>Wajah tidak dikenali</small>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <div class="alert alert-info mt-3">
117
+ <i class="fas fa-lightbulb me-2"></i>
118
+ <strong>Tips:</strong>
119
+ <ul class="mb-0 mt-2">
120
+ <li>Pastikan wajah menghadap kamera</li>
121
+ <li>Gunakan pencahayaan yang cukup</li>
122
+ <li>Jaga jarak yang tepat dari kamera</li>
123
+ <li>Hindari gerakan yang terlalu cepat</li>
124
+ </ul>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="row justify-content-center mt-4">
134
+ <div class="col-md-10">
135
+ <div class="card">
136
+ <!-- <div class="card-body">
137
+ <h5 class="card-title">
138
+ <i class="fas fa-chart-line me-2"></i>
139
+ Tingkat Akurasi
140
+ </h5>
141
+ <p>Persentase yang ditampilkan menunjukkan tingkat kepercayaan sistem terhadap pengenalan wajah:</p>
142
+ <div class="row">
143
+ <div class="col-md-4">
144
+ <div class="text-center p-3 bg-success text-white rounded">
145
+ <h4>90-100%</h4>
146
+ <small>Sangat Yakin</small>
147
+ </div>
148
+ </div>
149
+ <div class="col-md-4">
150
+ <div class="text-center p-3 bg-warning text-dark rounded">
151
+ <h4>70-89%</h4>
152
+ <small>Cukup Yakin</small>
153
+ </div>
154
+ </div>
155
+ <div class="col-md-4">
156
+ <div class="text-center p-3 bg-danger text-white rounded">
157
+ <h4>&lt;70%</h4>
158
+ <small>Tidak Yakin</small>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div> -->
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+
168
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
169
+ <script>
170
+ // Reload video stream jika ada error
171
+ document.getElementById("video").addEventListener("error", function () {
172
+ setTimeout(() => {
173
+ this.src = this.src + "?t=" + new Date().getTime();
174
+ }, 1000);
175
+ });
176
+
177
+ // Update timestamp setiap 30 detik untuk menjaga koneksi
178
+ setInterval(() => {
179
+ const video = document.getElementById("video");
180
+ const currentSrc = video.src.split("?")[0];
181
+ video.src = currentSrc + "?t=" + new Date().getTime();
182
+ }, 30000);
183
+ </script>
184
+ </body>
185
+ </html>
templates/register.html ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Daftarkan Wajah Baru - Sistem Pengenalan Wajah</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
9
+ <style>
10
+ body {
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
14
+ }
15
+ .card {
16
+ border-radius: 15px;
17
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
18
+ border: none;
19
+ }
20
+ .btn-custom {
21
+ border-radius: 25px;
22
+ padding: 12px 30px;
23
+ font-weight: 600;
24
+ text-transform: uppercase;
25
+ letter-spacing: 1px;
26
+ transition: all 0.3s ease;
27
+ }
28
+ .btn-custom:hover {
29
+ transform: translateY(-2px);
30
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
31
+ }
32
+ .video-container {
33
+ position: relative;
34
+ background: #000;
35
+ border-radius: 10px;
36
+ overflow: hidden;
37
+ }
38
+ #video {
39
+ width: 100%;
40
+ height: auto;
41
+ display: block;
42
+ }
43
+ .form-control {
44
+ border-radius: 10px;
45
+ padding: 12px 15px;
46
+ }
47
+ .navbar-brand {
48
+ font-weight: bold;
49
+ font-size: 1.5rem;
50
+ }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <nav class="navbar navbar-expand-lg navbar-dark" style="background-color: rgba(0, 0, 0, 0.1)">
55
+ <div class="container">
56
+ <a class="navbar-brand" href="/">
57
+ <i class="fas fa-user-check me-2"></i>
58
+ Sistem Pengenalan Wajah
59
+ </a>
60
+ <a href="/" class="btn btn-outline-light">
61
+ <i class="fas fa-arrow-left me-2"></i>
62
+ Kembali
63
+ </a>
64
+ </div>
65
+ </nav>
66
+
67
+ <div class="container mt-5">
68
+ <div class="row justify-content-center">
69
+ <div class="col-md-10">
70
+ <div class="card">
71
+ <div class="card-header text-center bg-success text-white">
72
+ <h2 class="mb-0">
73
+ <i class="fas fa-user-plus me-2"></i>
74
+ Daftarkan Wajah Baru
75
+ </h2>
76
+ </div>
77
+ <div class="card-body p-4">
78
+ <div class="row">
79
+ <div class="col-md-8">
80
+ <div class="video-container mb-3">
81
+ <img id="video" src="{{ url_for('video_feed') }}" alt="Video Stream" />
82
+ </div>
83
+ </div>
84
+ <div class="col-md-4">
85
+ <div class="mb-3">
86
+ <label for="nameInput" class="form-label">
87
+ <i class="fas fa-user me-2"></i>
88
+ Nama Lengkap
89
+ </label>
90
+ <input type="text" class="form-control" id="nameInput" placeholder="Masukkan nama lengkap" required />
91
+ </div>
92
+
93
+ <button type="button" class="btn btn-success btn-custom w-100 mb-3" id="captureBtn">
94
+ <i class="fas fa-camera me-2"></i>
95
+ Ambil Foto
96
+ </button>
97
+
98
+ <div class="alert alert-info">
99
+ <i class="fas fa-info-circle me-2"></i>
100
+ <strong>Petunjuk:</strong>
101
+ <ul class="mb-0 mt-2">
102
+ <li>Pastikan wajah Anda terlihat jelas</li>
103
+ <li>Posisikan wajah di tengah kamera</li>
104
+ <li>Pastikan pencahayaan cukup</li>
105
+ <li>Hanya satu wajah yang boleh terlihat</li>
106
+ </ul>
107
+ </div>
108
+
109
+ <div id="messageArea"></div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ <div class="row justify-content-center mt-4">
118
+ <div class="col-md-10">
119
+ <div class="card">
120
+ <!-- <div class="card-body">
121
+ <h5 class="card-title">
122
+ <i class="fas fa-question-circle me-2"></i>
123
+ Tips untuk Hasil Terbaik
124
+ </h5>
125
+ <div class="row">
126
+ <div class="col-md-6">
127
+ <ul>
128
+ <li>Gunakan pencahayaan yang baik dan merata</li>
129
+ <li>Hindari bayangan pada wajah</li>
130
+ <li>Tatap langsung ke kamera</li>
131
+ </ul>
132
+ </div>
133
+ <div class="col-md-6">
134
+ <ul>
135
+ <li>Jangan gunakan kacamata hitam atau topi</li>
136
+ <li>Pastikan tidak ada orang lain di frame</li>
137
+ <li>Jaga ekspresi wajah yang natural</li>
138
+ </ul>
139
+ </div>
140
+ </div>
141
+ </div> -->
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
148
+ <script>
149
+ document.getElementById("captureBtn").addEventListener("click", function () {
150
+ const name = document.getElementById("nameInput").value.trim();
151
+ const messageArea = document.getElementById("messageArea");
152
+
153
+ if (!name) {
154
+ showMessage("Silakan masukkan nama terlebih dahulu!", "danger");
155
+ return;
156
+ }
157
+
158
+ // Disable button dan show loading
159
+ this.disabled = true;
160
+ this.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Mengambil Foto...';
161
+
162
+ fetch("/capture_face", {
163
+ method: "POST",
164
+ headers: {
165
+ "Content-Type": "application/json",
166
+ },
167
+ body: JSON.stringify({ name: name }),
168
+ })
169
+ .then((response) => response.json())
170
+ .then((data) => {
171
+ if (data.success) {
172
+ showMessage(data.success, "success");
173
+ document.getElementById("nameInput").value = "";
174
+ } else {
175
+ showMessage(data.error, "danger");
176
+ }
177
+ })
178
+ .catch((error) => {
179
+ showMessage("Terjadi kesalahan: " + error, "danger");
180
+ })
181
+ .finally(() => {
182
+ // Re-enable button
183
+ this.disabled = false;
184
+ this.innerHTML = '<i class="fas fa-camera me-2"></i>Ambil Foto';
185
+ });
186
+ });
187
+
188
+ function showMessage(message, type) {
189
+ const messageArea = document.getElementById("messageArea");
190
+ messageArea.innerHTML = `
191
+ <div class="alert alert-${type} alert-dismissible fade show">
192
+ <i class="fas fa-${type === "success" ? "check-circle" : "exclamation-circle"} me-2"></i>
193
+ ${message}
194
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
195
+ </div>
196
+ `;
197
+ }
198
+
199
+ // Enter key support
200
+ document.getElementById("nameInput").addEventListener("keypress", function (e) {
201
+ if (e.key === "Enter") {
202
+ document.getElementById("captureBtn").click();
203
+ }
204
+ });
205
+ </script>
206
+ </body>
207
+ </html>