Signed-off-by: sairate <sairate@sina.cn>
This commit is contained in:
commit
ff7b934797
|
@ -0,0 +1,8 @@
|
|||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (photo)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (photo)" project-jdk-type="Python SDK" />
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/photo.iml" filepath="$PROJECT_DIR$/.idea/photo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,125 @@
|
|||
下面是一个示例 README 文档,你可以将其保存为 `README.md` 文件,并根据实际需求进行调整:
|
||||
|
||||
---
|
||||
|
||||
# Photo Project
|
||||
|
||||
本项目是一个基于 Flask 的人脸识别应用,用于处理用户头像上传和照片匹配。系统通过提取上传图片中最大的人脸区域生成面部特征码,并将用户信息和照片信息存储到 SQLite 数据库中,支持查询用户照片。
|
||||
|
||||
## 项目功能
|
||||
|
||||
- **用户管理**
|
||||
- 上传用户头像,自动提取最大人脸并生成面部特征码。
|
||||
- 编辑和删除用户信息。
|
||||
|
||||
- **照片上传**
|
||||
- 支持批量照片上传。
|
||||
- 自动检测每张照片中最大的那张人脸,提取面部特征码,与已注册用户进行比对。
|
||||
- 匹配成功后将照片移动到用户目录,并记录拍摄时间。
|
||||
|
||||
- **查询功能**
|
||||
- 根据用户名和拍摄日期查询用户照片。
|
||||
|
||||
- **其他功能**
|
||||
- 根据图片 EXIF 信息获取拍摄时间。
|
||||
- 自动校正图片方向。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
photo_project/ # 项目根目录
|
||||
├── .venv/ # 虚拟环境目录(可选)
|
||||
├── app.py # Flask 主应用程序,包含所有业务逻辑和路由
|
||||
├── fuctions.py # 功能函数
|
||||
├── moodle.py # 数据库模型定义(User、Photo)
|
||||
├── requirements.txt # 项目依赖包列表
|
||||
├── README.md # 项目说明文档
|
||||
├── templates/ # HTML 模板文件
|
||||
│ ├── index.html # 首页模板
|
||||
│ ├── user_management.html # 用户管理页面模板
|
||||
│ ├── upload.html # 照片上传及结果展示模板
|
||||
│ └── search.html # 查询页面模板
|
||||
└── static/ # 静态文件目录
|
||||
└── uploads/ # 图片上传目录
|
||||
├── users/ # 用户头像和照片存储目录(按用户/日期组织)
|
||||
└── temp/ # 临时目录,用于存储上传处理中的图片,分类完成的照片会删除
|
||||
```
|
||||
|
||||
## 安装与配置
|
||||
|
||||
1. **克隆项目**
|
||||
|
||||
```bash
|
||||
git clone <repository_url>
|
||||
cd photo_project
|
||||
```
|
||||
|
||||
2. **创建虚拟环境并安装依赖**
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # Linux/macOS
|
||||
.venv\Scripts\activate # Windows
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. **配置环境变量**
|
||||
|
||||
如果需要自定义密钥,可以设置环境变量 `SECRET_KEY`,例如:
|
||||
|
||||
```bash
|
||||
export SECRET_KEY='your_secret_key'
|
||||
```
|
||||
|
||||
4. **初始化数据库**
|
||||
|
||||
运行项目时,系统会自动在应用上下文中创建数据库表。
|
||||
|
||||
## 运行项目
|
||||
|
||||
在项目根目录下执行:
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
项目将启动在 `http://127.0.0.1:5000/`,可在浏览器中访问。
|
||||
|
||||
## 使用说明
|
||||
|
||||
- **首页**
|
||||
访问根网页 `/`,进入首页,可选择进入用户管理、照片上传和查询页面。
|
||||
|
||||
- **用户管理**
|
||||
访问 `/user_management` 页面,上传用户头像后,系统自动提取最大人脸并生成面部特征码保存到数据库。
|
||||
|
||||
- **照片上传**
|
||||
访问 `/photo_upload` 页面,上传一张或多张照片,系统将对每张照片提取最大人脸,与数据库中用户进行比对,匹配成功后将照片移动至用户目录,并记录拍摄时间。
|
||||
|
||||
- **查询照片**
|
||||
访问 `/search` 页面,根据输入的用户名和拍摄日期(格式:YYYY-MM-DD)查询用户当天的照片。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 请确保 `templates` 文件夹中包含必要的 HTML 模板(如 `index.html`、`user_management.html`、`upload.html` 和 `search.html`)。
|
||||
- 照片上传目录默认位于 `static/uploads` 下,建议定期检查和清理临时文件夹 `static/uploads/temp`。
|
||||
- 本项目使用 SQLite 数据库,适合中小型应用,若用于生产环境,请考虑替换为更适合的数据库。
|
||||
|
||||
## 依赖
|
||||
|
||||
主要依赖包包括:
|
||||
|
||||
- Flask
|
||||
- flask_session
|
||||
- SQLAlchemy
|
||||
- face_recognition
|
||||
- OpenCV (opencv-python)
|
||||
- Pillow
|
||||
- numpy
|
||||
|
||||
详细依赖请参见 `requirements.txt`。
|
||||
|
||||
## 版权与许可证
|
||||
|
||||
本项目仅供学习和测试使用,具体版权和许可证信息请根据实际情况添加。
|
||||
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,94 @@
|
|||
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
|
||||
from functions import add_user, delete_user, edit_user, get_user, classify_photos, search_photos
|
||||
from models import db, User, Photo
|
||||
import os
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# 初始化Flask应用
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'your_secret_key'
|
||||
|
||||
# 设置数据库配置
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db' # 修改为你的数据库URI
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 禁用修改追踪
|
||||
|
||||
# 初始化SQLAlchemy
|
||||
db.init_app(app)
|
||||
|
||||
# 创建数据库表
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# 首页路由
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
# 用户管理页面
|
||||
@app.route('/user_management')
|
||||
def user_management():
|
||||
users = User.query.all()
|
||||
return render_template('user_management.html', users=users)
|
||||
|
||||
@app.route('/get_encoding/<int:user_id>')
|
||||
def get_encoding(user_id):
|
||||
user = User.query.get(user_id)
|
||||
return jsonify({"encoding": user.encoding if user and user.encoding else "无数据"})
|
||||
|
||||
|
||||
# 添加用户
|
||||
@app.route('/add_user', methods=['POST'])
|
||||
def add_new_user():
|
||||
username = request.form['username']
|
||||
userphoto = request.files['userphoto']
|
||||
if add_user(username, userphoto):
|
||||
flash("用户添加成功", "success")
|
||||
else:
|
||||
flash("用户添加失败", "danger")
|
||||
return redirect(url_for('user_management'))
|
||||
|
||||
# 删除用户
|
||||
@app.route('/delete_user/<int:user_id>', methods=['POST'])
|
||||
def delete_existing_user(user_id):
|
||||
if delete_user(user_id):
|
||||
flash("用户删除成功", "success")
|
||||
else:
|
||||
flash("用户删除失败", "danger")
|
||||
return redirect(url_for('user_management'))
|
||||
|
||||
# 编辑用户
|
||||
@app.route('/edit_user/<int:user_id>', methods=['GET', 'POST'])
|
||||
def edit_existing_user(user_id):
|
||||
user = get_user(user_id)
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
userphoto = request.files.get('userphoto')
|
||||
if edit_user(user_id, username, userphoto):
|
||||
flash("用户信息更新成功", "success")
|
||||
else:
|
||||
flash("更新失败", "danger")
|
||||
return redirect(url_for('user_management'))
|
||||
return render_template('edit_user.html', user=user)
|
||||
|
||||
# 照片上传页面
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload():
|
||||
if request.method == 'POST':
|
||||
photos = request.files.getlist('photos')
|
||||
classify_photos(photos) # 调用分类处理函数
|
||||
return render_template('upload.html', photos=photos)
|
||||
return render_template('upload.html')
|
||||
|
||||
|
||||
# 照片搜索
|
||||
@app.route('/search', methods=['GET', 'POST'])
|
||||
def search():
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
date = request.form['date']
|
||||
photos = search_photos(name, date)
|
||||
return render_template('search.html', photos=photos)
|
||||
return render_template('search.html')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
|
@ -0,0 +1,243 @@
|
|||
import piexif
|
||||
from PIL import Image
|
||||
import face_recognition
|
||||
from datetime import datetime
|
||||
from models import db, User, Photo
|
||||
from werkzeug.utils import secure_filename
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
def process_face_encoding(photo_path):
|
||||
"""提取人脸特征编码并裁剪出最大的面部区域"""
|
||||
# 加载图片
|
||||
image = face_recognition.load_image_file(photo_path)
|
||||
|
||||
# 获取所有人脸的位置
|
||||
face_locations = face_recognition.face_locations(image)
|
||||
|
||||
if not face_locations:
|
||||
return None # 没有检测到人脸
|
||||
|
||||
# 找到最大的人脸
|
||||
largest_face = max(face_locations, key=lambda x: (x[2] - x[0]) * (x[3] - x[1])) # 按照面积选取最大的人脸
|
||||
|
||||
# 获取该人脸的特征编码
|
||||
face_encodings = face_recognition.face_encodings(image, [largest_face])
|
||||
|
||||
if face_encodings:
|
||||
# 裁剪出最大的人脸部分
|
||||
top, right, bottom, left = largest_face
|
||||
image_pil = Image.open(photo_path)
|
||||
cropped_face = image_pil.crop((left, top, right, bottom))
|
||||
|
||||
# 保存裁剪后的图像(可选)
|
||||
cropped_face_path = photo_path.replace(".jpg", "_cropped.jpg")
|
||||
cropped_face.save(cropped_face_path)
|
||||
|
||||
return face_encodings[0], cropped_face_path # 返回面部编码和裁剪后的人脸图片路径
|
||||
|
||||
return None # 如果没有提取到人脸特征编码
|
||||
|
||||
|
||||
def add_user(username, userphoto):
|
||||
"""添加用户并存储人脸特征"""
|
||||
if userphoto:
|
||||
filename = secure_filename(userphoto.filename)
|
||||
user_dir = os.path.join("static/uploads/users",username)
|
||||
user_dir =user_dir.replace("\\","/")
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
photo_path = os.path.join(user_dir,"photo.jpg")
|
||||
photo_path = photo_path.replace("\\","/")
|
||||
|
||||
|
||||
# 保存头像
|
||||
userphoto.save(photo_path)
|
||||
|
||||
# 处理人脸特征
|
||||
face_encoding = process_face_encoding(photo_path)
|
||||
if face_encoding is None:
|
||||
print("未检测到人脸,无法添加用户")
|
||||
return False
|
||||
|
||||
# 存储到数据库
|
||||
new_user = User(username=username, encoding=face_encoding, photo=photo_path)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
# 删除用户
|
||||
def delete_user(user_id):
|
||||
try:
|
||||
user = User.query.get(user_id)
|
||||
if user:
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
# 编辑用户
|
||||
def edit_user(user_id, username, userphoto):
|
||||
try:
|
||||
user = User.query.get(user_id)
|
||||
if user:
|
||||
user.username = username
|
||||
if userphoto:
|
||||
photo_path = os.path.join('static', 'uploads', 'users', str(user.id))
|
||||
os.makedirs(photo_path, exist_ok=True)
|
||||
photo_file = os.path.join(photo_path, userphoto.filename)
|
||||
userphoto.save(photo_file)
|
||||
# 更新照片路径
|
||||
photo = Photo.query.filter_by(user_id=user.id).first()
|
||||
photo.photo_path = photo_file
|
||||
db.session.commit()
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
# 获取用户
|
||||
def get_user(user_id):
|
||||
return User.query.get(user_id)
|
||||
|
||||
def save_temp_photo(photo):
|
||||
"""保存临时照片"""
|
||||
temp_dir = 'static/uploads/temp'
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
photo_path = os.path.join(temp_dir, photo.filename).replace("\\", "/")
|
||||
photo.save(photo_path)
|
||||
return photo_path
|
||||
|
||||
|
||||
def get_largest_face(image_path):
|
||||
"""检测并裁剪最大的人脸"""
|
||||
image = face_recognition.load_image_file(image_path)
|
||||
face_locations = face_recognition.face_locations(image)
|
||||
|
||||
if not face_locations:
|
||||
return None # 没有检测到人脸
|
||||
|
||||
# 找到最大的人脸
|
||||
largest_face = max(face_locations, key=lambda loc: (loc[2] - loc[0]) * (loc[3] - loc[1]))
|
||||
top, right, bottom, left = largest_face
|
||||
|
||||
# 裁剪最大的人脸
|
||||
with Image.open(image_path) as img:
|
||||
face_img = img.crop((left, top, right, bottom))
|
||||
face_crop_path = image_path.replace(".jpg", "_face.jpg") # 生成裁剪后的人脸图片路径
|
||||
face_img.save(face_crop_path)
|
||||
|
||||
return face_crop_path
|
||||
|
||||
def match_face(face_path):
|
||||
"""匹配人脸并返回匹配的用户"""
|
||||
face_image = face_recognition.load_image_file(face_path)
|
||||
face_encodings = face_recognition.face_encodings(face_image)
|
||||
|
||||
if not face_encodings:
|
||||
return None # 未检测到人脸
|
||||
|
||||
face_encoding = face_encodings[0] # 取第一张脸
|
||||
users = User.query.all()
|
||||
|
||||
for user in users:
|
||||
if user.encoding:
|
||||
user_encoding = np.array(user.encoding) # 从数据库中获取存储的编码
|
||||
match = face_recognition.compare_faces([user_encoding], face_encoding, tolerance=0.35)
|
||||
if match[0]:
|
||||
return user # 匹配成功
|
||||
return None # 未找到匹配
|
||||
|
||||
|
||||
|
||||
def get_exif_time(photo_path):
|
||||
"""提取 EXIF 时间"""
|
||||
try:
|
||||
with Image.open(photo_path) as img:
|
||||
exif_data = img._getexif()
|
||||
if exif_data:
|
||||
timestamp = exif_data.get(36867, None) # 36867 为 EXIF 拍摄时间
|
||||
return timestamp if timestamp else datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception as e:
|
||||
print(f"无法提取 EXIF 时间: {e}")
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def save_classified_photo(photo, user, timestamp):
|
||||
"""保存分类后的照片"""
|
||||
user_dir = os.path.join('static/uploads/users', str(user.id), timestamp)
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
|
||||
new_photo_path = os.path.join(user_dir, photo.filename)
|
||||
|
||||
try:
|
||||
with Image.open(photo) as img:
|
||||
img.save(new_photo_path, quality=95)
|
||||
except Exception as e:
|
||||
print(f"保存图片时出错: {e}")
|
||||
|
||||
# 保存到数据库
|
||||
photo_record = Photo(user_id=user.id, created_at=timestamp, classification=True,
|
||||
classification_image_path=new_photo_path)
|
||||
db.session.add(photo_record)
|
||||
db.session.commit()
|
||||
|
||||
print(f"照片 {photo.filename} 已保存至 {new_photo_path}")
|
||||
|
||||
|
||||
def classify_photos(photos):
|
||||
"""处理上传的照片"""
|
||||
for photo in photos:
|
||||
temp_photo_path = save_temp_photo(photo)
|
||||
cropped_face_path = get_largest_face(temp_photo_path)
|
||||
|
||||
if not cropped_face_path:
|
||||
print(f"没有检测到人脸:{photo.filename}")
|
||||
continue
|
||||
|
||||
matched_user = match_face(cropped_face_path)
|
||||
timestamp = get_exif_time(temp_photo_path)
|
||||
|
||||
if matched_user:
|
||||
save_classified_photo(photo, matched_user, timestamp)
|
||||
else:
|
||||
print(f"没有找到匹配的人脸:{photo.filename}")
|
||||
|
||||
|
||||
# 根据姓名和时间查找照片
|
||||
def search_photos(name, date):
|
||||
user = User.query.filter_by(username=name).first()
|
||||
if not user:
|
||||
return []
|
||||
photos = Photo.query.filter_by(user_id=user.id).all()
|
||||
filtered_photos = []
|
||||
for photo in photos:
|
||||
# 过滤日期
|
||||
exif_data = piexif.load(photo.photo_path)
|
||||
date_taken = exif_data.get(piexif.ExifIFD.DateTimeOriginal, b"").decode("utf-8")
|
||||
if date_taken and date in date_taken:
|
||||
filtered_photos.append(photo)
|
||||
return filtered_photos
|
||||
|
||||
def find_match(image_path, known_face_encodings):
|
||||
# 加载图片
|
||||
unknown_image = face_recognition.load_image_file(image_path)
|
||||
|
||||
# 获取图片中的人脸特征编码
|
||||
unknown_face_encoding = face_recognition.face_encodings(unknown_image)
|
||||
|
||||
if unknown_face_encoding:
|
||||
unknown_face_encoding = unknown_face_encoding[0]
|
||||
# 设置一个匹配的阈值,例如0.6,值越低匹配越宽松
|
||||
matches = face_recognition.compare_faces(known_face_encodings, unknown_face_encoding, tolerance=0.6)
|
||||
|
||||
if True in matches:
|
||||
match_index = matches.index(True)
|
||||
return match_index
|
||||
return None
|
Binary file not shown.
|
@ -0,0 +1,17 @@
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(50), unique=True, nullable=False)
|
||||
encoding = db.Column(db.PickleType, nullable=True) # 存储人脸特征
|
||||
photo = db.Column(db.String(255), nullable=True)
|
||||
|
||||
|
||||
class Photo(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
created_at = db.Column(db.String(50), nullable=False)
|
||||
classification = db.Column(db.Boolean, default=False)
|
||||
classification_image_path = db.Column(db.String(255), nullable=True)
|
|
@ -0,0 +1,115 @@
|
|||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="file"], input[type="date"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin: 5px 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-edit, .btn-delete {
|
||||
padding: 5px 10px;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background-color: #DC3545;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.photo-gallery {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
margin: 10px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.photo-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-photo {
|
||||
max-width: 100px; /* 设置最大宽度 */
|
||||
max-height: 100px; /* 设置最大高度 */
|
||||
width: auto; /* 保持宽高比例 */
|
||||
height: auto; /* 保持宽高比例 */
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>编辑用户</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>编辑用户</h1>
|
||||
<form action="{{ url_for('edit_user', user_id=user.id) }}" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名:</label>
|
||||
<input type="text" id="username" name="username" value="{{ user.username }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="userphoto">用户照片:</label>
|
||||
<input type="file" id="userphoto" name="userphoto" accept="image/*">
|
||||
</div>
|
||||
<button type="submit" class="btn">保存更改</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>首页</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>欢迎来到照片管理系统</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('user_management') }}">用户管理</a></li>
|
||||
<li><a href="{{ url_for('upload') }}">上传照片</a></li>
|
||||
<li><a href="{{ url_for('search') }}">查询照片</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>查询照片</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>根据姓名和时间查找照片</h1>
|
||||
|
||||
<form action="{{ url_for('search') }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="name">姓名:</label>
|
||||
<input type="text" id="name" name="name" placeholder="请输入用户名" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="date">日期:</label>
|
||||
<input type="text" id="date" name="date" placeholder="请输入日期 (YYYY-MM-DD)" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">查询照片</button>
|
||||
</form>
|
||||
|
||||
{% if photos %}
|
||||
<h2>查询结果:</h2>
|
||||
<div class="photo-gallery">
|
||||
{% for photo in photos %}
|
||||
<div class="photo-item">
|
||||
<img src="{{ url_for('static', filename='uploads/users/' + photo.user.username + '/photo.jpg') }}" alt="用户照片">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>上传照片</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>上传照片并进行分类</h1>
|
||||
|
||||
<!-- 上传照片表单 -->
|
||||
<form action="{{ url_for('upload') }}" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="photos">选择照片:</label>
|
||||
<input type="file" id="photos" name="photos" multiple accept="image/*" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">上传照片</button>
|
||||
</form>
|
||||
|
||||
{% if photos %}
|
||||
<h2>上传照片的分类结果:</h2>
|
||||
<div class="photo-gallery">
|
||||
{% for photo in photos %}
|
||||
<div class="photo-item">
|
||||
<img src="{{ url_for('static', filename='uploads/temp/' + photo.filename) }}" alt="分类照片">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,71 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>用户管理</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>用户管理</h1>
|
||||
|
||||
<!-- 添加用户表单 -->
|
||||
<div class="form-section">
|
||||
<h2>添加用户</h2>
|
||||
<form action="{{ url_for('add_new_user') }}" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名:</label>
|
||||
<input type="text" id="username" name="username" required placeholder="请输入用户名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="userphoto">用户照片:</label>
|
||||
<input type="file" id="userphoto" name="userphoto" accept="image/*" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">添加用户</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="user-list">
|
||||
<h2>用户列表</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>照片</th>
|
||||
<th>面部编码</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>
|
||||
<img src="{{ url_for('static', filename='uploads/users/' + user.username + '/photo.jpg') }}"
|
||||
alt="用户照片" class="user-photo">
|
||||
</td>
|
||||
<td>
|
||||
{% if user.encoding %}
|
||||
<pre>{{ user.encoding[:10] | join(", ") }}...</pre>
|
||||
{% else %}
|
||||
无数据
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('edit_existing_user', user_id=user.id) }}" class="btn-edit">编辑</a>
|
||||
<form action="{{ url_for('delete_existing_user', user_id=user.id) }}" method="POST" style="display:inline;">
|
||||
<button type="submit" class="btn-delete" onclick="return confirm('确定要删除这个用户吗?')">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue