Compare commits

..

No commits in common. "a3ba05a4266e04164418475f6d5b6b1af53d6f8f" and "490abddd23c3796f228e8c76ca4aa9e27430ac4a" have entirely different histories.

16 changed files with 76 additions and 245 deletions

View File

@ -5,8 +5,6 @@
</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" />
@ -18,7 +16,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" /> <change beforePath="$PROJECT_DIR$/face_database.db" beforeDir="false" afterPath="$PROJECT_DIR$/face_database.db" afterDir="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" />
@ -30,7 +28,6 @@
<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>
@ -60,28 +57,26 @@
<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"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"ASKED_ADD_EXTERNAL_FILES": "true", &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
"DefaultHtmlFileTemplate": "HTML File", &quot;Python.add_face.executor&quot;: &quot;Run&quot;,
"Python.add_face.executor": "Run", &quot;Python.match_face.executor&quot;: &quot;Run&quot;,
"Python.app.executor": "Run", &quot;Python.scanf_face.executor&quot;: &quot;Run&quot;,
"Python.match_face.executor": "Run", &quot;Python.sqlite.executor&quot;: &quot;Run&quot;,
"Python.scanf_face.executor": "Run", &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
"Python.sqlite.executor": "Run", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.OpenProjectViewOnStart": "true", &quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;git-widget-placeholder&quot;: &quot;master&quot;,
"SHARE_PROJECT_CONFIGURATION_FILES": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"git-widget-placeholder": "master", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;,
"nodejs_package_manager_path": "npm", &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
"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" />
@ -106,29 +101,6 @@
<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="" />
@ -201,7 +173,6 @@
<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" />
@ -235,11 +206,6 @@
<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" />
@ -296,8 +262,7 @@
<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$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="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$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
View File

@ -1,26 +0,0 @@
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: 20 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

View File

View File

@ -3,114 +3,18 @@ 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:
def create_face_database(db_name="face_database.db"):
conn = sqlite3.connect(db_name)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS faces
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
identity TEXT 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.close()
def add_face_to_database(name, identity, image_path, db_name="face_database.db"):
conn = sqlite3.connect(db_name)
c = conn.cursor()
# 加载图片并生成编码
image = face_recognition.load_image_file(image_path)
face_encodings = face_recognition.face_encodings(image)
if face_encodings:
face_encoding = face_encodings[0]
# 将编码转换为可以存储的格式
encoding_blob = np.array(face_encoding).tobytes()
c.execute("INSERT INTO faces (name, identity, encoding) VALUES (?, ?, ?)",
(name, identity, encoding_blob))
conn.commit()
conn.close()
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)
c = conn.cursor()
# 获取数据库中所有存储的人脸编码
c.execute("SELECT name, identity, encoding FROM faces")
known_faces = c.fetchall()
for image_path in captured_images:
# 加载待匹配图片并生成编码
unknown_image = face_recognition.load_image_file(image_path)
face_encodings = face_recognition.face_encodings(unknown_image)
if len(face_encodings) == 0:
print(f"没有人脸 {image_path}")
continue # 如果没有检测到人脸,跳过该图片
unknown_encoding = face_encodings[0]
for name, identity, encoding_blob in known_faces:
known_encoding = np.frombuffer(encoding_blob, dtype=np.float64)
match = face_recognition.compare_faces([known_encoding], unknown_encoding, tolerance=tolerance)
if match[0]: # 如果匹配成功
print(f"发现匹配: {name} ({identity}) 在 {image_path}")
log_match(name, identity, image_path, db_name, log_file) # 记录匹配信息和时间到数据库和TXT文件
conn.close()
return True # 一旦找到匹配,返回成功
print(f"没发现匹配: 在 {image_path}")
conn.close()
return False # 如果所有比较都没有匹配,返回失败
# 创建人脸数据库
create_face_database()
# 向数据库中添加人脸
add_face_to_database("屈礼", "居民", "./db_image/test.jpg")
# 主程序循环
while True:
ret, frame = cap.read() ret, frame = cap.read()
if not ret: if not ret:
break break
@ -121,20 +25,6 @@ while True:
# 检测人脸 # 检测人脸
face_locations = face_recognition.face_locations(rgb_frame) 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: for face_location in face_locations:
top, right, bottom, left = face_location top, right, bottom, left = face_location
@ -158,16 +48,78 @@ while True:
if cv2.waitKey(1) & 0xFF == ord('q'): if cv2.waitKey(1) & 0xFF == ord('q'):
break break
# 关闭窗口 cap.release()
cv2.destroyAllWindows() cv2.destroyAllWindows()
print(f"Captured {photo_count} images.")
def create_face_database(db_name="face_database.db"):
conn = sqlite3.connect(db_name)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS faces
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
encoding BLOB NOT NULL)''')
conn.commit()
conn.close()
def add_face_to_database(name, image_path, db_name="face_database.db"):
conn = sqlite3.connect(db_name)
c = conn.cursor()
# 加载图片并生成编码
image = face_recognition.load_image_file(image_path)
face_encodings = face_recognition.face_encodings(image)
if face_encodings:
face_encoding = face_encodings[0]
# 将编码转换为可以存储的格式
encoding_blob = np.array(face_encoding).tobytes()
c.execute("INSERT INTO faces (name, encoding) VALUES (?, ?)",
(name, encoding_blob))
conn.commit()
conn.close()
def match_faces(captured_images, db_name="face_database.db", tolerance=0.4):
conn = sqlite3.connect(db_name)
c = conn.cursor()
# 获取数据库中所有存储的人脸编码
c.execute("SELECT name, encoding FROM faces")
known_faces = c.fetchall()
for image_path in captured_images:
# 加载待匹配图片并生成编码
unknown_image = face_recognition.load_image_file(image_path)
face_encodings = face_recognition.face_encodings(unknown_image)
if len(face_encodings) == 0:
print(f"没有人脸 {image_path}")
continue # 如果没有检测到人脸,跳过该图片
unknown_encoding = face_encodings[0]
for name, encoding_blob in known_faces:
known_encoding = np.frombuffer(encoding_blob, dtype=np.float64)
match = face_recognition.compare_faces([known_encoding], unknown_encoding, tolerance=tolerance)
if match[0]: # 如果匹配成功
print(f"发现匹配: {name}{image_path}")
conn.close()
return True # 一旦找到匹配,返回成功
else:
print(f"没发现匹配: 在 {image_path}")
conn.close()
return False # 如果所有比较都没有匹配,返回失败
# 创建人脸数据库
create_face_database()
# 向数据库中添加人脸
add_face_to_database("屈礼", "./db_image/test.jpg")
# 逐张匹配抓拍的照片
if match_faces(captured_images): if match_faces(captured_images):
print("至少一张匹配") print("至少一张匹配")
else: else:
print("没有匹配") print("没有匹配")
# 等待60秒后继续循环检测
print("等待30秒后继续...")
time.sleep(30)
cap.release()

View File

@ -1,60 +0,0 @@
<!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>