Compare commits

...

No commits in common. "main" and "master" have entirely different histories.
main ... master

8 changed files with 313 additions and 35 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
__pycache__/
*.pyc
*.pyo
logs/
*.log
nohup.out
nohup_gpu*.out
py3/
*.egg-info/
*.pid

View File

@ -1,35 +0,0 @@
# Face Service — 人脸识别服务
## 概述
基于 InsightFace (buffalo_l) 的人脸检测与识别服务,部署于 GPU 服务器提供人脸检测、特征提取、1:1 比对、1:N 识别 API。
## 模型
- InsightFace buffalo_l (检测 + 识别)
- 检测尺寸: 640×640
- 特征维度: 512
## API
| 端点 | 方法 | 说明 |
|------|------|------|
| /api/status | GET | 服务状态和模型信息 |
| /api/detect | POST | 人脸检测 (返回 bbox + 关键点) |
| /api/recognize | POST | 人脸识别 (返回特征向量 + 匹配身份) |
| /api/compare | POST | 1:1 比对 (两张人脸 → 相似度) |
| /api/face-detect | POST | 别名: 同 /api/detect |
| /api/face-recognize | POST | 别名: 同 /api/recognize |
| /api/face-compare | POST | 别名: 同 /api/compare |
## 部署
```bash
cd /data/ymq/face-service
bash build.sh
sudo systemctl restart face-service
```
## 端口
9091

6
ah.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding:utf-8 -*-
from ahserver.webapp import webapp
from init import load_face_service
if __name__ == '__main__':
webapp(load_face_service)

57
build.sh Executable file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Face Service (InsightFace)
set -e
cd "$(dirname "$0")"
SERVICE_NAME="face-service"
PORT=9091
GPU=5
PY=/data/ymq/wan22-service/py3/bin/python
action="${1:-status}"
case "$action" in
deploy|update)
echo "=== $SERVICE_NAME Deploy (GPU $GPU, port $PORT) ==="
if [ -f ah.pid ] && kill -0 $(cat ah.pid) 2>/dev/null; then
kill $(cat ah.pid) 2>/dev/null || true; sleep 2
fi
if [ -d .git ] && [ -f .git/HEAD ]; then
git pull origin master 2>/dev/null || true
fi
mkdir -p logs files wwwroot
export PYTHONPATH="$(pwd)"
export CUDA_VISIBLE_DEVICES=$GPU
nohup $PY ah.py > nohup.out 2>&1 &
echo $! > ah.pid
echo "Started PID $(cat ah.pid) on port $PORT (GPU $GPU)"
sleep 5
if curl -s http://localhost:$PORT/api/status > /dev/null 2>&1; then
echo "Service healthy"
else
echo "WARNING: not responding, check nohup.out"
tail -20 nohup.out
fi
;;
stop)
if [ -f ah.pid ]; then
kill $(cat ah.pid) 2>/dev/null || true; rm -f ah.pid; echo "Stopped"
else echo "Not running"; fi
;;
start)
mkdir -p logs files wwwroot
export PYTHONPATH="$(pwd)"; export CUDA_VISIBLE_DEVICES=$GPU
nohup $PY ah.py > nohup.out 2>&1 &
echo $! > ah.pid; echo "Started PID $(cat ah.pid)"
;;
status)
echo "=== $SERVICE_NAME Status ==="
if [ -f ah.pid ] && kill -0 $(cat ah.pid) 2>/dev/null; then
echo "Process: running (PID $(cat ah.pid))"
else echo "Process: not running"; fi
echo "Port: $PORT, GPU: $GPU"
if curl -s --max-time 3 http://localhost:$PORT/api/status > /dev/null 2>&1; then
echo "HTTP: OK"
else echo "HTTP: not responding"; fi
;;
*) echo "Usage: $0 {deploy|update|stop|start|status}"; exit 1 ;;
esac

27
conf/config.json Normal file
View File

@ -0,0 +1,27 @@
{
"password_key": "FaceService2026Key",
"filesroot": "$[workdir]$/files",
"logger": {
"name": "face-service",
"levelname": "info",
"logfile": "$[workdir]$/logs/face-service.log"
},
"website": {
"paths": [["$[workdir]$/wwwroot", ""]],
"client_max_size": 52428800,
"host": "0.0.0.0",
"port": 9091,
"coding": "utf-8",
"indexes": ["index.html"],
"startswiths": [
{"leading": "/api/status", "registerfunction": "status"},
{"leading": "/api/detect", "registerfunction": "detect"},
{"leading": "/api/recognize", "registerfunction": "recognize"},
{"leading": "/api/compare", "registerfunction": "compare"}
],
"processors": [
[".tmpl", "tmpl"], [".app", "app"], [".ui", "bui"],
[".dspy", "dspy"], [".md", "md"]
]
}
}

104
init.py Normal file
View File

@ -0,0 +1,104 @@
# -*- coding:utf-8 -*-
from traceback import format_exc
from ahserver.serverenv import ServerEnv
from appPublic.registerfunction import RegisterFunction
from appPublic.log import exception
import json
async def status_handler(request, params_kw, *args, **kwargs):
import sys, os
sys.path.insert(0, os.getcwd())
from workers.face_model import health_check
health = health_check()
return json.dumps({
"service": "face-service",
"model": health["model"],
"model_loaded": health["loaded"],
"det_size": health["det_size"],
"endpoints": ["/api/status", "/api/detect", "/api/recognize", "/api/compare"]
}, indent=2, ensure_ascii=False)
async def detect_handler(request, params_kw, *args, **kwargs):
import sys, os, time
sys.path.insert(0, os.getcwd())
from workers.face_model import detect
try:
images = params_kw.get("images", [])
if not images:
return json.dumps({"error": "images list required"})
start = time.time()
all_results = []
for img in images:
result = detect(img)
all_results.append(result)
elapsed = round(time.time() - start, 4)
return json.dumps({
"status": "SUCCEEDED",
"results": all_results,
"elapsed": elapsed
}, ensure_ascii=False)
except Exception as e:
exception(f"{e}, {format_exc()}")
return json.dumps({"error": str(e)})
async def recognize_handler(request, params_kw, *args, **kwargs):
import sys, os, time
sys.path.insert(0, os.getcwd())
from workers.face_model import recognize
try:
images = params_kw.get("images", [])
if not images:
return json.dumps({"error": "images list required"})
start = time.time()
all_results = []
for img in images:
result = recognize(img)
all_results.append(result)
elapsed = round(time.time() - start, 4)
return json.dumps({
"status": "SUCCEEDED",
"results": all_results,
"elapsed": elapsed
}, ensure_ascii=False)
except Exception as e:
exception(f"{e}, {format_exc()}")
return json.dumps({"error": str(e)})
async def compare_handler(request, params_kw, *args, **kwargs):
import sys, os
sys.path.insert(0, os.getcwd())
from workers.face_model import compare
try:
embedding1 = params_kw.get("embedding1")
embedding2 = params_kw.get("embedding2")
if not embedding1 or not embedding2:
return json.dumps({"error": "embedding1 and embedding2 required"})
result = compare(embedding1, embedding2)
return json.dumps({
"status": "SUCCEEDED",
**result
}, ensure_ascii=False)
except Exception as e:
exception(f"{e}, {format_exc()}")
return json.dumps({"error": str(e)})
def load_face_service():
"""Register API handlers"""
env = ServerEnv()
rf = RegisterFunction()
rf.register("status", status_handler)
rf.register("detect", detect_handler)
rf.register("recognize", recognize_handler)
rf.register("compare", compare_handler)

0
workers/__init__.py Normal file
View File

109
workers/face_model.py Normal file
View File

@ -0,0 +1,109 @@
# -*- coding:utf-8 -*-
"""InsightFace buffalo_l model wrapper."""
import os
import numpy as np
from PIL import Image
_model = None
_lock = False
MODEL_NAME = "buffalo_l"
def get_model():
global _model, _lock
if _model is None and not _lock:
_lock = True
try:
import insightface
from insightface.app import FaceAnalysis
_model = FaceAnalysis(name=MODEL_NAME, providers=['CUDAExecutionProvider'])
_model.prepare(ctx_id=0, det_size=(640, 640))
except Exception as e:
_lock = False
raise RuntimeError(f"Failed to load InsightFace: {e}")
return _model
def load_image(image_input):
"""Load image from path, URL, or base64."""
if isinstance(image_input, str):
if image_input.startswith("/") and os.path.exists(image_input):
return Image.open(image_input).convert("RGB")
elif image_input.startswith("http"):
import urllib.request
import io
with urllib.request.urlopen(image_input, timeout=10) as resp:
return Image.open(io.BytesIO(resp.read())).convert("RGB")
raise ValueError(f"Cannot load image: {image_input[:50] if isinstance(image_input, str) else 'unknown'}")
def detect(image_input):
"""Detect faces in an image. Returns list of face info."""
model = get_model()
img = load_image(image_input)
img_np = np.array(img)
faces = model.get(img_np)
results = []
for i, face in enumerate(faces):
results.append({
"face_id": f"face_{i}",
"bbox": face.bbox.tolist(),
"det_score": round(float(face.det_score), 4),
"age": int(face.age) if face.age > 0 else None,
"gender": "M" if face.gender == 1 else "F",
"embedding_dim": len(face.embedding)
})
return {"faces": results, "count": len(results)}
def recognize(image_input):
"""Get face embeddings from an image."""
model = get_model()
img = load_image(image_input)
img_np = np.array(img)
faces = model.get(img_np)
results = []
for i, face in enumerate(faces):
# Normalize embedding to unit vector
embedding = face.embedding / np.linalg.norm(face.embedding)
results.append({
"face_id": f"face_{i}",
"bbox": face.bbox.tolist(),
"det_score": round(float(face.det_score), 4),
"embedding": embedding.tolist()
})
return {"faces": results, "count": len(results)}
def compare(embed1, embed2):
"""Compare two face embeddings. Returns cosine similarity."""
e1 = np.array(embed1)
e2 = np.array(embed2)
# Normalize
e1 = e1 / np.linalg.norm(e1)
e2 = e2 / np.linalg.norm(e2)
similarity = float(np.dot(e1, e2))
return {
"similarity": round(similarity, 6),
"is_same": similarity > 0.4,
"confidence": "high" if similarity > 0.6 else ("medium" if similarity > 0.4 else "low")
}
def health_check():
"""Check model status."""
model = get_model()
return {
"model": MODEL_NAME,
"loaded": model is not None,
"det_size": [640, 640]
}