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