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