first commit

This commit is contained in:
yumoqing 2025-06-24 11:49:37 +08:00
commit 4c6c422ba5
12 changed files with 442 additions and 0 deletions

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# FindPerson
a module to save and find face in image
* find and save all the face in image and save them in milvus vector database
* find all similarity faces in vector database with face in a given image
## dependent
see the requirements.txt
## installation
```
pip install git+https://github.com/yumoqing/findperson
```

15
app/imagefind.py Normal file
View File

@ -0,0 +1,15 @@
from ahserver.webapp import webapp
from ahserver.serverenv import ServerEnv
from appPublic.worker import awaitify
from findperson.init import load_findperson
def get_module_dbname(name):
return 'imagefind'
def init():
g = ServerEnv()
g.get_module_dbname = get_module_dbname
load_findperson
if __name__ == '__main__':
webapp(init)

53
conf/config.json Normal file
View File

@ -0,0 +1,53 @@
{
"logger":{
"name":"rag",
"levelname":"info",
"logfile":"$[workdir]$/logs/pcapi.log"
},
"vectordb_path":"$[workdir]$/vdb",
"authentication":{
"user":"kyycloud",
"password":"Kyy@123456",
"iplist":[
"47.93.12.75",
"127.0.0.1",
"117.50.205.57",
"10.60.179.61"
]
},
"filesroot":"$[workdir]$/files",
"website":{
"paths":[
["$[workdir]$/wwwroot",""]
],
"client_max_size":10000,
"host":"0.0.0.0",
"port":20001,
"coding":"utf-8",
"indexes":[
"index.ui",
"index.dspy",
"index.md"
],
"startswiths":[
{
"leading":"/idfile",
"registerfunction":"idFileDownload"
}
],
"processors":[
[".ui","bui"],
[".dspy","dspy"],
[".md","md"]
],
"session_max_time":3000,
"session_issue_time":2500
},
"langMapping":{
"zh-Hans-CN":"zh-cn",
"zh-CN":"zh-cn",
"en-us":"en",
"en-US":"en"
}
}

2
findperson/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from .version import __version__

View File

@ -0,0 +1,50 @@
import face_recognition
import numpy as np
from sklearn.decomposition import PCA
from appPublic.uniqueID import getID
def expand_to_768_zero_padding(embedding):
return np.pad(embedding, (0, 768 - 128), mode="constant")
class ImageImbedding:
def get_image_faces_vector(self, image_path):
image = face_recognition.load_image_file(image_path)
face_encodings = face_recognition.face_encodings(image, face_locations) # 计算 128 维特征
return face_encodings
def image2faces(self, image_path, imgid=None):
image = face_recognition.load_image_file(image_path)
# 检测人脸并提取嵌入向量
face_locations = face_recognition.face_locations(image) # 找到所有人脸
face_encodings = face_recognition.face_encodings(image, face_locations) # 计算 128 维特征
if imgid is None:
imgid = getID()
faces = []
ret = {
"id": imgid,
"image": image_path,
"faces": faces
}
for i, v in enumerate(face_encodings):
# print('vector v has', v.shape, 'shape', v)
# v_768 = expand_to_768_zero_padding(v)
# print(v.shape, v_768.shape)
top, right, bottom, left = face_locations[i]
face = {
"id": getID(),
"imgid": imgid,
"imagepath":image_path,
"vector": v,
"left": left,
"top": top,
"right": right,
"bottom": bottom
}
faces.append(face)
return ret
if __name__ == '__main__':
ii = ImageImbedding()
v = ii.image2vector('test.png')

65
findperson/imageface.py Normal file
View File

@ -0,0 +1,65 @@
import json
import face_recognition
from PIL import Image
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from .image_imbedding import ImageImbedding
from .vectordb import MilvusVectorDB
class ImageFaces:
"""
implements image to faces and to vector and save the face's vector to vector database
and search similarities faces in vector database with faces in argument image_path
"""
def __init__(self):
self.vdb = MilvusVectorDB()
self.ii = ImageImbedding()
def save_faces(self, userid, image_path, imgid=None):
"""
find all the faces in image identified by image_path,
and save the face's info to vector database
"""
v = self.ii.image2faces(image_path, imgid=imgid)
for face in v['faces']:
face['userid'] = userid
self.vdb.add('faces', face)
return v
def find_face_in_image(self, image_path, limit=5):
"""
similarities search for all the faces in image identified by image_path
return faces info in image attached similarities face's info
"""
self.vdb.create_vector_index('faces')
v = self.ii.image2faces(image_path)
for face in v['faces']:
ret = self.vdb.search_by_vector('faces', face['vector'], limit=limit)
ret = [DictObject(**d) for d in ret.pop()]
return ret
return []
if __name__ == '__main__':
import sys
from appPublic.jsonConfig import getConfig
config = getConfig('./', NS={'workdir':'.'})
if len(sys.argv) < 3:
print(f'{sys.argv[0]} COMMAND imgfile ...')
sys.exit(1)
act = sys.argv[1]
i_f = ImageFaces('oooo1')
if act == 'add':
for f in sys.argv[2:]:
v = i_f.save_faces(f)
else:
fif = sys.argv[2]
v = i_f.find_face_in_image(fif)
print(v['id'], v['image'])
for face in v['faces']:
t = face['similarities']
for d in t:
print(f'{d=}')
print(f"{face['id']=},{face['imgid']=}")

13
findperson/init.py Normal file
View File

@ -0,0 +1,13 @@
from ahserver.serverenv import ServerEnv
from appPublic.worker import awaitify
from .imageface import ImageFaces
from .utils_clip import CLIPEmbedder
def load_findperson():
g = ServerEnv()
ifs = ImageFaces()
ce = CLIPEmbedder()
g.find_face_in_image = awaitify(ifs.find_face_in_image)
g.save_faces = awaitify(ifs.save_faces)
g.embed_image = ce.embed_image
g.embed_text = ce.embed_text

127
findperson/milvus_utils.py Normal file
View File

@ -0,0 +1,127 @@
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from pymilvus import MilvusClient, DataType, FieldSchema, CollectionSchema
class MilvusCollection:
def __init__(self, client, name, dimension):
self.client = client
self.name = name,
self.dimension = dimension
sellf.set_fields()
self.set_indexes()
self.vectorfield=None
self.output_fields = []
def set_fields(self):
self.fields = []
def set_indexes():
self.indexes = []
def create_vector_index(self):
index_params = self.client.prepare_index_params()
for idx in self.indexes:
index_params.add_index(**idx)
self.client.create_index(self.name, index_params)
def create_table_if_not_exists(self):
if not self.client.has_collection(collection_name=self.name):
fields = []
for f in self.fields:
f = DictObject(**f)
if f.dtype == DataType.FLOAT_VECTOR:
self.vectorfield = f.name
f.dim=self.dimension
else:
self.output_fields.append(f.name)
fields.append(FieldSchema(**f))
schema = CollectionSchema(fields=fields,auto_id=False, enable_dynamic_field=True)
self.client.create_collection(
collection_name=self.name,
dimension=self.dimension,
schema=schema
)
def add(self, ns,flush=False):
self.create_table_if_not_exists(self.name)
ret = self.client.insert(collection_name=self.name, data=ns)
if flush:
self.create_vector_index(self.name)
return ret
def search_by_vector(self, vector, limit=5):
self.create_table_if_not_exists(self.name)
return self.client.search(
collection_name=self.name,
anns_field=self.vectorfield,
data=[vector],
output_fields=self.output_fields,
limit=limit
)
def delete(self, id):
self.create_table_if_not_exists(self.name)
return self.client.delete(
collection_name=self.name,
ids=[id]
)
class MilvusVectorDB:
def __init__(self, dimension=128):
config = getConfig()
dbname = config.vectordb_path
self.dbname = dbname
self.dimension = dimension
self.client = MilvusClient(dbname)
class Faces(MilvusCollection):
def set_fields(self):
self.fields = [
dict(name='id',dtype=DataType.VARCHAR,
auto_id=False,
is_primary=True, max_length=34),
dict(name='vector', dtype=DataType.FLOAT_VECTOR,
dim=self.dimension, description='vector'),
dict(name='imgid', dtype=DataType.VARCHAR, max_length=34),
dict(name='imagepath', dtype=DataType.VARCHAR, max_length=500),
dict(name='top', dtype=DataType.INT32),
dict(name='left', dtype=DataType.INT32),
dict(name='right', dtype=DataType.INT32),
dict(name='bottom', dtype=DataType.INT32),
dict(name='userid', dtype=DataType.VARCHAR, max_length=34)
]
def set_indexs(self):
self.indexes = {
"index_type": "IVF_FLAT", # Choose index type
"field_name":"vector",
"index_name":"vector_index",
# Distance metric: L2 (Euclidean) or IP (Inner Product)
"metric_type": "L2",
"params": {"nlist": 128} # Number of clusters for IVF
}
class ImageVector(MilvusCollection):
def set_fields(self):
self.fields = [
dict(name='id',dtype=DataType.VARCHAR,
auto_id=False,
is_primary=True, max_length=34),
dict(name='vector', dtype=DataType.FLOAT_VECTOR,
dim=self.dimension, description='vector'),
dict(name='imgid', dtype=DataType.VARCHAR, max_length=34),
dict(name='imagepath', dtype=DataType.VARCHAR, max_length=500),
dict(name='top', dtype=DataType.INT32),
dict(name='left', dtype=DataType.INT32),
dict(name='right', dtype=DataType.INT32),
dict(name='bottom', dtype=DataType.INT32),
dict(name='userid', dtype=DataType.VARCHAR, max_length=34)
]
def set_indexs(self):
self.indexes = {
"index_type": "IVF_FLAT", # Choose index type
"field_name":"vector",
"index_name":"vector_index",
# Distance metric: L2 (Euclidean) or IP (Inner Product)
"metric_type": "L2",
"params": {"nlist": 128} # Number of clusters for IVF
}

27
findperson/utils_clip.py Normal file
View File

@ -0,0 +1,27 @@
from appPublic.jsonConfig import getConfig
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import torch
class CLIPEmbedder:
def __init__(self):
# model_id="laion/CLIP-ViT-H-14-laion2B-s32B-b79K"
self.config = getConfig()
model_path = self.config.clip_model_path
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = CLIPModel.from_pretrained(model_path).to(self.device)
self.processor = CLIPProcessor.from_pretrained(model_path)
def embed_image(self, image_path):
image = Image.open(image_path).convert("RGB")
inputs = self.processor(images=image, return_tensors="pt").to(self.device)
with torch.no_grad():
embedding = self.model.get_image_features(**inputs)
return embedding[0].cpu().numpy()
def embed_text(self, text):
inputs = self.processor(text=text, return_tensors="pt").to(self.device)
with torch.no_grad():
embedding = self.model.get_text_features(**inputs)
return embedding[0].cpu().numpy()

70
findperson/vectordb.py Normal file
View File

@ -0,0 +1,70 @@
from appPublic.jsonConfig import getConfig
from pymilvus import MilvusClient, DataType, FieldSchema, CollectionSchema
class MilvusVectorDB:
def __init__(self, dimension=128):
config = getConfig()
dbname = config.vectordb_path
self.dbname = dbname
self.dimension = dimension
self.client = MilvusClient(dbname)
def create_vector_index(self, tblname):
self.client.flush(tblname)
index_params = self.client.prepare_index_params()
index_params.add_index(**{
"index_type": "IVF_FLAT", # Choose index type
"field_name":"vector",
"index_name":"vector_index",
"metric_type": "L2", # Distance metric: L2 (Euclidean) or IP (Inner Product)
"params": {"nlist": 128} # Number of clusters for IVF
})
self.client.create_index(tblname, index_params)
print("index created")
def create_table_if_not_exists(self, tblname):
if not self.client.has_collection(collection_name=tblname):
fields = [
FieldSchema(name='id',dtype=DataType.VARCHAR,
auto_id=False,
is_primary=True, max_length=34),
FieldSchema(name='vector', dtype=DataType.FLOAT_VECTOR,
dim=self.dimension, description='vector'),
FieldSchema(name='imgid', dtype=DataType.VARCHAR, max_length=34),
FieldSchema(name='imagepath', dtype=DataType.VARCHAR, max_length=500),
FieldSchema(name='top', dtype=DataType.INT32),
FieldSchema(name='left', dtype=DataType.INT32),
FieldSchema(name='right', dtype=DataType.INT32),
FieldSchema(name='bottom', dtype=DataType.INT32),
FieldSchema(name='userid', dtype=DataType.VARCHAR, max_length=34)
]
schema = CollectionSchema(fields=fields,auto_id=False, enable_dynamic_field=True)
self.client.create_collection(
collection_name=tblname,
dimension=self.dimension,
schema=schema
)
def add(self, tblname, ns,flush=False):
self.create_table_if_not_exists(tblname)
ret = self.client.insert(collection_name=tblname, data=ns)
if flush:
self.create_vector_index(tblname)
return ret
def search_by_vector(self, tblname, vector, limit=5):
self.create_table_if_not_exists(tblname)
return self.client.search(
collection_name=tblname,
anns_field="vector",
data=[vector],
output_fields=["imgid", "imagepath", "top","right","bottom", "left", "userid" ],
limit=limit
)
def delete(self, tblname, id):
self.create_table_if_not_exists(tblname)
return self.client.delete(
collection_name=tblname,
ids=[id]
)

1
findperson/version.py Normal file
View File

@ -0,0 +1 @@
__version__ = '0.0.1'

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
#
pillow
face_recognition
pymilvus