first commit
This commit is contained in:
commit
4c6c422ba5
14
README.md
Normal file
14
README.md
Normal 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
15
app/imagefind.py
Normal 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
53
conf/config.json
Normal 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
2
findperson/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .version import __version__
|
||||||
|
|
50
findperson/image_imbedding.py
Normal file
50
findperson/image_imbedding.py
Normal 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
65
findperson/imageface.py
Normal 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
13
findperson/init.py
Normal 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
127
findperson/milvus_utils.py
Normal 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
27
findperson/utils_clip.py
Normal 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
70
findperson/vectordb.py
Normal 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
1
findperson/version.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = '0.0.1'
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
pillow
|
||||||
|
face_recognition
|
||||||
|
pymilvus
|
||||||
|
|
Loading…
Reference in New Issue
Block a user