File indexing completed on 2025-01-05 03:58:08
0001 #!/usr/bin/env python3 0002 0003 # ============================================================ 0004 # 0005 # This file is a part of digiKam project 0006 # https://www.digikam.org 0007 # 0008 # Date : 2019-08-11 0009 # Description : A script to perform face recognition with OpenFace collection 0010 # 0011 # SPDX-FileCopyrightText: 2019 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com> 0012 # 0013 # SPDX-License-Identifier: BSD-3-Clause 0014 # 0015 # ============================================================ */ 0016 0017 from imutils import paths 0018 import imutils 0019 import cv2 0020 import argparse 0021 import os 0022 import numpy 0023 import sys 0024 0025 # Default values 0026 fileDir = os.path.dirname(os.path.realpath(__file__)) 0027 modelDir = os.path.join(fileDir, '..', 'models') 0028 dlibModelDir = os.path.join(modelDir, 'dlib') 0029 openfaceModelDir = os.path.join(modelDir, 'openface') 0030 imgDimDefault = 96 0031 0032 sys.path.append("/home/ttdinh/devel/personal/openface") 0033 0034 import openface 0035 0036 # Function to detect face 0037 def detectFaceCascade(image, detector): 0038 image = detectFaceNeutral(image, detector) 0039 image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 0040 image_gray = cv2.equalizeHist(image_gray) 0041 #-- Detect faces 0042 face = [] 0043 detectedFaces = detector.detectMultiScale(image_gray) 0044 # print(detectedFaces) 0045 for (x,y,w,h) in detectedFaces: 0046 face = image[y:y+h,x:x+w,:] 0047 if(face == []): 0048 face = image 0049 return face 0050 0051 def detectFaceNeutral(image, detector): 0052 # image = imutils.resize(image, width=600) 0053 return image 0054 0055 def detectFaceDNN(image, detector): 0056 face = [] 0057 (h, w) = image.shape[:2] 0058 # construct a blob from the image 0059 imageBlob = cv2.dnn.blobFromImage( 0060 cv2.resize(image, (300, 300)), 1.0, (300, 300), 0061 (104.0, 177.0, 123.0), swapRB=False, crop=False) 0062 # apply OpenCV's deep learning-based face detector to localize 0063 # faces in the input image 0064 detector.setInput(imageBlob) 0065 detections = detector.forward() 0066 # ensure at least one face was found 0067 if len(detections) > 0: 0068 # we're making the assumption that each image has only ONE 0069 # face, so find the bounding box with the largest probability 0070 i = numpy.argmax(detections[0, 0, :, 2]) 0071 confidence = detections[0, 0, i, 2] 0072 # print(confidence) 0073 # ensure that the detection with the largest probability also 0074 # means our minimum probability test (thus helping filter out 0075 # weak detections) 0076 if confidence >= 0.85: 0077 # compute the (x, y)-coordinates of the bounding box for 0078 # the face 0079 box = detections[0, 0, i, 3:7] * numpy.array([w, h, w, h]) 0080 (startX, startY, endX, endY) = box.astype("int") 0081 # extract the face ROI and grab the ROI dimensions 0082 face = image[startY:endY, startX:endX, :] 0083 (fH, fW) = face.shape[:2] 0084 # ensure the face width and height are sufficiently large 0085 if fW < 20 or fH < 20: 0086 face = [] 0087 if face == []: 0088 face = image 0089 return face 0090 0091 0092 def distanceToGroup(face, faceGroups): 0093 for faceRef in faceGroups: 0094 dist = cv2.norm(numpy.array(face)-numpy.array(faceRef),cv2.NORM_L2) 0095 return dist*1.0/len(faceGroups) 0096 0097 0098 def cosineDistance(v1, v2): 0099 vec1 = numpy.array(v1) 0100 vec2 = numpy.array(v2) 0101 return numpy.dot(vec1, vec2) / (cv2.norm(vec1,cv2.NORM_L2)*cv2.norm(vec2,cv2.NORM_L2)) 0102 0103 0104 def alignFace(face, alignTool, imgDim): 0105 bb = alignTool.getLargestFaceBoundingBox(face) 0106 if bb is None: 0107 print("Unable to find a face") 0108 return face 0109 alignedFace = alignTool.align(imgDim, face, bb, 0110 landmarkIndices=openface.AlignDlib.OUTER_EYES_AND_NOSE) 0111 if alignedFace is None: 0112 print("Unable to align face") 0113 return face 0114 # alignedFace = face 0115 return alignedFace 0116 0117 0118 def process(imagePath, detector, alignTool, imgDim, model): 0119 image = cv2.imread(imagePath) 0120 face = detectFaceDNN(image, detector) #[:,:,0] 0121 faceAligned = alignFace(face, alignTool, imgDim) 0122 faceBlob = cv2.dnn.blobFromImage(faceAligned, 1.0 / 255, 0123 (imgDim, imgDim), (0, 0, 0), swapRB=False, crop=True) # work with Pytorch openface model 0124 # faceBlob = cv2.dnn.blobFromImage(face, 1.0, 0125 # (224, 224), (0, 0, 0), swapRB=True, crop=False) # work with resnet 50 model 0126 model.setInput(faceBlob) 0127 return model.forward() 0128 0129 0130 # construct the argument parser and parse the arguments 0131 ap = argparse.ArgumentParser() 0132 ap.add_argument("-d", "--dataset", required=True, 0133 help="path to dataset directory") 0134 ap.add_argument("-m", "--model", required=False, 0135 help="path to deep learning model directory for OpenCV DNN", 0136 default=os.path.join(openfaceModelDir, 'nn4.small2.v1.t7')) 0137 ap.add_argument("-p", "--proto", required=False, 0138 help="path to deep learning proto model directory for OpenCV DNN") 0139 ap.add_argument("-dt", "--detector", required=False, 0140 help="path to detector model folder") 0141 ap.add_argument("-o", "--objects", required=True, 0142 help="number of objects to identify") 0143 ap.add_argument("-s", "--samples", required=True, 0144 help="number of samples per object") 0145 ap.add_argument("-r", "--ratio", required=True, 0146 help="ratio to divide test set / whole set") 0147 ap.add_argument("-fp", "--dlibFacePredictor", required=False, 0148 help="Path to dlib's face predictor.", 0149 default=os.path.join(dlibModelDir, "shape_predictor_68_face_landmarks.dat")) 0150 args = vars(ap.parse_args()) 0151 0152 0153 # Parse arguments 0154 nbObjects = int(args["objects"]) 0155 nbSamples = int(args["samples"]) 0156 ratio = float(args["ratio"]) 0157 dataset = args["dataset"] 0158 0159 0160 # Load detector 0161 protoPath = os.path.sep.join([args["detector"], "deploy.prototxt"]) 0162 modelPath = os.path.sep.join([args["detector"], 0163 "VGG_VOC0712_SSD_300x300_iter_120000.caffemodel"]) 0164 detector = cv2.dnn.readNetFromCaffe(protoPath, modelPath) 0165 # detector = cv2.CascadeClassifier(args["detector"]) 0166 0167 0168 # Load model 0169 # protoPath = os.path.sep.join([args["model"], "deploy.prototxt"]) 0170 # modelPath = os.path.sep.join([args["model"], "deploy.caffemodel"]) 0171 model = cv2.dnn.readNetFromTorch(args["model"]) 0172 # model = cv2.dnn.readNetFromCaffe(args["proto"], args["model"]) 0173 0174 0175 # Face alignment 0176 alignTool = openface.AlignDlib(args["dlibFacePredictor"]) 0177 0178 0179 # List training set and test set 0180 trainingSet = [] 0181 testSet = [] 0182 for i in range(0, nbObjects): 0183 pathToDataSetObj = os.path.sep.join([dataset, "s%d" %(i+1)]) 0184 trainingSetObj = [] 0185 testSetObj = [] 0186 for j in range(0, nbSamples): 0187 if(j < nbSamples*ratio): 0188 trainingSetObj.append(os.path.sep.join([pathToDataSetObj, "%d.pgm" %(j+1)])) 0189 else: 0190 testSetObj.append(os.path.sep.join([pathToDataSetObj, "%d.pgm" %(j+1)])) 0191 trainingSet.append(trainingSetObj.copy()) 0192 testSet.append(testSetObj.copy()) 0193 trainingSetObj.clear() 0194 testSetObj.clear() 0195 0196 0197 # Compute embedding of faces for training set 0198 trainingDict = {} 0199 print("Training...") 0200 for i in range(0, nbObjects): 0201 trainingVec = [] 0202 for imagePath in trainingSet[i]: 0203 # print(imagePath) 0204 trainingRes = process(imagePath, detector, alignTool, imgDimDefault, model) 0205 trainingVec.append(trainingRes.flatten()) 0206 trainingDict[i + 1] = trainingVec.copy() 0207 trainingVec.clear() 0208 print("trainingDict len: %d" %(len(trainingDict))) 0209 0210 0211 # Compute embedding of faces for test set 0212 testDict = {} 0213 print("Compute test...") 0214 for i in range(0, nbObjects): 0215 testVec = [] 0216 for imagePath in testSet[i]: 0217 testRes = process(imagePath, detector, alignTool, imgDimDefault, model) 0218 testVec.append(testRes.flatten()) 0219 testDict[i + 1] = testVec.copy() 0220 testVec.clear() 0221 print("testDict len %d" %(len(testVec))) 0222 0223 0224 # Testing 0225 testResult = {} 0226 threshold = 1.6 0227 print("Testing...") 0228 for i in range(0, len(testDict)): 0229 testResultVec = [] 0230 l = 0 0231 for test in testDict[i + 1]: 0232 min_dist = 1000000 0233 max_dist = -1 0234 label = 0 0235 closestImage = "" 0236 for j in range(0, len(trainingDict)): 0237 # dist = distanceToGroup(test, trainingDict[j + 1]) 0238 # if(dist < min_dist): 0239 # min_dist = dist 0240 # label = j + 1 0241 # if(min_dist > threshold): 0242 # label = 0 0243 for train in trainingDict[j + 1]: 0244 dist = cosineDistance(train, test) 0245 if(dist > max_dist): 0246 max_dist = dist 0247 label = j + 1 0248 # testResultVec.append([label, min_dist, testSet[i][l]]) 0249 testResultVec.append([label, max_dist, testSet[i][l]]) 0250 testResult[i + 1] = testResultVec.copy() 0251 testResultVec.clear() 0252 l += 1 0253 # print(testResult) 0254 0255 0256 # Evaluation 0257 nbTests = 0 0258 recognized = 0 0259 falsePositive = 0 0260 unknown = 0 0261 for i in range(0, len(testResult)): 0262 nbTests += len(testResult[i + 1]) 0263 for res in testResult[i + 1]: 0264 if(res[0] == 0): 0265 unknown += 1 0266 elif(res[0] != (i + 1)): 0267 falsePositive += 1 0268 print([i+1, res]) 0269 else: 0270 recognized += 1 0271 print("nbTests = %d" %(nbTests)) 0272 print("recognized = %d / %d (%.2f %%)" %(recognized, nbTests, recognized*100.0/nbTests)) 0273 print("falsePositive = %d / %d (%.2f %%)" %(falsePositive, nbTests, falsePositive*100.0/nbTests)) 0274 print("unknown = %d / %d (%.2f %%)" %(unknown, nbTests, unknown*100.0/nbTests)) 0275