diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..ef4c87e
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index e1582d9..018fcf4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,4 +4,7 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/photo.iml b/.idea/photo.iml
index 2c80e12..063cf1f 100644
--- a/.idea/photo.iml
+++ b/.idea/photo.iml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 285ec61..e4df415 100644
--- a/README.md
+++ b/README.md
@@ -1,125 +1,140 @@
-下面是一个示例 README 文档,你可以将其保存为 `README.md` 文件,并根据实际需求进行调整:
+# 照片管理系统项目文档
----
+## 项目概述
+本项目是一个基于 Flask 框架的照片管理系统,主要功能包括:
+1. **人脸管理**:增删改查用户的人脸数据。
+2. **照片上传与分类**:用户可以上传照片,系统会根据 EXIF 时间信息和数据库中的人脸数据进行分类存储。
+3. **照片查询**:用户可以根据姓名和日期查找相应的照片。
-# Photo Project
+## 技术栈
+- **后端**:Flask(Python)
+- **数据库**:SQLAlchemy(SQLite / MySQL)
+- **前端**:HTML + CSS + Jinja2
+- **存储**:文件系统存储上传的照片
-本项目是一个基于 Flask 的人脸识别应用,用于处理用户头像上传和照片匹配。系统通过提取上传图片中最大的人脸区域生成面部特征码,并将用户信息和照片信息存储到 SQLite 数据库中,支持查询用户照片。
+## 数据库设计
+### 1. `users` 表(用户信息表)
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | 主键 | 自动生成 |
+| username | 字符串 | 用户名,唯一且非空 |
+| encoding | 二进制数据 | 人脸识别编码,用于匹配 |
+| photo | 字符串 | 用户头像照片路径 |
-## 项目功能
+### 2. `photos` 表(照片信息表)
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | 主键 | 自动生成 |
+| user_id | 外键 | 关联到 `users` 表的 `id` |
+| created_at | 时间戳 | 照片拍摄时间(来自 EXIF 信息) |
+| classification | 布尔值 | 照片是否分类(默认 `False`,分类成功后 `True`) |
+| classification_confidence | 浮点数 | 照片分类置信度(默认 `0`) |
+| classification_image_path | 字符串 | 分类后的照片存储路径 |
-- **用户管理**
- - 上传用户头像,自动提取最大人脸并生成面部特征码。
- - 编辑和删除用户信息。
+## 主要功能实现
+### 1. 用户管理
+- **添加用户**:用户可上传照片,系统提取人脸编码并存入数据库。
+- **查看用户列表**:显示所有用户及其头像。
+- **删除用户**:从数据库中删除用户信息及相关照片。
-- **照片上传**
- - 支持批量照片上传。
- - 自动检测每张照片中最大的那张人脸,提取面部特征码,与已注册用户进行比对。
- - 匹配成功后将照片移动到用户目录,并记录拍摄时间。
+### 2. 照片上传与分类
+- **上传照片**:用户上传照片,系统解析 EXIF 数据确定拍摄时间。
+- **人脸匹配分类**:系统根据数据库中的人脸编码进行匹配,分类并存储到相应目录。
+- **存储路径规则**:
+ ```
+ static/uploads/users/{用户名}/{日期(年-月-日)}/照片文件名
+ ```
-- **查询功能**
- - 根据用户名和拍摄日期查询用户照片。
+### 3. 照片查询
+- **基于姓名和日期查询**:用户输入姓名和日期,系统返回符合条件的照片。
+- **展示分类结果**:前端页面显示照片缩略图,并提供原始照片下载链接。
-- **其他功能**
- - 根据图片 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. 照片上传(`upload` 端点)
+```python
+@app.route('/upload', methods=['GET', 'POST'])
+def upload():
+ if request.method == 'POST':
+ photos = request.files.getlist('photos')
+ classify_photos(photos) # 进行分类处理
+
+ # 获取最新照片并关联用户信息
+ recent_photos = Photo.query.order_by(Photo.created_at.desc()).limit(len(photos)).all()
+ photos_with_users = []
+ for photo in recent_photos:
+ user = User.query.get(photo.user_id)
+ photos_with_users.append({
+ "filename": photo.classification_image_path,
+ "username": user.username if user else "未知",
+ "created_at": photo.created_at
+ })
+
+ return render_template('upload.html', photos=photos_with_users)
+
+ return render_template('upload.html', photos=[])
```
-## 安装与配置
+### 2. 照片查询(`search` 端点)
+```python
+@app.route('/search', methods=['POST'])
+def search():
+ name = request.form['name']
+ date = request.form['date']
+
+ user = User.query.filter_by(username=name).first()
+ if not user:
+ return render_template('search.html', photos=[], message="用户不存在")
+
+ photos = Photo.query.filter_by(user_id=user.id).filter(Photo.created_at.like(f"{date}%")).all()
+ return render_template('search.html', photos=photos)
+```
-1. **克隆项目**
+## 前端页面示例
+### 1. 照片查询页面(`search.html`)
+```html
+
+```
- ```bash
- git clone
- cd photo_project
- ```
+## 目录结构
+```
+project_root/
+│── app.py # Flask 入口文件
+│── functions.py # 处理照片分类等功能
+│── models.py # 数据库模型
+│── templates/
+│ ├── base.html # 公共模板
+│ ├── upload.html # 上传页面
+│ ├── search.html # 查询页面
+│── static/
+│ ├── uploads/ # 存储照片
+│ ├── styles.css # 样式表
+│── .venv/ # 虚拟环境
+│── requirements.txt # 依赖文件
+```
-2. **创建虚拟环境并安装依赖**
-
- ```bash
- python -m venv .venv
- source .venv/bin/activate # Linux/macOS
- .venv\Scripts\activate # Windows
+## 部署方式
+1. **安装依赖**
+ ```sh
pip install -r requirements.txt
```
-
-3. **配置环境变量**
-
- 如果需要自定义密钥,可以设置环境变量 `SECRET_KEY`,例如:
-
- ```bash
- export SECRET_KEY='your_secret_key'
+2. **运行 Flask 服务器**
+ ```sh
+ flask run
```
+3. **访问应用**
+ 在浏览器中打开 `http://127.0.0.1:5000`
-4. **初始化数据库**
+## 未来优化方向
+- **添加身份验证**:使用 Flask-Login 实现用户登录功能。
+- **优化分类算法**:提高人脸识别和分类的准确率。
+- **支持云存储**:将照片存储到云端,如 AWS S3 或阿里云 OSS。
- 运行项目时,系统会自动在应用上下文中创建数据库表。
-
-## 运行项目
-
-在项目根目录下执行:
-
-```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`。
-
-## 版权与许可证
-
-本项目仅供学习和测试使用,具体版权和许可证信息请根据实际情况添加。
+---
+本项目旨在提供一个高效、易用的照片管理系统,欢迎优化和扩展!
diff --git a/app.py b/app.py
index 2e2611b..0ff99b9 100644
--- a/app.py
+++ b/app.py
@@ -1,30 +1,29 @@
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 functions import *
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 # 禁用修改追踪
+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()
@@ -35,7 +34,6 @@ 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():
@@ -70,14 +68,30 @@ def edit_existing_user(user_id):
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')
+ photos = request.files.getlist('photos') # 获取上传的照片
+ classify_photos(photos) # 进行照片分类处理
+
+ # 获取数据库中最新的照片记录,按照创建时间排序
+ recent_photos = Photo.query.order_by(Photo.created_at.desc()).limit(len(photos)).all()
+
+ # 组装数据:获取照片对应的用户信息
+ photos_with_users = []
+ for photo in recent_photos:
+ user = User.query.get(photo.user_id) # 获取用户信息
+ photos_with_users.append({
+ "filename": photo.classification_image_path, # 分类后的照片路径
+ "username": user.username if user else "未知", # 用户名
+ "created_at": photo.created_at # 照片创建时间
+ })
+
+ return render_template('upload.html', photos=photos_with_users)
+
+ return render_template('upload.html', photos=[])
+
# 照片搜索
diff --git a/functions.py b/functions.py
index 3342ad0..26aec0b 100644
--- a/functions.py
+++ b/functions.py
@@ -1,243 +1,188 @@
-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 json
import numpy as np
+import face_recognition
+import piexif
+from datetime import datetime
+from werkzeug.utils import secure_filename
+from PIL import Image
+from models import db, User, Photo
-def process_face_encoding(photo_path):
- """提取人脸特征编码并裁剪出最大的面部区域"""
- # 加载图片
+# 目录路径
+UPLOAD_FOLDER = 'static/uploads'
+USER_FOLDER = os.path.join(UPLOAD_FOLDER, 'users')
+PHOTO_FOLDER = os.path.join(UPLOAD_FOLDER, 'photos')
+TEMP_FOLDER = os.path.join(UPLOAD_FOLDER, 'temp')
+
+# 确保目录存在
+for folder in [USER_FOLDER, PHOTO_FOLDER, TEMP_FOLDER]:
+ os.makedirs(folder, exist_ok=True)
+
+
+def extract_face_encoding(photo_path):
+ """提取人脸编码"""
image = face_recognition.load_image_file(photo_path)
+ encodings = face_recognition.face_encodings(image)
+ return encodings[0] if encodings else None
- # 获取所有人脸的位置
- face_locations = face_recognition.face_locations(image)
- if not face_locations:
- return None # 没有检测到人脸
+def save_with_exif(image_path, save_path):
+ """保存图片并保留 EXIF 信息"""
+ image = Image.open(image_path)
+ exif_data = image.getexif()
+ if exif_data:
+ exif_bytes = piexif.dump(dict(exif_data))
+ image.save(save_path, exif=exif_bytes)
+ else:
+ image.save(save_path)
+ return exif_data
- # 找到最大的人脸
- 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 format_exif_date(exif_data):
+ """格式化 EXIF 时间为 YYYY-MM-DD"""
+ try:
+ return datetime.strptime(exif_data[306], "%Y:%m:%d %H:%M:%S").strftime("%Y-%m-%d")
+ except (KeyError, ValueError, TypeError):
+ 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)
+ """添加用户并提取人脸编码"""
+ if not username or not userphoto:
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)
+ user_dir = os.path.join(USER_FOLDER, username)
os.makedirs(user_dir, exist_ok=True)
- new_photo_path = os.path.join(user_dir, photo.filename)
+ photo_path = os.path.join(user_dir, "photo.jpg")
+ userphoto.save(photo_path)
- try:
- with Image.open(photo) as img:
- img.save(new_photo_path, quality=95)
- except Exception as e:
- print(f"保存图片时出错: {e}")
+ encoding = extract_face_encoding(photo_path)
+ if encoding is None:
+ return False
- # 保存到数据库
- photo_record = Photo(user_id=user.id, created_at=timestamp, classification=True,
- classification_image_path=new_photo_path)
- db.session.add(photo_record)
+ new_user = User(username=username, encoding=json.dumps(encoding.tolist()), photo=photo_path)
+ db.session.add(new_user)
db.session.commit()
+ return True
- print(f"照片 {photo.filename} 已保存至 {new_photo_path}")
+
+def edit_user(user_id, new_name, new_photo):
+ """编辑用户信息"""
+ user = User.query.get(user_id)
+ if not user:
+ return False
+
+ user.username = new_name
+
+ if new_photo:
+ user_dir = os.path.join(USER_FOLDER, new_name)
+ os.makedirs(user_dir, exist_ok=True)
+
+ photo_path = os.path.join(user_dir, secure_filename(new_photo.filename))
+ new_photo.save(photo_path)
+
+ encoding = extract_face_encoding(photo_path)
+ if encoding:
+ user.encoding = json.dumps(encoding.tolist())
+ user.photo = photo_path
+
+ db.session.commit()
+ return True
+
+
+
+import shutil
+
+def delete_user(user_id):
+ """删除用户及其所有相关文件"""
+ user = User.query.get(user_id)
+ if not user:
+ return False
+
+ user_folder = os.path.join("static", "uploads", "users", user.username)
+ user_folder = user_folder.replace("\\", "/")
+
+ # 删除整个用户文件夹及其内容
+ if os.path.exists(user_folder):
+ shutil.rmtree(user_folder)
+
+ db.session.delete(user)
+ db.session.commit()
+ return True
+
+
+
+def match_face(photo_path, threshold=0.30):
+ """匹配数据库中的人脸,一旦找到合适的直接返回"""
+ known_users = User.query.all()
+ if not known_users:
+ return None
+
+ unknown_encoding = extract_face_encoding(photo_path)
+ if unknown_encoding is None:
+ return None
+
+ for user in known_users:
+ if not user.encoding:
+ continue
+
+ try:
+ stored_encoding = np.array(json.loads(user.encoding))
+ if stored_encoding.size == 0 or stored_encoding.ndim != 1:
+ continue
+ except (json.JSONDecodeError, TypeError):
+ continue # 解析失败,跳过
+
+ if np.linalg.norm(stored_encoding - unknown_encoding) < threshold:
+ return user # 直接返回匹配的用户
+
+ return None
def classify_photos(photos):
- """处理上传的照片"""
+ """分类照片"""
for photo in photos:
- temp_photo_path = save_temp_photo(photo)
- cropped_face_path = get_largest_face(temp_photo_path)
+ filename = secure_filename(photo.filename)
+ temp_photo_path = os.path.join(TEMP_FOLDER, filename)
+ temp_photo_path = temp_photo_path.replace("\\", "/")
- if not cropped_face_path:
- print(f"没有检测到人脸:{photo.filename}")
+ exif_data = save_with_exif(photo, temp_photo_path)
+ date_str = format_exif_date(exif_data)
+ if not date_str:
+ os.remove(temp_photo_path) # 无效时间的照片直接删除
continue
- matched_user = match_face(cropped_face_path)
- timestamp = get_exif_time(temp_photo_path)
-
+ matched_user = match_face(temp_photo_path)
if matched_user:
- save_classified_photo(photo, matched_user, timestamp)
+ user_folder = os.path.join(USER_FOLDER, matched_user.username, date_str)
+ os.makedirs(user_folder, exist_ok=True)
+
+ classified_photo_path = os.path.join(user_folder, filename)
+ classified_photo_path =classified_photo_path.replace("\\", "/")
+ os.rename(temp_photo_path, classified_photo_path)
+
+ new_photo = Photo(
+ user_id=matched_user.id,
+ created_at=date_str,
+ classification=True,
+ classification_image_path=classified_photo_path
+ )
+ db.session.add(new_photo)
else:
- print(f"没有找到匹配的人脸:{photo.filename}")
+ os.remove(temp_photo_path) # 没匹配到的照片删除
+
+ db.session.commit()
-# 根据姓名和时间查找照片
-def search_photos(name, date):
- user = User.query.filter_by(username=name).first()
+def search_photos(username, date):
+ """搜索用户的照片"""
+ user = User.query.filter_by(username=username).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)
+ query = Photo.query.filter_by(user_id=user.id)
+ if date:
+ query = query.filter(Photo.created_at.like(f"{date}%"))
- # 获取图片中的人脸特征编码
- 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
+ return query.all()
diff --git a/instance/database.db b/instance/database.db
deleted file mode 100644
index 1dbbf68..0000000
Binary files a/instance/database.db and /dev/null differ
diff --git a/models.py b/models.py
index 9ec99cc..267760d 100644
--- a/models.py
+++ b/models.py
@@ -14,4 +14,5 @@ class Photo(db.Model):
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)
\ No newline at end of file
+ classification_confidence = db.Column(db.Float, default=0.0) # ✅ 添加字段
+ classification_image_path = db.Column(db.String(255), nullable=True)
diff --git a/static/styles.css b/static/styles.css
index 0f93783..a41e938 100644
--- a/static/styles.css
+++ b/static/styles.css
@@ -7,15 +7,16 @@ body {
.container {
width: 80%;
- margin: 0 auto;
+ margin: 50px auto;
padding: 20px;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
- margin-top: 50px;
+ border-radius: 8px;
}
h1, h2 {
color: #333;
+ text-align: center;
}
form {
@@ -23,7 +24,7 @@ form {
}
.form-group {
- margin-bottom: 10px;
+ margin-bottom: 15px;
}
label {
@@ -40,19 +41,47 @@ input[type="text"], input[type="file"], input[type="date"] {
border: 1px solid #ddd;
}
-button[type="submit"] {
+button {
padding: 10px 20px;
- background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
+button[type="submit"] {
+ background-color: #4CAF50;
+}
+
button[type="submit"]:hover {
background-color: #45a049;
}
+.btn-edit, .btn-delete {
+ padding: 5px 10px;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.btn-edit {
+ background-color: #007BFF;
+}
+
+.btn-edit:hover {
+ background-color: #0056b3;
+}
+
+.btn-delete {
+ background-color: #DC3545;
+}
+
+.btn-delete:hover {
+ background-color: #c82333;
+}
+
+/* 表格样式 */
table {
width: 100%;
border-collapse: collapse;
@@ -68,48 +97,35 @@ th, td {
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;
+ justify-content: center;
+ gap: 15px;
+ margin-top: 20px;
}
.photo-item {
- margin: 10px;
+ border: 1px solid #ddd;
+ padding: 10px;
+ border-radius: 5px;
+ text-align: center;
width: 200px;
- height: 200px;
- overflow: hidden;
+ box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
+ background-color: white;
}
.photo-item img {
width: 100%;
- height: 100%;
- object-fit: cover;
+ height: auto;
+ border-radius: 5px;
}
+/* 用户头像 */
.user-photo {
- max-width: 100px; /* 设置最大宽度 */
- max-height: 100px; /* 设置最大高度 */
- width: auto; /* 保持宽高比例 */
- height: auto; /* 保持宽高比例 */
+ max-width: 100px;
+ max-height: 100px;
+ width: auto;
+ height: auto;
}
diff --git a/static/uploads/temp/IMG_20250119_120604.jpg b/static/uploads/temp/IMG_20250119_120604.jpg
deleted file mode 100644
index 2a44ef8..0000000
Binary files a/static/uploads/temp/IMG_20250119_120604.jpg and /dev/null differ
diff --git a/static/uploads/temp/IMG_20250119_120604_face.jpg b/static/uploads/temp/IMG_20250119_120604_face.jpg
deleted file mode 100644
index 4fb7ea9..0000000
Binary files a/static/uploads/temp/IMG_20250119_120604_face.jpg and /dev/null differ
diff --git a/static/uploads/users/张庭嘉/photo.jpg b/static/uploads/users/张庭嘉/photo.jpg
deleted file mode 100644
index 2a44ef8..0000000
Binary files a/static/uploads/users/张庭嘉/photo.jpg and /dev/null differ
diff --git a/static/uploads/users/张庭嘉/photo_cropped.jpg b/static/uploads/users/张庭嘉/photo_cropped.jpg
deleted file mode 100644
index 4fb7ea9..0000000
Binary files a/static/uploads/users/张庭嘉/photo_cropped.jpg and /dev/null differ
diff --git a/templates/search.html b/templates/search.html
index fd2011d..460a3a9 100644
--- a/templates/search.html
+++ b/templates/search.html
@@ -17,7 +17,7 @@
-
+
@@ -27,11 +27,12 @@
{% for photo in photos %}
-
 }})
+
{% endfor %}
{% endif %}
+