Compare commits

...

2 Commits

Author SHA1 Message Date
sairate a3ba05a426 Signed-off-by: sairate <sairate@sina.cn> 2024-08-25 11:36:46 +08:00
sairate 9199881829 Signed-off-by: sairate <sairate@sina.cn> 2024-08-25 11:36:23 +08:00
16 changed files with 245 additions and 76 deletions

View File

@ -5,6 +5,8 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="c512d54a-7f5f-4cfb-af24-29d5821a33bf" name="更改" comment="Signed-off-by: sairate &lt;sairate@sina.cn&gt;"> <list default="true" id="c512d54a-7f5f-4cfb-af24-29d5821a33bf" name="更改" comment="Signed-off-by: sairate &lt;sairate@sina.cn&gt;">
<change afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/captured_faces/face_1.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_1.jpg" afterDir="false" /> <change beforePath="$PROJECT_DIR$/captured_faces/face_1.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_1.jpg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/captured_faces/face_10.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_10.jpg" afterDir="false" /> <change beforePath="$PROJECT_DIR$/captured_faces/face_10.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_10.jpg" afterDir="false" />
@ -16,7 +18,7 @@
<change beforePath="$PROJECT_DIR$/captured_faces/face_7.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_7.jpg" afterDir="false" /> <change beforePath="$PROJECT_DIR$/captured_faces/face_7.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_7.jpg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/captured_faces/face_8.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_8.jpg" afterDir="false" /> <change beforePath="$PROJECT_DIR$/captured_faces/face_8.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_8.jpg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/captured_faces/face_9.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_9.jpg" afterDir="false" /> <change beforePath="$PROJECT_DIR$/captured_faces/face_9.jpg" beforeDir="false" afterPath="$PROJECT_DIR$/captured_faces/face_9.jpg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/face_database.db" beforeDir="false" afterPath="$PROJECT_DIR$/face_database.db" afterDir="false" /> <change beforePath="$PROJECT_DIR$/face_database.db" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/scanf_face.py" beforeDir="false" afterPath="$PROJECT_DIR$/scanf_face.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/scanf_face.py" beforeDir="false" afterPath="$PROJECT_DIR$/scanf_face.py" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@ -28,6 +30,7 @@
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="Python Script" /> <option value="Python Script" />
<option value="HTML File" />
</list> </list>
</option> </option>
</component> </component>
@ -57,26 +60,28 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, "ASKED_ADD_EXTERNAL_FILES": "true",
&quot;Python.add_face.executor&quot;: &quot;Run&quot;, "DefaultHtmlFileTemplate": "HTML File",
&quot;Python.match_face.executor&quot;: &quot;Run&quot;, "Python.add_face.executor": "Run",
&quot;Python.scanf_face.executor&quot;: &quot;Run&quot;, "Python.app.executor": "Run",
&quot;Python.sqlite.executor&quot;: &quot;Run&quot;, "Python.match_face.executor": "Run",
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;, "Python.scanf_face.executor": "Run",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "Python.sqlite.executor": "Run",
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "RunOnceActivity.OpenProjectViewOnStart": "true",
&quot;git-widget-placeholder&quot;: &quot;master&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "git-widget-placeholder": "master",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.tslint": "true",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
"vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="RunManager" selected="Python.scanf_face"> <component name="RunManager" selected="Python.scanf_face">
<configuration name="add_face" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> <configuration name="add_face" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="face" /> <module name="face" />
@ -101,6 +106,29 @@
<option name="INPUT_FILE" value="" /> <option name="INPUT_FILE" value="" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="app" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="face" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/app.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="match_face" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> <configuration name="match_face" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="face" /> <module name="face" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
@ -173,6 +201,7 @@
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Python.scanf_face" /> <item itemvalue="Python.scanf_face" />
<item itemvalue="Python.app" />
<item itemvalue="Python.match_face" /> <item itemvalue="Python.match_face" />
<item itemvalue="Python.add_face" /> <item itemvalue="Python.add_face" />
<item itemvalue="Python.sqlite" /> <item itemvalue="Python.sqlite" />
@ -206,6 +235,11 @@
<workItem from="1724382932780" duration="803000" /> <workItem from="1724382932780" duration="803000" />
<workItem from="1724385627303" duration="110000" /> <workItem from="1724385627303" duration="110000" />
<workItem from="1724388047550" duration="281000" /> <workItem from="1724388047550" duration="281000" />
<workItem from="1724392582289" duration="224000" />
<workItem from="1724402091135" duration="241000" />
<workItem from="1724487223382" duration="3000" />
<workItem from="1724487238846" duration="322000" />
<workItem from="1724546711749" duration="3708000" />
</task> </task>
<task id="LOCAL-00001" summary="sairate"> <task id="LOCAL-00001" summary="sairate">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -262,7 +296,8 @@
<component name="com.intellij.coverage.CoverageDataManagerImpl"> <component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/face$sqlite.coverage" NAME="sqlite 覆盖结果" MODIFIED="1724310603865" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/face$sqlite.coverage" NAME="sqlite 覆盖结果" MODIFIED="1724310603865" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/face$match_face.coverage" NAME="match_face 覆盖结果" MODIFIED="1724314395402" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/face$match_face.coverage" NAME="match_face 覆盖结果" MODIFIED="1724314395402" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/face$scanf_face.coverage" NAME="scanf_face 覆盖结果" MODIFIED="1724388311934" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/face$app.coverage" NAME="app 覆盖结果" MODIFIED="1724556824546" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/face$scanf_face.coverage" NAME="scanf_face 覆盖结果" MODIFIED="1724556840774" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/face$add_face.coverage" NAME="add_face 覆盖结果" MODIFIED="1724311005030" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/face$add_face.coverage" NAME="add_face 覆盖结果" MODIFIED="1724311005030" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
</component> </component>
</project> </project>

26
app.py Normal file
View File

@ -0,0 +1,26 @@
from flask import Flask, render_template
import sqlite3
import subprocess
#subprocess.Popen(["python", "scanf_face.py"])
app = Flask(__name__)
# 从数据库中获取匹配日志记录
def get_match_logs(db_name="face_database.db"):
conn = sqlite3.connect(db_name)
c = conn.cursor()
c.execute("SELECT name, identity, image_path, match_time FROM match_logs")
logs = c.fetchall()
conn.close()
return logs
# 首页,展示匹配记录
@app.route('/')
def index():
logs = get_match_logs()
return render_template('index.html', logs=logs)
if __name__ == '__main__':
app.run(debug=True)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

0
match_log.txt Normal file
View File

View File

@ -3,55 +3,17 @@ import face_recognition
import os import os
import sqlite3 import sqlite3
import numpy as np import numpy as np
from datetime import datetime
import time
# 初始化摄像头 # 初始化摄像头
cap = cv2.VideoCapture(0) cap = cv2.VideoCapture(0)
photo_count = 0
max_photos = 10 max_photos = 10
captured_images = []
# 创建目录以保存照片 # 创建目录以保存照片
save_path = "./captured_faces" save_path = "./captured_faces"
os.makedirs(save_path, exist_ok=True) os.makedirs(save_path, exist_ok=True)
while photo_count < max_photos:
ret, frame = cap.read()
if not ret:
break
# 将图像转换为RGB颜色
rgb_frame = frame[:, :, ::-1]
# 检测人脸
face_locations = face_recognition.face_locations(rgb_frame)
for face_location in face_locations:
top, right, bottom, left = face_location
# 在图像上绘制绿框
cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2)
# 提取人脸区域
face_image = frame[top:bottom, left:right]
# 保存抓拍的照片
image_path = os.path.join(save_path, f"face_{photo_count + 1}.jpg")
cv2.imwrite(image_path, face_image)
captured_images.append(image_path)
photo_count += 1
if photo_count >= max_photos:
break
# 显示结果
cv2.imshow("Capturing Faces", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print(f"Captured {photo_count} images.")
def create_face_database(db_name="face_database.db"): def create_face_database(db_name="face_database.db"):
conn = sqlite3.connect(db_name) conn = sqlite3.connect(db_name)
@ -59,11 +21,21 @@ def create_face_database(db_name="face_database.db"):
c.execute('''CREATE TABLE IF NOT EXISTS faces c.execute('''CREATE TABLE IF NOT EXISTS faces
(id INTEGER PRIMARY KEY AUTOINCREMENT, (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
identity TEXT NOT NULL,
encoding BLOB NOT NULL)''') encoding BLOB NOT NULL)''')
# 创建匹配日志表
c.execute('''CREATE TABLE IF NOT EXISTS match_logs
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
identity TEXT NOT NULL,
image_path TEXT NOT NULL,
match_time TEXT NOT NULL)''')
conn.commit() conn.commit()
conn.close() conn.close()
def add_face_to_database(name, image_path, db_name="face_database.db"):
def add_face_to_database(name, identity, image_path, db_name="face_database.db"):
conn = sqlite3.connect(db_name) conn = sqlite3.connect(db_name)
c = conn.cursor() c = conn.cursor()
@ -75,17 +47,36 @@ def add_face_to_database(name, image_path, db_name="face_database.db"):
face_encoding = face_encodings[0] face_encoding = face_encodings[0]
# 将编码转换为可以存储的格式 # 将编码转换为可以存储的格式
encoding_blob = np.array(face_encoding).tobytes() encoding_blob = np.array(face_encoding).tobytes()
c.execute("INSERT INTO faces (name, encoding) VALUES (?, ?)", c.execute("INSERT INTO faces (name, identity, encoding) VALUES (?, ?, ?)",
(name, encoding_blob)) (name, identity, encoding_blob))
conn.commit() conn.commit()
conn.close() conn.close()
def match_faces(captured_images, db_name="face_database.db", tolerance=0.4):
def log_match(name, identity, image_path, db_name="face_database.db", log_file="match_log.txt"):
conn = sqlite3.connect(db_name)
c = conn.cursor()
# 获取当前时间
match_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 将匹配信息插入到匹配日志表中
c.execute("INSERT INTO match_logs (name, identity, image_path, match_time) VALUES (?, ?, ?, ?)",
(name, identity, image_path, match_time))
conn.commit()
conn.close()
# 将匹配信息写入文本文件
with open(log_file, "a") as f:
f.write(f"匹配成功: {name} ({identity}) 在 {image_path} 时间: {match_time}\n")
def match_faces(captured_images, db_name="face_database.db", tolerance=0.4, log_file="match_log.txt"):
conn = sqlite3.connect(db_name) conn = sqlite3.connect(db_name)
c = conn.cursor() c = conn.cursor()
# 获取数据库中所有存储的人脸编码 # 获取数据库中所有存储的人脸编码
c.execute("SELECT name, encoding FROM faces") c.execute("SELECT name, identity, encoding FROM faces")
known_faces = c.fetchall() known_faces = c.fetchall()
for image_path in captured_images: for image_path in captured_images:
@ -99,16 +90,16 @@ def match_faces(captured_images, db_name="face_database.db", tolerance=0.4):
unknown_encoding = face_encodings[0] unknown_encoding = face_encodings[0]
for name, encoding_blob in known_faces: for name, identity, encoding_blob in known_faces:
known_encoding = np.frombuffer(encoding_blob, dtype=np.float64) known_encoding = np.frombuffer(encoding_blob, dtype=np.float64)
match = face_recognition.compare_faces([known_encoding], unknown_encoding, tolerance=tolerance) match = face_recognition.compare_faces([known_encoding], unknown_encoding, tolerance=tolerance)
if match[0]: # 如果匹配成功 if match[0]: # 如果匹配成功
print(f"发现匹配: {name}{image_path}") print(f"发现匹配: {name} ({identity}) 在 {image_path}")
log_match(name, identity, image_path, db_name, log_file) # 记录匹配信息和时间到数据库和TXT文件
conn.close() conn.close()
return True # 一旦找到匹配,返回成功 return True # 一旦找到匹配,返回成功
else: print(f"没发现匹配: 在 {image_path}")
print(f"没发现匹配: 在 {image_path}")
conn.close() conn.close()
return False # 如果所有比较都没有匹配,返回失败 return False # 如果所有比较都没有匹配,返回失败
@ -116,10 +107,67 @@ def match_faces(captured_images, db_name="face_database.db", tolerance=0.4):
create_face_database() create_face_database()
# 向数据库中添加人脸 # 向数据库中添加人脸
add_face_to_database("屈礼", "./db_image/test.jpg") add_face_to_database("屈礼", "居民", "./db_image/test.jpg")
# 逐张匹配抓拍的照片 # 主程序循环
if match_faces(captured_images): while True:
print("至少一张匹配") ret, frame = cap.read()
else: if not ret:
print("没有匹配") break
# 将图像转换为RGB颜色
rgb_frame = frame[:, :, ::-1]
# 检测人脸
face_locations = face_recognition.face_locations(rgb_frame)
if face_locations:
print("检测到人脸,开始抓拍...")
captured_images = []
photo_count = 0
while photo_count < max_photos:
ret, frame = cap.read()
if not ret:
break
rgb_frame = frame[:, :, ::-1]
face_locations = face_recognition.face_locations(rgb_frame)
for face_location in face_locations:
top, right, bottom, left = face_location
# 在图像上绘制绿框
cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2)
# 提取人脸区域
face_image = frame[top:bottom, left:right]
# 保存抓拍的照片
image_path = os.path.join(save_path, f"face_{photo_count + 1}.jpg")
cv2.imwrite(image_path, face_image)
captured_images.append(image_path)
photo_count += 1
if photo_count >= max_photos:
break
# 显示结果
cv2.imshow("Capturing Faces", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 关闭窗口
cv2.destroyAllWindows()
if match_faces(captured_images):
print("至少一张匹配")
else:
print("没有匹配")
# 等待60秒后继续循环检测
print("等待30秒后继续...")
time.sleep(30)
cap.release()

60
templates/index.html Normal file
View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>匹配记录</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 80%;
margin: 20px auto;
background: #fff;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table, th, td {
border: 1px solid #ddd;
}
th, td {
padding: 10px;
text-align: left;
}
th {
background-color: #4CAF50;
color: white;
}
</style>
</head>
<body>
<div class="container">
<h1>Face Match Logs</h1>
<table>
<tr>
<th>Name</th>
<th>Identity</th>
<th>Image Path</th>
<th>Match Time</th>
</tr>
{% for log in logs %}
<tr>
<td>{{ log[0] }}</td>
<td>{{ log[1] }}</td>
<td>{{ log[2] }}</td>
<td>{{ log[3] }}</td>
</tr>
{% endfor %}
</table>
</div>
</body>
</html>